Merge "Emit return-void-barrier when appropriate." into dalvik-dev
diff --git a/dexgen/src/com/android/dexgen/dex/code/ArrayData.java b/dexgen/src/com/android/dexgen/dex/code/ArrayData.java
new file mode 100644
index 0000000..d89a93f
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/ArrayData.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.SourcePosition;
+import com.android.dexgen.rop.cst.*;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+import java.util.ArrayList;
+
+/**
+ * Pseudo-instruction which holds fill array data.
+ */
+public final class ArrayData extends VariableSizeInsn {
+    /**
+     * {@code non-null;} address representing the instruction that uses this
+     * instance
+     */
+    private final CodeAddress user;
+
+    /** {@code non-null;} initial values to be filled into an array */
+    private final ArrayList<Constant> values;
+
+    /** non-null: type of constant that initializes the array */
+    private final Constant arrayType;
+
+    /** Width of the init value element */
+    private final int elemWidth;
+
+    /** Length of the init list */
+    private final int initLength;
+
+    /**
+     * Constructs an instance. The output address of this instance is initially
+     * unknown ({@code -1}).
+     *
+     * @param position {@code non-null;} source position
+     * @param user {@code non-null;} address representing the instruction that
+     * uses this instance
+     * @param values {@code non-null;} initial values to be filled into an array
+     */
+    public ArrayData(SourcePosition position, CodeAddress user,
+                     ArrayList<Constant> values,
+                     Constant arrayType) {
+        super(position, RegisterSpecList.EMPTY);
+
+        if (user == null) {
+            throw new NullPointerException("user == null");
+        }
+
+        if (values == null) {
+            throw new NullPointerException("values == null");
+        }
+
+        int sz = values.size();
+
+        if (sz <= 0) {
+            throw new IllegalArgumentException("Illegal number of init values");
+        }
+
+        this.arrayType = arrayType;
+
+        if (arrayType == CstType.BYTE_ARRAY ||
+                arrayType == CstType.BOOLEAN_ARRAY) {
+            elemWidth = 1;
+        } else if (arrayType == CstType.SHORT_ARRAY ||
+                arrayType == CstType.CHAR_ARRAY) {
+            elemWidth = 2;
+        } else if (arrayType == CstType.INT_ARRAY ||
+                arrayType == CstType.FLOAT_ARRAY) {
+            elemWidth = 4;
+        } else if (arrayType == CstType.LONG_ARRAY ||
+                arrayType == CstType.DOUBLE_ARRAY) {
+            elemWidth = 8;
+        } else {
+            throw new IllegalArgumentException("Unexpected constant type");
+        }
+        this.user = user;
+        this.values = values;
+        initLength = values.size();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        int sz = initLength;
+        // Note: the unit here is 16-bit
+        return 4 + ((sz * elemWidth) + 1) / 2;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out) {
+        int sz = values.size();
+
+        out.writeShort(0x300 | DalvOps.NOP);
+        out.writeShort(elemWidth);
+        out.writeInt(initLength);
+
+
+        // For speed reasons, replicate the for loop in each case
+        switch (elemWidth) {
+            case 1: {
+                for (int i = 0; i < sz; i++) {
+                    Constant cst = values.get(i);
+                    out.writeByte((byte) ((CstLiteral32) cst).getIntBits());
+                }
+                break;
+            }
+            case 2: {
+                for (int i = 0; i < sz; i++) {
+                    Constant cst = values.get(i);
+                    out.writeShort((short) ((CstLiteral32) cst).getIntBits());
+                }
+                break;
+            }
+            case 4: {
+                for (int i = 0; i < sz; i++) {
+                    Constant cst = values.get(i);
+                    out.writeInt(((CstLiteral32) cst).getIntBits());
+                }
+                break;
+            }
+            case 8: {
+                for (int i = 0; i < sz; i++) {
+                    Constant cst = values.get(i);
+                    out.writeLong(((CstLiteral64) cst).getLongBits());
+                }
+                break;
+            }
+            default:
+                break;
+        }
+
+        // Pad one byte to make the size of data table multiples of 16-bits
+        if (elemWidth == 1 && (sz % 2 != 0)) {
+            out.writeByte(0x00);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withRegisters(RegisterSpecList registers) {
+        return new ArrayData(getPosition(), user, values, arrayType);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String argString() {
+        StringBuffer sb = new StringBuffer(100);
+
+        int sz = values.size();
+        for (int i = 0; i < sz; i++) {
+            sb.append("\n    ");
+            sb.append(i);
+            sb.append(": ");
+            sb.append(values.get(i).toHuman());
+        }
+
+        return sb.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String listingString0(boolean noteIndices) {
+        int baseAddress = user.getAddress();
+        StringBuffer sb = new StringBuffer(100);
+        int sz = values.size();
+
+        sb.append("array-data // for fill-array-data @ ");
+        sb.append(Hex.u2(baseAddress));
+
+        for (int i = 0; i < sz; i++) {
+            sb.append("\n  ");
+            sb.append(i);
+            sb.append(": ");
+            sb.append(values.get(i).toHuman());
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/BlockAddresses.java b/dexgen/src/com/android/dexgen/dex/code/BlockAddresses.java
new file mode 100644
index 0000000..fa8096e
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/BlockAddresses.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.BasicBlock;
+import com.android.dexgen.rop.code.BasicBlockList;
+import com.android.dexgen.rop.code.Insn;
+import com.android.dexgen.rop.code.RopMethod;
+import com.android.dexgen.rop.code.SourcePosition;
+
+/**
+ * Container for the set of {@link CodeAddress} instances associated with
+ * the blocks of a particular method. Each block has a corresponding
+ * start address, end address, and last instruction address.
+ */
+public final class BlockAddresses {
+    /** {@code non-null;} array containing addresses for the start of each basic
+     * block (indexed by basic block label) */
+    private final CodeAddress[] starts;
+
+    /** {@code non-null;} array containing addresses for the final instruction
+     * of each basic block (indexed by basic block label) */
+    private final CodeAddress[] lasts;
+
+    /** {@code non-null;} array containing addresses for the end (just past the
+     * final instruction) of each basic block (indexed by basic block
+     * label) */
+    private final CodeAddress[] ends;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param method {@code non-null;} the method to have block addresses for
+     */
+    public BlockAddresses(RopMethod method) {
+        BasicBlockList blocks = method.getBlocks();
+        int maxLabel = blocks.getMaxLabel();
+
+        this.starts = new CodeAddress[maxLabel];
+        this.lasts = new CodeAddress[maxLabel];
+        this.ends = new CodeAddress[maxLabel];
+
+        setupArrays(method);
+    }
+
+    /**
+     * Gets the instance for the start of the given block.
+     *
+     * @param block {@code non-null;} the block in question
+     * @return {@code non-null;} the appropriate instance
+     */
+    public CodeAddress getStart(BasicBlock block) {
+        return starts[block.getLabel()];
+    }
+
+    /**
+     * Gets the instance for the start of the block with the given label.
+     *
+     * @param label {@code non-null;} the label of the block in question
+     * @return {@code non-null;} the appropriate instance
+     */
+    public CodeAddress getStart(int label) {
+        return starts[label];
+    }
+
+    /**
+     * Gets the instance for the final instruction of the given block.
+     *
+     * @param block {@code non-null;} the block in question
+     * @return {@code non-null;} the appropriate instance
+     */
+    public CodeAddress getLast(BasicBlock block) {
+        return lasts[block.getLabel()];
+    }
+
+    /**
+     * Gets the instance for the final instruction of the block with
+     * the given label.
+     *
+     * @param label {@code non-null;} the label of the block in question
+     * @return {@code non-null;} the appropriate instance
+     */
+    public CodeAddress getLast(int label) {
+        return lasts[label];
+    }
+
+    /**
+     * Gets the instance for the end (address after the final instruction)
+     * of the given block.
+     *
+     * @param block {@code non-null;} the block in question
+     * @return {@code non-null;} the appropriate instance
+     */
+    public CodeAddress getEnd(BasicBlock block) {
+        return ends[block.getLabel()];
+    }
+
+    /**
+     * Gets the instance for the end (address after the final instruction)
+     * of the block with the given label.
+     *
+     * @param label {@code non-null;} the label of the block in question
+     * @return {@code non-null;} the appropriate instance
+     */
+    public CodeAddress getEnd(int label) {
+        return ends[label];
+    }
+
+    /**
+     * Sets up the address arrays.
+     */
+    private void setupArrays(RopMethod method) {
+        BasicBlockList blocks = method.getBlocks();
+        int sz = blocks.size();
+
+        for (int i = 0; i < sz; i++) {
+            BasicBlock one = blocks.get(i);
+            int label = one.getLabel();
+            Insn insn = one.getInsns().get(0);
+
+            starts[label] = new CodeAddress(insn.getPosition());
+
+            SourcePosition pos = one.getLastInsn().getPosition();
+
+            lasts[label] = new CodeAddress(pos);
+            ends[label] = new CodeAddress(pos);
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/CatchBuilder.java b/dexgen/src/com/android/dexgen/dex/code/CatchBuilder.java
new file mode 100644
index 0000000..adb119a
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/CatchBuilder.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.type.Type;
+
+import java.util.HashSet;
+
+/**
+ * Interface for the construction of {@link CatchTable} instances.
+ */
+public interface CatchBuilder {
+    /**
+     * Builds and returns the catch table for this instance.
+     *
+     * @return {@code non-null;} the constructed table
+     */
+    public CatchTable build();
+
+    /**
+     * Gets whether this instance has any catches at all (either typed
+     * or catch-all).
+     *
+     * @return whether this instance has any catches at all
+     */
+    public boolean hasAnyCatches();
+
+    /**
+     * Gets the set of catch types associated with this instance.
+     *
+     * @return {@code non-null;} the set of catch types
+     */
+    public HashSet<Type> getCatchTypes();
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/CatchHandlerList.java b/dexgen/src/com/android/dexgen/dex/code/CatchHandlerList.java
new file mode 100644
index 0000000..54200ed
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/CatchHandlerList.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.util.FixedSizeList;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Ordered list of (exception type, handler address) entries.
+ */
+public final class CatchHandlerList extends FixedSizeList
+        implements Comparable<CatchHandlerList> {
+    /** {@code non-null;} empty instance */
+    public static final CatchHandlerList EMPTY = new CatchHandlerList(0);
+
+    /**
+     * Constructs an instance. All indices initially contain {@code null}.
+     *
+     * @param size {@code >= 0;} the size of the list
+     */
+    public CatchHandlerList(int size) {
+        super(size);
+    }
+
+    /**
+     * Gets the element at the given index. It is an error to call
+     * this with the index for an element which was never set; if you
+     * do that, this will throw {@code NullPointerException}.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @return {@code non-null;} element at that index
+     */
+    public Entry get(int n) {
+        return (Entry) get0(n);
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return toHuman("", "");
+    }
+
+    /**
+     * Get the human form of this instance, prefixed on each line
+     * with the string.
+     *
+     * @param prefix {@code non-null;} the prefix for every line
+     * @param header {@code non-null;} the header for the first line (after the
+     * first prefix)
+     * @return {@code non-null;} the human form
+     */
+    public String toHuman(String prefix, String header) {
+        StringBuilder sb = new StringBuilder(100);
+        int size = size();
+
+        sb.append(prefix);
+        sb.append(header);
+        sb.append("catch ");
+
+        for (int i = 0; i < size; i++) {
+            Entry entry = get(i);
+
+            if (i != 0) {
+                sb.append(",\n");
+                sb.append(prefix);
+                sb.append("  ");
+            }
+
+            if ((i == (size - 1)) && catchesAll()) {
+                sb.append("<any>");
+            } else {
+                sb.append(entry.getExceptionType().toHuman());
+            }
+
+            sb.append(" -> ");
+            sb.append(Hex.u2or4(entry.getHandler()));
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Returns whether or not this instance ends with a "catch-all"
+     * handler.
+     *
+     * @return {@code true} if this instance ends with a "catch-all"
+     * handler or {@code false} if not
+     */
+    public boolean catchesAll() {
+        int size = size();
+
+        if (size == 0) {
+            return false;
+        }
+
+        Entry last = get(size - 1);
+        return last.getExceptionType().equals(CstType.OBJECT);
+    }
+
+    /**
+     * Sets the entry at the given index.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @param exceptionType {@code non-null;} type of exception handled
+     * @param handler {@code >= 0;} exception handler address
+     */
+    public void set(int n, CstType exceptionType, int handler) {
+        set0(n, new Entry(exceptionType, handler));
+    }
+
+    /**
+     * Sets the entry at the given index.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @param entry {@code non-null;} the entry to set at {@code n}
+     */
+    public void set(int n, Entry entry) {
+        set0(n, entry);
+    }
+
+    /** {@inheritDoc} */
+    public int compareTo(CatchHandlerList other) {
+        if (this == other) {
+            // Easy out.
+            return 0;
+        }
+
+        int thisSize = size();
+        int otherSize = other.size();
+        int checkSize = Math.min(thisSize, otherSize);
+
+        for (int i = 0; i < checkSize; i++) {
+            Entry thisEntry = get(i);
+            Entry otherEntry = other.get(i);
+            int compare = thisEntry.compareTo(otherEntry);
+            if (compare != 0) {
+                return compare;
+            }
+        }
+
+        if (thisSize < otherSize) {
+            return -1;
+        } else if (thisSize > otherSize) {
+            return 1;
+        }
+
+        return 0;
+    }
+
+    /**
+     * Entry in the list.
+     */
+    public static class Entry implements Comparable<Entry> {
+        /** {@code non-null;} type of exception handled */
+        private final CstType exceptionType;
+
+        /** {@code >= 0;} exception handler address */
+        private final int handler;
+
+        /**
+         * Constructs an instance.
+         *
+         * @param exceptionType {@code non-null;} type of exception handled
+         * @param handler {@code >= 0;} exception handler address
+         */
+        public Entry(CstType exceptionType, int handler) {
+            if (handler < 0) {
+                throw new IllegalArgumentException("handler < 0");
+            }
+
+            if (exceptionType == null) {
+                throw new NullPointerException("exceptionType == null");
+            }
+
+            this.handler = handler;
+            this.exceptionType = exceptionType;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int hashCode() {
+            return (handler * 31) + exceptionType.hashCode();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public boolean equals(Object other) {
+            if (other instanceof Entry) {
+                return (compareTo((Entry) other) == 0);
+            }
+
+            return false;
+        }
+
+        /** {@inheritDoc} */
+        public int compareTo(Entry other) {
+            if (handler < other.handler) {
+                return -1;
+            } else if (handler > other.handler) {
+                return 1;
+            }
+
+            return exceptionType.compareTo(other.exceptionType);
+        }
+
+        /**
+         * Gets the exception type handled.
+         *
+         * @return {@code non-null;} the exception type
+         */
+        public CstType getExceptionType() {
+            return exceptionType;
+        }
+
+        /**
+         * Gets the handler address.
+         *
+         * @return {@code >= 0;} the handler address
+         */
+        public int getHandler() {
+            return handler;
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/CatchTable.java b/dexgen/src/com/android/dexgen/dex/code/CatchTable.java
new file mode 100644
index 0000000..4de0da0
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/CatchTable.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.util.FixedSizeList;
+
+/**
+ * Table of catch entries. Each entry includes a range of code
+ * addresses for which it is valid and an associated {@link
+ * CatchHandlerList}.
+ */
+public final class CatchTable extends FixedSizeList
+        implements Comparable<CatchTable> {
+    /** {@code non-null;} empty instance */
+    public static final CatchTable EMPTY = new CatchTable(0);
+
+    /**
+     * Constructs an instance. All indices initially contain {@code null}.
+     *
+     * @param size {@code >= 0;} the size of the table
+     */
+    public CatchTable(int size) {
+        super(size);
+    }
+
+    /**
+     * Gets the element at the given index. It is an error to call
+     * this with the index for an element which was never set; if you
+     * do that, this will throw {@code NullPointerException}.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @return {@code non-null;} element at that index
+     */
+    public Entry get(int n) {
+        return (Entry) get0(n);
+    }
+
+    /**
+     * Sets the entry at the given index.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @param entry {@code non-null;} the entry to set at {@code n}
+     */
+    public void set(int n, Entry entry) {
+        set0(n, entry);
+    }
+
+    /** {@inheritDoc} */
+    public int compareTo(CatchTable other) {
+        if (this == other) {
+            // Easy out.
+            return 0;
+        }
+
+        int thisSize = size();
+        int otherSize = other.size();
+        int checkSize = Math.min(thisSize, otherSize);
+
+        for (int i = 0; i < checkSize; i++) {
+            Entry thisEntry = get(i);
+            Entry otherEntry = other.get(i);
+            int compare = thisEntry.compareTo(otherEntry);
+            if (compare != 0) {
+                return compare;
+            }
+        }
+
+        if (thisSize < otherSize) {
+            return -1;
+        } else if (thisSize > otherSize) {
+            return 1;
+        }
+
+        return 0;
+    }
+
+    /**
+     * Entry in a catch list.
+     */
+    public static class Entry implements Comparable<Entry> {
+        /** {@code >= 0;} start address */
+        private final int start;
+
+        /** {@code > start;} end address (exclusive) */
+        private final int end;
+
+        /** {@code non-null;} list of catch handlers */
+        private final CatchHandlerList handlers;
+
+        /**
+         * Constructs an instance.
+         *
+         * @param start {@code >= 0;} start address
+         * @param end {@code > start;} end address (exclusive)
+         * @param handlers {@code non-null;} list of catch handlers
+         */
+        public Entry(int start, int end, CatchHandlerList handlers) {
+            if (start < 0) {
+                throw new IllegalArgumentException("start < 0");
+            }
+
+            if (end <= start) {
+                throw new IllegalArgumentException("end <= start");
+            }
+
+            if (handlers.isMutable()) {
+                throw new IllegalArgumentException("handlers.isMutable()");
+            }
+
+            this.start = start;
+            this.end = end;
+            this.handlers = handlers;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int hashCode() {
+            int hash = (start * 31) + end;
+            hash = (hash * 31) + handlers.hashCode();
+            return hash;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public boolean equals(Object other) {
+            if (other instanceof Entry) {
+                return (compareTo((Entry) other) == 0);
+            }
+
+            return false;
+        }
+
+        /** {@inheritDoc} */
+        public int compareTo(Entry other) {
+            if (start < other.start) {
+                return -1;
+            } else if (start > other.start) {
+                return 1;
+            }
+
+            if (end < other.end) {
+                return -1;
+            } else if (end > other.end) {
+                return 1;
+            }
+
+            return handlers.compareTo(other.handlers);
+        }
+
+        /**
+         * Gets the start address.
+         *
+         * @return {@code >= 0;} the start address
+         */
+        public int getStart() {
+            return start;
+        }
+
+        /**
+         * Gets the end address (exclusive).
+         *
+         * @return {@code > start;} the end address (exclusive)
+         */
+        public int getEnd() {
+            return end;
+        }
+
+        /**
+         * Gets the handlers.
+         *
+         * @return {@code non-null;} the handlers
+         */
+        public CatchHandlerList getHandlers() {
+            return handlers;
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/CodeAddress.java b/dexgen/src/com/android/dexgen/dex/code/CodeAddress.java
new file mode 100644
index 0000000..b9600ee
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/CodeAddress.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.SourcePosition;
+
+/**
+ * Pseudo-instruction which is used to track an address within a code
+ * array. Instances are used for such things as branch targets and
+ * exception handler ranges. Its code size is zero, and so instances
+ * do not in general directly wind up in any output (either
+ * human-oriented or binary file).
+ */
+public final class CodeAddress extends ZeroSizeInsn {
+    /**
+     * Constructs an instance. The output address of this instance is initially
+     * unknown ({@code -1}).
+     *
+     * @param position {@code non-null;} source position
+     */
+    public CodeAddress(SourcePosition position) {
+        super(position);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final DalvInsn withRegisters(RegisterSpecList registers) {
+        return new CodeAddress(getPosition());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String argString() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String listingString0(boolean noteIndices) {
+        return "code-address";
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/CstInsn.java b/dexgen/src/com/android/dexgen/dex/code/CstInsn.java
new file mode 100644
index 0000000..901266b
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/CstInsn.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.SourcePosition;
+import com.android.dexgen.rop.cst.Constant;
+
+/**
+ * Instruction which has a single constant argument in addition
+ * to all the normal instruction information.
+ */
+public final class CstInsn extends FixedSizeInsn {
+    /** {@code non-null;} the constant argument for this instruction */
+    private final Constant constant;
+
+    /**
+     * {@code >= -1;} the constant pool index for {@link #constant}, or
+     * {@code -1} if not yet set
+     */
+    private int index;
+
+    /**
+     * {@code >= -1;} the constant pool index for the class reference in
+     * {@link #constant} if any, or {@code -1} if not yet set
+     */
+    private int classIndex;
+
+    /**
+     * Constructs an instance. The output address of this instance is
+     * initially unknown ({@code -1}) as is the constant pool index.
+     *
+     * @param opcode the opcode; one of the constants from {@link Dops}
+     * @param position {@code non-null;} source position
+     * @param registers {@code non-null;} register list, including a
+     * result register if appropriate (that is, registers may be either
+     * ins or outs)
+     * @param constant {@code non-null;} constant argument
+     */
+    public CstInsn(Dop opcode, SourcePosition position,
+                   RegisterSpecList registers, Constant constant) {
+        super(opcode, position, registers);
+
+        if (constant == null) {
+            throw new NullPointerException("constant == null");
+        }
+
+        this.constant = constant;
+        this.index = -1;
+        this.classIndex = -1;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withOpcode(Dop opcode) {
+        CstInsn result =
+            new CstInsn(opcode, getPosition(), getRegisters(), constant);
+
+        if (index >= 0) {
+            result.setIndex(index);
+        }
+
+        if (classIndex >= 0) {
+            result.setClassIndex(classIndex);
+        }
+
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withRegisters(RegisterSpecList registers) {
+        CstInsn result =
+            new CstInsn(getOpcode(), getPosition(), registers, constant);
+
+        if (index >= 0) {
+            result.setIndex(index);
+        }
+
+        if (classIndex >= 0) {
+            result.setClassIndex(classIndex);
+        }
+
+        return result;
+    }
+
+    /**
+     * Gets the constant argument.
+     *
+     * @return {@code non-null;} the constant argument
+     */
+    public Constant getConstant() {
+        return constant;
+    }
+
+    /**
+     * Gets the constant's index. It is only valid to call this after
+     * {@link #setIndex} has been called.
+     *
+     * @return {@code >= 0;} the constant pool index
+     */
+    public int getIndex() {
+        if (index < 0) {
+            throw new RuntimeException("index not yet set for " + constant);
+        }
+
+        return index;
+    }
+
+    /**
+     * Returns whether the constant's index has been set for this instance.
+     *
+     * @see #setIndex
+     *
+     * @return {@code true} iff the index has been set
+     */
+    public boolean hasIndex() {
+        return (index >= 0);
+    }
+
+    /**
+     * Sets the constant's index. It is only valid to call this method once
+     * per instance.
+     *
+     * @param index {@code >= 0;} the constant pool index
+     */
+    public void setIndex(int index) {
+        if (index < 0) {
+            throw new IllegalArgumentException("index < 0");
+        }
+
+        if (this.index >= 0) {
+            throw new RuntimeException("index already set");
+        }
+
+        this.index = index;
+    }
+
+    /**
+     * Gets the constant's class index. It is only valid to call this after
+     * {@link #setClassIndex} has been called.
+     *
+     * @return {@code >= 0;} the constant's class's constant pool index
+     */
+    public int getClassIndex() {
+        if (classIndex < 0) {
+            throw new RuntimeException("class index not yet set");
+        }
+
+        return classIndex;
+    }
+
+    /**
+     * Returns whether the constant's class index has been set for this
+     * instance.
+     *
+     * @see #setClassIndex
+     *
+     * @return {@code true} iff the index has been set
+     */
+    public boolean hasClassIndex() {
+        return (classIndex >= 0);
+    }
+
+    /**
+     * Sets the constant's class index. This is the constant pool index
+     * for the class referred to by this instance's constant. Only
+     * reference constants have a class, so it is only on instances
+     * with reference constants that this method should ever be
+     * called. It is only valid to call this method once per instance.
+     *
+     * @param index {@code >= 0;} the constant's class's constant pool index
+     */
+    public void setClassIndex(int index) {
+        if (index < 0) {
+            throw new IllegalArgumentException("index < 0");
+        }
+
+        if (this.classIndex >= 0) {
+            throw new RuntimeException("class index already set");
+        }
+
+        this.classIndex = index;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String argString() {
+        return constant.toHuman();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/DalvCode.java b/dexgen/src/com/android/dexgen/dex/code/DalvCode.java
new file mode 100644
index 0000000..2df49ed
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/DalvCode.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.type.Type;
+
+import java.util.HashSet;
+
+/**
+ * Container for all the pieces of a concrete method. Each instance
+ * corresponds to a {@code code} structure in a {@code .dex} file.
+ */
+public final class DalvCode {
+    /**
+     * how much position info to preserve; one of the static
+     * constants in {@link PositionList}
+     */
+    private final int positionInfo;
+
+    /**
+     * {@code null-ok;} the instruction list, ready for final processing;
+     * nulled out in {@link #finishProcessingIfNecessary}
+     */
+    private OutputFinisher unprocessedInsns;
+
+    /**
+     * {@code non-null;} unprocessed catch table;
+     * nulled out in {@link #finishProcessingIfNecessary}
+     */
+    private CatchBuilder unprocessedCatches;
+
+    /**
+     * {@code null-ok;} catch table; set in
+     * {@link #finishProcessingIfNecessary}
+     */
+    private CatchTable catches;
+
+    /**
+     * {@code null-ok;} source positions list; set in
+     * {@link #finishProcessingIfNecessary}
+     */
+    private PositionList positions;
+
+    /**
+     * {@code null-ok;} local variable list; set in
+     * {@link #finishProcessingIfNecessary}
+     */
+    private LocalList locals;
+
+    /**
+     * {@code null-ok;} the processed instruction list; set in
+     * {@link #finishProcessingIfNecessary}
+     */
+    private DalvInsnList insns;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param positionInfo how much position info to preserve; one of the
+     * static constants in {@link PositionList}
+     * @param unprocessedInsns {@code non-null;} the instruction list, ready
+     * for final processing
+     * @param unprocessedCatches {@code non-null;} unprocessed catch
+     * (exception handler) table
+     */
+    public DalvCode(int positionInfo, OutputFinisher unprocessedInsns,
+            CatchBuilder unprocessedCatches) {
+        if (unprocessedInsns == null) {
+            throw new NullPointerException("unprocessedInsns == null");
+        }
+
+        if (unprocessedCatches == null) {
+            throw new NullPointerException("unprocessedCatches == null");
+        }
+
+        this.positionInfo = positionInfo;
+        this.unprocessedInsns = unprocessedInsns;
+        this.unprocessedCatches = unprocessedCatches;
+        this.catches = null;
+        this.positions = null;
+        this.locals = null;
+        this.insns = null;
+    }
+
+    /**
+     * Finish up processing of the method.
+     */
+    private void finishProcessingIfNecessary() {
+        if (insns != null) {
+            return;
+        }
+
+        insns = unprocessedInsns.finishProcessingAndGetList();
+        positions = PositionList.make(insns, positionInfo);
+        locals = LocalList.make(insns);
+        catches = unprocessedCatches.build();
+
+        // Let them be gc'ed.
+        unprocessedInsns = null;
+        unprocessedCatches = null;
+    }
+
+    /**
+     * Assign indices in all instructions that need them, using the
+     * given callback to perform lookups. This must be called before
+     * {@link #getInsns}.
+     *
+     * @param callback {@code non-null;} callback object
+     */
+    public void assignIndices(AssignIndicesCallback callback) {
+        unprocessedInsns.assignIndices(callback);
+    }
+
+    /**
+     * Gets whether this instance has any position data to represent.
+     *
+     * @return {@code true} iff this instance has any position
+     * data to represent
+     */
+    public boolean hasPositions() {
+        return (positionInfo != PositionList.NONE)
+            && unprocessedInsns.hasAnyPositionInfo();
+    }
+
+    /**
+     * Gets whether this instance has any local variable data to represent.
+     *
+     * @return {@code true} iff this instance has any local variable
+     * data to represent
+     */
+    public boolean hasLocals() {
+        return unprocessedInsns.hasAnyLocalInfo();
+    }
+
+    /**
+     * Gets whether this instance has any catches at all (either typed
+     * or catch-all).
+     *
+     * @return whether this instance has any catches at all
+     */
+    public boolean hasAnyCatches() {
+        return unprocessedCatches.hasAnyCatches();
+    }
+
+    /**
+     * Gets the set of catch types handled anywhere in the code.
+     *
+     * @return {@code non-null;} the set of catch types
+     */
+    public HashSet<Type> getCatchTypes() {
+        return unprocessedCatches.getCatchTypes();
+    }
+
+    /**
+     * Gets the set of all constants referred to by instructions in
+     * the code.
+     *
+     * @return {@code non-null;} the set of constants
+     */
+    public HashSet<Constant> getInsnConstants() {
+        return unprocessedInsns.getAllConstants();
+    }
+
+    /**
+     * Gets the list of instructions.
+     *
+     * @return {@code non-null;} the instruction list
+     */
+    public DalvInsnList getInsns() {
+        finishProcessingIfNecessary();
+        return insns;
+    }
+
+    /**
+     * Gets the catch (exception handler) table.
+     *
+     * @return {@code non-null;} the catch table
+     */
+    public CatchTable getCatches() {
+        finishProcessingIfNecessary();
+        return catches;
+    }
+
+    /**
+     * Gets the source positions list.
+     *
+     * @return {@code non-null;} the source positions list
+     */
+    public PositionList getPositions() {
+        finishProcessingIfNecessary();
+        return positions;
+    }
+
+    /**
+     * Gets the source positions list.
+     *
+     * @return {@code non-null;} the source positions list
+     */
+    public LocalList getLocals() {
+        finishProcessingIfNecessary();
+        return locals;
+    }
+
+    /**
+     * Class used as a callback for {@link #assignIndices}.
+     */
+    public static interface AssignIndicesCallback {
+        /**
+         * Gets the index for the given constant.
+         *
+         * @param cst {@code non-null;} the constant
+         * @return {@code >= -1;} the index or {@code -1} if the constant
+         * shouldn't actually be reified with an index
+         */
+        public int getIndex(Constant cst);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/DalvInsn.java b/dexgen/src/com/android/dexgen/dex/code/DalvInsn.java
new file mode 100644
index 0000000..95b5feb
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/DalvInsn.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.SourcePosition;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.TwoColumnOutput;
+
+/**
+ * Base class for Dalvik instructions.
+ */
+public abstract class DalvInsn {
+    /**
+     * the actual output address of this instance, if known, or
+     * {@code -1} if not
+     */
+    private int address;
+
+    /** the opcode; one of the constants from {@link Dops} */
+    private final Dop opcode;
+
+    /** {@code non-null;} source position */
+    private final SourcePosition position;
+
+    /** {@code non-null;} list of register arguments */
+    private final RegisterSpecList registers;
+
+    /**
+     * Makes a move instruction, appropriate and ideal for the given arguments.
+     *
+     * @param position {@code non-null;} source position information
+     * @param dest {@code non-null;} destination register
+     * @param src {@code non-null;} source register
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public static SimpleInsn makeMove(SourcePosition position,
+            RegisterSpec dest, RegisterSpec src) {
+        boolean category1 = dest.getCategory() == 1;
+        boolean reference = dest.getType().isReference();
+        int destReg = dest.getReg();
+        int srcReg = src.getReg();
+        Dop opcode;
+
+        if ((srcReg | destReg) < 16) {
+            opcode = reference ? Dops.MOVE_OBJECT :
+                (category1 ? Dops.MOVE : Dops.MOVE_WIDE);
+        } else if (destReg < 256) {
+            opcode = reference ? Dops.MOVE_OBJECT_FROM16 :
+                (category1 ? Dops.MOVE_FROM16 : Dops.MOVE_WIDE_FROM16);
+        } else {
+            opcode = reference ? Dops.MOVE_OBJECT_16 :
+                (category1 ? Dops.MOVE_16 : Dops.MOVE_WIDE_16);
+        }
+
+        return new SimpleInsn(opcode, position,
+                              RegisterSpecList.make(dest, src));
+    }
+
+    /**
+     * Constructs an instance. The output address of this instance is initially
+     * unknown ({@code -1}).
+     *
+     * <p><b>Note:</b> In the unlikely event that an instruction takes
+     * absolutely no registers (e.g., a {@code nop} or a
+     * no-argument no-result static method call), then the given
+     * register list may be passed as {@link
+     * RegisterSpecList#EMPTY}.</p>
+     *
+     * @param opcode the opcode; one of the constants from {@link Dops}
+     * @param position {@code non-null;} source position
+     * @param registers {@code non-null;} register list, including a
+     * result register if appropriate (that is, registers may be either
+     * ins and outs)
+     */
+    public DalvInsn(Dop opcode, SourcePosition position,
+                    RegisterSpecList registers) {
+        if (opcode == null) {
+            throw new NullPointerException("opcode == null");
+        }
+
+        if (position == null) {
+            throw new NullPointerException("position == null");
+        }
+
+        if (registers == null) {
+            throw new NullPointerException("registers == null");
+        }
+
+        this.address = -1;
+        this.opcode = opcode;
+        this.position = position;
+        this.registers = registers;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final String toString() {
+        StringBuffer sb = new StringBuffer(100);
+
+        sb.append(identifierString());
+        sb.append(' ');
+        sb.append(position);
+
+        sb.append(": ");
+        sb.append(opcode.getName());
+
+        boolean needComma = false;
+        if (registers.size() != 0) {
+            sb.append(registers.toHuman(" ", ", ", null));
+            needComma = true;
+        }
+
+        String extra = argString();
+        if (extra != null) {
+            if (needComma) {
+                sb.append(',');
+            }
+            sb.append(' ');
+            sb.append(extra);
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Gets whether the address of this instruction is known.
+     *
+     * @see #getAddress
+     * @see #setAddress
+     */
+    public final boolean hasAddress() {
+        return (address >= 0);
+    }
+
+    /**
+     * Gets the output address of this instruction, if it is known. This throws
+     * a {@code RuntimeException} if it has not yet been set.
+     *
+     * @see #setAddress
+     *
+     * @return {@code >= 0;} the output address
+     */
+    public final int getAddress() {
+        if (address < 0) {
+            throw new RuntimeException("address not yet known");
+        }
+
+        return address;
+    }
+
+    /**
+     * Gets the opcode.
+     *
+     * @return {@code non-null;} the opcode
+     */
+    public final Dop getOpcode() {
+        return opcode;
+    }
+
+    /**
+     * Gets the source position.
+     *
+     * @return {@code non-null;} the source position
+     */
+    public final SourcePosition getPosition() {
+        return position;
+    }
+
+    /**
+     * Gets the register list for this instruction.
+     *
+     * @return {@code non-null;} the registers
+     */
+    public final RegisterSpecList getRegisters() {
+        return registers;
+    }
+
+    /**
+     * Returns whether this instance's opcode uses a result register.
+     * This method is a convenient shorthand for
+     * {@code getOpcode().hasResult()}.
+     *
+     * @return {@code true} iff this opcode uses a result register
+     */
+    public final boolean hasResult() {
+        return opcode.hasResult();
+    }
+
+    /**
+     * Gets the minimum distinct registers required for this instruction.
+     * This assumes that the result (if any) can share registers with the
+     * sources (if any), that each source register is unique, and that
+     * (to be explicit here) category-2 values take up two consecutive
+     * registers.
+     *
+     * @return {@code >= 0;} the minimum distinct register requirement
+     */
+    public final int getMinimumRegisterRequirement() {
+        boolean hasResult = hasResult();
+        int regSz = registers.size();
+        int resultRequirement = hasResult ? registers.get(0).getCategory() : 0;
+        int sourceRequirement = 0;
+
+        for (int i = hasResult ? 1 : 0; i < regSz; i++) {
+            sourceRequirement += registers.get(i).getCategory();
+        }
+
+        return Math.max(sourceRequirement, resultRequirement);
+    }
+
+    /**
+     * Gets the instruction prefix required, if any, to use in a high
+     * register transformed version of this instance.
+     *
+     * @see #hrVersion
+     *
+     * @return {@code null-ok;} the prefix, if any
+     */
+    public DalvInsn hrPrefix() {
+        RegisterSpecList regs = registers;
+        int sz = regs.size();
+
+        if (hasResult()) {
+            if (sz == 1) {
+                return null;
+            }
+            regs = regs.withoutFirst();
+        } else if (sz == 0) {
+            return null;
+        }
+
+        return new HighRegisterPrefix(position, regs);
+    }
+
+    /**
+     * Gets the instruction suffix required, if any, to use in a high
+     * register transformed version of this instance.
+     *
+     * @see #hrVersion
+     *
+     * @return {@code null-ok;} the suffix, if any
+     */
+    public DalvInsn hrSuffix() {
+        if (hasResult()) {
+            RegisterSpec r = registers.get(0);
+            return makeMove(position, r, r.withReg(0));
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Gets the instruction that is equivalent to this one, except that
+     * uses sequential registers starting at {@code 0} (storing
+     * the result, if any, in register {@code 0} as well). The
+     * sequence of instructions from {@link #hrPrefix} and {@link
+     * #hrSuffix} (if non-null) surrounding the result of a call to
+     * this method are the high register transformation of this
+     * instance, and it is guaranteed that the number of low registers
+     * used will be the number returned by {@link
+     * #getMinimumRegisterRequirement}.
+     *
+     * @return {@code non-null;} the replacement
+     */
+    public DalvInsn hrVersion() {
+        RegisterSpecList regs =
+            registers.withSequentialRegisters(0, hasResult());
+        return withRegisters(regs);
+    }
+
+    /**
+     * Gets the short identifier for this instruction. This is its
+     * address, if assigned, or its identity hashcode if not.
+     *
+     * @return {@code non-null;} the identifier
+     */
+    public final String identifierString() {
+        if (address != -1) {
+            return String.format("%04x", address);
+        }
+
+        return Hex.u4(System.identityHashCode(this));
+    }
+
+    /**
+     * Returns the string form of this instance suitable for inclusion in
+     * a human-oriented listing dump. This method will return {@code null}
+     * if this instance should not appear in a listing.
+     *
+     * @param prefix {@code non-null;} prefix before the address; each follow-on
+     * line will be indented to match as well
+     * @param width {@code >= 0;} the width of the output or {@code 0} for
+     * unlimited width
+     * @param noteIndices whether to include an explicit notation of
+     * constant pool indices
+     * @return {@code null-ok;} the string form or {@code null} if this
+     * instance should not appear in a listing
+     */
+    public final String listingString(String prefix, int width,
+            boolean noteIndices) {
+        String insnPerSe = listingString0(noteIndices);
+
+        if (insnPerSe == null) {
+            return null;
+        }
+
+        String addr = prefix + identifierString() + ": ";
+        int w1 = addr.length();
+        int w2 = (width == 0) ? insnPerSe.length() : (width - w1);
+
+        return TwoColumnOutput.toString(addr, w1, "", insnPerSe, w2);
+    }
+
+    /**
+     * Sets the output address.
+     *
+     * @param address {@code >= 0;} the output address
+     */
+    public final void setAddress(int address) {
+        if (address < 0) {
+            throw new IllegalArgumentException("address < 0");
+        }
+
+        this.address = address;
+    }
+
+    /**
+     * Gets the address immediately after this instance. This is only
+     * calculable if this instance's address is known, and it is equal
+     * to the address plus the length of the instruction format of this
+     * instance's opcode.
+     *
+     * @return {@code >= 0;} the next address
+     */
+    public final int getNextAddress() {
+        return getAddress() + codeSize();
+    }
+
+    /**
+     * Gets the size of this instruction, in 16-bit code units.
+     *
+     * @return {@code >= 0;} the code size of this instruction
+     */
+    public abstract int codeSize();
+
+    /**
+     * Writes this instance to the given output. This method should
+     * never annotate the output.
+     *
+     * @param out {@code non-null;} where to write to
+     */
+    public abstract void writeTo(AnnotatedOutput out);
+
+    /**
+     * Returns an instance that is just like this one, except that its
+     * opcode is replaced by the one given, and its address is reset.
+     *
+     * @param opcode {@code non-null;} the new opcode
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public abstract DalvInsn withOpcode(Dop opcode);
+
+    /**
+     * Returns an instance that is just like this one, except that all
+     * register references have been offset by the given delta, and its
+     * address is reset.
+     *
+     * @param delta the amount to offset register references by
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public abstract DalvInsn withRegisterOffset(int delta);
+
+    /**
+     * Returns an instance that is just like this one, except that the
+     * register list is replaced by the given one, and its address is
+     * reset.
+     *
+     * @param registers {@code non-null;} new register list
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public abstract DalvInsn withRegisters(RegisterSpecList registers);
+
+    /**
+     * Gets the string form for any arguments to this instance. Subclasses
+     * must override this.
+     *
+     * @return {@code null-ok;} the string version of any arguments or
+     * {@code null} if there are none
+     */
+    protected abstract String argString();
+
+    /**
+     * Helper for {@link #listingString}, which returns the string
+     * form of this instance suitable for inclusion in a
+     * human-oriented listing dump, not including the instruction
+     * address and without respect for any output formatting. This
+     * method should return {@code null} if this instance should
+     * not appear in a listing.
+     *
+     * @param noteIndices whether to include an explicit notation of
+     * constant pool indices
+     * @return {@code null-ok;} the listing string
+     */
+    protected abstract String listingString0(boolean noteIndices);
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/DalvInsnList.java b/dexgen/src/com/android/dexgen/dex/code/DalvInsnList.java
new file mode 100644
index 0000000..15f82c1
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/DalvInsnList.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstBaseMethodRef;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.ExceptionWithContext;
+import com.android.dexgen.util.FixedSizeList;
+import com.android.dexgen.util.IndentingWriter;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+
+/**
+ * List of {@link DalvInsn} instances.
+ */
+public final class DalvInsnList extends FixedSizeList {
+
+    /**
+     * The amount of register space, in register units, required for this
+     * code block. This may be greater than the largest observed register+
+     * category because the method this code block exists in may
+     * specify arguments that are unused by the method.
+     */
+    private final int regCount;
+
+    /**
+     * Constructs and returns an immutable instance whose elements are
+     * identical to the ones in the given list, in the same order.
+     *
+     * @param list {@code non-null;} the list to use for elements
+     * @param regCount count, in register-units, of the number of registers
+     * this code block requires.
+     * @return {@code non-null;} an appropriately-constructed instance of this
+     * class
+     */
+    public static DalvInsnList makeImmutable(ArrayList<DalvInsn> list,
+            int regCount) {
+        int size = list.size();
+        DalvInsnList result = new DalvInsnList(size, regCount);
+
+        for (int i = 0; i < size; i++) {
+            result.set(i, list.get(i));
+        }
+
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Constructs an instance. All indices initially contain {@code null}.
+     *
+     * @param size the size of the list
+     */
+    public DalvInsnList(int size, int regCount) {
+        super(size);
+        this.regCount = regCount;
+    }
+
+    /**
+     * Gets the element at the given index. It is an error to call
+     * this with the index for an element which was never set; if you
+     * do that, this will throw {@code NullPointerException}.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @return {@code non-null;} element at that index
+     */
+    public DalvInsn get(int n) {
+        return (DalvInsn) get0(n);
+    }
+
+    /**
+     * Sets the instruction at the given index.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @param insn {@code non-null;} the instruction to set at {@code n}
+     */
+    public void set(int n, DalvInsn insn) {
+        set0(n, insn);
+    }
+
+    /**
+     * Gets the size of this instance, in 16-bit code units. This will only
+     * return a meaningful result if the instructions in this instance all
+     * have valid addresses.
+     *
+     * @return {@code >= 0;} the size
+     */
+    public int codeSize() {
+        int sz = size();
+
+        if (sz == 0) {
+            return 0;
+        }
+
+        DalvInsn last = get(sz - 1);
+        return last.getNextAddress();
+    }
+
+    /**
+     * Writes all the instructions in this instance to the given output
+     * destination.
+     *
+     * @param out {@code non-null;} where to write to
+     */
+    public void writeTo(AnnotatedOutput out) {
+        int startCursor = out.getCursor();
+        int sz = size();
+
+        if (out.annotates()) {
+            boolean verbose = out.isVerbose();
+
+            for (int i = 0; i < sz; i++) {
+                DalvInsn insn = (DalvInsn) get0(i);
+                int codeBytes = insn.codeSize() * 2;
+                String s;
+
+                if ((codeBytes != 0) || verbose) {
+                    s = insn.listingString("  ", out.getAnnotationWidth(),
+                            true);
+                } else {
+                    s = null;
+                }
+
+                if (s != null) {
+                    out.annotate(codeBytes, s);
+                } else if (codeBytes != 0) {
+                    out.annotate(codeBytes, "");
+                }
+            }
+        }
+
+        for (int i = 0; i < sz; i++) {
+            DalvInsn insn = (DalvInsn) get0(i);
+            try {
+                insn.writeTo(out);
+            } catch (RuntimeException ex) {
+                throw ExceptionWithContext.withContext(ex,
+                        "...while writing " + insn);
+            }
+        }
+
+        // Sanity check of the amount written.
+        int written = (out.getCursor() - startCursor) / 2;
+        if (written != codeSize()) {
+            throw new RuntimeException("write length mismatch; expected " +
+                    codeSize() + " but actually wrote " + written);
+        }
+    }
+
+    /**
+     * Gets the minimum required register count implied by this
+     * instance.  This includes any unused parameters that could
+     * potentially be at the top of the register space.
+     * @return {@code >= 0;} the required registers size
+     */
+    public int getRegistersSize() {
+        return regCount;
+    }
+
+    /**
+     * Gets the size of the outgoing arguments area required by this
+     * method. This is equal to the largest argument word count of any
+     * method referred to by this instance.
+     *
+     * @return {@code >= 0;} the required outgoing arguments size
+     */
+    public int getOutsSize() {
+        int sz = size();
+        int result = 0;
+
+        for (int i = 0; i < sz; i++) {
+            DalvInsn insn = (DalvInsn) get0(i);
+
+            if (!(insn instanceof CstInsn)) {
+                continue;
+            }
+
+            Constant cst = ((CstInsn) insn).getConstant();
+
+            if (!(cst instanceof CstBaseMethodRef)) {
+                continue;
+            }
+
+            boolean isStatic =
+                (insn.getOpcode().getFamily() == DalvOps.INVOKE_STATIC);
+            int count =
+                ((CstBaseMethodRef) cst).getParameterWordCount(isStatic);
+
+            if (count > result) {
+                result = count;
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Does a human-friendly dump of this instance.
+     *
+     * @param out {@code non-null;} where to dump
+     * @param prefix {@code non-null;} prefix to attach to each line of output
+     * @param verbose whether to be verbose; verbose output includes
+     * lines for zero-size instructions and explicit constant pool indices
+     */
+    public void debugPrint(Writer out, String prefix, boolean verbose) {
+        IndentingWriter iw = new IndentingWriter(out, 0, prefix);
+        int sz = size();
+
+        try {
+            for (int i = 0; i < sz; i++) {
+                DalvInsn insn = (DalvInsn) get0(i);
+                String s;
+
+                if ((insn.codeSize() != 0) || verbose) {
+                    s = insn.listingString("", 0, verbose);
+                } else {
+                    s = null;
+                }
+
+                if (s != null) {
+                    iw.write(s);
+                }
+            }
+
+            iw.flush();
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    /**
+     * Does a human-friendly dump of this instance.
+     *
+     * @param out {@code non-null;} where to dump
+     * @param prefix {@code non-null;} prefix to attach to each line of output
+     * @param verbose whether to be verbose; verbose output includes
+     * lines for zero-size instructions
+     */
+    public void debugPrint(OutputStream out, String prefix, boolean verbose) {
+        Writer w = new OutputStreamWriter(out);
+        debugPrint(w, prefix, verbose);
+
+        try {
+            w.flush();
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/DalvOps.java b/dexgen/src/com/android/dexgen/dex/code/DalvOps.java
new file mode 100644
index 0000000..1d051ea
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/DalvOps.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+/**
+ * All the Dalvik opcode value constants. See the related spec
+ * document for the meaning and instruction format of each opcode.
+ */
+public final class DalvOps {
+    /** pseudo-opcode used for nonstandard format "instructions" */
+    public static final int SPECIAL_FORMAT = -1;
+
+    /** minimum valid opcode value */
+    public static final int MIN_VALUE = -1;
+
+    /** maximum valid opcode value */
+    public static final int MAX_VALUE = 0xff;
+
+    // BEGIN(opcodes); GENERATED AUTOMATICALLY BY opcode-gen
+    public static final int NOP = 0x00;
+    public static final int MOVE = 0x01;
+    public static final int MOVE_FROM16 = 0x02;
+    public static final int MOVE_16 = 0x03;
+    public static final int MOVE_WIDE = 0x04;
+    public static final int MOVE_WIDE_FROM16 = 0x05;
+    public static final int MOVE_WIDE_16 = 0x06;
+    public static final int MOVE_OBJECT = 0x07;
+    public static final int MOVE_OBJECT_FROM16 = 0x08;
+    public static final int MOVE_OBJECT_16 = 0x09;
+    public static final int MOVE_RESULT = 0x0a;
+    public static final int MOVE_RESULT_WIDE = 0x0b;
+    public static final int MOVE_RESULT_OBJECT = 0x0c;
+    public static final int MOVE_EXCEPTION = 0x0d;
+    public static final int RETURN_VOID = 0x0e;
+    public static final int RETURN = 0x0f;
+    public static final int RETURN_WIDE = 0x10;
+    public static final int RETURN_OBJECT = 0x11;
+    public static final int CONST_4 = 0x12;
+    public static final int CONST_16 = 0x13;
+    public static final int CONST = 0x14;
+    public static final int CONST_HIGH16 = 0x15;
+    public static final int CONST_WIDE_16 = 0x16;
+    public static final int CONST_WIDE_32 = 0x17;
+    public static final int CONST_WIDE = 0x18;
+    public static final int CONST_WIDE_HIGH16 = 0x19;
+    public static final int CONST_STRING = 0x1a;
+    public static final int CONST_STRING_JUMBO = 0x1b;
+    public static final int CONST_CLASS = 0x1c;
+    public static final int MONITOR_ENTER = 0x1d;
+    public static final int MONITOR_EXIT = 0x1e;
+    public static final int CHECK_CAST = 0x1f;
+    public static final int INSTANCE_OF = 0x20;
+    public static final int ARRAY_LENGTH = 0x21;
+    public static final int NEW_INSTANCE = 0x22;
+    public static final int NEW_ARRAY = 0x23;
+    public static final int FILLED_NEW_ARRAY = 0x24;
+    public static final int FILLED_NEW_ARRAY_RANGE = 0x25;
+    public static final int FILL_ARRAY_DATA = 0x26;
+    public static final int THROW = 0x27;
+    public static final int GOTO = 0x28;
+    public static final int GOTO_16 = 0x29;
+    public static final int GOTO_32 = 0x2a;
+    public static final int PACKED_SWITCH = 0x2b;
+    public static final int SPARSE_SWITCH = 0x2c;
+    public static final int CMPL_FLOAT = 0x2d;
+    public static final int CMPG_FLOAT = 0x2e;
+    public static final int CMPL_DOUBLE = 0x2f;
+    public static final int CMPG_DOUBLE = 0x30;
+    public static final int CMP_LONG = 0x31;
+    public static final int IF_EQ = 0x32;
+    public static final int IF_NE = 0x33;
+    public static final int IF_LT = 0x34;
+    public static final int IF_GE = 0x35;
+    public static final int IF_GT = 0x36;
+    public static final int IF_LE = 0x37;
+    public static final int IF_EQZ = 0x38;
+    public static final int IF_NEZ = 0x39;
+    public static final int IF_LTZ = 0x3a;
+    public static final int IF_GEZ = 0x3b;
+    public static final int IF_GTZ = 0x3c;
+    public static final int IF_LEZ = 0x3d;
+    public static final int UNUSED_3E = 0x3e;
+    public static final int UNUSED_3F = 0x3f;
+    public static final int UNUSED_40 = 0x40;
+    public static final int UNUSED_41 = 0x41;
+    public static final int UNUSED_42 = 0x42;
+    public static final int UNUSED_43 = 0x43;
+    public static final int AGET = 0x44;
+    public static final int AGET_WIDE = 0x45;
+    public static final int AGET_OBJECT = 0x46;
+    public static final int AGET_BOOLEAN = 0x47;
+    public static final int AGET_BYTE = 0x48;
+    public static final int AGET_CHAR = 0x49;
+    public static final int AGET_SHORT = 0x4a;
+    public static final int APUT = 0x4b;
+    public static final int APUT_WIDE = 0x4c;
+    public static final int APUT_OBJECT = 0x4d;
+    public static final int APUT_BOOLEAN = 0x4e;
+    public static final int APUT_BYTE = 0x4f;
+    public static final int APUT_CHAR = 0x50;
+    public static final int APUT_SHORT = 0x51;
+    public static final int IGET = 0x52;
+    public static final int IGET_WIDE = 0x53;
+    public static final int IGET_OBJECT = 0x54;
+    public static final int IGET_BOOLEAN = 0x55;
+    public static final int IGET_BYTE = 0x56;
+    public static final int IGET_CHAR = 0x57;
+    public static final int IGET_SHORT = 0x58;
+    public static final int IPUT = 0x59;
+    public static final int IPUT_WIDE = 0x5a;
+    public static final int IPUT_OBJECT = 0x5b;
+    public static final int IPUT_BOOLEAN = 0x5c;
+    public static final int IPUT_BYTE = 0x5d;
+    public static final int IPUT_CHAR = 0x5e;
+    public static final int IPUT_SHORT = 0x5f;
+    public static final int SGET = 0x60;
+    public static final int SGET_WIDE = 0x61;
+    public static final int SGET_OBJECT = 0x62;
+    public static final int SGET_BOOLEAN = 0x63;
+    public static final int SGET_BYTE = 0x64;
+    public static final int SGET_CHAR = 0x65;
+    public static final int SGET_SHORT = 0x66;
+    public static final int SPUT = 0x67;
+    public static final int SPUT_WIDE = 0x68;
+    public static final int SPUT_OBJECT = 0x69;
+    public static final int SPUT_BOOLEAN = 0x6a;
+    public static final int SPUT_BYTE = 0x6b;
+    public static final int SPUT_CHAR = 0x6c;
+    public static final int SPUT_SHORT = 0x6d;
+    public static final int INVOKE_VIRTUAL = 0x6e;
+    public static final int INVOKE_SUPER = 0x6f;
+    public static final int INVOKE_DIRECT = 0x70;
+    public static final int INVOKE_STATIC = 0x71;
+    public static final int INVOKE_INTERFACE = 0x72;
+    public static final int UNUSED_73 = 0x73;
+    public static final int INVOKE_VIRTUAL_RANGE = 0x74;
+    public static final int INVOKE_SUPER_RANGE = 0x75;
+    public static final int INVOKE_DIRECT_RANGE = 0x76;
+    public static final int INVOKE_STATIC_RANGE = 0x77;
+    public static final int INVOKE_INTERFACE_RANGE = 0x78;
+    public static final int UNUSED_79 = 0x79;
+    public static final int UNUSED_7A = 0x7a;
+    public static final int NEG_INT = 0x7b;
+    public static final int NOT_INT = 0x7c;
+    public static final int NEG_LONG = 0x7d;
+    public static final int NOT_LONG = 0x7e;
+    public static final int NEG_FLOAT = 0x7f;
+    public static final int NEG_DOUBLE = 0x80;
+    public static final int INT_TO_LONG = 0x81;
+    public static final int INT_TO_FLOAT = 0x82;
+    public static final int INT_TO_DOUBLE = 0x83;
+    public static final int LONG_TO_INT = 0x84;
+    public static final int LONG_TO_FLOAT = 0x85;
+    public static final int LONG_TO_DOUBLE = 0x86;
+    public static final int FLOAT_TO_INT = 0x87;
+    public static final int FLOAT_TO_LONG = 0x88;
+    public static final int FLOAT_TO_DOUBLE = 0x89;
+    public static final int DOUBLE_TO_INT = 0x8a;
+    public static final int DOUBLE_TO_LONG = 0x8b;
+    public static final int DOUBLE_TO_FLOAT = 0x8c;
+    public static final int INT_TO_BYTE = 0x8d;
+    public static final int INT_TO_CHAR = 0x8e;
+    public static final int INT_TO_SHORT = 0x8f;
+    public static final int ADD_INT = 0x90;
+    public static final int SUB_INT = 0x91;
+    public static final int MUL_INT = 0x92;
+    public static final int DIV_INT = 0x93;
+    public static final int REM_INT = 0x94;
+    public static final int AND_INT = 0x95;
+    public static final int OR_INT = 0x96;
+    public static final int XOR_INT = 0x97;
+    public static final int SHL_INT = 0x98;
+    public static final int SHR_INT = 0x99;
+    public static final int USHR_INT = 0x9a;
+    public static final int ADD_LONG = 0x9b;
+    public static final int SUB_LONG = 0x9c;
+    public static final int MUL_LONG = 0x9d;
+    public static final int DIV_LONG = 0x9e;
+    public static final int REM_LONG = 0x9f;
+    public static final int AND_LONG = 0xa0;
+    public static final int OR_LONG = 0xa1;
+    public static final int XOR_LONG = 0xa2;
+    public static final int SHL_LONG = 0xa3;
+    public static final int SHR_LONG = 0xa4;
+    public static final int USHR_LONG = 0xa5;
+    public static final int ADD_FLOAT = 0xa6;
+    public static final int SUB_FLOAT = 0xa7;
+    public static final int MUL_FLOAT = 0xa8;
+    public static final int DIV_FLOAT = 0xa9;
+    public static final int REM_FLOAT = 0xaa;
+    public static final int ADD_DOUBLE = 0xab;
+    public static final int SUB_DOUBLE = 0xac;
+    public static final int MUL_DOUBLE = 0xad;
+    public static final int DIV_DOUBLE = 0xae;
+    public static final int REM_DOUBLE = 0xaf;
+    public static final int ADD_INT_2ADDR = 0xb0;
+    public static final int SUB_INT_2ADDR = 0xb1;
+    public static final int MUL_INT_2ADDR = 0xb2;
+    public static final int DIV_INT_2ADDR = 0xb3;
+    public static final int REM_INT_2ADDR = 0xb4;
+    public static final int AND_INT_2ADDR = 0xb5;
+    public static final int OR_INT_2ADDR = 0xb6;
+    public static final int XOR_INT_2ADDR = 0xb7;
+    public static final int SHL_INT_2ADDR = 0xb8;
+    public static final int SHR_INT_2ADDR = 0xb9;
+    public static final int USHR_INT_2ADDR = 0xba;
+    public static final int ADD_LONG_2ADDR = 0xbb;
+    public static final int SUB_LONG_2ADDR = 0xbc;
+    public static final int MUL_LONG_2ADDR = 0xbd;
+    public static final int DIV_LONG_2ADDR = 0xbe;
+    public static final int REM_LONG_2ADDR = 0xbf;
+    public static final int AND_LONG_2ADDR = 0xc0;
+    public static final int OR_LONG_2ADDR = 0xc1;
+    public static final int XOR_LONG_2ADDR = 0xc2;
+    public static final int SHL_LONG_2ADDR = 0xc3;
+    public static final int SHR_LONG_2ADDR = 0xc4;
+    public static final int USHR_LONG_2ADDR = 0xc5;
+    public static final int ADD_FLOAT_2ADDR = 0xc6;
+    public static final int SUB_FLOAT_2ADDR = 0xc7;
+    public static final int MUL_FLOAT_2ADDR = 0xc8;
+    public static final int DIV_FLOAT_2ADDR = 0xc9;
+    public static final int REM_FLOAT_2ADDR = 0xca;
+    public static final int ADD_DOUBLE_2ADDR = 0xcb;
+    public static final int SUB_DOUBLE_2ADDR = 0xcc;
+    public static final int MUL_DOUBLE_2ADDR = 0xcd;
+    public static final int DIV_DOUBLE_2ADDR = 0xce;
+    public static final int REM_DOUBLE_2ADDR = 0xcf;
+    public static final int ADD_INT_LIT16 = 0xd0;
+    public static final int RSUB_INT = 0xd1;
+    public static final int MUL_INT_LIT16 = 0xd2;
+    public static final int DIV_INT_LIT16 = 0xd3;
+    public static final int REM_INT_LIT16 = 0xd4;
+    public static final int AND_INT_LIT16 = 0xd5;
+    public static final int OR_INT_LIT16 = 0xd6;
+    public static final int XOR_INT_LIT16 = 0xd7;
+    public static final int ADD_INT_LIT8 = 0xd8;
+    public static final int RSUB_INT_LIT8 = 0xd9;
+    public static final int MUL_INT_LIT8 = 0xda;
+    public static final int DIV_INT_LIT8 = 0xdb;
+    public static final int REM_INT_LIT8 = 0xdc;
+    public static final int AND_INT_LIT8 = 0xdd;
+    public static final int OR_INT_LIT8 = 0xde;
+    public static final int XOR_INT_LIT8 = 0xdf;
+    public static final int SHL_INT_LIT8 = 0xe0;
+    public static final int SHR_INT_LIT8 = 0xe1;
+    public static final int USHR_INT_LIT8 = 0xe2;
+    public static final int UNUSED_E3 = 0xe3;
+    public static final int UNUSED_E4 = 0xe4;
+    public static final int UNUSED_E5 = 0xe5;
+    public static final int UNUSED_E6 = 0xe6;
+    public static final int UNUSED_E7 = 0xe7;
+    public static final int UNUSED_E8 = 0xe8;
+    public static final int UNUSED_E9 = 0xe9;
+    public static final int UNUSED_EA = 0xea;
+    public static final int UNUSED_EB = 0xeb;
+    public static final int UNUSED_EC = 0xec;
+    public static final int UNUSED_ED = 0xed;
+    public static final int UNUSED_EE = 0xee;
+    public static final int UNUSED_EF = 0xef;
+    public static final int UNUSED_F0 = 0xf0;
+    public static final int UNUSED_F1 = 0xf1;
+    public static final int UNUSED_F2 = 0xf2;
+    public static final int UNUSED_F3 = 0xf3;
+    public static final int UNUSED_F4 = 0xf4;
+    public static final int UNUSED_F5 = 0xf5;
+    public static final int UNUSED_F6 = 0xf6;
+    public static final int UNUSED_F7 = 0xf7;
+    public static final int UNUSED_F8 = 0xf8;
+    public static final int UNUSED_F9 = 0xf9;
+    public static final int UNUSED_FA = 0xfa;
+    public static final int UNUSED_FB = 0xfb;
+    public static final int UNUSED_FC = 0xfc;
+    public static final int UNUSED_FD = 0xfd;
+    public static final int UNUSED_FE = 0xfe;
+    public static final int UNUSED_FF = 0xff;
+    // END(opcodes)
+
+    /**
+     * This class is uninstantiable.
+     */
+    private DalvOps() {
+        // This space intentionally left blank.
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/Dop.java b/dexgen/src/com/android/dexgen/dex/code/Dop.java
new file mode 100644
index 0000000..dc788f1
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/Dop.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+/**
+ * Representation of an opcode.
+ */
+public final class Dop {
+    /** DalvOps.MIN_VALUE..DalvOps.MAX_VALUE; the opcode value itself */
+    private final int opcode;
+
+    /** DalvOps.MIN_VALUE..DalvOps.MAX_VALUE; the opcode family */
+    private final int family;
+
+    /** {@code non-null;} the instruction format */
+    private final InsnFormat format;
+
+    /** whether this opcode uses a result register */
+    private final boolean hasResult;
+
+    /** {@code non-null;} the name */
+    private final String name;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param opcode {@code DalvOps.MIN_VALUE..DalvOps.MAX_VALUE;} the opcode
+     * value itself
+     * @param family {@code DalvOps.MIN_VALUE..DalvOps.MAX_VALUE;} the opcode family
+     * @param format {@code non-null;} the instruction format
+     * @param hasResult whether the opcode has a result register; if so it
+     * is always the first register
+     * @param name {@code non-null;} the name
+     */
+    public Dop(int opcode, int family, InsnFormat format,
+               boolean hasResult, String name) {
+        if ((opcode < DalvOps.MIN_VALUE) || (opcode > DalvOps.MAX_VALUE)) {
+            throw new IllegalArgumentException("bogus opcode");
+        }
+
+        if ((family < DalvOps.MIN_VALUE) || (family > DalvOps.MAX_VALUE)) {
+            throw new IllegalArgumentException("bogus family");
+        }
+
+        if (format == null) {
+            throw new NullPointerException("format == null");
+        }
+
+        if (name == null) {
+            throw new NullPointerException("name == null");
+        }
+
+        this.opcode = opcode;
+        this.family = family;
+        this.format = format;
+        this.hasResult = hasResult;
+        this.name = name;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return name;
+    }
+
+    /**
+     * Gets the opcode value.
+     *
+     * @return {@code DalvOps.MIN_VALUE..DalvOps.MAX_VALUE;} the opcode value
+     */
+    public int getOpcode() {
+        return opcode;
+    }
+
+    /**
+     * Gets the opcode family. The opcode family is the unmarked (no
+     * "/...") opcode that has equivalent semantics to this one.
+     *
+     * @return {@code DalvOps.MIN_VALUE..DalvOps.MAX_VALUE;} the opcode family
+     */
+    public int getFamily() {
+        return family;
+    }
+
+    /**
+     * Gets the instruction format.
+     *
+     * @return {@code non-null;} the instruction format
+     */
+    public InsnFormat getFormat() {
+        return format;
+    }
+
+    /**
+     * Returns whether this opcode uses a result register.
+     *
+     * @return {@code true} iff this opcode uses a result register
+     */
+    public boolean hasResult() {
+        return hasResult;
+    }
+
+    /**
+     * Gets the opcode name.
+     *
+     * @return {@code non-null;} the opcode name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Gets the opcode for the opposite test of this instance. This is only
+     * valid for opcodes which are in fact tests.
+     *
+     * @return {@code non-null;} the opposite test
+     */
+    public Dop getOppositeTest() {
+        switch (opcode) {
+            case DalvOps.IF_EQ:  return Dops.IF_NE;
+            case DalvOps.IF_NE:  return Dops.IF_EQ;
+            case DalvOps.IF_LT:  return Dops.IF_GE;
+            case DalvOps.IF_GE:  return Dops.IF_LT;
+            case DalvOps.IF_GT:  return Dops.IF_LE;
+            case DalvOps.IF_LE:  return Dops.IF_GT;
+            case DalvOps.IF_EQZ: return Dops.IF_NEZ;
+            case DalvOps.IF_NEZ: return Dops.IF_EQZ;
+            case DalvOps.IF_LTZ: return Dops.IF_GEZ;
+            case DalvOps.IF_GEZ: return Dops.IF_LTZ;
+            case DalvOps.IF_GTZ: return Dops.IF_LEZ;
+            case DalvOps.IF_LEZ: return Dops.IF_GTZ;
+        }
+
+        throw new IllegalArgumentException("bogus opcode: " + this);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/Dops.java b/dexgen/src/com/android/dexgen/dex/code/Dops.java
new file mode 100644
index 0000000..afd21e3
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/Dops.java
@@ -0,0 +1,1231 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.dex.code.form.Form10t;
+import com.android.dexgen.dex.code.form.Form10x;
+import com.android.dexgen.dex.code.form.Form11n;
+import com.android.dexgen.dex.code.form.Form11x;
+import com.android.dexgen.dex.code.form.Form12x;
+import com.android.dexgen.dex.code.form.Form20t;
+import com.android.dexgen.dex.code.form.Form21c;
+import com.android.dexgen.dex.code.form.Form21h;
+import com.android.dexgen.dex.code.form.Form21s;
+import com.android.dexgen.dex.code.form.Form21t;
+import com.android.dexgen.dex.code.form.Form22b;
+import com.android.dexgen.dex.code.form.Form22c;
+import com.android.dexgen.dex.code.form.Form22s;
+import com.android.dexgen.dex.code.form.Form22t;
+import com.android.dexgen.dex.code.form.Form22x;
+import com.android.dexgen.dex.code.form.Form23x;
+import com.android.dexgen.dex.code.form.Form30t;
+import com.android.dexgen.dex.code.form.Form31c;
+import com.android.dexgen.dex.code.form.Form31i;
+import com.android.dexgen.dex.code.form.Form31t;
+import com.android.dexgen.dex.code.form.Form32x;
+import com.android.dexgen.dex.code.form.Form35c;
+import com.android.dexgen.dex.code.form.Form3rc;
+import com.android.dexgen.dex.code.form.Form51l;
+import com.android.dexgen.dex.code.form.SpecialFormat;
+
+/**
+ * Standard instances of {@link Dop} and utility methods for getting
+ * them.
+ */
+public final class Dops {
+    /** {@code non-null;} array containing all the standard instances */
+    private static final Dop[] DOPS;
+
+    /**
+     * pseudo-opcode used for nonstandard formatted "instructions"
+     * (which are mostly not actually instructions, though they do
+     * appear in instruction lists)
+     */
+    public static final Dop SPECIAL_FORMAT =
+        new Dop(DalvOps.SPECIAL_FORMAT, DalvOps.SPECIAL_FORMAT,
+                SpecialFormat.THE_ONE, false, "<special>");
+
+    // BEGIN(dops); GENERATED AUTOMATICALLY BY opcode-gen
+    public static final Dop NOP =
+        new Dop(DalvOps.NOP, DalvOps.NOP,
+            Form10x.THE_ONE, false, "nop");
+
+    public static final Dop MOVE =
+        new Dop(DalvOps.MOVE, DalvOps.MOVE,
+            Form12x.THE_ONE, true, "move");
+
+    public static final Dop MOVE_FROM16 =
+        new Dop(DalvOps.MOVE_FROM16, DalvOps.MOVE,
+            Form22x.THE_ONE, true, "move/from16");
+
+    public static final Dop MOVE_16 =
+        new Dop(DalvOps.MOVE_16, DalvOps.MOVE,
+            Form32x.THE_ONE, true, "move/16");
+
+    public static final Dop MOVE_WIDE =
+        new Dop(DalvOps.MOVE_WIDE, DalvOps.MOVE_WIDE,
+            Form12x.THE_ONE, true, "move-wide");
+
+    public static final Dop MOVE_WIDE_FROM16 =
+        new Dop(DalvOps.MOVE_WIDE_FROM16, DalvOps.MOVE_WIDE,
+            Form22x.THE_ONE, true, "move-wide/from16");
+
+    public static final Dop MOVE_WIDE_16 =
+        new Dop(DalvOps.MOVE_WIDE_16, DalvOps.MOVE_WIDE,
+            Form32x.THE_ONE, true, "move-wide/16");
+
+    public static final Dop MOVE_OBJECT =
+        new Dop(DalvOps.MOVE_OBJECT, DalvOps.MOVE_OBJECT,
+            Form12x.THE_ONE, true, "move-object");
+
+    public static final Dop MOVE_OBJECT_FROM16 =
+        new Dop(DalvOps.MOVE_OBJECT_FROM16, DalvOps.MOVE_OBJECT,
+            Form22x.THE_ONE, true, "move-object/from16");
+
+    public static final Dop MOVE_OBJECT_16 =
+        new Dop(DalvOps.MOVE_OBJECT_16, DalvOps.MOVE_OBJECT,
+            Form32x.THE_ONE, true, "move-object/16");
+
+    public static final Dop MOVE_RESULT =
+        new Dop(DalvOps.MOVE_RESULT, DalvOps.MOVE_RESULT,
+            Form11x.THE_ONE, true, "move-result");
+
+    public static final Dop MOVE_RESULT_WIDE =
+        new Dop(DalvOps.MOVE_RESULT_WIDE, DalvOps.MOVE_RESULT_WIDE,
+            Form11x.THE_ONE, true, "move-result-wide");
+
+    public static final Dop MOVE_RESULT_OBJECT =
+        new Dop(DalvOps.MOVE_RESULT_OBJECT, DalvOps.MOVE_RESULT_OBJECT,
+            Form11x.THE_ONE, true, "move-result-object");
+
+    public static final Dop MOVE_EXCEPTION =
+        new Dop(DalvOps.MOVE_EXCEPTION, DalvOps.MOVE_EXCEPTION,
+            Form11x.THE_ONE, true, "move-exception");
+
+    public static final Dop RETURN_VOID =
+        new Dop(DalvOps.RETURN_VOID, DalvOps.RETURN_VOID,
+            Form10x.THE_ONE, false, "return-void");
+
+    public static final Dop RETURN =
+        new Dop(DalvOps.RETURN, DalvOps.RETURN,
+            Form11x.THE_ONE, false, "return");
+
+    public static final Dop RETURN_WIDE =
+        new Dop(DalvOps.RETURN_WIDE, DalvOps.RETURN_WIDE,
+            Form11x.THE_ONE, false, "return-wide");
+
+    public static final Dop RETURN_OBJECT =
+        new Dop(DalvOps.RETURN_OBJECT, DalvOps.RETURN_OBJECT,
+            Form11x.THE_ONE, false, "return-object");
+
+    public static final Dop CONST_4 =
+        new Dop(DalvOps.CONST_4, DalvOps.CONST,
+            Form11n.THE_ONE, true, "const/4");
+
+    public static final Dop CONST_16 =
+        new Dop(DalvOps.CONST_16, DalvOps.CONST,
+            Form21s.THE_ONE, true, "const/16");
+
+    public static final Dop CONST =
+        new Dop(DalvOps.CONST, DalvOps.CONST,
+            Form31i.THE_ONE, true, "const");
+
+    public static final Dop CONST_HIGH16 =
+        new Dop(DalvOps.CONST_HIGH16, DalvOps.CONST,
+            Form21h.THE_ONE, true, "const/high16");
+
+    public static final Dop CONST_WIDE_16 =
+        new Dop(DalvOps.CONST_WIDE_16, DalvOps.CONST_WIDE,
+            Form21s.THE_ONE, true, "const-wide/16");
+
+    public static final Dop CONST_WIDE_32 =
+        new Dop(DalvOps.CONST_WIDE_32, DalvOps.CONST_WIDE,
+            Form31i.THE_ONE, true, "const-wide/32");
+
+    public static final Dop CONST_WIDE =
+        new Dop(DalvOps.CONST_WIDE, DalvOps.CONST_WIDE,
+            Form51l.THE_ONE, true, "const-wide");
+
+    public static final Dop CONST_WIDE_HIGH16 =
+        new Dop(DalvOps.CONST_WIDE_HIGH16, DalvOps.CONST_WIDE,
+            Form21h.THE_ONE, true, "const-wide/high16");
+
+    public static final Dop CONST_STRING =
+        new Dop(DalvOps.CONST_STRING, DalvOps.CONST_STRING,
+            Form21c.THE_ONE, true, "const-string");
+
+    public static final Dop CONST_STRING_JUMBO =
+        new Dop(DalvOps.CONST_STRING_JUMBO, DalvOps.CONST_STRING,
+            Form31c.THE_ONE, true, "const-string/jumbo");
+
+    public static final Dop CONST_CLASS =
+        new Dop(DalvOps.CONST_CLASS, DalvOps.CONST_CLASS,
+            Form21c.THE_ONE, true, "const-class");
+
+    public static final Dop MONITOR_ENTER =
+        new Dop(DalvOps.MONITOR_ENTER, DalvOps.MONITOR_ENTER,
+            Form11x.THE_ONE, false, "monitor-enter");
+
+    public static final Dop MONITOR_EXIT =
+        new Dop(DalvOps.MONITOR_EXIT, DalvOps.MONITOR_EXIT,
+            Form11x.THE_ONE, false, "monitor-exit");
+
+    public static final Dop CHECK_CAST =
+        new Dop(DalvOps.CHECK_CAST, DalvOps.CHECK_CAST,
+            Form21c.THE_ONE, true, "check-cast");
+
+    public static final Dop INSTANCE_OF =
+        new Dop(DalvOps.INSTANCE_OF, DalvOps.INSTANCE_OF,
+            Form22c.THE_ONE, true, "instance-of");
+
+    public static final Dop ARRAY_LENGTH =
+        new Dop(DalvOps.ARRAY_LENGTH, DalvOps.ARRAY_LENGTH,
+            Form12x.THE_ONE, true, "array-length");
+
+    public static final Dop NEW_INSTANCE =
+        new Dop(DalvOps.NEW_INSTANCE, DalvOps.NEW_INSTANCE,
+            Form21c.THE_ONE, true, "new-instance");
+
+    public static final Dop NEW_ARRAY =
+        new Dop(DalvOps.NEW_ARRAY, DalvOps.NEW_ARRAY,
+            Form22c.THE_ONE, true, "new-array");
+
+    public static final Dop FILLED_NEW_ARRAY =
+        new Dop(DalvOps.FILLED_NEW_ARRAY, DalvOps.FILLED_NEW_ARRAY,
+            Form35c.THE_ONE, false, "filled-new-array");
+
+    public static final Dop FILLED_NEW_ARRAY_RANGE =
+        new Dop(DalvOps.FILLED_NEW_ARRAY_RANGE, DalvOps.FILLED_NEW_ARRAY,
+            Form3rc.THE_ONE, false, "filled-new-array/range");
+
+    public static final Dop FILL_ARRAY_DATA =
+        new Dop(DalvOps.FILL_ARRAY_DATA, DalvOps.FILL_ARRAY_DATA,
+            Form31t.THE_ONE, false, "fill-array-data");
+
+    public static final Dop THROW =
+        new Dop(DalvOps.THROW, DalvOps.THROW,
+            Form11x.THE_ONE, false, "throw");
+
+    public static final Dop GOTO =
+        new Dop(DalvOps.GOTO, DalvOps.GOTO,
+            Form10t.THE_ONE, false, "goto");
+
+    public static final Dop GOTO_16 =
+        new Dop(DalvOps.GOTO_16, DalvOps.GOTO,
+            Form20t.THE_ONE, false, "goto/16");
+
+    public static final Dop GOTO_32 =
+        new Dop(DalvOps.GOTO_32, DalvOps.GOTO,
+            Form30t.THE_ONE, false, "goto/32");
+
+    public static final Dop PACKED_SWITCH =
+        new Dop(DalvOps.PACKED_SWITCH, DalvOps.PACKED_SWITCH,
+            Form31t.THE_ONE, false, "packed-switch");
+
+    public static final Dop SPARSE_SWITCH =
+        new Dop(DalvOps.SPARSE_SWITCH, DalvOps.SPARSE_SWITCH,
+            Form31t.THE_ONE, false, "sparse-switch");
+
+    public static final Dop CMPL_FLOAT =
+        new Dop(DalvOps.CMPL_FLOAT, DalvOps.CMPL_FLOAT,
+            Form23x.THE_ONE, true, "cmpl-float");
+
+    public static final Dop CMPG_FLOAT =
+        new Dop(DalvOps.CMPG_FLOAT, DalvOps.CMPG_FLOAT,
+            Form23x.THE_ONE, true, "cmpg-float");
+
+    public static final Dop CMPL_DOUBLE =
+        new Dop(DalvOps.CMPL_DOUBLE, DalvOps.CMPL_DOUBLE,
+            Form23x.THE_ONE, true, "cmpl-double");
+
+    public static final Dop CMPG_DOUBLE =
+        new Dop(DalvOps.CMPG_DOUBLE, DalvOps.CMPG_DOUBLE,
+            Form23x.THE_ONE, true, "cmpg-double");
+
+    public static final Dop CMP_LONG =
+        new Dop(DalvOps.CMP_LONG, DalvOps.CMP_LONG,
+            Form23x.THE_ONE, true, "cmp-long");
+
+    public static final Dop IF_EQ =
+        new Dop(DalvOps.IF_EQ, DalvOps.IF_EQ,
+            Form22t.THE_ONE, false, "if-eq");
+
+    public static final Dop IF_NE =
+        new Dop(DalvOps.IF_NE, DalvOps.IF_NE,
+            Form22t.THE_ONE, false, "if-ne");
+
+    public static final Dop IF_LT =
+        new Dop(DalvOps.IF_LT, DalvOps.IF_LT,
+            Form22t.THE_ONE, false, "if-lt");
+
+    public static final Dop IF_GE =
+        new Dop(DalvOps.IF_GE, DalvOps.IF_GE,
+            Form22t.THE_ONE, false, "if-ge");
+
+    public static final Dop IF_GT =
+        new Dop(DalvOps.IF_GT, DalvOps.IF_GT,
+            Form22t.THE_ONE, false, "if-gt");
+
+    public static final Dop IF_LE =
+        new Dop(DalvOps.IF_LE, DalvOps.IF_LE,
+            Form22t.THE_ONE, false, "if-le");
+
+    public static final Dop IF_EQZ =
+        new Dop(DalvOps.IF_EQZ, DalvOps.IF_EQZ,
+            Form21t.THE_ONE, false, "if-eqz");
+
+    public static final Dop IF_NEZ =
+        new Dop(DalvOps.IF_NEZ, DalvOps.IF_NEZ,
+            Form21t.THE_ONE, false, "if-nez");
+
+    public static final Dop IF_LTZ =
+        new Dop(DalvOps.IF_LTZ, DalvOps.IF_LTZ,
+            Form21t.THE_ONE, false, "if-ltz");
+
+    public static final Dop IF_GEZ =
+        new Dop(DalvOps.IF_GEZ, DalvOps.IF_GEZ,
+            Form21t.THE_ONE, false, "if-gez");
+
+    public static final Dop IF_GTZ =
+        new Dop(DalvOps.IF_GTZ, DalvOps.IF_GTZ,
+            Form21t.THE_ONE, false, "if-gtz");
+
+    public static final Dop IF_LEZ =
+        new Dop(DalvOps.IF_LEZ, DalvOps.IF_LEZ,
+            Form21t.THE_ONE, false, "if-lez");
+
+    public static final Dop AGET =
+        new Dop(DalvOps.AGET, DalvOps.AGET,
+            Form23x.THE_ONE, true, "aget");
+
+    public static final Dop AGET_WIDE =
+        new Dop(DalvOps.AGET_WIDE, DalvOps.AGET_WIDE,
+            Form23x.THE_ONE, true, "aget-wide");
+
+    public static final Dop AGET_OBJECT =
+        new Dop(DalvOps.AGET_OBJECT, DalvOps.AGET_OBJECT,
+            Form23x.THE_ONE, true, "aget-object");
+
+    public static final Dop AGET_BOOLEAN =
+        new Dop(DalvOps.AGET_BOOLEAN, DalvOps.AGET_BOOLEAN,
+            Form23x.THE_ONE, true, "aget-boolean");
+
+    public static final Dop AGET_BYTE =
+        new Dop(DalvOps.AGET_BYTE, DalvOps.AGET_BYTE,
+            Form23x.THE_ONE, true, "aget-byte");
+
+    public static final Dop AGET_CHAR =
+        new Dop(DalvOps.AGET_CHAR, DalvOps.AGET_CHAR,
+            Form23x.THE_ONE, true, "aget-char");
+
+    public static final Dop AGET_SHORT =
+        new Dop(DalvOps.AGET_SHORT, DalvOps.AGET_SHORT,
+            Form23x.THE_ONE, true, "aget-short");
+
+    public static final Dop APUT =
+        new Dop(DalvOps.APUT, DalvOps.APUT,
+            Form23x.THE_ONE, false, "aput");
+
+    public static final Dop APUT_WIDE =
+        new Dop(DalvOps.APUT_WIDE, DalvOps.APUT_WIDE,
+            Form23x.THE_ONE, false, "aput-wide");
+
+    public static final Dop APUT_OBJECT =
+        new Dop(DalvOps.APUT_OBJECT, DalvOps.APUT_OBJECT,
+            Form23x.THE_ONE, false, "aput-object");
+
+    public static final Dop APUT_BOOLEAN =
+        new Dop(DalvOps.APUT_BOOLEAN, DalvOps.APUT_BOOLEAN,
+            Form23x.THE_ONE, false, "aput-boolean");
+
+    public static final Dop APUT_BYTE =
+        new Dop(DalvOps.APUT_BYTE, DalvOps.APUT_BYTE,
+            Form23x.THE_ONE, false, "aput-byte");
+
+    public static final Dop APUT_CHAR =
+        new Dop(DalvOps.APUT_CHAR, DalvOps.APUT_CHAR,
+            Form23x.THE_ONE, false, "aput-char");
+
+    public static final Dop APUT_SHORT =
+        new Dop(DalvOps.APUT_SHORT, DalvOps.APUT_SHORT,
+            Form23x.THE_ONE, false, "aput-short");
+
+    public static final Dop IGET =
+        new Dop(DalvOps.IGET, DalvOps.IGET,
+            Form22c.THE_ONE, true, "iget");
+
+    public static final Dop IGET_WIDE =
+        new Dop(DalvOps.IGET_WIDE, DalvOps.IGET_WIDE,
+            Form22c.THE_ONE, true, "iget-wide");
+
+    public static final Dop IGET_OBJECT =
+        new Dop(DalvOps.IGET_OBJECT, DalvOps.IGET_OBJECT,
+            Form22c.THE_ONE, true, "iget-object");
+
+    public static final Dop IGET_BOOLEAN =
+        new Dop(DalvOps.IGET_BOOLEAN, DalvOps.IGET_BOOLEAN,
+            Form22c.THE_ONE, true, "iget-boolean");
+
+    public static final Dop IGET_BYTE =
+        new Dop(DalvOps.IGET_BYTE, DalvOps.IGET_BYTE,
+            Form22c.THE_ONE, true, "iget-byte");
+
+    public static final Dop IGET_CHAR =
+        new Dop(DalvOps.IGET_CHAR, DalvOps.IGET_CHAR,
+            Form22c.THE_ONE, true, "iget-char");
+
+    public static final Dop IGET_SHORT =
+        new Dop(DalvOps.IGET_SHORT, DalvOps.IGET_SHORT,
+            Form22c.THE_ONE, true, "iget-short");
+
+    public static final Dop IPUT =
+        new Dop(DalvOps.IPUT, DalvOps.IPUT,
+            Form22c.THE_ONE, false, "iput");
+
+    public static final Dop IPUT_WIDE =
+        new Dop(DalvOps.IPUT_WIDE, DalvOps.IPUT_WIDE,
+            Form22c.THE_ONE, false, "iput-wide");
+
+    public static final Dop IPUT_OBJECT =
+        new Dop(DalvOps.IPUT_OBJECT, DalvOps.IPUT_OBJECT,
+            Form22c.THE_ONE, false, "iput-object");
+
+    public static final Dop IPUT_BOOLEAN =
+        new Dop(DalvOps.IPUT_BOOLEAN, DalvOps.IPUT_BOOLEAN,
+            Form22c.THE_ONE, false, "iput-boolean");
+
+    public static final Dop IPUT_BYTE =
+        new Dop(DalvOps.IPUT_BYTE, DalvOps.IPUT_BYTE,
+            Form22c.THE_ONE, false, "iput-byte");
+
+    public static final Dop IPUT_CHAR =
+        new Dop(DalvOps.IPUT_CHAR, DalvOps.IPUT_CHAR,
+            Form22c.THE_ONE, false, "iput-char");
+
+    public static final Dop IPUT_SHORT =
+        new Dop(DalvOps.IPUT_SHORT, DalvOps.IPUT_SHORT,
+            Form22c.THE_ONE, false, "iput-short");
+
+    public static final Dop SGET =
+        new Dop(DalvOps.SGET, DalvOps.SGET,
+            Form21c.THE_ONE, true, "sget");
+
+    public static final Dop SGET_WIDE =
+        new Dop(DalvOps.SGET_WIDE, DalvOps.SGET_WIDE,
+            Form21c.THE_ONE, true, "sget-wide");
+
+    public static final Dop SGET_OBJECT =
+        new Dop(DalvOps.SGET_OBJECT, DalvOps.SGET_OBJECT,
+            Form21c.THE_ONE, true, "sget-object");
+
+    public static final Dop SGET_BOOLEAN =
+        new Dop(DalvOps.SGET_BOOLEAN, DalvOps.SGET_BOOLEAN,
+            Form21c.THE_ONE, true, "sget-boolean");
+
+    public static final Dop SGET_BYTE =
+        new Dop(DalvOps.SGET_BYTE, DalvOps.SGET_BYTE,
+            Form21c.THE_ONE, true, "sget-byte");
+
+    public static final Dop SGET_CHAR =
+        new Dop(DalvOps.SGET_CHAR, DalvOps.SGET_CHAR,
+            Form21c.THE_ONE, true, "sget-char");
+
+    public static final Dop SGET_SHORT =
+        new Dop(DalvOps.SGET_SHORT, DalvOps.SGET_SHORT,
+            Form21c.THE_ONE, true, "sget-short");
+
+    public static final Dop SPUT =
+        new Dop(DalvOps.SPUT, DalvOps.SPUT,
+            Form21c.THE_ONE, false, "sput");
+
+    public static final Dop SPUT_WIDE =
+        new Dop(DalvOps.SPUT_WIDE, DalvOps.SPUT_WIDE,
+            Form21c.THE_ONE, false, "sput-wide");
+
+    public static final Dop SPUT_OBJECT =
+        new Dop(DalvOps.SPUT_OBJECT, DalvOps.SPUT_OBJECT,
+            Form21c.THE_ONE, false, "sput-object");
+
+    public static final Dop SPUT_BOOLEAN =
+        new Dop(DalvOps.SPUT_BOOLEAN, DalvOps.SPUT_BOOLEAN,
+            Form21c.THE_ONE, false, "sput-boolean");
+
+    public static final Dop SPUT_BYTE =
+        new Dop(DalvOps.SPUT_BYTE, DalvOps.SPUT_BYTE,
+            Form21c.THE_ONE, false, "sput-byte");
+
+    public static final Dop SPUT_CHAR =
+        new Dop(DalvOps.SPUT_CHAR, DalvOps.SPUT_CHAR,
+            Form21c.THE_ONE, false, "sput-char");
+
+    public static final Dop SPUT_SHORT =
+        new Dop(DalvOps.SPUT_SHORT, DalvOps.SPUT_SHORT,
+            Form21c.THE_ONE, false, "sput-short");
+
+    public static final Dop INVOKE_VIRTUAL =
+        new Dop(DalvOps.INVOKE_VIRTUAL, DalvOps.INVOKE_VIRTUAL,
+            Form35c.THE_ONE, false, "invoke-virtual");
+
+    public static final Dop INVOKE_SUPER =
+        new Dop(DalvOps.INVOKE_SUPER, DalvOps.INVOKE_SUPER,
+            Form35c.THE_ONE, false, "invoke-super");
+
+    public static final Dop INVOKE_DIRECT =
+        new Dop(DalvOps.INVOKE_DIRECT, DalvOps.INVOKE_DIRECT,
+            Form35c.THE_ONE, false, "invoke-direct");
+
+    public static final Dop INVOKE_STATIC =
+        new Dop(DalvOps.INVOKE_STATIC, DalvOps.INVOKE_STATIC,
+            Form35c.THE_ONE, false, "invoke-static");
+
+    public static final Dop INVOKE_INTERFACE =
+        new Dop(DalvOps.INVOKE_INTERFACE, DalvOps.INVOKE_INTERFACE,
+            Form35c.THE_ONE, false, "invoke-interface");
+
+    public static final Dop INVOKE_VIRTUAL_RANGE =
+        new Dop(DalvOps.INVOKE_VIRTUAL_RANGE, DalvOps.INVOKE_VIRTUAL,
+            Form3rc.THE_ONE, false, "invoke-virtual/range");
+
+    public static final Dop INVOKE_SUPER_RANGE =
+        new Dop(DalvOps.INVOKE_SUPER_RANGE, DalvOps.INVOKE_SUPER,
+            Form3rc.THE_ONE, false, "invoke-super/range");
+
+    public static final Dop INVOKE_DIRECT_RANGE =
+        new Dop(DalvOps.INVOKE_DIRECT_RANGE, DalvOps.INVOKE_DIRECT,
+            Form3rc.THE_ONE, false, "invoke-direct/range");
+
+    public static final Dop INVOKE_STATIC_RANGE =
+        new Dop(DalvOps.INVOKE_STATIC_RANGE, DalvOps.INVOKE_STATIC,
+            Form3rc.THE_ONE, false, "invoke-static/range");
+
+    public static final Dop INVOKE_INTERFACE_RANGE =
+        new Dop(DalvOps.INVOKE_INTERFACE_RANGE, DalvOps.INVOKE_INTERFACE,
+            Form3rc.THE_ONE, false, "invoke-interface/range");
+
+    public static final Dop NEG_INT =
+        new Dop(DalvOps.NEG_INT, DalvOps.NEG_INT,
+            Form12x.THE_ONE, true, "neg-int");
+
+    public static final Dop NOT_INT =
+        new Dop(DalvOps.NOT_INT, DalvOps.NOT_INT,
+            Form12x.THE_ONE, true, "not-int");
+
+    public static final Dop NEG_LONG =
+        new Dop(DalvOps.NEG_LONG, DalvOps.NEG_LONG,
+            Form12x.THE_ONE, true, "neg-long");
+
+    public static final Dop NOT_LONG =
+        new Dop(DalvOps.NOT_LONG, DalvOps.NOT_LONG,
+            Form12x.THE_ONE, true, "not-long");
+
+    public static final Dop NEG_FLOAT =
+        new Dop(DalvOps.NEG_FLOAT, DalvOps.NEG_FLOAT,
+            Form12x.THE_ONE, true, "neg-float");
+
+    public static final Dop NEG_DOUBLE =
+        new Dop(DalvOps.NEG_DOUBLE, DalvOps.NEG_DOUBLE,
+            Form12x.THE_ONE, true, "neg-double");
+
+    public static final Dop INT_TO_LONG =
+        new Dop(DalvOps.INT_TO_LONG, DalvOps.INT_TO_LONG,
+            Form12x.THE_ONE, true, "int-to-long");
+
+    public static final Dop INT_TO_FLOAT =
+        new Dop(DalvOps.INT_TO_FLOAT, DalvOps.INT_TO_FLOAT,
+            Form12x.THE_ONE, true, "int-to-float");
+
+    public static final Dop INT_TO_DOUBLE =
+        new Dop(DalvOps.INT_TO_DOUBLE, DalvOps.INT_TO_DOUBLE,
+            Form12x.THE_ONE, true, "int-to-double");
+
+    public static final Dop LONG_TO_INT =
+        new Dop(DalvOps.LONG_TO_INT, DalvOps.LONG_TO_INT,
+            Form12x.THE_ONE, true, "long-to-int");
+
+    public static final Dop LONG_TO_FLOAT =
+        new Dop(DalvOps.LONG_TO_FLOAT, DalvOps.LONG_TO_FLOAT,
+            Form12x.THE_ONE, true, "long-to-float");
+
+    public static final Dop LONG_TO_DOUBLE =
+        new Dop(DalvOps.LONG_TO_DOUBLE, DalvOps.LONG_TO_DOUBLE,
+            Form12x.THE_ONE, true, "long-to-double");
+
+    public static final Dop FLOAT_TO_INT =
+        new Dop(DalvOps.FLOAT_TO_INT, DalvOps.FLOAT_TO_INT,
+            Form12x.THE_ONE, true, "float-to-int");
+
+    public static final Dop FLOAT_TO_LONG =
+        new Dop(DalvOps.FLOAT_TO_LONG, DalvOps.FLOAT_TO_LONG,
+            Form12x.THE_ONE, true, "float-to-long");
+
+    public static final Dop FLOAT_TO_DOUBLE =
+        new Dop(DalvOps.FLOAT_TO_DOUBLE, DalvOps.FLOAT_TO_DOUBLE,
+            Form12x.THE_ONE, true, "float-to-double");
+
+    public static final Dop DOUBLE_TO_INT =
+        new Dop(DalvOps.DOUBLE_TO_INT, DalvOps.DOUBLE_TO_INT,
+            Form12x.THE_ONE, true, "double-to-int");
+
+    public static final Dop DOUBLE_TO_LONG =
+        new Dop(DalvOps.DOUBLE_TO_LONG, DalvOps.DOUBLE_TO_LONG,
+            Form12x.THE_ONE, true, "double-to-long");
+
+    public static final Dop DOUBLE_TO_FLOAT =
+        new Dop(DalvOps.DOUBLE_TO_FLOAT, DalvOps.DOUBLE_TO_FLOAT,
+            Form12x.THE_ONE, true, "double-to-float");
+
+    public static final Dop INT_TO_BYTE =
+        new Dop(DalvOps.INT_TO_BYTE, DalvOps.INT_TO_BYTE,
+            Form12x.THE_ONE, true, "int-to-byte");
+
+    public static final Dop INT_TO_CHAR =
+        new Dop(DalvOps.INT_TO_CHAR, DalvOps.INT_TO_CHAR,
+            Form12x.THE_ONE, true, "int-to-char");
+
+    public static final Dop INT_TO_SHORT =
+        new Dop(DalvOps.INT_TO_SHORT, DalvOps.INT_TO_SHORT,
+            Form12x.THE_ONE, true, "int-to-short");
+
+    public static final Dop ADD_INT =
+        new Dop(DalvOps.ADD_INT, DalvOps.ADD_INT,
+            Form23x.THE_ONE, true, "add-int");
+
+    public static final Dop SUB_INT =
+        new Dop(DalvOps.SUB_INT, DalvOps.SUB_INT,
+            Form23x.THE_ONE, true, "sub-int");
+
+    public static final Dop MUL_INT =
+        new Dop(DalvOps.MUL_INT, DalvOps.MUL_INT,
+            Form23x.THE_ONE, true, "mul-int");
+
+    public static final Dop DIV_INT =
+        new Dop(DalvOps.DIV_INT, DalvOps.DIV_INT,
+            Form23x.THE_ONE, true, "div-int");
+
+    public static final Dop REM_INT =
+        new Dop(DalvOps.REM_INT, DalvOps.REM_INT,
+            Form23x.THE_ONE, true, "rem-int");
+
+    public static final Dop AND_INT =
+        new Dop(DalvOps.AND_INT, DalvOps.AND_INT,
+            Form23x.THE_ONE, true, "and-int");
+
+    public static final Dop OR_INT =
+        new Dop(DalvOps.OR_INT, DalvOps.OR_INT,
+            Form23x.THE_ONE, true, "or-int");
+
+    public static final Dop XOR_INT =
+        new Dop(DalvOps.XOR_INT, DalvOps.XOR_INT,
+            Form23x.THE_ONE, true, "xor-int");
+
+    public static final Dop SHL_INT =
+        new Dop(DalvOps.SHL_INT, DalvOps.SHL_INT,
+            Form23x.THE_ONE, true, "shl-int");
+
+    public static final Dop SHR_INT =
+        new Dop(DalvOps.SHR_INT, DalvOps.SHR_INT,
+            Form23x.THE_ONE, true, "shr-int");
+
+    public static final Dop USHR_INT =
+        new Dop(DalvOps.USHR_INT, DalvOps.USHR_INT,
+            Form23x.THE_ONE, true, "ushr-int");
+
+    public static final Dop ADD_LONG =
+        new Dop(DalvOps.ADD_LONG, DalvOps.ADD_LONG,
+            Form23x.THE_ONE, true, "add-long");
+
+    public static final Dop SUB_LONG =
+        new Dop(DalvOps.SUB_LONG, DalvOps.SUB_LONG,
+            Form23x.THE_ONE, true, "sub-long");
+
+    public static final Dop MUL_LONG =
+        new Dop(DalvOps.MUL_LONG, DalvOps.MUL_LONG,
+            Form23x.THE_ONE, true, "mul-long");
+
+    public static final Dop DIV_LONG =
+        new Dop(DalvOps.DIV_LONG, DalvOps.DIV_LONG,
+            Form23x.THE_ONE, true, "div-long");
+
+    public static final Dop REM_LONG =
+        new Dop(DalvOps.REM_LONG, DalvOps.REM_LONG,
+            Form23x.THE_ONE, true, "rem-long");
+
+    public static final Dop AND_LONG =
+        new Dop(DalvOps.AND_LONG, DalvOps.AND_LONG,
+            Form23x.THE_ONE, true, "and-long");
+
+    public static final Dop OR_LONG =
+        new Dop(DalvOps.OR_LONG, DalvOps.OR_LONG,
+            Form23x.THE_ONE, true, "or-long");
+
+    public static final Dop XOR_LONG =
+        new Dop(DalvOps.XOR_LONG, DalvOps.XOR_LONG,
+            Form23x.THE_ONE, true, "xor-long");
+
+    public static final Dop SHL_LONG =
+        new Dop(DalvOps.SHL_LONG, DalvOps.SHL_LONG,
+            Form23x.THE_ONE, true, "shl-long");
+
+    public static final Dop SHR_LONG =
+        new Dop(DalvOps.SHR_LONG, DalvOps.SHR_LONG,
+            Form23x.THE_ONE, true, "shr-long");
+
+    public static final Dop USHR_LONG =
+        new Dop(DalvOps.USHR_LONG, DalvOps.USHR_LONG,
+            Form23x.THE_ONE, true, "ushr-long");
+
+    public static final Dop ADD_FLOAT =
+        new Dop(DalvOps.ADD_FLOAT, DalvOps.ADD_FLOAT,
+            Form23x.THE_ONE, true, "add-float");
+
+    public static final Dop SUB_FLOAT =
+        new Dop(DalvOps.SUB_FLOAT, DalvOps.SUB_FLOAT,
+            Form23x.THE_ONE, true, "sub-float");
+
+    public static final Dop MUL_FLOAT =
+        new Dop(DalvOps.MUL_FLOAT, DalvOps.MUL_FLOAT,
+            Form23x.THE_ONE, true, "mul-float");
+
+    public static final Dop DIV_FLOAT =
+        new Dop(DalvOps.DIV_FLOAT, DalvOps.DIV_FLOAT,
+            Form23x.THE_ONE, true, "div-float");
+
+    public static final Dop REM_FLOAT =
+        new Dop(DalvOps.REM_FLOAT, DalvOps.REM_FLOAT,
+            Form23x.THE_ONE, true, "rem-float");
+
+    public static final Dop ADD_DOUBLE =
+        new Dop(DalvOps.ADD_DOUBLE, DalvOps.ADD_DOUBLE,
+            Form23x.THE_ONE, true, "add-double");
+
+    public static final Dop SUB_DOUBLE =
+        new Dop(DalvOps.SUB_DOUBLE, DalvOps.SUB_DOUBLE,
+            Form23x.THE_ONE, true, "sub-double");
+
+    public static final Dop MUL_DOUBLE =
+        new Dop(DalvOps.MUL_DOUBLE, DalvOps.MUL_DOUBLE,
+            Form23x.THE_ONE, true, "mul-double");
+
+    public static final Dop DIV_DOUBLE =
+        new Dop(DalvOps.DIV_DOUBLE, DalvOps.DIV_DOUBLE,
+            Form23x.THE_ONE, true, "div-double");
+
+    public static final Dop REM_DOUBLE =
+        new Dop(DalvOps.REM_DOUBLE, DalvOps.REM_DOUBLE,
+            Form23x.THE_ONE, true, "rem-double");
+
+    public static final Dop ADD_INT_2ADDR =
+        new Dop(DalvOps.ADD_INT_2ADDR, DalvOps.ADD_INT,
+            Form12x.THE_ONE, true, "add-int/2addr");
+
+    public static final Dop SUB_INT_2ADDR =
+        new Dop(DalvOps.SUB_INT_2ADDR, DalvOps.SUB_INT,
+            Form12x.THE_ONE, true, "sub-int/2addr");
+
+    public static final Dop MUL_INT_2ADDR =
+        new Dop(DalvOps.MUL_INT_2ADDR, DalvOps.MUL_INT,
+            Form12x.THE_ONE, true, "mul-int/2addr");
+
+    public static final Dop DIV_INT_2ADDR =
+        new Dop(DalvOps.DIV_INT_2ADDR, DalvOps.DIV_INT,
+            Form12x.THE_ONE, true, "div-int/2addr");
+
+    public static final Dop REM_INT_2ADDR =
+        new Dop(DalvOps.REM_INT_2ADDR, DalvOps.REM_INT,
+            Form12x.THE_ONE, true, "rem-int/2addr");
+
+    public static final Dop AND_INT_2ADDR =
+        new Dop(DalvOps.AND_INT_2ADDR, DalvOps.AND_INT,
+            Form12x.THE_ONE, true, "and-int/2addr");
+
+    public static final Dop OR_INT_2ADDR =
+        new Dop(DalvOps.OR_INT_2ADDR, DalvOps.OR_INT,
+            Form12x.THE_ONE, true, "or-int/2addr");
+
+    public static final Dop XOR_INT_2ADDR =
+        new Dop(DalvOps.XOR_INT_2ADDR, DalvOps.XOR_INT,
+            Form12x.THE_ONE, true, "xor-int/2addr");
+
+    public static final Dop SHL_INT_2ADDR =
+        new Dop(DalvOps.SHL_INT_2ADDR, DalvOps.SHL_INT,
+            Form12x.THE_ONE, true, "shl-int/2addr");
+
+    public static final Dop SHR_INT_2ADDR =
+        new Dop(DalvOps.SHR_INT_2ADDR, DalvOps.SHR_INT,
+            Form12x.THE_ONE, true, "shr-int/2addr");
+
+    public static final Dop USHR_INT_2ADDR =
+        new Dop(DalvOps.USHR_INT_2ADDR, DalvOps.USHR_INT,
+            Form12x.THE_ONE, true, "ushr-int/2addr");
+
+    public static final Dop ADD_LONG_2ADDR =
+        new Dop(DalvOps.ADD_LONG_2ADDR, DalvOps.ADD_LONG,
+            Form12x.THE_ONE, true, "add-long/2addr");
+
+    public static final Dop SUB_LONG_2ADDR =
+        new Dop(DalvOps.SUB_LONG_2ADDR, DalvOps.SUB_LONG,
+            Form12x.THE_ONE, true, "sub-long/2addr");
+
+    public static final Dop MUL_LONG_2ADDR =
+        new Dop(DalvOps.MUL_LONG_2ADDR, DalvOps.MUL_LONG,
+            Form12x.THE_ONE, true, "mul-long/2addr");
+
+    public static final Dop DIV_LONG_2ADDR =
+        new Dop(DalvOps.DIV_LONG_2ADDR, DalvOps.DIV_LONG,
+            Form12x.THE_ONE, true, "div-long/2addr");
+
+    public static final Dop REM_LONG_2ADDR =
+        new Dop(DalvOps.REM_LONG_2ADDR, DalvOps.REM_LONG,
+            Form12x.THE_ONE, true, "rem-long/2addr");
+
+    public static final Dop AND_LONG_2ADDR =
+        new Dop(DalvOps.AND_LONG_2ADDR, DalvOps.AND_LONG,
+            Form12x.THE_ONE, true, "and-long/2addr");
+
+    public static final Dop OR_LONG_2ADDR =
+        new Dop(DalvOps.OR_LONG_2ADDR, DalvOps.OR_LONG,
+            Form12x.THE_ONE, true, "or-long/2addr");
+
+    public static final Dop XOR_LONG_2ADDR =
+        new Dop(DalvOps.XOR_LONG_2ADDR, DalvOps.XOR_LONG,
+            Form12x.THE_ONE, true, "xor-long/2addr");
+
+    public static final Dop SHL_LONG_2ADDR =
+        new Dop(DalvOps.SHL_LONG_2ADDR, DalvOps.SHL_LONG,
+            Form12x.THE_ONE, true, "shl-long/2addr");
+
+    public static final Dop SHR_LONG_2ADDR =
+        new Dop(DalvOps.SHR_LONG_2ADDR, DalvOps.SHR_LONG,
+            Form12x.THE_ONE, true, "shr-long/2addr");
+
+    public static final Dop USHR_LONG_2ADDR =
+        new Dop(DalvOps.USHR_LONG_2ADDR, DalvOps.USHR_LONG,
+            Form12x.THE_ONE, true, "ushr-long/2addr");
+
+    public static final Dop ADD_FLOAT_2ADDR =
+        new Dop(DalvOps.ADD_FLOAT_2ADDR, DalvOps.ADD_FLOAT,
+            Form12x.THE_ONE, true, "add-float/2addr");
+
+    public static final Dop SUB_FLOAT_2ADDR =
+        new Dop(DalvOps.SUB_FLOAT_2ADDR, DalvOps.SUB_FLOAT,
+            Form12x.THE_ONE, true, "sub-float/2addr");
+
+    public static final Dop MUL_FLOAT_2ADDR =
+        new Dop(DalvOps.MUL_FLOAT_2ADDR, DalvOps.MUL_FLOAT,
+            Form12x.THE_ONE, true, "mul-float/2addr");
+
+    public static final Dop DIV_FLOAT_2ADDR =
+        new Dop(DalvOps.DIV_FLOAT_2ADDR, DalvOps.DIV_FLOAT,
+            Form12x.THE_ONE, true, "div-float/2addr");
+
+    public static final Dop REM_FLOAT_2ADDR =
+        new Dop(DalvOps.REM_FLOAT_2ADDR, DalvOps.REM_FLOAT,
+            Form12x.THE_ONE, true, "rem-float/2addr");
+
+    public static final Dop ADD_DOUBLE_2ADDR =
+        new Dop(DalvOps.ADD_DOUBLE_2ADDR, DalvOps.ADD_DOUBLE,
+            Form12x.THE_ONE, true, "add-double/2addr");
+
+    public static final Dop SUB_DOUBLE_2ADDR =
+        new Dop(DalvOps.SUB_DOUBLE_2ADDR, DalvOps.SUB_DOUBLE,
+            Form12x.THE_ONE, true, "sub-double/2addr");
+
+    public static final Dop MUL_DOUBLE_2ADDR =
+        new Dop(DalvOps.MUL_DOUBLE_2ADDR, DalvOps.MUL_DOUBLE,
+            Form12x.THE_ONE, true, "mul-double/2addr");
+
+    public static final Dop DIV_DOUBLE_2ADDR =
+        new Dop(DalvOps.DIV_DOUBLE_2ADDR, DalvOps.DIV_DOUBLE,
+            Form12x.THE_ONE, true, "div-double/2addr");
+
+    public static final Dop REM_DOUBLE_2ADDR =
+        new Dop(DalvOps.REM_DOUBLE_2ADDR, DalvOps.REM_DOUBLE,
+            Form12x.THE_ONE, true, "rem-double/2addr");
+
+    public static final Dop ADD_INT_LIT16 =
+        new Dop(DalvOps.ADD_INT_LIT16, DalvOps.ADD_INT,
+            Form22s.THE_ONE, true, "add-int/lit16");
+
+    public static final Dop RSUB_INT =
+        new Dop(DalvOps.RSUB_INT, DalvOps.RSUB_INT,
+            Form22s.THE_ONE, true, "rsub-int");
+
+    public static final Dop MUL_INT_LIT16 =
+        new Dop(DalvOps.MUL_INT_LIT16, DalvOps.MUL_INT,
+            Form22s.THE_ONE, true, "mul-int/lit16");
+
+    public static final Dop DIV_INT_LIT16 =
+        new Dop(DalvOps.DIV_INT_LIT16, DalvOps.DIV_INT,
+            Form22s.THE_ONE, true, "div-int/lit16");
+
+    public static final Dop REM_INT_LIT16 =
+        new Dop(DalvOps.REM_INT_LIT16, DalvOps.REM_INT,
+            Form22s.THE_ONE, true, "rem-int/lit16");
+
+    public static final Dop AND_INT_LIT16 =
+        new Dop(DalvOps.AND_INT_LIT16, DalvOps.AND_INT,
+            Form22s.THE_ONE, true, "and-int/lit16");
+
+    public static final Dop OR_INT_LIT16 =
+        new Dop(DalvOps.OR_INT_LIT16, DalvOps.OR_INT,
+            Form22s.THE_ONE, true, "or-int/lit16");
+
+    public static final Dop XOR_INT_LIT16 =
+        new Dop(DalvOps.XOR_INT_LIT16, DalvOps.XOR_INT,
+            Form22s.THE_ONE, true, "xor-int/lit16");
+
+    public static final Dop ADD_INT_LIT8 =
+        new Dop(DalvOps.ADD_INT_LIT8, DalvOps.ADD_INT,
+            Form22b.THE_ONE, true, "add-int/lit8");
+
+    public static final Dop RSUB_INT_LIT8 =
+        new Dop(DalvOps.RSUB_INT_LIT8, DalvOps.RSUB_INT,
+            Form22b.THE_ONE, true, "rsub-int/lit8");
+
+    public static final Dop MUL_INT_LIT8 =
+        new Dop(DalvOps.MUL_INT_LIT8, DalvOps.MUL_INT,
+            Form22b.THE_ONE, true, "mul-int/lit8");
+
+    public static final Dop DIV_INT_LIT8 =
+        new Dop(DalvOps.DIV_INT_LIT8, DalvOps.DIV_INT,
+            Form22b.THE_ONE, true, "div-int/lit8");
+
+    public static final Dop REM_INT_LIT8 =
+        new Dop(DalvOps.REM_INT_LIT8, DalvOps.REM_INT,
+            Form22b.THE_ONE, true, "rem-int/lit8");
+
+    public static final Dop AND_INT_LIT8 =
+        new Dop(DalvOps.AND_INT_LIT8, DalvOps.AND_INT,
+            Form22b.THE_ONE, true, "and-int/lit8");
+
+    public static final Dop OR_INT_LIT8 =
+        new Dop(DalvOps.OR_INT_LIT8, DalvOps.OR_INT,
+            Form22b.THE_ONE, true, "or-int/lit8");
+
+    public static final Dop XOR_INT_LIT8 =
+        new Dop(DalvOps.XOR_INT_LIT8, DalvOps.XOR_INT,
+            Form22b.THE_ONE, true, "xor-int/lit8");
+
+    public static final Dop SHL_INT_LIT8 =
+        new Dop(DalvOps.SHL_INT_LIT8, DalvOps.SHL_INT,
+            Form22b.THE_ONE, true, "shl-int/lit8");
+
+    public static final Dop SHR_INT_LIT8 =
+        new Dop(DalvOps.SHR_INT_LIT8, DalvOps.SHR_INT,
+            Form22b.THE_ONE, true, "shr-int/lit8");
+
+    public static final Dop USHR_INT_LIT8 =
+        new Dop(DalvOps.USHR_INT_LIT8, DalvOps.USHR_INT,
+            Form22b.THE_ONE, true, "ushr-int/lit8");
+
+    // END(dops)
+
+    // Static initialization.
+    static {
+        DOPS = new Dop[DalvOps.MAX_VALUE - DalvOps.MIN_VALUE + 1];
+
+        set(SPECIAL_FORMAT);
+
+        // BEGIN(dops-init); GENERATED AUTOMATICALLY BY opcode-gen
+        set(NOP);
+        set(MOVE);
+        set(MOVE_FROM16);
+        set(MOVE_16);
+        set(MOVE_WIDE);
+        set(MOVE_WIDE_FROM16);
+        set(MOVE_WIDE_16);
+        set(MOVE_OBJECT);
+        set(MOVE_OBJECT_FROM16);
+        set(MOVE_OBJECT_16);
+        set(MOVE_RESULT);
+        set(MOVE_RESULT_WIDE);
+        set(MOVE_RESULT_OBJECT);
+        set(MOVE_EXCEPTION);
+        set(RETURN_VOID);
+        set(RETURN);
+        set(RETURN_WIDE);
+        set(RETURN_OBJECT);
+        set(CONST_4);
+        set(CONST_16);
+        set(CONST);
+        set(CONST_HIGH16);
+        set(CONST_WIDE_16);
+        set(CONST_WIDE_32);
+        set(CONST_WIDE);
+        set(CONST_WIDE_HIGH16);
+        set(CONST_STRING);
+        set(CONST_STRING_JUMBO);
+        set(CONST_CLASS);
+        set(MONITOR_ENTER);
+        set(MONITOR_EXIT);
+        set(CHECK_CAST);
+        set(INSTANCE_OF);
+        set(ARRAY_LENGTH);
+        set(NEW_INSTANCE);
+        set(NEW_ARRAY);
+        set(FILLED_NEW_ARRAY);
+        set(FILLED_NEW_ARRAY_RANGE);
+        set(FILL_ARRAY_DATA);
+        set(THROW);
+        set(GOTO);
+        set(GOTO_16);
+        set(GOTO_32);
+        set(PACKED_SWITCH);
+        set(SPARSE_SWITCH);
+        set(CMPL_FLOAT);
+        set(CMPG_FLOAT);
+        set(CMPL_DOUBLE);
+        set(CMPG_DOUBLE);
+        set(CMP_LONG);
+        set(IF_EQ);
+        set(IF_NE);
+        set(IF_LT);
+        set(IF_GE);
+        set(IF_GT);
+        set(IF_LE);
+        set(IF_EQZ);
+        set(IF_NEZ);
+        set(IF_LTZ);
+        set(IF_GEZ);
+        set(IF_GTZ);
+        set(IF_LEZ);
+        set(AGET);
+        set(AGET_WIDE);
+        set(AGET_OBJECT);
+        set(AGET_BOOLEAN);
+        set(AGET_BYTE);
+        set(AGET_CHAR);
+        set(AGET_SHORT);
+        set(APUT);
+        set(APUT_WIDE);
+        set(APUT_OBJECT);
+        set(APUT_BOOLEAN);
+        set(APUT_BYTE);
+        set(APUT_CHAR);
+        set(APUT_SHORT);
+        set(IGET);
+        set(IGET_WIDE);
+        set(IGET_OBJECT);
+        set(IGET_BOOLEAN);
+        set(IGET_BYTE);
+        set(IGET_CHAR);
+        set(IGET_SHORT);
+        set(IPUT);
+        set(IPUT_WIDE);
+        set(IPUT_OBJECT);
+        set(IPUT_BOOLEAN);
+        set(IPUT_BYTE);
+        set(IPUT_CHAR);
+        set(IPUT_SHORT);
+        set(SGET);
+        set(SGET_WIDE);
+        set(SGET_OBJECT);
+        set(SGET_BOOLEAN);
+        set(SGET_BYTE);
+        set(SGET_CHAR);
+        set(SGET_SHORT);
+        set(SPUT);
+        set(SPUT_WIDE);
+        set(SPUT_OBJECT);
+        set(SPUT_BOOLEAN);
+        set(SPUT_BYTE);
+        set(SPUT_CHAR);
+        set(SPUT_SHORT);
+        set(INVOKE_VIRTUAL);
+        set(INVOKE_SUPER);
+        set(INVOKE_DIRECT);
+        set(INVOKE_STATIC);
+        set(INVOKE_INTERFACE);
+        set(INVOKE_VIRTUAL_RANGE);
+        set(INVOKE_SUPER_RANGE);
+        set(INVOKE_DIRECT_RANGE);
+        set(INVOKE_STATIC_RANGE);
+        set(INVOKE_INTERFACE_RANGE);
+        set(NEG_INT);
+        set(NOT_INT);
+        set(NEG_LONG);
+        set(NOT_LONG);
+        set(NEG_FLOAT);
+        set(NEG_DOUBLE);
+        set(INT_TO_LONG);
+        set(INT_TO_FLOAT);
+        set(INT_TO_DOUBLE);
+        set(LONG_TO_INT);
+        set(LONG_TO_FLOAT);
+        set(LONG_TO_DOUBLE);
+        set(FLOAT_TO_INT);
+        set(FLOAT_TO_LONG);
+        set(FLOAT_TO_DOUBLE);
+        set(DOUBLE_TO_INT);
+        set(DOUBLE_TO_LONG);
+        set(DOUBLE_TO_FLOAT);
+        set(INT_TO_BYTE);
+        set(INT_TO_CHAR);
+        set(INT_TO_SHORT);
+        set(ADD_INT);
+        set(SUB_INT);
+        set(MUL_INT);
+        set(DIV_INT);
+        set(REM_INT);
+        set(AND_INT);
+        set(OR_INT);
+        set(XOR_INT);
+        set(SHL_INT);
+        set(SHR_INT);
+        set(USHR_INT);
+        set(ADD_LONG);
+        set(SUB_LONG);
+        set(MUL_LONG);
+        set(DIV_LONG);
+        set(REM_LONG);
+        set(AND_LONG);
+        set(OR_LONG);
+        set(XOR_LONG);
+        set(SHL_LONG);
+        set(SHR_LONG);
+        set(USHR_LONG);
+        set(ADD_FLOAT);
+        set(SUB_FLOAT);
+        set(MUL_FLOAT);
+        set(DIV_FLOAT);
+        set(REM_FLOAT);
+        set(ADD_DOUBLE);
+        set(SUB_DOUBLE);
+        set(MUL_DOUBLE);
+        set(DIV_DOUBLE);
+        set(REM_DOUBLE);
+        set(ADD_INT_2ADDR);
+        set(SUB_INT_2ADDR);
+        set(MUL_INT_2ADDR);
+        set(DIV_INT_2ADDR);
+        set(REM_INT_2ADDR);
+        set(AND_INT_2ADDR);
+        set(OR_INT_2ADDR);
+        set(XOR_INT_2ADDR);
+        set(SHL_INT_2ADDR);
+        set(SHR_INT_2ADDR);
+        set(USHR_INT_2ADDR);
+        set(ADD_LONG_2ADDR);
+        set(SUB_LONG_2ADDR);
+        set(MUL_LONG_2ADDR);
+        set(DIV_LONG_2ADDR);
+        set(REM_LONG_2ADDR);
+        set(AND_LONG_2ADDR);
+        set(OR_LONG_2ADDR);
+        set(XOR_LONG_2ADDR);
+        set(SHL_LONG_2ADDR);
+        set(SHR_LONG_2ADDR);
+        set(USHR_LONG_2ADDR);
+        set(ADD_FLOAT_2ADDR);
+        set(SUB_FLOAT_2ADDR);
+        set(MUL_FLOAT_2ADDR);
+        set(DIV_FLOAT_2ADDR);
+        set(REM_FLOAT_2ADDR);
+        set(ADD_DOUBLE_2ADDR);
+        set(SUB_DOUBLE_2ADDR);
+        set(MUL_DOUBLE_2ADDR);
+        set(DIV_DOUBLE_2ADDR);
+        set(REM_DOUBLE_2ADDR);
+        set(ADD_INT_LIT16);
+        set(RSUB_INT);
+        set(MUL_INT_LIT16);
+        set(DIV_INT_LIT16);
+        set(REM_INT_LIT16);
+        set(AND_INT_LIT16);
+        set(OR_INT_LIT16);
+        set(XOR_INT_LIT16);
+        set(ADD_INT_LIT8);
+        set(RSUB_INT_LIT8);
+        set(MUL_INT_LIT8);
+        set(DIV_INT_LIT8);
+        set(REM_INT_LIT8);
+        set(AND_INT_LIT8);
+        set(OR_INT_LIT8);
+        set(XOR_INT_LIT8);
+        set(SHL_INT_LIT8);
+        set(SHR_INT_LIT8);
+        set(USHR_INT_LIT8);
+        // END(dops-init)
+    }
+
+    /**
+     * This class is uninstantiable.
+     */
+    private Dops() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Gets the {@link Dop} for the given opcode value.
+     *
+     * @param opcode {@code DalvOps.MIN_VALUE..DalvOps.MAX_VALUE;} the opcode value
+     * @return {@code non-null;} the associated opcode instance
+     */
+    public static Dop get(int opcode) {
+        int idx = opcode - DalvOps.MIN_VALUE;
+
+        try {
+            Dop result = DOPS[idx];
+            if (result != null) {
+                return result;
+            }
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            // Fall through.
+        }
+
+        throw new IllegalArgumentException("bogus opcode");
+    }
+
+    /**
+     * Gets the {@link Dop} with the given family/format combination, if
+     * any.
+     *
+     * @param family {@code DalvOps.MIN_VALUE..DalvOps.MAX_VALUE;} the opcode family
+     * @param format {@code non-null;} the opcode's instruction format
+     * @return {@code null-ok;} the corresponding opcode, or {@code null} if
+     * there is none
+     */
+    public static Dop getOrNull(int family, InsnFormat format) {
+        if (format == null) {
+            throw new NullPointerException("format == null");
+        }
+
+        int len = DOPS.length;
+
+        // TODO: Linear search is bad.
+        for (int i = 0; i < len; i++) {
+            Dop dop = DOPS[i];
+            if ((dop != null) &&
+                (dop.getFamily() == family) &&
+                (dop.getFormat() == format)) {
+                return dop;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Puts the given opcode into the table of all ops.
+     *
+     * @param opcode {@code non-null;} the opcode
+     */
+    private static void set(Dop opcode) {
+        int idx = opcode.getOpcode() - DalvOps.MIN_VALUE;
+        DOPS[idx] = opcode;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/FixedSizeInsn.java b/dexgen/src/com/android/dexgen/dex/code/FixedSizeInsn.java
new file mode 100644
index 0000000..28d8986
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/FixedSizeInsn.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.SourcePosition;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Base class for instructions which are of a fixed code size and which use {@link InsnFormat} methods to write themselves. This
+ * includes most &mdash; but not all &mdash; instructions.
+ */
+public abstract class FixedSizeInsn extends DalvInsn {
+    /**
+     * Constructs an instance. The output address of this instance is initially
+     * unknown ({@code -1}).
+     *
+     * <p><b>Note:</b> In the unlikely event that an instruction takes
+     * absolutely no registers (e.g., a {@code nop} or a
+     * no-argument no-result * static method call), then the given
+     * register list may be passed as {@link
+     * RegisterSpecList#EMPTY}.</p>
+     *
+     * @param opcode the opcode; one of the constants from {@link Dops}
+     * @param position {@code non-null;} source position
+     * @param registers {@code non-null;} register list, including a
+     * result register if appropriate (that is, registers may be either
+     * ins or outs)
+     */
+    public FixedSizeInsn(Dop opcode, SourcePosition position,
+                         RegisterSpecList registers) {
+        super(opcode, position, registers);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final int codeSize() {
+        return getOpcode().getFormat().codeSize();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final void writeTo(AnnotatedOutput out) {
+        getOpcode().getFormat().writeTo(out, this);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final DalvInsn withRegisterOffset(int delta) {
+        return withRegisters(getRegisters().withOffset(delta));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected final String listingString0(boolean noteIndices) {
+        return getOpcode().getFormat().listingString(this, noteIndices);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/HighRegisterPrefix.java b/dexgen/src/com/android/dexgen/dex/code/HighRegisterPrefix.java
new file mode 100644
index 0000000..08794ff
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/HighRegisterPrefix.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.SourcePosition;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Combination instruction which turns into a variable number of
+ * {@code move*} instructions to move a set of registers into
+ * registers starting at {@code 0} sequentially. This is used
+ * in translating an instruction whose register requirements cannot
+ * be met using a straightforward choice of a single opcode.
+ */
+public final class HighRegisterPrefix extends VariableSizeInsn {
+    /** {@code null-ok;} cached instructions, if constructed */
+    private SimpleInsn[] insns;
+
+    /**
+     * Constructs an instance. The output address of this instance is initially
+     * unknown ({@code -1}).
+     *
+     * @param position {@code non-null;} source position
+     * @param registers {@code non-null;} source registers
+     */
+    public HighRegisterPrefix(SourcePosition position,
+                              RegisterSpecList registers) {
+        super(position, registers);
+
+        if (registers.size() == 0) {
+            throw new IllegalArgumentException("registers.size() == 0");
+        }
+
+        insns = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        int result = 0;
+
+        calculateInsnsIfNecessary();
+
+        for (SimpleInsn insn : insns) {
+            result += insn.codeSize();
+        }
+
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out) {
+        calculateInsnsIfNecessary();
+
+        for (SimpleInsn insn : insns) {
+            insn.writeTo(out);
+        }
+    }
+
+    /**
+     * Helper for {@link #codeSize} and {@link #writeTo} which sets up
+     * {@link #insns} if not already done.
+     */
+    private void calculateInsnsIfNecessary() {
+        if (insns != null) {
+            return;
+        }
+
+        RegisterSpecList registers = getRegisters();
+        int sz = registers.size();
+
+        insns = new SimpleInsn[sz];
+
+        for (int i = 0, outAt = 0; i < sz; i++) {
+            RegisterSpec src = registers.get(i);
+            insns[i] = moveInsnFor(src, outAt);
+            outAt += src.getCategory();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withRegisters(RegisterSpecList registers) {
+        return new HighRegisterPrefix(getPosition(), registers);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String argString() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String listingString0(boolean noteIndices) {
+        RegisterSpecList registers = getRegisters();
+        int sz = registers.size();
+        StringBuffer sb = new StringBuffer(100);
+
+        for (int i = 0, outAt = 0; i < sz; i++) {
+            RegisterSpec src = registers.get(i);
+            SimpleInsn insn = moveInsnFor(src, outAt);
+
+            if (i != 0) {
+                sb.append('\n');
+            }
+
+            sb.append(insn.listingString0(noteIndices));
+
+            outAt += src.getCategory();
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Returns the proper move instruction for the given source spec
+     * and destination index.
+     *
+     * @param src {@code non-null;} the source register spec
+     * @param destIndex {@code >= 0;} the destination register index
+     * @return {@code non-null;} the appropriate move instruction
+     */
+    private static SimpleInsn moveInsnFor(RegisterSpec src, int destIndex) {
+        return DalvInsn.makeMove(SourcePosition.NO_INFO,
+                RegisterSpec.make(destIndex, src.getType()),
+                src);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/InsnFormat.java b/dexgen/src/com/android/dexgen/dex/code/InsnFormat.java
new file mode 100644
index 0000000..bd5b60d
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/InsnFormat.java
@@ -0,0 +1,578 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstInteger;
+import com.android.dexgen.rop.cst.CstKnownNull;
+import com.android.dexgen.rop.cst.CstLiteral64;
+import com.android.dexgen.rop.cst.CstLiteralBits;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Base class for all instruction format handlers. Instruction format
+ * handlers know how to translate {@link DalvInsn} instances into
+ * streams of code words, as well as human-oriented listing strings
+ * representing such translations.
+ */
+public abstract class InsnFormat {
+    /**
+     * Returns the string form, suitable for inclusion in a listing
+     * dump, of the given instruction. The instruction must be of this
+     * instance's format for proper operation.
+     *
+     * @param insn {@code non-null;} the instruction
+     * @param noteIndices whether to include an explicit notation of
+     * constant pool indices
+     * @return {@code non-null;} the string form
+     */
+    public final String listingString(DalvInsn insn, boolean noteIndices) {
+        String op = insn.getOpcode().getName();
+        String arg = insnArgString(insn);
+        String comment = insnCommentString(insn, noteIndices);
+        StringBuilder sb = new StringBuilder(100);
+
+        sb.append(op);
+
+        if (arg.length() != 0) {
+            sb.append(' ');
+            sb.append(arg);
+        }
+
+        if (comment.length() != 0) {
+            sb.append(" // ");
+            sb.append(comment);
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Returns the string form of the arguments to the given instruction.
+     * The instruction must be of this instance's format. If the instruction
+     * has no arguments, then the result should be {@code ""}, not
+     * {@code null}.
+     *
+     * <p>Subclasses must override this method.</p>
+     *
+     * @param insn {@code non-null;} the instruction
+     * @return {@code non-null;} the string form
+     */
+    public abstract String insnArgString(DalvInsn insn);
+
+    /**
+     * Returns the associated comment for the given instruction, if any.
+     * The instruction must be of this instance's format. If the instruction
+     * has no comment, then the result should be {@code ""}, not
+     * {@code null}.
+     *
+     * <p>Subclasses must override this method.</p>
+     *
+     * @param insn {@code non-null;} the instruction
+     * @param noteIndices whether to include an explicit notation of
+     * constant pool indices
+     * @return {@code non-null;} the string form
+     */
+    public abstract String insnCommentString(DalvInsn insn,
+            boolean noteIndices);
+
+    /**
+     * Gets the code size of instructions that use this format. The
+     * size is a number of 16-bit code units, not bytes. This should
+     * throw an exception if this format is of variable size.
+     *
+     * @return {@code >= 0;} the instruction length in 16-bit code units
+     */
+    public abstract int codeSize();
+
+    /**
+     * Returns whether or not the given instruction's arguments will
+     * fit in this instance's format. This includes such things as
+     * counting register arguments, checking register ranges, and
+     * making sure that additional arguments are of appropriate types
+     * and are in-range. If this format has a branch target but the
+     * instruction's branch offset is unknown, this method will simply
+     * not check the offset.
+     *
+     * <p>Subclasses must override this method.</p>
+     *
+     * @param insn {@code non-null;} the instruction to check
+     * @return {@code true} iff the instruction's arguments are
+     * appropriate for this instance, or {@code false} if not
+     */
+    public abstract boolean isCompatible(DalvInsn insn);
+
+    /**
+     * Returns whether or not the given instruction's branch offset will
+     * fit in this instance's format. This always returns {@code false}
+     * for formats that don't include a branch offset.
+     *
+     * <p>The default implementation of this method always returns
+     * {@code false}. Subclasses must override this method if they
+     * include branch offsets.</p>
+     *
+     * @param insn {@code non-null;} the instruction to check
+     * @return {@code true} iff the instruction's branch offset is
+     * appropriate for this instance, or {@code false} if not
+     */
+    public boolean branchFits(TargetInsn insn) {
+        return false;
+    }
+
+    /**
+     * Returns the next instruction format to try to match an instruction
+     * with, presuming that this instance isn't compatible, if any.
+     *
+     * <p>Subclasses must override this method.</p>
+     *
+     * @return {@code null-ok;} the next format to try, or {@code null} if
+     * there are no suitable alternatives
+     */
+    public abstract InsnFormat nextUp();
+
+    /**
+     * Writes the code units for the given instruction to the given
+     * output destination. The instruction must be of this instance's format.
+     *
+     * <p>Subclasses must override this method.</p>
+     *
+     * @param out {@code non-null;} the output destination to write to
+     * @param insn {@code non-null;} the instruction to write
+     */
+    public abstract void writeTo(AnnotatedOutput out, DalvInsn insn);
+
+    /**
+     * Helper method to return a register list string.
+     *
+     * @param list {@code non-null;} the list of registers
+     * @return {@code non-null;} the string form
+     */
+    protected static String regListString(RegisterSpecList list) {
+        int sz = list.size();
+        StringBuffer sb = new StringBuffer(sz * 5 + 2);
+
+        sb.append('{');
+
+        for (int i = 0; i < sz; i++) {
+            if (i != 0) {
+                sb.append(", ");
+            }
+            sb.append(list.get(i).regString());
+        }
+
+        sb.append('}');
+
+        return sb.toString();
+    }
+
+    /**
+     * Helper method to return a literal bits argument string.
+     *
+     * @param value the value
+     * @return {@code non-null;} the string form
+     */
+    protected static String literalBitsString(CstLiteralBits value) {
+        StringBuffer sb = new StringBuffer(100);
+
+        sb.append('#');
+
+        if (value instanceof CstKnownNull) {
+            sb.append("null");
+        } else {
+            sb.append(value.typeName());
+            sb.append(' ');
+            sb.append(value.toHuman());
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Helper method to return a literal bits comment string.
+     *
+     * @param value the value
+     * @param width the width of the constant, in bits (used for displaying
+     * the uninterpreted bits; one of: {@code 4 8 16 32 64}
+     * @return {@code non-null;} the comment
+     */
+    protected static String literalBitsComment(CstLiteralBits value,
+            int width) {
+        StringBuffer sb = new StringBuffer(20);
+
+        sb.append("#");
+
+        long bits;
+
+        if (value instanceof CstLiteral64) {
+            bits = ((CstLiteral64) value).getLongBits();
+        } else {
+            bits = value.getIntBits();
+        }
+
+        switch (width) {
+            case 4:  sb.append(Hex.uNibble((int) bits)); break;
+            case 8:  sb.append(Hex.u1((int) bits));      break;
+            case 16: sb.append(Hex.u2((int) bits));      break;
+            case 32: sb.append(Hex.u4((int) bits));      break;
+            case 64: sb.append(Hex.u8(bits));            break;
+            default: {
+                throw new RuntimeException("shouldn't happen");
+            }
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Helper method to return a branch address string.
+     *
+     * @param insn {@code non-null;} the instruction in question
+     * @return {@code non-null;} the string form of the instruction's branch target
+     */
+    protected static String branchString(DalvInsn insn) {
+        TargetInsn ti = (TargetInsn) insn;
+        int address = ti.getTargetAddress();
+
+        return (address == (char) address) ? Hex.u2(address) : Hex.u4(address);
+    }
+
+    /**
+     * Helper method to return the comment for a branch.
+     *
+     * @param insn {@code non-null;} the instruction in question
+     * @return {@code non-null;} the comment
+     */
+    protected static String branchComment(DalvInsn insn) {
+        TargetInsn ti = (TargetInsn) insn;
+        int offset = ti.getTargetOffset();
+
+        return (offset == (short) offset) ? Hex.s2(offset) : Hex.s4(offset);
+    }
+
+    /**
+     * Helper method to return a constant string.
+     *
+     * @param insn {@code non-null;} a constant-bearing instruction
+     * @return {@code non-null;} the string form of the contained constant
+     */
+    protected static String cstString(DalvInsn insn) {
+        CstInsn ci = (CstInsn) insn;
+        Constant cst = ci.getConstant();
+
+        return cst.toHuman();
+    }
+
+    /**
+     * Helper method to return an instruction comment for a constant.
+     *
+     * @param insn {@code non-null;} a constant-bearing instruction
+     * @return {@code non-null;} comment string representing the constant
+     */
+    protected static String cstComment(DalvInsn insn) {
+        CstInsn ci = (CstInsn) insn;
+
+        if (! ci.hasIndex()) {
+            return "";
+        }
+
+        StringBuilder sb = new StringBuilder(20);
+        int index = ci.getIndex();
+
+        sb.append(ci.getConstant().typeName());
+        sb.append('@');
+
+        if (index < 65536) {
+            sb.append(Hex.u2(index));
+        } else {
+            sb.append(Hex.u4(index));
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Helper method to determine if a signed int value fits in a nibble.
+     *
+     * @param value the value in question
+     * @return {@code true} iff it's in the range -8..+7
+     */
+    protected static boolean signedFitsInNibble(int value) {
+        return (value >= -8) && (value <= 7);
+    }
+
+    /**
+     * Helper method to determine if an unsigned int value fits in a nibble.
+     *
+     * @param value the value in question
+     * @return {@code true} iff it's in the range 0..0xf
+     */
+    protected static boolean unsignedFitsInNibble(int value) {
+        return value == (value & 0xf);
+    }
+
+    /**
+     * Helper method to determine if a signed int value fits in a byte.
+     *
+     * @param value the value in question
+     * @return {@code true} iff it's in the range -0x80..+0x7f
+     */
+    protected static boolean signedFitsInByte(int value) {
+        return (byte) value == value;
+    }
+
+    /**
+     * Helper method to determine if an unsigned int value fits in a byte.
+     *
+     * @param value the value in question
+     * @return {@code true} iff it's in the range 0..0xff
+     */
+    protected static boolean unsignedFitsInByte(int value) {
+        return value == (value & 0xff);
+    }
+
+    /**
+     * Helper method to determine if a signed int value fits in a short.
+     *
+     * @param value the value in question
+     * @return {@code true} iff it's in the range -0x8000..+0x7fff
+     */
+    protected static boolean signedFitsInShort(int value) {
+        return (short) value == value;
+    }
+
+    /**
+     * Helper method to determine if an unsigned int value fits in a short.
+     *
+     * @param value the value in question
+     * @return {@code true} iff it's in the range 0..0xffff
+     */
+    protected static boolean unsignedFitsInShort(int value) {
+        return value == (value & 0xffff);
+    }
+
+    /**
+     * Helper method to determine if a signed int value fits in three bytes.
+     *
+     * @param value the value in question
+     * @return {@code true} iff it's in the range -0x800000..+0x7fffff
+     */
+    protected static boolean signedFitsIn3Bytes(int value) {
+        return value == ((value << 8) >> 8);
+    }
+
+    /**
+     * Helper method to extract the callout-argument index from an
+     * appropriate instruction.
+     *
+     * @param insn {@code non-null;} the instruction
+     * @return {@code >= 0;} the callout argument index
+     */
+    protected static int argIndex(DalvInsn insn) {
+        int arg = ((CstInteger) ((CstInsn) insn).getConstant()).getValue();
+
+        if (arg < 0) {
+            throw new IllegalArgumentException("bogus insn");
+        }
+
+        return arg;
+    }
+
+    /**
+     * Helper method to combine an opcode and a second byte of data into
+     * the appropriate form for emitting into a code buffer.
+     *
+     * @param insn {@code non-null;} the instruction containing the opcode
+     * @param arg {@code 0..255;} arbitrary other byte value
+     * @return combined value
+     */
+    protected static short opcodeUnit(DalvInsn insn, int arg) {
+        if ((arg & 0xff) != arg) {
+            throw new IllegalArgumentException("arg out of range 0..255");
+        }
+
+        int opcode = insn.getOpcode().getOpcode();
+
+        if ((opcode & 0xff) != opcode) {
+            throw new IllegalArgumentException("opcode out of range 0..255");
+        }
+
+        return (short) (opcode | (arg << 8));
+    }
+
+    /**
+     * Helper method to combine two bytes into a code unit.
+     *
+     * @param low {@code 0..255;} low byte
+     * @param high {@code 0..255;} high byte
+     * @return combined value
+     */
+    protected static short codeUnit(int low, int high) {
+        if ((low & 0xff) != low) {
+            throw new IllegalArgumentException("low out of range 0..255");
+        }
+
+        if ((high & 0xff) != high) {
+            throw new IllegalArgumentException("high out of range 0..255");
+        }
+
+        return (short) (low | (high << 8));
+    }
+
+    /**
+     * Helper method to combine four nibbles into a code unit.
+     *
+     * @param n0 {@code 0..15;} low nibble
+     * @param n1 {@code 0..15;} medium-low nibble
+     * @param n2 {@code 0..15;} medium-high nibble
+     * @param n3 {@code 0..15;} high nibble
+     * @return combined value
+     */
+    protected static short codeUnit(int n0, int n1, int n2, int n3) {
+        if ((n0 & 0xf) != n0) {
+            throw new IllegalArgumentException("n0 out of range 0..15");
+        }
+
+        if ((n1 & 0xf) != n1) {
+            throw new IllegalArgumentException("n1 out of range 0..15");
+        }
+
+        if ((n2 & 0xf) != n2) {
+            throw new IllegalArgumentException("n2 out of range 0..15");
+        }
+
+        if ((n3 & 0xf) != n3) {
+            throw new IllegalArgumentException("n3 out of range 0..15");
+        }
+
+        return (short) (n0 | (n1 << 4) | (n2 << 8) | (n3 << 12));
+    }
+
+    /**
+     * Helper method to combine two nibbles into a byte.
+     *
+     * @param low {@code 0..15;} low nibble
+     * @param high {@code 0..15;} high nibble
+     * @return {@code 0..255;} combined value
+     */
+    protected static int makeByte(int low, int high) {
+        if ((low & 0xf) != low) {
+            throw new IllegalArgumentException("low out of range 0..15");
+        }
+
+        if ((high & 0xf) != high) {
+            throw new IllegalArgumentException("high out of range 0..15");
+        }
+
+        return low | (high << 4);
+    }
+
+    /**
+     * Writes one code unit to the given output destination.
+     *
+     * @param out {@code non-null;} where to write to
+     * @param c0 code unit to write
+     */
+    protected static void write(AnnotatedOutput out, short c0) {
+        out.writeShort(c0);
+    }
+
+    /**
+     * Writes two code units to the given output destination.
+     *
+     * @param out {@code non-null;} where to write to
+     * @param c0 code unit to write
+     * @param c1 code unit to write
+     */
+    protected static void write(AnnotatedOutput out, short c0, short c1) {
+        out.writeShort(c0);
+        out.writeShort(c1);
+    }
+
+    /**
+     * Writes three code units to the given output destination.
+     *
+     * @param out {@code non-null;} where to write to
+     * @param c0 code unit to write
+     * @param c1 code unit to write
+     * @param c2 code unit to write
+     */
+    protected static void write(AnnotatedOutput out, short c0, short c1,
+                                short c2) {
+        out.writeShort(c0);
+        out.writeShort(c1);
+        out.writeShort(c2);
+    }
+
+    /**
+     * Writes four code units to the given output destination.
+     *
+     * @param out {@code non-null;} where to write to
+     * @param c0 code unit to write
+     * @param c1 code unit to write
+     * @param c2 code unit to write
+     * @param c3 code unit to write
+     */
+    protected static void write(AnnotatedOutput out, short c0, short c1,
+                                short c2, short c3) {
+        out.writeShort(c0);
+        out.writeShort(c1);
+        out.writeShort(c2);
+        out.writeShort(c3);
+    }
+
+    /**
+     * Writes five code units to the given output destination.
+     *
+     * @param out {@code non-null;} where to write to
+     * @param c0 code unit to write
+     * @param c1 code unit to write
+     * @param c2 code unit to write
+     * @param c3 code unit to write
+     * @param c4 code unit to write
+     */
+    protected static void write(AnnotatedOutput out, short c0, short c1,
+                                short c2, short c3, short c4) {
+        out.writeShort(c0);
+        out.writeShort(c1);
+        out.writeShort(c2);
+        out.writeShort(c3);
+        out.writeShort(c4);
+    }
+
+    /**
+     * Writes six code units to the given output destination.
+     *
+     * @param out {@code non-null;} where to write to
+     * @param c0 code unit to write
+     * @param c1 code unit to write
+     * @param c2 code unit to write
+     * @param c3 code unit to write
+     * @param c4 code unit to write
+     * @param c5 code unit to write
+     */
+    protected static void write(AnnotatedOutput out, short c0, short c1,
+                                short c2, short c3, short c4, short c5) {
+        out.writeShort(c0);
+        out.writeShort(c1);
+        out.writeShort(c2);
+        out.writeShort(c3);
+        out.writeShort(c4);
+        out.writeShort(c5);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/LocalEnd.java b/dexgen/src/com/android/dexgen/dex/code/LocalEnd.java
new file mode 100644
index 0000000..130b08b
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/LocalEnd.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.SourcePosition;
+
+/**
+ * Pseudo-instruction which is used to explicitly end the mapping of a
+ * register to a named local variable. That is, an instance of this
+ * class in an instruction stream indicates that starting with the
+ * subsequent instruction, the indicated variable is no longer valid.
+ */
+public final class LocalEnd extends ZeroSizeInsn {
+    /**
+     * {@code non-null;} register spec representing the local variable ended
+     * by this instance. <b>Note:</b> Technically, only the register
+     * number needs to be recorded here as the rest of the information
+     * is implicit in the ambient local variable state, but other code
+     * will check the other info for consistency.
+     */
+    private final RegisterSpec local;
+
+    /**
+     * Constructs an instance. The output address of this instance is initially
+     * unknown ({@code -1}).
+     *
+     * @param position {@code non-null;} source position
+     * @param local {@code non-null;} register spec representing the local
+     * variable introduced by this instance
+     */
+    public LocalEnd(SourcePosition position, RegisterSpec local) {
+        super(position);
+
+        if (local == null) {
+            throw new NullPointerException("local == null");
+        }
+
+        this.local = local;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withRegisterOffset(int delta) {
+        return new LocalEnd(getPosition(), local.withOffset(delta));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withRegisters(RegisterSpecList registers) {
+        return new LocalEnd(getPosition(), local);
+    }
+
+    /**
+     * Gets the register spec representing the local variable ended
+     * by this instance.
+     *
+     * @return {@code non-null;} the register spec
+     */
+    public RegisterSpec getLocal() {
+        return local;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String argString() {
+        return local.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String listingString0(boolean noteIndices) {
+        return "local-end " + LocalStart.localString(local);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/LocalList.java b/dexgen/src/com/android/dexgen/dex/code/LocalList.java
new file mode 100644
index 0000000..c1c3921
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/LocalList.java
@@ -0,0 +1,948 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.RegisterSpecSet;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.FixedSizeList;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * List of local variables. Each local variable entry indicates a
+ * range of code which it is valid for, a register number, a name,
+ * and a type.
+ */
+public final class LocalList extends FixedSizeList {
+    /** {@code non-null;} empty instance */
+    public static final LocalList EMPTY = new LocalList(0);
+
+    /** whether to run the self-check code */
+    private static final boolean DEBUG = false;
+
+    /**
+     * Constructs an instance. All indices initially contain {@code null}.
+     *
+     * @param size {@code >= 0;} the size of the list
+     */
+    public LocalList(int size) {
+        super(size);
+    }
+
+    /**
+     * Gets the element at the given index. It is an error to call
+     * this with the index for an element which was never set; if you
+     * do that, this will throw {@code NullPointerException}.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @return {@code non-null;} element at that index
+     */
+    public Entry get(int n) {
+        return (Entry) get0(n);
+    }
+
+    /**
+     * Sets the entry at the given index.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @param entry {@code non-null;} the entry to set at {@code n}
+     */
+    public void set(int n, Entry entry) {
+        set0(n, entry);
+    }
+
+    /**
+     * Does a human-friendly dump of this instance.
+     *
+     * @param out {@code non-null;} where to dump
+     * @param prefix {@code non-null;} prefix to attach to each line of output
+     */
+    public void debugPrint(PrintStream out, String prefix) {
+        int sz = size();
+
+        for (int i = 0; i < sz; i++) {
+            out.print(prefix);
+            out.println(get(i));
+        }
+    }
+
+    /**
+     * Disposition of a local entry.
+     */
+    public static enum Disposition {
+        /** local started (introduced) */
+        START,
+
+        /** local ended without being replaced */
+        END_SIMPLY,
+
+        /** local ended because it was directly replaced */
+        END_REPLACED,
+
+        /** local ended because it was moved to a different register */
+        END_MOVED,
+
+        /**
+         * local ended because the previous local clobbered this one
+         * (because it is category-2)
+         */
+        END_CLOBBERED_BY_PREV,
+
+        /**
+         * local ended because the next local clobbered this one
+         * (because this one is a category-2)
+         */
+        END_CLOBBERED_BY_NEXT;
+    }
+
+    /**
+     * Entry in a local list.
+     */
+    public static class Entry implements Comparable<Entry> {
+        /** {@code >= 0;} address */
+        private final int address;
+
+        /** {@code non-null;} disposition of the local */
+        private final Disposition disposition;
+
+        /** {@code non-null;} register spec representing the variable */
+        private final RegisterSpec spec;
+
+        /** {@code non-null;} variable type (derived from {@code spec}) */
+        private final CstType type;
+
+        /**
+         * Constructs an instance.
+         *
+         * @param address {@code >= 0;} address
+         * @param disposition {@code non-null;} disposition of the local
+         * @param spec {@code non-null;} register spec representing
+         * the variable
+         */
+        public Entry(int address, Disposition disposition, RegisterSpec spec) {
+            if (address < 0) {
+                throw new IllegalArgumentException("address < 0");
+            }
+
+            if (disposition == null) {
+                throw new NullPointerException("disposition == null");
+            }
+
+            try {
+                if (spec.getLocalItem() == null) {
+                    throw new NullPointerException(
+                            "spec.getLocalItem() == null");
+                }
+            } catch (NullPointerException ex) {
+                // Elucidate the exception.
+                throw new NullPointerException("spec == null");
+            }
+
+            this.address = address;
+            this.disposition = disposition;
+            this.spec = spec;
+            this.type = CstType.intern(spec.getType());
+        }
+
+        /** {@inheritDoc} */
+        public String toString() {
+            return Integer.toHexString(address) + " " + disposition + " " +
+                spec;
+        }
+
+        /** {@inheritDoc} */
+        public boolean equals(Object other) {
+            if (!(other instanceof Entry)) {
+                return false;
+            }
+
+            return (compareTo((Entry) other) == 0);
+        }
+
+        /**
+         * Compares by (in priority order) address, end then start
+         * disposition (variants of end are all consistered
+         * equivalent), and spec.
+         *
+         * @param other {@code non-null;} entry to compare to
+         * @return {@code -1..1;} standard result of comparison
+         */
+        public int compareTo(Entry other) {
+            if (address < other.address) {
+                return -1;
+            } else if (address > other.address) {
+                return 1;
+            }
+
+            boolean thisIsStart = isStart();
+            boolean otherIsStart = other.isStart();
+
+            if (thisIsStart != otherIsStart) {
+                return thisIsStart ? 1 : -1;
+            }
+
+            return spec.compareTo(other.spec);
+        }
+
+        /**
+         * Gets the address.
+         *
+         * @return {@code >= 0;} the address
+         */
+        public int getAddress() {
+            return address;
+        }
+
+        /**
+         * Gets the disposition.
+         *
+         * @return {@code non-null;} the disposition
+         */
+        public Disposition getDisposition() {
+            return disposition;
+        }
+
+        /**
+         * Gets whether this is a local start. This is just shorthand for
+         * {@code getDisposition() == Disposition.START}.
+         *
+         * @return {@code true} iff this is a start
+         */
+        public boolean isStart() {
+            return disposition == Disposition.START;
+        }
+
+        /**
+         * Gets the variable name.
+         *
+         * @return {@code null-ok;} the variable name
+         */
+        public CstUtf8 getName() {
+            return spec.getLocalItem().getName();
+        }
+
+        /**
+         * Gets the variable signature.
+         *
+         * @return {@code null-ok;} the variable signature
+         */
+        public CstUtf8 getSignature() {
+            return spec.getLocalItem().getSignature();
+        }
+
+        /**
+         * Gets the variable's type.
+         *
+         * @return {@code non-null;} the type
+         */
+        public CstType getType() {
+            return type;
+        }
+
+        /**
+         * Gets the number of the register holding the variable.
+         *
+         * @return {@code >= 0;} the number of the register holding
+         * the variable
+         */
+        public int getRegister() {
+            return spec.getReg();
+        }
+
+        /**
+         * Gets the RegisterSpec of the register holding the variable.
+         *
+         * @return {@code non-null;} RegisterSpec of the holding register.
+         */
+        public RegisterSpec getRegisterSpec() {
+            return spec;
+        }
+
+        /**
+         * Returns whether or not this instance matches the given spec.
+         *
+         * @param otherSpec {@code non-null;} the spec in question
+         * @return {@code true} iff this instance matches
+         * {@code spec}
+         */
+        public boolean matches(RegisterSpec otherSpec) {
+            return spec.equalsUsingSimpleType(otherSpec);
+        }
+
+        /**
+         * Returns whether or not this instance matches the spec in
+         * the given instance.
+         *
+         * @param other {@code non-null;} another entry
+         * @return {@code true} iff this instance's spec matches
+         * {@code other}
+         */
+        public boolean matches(Entry other) {
+            return matches(other.spec);
+        }
+
+        /**
+         * Returns an instance just like this one but with the disposition
+         * set as given.
+         *
+         * @param disposition {@code non-null;} the new disposition
+         * @return {@code non-null;} an appropriately-constructed instance
+         */
+        public Entry withDisposition(Disposition disposition) {
+            if (disposition == this.disposition) {
+                return this;
+            }
+
+            return new Entry(address, disposition, spec);
+        }
+    }
+
+    /**
+     * Constructs an instance for the given method, based on the given
+     * block order and intermediate local information.
+     *
+     * @param insns {@code non-null;} instructions to convert
+     * @return {@code non-null;} the constructed list
+     */
+    public static LocalList make(DalvInsnList insns) {
+        int sz = insns.size();
+
+        /*
+         * Go through the insn list, looking for all the local
+         * variable pseudoinstructions, splitting out LocalSnapshots
+         * into separate per-variable starts, adding explicit ends
+         * wherever a variable is replaced or moved, and collecting
+         * these and all the other local variable "activity"
+         * together into an output list (without the other insns).
+         *
+         * Note: As of this writing, this method won't be handed any
+         * insn lists that contain local ends, but I (danfuzz) expect
+         * that to change at some point, when we start feeding that
+         * info explicitly into the rop layer rather than only trying
+         * to infer it. So, given that expectation, this code is
+         * written to deal with them.
+         */
+
+        MakeState state = new MakeState(sz);
+
+        for (int i = 0; i < sz; i++) {
+            DalvInsn insn = insns.get(i);
+
+            if (insn instanceof LocalSnapshot) {
+                RegisterSpecSet snapshot =
+                    ((LocalSnapshot) insn).getLocals();
+                state.snapshot(insn.getAddress(), snapshot);
+            } else if (insn instanceof LocalStart) {
+                RegisterSpec local = ((LocalStart) insn).getLocal();
+                state.startLocal(insn.getAddress(), local);
+            } else if (insn instanceof LocalEnd) {
+                RegisterSpec local = ((LocalEnd) insn).getLocal();
+                state.endLocal(insn.getAddress(), local);
+            }
+        }
+
+        LocalList result = state.finish();
+
+        if (DEBUG) {
+            debugVerify(result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Debugging helper that verifies the constraint that a list doesn't
+     * contain any redundant local starts and that local ends that are
+     * due to replacements are properly annotated.
+     */
+    private static void debugVerify(LocalList locals) {
+        try {
+            debugVerify0(locals);
+        } catch (RuntimeException ex) {
+            int sz = locals.size();
+            for (int i = 0; i < sz; i++) {
+                System.err.println(locals.get(i));
+            }
+            throw ex;
+        }
+
+    }
+
+    /**
+     * Helper for {@link #debugVerify} which does most of the work.
+     */
+    private static void debugVerify0(LocalList locals) {
+        int sz = locals.size();
+        Entry[] active = new Entry[65536];
+
+        for (int i = 0; i < sz; i++) {
+            Entry e = locals.get(i);
+            int reg = e.getRegister();
+
+            if (e.isStart()) {
+                Entry already = active[reg];
+
+                if ((already != null) && e.matches(already)) {
+                    throw new RuntimeException("redundant start at " +
+                            Integer.toHexString(e.getAddress()) + ": got " +
+                            e + "; had " + already);
+                }
+
+                active[reg] = e;
+            } else {
+                if (active[reg] == null) {
+                    throw new RuntimeException("redundant end at " +
+                            Integer.toHexString(e.getAddress()));
+                }
+
+                int addr = e.getAddress();
+                boolean foundStart = false;
+
+                for (int j = i + 1; j < sz; j++) {
+                    Entry test = locals.get(j);
+                    if (test.getAddress() != addr) {
+                        break;
+                    }
+                    if (test.getRegisterSpec().getReg() == reg) {
+                        if (test.isStart()) {
+                            if (e.getDisposition()
+                                    != Disposition.END_REPLACED) {
+                                throw new RuntimeException(
+                                        "improperly marked end at " +
+                                        Integer.toHexString(addr));
+                            }
+                            foundStart = true;
+                        } else {
+                            throw new RuntimeException(
+                                    "redundant end at " +
+                                    Integer.toHexString(addr));
+                        }
+                    }
+                }
+
+                if (!foundStart &&
+                        (e.getDisposition() == Disposition.END_REPLACED)) {
+                    throw new RuntimeException(
+                            "improper end replacement claim at " +
+                            Integer.toHexString(addr));
+                }
+
+                active[reg] = null;
+            }
+        }
+    }
+
+    /**
+     * Intermediate state when constructing a local list.
+     */
+    public static class MakeState {
+        /** {@code non-null;} result being collected */
+        private final ArrayList<Entry> result;
+
+        /**
+         * {@code >= 0;} running count of nulled result entries, to help with
+         * sizing the final list
+         */
+        private int nullResultCount;
+
+        /** {@code null-ok;} current register mappings */
+        private RegisterSpecSet regs;
+
+        /** {@code null-ok;} result indices where local ends are stored */
+        private int[] endIndices;
+
+        /** {@code >= 0;} last address seen */
+        private int lastAddress;
+
+        /**
+         * Constructs an instance.
+         */
+        public MakeState(int initialSize) {
+            result = new ArrayList<Entry>(initialSize);
+            nullResultCount = 0;
+            regs = null;
+            endIndices = null;
+            lastAddress = 0;
+        }
+
+        /**
+         * Checks the address and other vitals as a prerequisite to
+         * further processing.
+         *
+         * @param address {@code >= 0;} address about to be processed
+         * @param reg {@code >= 0;} register number about to be processed
+         */
+        private void aboutToProcess(int address, int reg) {
+            boolean first = (endIndices == null);
+
+            if ((address == lastAddress) && !first) {
+                return;
+            }
+
+            if (address < lastAddress) {
+                throw new RuntimeException("shouldn't happen");
+            }
+
+            if (first || (reg >= endIndices.length)) {
+                /*
+                 * This is the first allocation of the state set and
+                 * index array, or we need to grow. (The latter doesn't
+                 * happen much; in fact, we have only ever observed
+                 * it happening in test cases, never in "real" code.)
+                 */
+                int newSz = reg + 1;
+                RegisterSpecSet newRegs = new RegisterSpecSet(newSz);
+                int[] newEnds = new int[newSz];
+                Arrays.fill(newEnds, -1);
+
+                if (!first) {
+                    newRegs.putAll(regs);
+                    System.arraycopy(endIndices, 0, newEnds, 0,
+                            endIndices.length);
+                }
+
+                regs = newRegs;
+                endIndices = newEnds;
+            }
+        }
+
+        /**
+         * Sets the local state at the given address to the given snapshot.
+         * The first call on this instance must be to this method, so that
+         * the register state can be properly sized.
+         *
+         * @param address {@code >= 0;} the address
+         * @param specs {@code non-null;} spec set representing the locals
+         */
+        public void snapshot(int address, RegisterSpecSet specs) {
+            if (DEBUG) {
+                System.err.printf("%04x snapshot %s\n", address, specs);
+            }
+
+            int sz = specs.getMaxSize();
+            aboutToProcess(address, sz - 1);
+
+            for (int i = 0; i < sz; i++) {
+                RegisterSpec oldSpec = regs.get(i);
+                RegisterSpec newSpec = filterSpec(specs.get(i));
+
+                if (oldSpec == null) {
+                    if (newSpec != null) {
+                        startLocal(address, newSpec);
+                    }
+                } else if (newSpec == null) {
+                    endLocal(address, oldSpec);
+                } else if (! newSpec.equalsUsingSimpleType(oldSpec)) {
+                    endLocal(address, oldSpec);
+                    startLocal(address, newSpec);
+                }
+            }
+
+            if (DEBUG) {
+                System.err.printf("%04x snapshot done\n", address);
+            }
+        }
+
+        /**
+         * Starts a local at the given address.
+         *
+         * @param address {@code >= 0;} the address
+         * @param startedLocal {@code non-null;} spec representing the
+         * started local
+         */
+        public void startLocal(int address, RegisterSpec startedLocal) {
+            if (DEBUG) {
+                System.err.printf("%04x start %s\n", address, startedLocal);
+            }
+
+            int regNum = startedLocal.getReg();
+
+            startedLocal = filterSpec(startedLocal);
+            aboutToProcess(address, regNum);
+
+            RegisterSpec existingLocal = regs.get(regNum);
+
+            if (startedLocal.equalsUsingSimpleType(existingLocal)) {
+                // Silently ignore a redundant start.
+                return;
+            }
+
+            RegisterSpec movedLocal = regs.findMatchingLocal(startedLocal);
+            if (movedLocal != null) {
+                /*
+                 * The same variable was moved from one register to another.
+                 * So add an end for its old location.
+                 */
+                addOrUpdateEnd(address, Disposition.END_MOVED, movedLocal);
+            }
+
+            int endAt = endIndices[regNum];
+
+            if (existingLocal != null) {
+                /*
+                 * There is an existing (but non-matching) local.
+                 * Add an explicit end for it.
+                 */
+                add(address, Disposition.END_REPLACED, existingLocal);
+            } else if (endAt >= 0) {
+                /*
+                 * Look for an end local for the same register at the
+                 * same address. If found, then update it or delete
+                 * it, depending on whether or not it represents the
+                 * same variable as the one being started.
+                 */
+                Entry endEntry = result.get(endAt);
+                if (endEntry.getAddress() == address) {
+                    if (endEntry.matches(startedLocal)) {
+                        /*
+                         * There was already an end local for the same
+                         * variable at the same address. This turns
+                         * out to be superfluous, as we are starting
+                         * up the exact same local. This situation can
+                         * happen when a single local variable got
+                         * somehow "split up" during intermediate
+                         * processing. In any case, rather than represent
+                         * the end-then-start, just remove the old end.
+                         */
+                        result.set(endAt, null);
+                        nullResultCount++;
+                        regs.put(startedLocal);
+                        endIndices[regNum] = -1;
+                        return;
+                    } else {
+                        /*
+                         * There was a different variable ended at the
+                         * same address. Update it to indicate that
+                         * it was ended due to a replacement (rather than
+                         * ending for no particular reason).
+                         */
+                        endEntry = endEntry.withDisposition(
+                                Disposition.END_REPLACED);
+                        result.set(endAt, endEntry);
+                    }
+                }
+            }
+
+            /*
+             * The code above didn't find and remove an unnecessary
+             * local end, so we now have to add one or more entries to
+             * the output to capture the transition.
+             */
+
+            /*
+             * If the local just below (in the register set at reg-1)
+             * is of category-2, then it is ended by this new start.
+             */
+            if (regNum > 0) {
+                RegisterSpec justBelow = regs.get(regNum - 1);
+                if ((justBelow != null) && justBelow.isCategory2()) {
+                    addOrUpdateEnd(address,
+                            Disposition.END_CLOBBERED_BY_NEXT,
+                            justBelow);
+                }
+            }
+
+            /*
+             * Similarly, if this local is category-2, then the local
+             * just above (if any) is ended by the start now being
+             * emitted.
+             */
+            if (startedLocal.isCategory2()) {
+                RegisterSpec justAbove = regs.get(regNum + 1);
+                if (justAbove != null) {
+                    addOrUpdateEnd(address,
+                            Disposition.END_CLOBBERED_BY_PREV,
+                            justAbove);
+                }
+            }
+
+            /*
+             * TODO: Add an end for the same local in a different reg,
+             * if any (that is, if the local migrates from vX to vY,
+             * we should note that as a local end in vX).
+             */
+
+            add(address, Disposition.START, startedLocal);
+        }
+
+        /**
+         * Ends a local at the given address, using the disposition
+         * {@code END_SIMPLY}.
+         *
+         * @param address {@code >= 0;} the address
+         * @param endedLocal {@code non-null;} spec representing the
+         * local being ended
+         */
+        public void endLocal(int address, RegisterSpec endedLocal) {
+            endLocal(address, endedLocal, Disposition.END_SIMPLY);
+        }
+
+        /**
+         * Ends a local at the given address.
+         *
+         * @param address {@code >= 0;} the address
+         * @param endedLocal {@code non-null;} spec representing the
+         * local being ended
+         * @param disposition reason for the end
+         */
+        public void endLocal(int address, RegisterSpec endedLocal,
+                Disposition disposition) {
+            if (DEBUG) {
+                System.err.printf("%04x end %s\n", address, endedLocal);
+            }
+
+            int regNum = endedLocal.getReg();
+
+            endedLocal = filterSpec(endedLocal);
+            aboutToProcess(address, regNum);
+
+            int endAt = endIndices[regNum];
+
+            if (endAt >= 0) {
+                /*
+                 * The local in the given register is already ended.
+                 * Silently return without adding anything to the result.
+                 */
+                return;
+            }
+
+            // Check for start and end at the same address.
+            if (checkForEmptyRange(address, endedLocal)) {
+                return;
+            }
+
+            add(address, disposition, endedLocal);
+        }
+
+        /**
+         * Helper for {@link #endLocal}, which handles the cases where
+         * and end local is issued at the same address as a start local
+         * for the same register. If this case is found, then this
+         * method will remove the start (as the local was never actually
+         * active), update the {@link #endIndices} to be accurate, and
+         * if needed update the newly-active end to reflect an altered
+         * disposition.
+         *
+         * @param address {@code >= 0;} the address
+         * @param endedLocal {@code non-null;} spec representing the
+         * local being ended
+         * @return {@code true} iff this method found the case in question
+         * and adjusted things accordingly
+         */
+        private boolean checkForEmptyRange(int address,
+                RegisterSpec endedLocal) {
+            int at = result.size() - 1;
+            Entry entry;
+
+            // Look for a previous entry at the same address.
+            for (/*at*/; at >= 0; at--) {
+                entry = result.get(at);
+
+                if (entry == null) {
+                    continue;
+                }
+
+                if (entry.getAddress() != address) {
+                    // We didn't find any match at the same address.
+                    return false;
+                }
+
+                if (entry.matches(endedLocal)) {
+                    break;
+                }
+            }
+
+            /*
+             * In fact, we found that the endedLocal had started at the
+             * same address, so do all the requisite cleanup.
+             */
+
+            regs.remove(endedLocal);
+            result.set(at, null);
+            nullResultCount++;
+
+            int regNum = endedLocal.getReg();
+            boolean found = false;
+            entry = null;
+
+            // Now look back further to update where the register ended.
+            for (at--; at >= 0; at--) {
+                entry = result.get(at);
+
+                if (entry == null) {
+                    continue;
+                }
+
+                if (entry.getRegisterSpec().getReg() == regNum) {
+                    found = true;
+                    break;
+                }
+            }
+
+            if (found) {
+                // We found an end for the same register.
+                endIndices[regNum] = at;
+
+                if (entry.getAddress() == address) {
+                    /*
+                     * It's still the same address, so update the
+                     * disposition.
+                     */
+                    result.set(at,
+                            entry.withDisposition(Disposition.END_SIMPLY));
+                }
+            }
+
+            return true;
+        }
+
+        /**
+         * Converts a given spec into the form acceptable for use in a
+         * local list. This, in particular, transforms the "known
+         * null" type into simply {@code Object}. This method needs to
+         * be called for any spec that is on its way into a locals
+         * list.
+         *
+         * <p>This isn't necessarily the cleanest way to achieve the
+         * goal of not representing known nulls in a locals list, but
+         * it gets the job done.</p>
+         *
+         * @param orig {@code null-ok;} the original spec
+         * @return {@code null-ok;} an appropriately modified spec, or the
+         * original if nothing needs to be done
+         */
+        private static RegisterSpec filterSpec(RegisterSpec orig) {
+            if ((orig != null) && (orig.getType() == Type.KNOWN_NULL)) {
+                return orig.withType(Type.OBJECT);
+            }
+
+            return orig;
+        }
+
+        /**
+         * Adds an entry to the result, updating the adjunct tables
+         * accordingly.
+         *
+         * @param address {@code >= 0;} the address
+         * @param disposition {@code non-null;} the disposition
+         * @param spec {@code non-null;} spec representing the local
+         */
+        private void add(int address, Disposition disposition,
+                RegisterSpec spec) {
+            int regNum = spec.getReg();
+
+            result.add(new Entry(address, disposition, spec));
+
+            if (disposition == Disposition.START) {
+                regs.put(spec);
+                endIndices[regNum] = -1;
+            } else {
+                regs.remove(spec);
+                endIndices[regNum] = result.size() - 1;
+            }
+        }
+
+        /**
+         * Adds or updates an end local (changing its disposition). If
+         * this would cause an empty range for a local, this instead
+         * removes the local entirely.
+         *
+         * @param address {@code >= 0;} the address
+         * @param disposition {@code non-null;} the disposition
+         * @param spec {@code non-null;} spec representing the local
+         */
+        private void addOrUpdateEnd(int address, Disposition disposition,
+                RegisterSpec spec) {
+            if (disposition == Disposition.START) {
+                throw new RuntimeException("shouldn't happen");
+            }
+
+            int regNum = spec.getReg();
+            int endAt = endIndices[regNum];
+
+            if (endAt >= 0) {
+                // There is a previous end.
+                Entry endEntry = result.get(endAt);
+                if ((endEntry.getAddress() == address) &&
+                        endEntry.getRegisterSpec().equals(spec)) {
+                    /*
+                     * The end is for the right address and variable, so
+                     * update it.
+                     */
+                    result.set(endAt, endEntry.withDisposition(disposition));
+                    regs.remove(spec); // TODO: Is this line superfluous?
+                    return;
+                }
+            }
+
+            endLocal(address, spec, disposition);
+        }
+
+        /**
+         * Finishes processing altogether and gets the result.
+         *
+         * @return {@code non-null;} the result list
+         */
+        public LocalList finish() {
+            aboutToProcess(Integer.MAX_VALUE, 0);
+
+            int resultSz = result.size();
+            int finalSz = resultSz - nullResultCount;
+
+            if (finalSz == 0) {
+                return EMPTY;
+            }
+
+            /*
+             * Collect an array of only the non-null entries, and then
+             * sort it to get a consistent order for everything: Local
+             * ends and starts for a given address could come in any
+             * order, but we want ends before starts as well as
+             * registers in order (within ends or starts).
+             */
+
+            Entry[] resultArr = new Entry[finalSz];
+
+            if (resultSz == finalSz) {
+                result.toArray(resultArr);
+            } else {
+                int at = 0;
+                for (Entry e : result) {
+                    if (e != null) {
+                        resultArr[at++] = e;
+                    }
+                }
+            }
+
+            Arrays.sort(resultArr);
+
+            LocalList resultList = new LocalList(finalSz);
+
+            for (int i = 0; i < finalSz; i++) {
+                resultList.set(i, resultArr[i]);
+            }
+
+            resultList.setImmutable();
+            return resultList;
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/LocalSnapshot.java b/dexgen/src/com/android/dexgen/dex/code/LocalSnapshot.java
new file mode 100644
index 0000000..81b78c9
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/LocalSnapshot.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.RegisterSpecSet;
+import com.android.dexgen.rop.code.SourcePosition;
+
+/**
+ * Pseudo-instruction which is used to hold a snapshot of the
+ * state of local variable name mappings that exists immediately after
+ * the instance in an instruction array.
+ */
+public final class LocalSnapshot extends ZeroSizeInsn {
+    /** {@code non-null;} local state associated with this instance */
+    private final RegisterSpecSet locals;
+
+    /**
+     * Constructs an instance. The output address of this instance is initially
+     * unknown ({@code -1}).
+     *
+     * @param position {@code non-null;} source position
+     * @param locals {@code non-null;} associated local variable state
+     */
+    public LocalSnapshot(SourcePosition position, RegisterSpecSet locals) {
+        super(position);
+
+        if (locals == null) {
+            throw new NullPointerException("locals == null");
+        }
+
+        this.locals = locals;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withRegisterOffset(int delta) {
+        return new LocalSnapshot(getPosition(), locals.withOffset(delta));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withRegisters(RegisterSpecList registers) {
+        return new LocalSnapshot(getPosition(), locals);
+    }
+
+    /**
+     * Gets the local state associated with this instance.
+     *
+     * @return {@code non-null;} the state
+     */
+    public RegisterSpecSet getLocals() {
+        return locals;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String argString() {
+        return locals.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String listingString0(boolean noteIndices) {
+        int sz = locals.size();
+        int max = locals.getMaxSize();
+        StringBuffer sb = new StringBuffer(100 + sz * 40);
+
+        sb.append("local-snapshot");
+
+        for (int i = 0; i < max; i++) {
+            RegisterSpec spec = locals.get(i);
+            if (spec != null) {
+                sb.append("\n  ");
+                sb.append(LocalStart.localString(spec));
+            }
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/LocalStart.java b/dexgen/src/com/android/dexgen/dex/code/LocalStart.java
new file mode 100644
index 0000000..ba426e8
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/LocalStart.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.SourcePosition;
+
+/**
+ * Pseudo-instruction which is used to introduce a new local variable. That
+ * is, an instance of this class in an instruction stream indicates that
+ * starting with the subsequent instruction, the indicated variable
+ * is bound.
+ */
+public final class LocalStart extends ZeroSizeInsn {
+    /**
+     * {@code non-null;} register spec representing the local variable introduced
+     * by this instance
+     */
+    private final RegisterSpec local;
+
+    /**
+     * Returns the local variable listing string for a single register spec.
+     *
+     * @param spec {@code non-null;} the spec to convert
+     * @return {@code non-null;} the string form
+     */
+    public static String localString(RegisterSpec spec) {
+        return spec.regString() + ' ' + spec.getLocalItem().toString() + ": " +
+            spec.getTypeBearer().toHuman();
+    }
+
+    /**
+     * Constructs an instance. The output address of this instance is initially
+     * unknown ({@code -1}).
+     *
+     * @param position {@code non-null;} source position
+     * @param local {@code non-null;} register spec representing the local
+     * variable introduced by this instance
+     */
+    public LocalStart(SourcePosition position, RegisterSpec local) {
+        super(position);
+
+        if (local == null) {
+            throw new NullPointerException("local == null");
+        }
+
+        this.local = local;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withRegisterOffset(int delta) {
+        return new LocalStart(getPosition(), local.withOffset(delta));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withRegisters(RegisterSpecList registers) {
+        return new LocalStart(getPosition(), local);
+    }
+
+    /**
+     * Gets the register spec representing the local variable introduced
+     * by this instance.
+     *
+     * @return {@code non-null;} the register spec
+     */
+    public RegisterSpec getLocal() {
+        return local;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String argString() {
+        return local.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String listingString0(boolean noteIndices) {
+        return "local-start " + localString(local);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/OddSpacer.java b/dexgen/src/com/android/dexgen/dex/code/OddSpacer.java
new file mode 100644
index 0000000..8e2bab8
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/OddSpacer.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.SourcePosition;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Pseudo-instruction which either turns into a {@code nop} or
+ * nothingness, in order to make the subsequent instruction have an
+ * even address. This is used to align (subsequent) instructions that
+ * require it.
+ */
+public final class OddSpacer extends VariableSizeInsn {
+    /**
+     * Constructs an instance. The output address of this instance is initially
+     * unknown ({@code -1}).
+     *
+     * @param position {@code non-null;} source position
+     */
+    public OddSpacer(SourcePosition position) {
+        super(position, RegisterSpecList.EMPTY);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return (getAddress() & 1);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out) {
+        if (codeSize() != 0) {
+            out.writeShort(InsnFormat.codeUnit(DalvOps.NOP, 0));
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withRegisters(RegisterSpecList registers) {
+        return new OddSpacer(getPosition());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String argString() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String listingString0(boolean noteIndices) {
+        if (codeSize() == 0) {
+            return null;
+        }
+
+        return "nop // spacer";
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/OutputCollector.java b/dexgen/src/com/android/dexgen/dex/code/OutputCollector.java
new file mode 100644
index 0000000..7f970eb
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/OutputCollector.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import java.util.ArrayList;
+
+/**
+ * Destination for {@link DalvInsn} instances being output. This class
+ * receives and collects instructions in two pieces &mdash; a primary
+ * list and a suffix (generally consisting of adjunct data referred to
+ * by the primary list, such as switch case tables) &mdash; which it
+ * merges and emits back out in the form of a {@link DalvInsnList}
+ * instance.
+ */
+public final class OutputCollector {
+    /**
+     * {@code non-null;} the associated finisher (which holds the instruction
+     * list in-progress)
+     */
+    private final OutputFinisher finisher;
+
+    /**
+     * {@code null-ok;} suffix for the output, or {@code null} if the suffix
+     * has been appended to the main output (by {@link #appendSuffixToOutput})
+     */
+    private ArrayList<DalvInsn> suffix;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param initialCapacity {@code >= 0;} initial capacity of the output list
+     * @param suffixInitialCapacity {@code >= 0;} initial capacity of the output
+     * suffix
+     * @param regCount {@code >= 0;} register count for the method
+     */
+    public OutputCollector(int initialCapacity, int suffixInitialCapacity,
+            int regCount) {
+        this.finisher = new OutputFinisher(initialCapacity, regCount);
+        this.suffix = new ArrayList<DalvInsn>(suffixInitialCapacity);
+    }
+
+    /**
+     * Adds an instruction to the output.
+     *
+     * @param insn {@code non-null;} the instruction to add
+     */
+    public void add(DalvInsn insn) {
+        finisher.add(insn);
+    }
+
+    /**
+     * Reverses a branch which is buried a given number of instructions
+     * backward in the output. It is illegal to call this unless the
+     * indicated instruction really is a reversible branch.
+     *
+     * @param which how many instructions back to find the branch;
+     * {@code 0} is the most recently added instruction,
+     * {@code 1} is the instruction before that, etc.
+     * @param newTarget {@code non-null;} the new target for the reversed branch
+     */
+    public void reverseBranch(int which, CodeAddress newTarget) {
+        finisher.reverseBranch(which, newTarget);
+    }
+
+    /**
+     * Adds an instruction to the output suffix.
+     *
+     * @param insn {@code non-null;} the instruction to add
+     */
+    public void addSuffix(DalvInsn insn) {
+        suffix.add(insn);
+    }
+
+    /**
+     * Gets the results of all the calls on this instance, in the form of
+     * an {@link OutputFinisher}.
+     *
+     * @return {@code non-null;} the output finisher
+     * @throws UnsupportedOperationException if this method has
+     * already been called
+     */
+    public OutputFinisher getFinisher() {
+        if (suffix == null) {
+            throw new UnsupportedOperationException("already processed");
+        }
+
+        appendSuffixToOutput();
+        return finisher;
+    }
+
+    /**
+     * Helper for {@link #getFinisher}, which appends the suffix to
+     * the primary output.
+     */
+    private void appendSuffixToOutput() {
+        int size = suffix.size();
+
+        for (int i = 0; i < size; i++) {
+            finisher.add(suffix.get(i));
+        }
+
+        suffix = null;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/OutputFinisher.java b/dexgen/src/com/android/dexgen/dex/code/OutputFinisher.java
new file mode 100644
index 0000000..98c7e4e
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/OutputFinisher.java
@@ -0,0 +1,737 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.LocalItem;
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.RegisterSpecSet;
+import com.android.dexgen.rop.code.SourcePosition;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstMemberRef;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.rop.type.Type;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * Processor for instruction lists, which takes a "first cut" of
+ * instruction selection as a basis and produces a "final cut" in the
+ * form of a {@link DalvInsnList} instance.
+ */
+public final class OutputFinisher {
+    /**
+     * {@code >= 0;} register count for the method, not including any extra
+     * "reserved" registers needed to translate "difficult" instructions
+     */
+    private final int unreservedRegCount;
+
+    /** {@code non-null;} the list of instructions, per se */
+    private ArrayList<DalvInsn> insns;
+
+    /** whether any instruction has position info */
+    private boolean hasAnyPositionInfo;
+
+    /** whether any instruction has local variable info */
+    private boolean hasAnyLocalInfo;
+
+    /**
+     * {@code >= 0;} the count of reserved registers (low-numbered
+     * registers used when expanding instructions that can't be
+     * represented simply); becomes valid after a call to {@link
+     * #massageInstructions}
+     */
+    private int reservedCount;
+
+    /**
+     * Constructs an instance. It initially contains no instructions.
+     *
+     * @param regCount {@code >= 0;} register count for the method
+     * @param initialCapacity {@code >= 0;} initial capacity of the instructions
+     * list
+     */
+    public OutputFinisher(int initialCapacity, int regCount) {
+        this.unreservedRegCount = regCount;
+        this.insns = new ArrayList<DalvInsn>(initialCapacity);
+        this.reservedCount = -1;
+        this.hasAnyPositionInfo = false;
+        this.hasAnyLocalInfo = false;
+    }
+
+    /**
+     * Returns whether any of the instructions added to this instance
+     * come with position info.
+     *
+     * @return whether any of the instructions added to this instance
+     * come with position info
+     */
+    public boolean hasAnyPositionInfo() {
+        return hasAnyPositionInfo;
+    }
+
+    /**
+     * Returns whether this instance has any local variable information.
+     *
+     * @return whether this instance has any local variable information
+     */
+    public boolean hasAnyLocalInfo() {
+        return hasAnyLocalInfo;
+    }
+
+    /**
+     * Helper for {@link #add} which scrutinizes a single
+     * instruction for local variable information.
+     *
+     * @param insn {@code non-null;} instruction to scrutinize
+     * @return {@code true} iff the instruction refers to any
+     * named locals
+     */
+    private static boolean hasLocalInfo(DalvInsn insn) {
+        if (insn instanceof LocalSnapshot) {
+            RegisterSpecSet specs = ((LocalSnapshot) insn).getLocals();
+            int size = specs.size();
+            for (int i = 0; i < size; i++) {
+                if (hasLocalInfo(specs.get(i))) {
+                    return true;
+                }
+            }
+        } else if (insn instanceof LocalStart) {
+            RegisterSpec spec = ((LocalStart) insn).getLocal();
+            if (hasLocalInfo(spec)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Helper for {@link #hasAnyLocalInfo} which scrutinizes a single
+     * register spec.
+     *
+     * @param spec {@code non-null;} spec to scrutinize
+     * @return {@code true} iff the spec refers to any
+     * named locals
+     */
+    private static boolean hasLocalInfo(RegisterSpec spec) {
+        return (spec != null)
+            && (spec.getLocalItem().getName() != null);
+    }
+
+    /**
+     * Returns the set of all constants referred to by instructions added
+     * to this instance.
+     *
+     * @return {@code non-null;} the set of constants
+     */
+    public HashSet<Constant> getAllConstants() {
+        HashSet<Constant> result = new HashSet<Constant>(20);
+
+        for (DalvInsn insn : insns) {
+            addConstants(result, insn);
+        }
+
+        return result;
+    }
+
+    /**
+     * Helper for {@link #getAllConstants} which adds all the info for
+     * a single instruction.
+     *
+     * @param result {@code non-null;} result set to add to
+     * @param insn {@code non-null;} instruction to scrutinize
+     */
+    private static void addConstants(HashSet<Constant> result,
+            DalvInsn insn) {
+        if (insn instanceof CstInsn) {
+            Constant cst = ((CstInsn) insn).getConstant();
+            result.add(cst);
+        } else if (insn instanceof LocalSnapshot) {
+            RegisterSpecSet specs = ((LocalSnapshot) insn).getLocals();
+            int size = specs.size();
+            for (int i = 0; i < size; i++) {
+                addConstants(result, specs.get(i));
+            }
+        } else if (insn instanceof LocalStart) {
+            RegisterSpec spec = ((LocalStart) insn).getLocal();
+            addConstants(result, spec);
+        }
+    }
+
+    /**
+     * Helper for {@link #getAllConstants} which adds all the info for
+     * a single {@code RegisterSpec}.
+     *
+     * @param result {@code non-null;} result set to add to
+     * @param spec {@code null-ok;} register spec to add
+     */
+    private static void addConstants(HashSet<Constant> result,
+            RegisterSpec spec) {
+        if (spec == null) {
+            return;
+        }
+
+        LocalItem local = spec.getLocalItem();
+        CstUtf8 name = local.getName();
+        CstUtf8 signature = local.getSignature();
+        Type type = spec.getType();
+
+        if (type != Type.KNOWN_NULL) {
+            result.add(CstType.intern(type));
+        }
+
+        if (name != null) {
+            result.add(name);
+        }
+
+        if (signature != null) {
+            result.add(signature);
+        }
+    }
+
+    /**
+     * Adds an instruction to the output.
+     *
+     * @param insn {@code non-null;} the instruction to add
+     */
+    public void add(DalvInsn insn) {
+        insns.add(insn);
+        updateInfo(insn);
+    }
+
+    /**
+     * Inserts an instruction in the output at the given offset.
+     *
+     * @param at {@code >= 0;} what index to insert at
+     * @param insn {@code non-null;} the instruction to insert
+     */
+    public void insert(int at, DalvInsn insn) {
+        insns.add(at, insn);
+        updateInfo(insn);
+    }
+
+    /**
+     * Helper for {@link #add} and {@link #insert},
+     * which updates the position and local info flags.
+     *
+     * @param insn {@code non-null;} an instruction that was just introduced
+     */
+    private void updateInfo(DalvInsn insn) {
+        if (! hasAnyPositionInfo) {
+            SourcePosition pos = insn.getPosition();
+            if (pos.getLine() >= 0) {
+                hasAnyPositionInfo = true;
+            }
+        }
+
+        if (! hasAnyLocalInfo) {
+            if (hasLocalInfo(insn)) {
+                hasAnyLocalInfo = true;
+            }
+        }
+    }
+
+    /**
+     * Reverses a branch which is buried a given number of instructions
+     * backward in the output. It is illegal to call this unless the
+     * indicated instruction really is a reversible branch.
+     *
+     * @param which how many instructions back to find the branch;
+     * {@code 0} is the most recently added instruction,
+     * {@code 1} is the instruction before that, etc.
+     * @param newTarget {@code non-null;} the new target for the reversed branch
+     */
+    public void reverseBranch(int which, CodeAddress newTarget) {
+        int size = insns.size();
+        int index = size - which - 1;
+        TargetInsn targetInsn;
+
+        try {
+            targetInsn = (TargetInsn) insns.get(index);
+        } catch (IndexOutOfBoundsException ex) {
+            // Translate the exception.
+            throw new IllegalArgumentException("too few instructions");
+        } catch (ClassCastException ex) {
+            // Translate the exception.
+            throw new IllegalArgumentException("non-reversible instruction");
+        }
+
+        /*
+         * No need to call this.set(), since the format and other info
+         * are the same.
+         */
+        insns.set(index, targetInsn.withNewTargetAndReversed(newTarget));
+    }
+
+    /**
+     * Assigns indices in all instructions that need them, using the
+     * given callback to perform lookups. This should be called before
+     * calling {@link #finishProcessingAndGetList}.
+     *
+     * @param callback {@code non-null;} callback object
+     */
+    public void assignIndices(DalvCode.AssignIndicesCallback callback) {
+        for (DalvInsn insn : insns) {
+            if (insn instanceof CstInsn) {
+                assignIndices((CstInsn) insn, callback);
+            }
+        }
+    }
+
+    /**
+     * Helper for {@link #assignIndices} which does assignment for one
+     * instruction.
+     *
+     * @param insn {@code non-null;} the instruction
+     * @param callback {@code non-null;} the callback
+     */
+    private static void assignIndices(CstInsn insn,
+            DalvCode.AssignIndicesCallback callback) {
+        Constant cst = insn.getConstant();
+        int index = callback.getIndex(cst);
+
+        if (index >= 0) {
+            insn.setIndex(index);
+        }
+
+        if (cst instanceof CstMemberRef) {
+            CstMemberRef member = (CstMemberRef) cst;
+            CstType definer = member.getDefiningClass();
+            index = callback.getIndex(definer);
+            if (index >= 0) {
+                insn.setClassIndex(index);
+            }
+        }
+    }
+
+    /**
+     * Does final processing on this instance and gets the output as
+     * a {@link DalvInsnList}. Final processing consists of:
+     *
+     * <ul>
+     *   <li>optionally renumbering registers (to make room as needed for
+     *   expanded instructions)</li>
+     *   <li>picking a final opcode for each instruction</li>
+     *   <li>rewriting instructions, because of register number,
+     *   constant pool index, or branch target size issues</li>
+     *   <li>assigning final addresses</li>
+     * </ul>
+     *
+     * <p><b>Note:</b> This method may only be called once per instance
+     * of this class.</p>
+     *
+     * @return {@code non-null;} the output list
+     * @throws UnsupportedOperationException if this method has
+     * already been called
+     */
+    public DalvInsnList finishProcessingAndGetList() {
+        if (reservedCount >= 0) {
+            throw new UnsupportedOperationException("already processed");
+        }
+
+        InsnFormat[] formats = makeFormatsArray();
+        reserveRegisters(formats);
+        massageInstructions(formats);
+        assignAddressesAndFixBranches();
+
+        return DalvInsnList.makeImmutable(insns,
+                reservedCount + unreservedRegCount);
+    }
+
+    /**
+     * Helper for {@link #finishProcessingAndGetList}, which extracts
+     * the format out of each instruction into a separate array, to be
+     * further manipulated as things progress.
+     *
+     * @return {@code non-null;} the array of formats
+     */
+    private InsnFormat[] makeFormatsArray() {
+        int size = insns.size();
+        InsnFormat[] result = new InsnFormat[size];
+
+        for (int i = 0; i < size; i++) {
+            result[i] = insns.get(i).getOpcode().getFormat();
+        }
+
+        return result;
+    }
+
+    /**
+     * Helper for {@link #finishProcessingAndGetList}, which figures
+     * out how many reserved registers are required and then reserving
+     * them. It also updates the given {@code formats} array so
+     * as to avoid extra work when constructing the massaged
+     * instruction list.
+     *
+     * @param formats {@code non-null;} array of per-instruction format selections
+     */
+    private void reserveRegisters(InsnFormat[] formats) {
+        int oldReservedCount = (reservedCount < 0) ? 0 : reservedCount;
+
+        /*
+         * Call calculateReservedCount() and then perform register
+         * reservation, repeatedly until no new reservations happen.
+         */
+        for (;;) {
+            int newReservedCount = calculateReservedCount(formats);
+            if (oldReservedCount >= newReservedCount) {
+                break;
+            }
+
+            int reservedDifference = newReservedCount - oldReservedCount;
+            int size = insns.size();
+
+            for (int i = 0; i < size; i++) {
+                /*
+                 * CodeAddress instance identity is used to link
+                 * TargetInsns to their targets, so it is
+                 * inappropriate to make replacements, and they don't
+                 * have registers in any case. Hence, the instanceof
+                 * test below.
+                 */
+                DalvInsn insn = insns.get(i);
+                if (!(insn instanceof CodeAddress)) {
+                    /*
+                     * No need to call this.set() since the format and
+                     * other info are the same.
+                     */
+                    insns.set(i, insn.withRegisterOffset(reservedDifference));
+                }
+            }
+
+            oldReservedCount = newReservedCount;
+        }
+
+        reservedCount = oldReservedCount;
+    }
+
+    /**
+     * Helper for {@link #reserveRegisters}, which does one
+     * pass over the instructions, calculating the number of
+     * registers that need to be reserved. It also updates the
+     * {@code formats} list to help avoid extra work in future
+     * register reservation passes.
+     *
+     * @param formats {@code non-null;} array of per-instruction format selections
+     * @return {@code >= 0;} the count of reserved registers
+     */
+    private int calculateReservedCount(InsnFormat[] formats) {
+        int size = insns.size();
+
+        /*
+         * Potential new value of reservedCount, which gets updated in the
+         * following loop. It starts out with the existing reservedCount
+         * and gets increased if it turns out that additional registers
+         * need to be reserved.
+         */
+        int newReservedCount = reservedCount;
+
+        for (int i = 0; i < size; i++) {
+            DalvInsn insn = insns.get(i);
+            InsnFormat originalFormat = formats[i];
+            InsnFormat newFormat = findFormatForInsn(insn, originalFormat);
+
+            if (originalFormat == newFormat) {
+                continue;
+            }
+
+            if (newFormat == null) {
+                /*
+                 * The instruction will need to be expanded, so reserve
+                 * registers for it.
+                 */
+                int reserve = insn.getMinimumRegisterRequirement();
+                if (reserve > newReservedCount) {
+                    newReservedCount = reserve;
+                }
+            }
+
+            formats[i] = newFormat;
+        }
+
+        return newReservedCount;
+    }
+
+    /**
+     * Attempts to fit the given instruction into a format, returning
+     * either a format that the instruction fits into or {@code null}
+     * to indicate that the instruction will need to be expanded. This
+     * fitting process starts with the given format as a first "best
+     * guess" and then pessimizes from there if necessary.
+     *
+     * @param insn {@code non-null;} the instruction in question
+     * @param format {@code null-ok;} the current guess as to the best instruction
+     * format to use; {@code null} means that no simple format fits
+     * @return {@code null-ok;} a possibly-different format, which is either a
+     * good fit or {@code null} to indicate that no simple format
+     * fits
+     */
+    private InsnFormat findFormatForInsn(DalvInsn insn, InsnFormat format) {
+        if (format == null) {
+            // The instruction is already known not to fit any simple format.
+            return format;
+        }
+
+        if (format.isCompatible(insn)) {
+            // The instruction already fits in the current best-known format.
+            return format;
+        }
+
+        Dop dop = insn.getOpcode();
+        int family = dop.getFamily();
+
+        for (;;) {
+            format = format.nextUp();
+            if ((format == null) ||
+                    (format.isCompatible(insn) &&
+                     (Dops.getOrNull(family, format) != null))) {
+                break;
+            }
+        }
+
+        return format;
+    }
+
+    /**
+     * Helper for {@link #finishProcessingAndGetList}, which goes
+     * through each instruction in the output, making sure its opcode
+     * can accomodate its arguments. In cases where the opcode is
+     * unable to do so, this replaces the instruction with a larger
+     * instruction with identical semantics that <i>will</i> work.
+     *
+     * <p>This method may also reserve a number of low-numbered
+     * registers, renumbering the instructions' original registers, in
+     * order to have register space available in which to move
+     * very-high registers when expanding instructions into
+     * multi-instruction sequences. This expansion is done when no
+     * simple instruction format can be found for a given instruction that
+     * is able to accomodate that instruction's registers.</p>
+     *
+     * <p>This method ignores issues of branch target size, since
+     * final addresses aren't known at the point that this method is
+     * called.</p>
+     *
+     * @param formats {@code non-null;} array of per-instruction format selections
+     */
+    private void massageInstructions(InsnFormat[] formats) {
+        if (reservedCount == 0) {
+            /*
+             * The easy common case: No registers were reserved, so we
+             * merely need to replace any instructions whose format changed
+             * during the reservation pass, but all instructions will stay
+             * at their original indices, and the instruction list doesn't
+             * grow.
+             */
+            int size = insns.size();
+
+            for (int i = 0; i < size; i++) {
+                DalvInsn insn = insns.get(i);
+                Dop dop = insn.getOpcode();
+                InsnFormat format = formats[i];
+
+                if (format != dop.getFormat()) {
+                    dop = Dops.getOrNull(dop.getFamily(), format);
+                    insns.set(i, insn.withOpcode(dop));
+                }
+            }
+        } else {
+            /*
+             * The difficult uncommon case: Some instructions have to be
+             * expanded to deal with high registers.
+             */
+            insns = performExpansion(formats);
+        }
+    }
+
+    /**
+     * Helper for {@link #massageInstructions}, which constructs a
+     * replacement list, where each {link DalvInsn} instance that
+     * couldn't be represented simply (due to register representation
+     * problems) is expanded into a series of instances that together
+     * perform the proper function.
+     *
+     * @param formats {@code non-null;} array of per-instruction format selections
+     * @return {@code non-null;} the replacement list
+     */
+    private ArrayList<DalvInsn> performExpansion(InsnFormat[] formats) {
+        int size = insns.size();
+        ArrayList<DalvInsn> result = new ArrayList<DalvInsn>(size * 2);
+
+        for (int i = 0; i < size; i++) {
+            DalvInsn insn = insns.get(i);
+            Dop dop = insn.getOpcode();
+            InsnFormat originalFormat = dop.getFormat();
+            InsnFormat currentFormat = formats[i];
+            DalvInsn prefix;
+            DalvInsn suffix;
+
+            if (currentFormat != null) {
+                // No expansion is necessary.
+                prefix = null;
+                suffix = null;
+            } else {
+                // Expansion is required.
+                prefix = insn.hrPrefix();
+                suffix = insn.hrSuffix();
+
+                /*
+                 * Get the initial guess as to the hr version, but then
+                 * let findFormatForInsn() pick a better format, if any.
+                 */
+                insn = insn.hrVersion();
+                originalFormat = insn.getOpcode().getFormat();
+                currentFormat = findFormatForInsn(insn, originalFormat);
+            }
+
+            if (prefix != null) {
+                result.add(prefix);
+            }
+
+            if (currentFormat != originalFormat) {
+                dop = Dops.getOrNull(dop.getFamily(), currentFormat);
+                insn = insn.withOpcode(dop);
+            }
+            result.add(insn);
+
+            if (suffix != null) {
+                result.add(suffix);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Helper for {@link #finishProcessingAndGetList}, which assigns
+     * addresses to each instruction, possibly rewriting branches to
+     * fix ones that wouldn't otherwise be able to reach their
+     * targets.
+     */
+    private void assignAddressesAndFixBranches() {
+        for (;;) {
+            assignAddresses();
+            if (!fixBranches()) {
+                break;
+            }
+        }
+    }
+
+    /**
+     * Helper for {@link #assignAddressesAndFixBranches}, which
+     * assigns an address to each instruction, in order.
+     */
+    private void assignAddresses() {
+        int address = 0;
+        int size = insns.size();
+
+        for (int i = 0; i < size; i++) {
+            DalvInsn insn = insns.get(i);
+            insn.setAddress(address);
+            address += insn.codeSize();
+        }
+    }
+
+    /**
+     * Helper for {@link #assignAddressesAndFixBranches}, which checks
+     * the branch target size requirement of each branch instruction
+     * to make sure it fits. For instructions that don't fit, this
+     * rewrites them to use a {@code goto} of some sort. In the
+     * case of a conditional branch that doesn't fit, the sense of the
+     * test is reversed in order to branch around a {@code goto}
+     * to the original target.
+     *
+     * @return whether any branches had to be fixed
+     */
+    private boolean fixBranches() {
+        int size = insns.size();
+        boolean anyFixed = false;
+
+        for (int i = 0; i < size; i++) {
+            DalvInsn insn = insns.get(i);
+            if (!(insn instanceof TargetInsn)) {
+                // This loop only needs to inspect TargetInsns.
+                continue;
+            }
+
+            Dop dop = insn.getOpcode();
+            InsnFormat format = dop.getFormat();
+            TargetInsn target = (TargetInsn) insn;
+
+            if (format.branchFits(target)) {
+                continue;
+            }
+
+            if (dop.getFamily() == DalvOps.GOTO) {
+                // It is a goto; widen it if possible.
+                InsnFormat newFormat = findFormatForInsn(insn, format);
+                if (newFormat == null) {
+                    /*
+                     * The branch is already maximally large. This should
+                     * only be possible if a method somehow manages to have
+                     * more than 2^31 code units.
+                     */
+                    throw new UnsupportedOperationException("method too long");
+                }
+                dop = Dops.getOrNull(dop.getFamily(), newFormat);
+                insn = insn.withOpcode(dop);
+                insns.set(i, insn);
+            } else {
+                /*
+                 * It is a conditional: Reverse its sense, and arrange for
+                 * it to branch around an absolute goto to the original
+                 * branch target.
+                 *
+                 * Note: An invariant of the list being processed is
+                 * that every TargetInsn is followed by a CodeAddress.
+                 * Hence, it is always safe to get the next element
+                 * after a TargetInsn and cast it to CodeAddress, as
+                 * is happening a few lines down.
+                 *
+                 * Also note: Size gets incremented by one here, as we
+                 * have -- in the net -- added one additional element
+                 * to the list, so we increment i to match. The added
+                 * and changed elements will be inspected by a repeat
+                 * call to this method after this invocation returns.
+                 */
+                CodeAddress newTarget;
+                try {
+                    newTarget = (CodeAddress) insns.get(i + 1);
+                } catch (IndexOutOfBoundsException ex) {
+                    // The TargetInsn / CodeAddress invariant was violated.
+                    throw new IllegalStateException(
+                            "unpaired TargetInsn (dangling)");
+                } catch (ClassCastException ex) {
+                    // The TargetInsn / CodeAddress invariant was violated.
+                    throw new IllegalStateException("unpaired TargetInsn");
+                }
+                TargetInsn gotoInsn =
+                    new TargetInsn(Dops.GOTO, target.getPosition(),
+                            RegisterSpecList.EMPTY, target.getTarget());
+                insns.set(i, gotoInsn);
+                insns.add(i, target.withNewTargetAndReversed(newTarget));
+                size++;
+                i++;
+            }
+
+            anyFixed = true;
+        }
+
+        return anyFixed;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/PositionList.java b/dexgen/src/com/android/dexgen/dex/code/PositionList.java
new file mode 100644
index 0000000..8b52f26
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/PositionList.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.SourcePosition;
+import com.android.dexgen.util.FixedSizeList;
+
+/**
+ * List of source position entries. This class includes a utility
+ * method to extract an instance out of a {@link DalvInsnList}.
+ */
+public final class PositionList extends FixedSizeList {
+    /** {@code non-null;} empty instance */
+    public static final PositionList EMPTY = new PositionList(0);
+
+    /**
+     * constant for {@link #make} to indicate that no actual position
+     * information should be returned
+     */
+    public static final int NONE = 1;
+
+    /**
+     * constant for {@link #make} to indicate that only line number
+     * transitions should be returned
+     */
+    public static final int LINES = 2;
+
+    /**
+     * constant for {@link #make} to indicate that only "important" position
+     * information should be returned. This includes block starts and
+     * instructions that might throw.
+     */
+    public static final int IMPORTANT = 3;
+
+    /**
+     * Extracts and returns the source position information out of an
+     * instruction list.
+     *
+     * @param insns {@code non-null;} instructions to convert
+     * @param howMuch how much information should be included; one of the
+     * static constants defined by this class
+     * @return {@code non-null;} the positions list
+     */
+    public static PositionList make(DalvInsnList insns, int howMuch) {
+        switch (howMuch) {
+            case NONE: {
+                return EMPTY;
+            }
+            case LINES:
+            case IMPORTANT: {
+                // Valid.
+                break;
+            }
+            default: {
+                throw new IllegalArgumentException("bogus howMuch");
+            }
+        }
+
+        SourcePosition noInfo = SourcePosition.NO_INFO;
+        SourcePosition cur = noInfo;
+        int sz = insns.size();
+        PositionList.Entry[] arr = new PositionList.Entry[sz];
+        boolean lastWasTarget = false;
+        int at = 0;
+
+        for (int i = 0; i < sz; i++) {
+            DalvInsn insn = insns.get(i);
+
+            if (insn instanceof CodeAddress) {
+                lastWasTarget = true;;
+                continue;
+            }
+
+            SourcePosition pos = insn.getPosition();
+
+            if (pos.equals(noInfo) || pos.sameLine(cur)) {
+                continue;
+            }
+
+            if ((howMuch == IMPORTANT) && !lastWasTarget) {
+                continue;
+            }
+
+            cur = pos;
+            arr[at] = new PositionList.Entry(insn.getAddress(), pos);
+            at++;
+
+            lastWasTarget = false;
+        }
+
+        PositionList result = new PositionList(at);
+        for (int i = 0; i < at; i++) {
+            result.set(i, arr[i]);
+        }
+
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Constructs an instance. All indices initially contain {@code null}.
+     *
+     * @param size {@code >= 0;} the size of the list
+     */
+    public PositionList(int size) {
+        super(size);
+    }
+
+    /**
+     * Gets the element at the given index. It is an error to call
+     * this with the index for an element which was never set; if you
+     * do that, this will throw {@code NullPointerException}.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @return {@code non-null;} element at that index
+     */
+    public Entry get(int n) {
+        return (Entry) get0(n);
+    }
+
+    /**
+     * Sets the entry at the given index.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @param entry {@code non-null;} the entry to set at {@code n}
+     */
+    public void set(int n, Entry entry) {
+        set0(n, entry);
+    }
+
+    /**
+     * Entry in a position list.
+     */
+    public static class Entry {
+        /** {@code >= 0;} address of this entry */
+        private final int address;
+
+        /** {@code non-null;} corresponding source position information */
+        private final SourcePosition position;
+
+        /**
+         * Constructs an instance.
+         *
+         * @param address {@code >= 0;} address of this entry
+         * @param position {@code non-null;} corresponding source position information
+         */
+        public Entry (int address, SourcePosition position) {
+            if (address < 0) {
+                throw new IllegalArgumentException("address < 0");
+            }
+
+            if (position == null) {
+                throw new NullPointerException("position == null");
+            }
+
+            this.address = address;
+            this.position = position;
+        }
+
+        /**
+         * Gets the address.
+         *
+         * @return {@code >= 0;} the address
+         */
+        public int getAddress() {
+            return address;
+        }
+
+        /**
+         * Gets the source position information.
+         *
+         * @return {@code non-null;} the position information
+         */
+        public SourcePosition getPosition() {
+            return position;
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/RopToDop.java b/dexgen/src/com/android/dexgen/dex/code/RopToDop.java
new file mode 100644
index 0000000..03d1de8
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/RopToDop.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.Insn;
+import com.android.dexgen.rop.code.RegOps;
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.Rop;
+import com.android.dexgen.rop.code.Rops;
+import com.android.dexgen.rop.code.ThrowingCstInsn;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstFieldRef;
+import com.android.dexgen.rop.cst.CstString;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.type.Type;
+
+import java.util.HashMap;
+
+/**
+ * Translator from rop-level {@link Insn} instances to corresponding
+ * {@link Dop} instances.
+ */
+public final class RopToDop {
+    /** {@code non-null;} map from all the common rops to dalvik opcodes */
+    private static final HashMap<Rop, Dop> MAP;
+
+    /**
+     * This class is uninstantiable.
+     */
+    private RopToDop() {
+        // This space intentionally left blank.
+    }
+
+    static {
+        /*
+         * Note: The choices made here are to pick the optimistically
+         * smallest Dalvik opcode, and leave it to later processing to
+         * pessimize.
+         */
+        MAP = new HashMap<Rop, Dop>(400);
+        MAP.put(Rops.NOP,               Dops.NOP);
+        MAP.put(Rops.MOVE_INT,          Dops.MOVE);
+        MAP.put(Rops.MOVE_LONG,         Dops.MOVE_WIDE);
+        MAP.put(Rops.MOVE_FLOAT,        Dops.MOVE);
+        MAP.put(Rops.MOVE_DOUBLE,       Dops.MOVE_WIDE);
+        MAP.put(Rops.MOVE_OBJECT,       Dops.MOVE_OBJECT);
+        MAP.put(Rops.MOVE_PARAM_INT,    Dops.MOVE);
+        MAP.put(Rops.MOVE_PARAM_LONG,   Dops.MOVE_WIDE);
+        MAP.put(Rops.MOVE_PARAM_FLOAT,  Dops.MOVE);
+        MAP.put(Rops.MOVE_PARAM_DOUBLE, Dops.MOVE_WIDE);
+        MAP.put(Rops.MOVE_PARAM_OBJECT, Dops.MOVE_OBJECT);
+
+        /*
+         * Note: No entry for MOVE_EXCEPTION, since it varies by
+         * exception type. (That is, there is no unique instance to
+         * add to the map.)
+         */
+
+        MAP.put(Rops.CONST_INT,         Dops.CONST_4);
+        MAP.put(Rops.CONST_LONG,        Dops.CONST_WIDE_16);
+        MAP.put(Rops.CONST_FLOAT,       Dops.CONST_4);
+        MAP.put(Rops.CONST_DOUBLE,      Dops.CONST_WIDE_16);
+
+        /*
+         * Note: No entry for CONST_OBJECT, since it needs to turn
+         * into either CONST_STRING or CONST_CLASS.
+         */
+
+        /*
+         * TODO: I think the only case of this is for null, and
+         * const/4 should cover that.
+         */
+        MAP.put(Rops.CONST_OBJECT_NOTHROW, Dops.CONST_4);
+
+        MAP.put(Rops.GOTO,                 Dops.GOTO);
+        MAP.put(Rops.IF_EQZ_INT,           Dops.IF_EQZ);
+        MAP.put(Rops.IF_NEZ_INT,           Dops.IF_NEZ);
+        MAP.put(Rops.IF_LTZ_INT,           Dops.IF_LTZ);
+        MAP.put(Rops.IF_GEZ_INT,           Dops.IF_GEZ);
+        MAP.put(Rops.IF_LEZ_INT,           Dops.IF_LEZ);
+        MAP.put(Rops.IF_GTZ_INT,           Dops.IF_GTZ);
+        MAP.put(Rops.IF_EQZ_OBJECT,        Dops.IF_EQZ);
+        MAP.put(Rops.IF_NEZ_OBJECT,        Dops.IF_NEZ);
+        MAP.put(Rops.IF_EQ_INT,            Dops.IF_EQ);
+        MAP.put(Rops.IF_NE_INT,            Dops.IF_NE);
+        MAP.put(Rops.IF_LT_INT,            Dops.IF_LT);
+        MAP.put(Rops.IF_GE_INT,            Dops.IF_GE);
+        MAP.put(Rops.IF_LE_INT,            Dops.IF_LE);
+        MAP.put(Rops.IF_GT_INT,            Dops.IF_GT);
+        MAP.put(Rops.IF_EQ_OBJECT,         Dops.IF_EQ);
+        MAP.put(Rops.IF_NE_OBJECT,         Dops.IF_NE);
+        MAP.put(Rops.SWITCH,               Dops.SPARSE_SWITCH);
+        MAP.put(Rops.ADD_INT,              Dops.ADD_INT_2ADDR);
+        MAP.put(Rops.ADD_LONG,             Dops.ADD_LONG_2ADDR);
+        MAP.put(Rops.ADD_FLOAT,            Dops.ADD_FLOAT_2ADDR);
+        MAP.put(Rops.ADD_DOUBLE,           Dops.ADD_DOUBLE_2ADDR);
+        MAP.put(Rops.SUB_INT,              Dops.SUB_INT_2ADDR);
+        MAP.put(Rops.SUB_LONG,             Dops.SUB_LONG_2ADDR);
+        MAP.put(Rops.SUB_FLOAT,            Dops.SUB_FLOAT_2ADDR);
+        MAP.put(Rops.SUB_DOUBLE,           Dops.SUB_DOUBLE_2ADDR);
+        MAP.put(Rops.MUL_INT,              Dops.MUL_INT_2ADDR);
+        MAP.put(Rops.MUL_LONG,             Dops.MUL_LONG_2ADDR);
+        MAP.put(Rops.MUL_FLOAT,            Dops.MUL_FLOAT_2ADDR);
+        MAP.put(Rops.MUL_DOUBLE,           Dops.MUL_DOUBLE_2ADDR);
+        MAP.put(Rops.DIV_INT,              Dops.DIV_INT_2ADDR);
+        MAP.put(Rops.DIV_LONG,             Dops.DIV_LONG_2ADDR);
+        MAP.put(Rops.DIV_FLOAT,            Dops.DIV_FLOAT_2ADDR);
+        MAP.put(Rops.DIV_DOUBLE,           Dops.DIV_DOUBLE_2ADDR);
+        MAP.put(Rops.REM_INT,              Dops.REM_INT_2ADDR);
+        MAP.put(Rops.REM_LONG,             Dops.REM_LONG_2ADDR);
+        MAP.put(Rops.REM_FLOAT,            Dops.REM_FLOAT_2ADDR);
+        MAP.put(Rops.REM_DOUBLE,           Dops.REM_DOUBLE_2ADDR);
+        MAP.put(Rops.NEG_INT,              Dops.NEG_INT);
+        MAP.put(Rops.NEG_LONG,             Dops.NEG_LONG);
+        MAP.put(Rops.NEG_FLOAT,            Dops.NEG_FLOAT);
+        MAP.put(Rops.NEG_DOUBLE,           Dops.NEG_DOUBLE);
+        MAP.put(Rops.AND_INT,              Dops.AND_INT_2ADDR);
+        MAP.put(Rops.AND_LONG,             Dops.AND_LONG_2ADDR);
+        MAP.put(Rops.OR_INT,               Dops.OR_INT_2ADDR);
+        MAP.put(Rops.OR_LONG,              Dops.OR_LONG_2ADDR);
+        MAP.put(Rops.XOR_INT,              Dops.XOR_INT_2ADDR);
+        MAP.put(Rops.XOR_LONG,             Dops.XOR_LONG_2ADDR);
+        MAP.put(Rops.SHL_INT,              Dops.SHL_INT_2ADDR);
+        MAP.put(Rops.SHL_LONG,             Dops.SHL_LONG_2ADDR);
+        MAP.put(Rops.SHR_INT,              Dops.SHR_INT_2ADDR);
+        MAP.put(Rops.SHR_LONG,             Dops.SHR_LONG_2ADDR);
+        MAP.put(Rops.USHR_INT,             Dops.USHR_INT_2ADDR);
+        MAP.put(Rops.USHR_LONG,            Dops.USHR_LONG_2ADDR);
+        MAP.put(Rops.NOT_INT,              Dops.NOT_INT);
+        MAP.put(Rops.NOT_LONG,             Dops.NOT_LONG);
+
+        MAP.put(Rops.ADD_CONST_INT,        Dops.ADD_INT_LIT8);
+        // Note: No dalvik ops for other types of add_const.
+
+        /*
+         * Note: No dalvik ops for any type of sub_const; there's a
+         * *reverse* sub (constant - reg) for ints, though, but that
+         * should end up getting handled at optimization time.
+         */
+
+        MAP.put(Rops.MUL_CONST_INT,        Dops.MUL_INT_LIT8);
+        // Note: No dalvik ops for other types of mul_const.
+
+        MAP.put(Rops.DIV_CONST_INT,        Dops.DIV_INT_LIT8);
+        // Note: No dalvik ops for other types of div_const.
+
+        MAP.put(Rops.REM_CONST_INT,        Dops.REM_INT_LIT8);
+        // Note: No dalvik ops for other types of rem_const.
+
+        MAP.put(Rops.AND_CONST_INT,        Dops.AND_INT_LIT8);
+        // Note: No dalvik op for and_const_long.
+
+        MAP.put(Rops.OR_CONST_INT,         Dops.OR_INT_LIT8);
+        // Note: No dalvik op for or_const_long.
+
+        MAP.put(Rops.XOR_CONST_INT,        Dops.XOR_INT_LIT8);
+        // Note: No dalvik op for xor_const_long.
+
+        MAP.put(Rops.SHL_CONST_INT,        Dops.SHL_INT_LIT8);
+        // Note: No dalvik op for shl_const_long.
+
+        MAP.put(Rops.SHR_CONST_INT,        Dops.SHR_INT_LIT8);
+        // Note: No dalvik op for shr_const_long.
+
+        MAP.put(Rops.USHR_CONST_INT,       Dops.USHR_INT_LIT8);
+        // Note: No dalvik op for shr_const_long.
+
+        MAP.put(Rops.CMPL_LONG,            Dops.CMP_LONG);
+        MAP.put(Rops.CMPL_FLOAT,           Dops.CMPL_FLOAT);
+        MAP.put(Rops.CMPL_DOUBLE,          Dops.CMPL_DOUBLE);
+        MAP.put(Rops.CMPG_FLOAT,           Dops.CMPG_FLOAT);
+        MAP.put(Rops.CMPG_DOUBLE,          Dops.CMPG_DOUBLE);
+        MAP.put(Rops.CONV_L2I,             Dops.LONG_TO_INT);
+        MAP.put(Rops.CONV_F2I,             Dops.FLOAT_TO_INT);
+        MAP.put(Rops.CONV_D2I,             Dops.DOUBLE_TO_INT);
+        MAP.put(Rops.CONV_I2L,             Dops.INT_TO_LONG);
+        MAP.put(Rops.CONV_F2L,             Dops.FLOAT_TO_LONG);
+        MAP.put(Rops.CONV_D2L,             Dops.DOUBLE_TO_LONG);
+        MAP.put(Rops.CONV_I2F,             Dops.INT_TO_FLOAT);
+        MAP.put(Rops.CONV_L2F,             Dops.LONG_TO_FLOAT);
+        MAP.put(Rops.CONV_D2F,             Dops.DOUBLE_TO_FLOAT);
+        MAP.put(Rops.CONV_I2D,             Dops.INT_TO_DOUBLE);
+        MAP.put(Rops.CONV_L2D,             Dops.LONG_TO_DOUBLE);
+        MAP.put(Rops.CONV_F2D,             Dops.FLOAT_TO_DOUBLE);
+        MAP.put(Rops.TO_BYTE,              Dops.INT_TO_BYTE);
+        MAP.put(Rops.TO_CHAR,              Dops.INT_TO_CHAR);
+        MAP.put(Rops.TO_SHORT,             Dops.INT_TO_SHORT);
+        MAP.put(Rops.RETURN_VOID,          Dops.RETURN_VOID);
+        MAP.put(Rops.RETURN_INT,           Dops.RETURN);
+        MAP.put(Rops.RETURN_LONG,          Dops.RETURN_WIDE);
+        MAP.put(Rops.RETURN_FLOAT,         Dops.RETURN);
+        MAP.put(Rops.RETURN_DOUBLE,        Dops.RETURN_WIDE);
+        MAP.put(Rops.RETURN_OBJECT,        Dops.RETURN_OBJECT);
+        MAP.put(Rops.ARRAY_LENGTH,         Dops.ARRAY_LENGTH);
+        MAP.put(Rops.THROW,                Dops.THROW);
+        MAP.put(Rops.MONITOR_ENTER,        Dops.MONITOR_ENTER);
+        MAP.put(Rops.MONITOR_EXIT,         Dops.MONITOR_EXIT);
+        MAP.put(Rops.AGET_INT,             Dops.AGET);
+        MAP.put(Rops.AGET_LONG,            Dops.AGET_WIDE);
+        MAP.put(Rops.AGET_FLOAT,           Dops.AGET);
+        MAP.put(Rops.AGET_DOUBLE,          Dops.AGET_WIDE);
+        MAP.put(Rops.AGET_OBJECT,          Dops.AGET_OBJECT);
+        MAP.put(Rops.AGET_BOOLEAN,         Dops.AGET_BOOLEAN);
+        MAP.put(Rops.AGET_BYTE,            Dops.AGET_BYTE);
+        MAP.put(Rops.AGET_CHAR,            Dops.AGET_CHAR);
+        MAP.put(Rops.AGET_SHORT,           Dops.AGET_SHORT);
+        MAP.put(Rops.APUT_INT,             Dops.APUT);
+        MAP.put(Rops.APUT_LONG,            Dops.APUT_WIDE);
+        MAP.put(Rops.APUT_FLOAT,           Dops.APUT);
+        MAP.put(Rops.APUT_DOUBLE,          Dops.APUT_WIDE);
+        MAP.put(Rops.APUT_OBJECT,          Dops.APUT_OBJECT);
+        MAP.put(Rops.APUT_BOOLEAN,         Dops.APUT_BOOLEAN);
+        MAP.put(Rops.APUT_BYTE,            Dops.APUT_BYTE);
+        MAP.put(Rops.APUT_CHAR,            Dops.APUT_CHAR);
+        MAP.put(Rops.APUT_SHORT,           Dops.APUT_SHORT);
+        MAP.put(Rops.NEW_INSTANCE,         Dops.NEW_INSTANCE);
+        MAP.put(Rops.CHECK_CAST,           Dops.CHECK_CAST);
+        MAP.put(Rops.INSTANCE_OF,          Dops.INSTANCE_OF);
+
+        MAP.put(Rops.GET_FIELD_LONG,       Dops.IGET_WIDE);
+        MAP.put(Rops.GET_FIELD_FLOAT,      Dops.IGET);
+        MAP.put(Rops.GET_FIELD_DOUBLE,     Dops.IGET_WIDE);
+        MAP.put(Rops.GET_FIELD_OBJECT,     Dops.IGET_OBJECT);
+        /*
+         * Note: No map entries for get_field_* for non-long integral types,
+         * since they need to be handled specially (see dopFor() below).
+         */
+
+        MAP.put(Rops.GET_STATIC_LONG,      Dops.SGET_WIDE);
+        MAP.put(Rops.GET_STATIC_FLOAT,     Dops.SGET);
+        MAP.put(Rops.GET_STATIC_DOUBLE,    Dops.SGET_WIDE);
+        MAP.put(Rops.GET_STATIC_OBJECT,    Dops.SGET_OBJECT);
+        /*
+         * Note: No map entries for get_static* for non-long integral types,
+         * since they need to be handled specially (see dopFor() below).
+         */
+
+        MAP.put(Rops.PUT_FIELD_LONG,       Dops.IPUT_WIDE);
+        MAP.put(Rops.PUT_FIELD_FLOAT,      Dops.IPUT);
+        MAP.put(Rops.PUT_FIELD_DOUBLE,     Dops.IPUT_WIDE);
+        MAP.put(Rops.PUT_FIELD_OBJECT,     Dops.IPUT_OBJECT);
+        /*
+         * Note: No map entries for put_field_* for non-long integral types,
+         * since they need to be handled specially (see dopFor() below).
+         */
+
+        MAP.put(Rops.PUT_STATIC_LONG,      Dops.SPUT_WIDE);
+        MAP.put(Rops.PUT_STATIC_FLOAT,     Dops.SPUT);
+        MAP.put(Rops.PUT_STATIC_DOUBLE,    Dops.SPUT_WIDE);
+        MAP.put(Rops.PUT_STATIC_OBJECT,    Dops.SPUT_OBJECT);
+        /*
+         * Note: No map entries for put_static* for non-long integral types,
+         * since they need to be handled specially (see dopFor() below).
+         */
+
+        /*
+         * Note: No map entries for invoke*, new_array, and
+         * filled_new_array, since they need to be handled specially
+         * (see dopFor() below).
+         */
+    }
+
+    /**
+     * Returns the dalvik opcode appropriate for the given register-based
+     * instruction.
+     *
+     * @param insn {@code non-null;} the original instruction
+     * @return the corresponding dalvik opcode; one of the constants in
+     * {@link Dops}
+     */
+    public static Dop dopFor(Insn insn) {
+        Rop rop = insn.getOpcode();
+
+        /*
+         * First, just try looking up the rop in the MAP of easy
+         * cases.
+         */
+        Dop result = MAP.get(rop);
+        if (result != null) {
+            return result;
+        }
+
+        /*
+         * There was no easy case for the rop, so look up the opcode, and
+         * do something special for each:
+         *
+         * The move_exception, new_array, filled_new_array, and
+         * invoke* opcodes won't be found in MAP, since they'll each
+         * have different source and/or result register types / lists.
+         *
+         * The get* and put* opcodes for (non-long) integral types
+         * aren't in the map, since the type signatures aren't
+         * sufficient to distinguish between the types (the salient
+         * source or result will always be just "int").
+         *
+         * And const instruction need to distinguish between strings and
+         * classes.
+         */
+
+        switch (rop.getOpcode()) {
+            case RegOps.MOVE_EXCEPTION:   return Dops.MOVE_EXCEPTION;
+            case RegOps.INVOKE_STATIC:    return Dops.INVOKE_STATIC;
+            case RegOps.INVOKE_VIRTUAL:   return Dops.INVOKE_VIRTUAL;
+            case RegOps.INVOKE_SUPER:     return Dops.INVOKE_SUPER;
+            case RegOps.INVOKE_DIRECT:    return Dops.INVOKE_DIRECT;
+            case RegOps.INVOKE_INTERFACE: return Dops.INVOKE_INTERFACE;
+            case RegOps.NEW_ARRAY:        return Dops.NEW_ARRAY;
+            case RegOps.FILLED_NEW_ARRAY: return Dops.FILLED_NEW_ARRAY;
+            case RegOps.FILL_ARRAY_DATA:  return Dops.FILL_ARRAY_DATA;
+            case RegOps.MOVE_RESULT: {
+                RegisterSpec resultReg = insn.getResult();
+
+                if (resultReg == null) {
+                    return Dops.NOP;
+                } else {
+                    switch (resultReg.getBasicType()) {
+                        case Type.BT_INT:
+                        case Type.BT_FLOAT:
+                        case Type.BT_BOOLEAN:
+                        case Type.BT_BYTE:
+                        case Type.BT_CHAR:
+                        case Type.BT_SHORT:
+                            return Dops.MOVE_RESULT;
+                        case Type.BT_LONG:
+                        case Type.BT_DOUBLE:
+                            return Dops.MOVE_RESULT_WIDE;
+                        case Type.BT_OBJECT:
+                            return Dops.MOVE_RESULT_OBJECT;
+                        default: {
+                            throw new RuntimeException("Unexpected basic type");
+                        }
+                    }
+                }
+            }
+
+            case RegOps.GET_FIELD: {
+                CstFieldRef ref =
+                    (CstFieldRef) ((ThrowingCstInsn) insn).getConstant();
+                int basicType = ref.getBasicType();
+                switch (basicType) {
+                    case Type.BT_BOOLEAN: return Dops.IGET_BOOLEAN;
+                    case Type.BT_BYTE:    return Dops.IGET_BYTE;
+                    case Type.BT_CHAR:    return Dops.IGET_CHAR;
+                    case Type.BT_SHORT:   return Dops.IGET_SHORT;
+                    case Type.BT_INT:     return Dops.IGET;
+                }
+                break;
+            }
+            case RegOps.PUT_FIELD: {
+                CstFieldRef ref =
+                    (CstFieldRef) ((ThrowingCstInsn) insn).getConstant();
+                int basicType = ref.getBasicType();
+                switch (basicType) {
+                    case Type.BT_BOOLEAN: return Dops.IPUT_BOOLEAN;
+                    case Type.BT_BYTE:    return Dops.IPUT_BYTE;
+                    case Type.BT_CHAR:    return Dops.IPUT_CHAR;
+                    case Type.BT_SHORT:   return Dops.IPUT_SHORT;
+                    case Type.BT_INT:     return Dops.IPUT;
+                }
+                break;
+            }
+            case RegOps.GET_STATIC: {
+                CstFieldRef ref =
+                    (CstFieldRef) ((ThrowingCstInsn) insn).getConstant();
+                int basicType = ref.getBasicType();
+                switch (basicType) {
+                    case Type.BT_BOOLEAN: return Dops.SGET_BOOLEAN;
+                    case Type.BT_BYTE:    return Dops.SGET_BYTE;
+                    case Type.BT_CHAR:    return Dops.SGET_CHAR;
+                    case Type.BT_SHORT:   return Dops.SGET_SHORT;
+                    case Type.BT_INT:     return Dops.SGET;
+                }
+                break;
+            }
+            case RegOps.PUT_STATIC: {
+                CstFieldRef ref =
+                    (CstFieldRef) ((ThrowingCstInsn) insn).getConstant();
+                int basicType = ref.getBasicType();
+                switch (basicType) {
+                    case Type.BT_BOOLEAN: return Dops.SPUT_BOOLEAN;
+                    case Type.BT_BYTE:    return Dops.SPUT_BYTE;
+                    case Type.BT_CHAR:    return Dops.SPUT_CHAR;
+                    case Type.BT_SHORT:   return Dops.SPUT_SHORT;
+                    case Type.BT_INT:     return Dops.SPUT;
+                }
+                break;
+            }
+            case RegOps.CONST: {
+                Constant cst = ((ThrowingCstInsn) insn).getConstant();
+                if (cst instanceof CstType) {
+                    return Dops.CONST_CLASS;
+                } else if (cst instanceof CstString) {
+                    return Dops.CONST_STRING;
+                }
+                break;
+            }
+        }
+
+        throw new RuntimeException("unknown rop: " + rop);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/RopTranslator.java b/dexgen/src/com/android/dexgen/dex/code/RopTranslator.java
new file mode 100644
index 0000000..ad05f2b
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/RopTranslator.java
@@ -0,0 +1,872 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.BasicBlock;
+import com.android.dexgen.rop.code.BasicBlockList;
+import com.android.dexgen.rop.code.FillArrayDataInsn;
+import com.android.dexgen.rop.code.Insn;
+import com.android.dexgen.rop.code.LocalVariableInfo;
+import com.android.dexgen.rop.code.PlainCstInsn;
+import com.android.dexgen.rop.code.PlainInsn;
+import com.android.dexgen.rop.code.RegOps;
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.RegisterSpecSet;
+import com.android.dexgen.rop.code.Rop;
+import com.android.dexgen.rop.code.RopMethod;
+import com.android.dexgen.rop.code.SourcePosition;
+import com.android.dexgen.rop.code.SwitchInsn;
+import com.android.dexgen.rop.code.ThrowingCstInsn;
+import com.android.dexgen.rop.code.ThrowingInsn;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstInteger;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.Bits;
+import com.android.dexgen.util.IntList;
+
+import java.util.ArrayList;
+
+/**
+ * Translator from {@link RopMethod} to {@link DalvCode}. The {@link
+ * #translate} method is the thing to call on this class.
+ */
+public final class RopTranslator {
+    /** {@code non-null;} method to translate */
+    private final RopMethod method;
+
+    /**
+     * how much position info to preserve; one of the static
+     * constants in {@link PositionList}
+     */
+    private final int positionInfo;
+
+    /** {@code null-ok;} local variable info to use */
+    private final LocalVariableInfo locals;
+
+    /** {@code non-null;} container for all the address objects for the method */
+    private final BlockAddresses addresses;
+
+    /** {@code non-null;} list of output instructions in-progress */
+    private final OutputCollector output;
+
+    /** {@code non-null;} visitor to use during translation */
+    private final TranslationVisitor translationVisitor;
+
+    /** {@code >= 0;} register count for the method */
+    private final int regCount;
+
+    /** {@code null-ok;} block output order; becomes non-null in {@link #pickOrder} */
+    private int[] order;
+
+    /** size, in register units, of all the parameters to this method */
+    private final int paramSize;
+
+    /**
+     * true if the parameters to this method happen to be in proper order
+     * at the end of the frame (as the optimizer emits them)
+     */
+    private boolean paramsAreInOrder;
+
+    /**
+     * Translates a {@link RopMethod}. This may modify the given
+     * input.
+     *
+     * @param method {@code non-null;} the original method
+     * @param positionInfo how much position info to preserve; one of the
+     * static constants in {@link PositionList}
+     * @param locals {@code null-ok;} local variable information to use
+     * @param paramSize size, in register units, of all the parameters to
+     * this method
+     * @return {@code non-null;} the translated version
+     */
+    public static DalvCode translate(RopMethod method, int positionInfo,
+                                     LocalVariableInfo locals, int paramSize) {
+        RopTranslator translator =
+            new RopTranslator(method, positionInfo, locals,
+                    paramSize);
+        return translator.translateAndGetResult();
+    }
+
+    /**
+     * Constructs an instance. This method is private. Use {@link #translate}.
+     *
+     * @param method {@code non-null;} the original method
+     * @param positionInfo how much position info to preserve; one of the
+     * static constants in {@link PositionList}
+     * @param locals {@code null-ok;} local variable information to use
+     * @param paramSize size, in register units, of all the parameters to
+     * this method
+     */
+    private RopTranslator(RopMethod method, int positionInfo,
+                          LocalVariableInfo locals, int paramSize) {
+        this.method = method;
+        this.positionInfo = positionInfo;
+        this.locals = locals;
+        this.addresses = new BlockAddresses(method);
+        this.paramSize = paramSize;
+        this.order = null;
+        this.paramsAreInOrder = calculateParamsAreInOrder(method, paramSize);
+
+        BasicBlockList blocks = method.getBlocks();
+        int bsz = blocks.size();
+
+        /*
+         * Max possible instructions includes three code address
+         * objects per basic block (to the first and last instruction,
+         * and just past the end of the block), and the possibility of
+         * an extra goto at the end of each basic block.
+         */
+        int maxInsns = (bsz * 3) + blocks.getInstructionCount();
+
+        if (locals != null) {
+            /*
+             * If we're tracking locals, then there's could be another
+             * extra instruction per block (for the locals state at the
+             * start of the block) as well as one for each interblock
+             * local introduction.
+             */
+            maxInsns += bsz + locals.getAssignmentCount();
+        }
+
+        /*
+         * If params are not in order, we will need register space
+         * for them before this is all over...
+         */
+        this.regCount = blocks.getRegCount()
+                + (paramsAreInOrder ? 0 : this.paramSize);
+
+        this.output = new OutputCollector(maxInsns, bsz * 3, regCount);
+
+        if (locals != null) {
+            this.translationVisitor =
+                new LocalVariableAwareTranslationVisitor(output, locals);
+        } else {
+            this.translationVisitor = new TranslationVisitor(output);
+        }
+    }
+
+    /**
+     * Checks to see if the move-param instructions that occur in this
+     * method happen to slot the params in an order at the top of the
+     * stack frame that matches dalvik's calling conventions. This will
+     * alway result in "true" for methods that have run through the
+     * SSA optimizer.
+     *
+     * @param paramSize size, in register units, of all the parameters
+     * to this method
+     */
+    private static boolean calculateParamsAreInOrder(RopMethod method,
+            final int paramSize) {
+        final boolean[] paramsAreInOrder = { true };
+        final int initialRegCount = method.getBlocks().getRegCount();
+
+        /*
+         * We almost could just check the first block here, but the
+         * {@code cf} layer will put in a second move-param in a
+         * subsequent block in the case of synchronized methods.
+         */
+        method.getBlocks().forEachInsn(new Insn.BaseVisitor() {
+            public void visitPlainCstInsn(PlainCstInsn insn) {
+                if (insn.getOpcode().getOpcode()== RegOps.MOVE_PARAM) {
+                    int param =
+                        ((CstInteger) insn.getConstant()).getValue();
+
+                    paramsAreInOrder[0] = paramsAreInOrder[0]
+                            && ((initialRegCount - paramSize + param)
+                                == insn.getResult().getReg());
+                }
+            }
+        });
+
+        return paramsAreInOrder[0];
+    }
+
+    /**
+     * Does the translation and returns the result.
+     *
+     * @return {@code non-null;} the result
+     */
+    private DalvCode translateAndGetResult() {
+        pickOrder();
+        outputInstructions();
+
+        StdCatchBuilder catches =
+            new StdCatchBuilder(method, order, addresses);
+
+        return new DalvCode(positionInfo, output.getFinisher(), catches);
+    }
+
+    /**
+     * Performs initial creation of output instructions based on the
+     * original blocks.
+     */
+    private void outputInstructions() {
+        BasicBlockList blocks = method.getBlocks();
+        int[] order = this.order;
+        int len = order.length;
+
+        // Process the blocks in output order.
+        for (int i = 0; i < len; i++) {
+            int nextI = i + 1;
+            int nextLabel = (nextI == order.length) ? -1 : order[nextI];
+            outputBlock(blocks.labelToBlock(order[i]), nextLabel);
+        }
+    }
+
+    /**
+     * Helper for {@link #outputInstructions}, which does the processing
+     * and output of one block.
+     *
+     * @param block {@code non-null;} the block to process and output
+     * @param nextLabel {@code >= -1;} the next block that will be processed, or
+     * {@code -1} if there is no next block
+     */
+    private void outputBlock(BasicBlock block, int nextLabel) {
+        // Append the code address for this block.
+        CodeAddress startAddress = addresses.getStart(block);
+        output.add(startAddress);
+
+        // Append the local variable state for the block.
+        if (locals != null) {
+            RegisterSpecSet starts = locals.getStarts(block);
+            output.add(new LocalSnapshot(startAddress.getPosition(),
+                                         starts));
+        }
+
+        /*
+         * Choose and append an output instruction for each original
+         * instruction.
+         */
+        translationVisitor.setBlock(block, addresses.getLast(block));
+        block.getInsns().forEach(translationVisitor);
+
+        // Insert the block end code address.
+        output.add(addresses.getEnd(block));
+
+        // Set up for end-of-block activities.
+
+        int succ = block.getPrimarySuccessor();
+        Insn lastInsn = block.getLastInsn();
+
+        /*
+         * Check for (and possibly correct for) a non-optimal choice of
+         * which block will get output next.
+         */
+
+        if ((succ >= 0) && (succ != nextLabel)) {
+            /*
+             * The block has a "primary successor" and that primary
+             * successor isn't the next block to be output.
+             */
+            Rop lastRop = lastInsn.getOpcode();
+            if ((lastRop.getBranchingness() == Rop.BRANCH_IF) &&
+                    (block.getSecondarySuccessor() == nextLabel)) {
+                /*
+                 * The block ends with an "if" of some sort, and its
+                 * secondary successor (the "then") is in fact the
+                 * next block to output. So, reverse the sense of
+                 * the test, so that we can just emit the next block
+                 * without an interstitial goto.
+                 */
+                output.reverseBranch(1, addresses.getStart(succ));
+            } else {
+                /*
+                 * Our only recourse is to add a goto here to get the
+                 * flow to be correct.
+                 */
+                TargetInsn insn =
+                    new TargetInsn(Dops.GOTO, lastInsn.getPosition(),
+                            RegisterSpecList.EMPTY,
+                            addresses.getStart(succ));
+                output.add(insn);
+            }
+        }
+    }
+
+    /**
+     * Picks an order for the blocks by doing "trace" analysis.
+     */
+    private void pickOrder() {
+        BasicBlockList blocks = method.getBlocks();
+        int sz = blocks.size();
+        int maxLabel = blocks.getMaxLabel();
+        int[] workSet = Bits.makeBitSet(maxLabel);
+        int[] tracebackSet = Bits.makeBitSet(maxLabel);
+
+        for (int i = 0; i < sz; i++) {
+            BasicBlock one = blocks.get(i);
+            Bits.set(workSet, one.getLabel());
+        }
+
+        int[] order = new int[sz];
+        int at = 0;
+
+        /*
+         * Starting with the designated "first label" (that is, the
+         * first block of the method), add that label to the order,
+         * and then pick its first as-yet unordered successor to
+         * immediately follow it, giving top priority to the primary
+         * (aka default) successor (if any). Keep following successors
+         * until the trace runs out of possibilities. Then, continue
+         * by finding an unordered chain containing the first as-yet
+         * unordered block, and adding it to the order, and so on.
+         */
+        for (int label = method.getFirstLabel();
+             label != -1;
+             label = Bits.findFirst(workSet, 0)) {
+
+            /*
+             * Attempt to trace backward from the chosen block to an
+             * as-yet unordered predecessor which lists the chosen
+             * block as its primary successor, and so on, until we
+             * fail to find such an unordered predecessor. Start the
+             * trace with that block. Note that the first block in the
+             * method has no predecessors, so in that case this loop
+             * will simply terminate with zero iterations and without
+             * picking a new starter block.
+             */
+            traceBack:
+            for (;;) {
+                IntList preds = method.labelToPredecessors(label);
+                int psz = preds.size();
+
+                for (int i = 0; i < psz; i++) {
+                    int predLabel = preds.get(i);
+
+                    if (Bits.get(tracebackSet, predLabel)) {
+                        /*
+                         * We found a predecessor loop; stop tracing back
+                         * from here.
+                         */
+                        break;
+                    }
+
+                    if (!Bits.get(workSet, predLabel)) {
+                        // This one's already ordered.
+                        continue;
+                    }
+
+                    BasicBlock pred = blocks.labelToBlock(predLabel);
+                    if (pred.getPrimarySuccessor() == label) {
+                        // Found one!
+                        label = predLabel;
+                        Bits.set(tracebackSet, label);
+                        continue traceBack;
+                    }
+                }
+
+                // Failed to find a better block to start the trace.
+                break;
+            }
+
+            /*
+             * Trace a path from the chosen block to one of its
+             * unordered successors (hopefully the primary), and so
+             * on, until we run out of unordered successors.
+             */
+            while (label != -1) {
+                Bits.clear(workSet, label);
+                Bits.clear(tracebackSet, label);
+                order[at] = label;
+                at++;
+
+                BasicBlock one = blocks.labelToBlock(label);
+                BasicBlock preferredBlock = blocks.preferredSuccessorOf(one);
+
+                if (preferredBlock == null) {
+                    break;
+                }
+
+                int preferred = preferredBlock.getLabel();
+                int primary = one.getPrimarySuccessor();
+
+                if (Bits.get(workSet, preferred)) {
+                    /*
+                     * Order the current block's preferred successor
+                     * next, as it has yet to be scheduled.
+                     */
+                    label = preferred;
+                } else if ((primary != preferred) && (primary >= 0)
+                        && Bits.get(workSet, primary)) {
+                    /*
+                     * The primary is available, so use that.
+                     */
+                    label = primary;
+                } else {
+                    /*
+                     * There's no obvious candidate, so pick the first
+                     * one that's available, if any.
+                     */
+                    IntList successors = one.getSuccessors();
+                    int ssz = successors.size();
+                    label = -1;
+                    for (int i = 0; i < ssz; i++) {
+                        int candidate = successors.get(i);
+                        if (Bits.get(workSet, candidate)) {
+                            label = candidate;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        if (at != sz) {
+            // There was a duplicate block label.
+            throw new RuntimeException("shouldn't happen");
+        }
+
+        this.order = order;
+    }
+
+    /**
+     * Gets the complete register list (result and sources) out of a
+     * given rop instruction. For insns that are commutative, have
+     * two register sources, and have a source equal to the result,
+     * place that source first.
+     *
+     * @param insn {@code non-null;} instruction in question
+     * @return {@code non-null;} the instruction's complete register list
+     */
+    private static RegisterSpecList getRegs(Insn insn) {
+        return getRegs(insn, insn.getResult());
+    }
+
+    /**
+     * Gets the complete register list (result and sources) out of a
+     * given rop instruction. For insns that are commutative, have
+     * two register sources, and have a source equal to the result,
+     * place that source first.
+     *
+     * @param insn {@code non-null;} instruction in question
+     * @param resultReg {@code null-ok;} the real result to use (ignore the insn's)
+     * @return {@code non-null;} the instruction's complete register list
+     */
+    private static RegisterSpecList getRegs(Insn insn,
+            RegisterSpec resultReg) {
+        RegisterSpecList regs = insn.getSources();
+
+        if (insn.getOpcode().isCommutative()
+                && (regs.size() == 2)
+                && (resultReg.getReg() == regs.get(1).getReg())) {
+
+            /*
+             * For commutative ops which have two register sources,
+             * if the second source is the same register as the result,
+             * swap the sources so that an opcode of form 12x can be selected
+             * instead of one of form 23x
+             */
+
+            regs = RegisterSpecList.make(regs.get(1), regs.get(0));
+        }
+
+        if (resultReg == null) {
+            return regs;
+        }
+
+        return regs.withFirst(resultReg);
+    }
+
+    /**
+     * Instruction visitor class for doing the instruction translation per se.
+     */
+    private class TranslationVisitor implements Insn.Visitor {
+        /** {@code non-null;} list of output instructions in-progress */
+        private final OutputCollector output;
+
+        /** {@code non-null;} basic block being worked on */
+        private BasicBlock block;
+
+        /**
+         * {@code null-ok;} code address for the salient last instruction of the
+         * block (used before switches and throwing instructions)
+         */
+        private CodeAddress lastAddress;
+
+        /**
+         * Constructs an instance.
+         *
+         * @param output {@code non-null;} destination for instruction output
+         */
+        public TranslationVisitor(OutputCollector output) {
+            this.output = output;
+        }
+
+        /**
+         * Sets the block currently being worked on.
+         *
+         * @param block {@code non-null;} the block
+         * @param lastAddress {@code non-null;} code address for the salient
+         * last instruction of the block
+         */
+        public void setBlock(BasicBlock block, CodeAddress lastAddress) {
+            this.block = block;
+            this.lastAddress = lastAddress;
+        }
+
+        /** {@inheritDoc} */
+        public void visitPlainInsn(PlainInsn insn) {
+            Rop rop = insn.getOpcode();
+            if (rop.getOpcode() == RegOps.MARK_LOCAL) {
+                /*
+                 * Ignore these. They're dealt with by
+                 * the LocalVariableAwareTranslationVisitor
+                 */
+                return;
+            }
+            if (rop.getOpcode() == RegOps.MOVE_RESULT_PSEUDO) {
+                // These get skipped
+                return;
+            }
+
+            SourcePosition pos = insn.getPosition();
+            Dop opcode = RopToDop.dopFor(insn);
+            DalvInsn di;
+
+            switch (rop.getBranchingness()) {
+                case Rop.BRANCH_NONE:
+                case Rop.BRANCH_RETURN:
+                case Rop.BRANCH_THROW: {
+                    di = new SimpleInsn(opcode, pos, getRegs(insn));
+                    break;
+                }
+                case Rop.BRANCH_GOTO: {
+                    /*
+                     * Code in the main translation loop will emit a
+                     * goto if necessary (if the branch isn't to the
+                     * immediately subsequent block).
+                     */
+                    return;
+                }
+                case Rop.BRANCH_IF: {
+                    int target = block.getSuccessors().get(1);
+                    di = new TargetInsn(opcode, pos, getRegs(insn),
+                                        addresses.getStart(target));
+                    break;
+                }
+                default: {
+                    throw new RuntimeException("shouldn't happen");
+                }
+            }
+
+            addOutput(di);
+        }
+
+        /** {@inheritDoc} */
+        public void visitPlainCstInsn(PlainCstInsn insn) {
+            SourcePosition pos = insn.getPosition();
+            Dop opcode = RopToDop.dopFor(insn);
+            Rop rop = insn.getOpcode();
+            int ropOpcode = rop.getOpcode();
+            DalvInsn di;
+
+            if (rop.getBranchingness() != Rop.BRANCH_NONE) {
+                throw new RuntimeException("shouldn't happen");
+            }
+
+            if (ropOpcode == RegOps.MOVE_PARAM) {
+                if (!paramsAreInOrder) {
+                    /*
+                     * Parameters are not in order at the top of the reg space.
+                     * We need to add moves.
+                     */
+
+                    RegisterSpec dest = insn.getResult();
+                    int param =
+                        ((CstInteger) insn.getConstant()).getValue();
+                    RegisterSpec source =
+                        RegisterSpec.make(regCount - paramSize + param,
+                                dest.getType());
+                    di = new SimpleInsn(opcode, pos,
+                                        RegisterSpecList.make(dest, source));
+                    addOutput(di);
+                }
+            } else {
+                // No moves required for the parameters
+                RegisterSpecList regs = getRegs(insn);
+                di = new CstInsn(opcode, pos, regs, insn.getConstant());
+                addOutput(di);
+            }
+        }
+
+        /** {@inheritDoc} */
+        public void visitSwitchInsn(SwitchInsn insn) {
+            SourcePosition pos = insn.getPosition();
+            IntList cases = insn.getCases();
+            IntList successors = block.getSuccessors();
+            int casesSz = cases.size();
+            int succSz = successors.size();
+            int primarySuccessor = block.getPrimarySuccessor();
+
+            /*
+             * Check the assumptions that the number of cases is one
+             * less than the number of successors and that the last
+             * successor in the list is the primary (in this case, the
+             * default). This test is here to guard against forgetting
+             * to change this code if the way switch instructions are
+             * constructed also gets changed.
+             */
+            if ((casesSz != (succSz - 1)) ||
+                (primarySuccessor != successors.get(casesSz))) {
+                throw new RuntimeException("shouldn't happen");
+            }
+
+            CodeAddress[] switchTargets = new CodeAddress[casesSz];
+
+            for (int i = 0; i < casesSz; i++) {
+                int label = successors.get(i);
+                switchTargets[i] = addresses.getStart(label);
+            }
+
+            CodeAddress dataAddress = new CodeAddress(pos);
+            SwitchData dataInsn =
+                new SwitchData(pos, lastAddress, cases, switchTargets);
+            Dop opcode = dataInsn.isPacked() ?
+                Dops.PACKED_SWITCH : Dops.SPARSE_SWITCH;
+            TargetInsn switchInsn =
+                new TargetInsn(opcode, pos, getRegs(insn), dataAddress);
+
+            addOutput(lastAddress);
+            addOutput(switchInsn);
+
+            addOutputSuffix(new OddSpacer(pos));
+            addOutputSuffix(dataAddress);
+            addOutputSuffix(dataInsn);
+        }
+
+        /**
+         * Looks forward to the current block's primary successor, returning
+         * the RegisterSpec of the result of the move-result-pseudo at the
+         * top of that block or null if none.
+         *
+         * @return {@code null-ok;} result of move-result-pseudo at the beginning of
+         * primary successor
+         */
+        private RegisterSpec getNextMoveResultPseudo()
+        {
+            int label = block.getPrimarySuccessor();
+
+            if (label < 0) {
+                return null;
+            }
+
+            Insn insn
+                    = method.getBlocks().labelToBlock(label).getInsns().get(0);
+
+            if (insn.getOpcode().getOpcode() != RegOps.MOVE_RESULT_PSEUDO) {
+                return null;
+            } else {
+                return insn.getResult();
+            }
+        }
+
+        /** {@inheritDoc} */
+        public void visitThrowingCstInsn(ThrowingCstInsn insn) {
+            SourcePosition pos = insn.getPosition();
+            Dop opcode = RopToDop.dopFor(insn);
+            Rop rop = insn.getOpcode();
+            Constant cst = insn.getConstant();
+
+            if (rop.getBranchingness() != Rop.BRANCH_THROW) {
+                throw new RuntimeException("shouldn't happen");
+            }
+
+            addOutput(lastAddress);
+
+            if (rop.isCallLike()) {
+                RegisterSpecList regs = insn.getSources();
+                DalvInsn di = new CstInsn(opcode, pos, regs, cst);
+
+                addOutput(di);
+            } else {
+                RegisterSpec realResult = getNextMoveResultPseudo();
+
+                RegisterSpecList regs = getRegs(insn, realResult);
+                DalvInsn di;
+
+                boolean hasResult = opcode.hasResult()
+                        || (rop.getOpcode() == RegOps.CHECK_CAST);
+
+                if (hasResult != (realResult != null)) {
+                    throw new RuntimeException(
+                            "Insn with result/move-result-pseudo mismatch " +
+                            insn);
+                }
+
+                if ((rop.getOpcode() == RegOps.NEW_ARRAY) &&
+                    (opcode.getOpcode() != DalvOps.NEW_ARRAY)) {
+                    /*
+                     * It's a type-specific new-array-<primitive>, and
+                     * so it should be turned into a SimpleInsn (no
+                     * constant ref as it's implicit).
+                     */
+                    di = new SimpleInsn(opcode, pos, regs);
+                } else {
+                    /*
+                     * This is the general case for constant-bearing
+                     * instructions.
+                     */
+                    di = new CstInsn(opcode, pos, regs, cst);
+                }
+
+                addOutput(di);
+            }
+        }
+
+        /** {@inheritDoc} */
+        public void visitThrowingInsn(ThrowingInsn insn) {
+            SourcePosition pos = insn.getPosition();
+            Dop opcode = RopToDop.dopFor(insn);
+            Rop rop = insn.getOpcode();
+            RegisterSpec realResult;
+
+            if (rop.getBranchingness() != Rop.BRANCH_THROW) {
+                throw new RuntimeException("shouldn't happen");
+            }
+
+            realResult = getNextMoveResultPseudo();
+
+            if (opcode.hasResult() != (realResult != null)) {
+                throw new RuntimeException(
+                        "Insn with result/move-result-pseudo mismatch" + insn);
+            }
+
+            addOutput(lastAddress);
+
+            DalvInsn di = new SimpleInsn(opcode, pos,
+                    getRegs(insn, realResult));
+
+            addOutput(di);
+        }
+
+        /** {@inheritDoc} */
+        public void visitFillArrayDataInsn(FillArrayDataInsn insn) {
+            SourcePosition pos = insn.getPosition();
+            Constant cst = insn.getConstant();
+            ArrayList<Constant> values = insn.getInitValues();
+            Rop rop = insn.getOpcode();
+
+            if (rop.getBranchingness() != Rop.BRANCH_NONE) {
+                throw new RuntimeException("shouldn't happen");
+            }
+            CodeAddress dataAddress = new CodeAddress(pos);
+            ArrayData dataInsn =
+                new ArrayData(pos, lastAddress, values, cst);
+
+            TargetInsn fillArrayDataInsn =
+                new TargetInsn(Dops.FILL_ARRAY_DATA, pos, getRegs(insn),
+                        dataAddress);
+
+            addOutput(lastAddress);
+            addOutput(fillArrayDataInsn);
+
+            addOutputSuffix(new OddSpacer(pos));
+            addOutputSuffix(dataAddress);
+            addOutputSuffix(dataInsn);
+        }
+
+        /**
+         * Adds to the output.
+         *
+         * @param insn {@code non-null;} instruction to add
+         */
+        protected void addOutput(DalvInsn insn) {
+            output.add(insn);
+        }
+
+        /**
+         * Adds to the output suffix.
+         *
+         * @param insn {@code non-null;} instruction to add
+         */
+        protected void addOutputSuffix(DalvInsn insn) {
+            output.addSuffix(insn);
+        }
+    }
+
+    /**
+     * Instruction visitor class for doing instruction translation with
+     * local variable tracking
+     */
+    private class LocalVariableAwareTranslationVisitor
+            extends TranslationVisitor {
+        /** {@code non-null;} local variable info */
+        private LocalVariableInfo locals;
+
+        /**
+         * Constructs an instance.
+         *
+         * @param output {@code non-null;} destination for instruction output
+         * @param locals {@code non-null;} the local variable info
+         */
+        public LocalVariableAwareTranslationVisitor(OutputCollector output,
+                                                    LocalVariableInfo locals) {
+            super(output);
+            this.locals = locals;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void visitPlainInsn(PlainInsn insn) {
+            super.visitPlainInsn(insn);
+            addIntroductionIfNecessary(insn);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void visitPlainCstInsn(PlainCstInsn insn) {
+            super.visitPlainCstInsn(insn);
+            addIntroductionIfNecessary(insn);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void visitSwitchInsn(SwitchInsn insn) {
+            super.visitSwitchInsn(insn);
+            addIntroductionIfNecessary(insn);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void visitThrowingCstInsn(ThrowingCstInsn insn) {
+            super.visitThrowingCstInsn(insn);
+            addIntroductionIfNecessary(insn);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void visitThrowingInsn(ThrowingInsn insn) {
+            super.visitThrowingInsn(insn);
+            addIntroductionIfNecessary(insn);
+        }
+
+        /**
+         * Adds a {@link LocalStart} to the output if the given
+         * instruction in fact introduces a local variable.
+         *
+         * @param insn {@code non-null;} instruction in question
+         */
+        public void addIntroductionIfNecessary(Insn insn) {
+            RegisterSpec spec = locals.getAssignment(insn);
+
+            if (spec != null) {
+                addOutput(new LocalStart(insn.getPosition(), spec));
+            }
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/SimpleInsn.java b/dexgen/src/com/android/dexgen/dex/code/SimpleInsn.java
new file mode 100644
index 0000000..abef242
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/SimpleInsn.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.SourcePosition;
+
+/**
+ * Instruction which has no extra info beyond the basics provided for in
+ * the base class.
+ */
+public final class SimpleInsn extends FixedSizeInsn {
+    /**
+     * Constructs an instance. The output address of this instance is initially
+     * unknown ({@code -1}).
+     *
+     * @param opcode the opcode; one of the constants from {@link Dops}
+     * @param position {@code non-null;} source position
+     * @param registers {@code non-null;} register list, including a
+     * result register if appropriate (that is, registers may be either
+     * ins or outs)
+     */
+    public SimpleInsn(Dop opcode, SourcePosition position,
+                      RegisterSpecList registers) {
+        super(opcode, position, registers);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withOpcode(Dop opcode) {
+        return new SimpleInsn(opcode, getPosition(), getRegisters());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withRegisters(RegisterSpecList registers) {
+        return new SimpleInsn(getOpcode(), getPosition(), registers);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String argString() {
+        return null;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/StdCatchBuilder.java b/dexgen/src/com/android/dexgen/dex/code/StdCatchBuilder.java
new file mode 100644
index 0000000..ba149e7
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/StdCatchBuilder.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.BasicBlock;
+import com.android.dexgen.rop.code.BasicBlockList;
+import com.android.dexgen.rop.code.RopMethod;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeList;
+import com.android.dexgen.util.IntList;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * Constructor of {@link CatchTable} instances from {@link RopMethod}
+ * and associated data.
+ */
+public final class StdCatchBuilder implements CatchBuilder {
+    /** the maximum range of a single catch handler, in code units */
+    private static final int MAX_CATCH_RANGE = 65535;
+
+    /** {@code non-null;} method to build the list for */
+    private final RopMethod method;
+
+    /** {@code non-null;} block output order */
+    private final int[] order;
+
+    /** {@code non-null;} address objects for each block */
+    private final BlockAddresses addresses;
+
+    /**
+     * Constructs an instance. It merely holds onto its parameters for
+     * a subsequent call to {@link #build}.
+     *
+     * @param method {@code non-null;} method to build the list for
+     * @param order {@code non-null;} block output order
+     * @param addresses {@code non-null;} address objects for each block
+     */
+    public StdCatchBuilder(RopMethod method, int[] order,
+            BlockAddresses addresses) {
+        if (method == null) {
+            throw new NullPointerException("method == null");
+        }
+
+        if (order == null) {
+            throw new NullPointerException("order == null");
+        }
+
+        if (addresses == null) {
+            throw new NullPointerException("addresses == null");
+        }
+
+        this.method = method;
+        this.order = order;
+        this.addresses = addresses;
+    }
+
+    /** {@inheritDoc} */
+    public CatchTable build() {
+        return build(method, order, addresses);
+    }
+
+    /** {@inheritDoc} */
+    public boolean hasAnyCatches() {
+        BasicBlockList blocks = method.getBlocks();
+        int size = blocks.size();
+
+        for (int i = 0; i < size; i++) {
+            BasicBlock block = blocks.get(i);
+            TypeList catches = block.getLastInsn().getCatches();
+            if (catches.size() != 0) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public HashSet<Type> getCatchTypes() {
+        HashSet<Type> result = new HashSet<Type>(20);
+        BasicBlockList blocks = method.getBlocks();
+        int size = blocks.size();
+
+        for (int i = 0; i < size; i++) {
+            BasicBlock block = blocks.get(i);
+            TypeList catches = block.getLastInsn().getCatches();
+            int catchSize = catches.size();
+
+            for (int j = 0; j < catchSize; j++) {
+                result.add(catches.getType(j));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Builds and returns the catch table for a given method.
+     *
+     * @param method {@code non-null;} method to build the list for
+     * @param order {@code non-null;} block output order
+     * @param addresses {@code non-null;} address objects for each block
+     * @return {@code non-null;} the constructed table
+     */
+    public static CatchTable build(RopMethod method, int[] order,
+            BlockAddresses addresses) {
+        int len = order.length;
+        BasicBlockList blocks = method.getBlocks();
+        ArrayList<CatchTable.Entry> resultList =
+            new ArrayList<CatchTable.Entry>(len);
+        CatchHandlerList currentHandlers = CatchHandlerList.EMPTY;
+        BasicBlock currentStartBlock = null;
+        BasicBlock currentEndBlock = null;
+
+        for (int i = 0; i < len; i++) {
+            BasicBlock block = blocks.labelToBlock(order[i]);
+
+            if (!block.canThrow()) {
+                /*
+                 * There is no need to concern ourselves with the
+                 * placement of blocks that can't throw with respect
+                 * to the blocks that *can* throw.
+                 */
+                continue;
+            }
+
+            CatchHandlerList handlers = handlersFor(block, addresses);
+
+            if (currentHandlers.size() == 0) {
+                // This is the start of a new catch range.
+                currentStartBlock = block;
+                currentEndBlock = block;
+                currentHandlers = handlers;
+                continue;
+            }
+
+            if (currentHandlers.equals(handlers)
+                    && rangeIsValid(currentStartBlock, block, addresses)) {
+                /*
+                 * The block we are looking at now has the same handlers
+                 * as the block that started the currently open catch
+                 * range, and adding it to the currently open range won't
+                 * cause it to be too long.
+                 */
+                currentEndBlock = block;
+                continue;
+            }
+
+            /*
+             * The block we are looking at now has incompatible handlers,
+             * so we need to finish off the last entry and start a new
+             * one. Note: We only emit an entry if it has associated handlers.
+             */
+            if (currentHandlers.size() != 0) {
+                CatchTable.Entry entry =
+                    makeEntry(currentStartBlock, currentEndBlock,
+                            currentHandlers, addresses);
+                resultList.add(entry);
+            }
+
+            currentStartBlock = block;
+            currentEndBlock = block;
+            currentHandlers = handlers;
+        }
+
+        if (currentHandlers.size() != 0) {
+            // Emit an entry for the range that was left hanging.
+            CatchTable.Entry entry =
+                makeEntry(currentStartBlock, currentEndBlock,
+                        currentHandlers, addresses);
+            resultList.add(entry);
+        }
+
+        // Construct the final result.
+
+        int resultSz = resultList.size();
+
+        if (resultSz == 0) {
+            return CatchTable.EMPTY;
+        }
+
+        CatchTable result = new CatchTable(resultSz);
+
+        for (int i = 0; i < resultSz; i++) {
+            result.set(i, resultList.get(i));
+        }
+
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Makes the {@link CatchHandlerList} for the given basic block.
+     *
+     * @param block {@code non-null;} block to get entries for
+     * @param addresses {@code non-null;} address objects for each block
+     * @return {@code non-null;} array of entries
+     */
+    private static CatchHandlerList handlersFor(BasicBlock block,
+            BlockAddresses addresses) {
+        IntList successors = block.getSuccessors();
+        int succSize = successors.size();
+        int primary = block.getPrimarySuccessor();
+        TypeList catches = block.getLastInsn().getCatches();
+        int catchSize = catches.size();
+
+        if (catchSize == 0) {
+            return CatchHandlerList.EMPTY;
+        }
+
+        if (((primary == -1) && (succSize != catchSize))
+                || ((primary != -1) &&
+                        ((succSize != (catchSize + 1))
+                                || (primary != successors.get(catchSize))))) {
+            /*
+             * Blocks that throw are supposed to list their primary
+             * successor -- if any -- last in the successors list, but
+             * that constraint appears to be violated here.
+             */
+            throw new RuntimeException(
+                    "shouldn't happen: weird successors list");
+        }
+
+        /*
+         * Reduce the effective catchSize if we spot a catch-all that
+         * isn't at the end.
+         */
+        for (int i = 0; i < catchSize; i++) {
+            Type type = catches.getType(i);
+            if (type.equals(Type.OBJECT)) {
+                catchSize = i + 1;
+                break;
+            }
+        }
+
+        CatchHandlerList result = new CatchHandlerList(catchSize);
+
+        for (int i = 0; i < catchSize; i++) {
+            CstType oneType = new CstType(catches.getType(i));
+            CodeAddress oneHandler = addresses.getStart(successors.get(i));
+            result.set(i, oneType, oneHandler.getAddress());
+        }
+
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Makes a {@link CatchTable#Entry} for the given block range and
+     * handlers.
+     *
+     * @param start {@code non-null;} the start block for the range (inclusive)
+     * @param end {@code non-null;} the start block for the range (also inclusive)
+     * @param handlers {@code non-null;} the handlers for the range
+     * @param addresses {@code non-null;} address objects for each block
+     */
+    private static CatchTable.Entry makeEntry(BasicBlock start,
+            BasicBlock end, CatchHandlerList handlers,
+            BlockAddresses addresses) {
+        /*
+         * We start at the *last* instruction of the start block, since
+         * that's the instruction that can throw...
+         */
+        CodeAddress startAddress = addresses.getLast(start);
+
+        // ...And we end *after* the last instruction of the end block.
+        CodeAddress endAddress = addresses.getEnd(end);
+
+        return new CatchTable.Entry(startAddress.getAddress(),
+                endAddress.getAddress(), handlers);
+    }
+
+    /**
+     * Gets whether the address range for the given two blocks is valid
+     * for a catch handler. This is true as long as the covered range is
+     * under 65536 code units.
+     *
+     * @param start {@code non-null;} the start block for the range (inclusive)
+     * @param end {@code non-null;} the start block for the range (also inclusive)
+     * @param addresses {@code non-null;} address objects for each block
+     * @return {@code true} if the range is valid as a catch range
+     */
+    private static boolean rangeIsValid(BasicBlock start, BasicBlock end,
+            BlockAddresses addresses) {
+        if (start == null) {
+            throw new NullPointerException("start == null");
+        }
+
+        if (end == null) {
+            throw new NullPointerException("end == null");
+        }
+
+        // See above about selection of instructions.
+        int startAddress = addresses.getLast(start).getAddress();
+        int endAddress = addresses.getEnd(end).getAddress();
+
+        return (endAddress - startAddress) <= MAX_CATCH_RANGE;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/SwitchData.java b/dexgen/src/com/android/dexgen/dex/code/SwitchData.java
new file mode 100644
index 0000000..a7d8465
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/SwitchData.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.SourcePosition;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.IntList;
+
+/**
+ * Pseudo-instruction which holds switch data. The switch data is
+ * a map of values to target addresses, and this class writes the data
+ * in either a "packed" or "sparse" form.
+ */
+public final class SwitchData extends VariableSizeInsn {
+    /**
+     * {@code non-null;} address representing the instruction that uses this
+     * instance
+     */
+    private final CodeAddress user;
+
+    /** {@code non-null;} sorted list of switch cases (keys) */
+    private final IntList cases;
+
+    /**
+     * {@code non-null;} corresponding list of code addresses; the branch
+     * target for each case
+     */
+    private final CodeAddress[] targets;
+
+    /** whether the output table will be packed (vs. sparse) */
+    private final boolean packed;
+
+    /**
+     * Constructs an instance. The output address of this instance is initially
+     * unknown ({@code -1}).
+     *
+     * @param position {@code non-null;} source position
+     * @param user {@code non-null;} address representing the instruction that
+     * uses this instance
+     * @param cases {@code non-null;} sorted list of switch cases (keys)
+     * @param targets {@code non-null;} corresponding list of code addresses; the
+     * branch target for each case
+     */
+    public SwitchData(SourcePosition position, CodeAddress user,
+                      IntList cases, CodeAddress[] targets) {
+        super(position, RegisterSpecList.EMPTY);
+
+        if (user == null) {
+            throw new NullPointerException("user == null");
+        }
+
+        if (cases == null) {
+            throw new NullPointerException("cases == null");
+        }
+
+        if (targets == null) {
+            throw new NullPointerException("targets == null");
+        }
+
+        int sz = cases.size();
+
+        if (sz != targets.length) {
+            throw new IllegalArgumentException("cases / targets mismatch");
+        }
+
+        if (sz > 65535) {
+            throw new IllegalArgumentException("too many cases");
+        }
+
+        this.user = user;
+        this.cases = cases;
+        this.targets = targets;
+        this.packed = shouldPack(cases);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return packed ? (int) packedCodeSize(cases) :
+            (int) sparseCodeSize(cases);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out) {
+        int baseAddress = user.getAddress();
+        int defaultTarget = Dops.PACKED_SWITCH.getFormat().codeSize();
+        int sz = targets.length;
+
+        if (packed) {
+            int firstCase = (sz == 0) ? 0 : cases.get(0);
+            int lastCase = (sz == 0) ? 0 : cases.get(sz - 1);
+            int outSz = lastCase - firstCase + 1;
+
+            out.writeShort(0x100 | DalvOps.NOP);
+            out.writeShort(outSz);
+            out.writeInt(firstCase);
+
+            int caseAt = 0;
+            for (int i = 0; i < outSz; i++) {
+                int outCase = firstCase + i;
+                int oneCase = cases.get(caseAt);
+                int relTarget;
+
+                if (oneCase > outCase) {
+                    relTarget = defaultTarget;
+                } else {
+                    relTarget = targets[caseAt].getAddress() - baseAddress;
+                    caseAt++;
+                }
+
+                out.writeInt(relTarget);
+            }
+        } else {
+            out.writeShort(0x200 | DalvOps.NOP);
+            out.writeShort(sz);
+
+            for (int i = 0; i < sz; i++) {
+                out.writeInt(cases.get(i));
+            }
+
+            for (int i = 0; i < sz; i++) {
+                int relTarget = targets[i].getAddress() - baseAddress;
+                out.writeInt(relTarget);
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withRegisters(RegisterSpecList registers) {
+        return new SwitchData(getPosition(), user, cases, targets);
+    }
+
+    /**
+     * Returns whether or not this instance's data will be output as packed.
+     *
+     * @return {@code true} iff the data is to be packed
+     */
+    public boolean isPacked() {
+        return packed;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String argString() {
+        StringBuffer sb = new StringBuffer(100);
+
+        int sz = targets.length;
+        for (int i = 0; i < sz; i++) {
+            sb.append("\n    ");
+            sb.append(cases.get(i));
+            sb.append(": ");
+            sb.append(targets[i]);
+        }
+
+        return sb.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String listingString0(boolean noteIndices) {
+        int baseAddress = user.getAddress();
+        StringBuffer sb = new StringBuffer(100);
+        int sz = targets.length;
+
+        sb.append(packed ? "packed" : "sparse");
+        sb.append("-switch-data // for switch @ ");
+        sb.append(Hex.u2(baseAddress));
+
+        for (int i = 0; i < sz; i++) {
+            int absTarget = targets[i].getAddress();
+            int relTarget = absTarget - baseAddress;
+            sb.append("\n  ");
+            sb.append(cases.get(i));
+            sb.append(": ");
+            sb.append(Hex.u4(absTarget));
+            sb.append(" // ");
+            sb.append(Hex.s4(relTarget));
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Gets the size of a packed table for the given cases, in 16-bit code
+     * units.
+     *
+     * @param cases {@code non-null;} sorted list of cases
+     * @return {@code >= -1;} the packed table size or {@code -1} if the
+     * cases couldn't possibly be represented as a packed table
+     */
+    private static long packedCodeSize(IntList cases) {
+        int sz = cases.size();
+        long low = cases.get(0);
+        long high = cases.get(sz - 1);
+        long result = ((high - low + 1)) * 2 + 4;
+
+        return (result <= 0x7fffffff) ? result : -1;
+    }
+
+    /**
+     * Gets the size of a sparse table for the given cases, in 16-bit code
+     * units.
+     *
+     * @param cases {@code non-null;} sorted list of cases
+     * @return {@code > 0;} the sparse table size
+     */
+    private static long sparseCodeSize(IntList cases) {
+        int sz = cases.size();
+
+        return (sz * 4L) + 2;
+    }
+
+    /**
+     * Determines whether the given list of cases warrant being packed.
+     *
+     * @param cases {@code non-null;} sorted list of cases
+     * @return {@code true} iff the table encoding the cases
+     * should be packed
+     */
+    private static boolean shouldPack(IntList cases) {
+        int sz = cases.size();
+
+        if (sz < 2) {
+            return true;
+        }
+
+        long packedSize = packedCodeSize(cases);
+        long sparseSize = sparseCodeSize(cases);
+
+        /*
+         * We pick the packed representation if it is possible and
+         * would be as small or smaller than 5/4 of the sparse
+         * representation. That is, we accept some size overhead on
+         * the packed representation, since that format is faster to
+         * execute at runtime.
+         */
+        return (packedSize >= 0) && (packedSize <= ((sparseSize * 5) / 4));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/TargetInsn.java b/dexgen/src/com/android/dexgen/dex/code/TargetInsn.java
new file mode 100644
index 0000000..8e02255
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/TargetInsn.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.SourcePosition;
+
+/**
+ * Instruction which has a single branch target.
+ */
+public final class TargetInsn extends FixedSizeInsn {
+    /** {@code non-null;} the branch target */
+    private CodeAddress target;
+
+    /**
+     * Constructs an instance. The output address of this instance is initially
+     * unknown ({@code -1}), and the target is initially
+     * {@code null}.
+     *
+     * @param opcode the opcode; one of the constants from {@link Dops}
+     * @param position {@code non-null;} source position
+     * @param registers {@code non-null;} register list, including a
+     * result register if appropriate (that is, registers may be either
+     * ins or outs)
+     * @param target {@code non-null;} the branch target
+     */
+    public TargetInsn(Dop opcode, SourcePosition position,
+                      RegisterSpecList registers, CodeAddress target) {
+        super(opcode, position, registers);
+
+        if (target == null) {
+            throw new NullPointerException("target == null");
+        }
+
+        this.target = target;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withOpcode(Dop opcode) {
+        return new TargetInsn(opcode, getPosition(), getRegisters(), target);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withRegisters(RegisterSpecList registers) {
+        return new TargetInsn(getOpcode(), getPosition(), registers, target);
+    }
+
+    /**
+     * Returns an instance that is just like this one, except that its
+     * opcode has the opposite sense (as a test; e.g. a
+     * {@code lt} test becomes a {@code ge}), and its branch
+     * target is replaced by the one given, and all set-once values
+     * associated with the class (such as its address) are reset.
+     *
+     * @param target {@code non-null;} the new branch target
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public TargetInsn withNewTargetAndReversed(CodeAddress target) {
+        Dop opcode = getOpcode().getOppositeTest();
+
+        return new TargetInsn(opcode, getPosition(), getRegisters(), target);
+    }
+
+    /**
+     * Gets the unique branch target of this instruction.
+     *
+     * @return {@code non-null;} the branch target
+     */
+    public CodeAddress getTarget() {
+        return target;
+    }
+
+    /**
+     * Gets the target address of this instruction. This is only valid
+     * to call if the target instruction has been assigned an address,
+     * and it is merely a convenient shorthand for
+     * {@code getTarget().getAddress()}.
+     *
+     * @return {@code >= 0;} the target address
+     */
+    public int getTargetAddress() {
+        return target.getAddress();
+    }
+
+    /**
+     * Gets the branch offset of this instruction. This is only valid to
+     * call if both this and the target instruction each has been assigned
+     * an address, and it is merely a convenient shorthand for
+     * {@code getTargetAddress() - getAddress()}.
+     *
+     * @return the branch offset
+     */
+    public int getTargetOffset() {
+        return target.getAddress() - getAddress();
+    }
+
+    /**
+     * Returns whether the target offset is known.
+     *
+     * @return {@code true} if the target offset is known or
+     * {@code false} if not
+     */
+    public boolean hasTargetOffset() {
+        return hasAddress() && target.hasAddress();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String argString() {
+        if (target == null) {
+            return "????";
+        }
+
+        return target.identifierString();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/VariableSizeInsn.java b/dexgen/src/com/android/dexgen/dex/code/VariableSizeInsn.java
new file mode 100644
index 0000000..baa62a3
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/VariableSizeInsn.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.SourcePosition;
+
+/**
+ * Pseudo-instruction base class for variable-sized instructions.
+ */
+public abstract class VariableSizeInsn extends DalvInsn {
+    /**
+     * Constructs an instance. The output address of this instance is initially
+     * unknown ({@code -1}).
+     *
+     * @param position {@code non-null;} source position
+     * @param registers {@code non-null;} source registers
+     */
+    public VariableSizeInsn(SourcePosition position,
+                            RegisterSpecList registers) {
+        super(Dops.SPECIAL_FORMAT, position, registers);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final DalvInsn withOpcode(Dop opcode) {
+        throw new RuntimeException("unsupported");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final DalvInsn withRegisterOffset(int delta) {
+        return withRegisters(getRegisters().withOffset(delta));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/ZeroSizeInsn.java b/dexgen/src/com/android/dexgen/dex/code/ZeroSizeInsn.java
new file mode 100644
index 0000000..3c8c94a
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/ZeroSizeInsn.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.SourcePosition;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Pseudo-instruction base class for zero-size (no code emitted)
+ * instructions, which are generally used for tracking metainformation
+ * about the code they are adjacent to.
+ */
+public abstract class ZeroSizeInsn extends DalvInsn {
+    /**
+     * Constructs an instance. The output address of this instance is initially
+     * unknown ({@code -1}).
+     *
+     * @param position {@code non-null;} source position
+     */
+    public ZeroSizeInsn(SourcePosition position) {
+        super(Dops.SPECIAL_FORMAT, position, RegisterSpecList.EMPTY);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final int codeSize() {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final void writeTo(AnnotatedOutput out) {
+        // Nothing to do here, for this class.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final DalvInsn withOpcode(Dop opcode) {
+        throw new RuntimeException("unsupported");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DalvInsn withRegisterOffset(int delta) {
+        return withRegisters(getRegisters().withOffset(delta));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form10t.java b/dexgen/src/com/android/dexgen/dex/code/form/Form10t.java
new file mode 100644
index 0000000..4784fc3
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form10t.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.dex.code.TargetInsn;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 10t}. See the instruction format spec
+ * for details.
+ */
+public final class Form10t extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form10t();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form10t() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        return branchString(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        return branchComment(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        if (!((insn instanceof TargetInsn) &&
+              (insn.getRegisters().size() == 0))) {
+            return false;
+        }
+
+        TargetInsn ti = (TargetInsn) insn;
+        return ti.hasTargetOffset() ? branchFits(ti) : true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean branchFits(TargetInsn insn) {
+        int offset = insn.getTargetOffset();
+
+        // Note: A zero offset would fit, but it is prohibited by the spec.
+        return (offset != 0) && signedFitsInByte(offset);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return Form20t.THE_ONE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        int offset = ((TargetInsn) insn).getTargetOffset();
+
+        write(out, opcodeUnit(insn, (offset & 0xff)));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form10x.java b/dexgen/src/com/android/dexgen/dex/code/form/Form10x.java
new file mode 100644
index 0000000..63c861c
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form10x.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.dex.code.SimpleInsn;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 10x}. See the instruction format spec
+ * for details.
+ */
+public final class Form10x extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form10x();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form10x() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        // This format has no arguments.
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        // This format has no comment.
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        return (insn instanceof SimpleInsn) &&
+            (insn.getRegisters().size() == 0);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        write(out, opcodeUnit(insn, 0));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form11n.java b/dexgen/src/com/android/dexgen/dex/code/form/Form11n.java
new file mode 100644
index 0000000..511d7d1
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form11n.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.CstInsn;
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstLiteralBits;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 11n}. See the instruction format spec
+ * for details.
+ */
+public final class Form11n extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form11n();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form11n() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant();
+
+        return regs.get(0).regString() + ", " + literalBitsString(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant();
+        return literalBitsComment(value, 4);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+
+        if (!((insn instanceof CstInsn) &&
+              (regs.size() == 1) &&
+              unsignedFitsInNibble(regs.get(0).getReg()))) {
+            return false;
+        }
+
+        CstInsn ci = (CstInsn) insn;
+        Constant cst = ci.getConstant();
+
+        if (!(cst instanceof CstLiteralBits)) {
+            return false;
+        }
+
+        CstLiteralBits cb = (CstLiteralBits) cst;
+
+        return cb.fitsInInt() && signedFitsInNibble(cb.getIntBits());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return Form21s.THE_ONE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        int value =
+            ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits();
+
+        write(out,
+              opcodeUnit(insn, makeByte(regs.get(0).getReg(), value & 0xf)));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form11x.java b/dexgen/src/com/android/dexgen/dex/code/form/Form11x.java
new file mode 100644
index 0000000..8bf9bba
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form11x.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.dex.code.SimpleInsn;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 11x}. See the instruction format spec
+ * for details.
+ */
+public final class Form11x extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form11x();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form11x() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        return regs.get(0).regString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        // This format has no comment.
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        return (insn instanceof SimpleInsn) &&
+            (regs.size() == 1) &&
+            unsignedFitsInByte(regs.get(0).getReg());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        write(out, opcodeUnit(insn, regs.get(0).getReg()));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form12x.java b/dexgen/src/com/android/dexgen/dex/code/form/Form12x.java
new file mode 100644
index 0000000..d55a66a
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form12x.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.HighRegisterPrefix;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.dex.code.SimpleInsn;
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 12x}. See the instruction format spec
+ * for details.
+ */
+public final class Form12x extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form12x();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form12x() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        int sz = regs.size();
+
+        /*
+         * The (sz - 2) and (sz - 1) below makes this code work for
+         * both the two- and three-register ops. (See "case 3" in
+         * isCompatible(), below.)
+         */
+
+        return regs.get(sz - 2).regString() + ", " +
+            regs.get(sz - 1).regString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        // This format has no comment.
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        if (!(insn instanceof SimpleInsn)) {
+            return false;
+        }
+
+        RegisterSpecList regs = insn.getRegisters();
+        RegisterSpec rs1;
+        RegisterSpec rs2;
+
+        switch (regs.size()) {
+            case 2: {
+                rs1 = regs.get(0);
+                rs2 = regs.get(1);
+                break;
+            }
+            case 3: {
+                /*
+                 * This format is allowed for ops that are effectively
+                 * 3-arg but where the first two args are identical.
+                 */
+                rs1 = regs.get(1);
+                rs2 = regs.get(2);
+                if (rs1.getReg() != regs.get(0).getReg()) {
+                    return false;
+                }
+                break;
+            }
+            default: {
+                return false;
+            }
+        }
+
+        return unsignedFitsInNibble(rs1.getReg()) &&
+            unsignedFitsInNibble(rs2.getReg());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return Form22x.THE_ONE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        int sz = regs.size();
+
+        /*
+         * The (sz - 2) and (sz - 1) below makes this code work for
+         * both the two- and three-register ops. (See "case 3" in
+         * isCompatible(), above.)
+         */
+
+        write(out, opcodeUnit(insn,
+                              makeByte(regs.get(sz - 2).getReg(),
+                                       regs.get(sz - 1).getReg())));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form20t.java b/dexgen/src/com/android/dexgen/dex/code/form/Form20t.java
new file mode 100644
index 0000000..2760606
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form20t.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.dex.code.TargetInsn;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 20t}. See the instruction format spec
+ * for details.
+ */
+public final class Form20t extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form20t();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form20t() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        return branchString(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        return branchComment(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 2;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        if (!((insn instanceof TargetInsn) &&
+              (insn.getRegisters().size() == 0))) {
+            return false;
+        }
+
+        TargetInsn ti = (TargetInsn) insn;
+        return ti.hasTargetOffset() ? branchFits(ti) : true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean branchFits(TargetInsn insn) {
+        int offset = insn.getTargetOffset();
+
+        // Note: A zero offset would fit, but it is prohibited by the spec.
+        return (offset != 0) && signedFitsInShort(offset);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return Form30t.THE_ONE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        int offset = ((TargetInsn) insn).getTargetOffset();
+
+        write(out, opcodeUnit(insn, 0), (short) offset);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form21c.java b/dexgen/src/com/android/dexgen/dex/code/form/Form21c.java
new file mode 100644
index 0000000..33df3d6
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form21c.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.CstInsn;
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstFieldRef;
+import com.android.dexgen.rop.cst.CstString;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 21c}. See the instruction format spec
+ * for details.
+ */
+public final class Form21c extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form21c();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form21c() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        return regs.get(0).regString() + ", " + cstString(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        if (noteIndices) {
+            return cstComment(insn);
+        } else {
+            return "";
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 2;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        if (!(insn instanceof CstInsn)) {
+            return false;
+        }
+
+        RegisterSpecList regs = insn.getRegisters();
+        RegisterSpec reg;
+
+        switch (regs.size()) {
+            case 1: {
+                reg = regs.get(0);
+                break;
+            }
+            case 2: {
+                /*
+                 * This format is allowed for ops that are effectively
+                 * 2-arg but where the two args are identical.
+                 */
+                reg = regs.get(0);
+                if (reg.getReg() != regs.get(1).getReg()) {
+                    return false;
+                }
+                break;
+            }
+            default: {
+                return false;
+            }
+        }
+
+        if (!unsignedFitsInByte(reg.getReg())) {
+            return false;
+        }
+
+        CstInsn ci = (CstInsn) insn;
+        int cpi = ci.getIndex();
+
+        if (! unsignedFitsInShort(cpi)) {
+            return false;
+        }
+
+        Constant cst = ci.getConstant();
+        return (cst instanceof CstType) ||
+            (cst instanceof CstFieldRef) ||
+            (cst instanceof CstString);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return Form31c.THE_ONE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        int cpi = ((CstInsn) insn).getIndex();
+
+        write(out,
+              opcodeUnit(insn, regs.get(0).getReg()),
+              (short) cpi);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form21h.java b/dexgen/src/com/android/dexgen/dex/code/form/Form21h.java
new file mode 100644
index 0000000..ee6ed3e
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form21h.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.CstInsn;
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstLiteralBits;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 21h}. See the instruction format spec
+ * for details.
+ */
+public final class Form21h extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form21h();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form21h() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant();
+
+        return regs.get(0).regString() + ", " + literalBitsString(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        RegisterSpecList regs = insn.getRegisters();
+        CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant();
+
+        return
+            literalBitsComment(value,
+                    (regs.get(0).getCategory() == 1) ? 32 : 64);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 2;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        if (!((insn instanceof CstInsn) &&
+              (regs.size() == 1) &&
+              unsignedFitsInByte(regs.get(0).getReg()))) {
+            return false;
+        }
+
+        CstInsn ci = (CstInsn) insn;
+        Constant cst = ci.getConstant();
+
+        if (!(cst instanceof CstLiteralBits)) {
+            return false;
+        }
+
+        CstLiteralBits cb = (CstLiteralBits) cst;
+
+        // Where the high bits are depends on the category of the target.
+        if (regs.get(0).getCategory() == 1) {
+            int bits = cb.getIntBits();
+            return ((bits & 0xffff) == 0);
+        } else {
+            long bits = cb.getLongBits();
+            return ((bits & 0xffffffffffffL) == 0);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return Form31i.THE_ONE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        CstLiteralBits cb = (CstLiteralBits) ((CstInsn) insn).getConstant();
+        short bits;
+
+        // Where the high bits are depends on the category of the target.
+        if (regs.get(0).getCategory() == 1) {
+            bits = (short) (cb.getIntBits() >>> 16);
+        } else {
+            bits = (short) (cb.getLongBits() >>> 48);
+        }
+
+        write(out, opcodeUnit(insn, regs.get(0).getReg()), bits);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form21s.java b/dexgen/src/com/android/dexgen/dex/code/form/Form21s.java
new file mode 100644
index 0000000..4b853d0
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form21s.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.CstInsn;
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstLiteralBits;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 21s}. See the instruction format spec
+ * for details.
+ */
+public final class Form21s extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form21s();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form21s() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant();
+
+        return regs.get(0).regString() + ", " + literalBitsString(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant();
+        return literalBitsComment(value, 16);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 2;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        if (!((insn instanceof CstInsn) &&
+              (regs.size() == 1) &&
+              unsignedFitsInByte(regs.get(0).getReg()))) {
+            return false;
+        }
+
+        CstInsn ci = (CstInsn) insn;
+        Constant cst = ci.getConstant();
+
+        if (!(cst instanceof CstLiteralBits)) {
+            return false;
+        }
+
+        CstLiteralBits cb = (CstLiteralBits) cst;
+
+        return cb.fitsInInt() && signedFitsInShort(cb.getIntBits());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return Form21h.THE_ONE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        int value =
+            ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits();
+
+        write(out,
+              opcodeUnit(insn, regs.get(0).getReg()),
+              (short) value);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form21t.java b/dexgen/src/com/android/dexgen/dex/code/form/Form21t.java
new file mode 100644
index 0000000..61599f6
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form21t.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.dex.code.TargetInsn;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 21t}. See the instruction format spec
+ * for details.
+ */
+public final class Form21t extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form21t();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form21t() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        return regs.get(0).regString() + ", " + branchString(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        return branchComment(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 2;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+
+        if (!((insn instanceof TargetInsn) &&
+              (regs.size() == 1) &&
+              unsignedFitsInByte(regs.get(0).getReg()))) {
+            return false;
+        }
+
+        TargetInsn ti = (TargetInsn) insn;
+        return ti.hasTargetOffset() ? branchFits(ti) : true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean branchFits(TargetInsn insn) {
+        int offset = insn.getTargetOffset();
+
+        // Note: A zero offset would fit, but it is prohibited by the spec.
+        return (offset != 0) && signedFitsInShort(offset);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return Form31t.THE_ONE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        int offset = ((TargetInsn) insn).getTargetOffset();
+
+        write(out,
+              opcodeUnit(insn, regs.get(0).getReg()),
+              (short) offset);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form22b.java b/dexgen/src/com/android/dexgen/dex/code/form/Form22b.java
new file mode 100644
index 0000000..6c37d57
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form22b.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.CstInsn;
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstLiteralBits;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 22b}. See the instruction format spec
+ * for details.
+ */
+public final class Form22b extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form22b();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form22b() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant();
+
+        return regs.get(0).regString() + ", " + regs.get(1).regString() +
+            ", " + literalBitsString(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant();
+        return literalBitsComment(value, 8);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 2;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        if (!((insn instanceof CstInsn) &&
+              (regs.size() == 2) &&
+              unsignedFitsInByte(regs.get(0).getReg()) &&
+              unsignedFitsInByte(regs.get(1).getReg()))) {
+            return false;
+        }
+
+        CstInsn ci = (CstInsn) insn;
+        Constant cst = ci.getConstant();
+
+        if (!(cst instanceof CstLiteralBits)) {
+            return false;
+        }
+
+        CstLiteralBits cb = (CstLiteralBits) cst;
+
+        return cb.fitsInInt() && signedFitsInByte(cb.getIntBits());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return Form22s.THE_ONE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        int value =
+            ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits();
+
+        write(out,
+              opcodeUnit(insn, regs.get(0).getReg()),
+              codeUnit(regs.get(1).getReg(), value & 0xff));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form22c.java b/dexgen/src/com/android/dexgen/dex/code/form/Form22c.java
new file mode 100644
index 0000000..b089ec4
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form22c.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.CstInsn;
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstFieldRef;
+import com.android.dexgen.rop.cst.CstString;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 22c}. See the instruction format spec
+ * for details.
+ */
+public final class Form22c extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form22c();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form22c() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        return regs.get(0).regString() + ", " + regs.get(1).regString() +
+            ", " + cstString(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        if (noteIndices) {
+            return cstComment(insn);
+        } else {
+            return "";
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 2;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        if (!((insn instanceof CstInsn) &&
+              (regs.size() == 2) &&
+              unsignedFitsInNibble(regs.get(0).getReg()) &&
+              unsignedFitsInNibble(regs.get(1).getReg()))) {
+            return false;
+        }
+
+        CstInsn ci = (CstInsn) insn;
+        int cpi = ci.getIndex();
+
+        if (! unsignedFitsInShort(cpi)) {
+            return false;
+        }
+
+        Constant cst = ci.getConstant();
+        return (cst instanceof CstType) ||
+            (cst instanceof CstFieldRef);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        int cpi = ((CstInsn) insn).getIndex();
+
+        write(out,
+              opcodeUnit(insn,
+                         makeByte(regs.get(0).getReg(), regs.get(1).getReg())),
+              (short) cpi);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form22s.java b/dexgen/src/com/android/dexgen/dex/code/form/Form22s.java
new file mode 100644
index 0000000..0eca280
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form22s.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.CstInsn;
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstLiteralBits;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 22s}. See the instruction format spec
+ * for details.
+ */
+public final class Form22s extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form22s();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form22s() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant();
+
+        return regs.get(0).regString() + ", " + regs.get(1).regString()
+            + ", " + literalBitsString(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant();
+        return literalBitsComment(value, 16);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 2;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        if (!((insn instanceof CstInsn) &&
+              (regs.size() == 2) &&
+              unsignedFitsInNibble(regs.get(0).getReg()) &&
+              unsignedFitsInNibble(regs.get(1).getReg()))) {
+            return false;
+        }
+
+        CstInsn ci = (CstInsn) insn;
+        Constant cst = ci.getConstant();
+
+        if (!(cst instanceof CstLiteralBits)) {
+            return false;
+        }
+
+        CstLiteralBits cb = (CstLiteralBits) cst;
+
+        return cb.fitsInInt() && signedFitsInShort(cb.getIntBits());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        int value =
+            ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits();
+
+        write(out,
+              opcodeUnit(insn,
+                         makeByte(regs.get(0).getReg(), regs.get(1).getReg())),
+              (short) value);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form22t.java b/dexgen/src/com/android/dexgen/dex/code/form/Form22t.java
new file mode 100644
index 0000000..707bb97
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form22t.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.dex.code.TargetInsn;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 22t}. See the instruction format spec
+ * for details.
+ */
+public final class Form22t extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form22t();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form22t() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        return regs.get(0).regString() + ", " + regs.get(1).regString() +
+            ", " + branchString(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        return branchComment(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 2;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+
+        if (!((insn instanceof TargetInsn) &&
+              (regs.size() == 2) &&
+              unsignedFitsInNibble(regs.get(0).getReg()) &&
+              unsignedFitsInNibble(regs.get(1).getReg()))) {
+            return false;
+        }
+
+        TargetInsn ti = (TargetInsn) insn;
+        return ti.hasTargetOffset() ? branchFits(ti) : true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean branchFits(TargetInsn insn) {
+        int offset = insn.getTargetOffset();
+
+        // Note: A zero offset would fit, but it is prohibited by the spec.
+        return (offset != 0) && signedFitsInShort(offset);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        int offset = ((TargetInsn) insn).getTargetOffset();
+
+        write(out,
+              opcodeUnit(insn,
+                         makeByte(regs.get(0).getReg(), regs.get(1).getReg())),
+              (short) offset);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form22x.java b/dexgen/src/com/android/dexgen/dex/code/form/Form22x.java
new file mode 100644
index 0000000..bd6a8df
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form22x.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.dex.code.SimpleInsn;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 22x}. See the instruction format spec
+ * for details.
+ */
+public final class Form22x extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form22x();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form22x() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        return regs.get(0).regString() + ", " + regs.get(1).regString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        // This format has no comment.
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 2;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+
+        return (insn instanceof SimpleInsn) &&
+            (regs.size() == 2) &&
+            unsignedFitsInByte(regs.get(0).getReg()) &&
+            unsignedFitsInShort(regs.get(1).getReg());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return Form23x.THE_ONE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        write(out,
+              opcodeUnit(insn, regs.get(0).getReg()),
+              (short) regs.get(1).getReg());
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form23x.java b/dexgen/src/com/android/dexgen/dex/code/form/Form23x.java
new file mode 100644
index 0000000..eaf1cee
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form23x.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.dex.code.SimpleInsn;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 23x}. See the instruction format spec
+ * for details.
+ */
+public final class Form23x extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form23x();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form23x() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        return regs.get(0).regString() + ", " + regs.get(1).regString() +
+            ", " + regs.get(2).regString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        // This format has no comment.
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 2;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+
+        return (insn instanceof SimpleInsn) &&
+            (regs.size() == 3) &&
+            unsignedFitsInByte(regs.get(0).getReg()) &&
+            unsignedFitsInByte(regs.get(1).getReg()) &&
+            unsignedFitsInByte(regs.get(2).getReg());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return Form32x.THE_ONE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        write(out,
+              opcodeUnit(insn, regs.get(0).getReg()),
+              codeUnit(regs.get(1).getReg(), regs.get(2).getReg()));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form30t.java b/dexgen/src/com/android/dexgen/dex/code/form/Form30t.java
new file mode 100644
index 0000000..0909ec8
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form30t.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.dex.code.TargetInsn;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 30t}. See the instruction format spec
+ * for details.
+ */
+public final class Form30t extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form30t();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form30t() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        return branchString(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        return branchComment(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 3;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        if (!((insn instanceof TargetInsn) &&
+              (insn.getRegisters().size() == 0))) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean branchFits(TargetInsn insn) {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        int offset = ((TargetInsn) insn).getTargetOffset();
+
+        write(out, opcodeUnit(insn, 0),
+                (short) offset,
+                (short) (offset >> 16));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form31c.java b/dexgen/src/com/android/dexgen/dex/code/form/Form31c.java
new file mode 100644
index 0000000..c87a451
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form31c.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.CstInsn;
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstFieldRef;
+import com.android.dexgen.rop.cst.CstString;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 31c}. See the instruction format spec
+ * for details.
+ */
+public final class Form31c extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form31c();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form31c() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        return regs.get(0).regString() + ", " + cstString(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        if (noteIndices) {
+            return cstComment(insn);
+        } else {
+            return "";
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 3;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        if (!(insn instanceof CstInsn)) {
+            return false;
+        }
+
+        RegisterSpecList regs = insn.getRegisters();
+        RegisterSpec reg;
+
+        switch (regs.size()) {
+            case 1: {
+                reg = regs.get(0);
+                break;
+            }
+            case 2: {
+                /*
+                 * This format is allowed for ops that are effectively
+                 * 2-arg but where the two args are identical.
+                 */
+                reg = regs.get(0);
+                if (reg.getReg() != regs.get(1).getReg()) {
+                    return false;
+                }
+                break;
+            }
+            default: {
+                return false;
+            }
+        }
+
+        if (!unsignedFitsInByte(reg.getReg())) {
+            return false;
+        }
+
+        CstInsn ci = (CstInsn) insn;
+        Constant cst = ci.getConstant();
+
+        return ((cst instanceof CstType) ||
+                (cst instanceof CstFieldRef) ||
+                (cst instanceof CstString));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        int cpi = ((CstInsn) insn).getIndex();
+
+        write(out,
+                opcodeUnit(insn, regs.get(0).getReg()),
+                (short) cpi,
+                (short) (cpi >> 16));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form31i.java b/dexgen/src/com/android/dexgen/dex/code/form/Form31i.java
new file mode 100644
index 0000000..e74ea86
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form31i.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.CstInsn;
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstLiteralBits;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 31i}. See the instruction format spec
+ * for details.
+ */
+public final class Form31i extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form31i();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form31i() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant();
+
+        return regs.get(0).regString() + ", " + literalBitsString(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant();
+        return literalBitsComment(value, 32);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 3;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        if (!((insn instanceof CstInsn) &&
+              (regs.size() == 1) &&
+              unsignedFitsInByte(regs.get(0).getReg()))) {
+            return false;
+        }
+
+        CstInsn ci = (CstInsn) insn;
+        Constant cst = ci.getConstant();
+
+        if (!(cst instanceof CstLiteralBits)) {
+            return false;
+        }
+
+        return ((CstLiteralBits) cst).fitsInInt();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return Form51l.THE_ONE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        int value =
+            ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits();
+
+        write(out,
+              opcodeUnit(insn, regs.get(0).getReg()),
+              (short) value,
+              (short) (value >> 16));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form31t.java b/dexgen/src/com/android/dexgen/dex/code/form/Form31t.java
new file mode 100644
index 0000000..212f93b
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form31t.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.dex.code.TargetInsn;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 31t}. See the instruction format spec
+ * for details.
+ */
+public final class Form31t extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form31t();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form31t() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        return regs.get(0).regString() + ", " + branchString(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        return branchComment(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 3;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+
+        if (!((insn instanceof TargetInsn) &&
+              (regs.size() == 1) &&
+              unsignedFitsInByte(regs.get(0).getReg()))) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean branchFits(TargetInsn insn) {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        int offset = ((TargetInsn) insn).getTargetOffset();
+
+        write(out, opcodeUnit(insn, regs.get(0).getReg()),
+                (short) offset,
+                (short) (offset >> 16));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form32x.java b/dexgen/src/com/android/dexgen/dex/code/form/Form32x.java
new file mode 100644
index 0000000..097fb65
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form32x.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.dex.code.SimpleInsn;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 32x}. See the instruction format spec
+ * for details.
+ */
+public final class Form32x extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form32x();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form32x() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        return regs.get(0).regString() + ", " + regs.get(1).regString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        // This format has no comment.
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 3;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        return (insn instanceof SimpleInsn) &&
+            (regs.size() == 2) &&
+            unsignedFitsInShort(regs.get(0).getReg()) &&
+            unsignedFitsInShort(regs.get(1).getReg());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+
+        write(out,
+              opcodeUnit(insn, 0),
+              (short) regs.get(0).getReg(),
+              (short) regs.get(1).getReg());
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form35c.java b/dexgen/src/com/android/dexgen/dex/code/form/Form35c.java
new file mode 100644
index 0000000..147aac1
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form35c.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.CstInsn;
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstMethodRef;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 35c}. See the instruction format spec
+ * for details.
+ */
+public final class Form35c extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form35c();
+
+    /** Maximal number of operands */
+    private static final int MAX_NUM_OPS = 5;
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form35c() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = explicitize(insn.getRegisters());
+        return regListString(regs) + ", " + cstString(insn);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        if (noteIndices) {
+            return cstComment(insn);
+        } else {
+            return "";
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 3;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        if (!(insn instanceof CstInsn)) {
+            return false;
+        }
+
+        CstInsn ci = (CstInsn) insn;
+        int cpi = ci.getIndex();
+
+        if (! unsignedFitsInShort(cpi)) {
+            return false;
+        }
+
+        Constant cst = ci.getConstant();
+        if (!((cst instanceof CstMethodRef) ||
+              (cst instanceof CstType))) {
+            return false;
+        }
+
+        RegisterSpecList regs = ci.getRegisters();
+        return (wordCount(regs) >= 0);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return Form3rc.THE_ONE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        int cpi = ((CstInsn) insn).getIndex();
+        RegisterSpecList regs = explicitize(insn.getRegisters());
+        int sz = regs.size();
+        int r0 = (sz > 0) ? regs.get(0).getReg() : 0;
+        int r1 = (sz > 1) ? regs.get(1).getReg() : 0;
+        int r2 = (sz > 2) ? regs.get(2).getReg() : 0;
+        int r3 = (sz > 3) ? regs.get(3).getReg() : 0;
+        int r4 = (sz > 4) ? regs.get(4).getReg() : 0;
+
+        write(out,
+              opcodeUnit(insn,
+                         makeByte(r4, sz)), // encode the fifth operand here
+              (short) cpi,
+              codeUnit(r0, r1, r2, r3));
+    }
+
+    /**
+     * Gets the number of words required for the given register list, where
+     * category-2 values count as two words. Return {@code -1} if the
+     * list requires more than five words or contains registers that need
+     * more than a nibble to identify them.
+     *
+     * @param regs {@code non-null;} the register list in question
+     * @return {@code >= -1;} the number of words required, or {@code -1}
+     * if the list couldn't possibly fit in this format
+     */
+    private static int wordCount(RegisterSpecList regs) {
+        int sz = regs.size();
+
+        if (sz > MAX_NUM_OPS) {
+            // It can't possibly fit.
+            return -1;
+        }
+
+        int result = 0;
+
+        for (int i = 0; i < sz; i++) {
+            RegisterSpec one = regs.get(i);
+            result += one.getCategory();
+            /*
+             * The check below adds (category - 1) to the register, to
+             * account for the fact that the second half of a
+             * category-2 register has to be represented explicitly in
+             * the result.
+             */
+            if (!unsignedFitsInNibble(one.getReg() + one.getCategory() - 1)) {
+                return -1;
+            }
+        }
+
+        return (result <= MAX_NUM_OPS) ? result : -1;
+    }
+
+    /**
+     * Returns a register list which is equivalent to the given one,
+     * except that it splits category-2 registers into two explicit
+     * entries. This returns the original list if no modification is
+     * required
+     *
+     * @param orig {@code non-null;} the original list
+     * @return {@code non-null;} the list with the described transformation
+     */
+    private static RegisterSpecList explicitize(RegisterSpecList orig) {
+        int wordCount = wordCount(orig);
+        int sz = orig.size();
+
+        if (wordCount == sz) {
+            return orig;
+        }
+
+        RegisterSpecList result = new RegisterSpecList(wordCount);
+        int wordAt = 0;
+
+        for (int i = 0; i < sz; i++) {
+            RegisterSpec one = orig.get(i);
+            result.set(wordAt, one);
+            if (one.getCategory() == 2) {
+                result.set(wordAt + 1,
+                           RegisterSpec.make(one.getReg() + 1, Type.VOID));
+                wordAt += 2;
+            } else {
+                wordAt++;
+            }
+        }
+
+        result.setImmutable();
+        return result;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form3rc.java b/dexgen/src/com/android/dexgen/dex/code/form/Form3rc.java
new file mode 100644
index 0000000..a061c6f
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form3rc.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.CstInsn;
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstMethodRef;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 3rc}. See the instruction format spec
+ * for details.
+ */
+public final class Form3rc extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form3rc();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form3rc() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        int size = regs.size();
+        StringBuilder sb = new StringBuilder(30);
+
+        sb.append("{");
+
+        switch (size) {
+            case 0: {
+                // Nothing to do.
+                break;
+            }
+            case 1: {
+                sb.append(regs.get(0).regString());
+                break;
+            }
+            default: {
+                RegisterSpec lastReg = regs.get(size - 1);
+                if (lastReg.getCategory() == 2) {
+                    /*
+                     * Add one to properly represent a list-final
+                     * category-2 register.
+                     */
+                    lastReg = lastReg.withOffset(1);
+                }
+
+                sb.append(regs.get(0).regString());
+                sb.append("..");
+                sb.append(lastReg.regString());
+            }
+        }
+
+        sb.append("}, ");
+        sb.append(cstString(insn));
+
+        return sb.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        if (noteIndices) {
+            return cstComment(insn);
+        } else {
+            return "";
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 3;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        if (!(insn instanceof CstInsn)) {
+            return false;
+        }
+
+        CstInsn ci = (CstInsn) insn;
+        int cpi = ci.getIndex();
+
+        if (! unsignedFitsInShort(cpi)) {
+            return false;
+        }
+
+        Constant cst = ci.getConstant();
+        if (!((cst instanceof CstMethodRef) ||
+              (cst instanceof CstType))) {
+            return false;
+        }
+
+        RegisterSpecList regs = ci.getRegisters();
+        int sz = regs.size();
+
+        if (sz == 0) {
+            return true;
+        }
+
+        int first = regs.get(0).getReg();
+        int next = first;
+
+        if (!unsignedFitsInShort(first)) {
+            return false;
+        }
+
+        for (int i = 0; i < sz; i++) {
+            RegisterSpec one = regs.get(i);
+            if (one.getReg() != next) {
+                return false;
+            }
+            next += one.getCategory();
+        }
+
+        return unsignedFitsInByte(next - first);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        int sz = regs.size();
+        int cpi = ((CstInsn) insn).getIndex();
+        int firstReg;
+        int count;
+
+        if (sz == 0) {
+            firstReg = 0;
+            count = 0;
+        } else {
+            int lastReg = regs.get(sz - 1).getNextReg();
+            firstReg = regs.get(0).getReg();
+            count = lastReg - firstReg;
+        }
+
+        write(out,
+              opcodeUnit(insn, count),
+              (short) cpi,
+              (short) firstReg);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form51l.java b/dexgen/src/com/android/dexgen/dex/code/form/Form51l.java
new file mode 100644
index 0000000..537eaa9
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/Form51l.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.CstInsn;
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstLiteral64;
+import com.android.dexgen.rop.cst.CstLiteralBits;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format {@code 51l}. See the instruction format spec
+ * for details.
+ */
+public final class Form51l extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new Form51l();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private Form51l() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant();
+
+        return regs.get(0).regString() + ", " + literalBitsString(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant();
+        return literalBitsComment(value, 64);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        return 5;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        if (!((insn instanceof CstInsn) &&
+              (regs.size() == 1) &&
+              unsignedFitsInByte(regs.get(0).getReg()))) {
+            return false;
+        }
+
+        CstInsn ci = (CstInsn) insn;
+        Constant cst = ci.getConstant();
+
+        return (cst instanceof CstLiteral64);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        RegisterSpecList regs = insn.getRegisters();
+        long value =
+            ((CstLiteral64) ((CstInsn) insn).getConstant()).getLongBits();
+
+        write(out,
+              opcodeUnit(insn, regs.get(0).getReg()),
+              (short) value,
+              (short) (value >> 16),
+              (short) (value >> 32),
+              (short) (value >> 48));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/code/form/SpecialFormat.java b/dexgen/src/com/android/dexgen/dex/code/form/SpecialFormat.java
new file mode 100644
index 0000000..c75f18f
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/code/form/SpecialFormat.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.code.form;
+
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.DalvOps;
+import com.android.dexgen.dex.code.InsnFormat;
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Instruction format for nonstandard format instructions, which aren't
+ * generally real instructions but do end up appearing in instruction
+ * lists. Most of the overridden methods on this class end up throwing
+ * exceptions, as code should know (implicitly or explicitly) to avoid
+ * using this class. The one exception is {@link #isCompatible}, which
+ * always returns {@code true}.
+ */
+public final class SpecialFormat extends InsnFormat {
+    /** {@code non-null;} unique instance of this class */
+    public static final InsnFormat THE_ONE = new SpecialFormat();
+
+    /**
+     * Constructs an instance. This class is not publicly
+     * instantiable. Use {@link #THE_ONE}.
+     */
+    private SpecialFormat() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnArgString(DalvInsn insn) {
+        throw new RuntimeException("unsupported");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String insnCommentString(DalvInsn insn, boolean noteIndices) {
+        throw new RuntimeException("unsupported");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int codeSize() {
+        throw new RuntimeException("unsupported");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCompatible(DalvInsn insn) {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InsnFormat nextUp() {
+        throw new RuntimeException("unsupported");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(AnnotatedOutput out, DalvInsn insn) {
+        throw new RuntimeException("unsupported");
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/AnnotationItem.java b/dexgen/src/com/android/dexgen/dex/file/AnnotationItem.java
new file mode 100644
index 0000000..a078bc0
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/AnnotationItem.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.annotation.Annotation;
+import com.android.dexgen.rop.annotation.AnnotationVisibility;
+import com.android.dexgen.rop.annotation.NameValuePair;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstAnnotation;
+import com.android.dexgen.rop.cst.CstArray;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.ByteArrayAnnotatedOutput;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Single annotation, which consists of a type and a set of name-value
+ * element pairs.
+ */
+public final class AnnotationItem extends OffsettedItem {
+    /** annotation visibility constant: visible at build time only */
+    private static final int VISIBILITY_BUILD = 0;
+
+    /** annotation visibility constant: visible at runtime */
+    private static final int VISIBILITY_RUNTIME = 1;
+
+    /** annotation visibility constant: visible at runtime only to system */
+    private static final int VISIBILITY_SYSTEM = 2;
+
+    /** the required alignment for instances of this class */
+    private static final int ALIGNMENT = 1;
+
+    /** {@code non-null;} unique instance of {@link #TypeIdSorter} */
+    private static final TypeIdSorter TYPE_ID_SORTER = new TypeIdSorter();
+
+    /** {@code non-null;} the annotation to represent */
+    private final Annotation annotation;
+
+    /**
+     * {@code null-ok;} type reference for the annotation type; set during
+     * {@link #addContents}
+     */
+    private TypeIdItem type;
+
+    /**
+     * {@code null-ok;} encoded form, ready for writing to a file; set during
+     * {@link #place0}
+     */
+    private byte[] encodedForm;
+
+    /**
+     * Comparator that sorts (outer) instances by type id index.
+     */
+    private static class TypeIdSorter implements Comparator<AnnotationItem> {
+        /** {@inheritDoc} */
+        public int compare(AnnotationItem item1, AnnotationItem item2) {
+            int index1 = item1.type.getIndex();
+            int index2 = item2.type.getIndex();
+
+            if (index1 < index2) {
+                return -1;
+            } else if (index1 > index2) {
+                return 1;
+            }
+
+            return 0;
+        }
+    }
+
+    /**
+     * Sorts an array of instances, in place, by type id index,
+     * ignoring all other aspects of the elements. This is only valid
+     * to use after type id indices are known.
+     *
+     * @param array {@code non-null;} array to sort
+     */
+    public static void sortByTypeIdIndex(AnnotationItem[] array) {
+        Arrays.sort(array, TYPE_ID_SORTER);
+    }
+
+    /**
+     * Constructs an instance.
+     *
+     * @param annotation {@code non-null;} annotation to represent
+     */
+    public AnnotationItem(Annotation annotation) {
+        /*
+         * The write size isn't known up-front because (the variable-lengthed)
+         * leb128 type is used to represent some things.
+         */
+        super(ALIGNMENT, -1);
+
+        if (annotation == null) {
+            throw new NullPointerException("annotation == null");
+        }
+
+        this.annotation = annotation;
+        this.type = null;
+        this.encodedForm = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_ANNOTATION_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return annotation.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int compareTo0(OffsettedItem other) {
+        AnnotationItem otherAnnotation = (AnnotationItem) other;
+
+        return annotation.compareTo(otherAnnotation.annotation);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toHuman() {
+        return annotation.toHuman();
+    }
+
+    /** {@inheritDoc} */
+    public void addContents(DexFile file) {
+        type = file.getTypeIds().intern(annotation.getType());
+        ValueEncoder.addContents(file, annotation);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void place0(Section addedTo, int offset) {
+        // Encode the data and note the size.
+
+        ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
+        ValueEncoder encoder = new ValueEncoder(addedTo.getFile(), out);
+
+        encoder.writeAnnotation(annotation, false);
+        encodedForm = out.toByteArray();
+
+        // Add one for the visibility byte in front of the encoded annotation.
+        setWriteSize(encodedForm.length + 1);
+    }
+
+    /**
+     * Write a (listing file) annotation for this instance to the given
+     * output, that consumes no bytes of output. This is for annotating
+     * a reference to this instance at the point of the reference.
+     *
+     * @param out {@code non-null;} where to output to
+     * @param prefix {@code non-null;} prefix for each line of output
+     */
+    public void annotateTo(AnnotatedOutput out, String prefix) {
+        out.annotate(0, prefix + "visibility: " +
+                annotation.getVisibility().toHuman());
+        out.annotate(0, prefix + "type: " + annotation.getType().toHuman());
+
+        for (NameValuePair pair : annotation.getNameValuePairs()) {
+            CstUtf8 name = pair.getName();
+            Constant value = pair.getValue();
+
+            out.annotate(0, prefix + name.toHuman() + ": " +
+                    ValueEncoder.constantToHuman(value));
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void writeTo0(DexFile file, AnnotatedOutput out) {
+        boolean annotates = out.annotates();
+        AnnotationVisibility visibility = annotation.getVisibility();
+
+        if (annotates) {
+            out.annotate(0, offsetString() + " annotation");
+            out.annotate(1, "  visibility: VISBILITY_" + visibility);
+        }
+
+        switch (visibility) {
+            case BUILD:   out.writeByte(VISIBILITY_BUILD); break;
+            case RUNTIME: out.writeByte(VISIBILITY_RUNTIME); break;
+            case SYSTEM:  out.writeByte(VISIBILITY_SYSTEM); break;
+            default: {
+                // EMBEDDED shouldn't appear at the top level.
+                throw new RuntimeException("shouldn't happen");
+            }
+        }
+
+        if (annotates) {
+            /*
+             * The output is to be annotated, so redo the work previously
+             * done by place0(), except this time annotations will actually
+             * get emitted.
+             */
+            ValueEncoder encoder = new ValueEncoder(file, out);
+            encoder.writeAnnotation(annotation, true);
+        } else {
+            out.write(encodedForm);
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/AnnotationSetItem.java b/dexgen/src/com/android/dexgen/dex/file/AnnotationSetItem.java
new file mode 100644
index 0000000..46ea2f0
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/AnnotationSetItem.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.annotation.Annotation;
+import com.android.dexgen.rop.annotation.Annotations;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Set of annotations, where no annotation type appears more than once.
+ */
+public final class AnnotationSetItem extends OffsettedItem {
+    /** the required alignment for instances of this class */
+    private static final int ALIGNMENT = 4;
+
+    /** the size of an entry int the set: one {@code uint} */
+    private static final int ENTRY_WRITE_SIZE = 4;
+
+    /** {@code non-null;} the set of annotations */
+    private final Annotations annotations;
+
+    /**
+     * {@code non-null;} set of annotations as individual items in an array.
+     * <b>Note:</b> The contents have to get sorted by type id before
+     * writing.
+     */
+    private final AnnotationItem[] items;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param annotations {@code non-null;} set of annotations
+     */
+    public AnnotationSetItem(Annotations annotations) {
+        super(ALIGNMENT, writeSize(annotations));
+
+        this.annotations = annotations;
+        this.items = new AnnotationItem[annotations.size()];
+
+        int at = 0;
+        for (Annotation a : annotations.getAnnotations()) {
+            items[at] = new AnnotationItem(a);
+            at++;
+        }
+    }
+
+    /**
+     * Gets the write size for the given set.
+     *
+     * @param annotations {@code non-null;} the set
+     * @return {@code > 0;} the write size
+     */
+    private static int writeSize(Annotations annotations) {
+        // This includes an int size at the start of the list.
+
+        try {
+            return (annotations.size() * ENTRY_WRITE_SIZE) + 4;
+        } catch (NullPointerException ex) {
+            // Elucidate the exception.
+            throw new NullPointerException("list == null");
+        }
+    }
+
+    /**
+     * Gets the underlying annotations of this instance
+     *
+     * @return {@code non-null;} the annotations
+     */
+    public Annotations getAnnotations() {
+        return annotations;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return annotations.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int compareTo0(OffsettedItem other) {
+        AnnotationSetItem otherSet = (AnnotationSetItem) other;
+
+        return annotations.compareTo(otherSet.annotations);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_ANNOTATION_SET_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toHuman() {
+        return annotations.toString();
+    }
+
+    /** {@inheritDoc} */
+    public void addContents(DexFile file) {
+        MixedItemSection byteData = file.getByteData();
+        int size = items.length;
+
+        for (int i = 0; i < size; i++) {
+            items[i] = byteData.intern(items[i]);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void place0(Section addedTo, int offset) {
+        // Sort the array to be in type id index order.
+        AnnotationItem.sortByTypeIdIndex(items);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void writeTo0(DexFile file, AnnotatedOutput out) {
+        boolean annotates = out.annotates();
+        int size = items.length;
+
+        if (annotates) {
+            out.annotate(0, offsetString() + " annotation set");
+            out.annotate(4, "  size: " + Hex.u4(size));
+        }
+
+        out.writeInt(size);
+
+        for (int i = 0; i < size; i++) {
+            AnnotationItem item = items[i];
+            int offset = item.getAbsoluteOffset();
+
+            if (annotates) {
+                out.annotate(4, "  entries[" + Integer.toHexString(i) + "]: " +
+                        Hex.u4(offset));
+                items[i].annotateTo(out, "    ");
+            }
+
+            out.writeInt(offset);
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/AnnotationSetRefItem.java b/dexgen/src/com/android/dexgen/dex/file/AnnotationSetRefItem.java
new file mode 100644
index 0000000..b876ce0
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/AnnotationSetRefItem.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Indirect reference to an {@link AnnotationSetItem}.
+ */
+public final class AnnotationSetRefItem extends OffsettedItem {
+    /** the required alignment for instances of this class */
+    private static final int ALIGNMENT = 4;
+
+    /** write size of this class, in bytes */
+    private static final int WRITE_SIZE = 4;
+
+    /** {@code non-null;} the annotation set to refer to */
+    private AnnotationSetItem annotations;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param annotations {@code non-null;} the annotation set to refer to
+     */
+    public AnnotationSetRefItem(AnnotationSetItem annotations) {
+        super(ALIGNMENT, WRITE_SIZE);
+
+        if (annotations == null) {
+            throw new NullPointerException("annotations == null");
+        }
+
+        this.annotations = annotations;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_ANNOTATION_SET_REF_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    public void addContents(DexFile file) {
+        MixedItemSection wordData = file.getWordData();
+
+        annotations = wordData.intern(annotations);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toHuman() {
+        return annotations.toHuman();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void writeTo0(DexFile file, AnnotatedOutput out) {
+        int annotationsOff = annotations.getAbsoluteOffset();
+
+        if (out.annotates()) {
+            out.annotate(4, "  annotations_off: " + Hex.u4(annotationsOff));
+        }
+
+        out.writeInt(annotationsOff);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/AnnotationUtils.java b/dexgen/src/com/android/dexgen/dex/file/AnnotationUtils.java
new file mode 100644
index 0000000..111ba8a
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/AnnotationUtils.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.annotation.Annotation;
+import com.android.dexgen.rop.annotation.NameValuePair;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstAnnotation;
+import com.android.dexgen.rop.cst.CstArray;
+import com.android.dexgen.rop.cst.CstInteger;
+import com.android.dexgen.rop.cst.CstKnownNull;
+import com.android.dexgen.rop.cst.CstMethodRef;
+import com.android.dexgen.rop.cst.CstString;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeList;
+
+import java.util.ArrayList;
+
+import static com.android.dexgen.rop.annotation.AnnotationVisibility.*;
+
+/**
+ * Utility class for dealing with annotations.
+ */
+public final class AnnotationUtils {
+    /** {@code non-null;} type for {@code AnnotationDefault} annotations */
+    private static final CstType ANNOTATION_DEFAULT_TYPE =
+        CstType.intern(Type.intern("Ldalvik/annotation/AnnotationDefault;"));
+
+    /** {@code non-null;} type for {@code EnclosingClass} annotations */
+    private static final CstType ENCLOSING_CLASS_TYPE =
+        CstType.intern(Type.intern("Ldalvik/annotation/EnclosingClass;"));
+
+    /** {@code non-null;} type for {@code EnclosingMethod} annotations */
+    private static final CstType ENCLOSING_METHOD_TYPE =
+        CstType.intern(Type.intern("Ldalvik/annotation/EnclosingMethod;"));
+
+    /** {@code non-null;} type for {@code InnerClass} annotations */
+    private static final CstType INNER_CLASS_TYPE =
+        CstType.intern(Type.intern("Ldalvik/annotation/InnerClass;"));
+
+    /** {@code non-null;} type for {@code MemberClasses} annotations */
+    private static final CstType MEMBER_CLASSES_TYPE =
+        CstType.intern(Type.intern("Ldalvik/annotation/MemberClasses;"));
+
+    /** {@code non-null;} type for {@code Signature} annotations */
+    private static final CstType SIGNATURE_TYPE =
+        CstType.intern(Type.intern("Ldalvik/annotation/Signature;"));
+
+    /** {@code non-null;} type for {@code Throws} annotations */
+    private static final CstType THROWS_TYPE =
+        CstType.intern(Type.intern("Ldalvik/annotation/Throws;"));
+
+    /** {@code non-null;} the UTF-8 constant {@code "accessFlags"} */
+    private static final CstUtf8 ACCESS_FLAGS_UTF = new CstUtf8("accessFlags");
+
+    /** {@code non-null;} the UTF-8 constant {@code "name"} */
+    private static final CstUtf8 NAME_UTF = new CstUtf8("name");
+
+    /** {@code non-null;} the UTF-8 constant {@code "value"} */
+    private static final CstUtf8 VALUE_UTF = new CstUtf8("value");
+
+    /**
+     * This class is uninstantiable.
+     */
+    private AnnotationUtils() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Constructs a standard {@code AnnotationDefault} annotation.
+     *
+     * @param defaults {@code non-null;} the defaults, itself as an annotation
+     * @return {@code non-null;} the constructed annotation
+     */
+    public static Annotation makeAnnotationDefault(Annotation defaults) {
+        Annotation result = new Annotation(ANNOTATION_DEFAULT_TYPE, SYSTEM);
+
+        result.put(new NameValuePair(VALUE_UTF, new CstAnnotation(defaults)));
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Constructs a standard {@code EnclosingClass} annotation.
+     *
+     * @param clazz {@code non-null;} the enclosing class
+     * @return {@code non-null;} the annotation
+     */
+    public static Annotation makeEnclosingClass(CstType clazz) {
+        Annotation result = new Annotation(ENCLOSING_CLASS_TYPE, SYSTEM);
+
+        result.put(new NameValuePair(VALUE_UTF, clazz));
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Constructs a standard {@code EnclosingMethod} annotation.
+     *
+     * @param method {@code non-null;} the enclosing method
+     * @return {@code non-null;} the annotation
+     */
+    public static Annotation makeEnclosingMethod(CstMethodRef method) {
+        Annotation result = new Annotation(ENCLOSING_METHOD_TYPE, SYSTEM);
+
+        result.put(new NameValuePair(VALUE_UTF, method));
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Constructs a standard {@code InnerClass} annotation.
+     *
+     * @param name {@code null-ok;} the original name of the class, or
+     * {@code null} to represent an anonymous class
+     * @param accessFlags the original access flags
+     * @return {@code non-null;} the annotation
+     */
+    public static Annotation makeInnerClass(CstUtf8 name, int accessFlags) {
+        Annotation result = new Annotation(INNER_CLASS_TYPE, SYSTEM);
+        Constant nameCst =
+            (name != null) ? new CstString(name) : CstKnownNull.THE_ONE;
+
+        result.put(new NameValuePair(NAME_UTF, nameCst));
+        result.put(new NameValuePair(ACCESS_FLAGS_UTF,
+                        CstInteger.make(accessFlags)));
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Constructs a standard {@code MemberClasses} annotation.
+     *
+     * @param types {@code non-null;} the list of (the types of) the member classes
+     * @return {@code non-null;} the annotation
+     */
+    public static Annotation makeMemberClasses(TypeList types) {
+        CstArray array = makeCstArray(types);
+        Annotation result = new Annotation(MEMBER_CLASSES_TYPE, SYSTEM);
+        result.put(new NameValuePair(VALUE_UTF, array));
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Constructs a standard {@code Signature} annotation.
+     *
+     * @param signature {@code non-null;} the signature string
+     * @return {@code non-null;} the annotation
+     */
+    public static Annotation makeSignature(CstUtf8 signature) {
+        Annotation result = new Annotation(SIGNATURE_TYPE, SYSTEM);
+
+        /*
+         * Split the string into pieces that are likely to be common
+         * across many signatures and the rest of the file.
+         */
+
+        String raw = signature.getString();
+        int rawLength = raw.length();
+        ArrayList<String> pieces = new ArrayList<String>(20);
+
+        for (int at = 0; at < rawLength; /*at*/) {
+            char c = raw.charAt(at);
+            int endAt = at + 1;
+            if (c == 'L') {
+                // Scan to ';' or '<'. Consume ';' but not '<'.
+                while (endAt < rawLength) {
+                    c = raw.charAt(endAt);
+                    if (c == ';') {
+                        endAt++;
+                        break;
+                    } else if (c == '<') {
+                        break;
+                    }
+                    endAt++;
+                }
+            } else {
+                // Scan to 'L' without consuming it.
+                while (endAt < rawLength) {
+                    c = raw.charAt(endAt);
+                    if (c == 'L') {
+                        break;
+                    }
+                    endAt++;
+                }
+            }
+
+            pieces.add(raw.substring(at, endAt));
+            at = endAt;
+        }
+
+        int size = pieces.size();
+        CstArray.List list = new CstArray.List(size);
+
+        for (int i = 0; i < size; i++) {
+            list.set(i, new CstString(pieces.get(i)));
+        }
+
+        list.setImmutable();
+
+        result.put(new NameValuePair(VALUE_UTF, new CstArray(list)));
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Constructs a standard {@code Throws} annotation.
+     *
+     * @param types {@code non-null;} the list of thrown types
+     * @return {@code non-null;} the annotation
+     */
+    public static Annotation makeThrows(TypeList types) {
+        CstArray array = makeCstArray(types);
+        Annotation result = new Annotation(THROWS_TYPE, SYSTEM);
+        result.put(new NameValuePair(VALUE_UTF, array));
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Converts a {@link TypeList} to a {@link CstArray}.
+     *
+     * @param types {@code non-null;} the type list
+     * @return {@code non-null;} the corresponding array constant
+     */
+    private static CstArray makeCstArray(TypeList types) {
+        int size = types.size();
+        CstArray.List list = new CstArray.List(size);
+
+        for (int i = 0; i < size; i++) {
+            list.set(i, CstType.intern(types.getType(i)));
+        }
+
+        list.setImmutable();
+        return new CstArray(list);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/AnnotationsDirectoryItem.java b/dexgen/src/com/android/dexgen/dex/file/AnnotationsDirectoryItem.java
new file mode 100644
index 0000000..860c16d
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/AnnotationsDirectoryItem.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.annotation.Annotations;
+import com.android.dexgen.rop.annotation.AnnotationsList;
+import com.android.dexgen.rop.cst.CstFieldRef;
+import com.android.dexgen.rop.cst.CstMethodRef;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Per-class directory of annotations.
+ */
+public final class AnnotationsDirectoryItem extends OffsettedItem {
+    /** the required alignment for instances of this class */
+    private static final int ALIGNMENT = 4;
+
+    /** write size of this class's header, in bytes */
+    private static final int HEADER_SIZE = 16;
+
+    /** write size of a list element, in bytes */
+    private static final int ELEMENT_SIZE = 8;
+
+    /** {@code null-ok;} the class-level annotations, if any */
+    private AnnotationSetItem classAnnotations;
+
+    /** {@code null-ok;} the annotated fields, if any */
+    private ArrayList<FieldAnnotationStruct> fieldAnnotations;
+
+    /** {@code null-ok;} the annotated methods, if any */
+    private ArrayList<MethodAnnotationStruct> methodAnnotations;
+
+    /** {@code null-ok;} the annotated parameters, if any */
+    private ArrayList<ParameterAnnotationStruct> parameterAnnotations;
+
+    /**
+     * Constructs an empty instance.
+     */
+    public AnnotationsDirectoryItem() {
+        super(ALIGNMENT, -1);
+
+        classAnnotations = null;
+        fieldAnnotations = null;
+        methodAnnotations = null;
+        parameterAnnotations = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_ANNOTATIONS_DIRECTORY_ITEM;
+    }
+
+    /**
+     * Returns whether this item is empty (has no contents).
+     *
+     * @return {@code true} if this item is empty, or {@code false}
+     * if not
+     */
+    public boolean isEmpty() {
+        return (classAnnotations == null) &&
+            (fieldAnnotations == null) &&
+            (methodAnnotations == null) &&
+            (parameterAnnotations == null);
+    }
+
+    /**
+     * Returns whether this item is a candidate for interning. The only
+     * interning candidates are ones that <i>only</i> have a non-null
+     * set of class annotations, with no other lists.
+     *
+     * @return {@code true} if this is an interning candidate, or
+     * {@code false} if not
+     */
+    public boolean isInternable() {
+        return (classAnnotations != null) &&
+            (fieldAnnotations == null) &&
+            (methodAnnotations == null) &&
+            (parameterAnnotations == null);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        if (classAnnotations == null) {
+            return 0;
+        }
+
+        return classAnnotations.hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><b>Note:</b>: This throws an exception if this item is not
+     * internable.</p>
+     *
+     * @see #isInternable
+     */
+    @Override
+    public int compareTo0(OffsettedItem other) {
+        if (! isInternable()) {
+            throw new UnsupportedOperationException("uninternable instance");
+        }
+
+        AnnotationsDirectoryItem otherDirectory =
+            (AnnotationsDirectoryItem) other;
+        return classAnnotations.compareTo(otherDirectory.classAnnotations);
+    }
+
+    /**
+     * Sets the direct annotations on this instance. These are annotations
+     * made on the class, per se, as opposed to on one of its members.
+     * It is only valid to call this method at most once per instance.
+     *
+     * @param annotations {@code non-null;} annotations to set for this class
+     */
+    public void setClassAnnotations(Annotations annotations) {
+        if (annotations == null) {
+            throw new NullPointerException("annotations == null");
+        }
+
+        if (classAnnotations != null) {
+            throw new UnsupportedOperationException(
+                    "class annotations already set");
+        }
+
+        classAnnotations = new AnnotationSetItem(annotations);
+    }
+
+    /**
+     * Adds a field annotations item to this instance.
+     *
+     * @param field {@code non-null;} field in question
+     * @param annotations {@code non-null;} associated annotations to add
+     */
+    public void addFieldAnnotations(CstFieldRef field,
+            Annotations annotations) {
+        if (fieldAnnotations == null) {
+            fieldAnnotations = new ArrayList<FieldAnnotationStruct>();
+        }
+
+        fieldAnnotations.add(new FieldAnnotationStruct(field,
+                        new AnnotationSetItem(annotations)));
+    }
+
+    /**
+     * Adds a method annotations item to this instance.
+     *
+     * @param method {@code non-null;} method in question
+     * @param annotations {@code non-null;} associated annotations to add
+     */
+    public void addMethodAnnotations(CstMethodRef method,
+            Annotations annotations) {
+        if (methodAnnotations == null) {
+            methodAnnotations = new ArrayList<MethodAnnotationStruct>();
+        }
+
+        methodAnnotations.add(new MethodAnnotationStruct(method,
+                        new AnnotationSetItem(annotations)));
+    }
+
+    /**
+     * Adds a parameter annotations item to this instance.
+     *
+     * @param method {@code non-null;} method in question
+     * @param list {@code non-null;} associated list of annotation sets to add
+     */
+    public void addParameterAnnotations(CstMethodRef method,
+            AnnotationsList list) {
+        if (parameterAnnotations == null) {
+            parameterAnnotations = new ArrayList<ParameterAnnotationStruct>();
+        }
+
+        parameterAnnotations.add(new ParameterAnnotationStruct(method, list));
+    }
+
+    /**
+     * Gets the method annotations for a given method, if any. This is
+     * meant for use by debugging / dumping code.
+     *
+     * @param method {@code non-null;} the method
+     * @return {@code null-ok;} the method annotations, if any
+     */
+    public Annotations getMethodAnnotations(CstMethodRef method) {
+        if (methodAnnotations == null) {
+            return null;
+        }
+
+        for (MethodAnnotationStruct item : methodAnnotations) {
+            if (item.getMethod().equals(method)) {
+                return item.getAnnotations();
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Gets the parameter annotations for a given method, if any. This is
+     * meant for use by debugging / dumping code.
+     *
+     * @param method {@code non-null;} the method
+     * @return {@code null-ok;} the parameter annotations, if any
+     */
+    public AnnotationsList getParameterAnnotations(CstMethodRef method) {
+        if (parameterAnnotations == null) {
+            return null;
+        }
+
+        for (ParameterAnnotationStruct item : parameterAnnotations) {
+            if (item.getMethod().equals(method)) {
+                return item.getAnnotationsList();
+            }
+        }
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public void addContents(DexFile file) {
+        MixedItemSection wordData = file.getWordData();
+
+        if (classAnnotations != null) {
+            classAnnotations = wordData.intern(classAnnotations);
+        }
+
+        if (fieldAnnotations != null) {
+            for (FieldAnnotationStruct item : fieldAnnotations) {
+                item.addContents(file);
+            }
+        }
+
+        if (methodAnnotations != null) {
+            for (MethodAnnotationStruct item : methodAnnotations) {
+                item.addContents(file);
+            }
+        }
+
+        if (parameterAnnotations != null) {
+            for (ParameterAnnotationStruct item : parameterAnnotations) {
+                item.addContents(file);
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toHuman() {
+        throw new RuntimeException("unsupported");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void place0(Section addedTo, int offset) {
+        // We just need to set the write size here.
+
+        int elementCount = listSize(fieldAnnotations)
+            + listSize(methodAnnotations) + listSize(parameterAnnotations);
+        setWriteSize(HEADER_SIZE + (elementCount * ELEMENT_SIZE));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void writeTo0(DexFile file, AnnotatedOutput out) {
+        boolean annotates = out.annotates();
+        int classOff = OffsettedItem.getAbsoluteOffsetOr0(classAnnotations);
+        int fieldsSize = listSize(fieldAnnotations);
+        int methodsSize = listSize(methodAnnotations);
+        int parametersSize = listSize(parameterAnnotations);
+
+        if (annotates) {
+            out.annotate(0, offsetString() + " annotations directory");
+            out.annotate(4, "  class_annotations_off: " + Hex.u4(classOff));
+            out.annotate(4, "  fields_size:           " +
+                    Hex.u4(fieldsSize));
+            out.annotate(4, "  methods_size:          " +
+                    Hex.u4(methodsSize));
+            out.annotate(4, "  parameters_size:       " +
+                    Hex.u4(parametersSize));
+        }
+
+        out.writeInt(classOff);
+        out.writeInt(fieldsSize);
+        out.writeInt(methodsSize);
+        out.writeInt(parametersSize);
+
+        if (fieldsSize != 0) {
+            Collections.sort(fieldAnnotations);
+            if (annotates) {
+                out.annotate(0, "  fields:");
+            }
+            for (FieldAnnotationStruct item : fieldAnnotations) {
+                item.writeTo(file, out);
+            }
+        }
+
+        if (methodsSize != 0) {
+            Collections.sort(methodAnnotations);
+            if (annotates) {
+                out.annotate(0, "  methods:");
+            }
+            for (MethodAnnotationStruct item : methodAnnotations) {
+                item.writeTo(file, out);
+            }
+        }
+
+        if (parametersSize != 0) {
+            Collections.sort(parameterAnnotations);
+            if (annotates) {
+                out.annotate(0, "  parameters:");
+            }
+            for (ParameterAnnotationStruct item : parameterAnnotations) {
+                item.writeTo(file, out);
+            }
+        }
+    }
+
+    /**
+     * Gets the list size of the given list, or {@code 0} if given
+     * {@code null}.
+     *
+     * @param list {@code null-ok;} the list in question
+     * @return {@code >= 0;} its size
+     */
+    private static int listSize(ArrayList<?> list) {
+        if (list == null) {
+            return 0;
+        }
+
+        return list.size();
+    }
+
+    /**
+     * Prints out the contents of this instance, in a debugging-friendly
+     * way. This is meant to be called from {@link ClassDefItem#debugPrint}.
+     *
+     * @param out {@code non-null;} where to output to
+     */
+    /*package*/ void debugPrint(PrintWriter out) {
+        if (classAnnotations != null) {
+            out.println("  class annotations: " + classAnnotations);
+        }
+
+        if (fieldAnnotations != null) {
+            out.println("  field annotations:");
+            for (FieldAnnotationStruct item : fieldAnnotations) {
+                out.println("    " + item.toHuman());
+            }
+        }
+
+        if (methodAnnotations != null) {
+            out.println("  method annotations:");
+            for (MethodAnnotationStruct item : methodAnnotations) {
+                out.println("    " + item.toHuman());
+            }
+        }
+
+        if (parameterAnnotations != null) {
+            out.println("  parameter annotations:");
+            for (ParameterAnnotationStruct item : parameterAnnotations) {
+                out.println("    " + item.toHuman());
+            }
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/CatchStructs.java b/dexgen/src/com/android/dexgen/dex/file/CatchStructs.java
new file mode 100644
index 0000000..df9a847
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/CatchStructs.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.dex.code.CatchHandlerList;
+import com.android.dexgen.dex.code.CatchTable;
+import com.android.dexgen.dex.code.DalvCode;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.ByteArrayAnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * List of exception handlers (tuples of covered range, catch type,
+ * handler address) for a particular piece of code. Instances of this
+ * class correspond to a {@code try_item[]} and a
+ * {@code catch_handler_item[]}.
+ */
+public final class CatchStructs {
+    /**
+     * the size of a {@code try_item}: a {@code uint}
+     * and two {@code ushort}s
+     */
+    private static final int TRY_ITEM_WRITE_SIZE = 4 + (2 * 2);
+
+    /** {@code non-null;} code that contains the catches */
+    private final DalvCode code;
+
+    /**
+     * {@code null-ok;} the underlying table; set in
+     * {@link #finishProcessingIfNecessary}
+     */
+    private CatchTable table;
+
+    /**
+     * {@code null-ok;} the encoded handler list, if calculated; set in
+     * {@link #encode}
+     */
+    private byte[] encodedHandlers;
+
+    /**
+     * length of the handlers header (encoded size), if known; used for
+     * annotation
+     */
+    private int encodedHandlerHeaderSize;
+
+    /**
+     * {@code null-ok;} map from handler lists to byte offsets, if calculated; set in
+     * {@link #encode}
+     */
+    private TreeMap<CatchHandlerList, Integer> handlerOffsets;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param code {@code non-null;} code that contains the catches
+     */
+    public CatchStructs(DalvCode code) {
+        this.code = code;
+        this.table = null;
+        this.encodedHandlers = null;
+        this.encodedHandlerHeaderSize = 0;
+        this.handlerOffsets = null;
+    }
+
+    /**
+     * Finish processing the catches, if necessary.
+     */
+    private void finishProcessingIfNecessary() {
+        if (table == null) {
+            table = code.getCatches();
+        }
+    }
+
+    /**
+     * Gets the size of the tries list, in entries.
+     *
+     * @return {@code >= 0;} the tries list size
+     */
+    public int triesSize() {
+        finishProcessingIfNecessary();
+        return table.size();
+    }
+
+    /**
+     * Does a human-friendly dump of this instance.
+     *
+     * @param out {@code non-null;} where to dump
+     * @param prefix {@code non-null;} prefix to attach to each line of output
+     */
+    public void debugPrint(PrintWriter out, String prefix) {
+        annotateEntries(prefix, out, null);
+    }
+
+    /**
+     * Encodes the handler lists.
+     *
+     * @param file {@code non-null;} file this instance is part of
+     */
+    public void encode(DexFile file) {
+        finishProcessingIfNecessary();
+
+        TypeIdsSection typeIds = file.getTypeIds();
+        int size = table.size();
+
+        handlerOffsets = new TreeMap<CatchHandlerList, Integer>();
+
+        /*
+         * First add a map entry for each unique list. The tree structure
+         * will ensure they are sorted when we reiterate later.
+         */
+        for (int i = 0; i < size; i++) {
+            handlerOffsets.put(table.get(i).getHandlers(), null);
+        }
+
+        if (handlerOffsets.size() > 65535) {
+            throw new UnsupportedOperationException(
+                    "too many catch handlers");
+        }
+
+        ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
+
+        // Write out the handlers "header" consisting of its size in entries.
+        encodedHandlerHeaderSize =
+            out.writeUnsignedLeb128(handlerOffsets.size());
+
+        // Now write the lists out in order, noting the offset of each.
+        for (Map.Entry<CatchHandlerList, Integer> mapping :
+                 handlerOffsets.entrySet()) {
+            CatchHandlerList list = mapping.getKey();
+            int listSize = list.size();
+            boolean catchesAll = list.catchesAll();
+
+            // Set the offset before we do any writing.
+            mapping.setValue(out.getCursor());
+
+            if (catchesAll) {
+                // A size <= 0 means that the list ends with a catch-all.
+                out.writeSignedLeb128(-(listSize - 1));
+                listSize--;
+            } else {
+                out.writeSignedLeb128(listSize);
+            }
+
+            for (int i = 0; i < listSize; i++) {
+                CatchHandlerList.Entry entry = list.get(i);
+                out.writeUnsignedLeb128(
+                        typeIds.indexOf(entry.getExceptionType()));
+                out.writeUnsignedLeb128(entry.getHandler());
+            }
+
+            if (catchesAll) {
+                out.writeUnsignedLeb128(list.get(listSize).getHandler());
+            }
+        }
+
+        encodedHandlers = out.toByteArray();
+    }
+
+    /**
+     * Gets the write size of this instance, in bytes.
+     *
+     * @return {@code >= 0;} the write size
+     */
+    public int writeSize() {
+        return (triesSize() * TRY_ITEM_WRITE_SIZE) +
+                + encodedHandlers.length;
+    }
+
+    /**
+     * Writes this instance to the given stream.
+     *
+     * @param file {@code non-null;} file this instance is part of
+     * @param out {@code non-null;} where to write to
+     */
+    public void writeTo(DexFile file, AnnotatedOutput out) {
+        finishProcessingIfNecessary();
+
+        if (out.annotates()) {
+            annotateEntries("  ", null, out);
+        }
+
+        int tableSize = table.size();
+        for (int i = 0; i < tableSize; i++) {
+            CatchTable.Entry one = table.get(i);
+            int start = one.getStart();
+            int end = one.getEnd();
+            int insnCount = end - start;
+
+            if (insnCount >= 65536) {
+                throw new UnsupportedOperationException(
+                        "bogus exception range: " + Hex.u4(start) + ".." +
+                        Hex.u4(end));
+            }
+
+            out.writeInt(start);
+            out.writeShort(insnCount);
+            out.writeShort(handlerOffsets.get(one.getHandlers()));
+        }
+
+        out.write(encodedHandlers);
+    }
+
+    /**
+     * Helper method to annotate or simply print the exception handlers.
+     * Only one of {@code printTo} or {@code annotateTo} should
+     * be non-null.
+     *
+     * @param prefix {@code non-null;} prefix for each line
+     * @param printTo {@code null-ok;} where to print to
+     * @param annotateTo {@code null-ok;} where to consume bytes and annotate to
+     */
+    private void annotateEntries(String prefix, PrintWriter printTo,
+            AnnotatedOutput annotateTo) {
+        finishProcessingIfNecessary();
+
+        boolean consume = (annotateTo != null);
+        int amt1 = consume ? 6 : 0;
+        int amt2 = consume ? 2 : 0;
+        int size = table.size();
+        String subPrefix = prefix + "  ";
+
+        if (consume) {
+            annotateTo.annotate(0, prefix + "tries:");
+        } else {
+            printTo.println(prefix + "tries:");
+        }
+
+        for (int i = 0; i < size; i++) {
+            CatchTable.Entry entry = table.get(i);
+            CatchHandlerList handlers = entry.getHandlers();
+            String s1 = subPrefix + "try " + Hex.u2or4(entry.getStart())
+                + ".." + Hex.u2or4(entry.getEnd());
+            String s2 = handlers.toHuman(subPrefix, "");
+
+            if (consume) {
+                annotateTo.annotate(amt1, s1);
+                annotateTo.annotate(amt2, s2);
+            } else {
+                printTo.println(s1);
+                printTo.println(s2);
+            }
+        }
+
+        if (! consume) {
+            // Only emit the handler lists if we are consuming bytes.
+            return;
+        }
+
+        annotateTo.annotate(0, prefix + "handlers:");
+        annotateTo.annotate(encodedHandlerHeaderSize,
+                subPrefix + "size: " + Hex.u2(handlerOffsets.size()));
+
+        int lastOffset = 0;
+        CatchHandlerList lastList = null;
+
+        for (Map.Entry<CatchHandlerList, Integer> mapping :
+                 handlerOffsets.entrySet()) {
+            CatchHandlerList list = mapping.getKey();
+            int offset = mapping.getValue();
+
+            if (lastList != null) {
+                annotateAndConsumeHandlers(lastList, lastOffset,
+                        offset - lastOffset, subPrefix, printTo, annotateTo);
+            }
+
+            lastList = list;
+            lastOffset = offset;
+        }
+
+        annotateAndConsumeHandlers(lastList, lastOffset,
+                encodedHandlers.length - lastOffset,
+                subPrefix, printTo, annotateTo);
+    }
+
+    /**
+     * Helper for {@link #annotateEntries} to annotate a catch handler list
+     * while consuming it.
+     *
+     * @param handlers {@code non-null;} handlers to annotate
+     * @param offset {@code >= 0;} the offset of this handler
+     * @param size {@code >= 1;} the number of bytes the handlers consume
+     * @param prefix {@code non-null;} prefix for each line
+     * @param printTo {@code null-ok;} where to print to
+     * @param annotateTo {@code non-null;} where to annotate to
+     */
+    private static void annotateAndConsumeHandlers(CatchHandlerList handlers,
+            int offset, int size, String prefix, PrintWriter printTo,
+            AnnotatedOutput annotateTo) {
+        String s = handlers.toHuman(prefix, Hex.u2(offset) + ": ");
+
+        if (printTo != null) {
+            printTo.println(s);
+        }
+
+        annotateTo.annotate(size, s);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/ClassDataItem.java b/dexgen/src/com/android/dexgen/dex/file/ClassDataItem.java
new file mode 100644
index 0000000..c46a4a5
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/ClassDataItem.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstArray;
+import com.android.dexgen.rop.cst.CstLiteralBits;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.Zeroes;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.ByteArrayAnnotatedOutput;
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.Writers;
+
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.HashMap;
+
+/**
+ * Representation of all the parts of a Dalvik class that are generally
+ * "inflated" into an in-memory representation at runtime. Instances of
+ * this class are represented in a compact streamable form in a
+ * {@code dex} file, as opposed to a random-access form.
+ */
+public final class ClassDataItem extends OffsettedItem {
+    /** {@code non-null;} what class this data is for, just for listing generation */
+    private final CstType thisClass;
+
+    /** {@code non-null;} list of static fields */
+    private final ArrayList<EncodedField> staticFields;
+
+    /** {@code non-null;} list of initial values for static fields */
+    private final HashMap<EncodedField, Constant> staticValues;
+
+    /** {@code non-null;} list of instance fields */
+    private final ArrayList<EncodedField> instanceFields;
+
+    /** {@code non-null;} list of direct methods */
+    private final ArrayList<EncodedMethod> directMethods;
+
+    /** {@code non-null;} list of virtual methods */
+    private final ArrayList<EncodedMethod> virtualMethods;
+
+    /** {@code null-ok;} static initializer list; set in {@link #addContents} */
+    private CstArray staticValuesConstant;
+
+    /**
+     * {@code null-ok;} encoded form, ready for writing to a file; set during
+     * {@link #place0}
+     */
+    private byte[] encodedForm;
+
+    /**
+     * Constructs an instance. Its sets of members are initially
+     * empty.
+     *
+     * @param thisClass {@code non-null;} what class this data is for, just
+     * for listing generation
+     */
+    public ClassDataItem(CstType thisClass) {
+        super(1, -1);
+
+        if (thisClass == null) {
+            throw new NullPointerException("thisClass == null");
+        }
+
+        this.thisClass = thisClass;
+        this.staticFields = new ArrayList<EncodedField>(20);
+        this.staticValues = new HashMap<EncodedField, Constant>(40);
+        this.instanceFields = new ArrayList<EncodedField>(20);
+        this.directMethods = new ArrayList<EncodedMethod>(20);
+        this.virtualMethods = new ArrayList<EncodedMethod>(20);
+        this.staticValuesConstant = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_CLASS_DATA_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toHuman() {
+        return toString();
+    }
+
+    /**
+     * Returns whether this instance is empty.
+     *
+     * @return {@code true} if this instance is empty or
+     * {@code false} if at least one element has been added to it
+     */
+    public boolean isEmpty() {
+        return staticFields.isEmpty() && instanceFields.isEmpty()
+            && directMethods.isEmpty() && virtualMethods.isEmpty();
+    }
+
+    /**
+     * Adds a static field.
+     *
+     * @param field {@code non-null;} the field to add
+     * @param value {@code null-ok;} initial value for the field, if any
+     */
+    public void addStaticField(EncodedField field, Constant value) {
+        if (field == null) {
+            throw new NullPointerException("field == null");
+        }
+
+        if (staticValuesConstant != null) {
+            throw new UnsupportedOperationException(
+                    "static fields already sorted");
+        }
+
+        staticFields.add(field);
+        staticValues.put(field, value);
+    }
+
+    /**
+     * Adds an instance field.
+     *
+     * @param field {@code non-null;} the field to add
+     */
+    public void addInstanceField(EncodedField field) {
+        if (field == null) {
+            throw new NullPointerException("field == null");
+        }
+
+        instanceFields.add(field);
+    }
+
+    /**
+     * Adds a direct ({@code static} and/or {@code private}) method.
+     *
+     * @param method {@code non-null;} the method to add
+     */
+    public void addDirectMethod(EncodedMethod method) {
+        if (method == null) {
+            throw new NullPointerException("method == null");
+        }
+
+        directMethods.add(method);
+    }
+
+    /**
+     * Adds a virtual method.
+     *
+     * @param method {@code non-null;} the method to add
+     */
+    public void addVirtualMethod(EncodedMethod method) {
+        if (method == null) {
+            throw new NullPointerException("method == null");
+        }
+
+        virtualMethods.add(method);
+    }
+
+    /**
+     * Gets all the methods in this class. The returned list is not linked
+     * in any way to the underlying lists contained in this instance, but
+     * the objects contained in the list are shared.
+     *
+     * @return {@code non-null;} list of all methods
+     */
+    public ArrayList<EncodedMethod> getMethods() {
+        int sz = directMethods.size() + virtualMethods.size();
+        ArrayList<EncodedMethod> result = new ArrayList<EncodedMethod>(sz);
+
+        result.addAll(directMethods);
+        result.addAll(virtualMethods);
+
+        return result;
+    }
+
+
+    /**
+     * Prints out the contents of this instance, in a debugging-friendly
+     * way.
+     *
+     * @param out {@code non-null;} where to output to
+     * @param verbose whether to be verbose with the output
+     */
+    public void debugPrint(Writer out, boolean verbose) {
+        PrintWriter pw = Writers.printWriterFor(out);
+
+        int sz = staticFields.size();
+        for (int i = 0; i < sz; i++) {
+            pw.println("  sfields[" + i + "]: " + staticFields.get(i));
+        }
+
+        sz = instanceFields.size();
+        for (int i = 0; i < sz; i++) {
+            pw.println("  ifields[" + i + "]: " + instanceFields.get(i));
+        }
+
+        sz = directMethods.size();
+        for (int i = 0; i < sz; i++) {
+            pw.println("  dmeths[" + i + "]:");
+            directMethods.get(i).debugPrint(pw, verbose);
+        }
+
+        sz = virtualMethods.size();
+        for (int i = 0; i < sz; i++) {
+            pw.println("  vmeths[" + i + "]:");
+            virtualMethods.get(i).debugPrint(pw, verbose);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        if (!staticFields.isEmpty()) {
+            getStaticValuesConstant(); // Force the fields to be sorted.
+            for (EncodedField field : staticFields) {
+                field.addContents(file);
+            }
+        }
+
+        if (!instanceFields.isEmpty()) {
+            Collections.sort(instanceFields);
+            for (EncodedField field : instanceFields) {
+                field.addContents(file);
+            }
+        }
+
+        if (!directMethods.isEmpty()) {
+            Collections.sort(directMethods);
+            for (EncodedMethod method : directMethods) {
+                method.addContents(file);
+            }
+        }
+
+        if (!virtualMethods.isEmpty()) {
+            Collections.sort(virtualMethods);
+            for (EncodedMethod method : virtualMethods) {
+                method.addContents(file);
+            }
+        }
+    }
+
+    /**
+     * Gets a {@link CstArray} corresponding to {@link #staticValues} if
+     * it contains any non-zero non-{@code null} values.
+     *
+     * @return {@code null-ok;} the corresponding constant or {@code null} if
+     * there are no values to encode
+     */
+    public CstArray getStaticValuesConstant() {
+        if ((staticValuesConstant == null) && (staticFields.size() != 0)) {
+            staticValuesConstant = makeStaticValuesConstant();
+        }
+
+        return staticValuesConstant;
+    }
+
+    /**
+     * Gets a {@link CstArray} corresponding to {@link #staticValues} if
+     * it contains any non-zero non-{@code null} values.
+     *
+     * @return {@code null-ok;} the corresponding constant or {@code null} if
+     * there are no values to encode
+     */
+    private CstArray makeStaticValuesConstant() {
+        // First sort the statics into their final order.
+        Collections.sort(staticFields);
+
+        /*
+         * Get the size of staticValues minus any trailing zeros/nulls (both
+         * nulls per se as well as instances of CstKnownNull).
+         */
+
+        int size = staticFields.size();
+        while (size > 0) {
+            EncodedField field = staticFields.get(size - 1);
+            Constant cst = staticValues.get(field);
+            if (cst instanceof CstLiteralBits) {
+                // Note: CstKnownNull extends CstLiteralBits.
+                if (((CstLiteralBits) cst).getLongBits() != 0) {
+                    break;
+                }
+            } else if (cst != null) {
+                break;
+            }
+            size--;
+        }
+
+        if (size == 0) {
+            return null;
+        }
+
+        // There is something worth encoding, so build up a result.
+
+        CstArray.List list = new CstArray.List(size);
+        for (int i = 0; i < size; i++) {
+            EncodedField field = staticFields.get(i);
+            Constant cst = staticValues.get(field);
+            if (cst == null) {
+                cst = Zeroes.zeroFor(field.getRef().getType());
+            }
+            list.set(i, cst);
+        }
+        list.setImmutable();
+
+        return new CstArray(list);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void place0(Section addedTo, int offset) {
+        // Encode the data and note the size.
+
+        ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
+
+        encodeOutput(addedTo.getFile(), out);
+        encodedForm = out.toByteArray();
+        setWriteSize(encodedForm.length);
+    }
+
+    /**
+     * Writes out the encoded form of this instance.
+     *
+     * @param file {@code non-null;} file this instance is part of
+     * @param out {@code non-null;} where to write to
+     */
+    private void encodeOutput(DexFile file, AnnotatedOutput out) {
+        boolean annotates = out.annotates();
+
+        if (annotates) {
+            out.annotate(0, offsetString() + " class data for " +
+                    thisClass.toHuman());
+        }
+
+        encodeSize(file, out, "static_fields", staticFields.size());
+        encodeSize(file, out, "instance_fields", instanceFields.size());
+        encodeSize(file, out, "direct_methods", directMethods.size());
+        encodeSize(file, out, "virtual_methods", virtualMethods.size());
+
+        encodeList(file, out, "static_fields", staticFields);
+        encodeList(file, out, "instance_fields", instanceFields);
+        encodeList(file, out, "direct_methods", directMethods);
+        encodeList(file, out, "virtual_methods", virtualMethods);
+
+        if (annotates) {
+            out.endAnnotation();
+        }
+    }
+
+    /**
+     * Helper for {@link #encodeOutput}, which writes out the given
+     * size value, annotating it as well (if annotations are enabled).
+     *
+     * @param file {@code non-null;} file this instance is part of
+     * @param out {@code non-null;} where to write to
+     * @param label {@code non-null;} the label for the purposes of annotation
+     * @param size {@code >= 0;} the size to write
+     */
+    private static void encodeSize(DexFile file, AnnotatedOutput out,
+            String label, int size) {
+        if (out.annotates()) {
+            out.annotate(String.format("  %-21s %08x", label + "_size:",
+                            size));
+        }
+
+        out.writeUnsignedLeb128(size);
+    }
+
+    /**
+     * Helper for {@link #encodeOutput}, which writes out the given
+     * list. It also annotates the items (if any and if annotations
+     * are enabled).
+     *
+     * @param file {@code non-null;} file this instance is part of
+     * @param out {@code non-null;} where to write to
+     * @param label {@code non-null;} the label for the purposes of annotation
+     * @param list {@code non-null;} the list in question
+     */
+    private static void encodeList(DexFile file, AnnotatedOutput out,
+            String label, ArrayList<? extends EncodedMember> list) {
+        int size = list.size();
+        int lastIndex = 0;
+
+        if (size == 0) {
+            return;
+        }
+
+        if (out.annotates()) {
+            out.annotate(0, "  " + label + ":");
+        }
+
+        for (int i = 0; i < size; i++) {
+            lastIndex = list.get(i).encode(file, out, lastIndex, i);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo0(DexFile file, AnnotatedOutput out) {
+        boolean annotates = out.annotates();
+
+        if (annotates) {
+            /*
+             * The output is to be annotated, so redo the work previously
+             * done by place0(), except this time annotations will actually
+             * get emitted.
+             */
+            encodeOutput(file, out);
+        } else {
+            out.write(encodedForm);
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/ClassDefItem.java b/dexgen/src/com/android/dexgen/dex/file/ClassDefItem.java
new file mode 100644
index 0000000..6177145
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/ClassDefItem.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.annotation.Annotations;
+import com.android.dexgen.rop.annotation.AnnotationsList;
+import com.android.dexgen.rop.code.AccessFlags;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstArray;
+import com.android.dexgen.rop.cst.CstFieldRef;
+import com.android.dexgen.rop.cst.CstMethodRef;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.TypeList;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.Writers;
+
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.TreeSet;
+
+/**
+ * Representation of a Dalvik class, which is basically a set of
+ * members (fields or methods) along with a few more pieces of
+ * information.
+ */
+public final class ClassDefItem extends IndexedItem {
+    /** size of instances when written out to a file, in bytes */
+    public static final int WRITE_SIZE = 32;
+
+    /** {@code non-null;} type constant for this class */
+    private final CstType thisClass;
+
+    /** access flags */
+    private final int accessFlags;
+
+    /**
+     * {@code null-ok;} superclass or {@code null} if this class is a/the
+     * root class
+     */
+    private final CstType superclass;
+
+    /** {@code null-ok;} list of implemented interfaces */
+    private TypeListItem interfaces;
+
+    /** {@code null-ok;} source file name or {@code null} if unknown */
+    private final CstUtf8 sourceFile;
+
+    /** {@code non-null;} associated class data object */
+    private final ClassDataItem classData;
+
+    /**
+     * {@code null-ok;} item wrapper for the static values, initialized
+     * in {@link #addContents}
+     */
+    private EncodedArrayItem staticValuesItem;
+
+    /** {@code non-null;} annotations directory */
+    private AnnotationsDirectoryItem annotationsDirectory;
+
+    /**
+     * Constructs an instance. Its sets of members and annotations are
+     * initially empty.
+     *
+     * @param thisClass {@code non-null;} type constant for this class
+     * @param accessFlags access flags
+     * @param superclass {@code null-ok;} superclass or {@code null} if
+     * this class is a/the root class
+     * @param interfaces {@code non-null;} list of implemented interfaces
+     * @param sourceFile {@code null-ok;} source file name or
+     * {@code null} if unknown
+     */
+    public ClassDefItem(CstType thisClass, int accessFlags,
+            CstType superclass, TypeList interfaces, CstUtf8 sourceFile) {
+        if (thisClass == null) {
+            throw new NullPointerException("thisClass == null");
+        }
+
+        /*
+         * TODO: Maybe check accessFlags and superclass, at
+         * least for easily-checked stuff?
+         */
+
+        if (interfaces == null) {
+            throw new NullPointerException("interfaces == null");
+        }
+
+        this.thisClass = thisClass;
+        this.accessFlags = accessFlags;
+        this.superclass = superclass;
+        this.interfaces =
+            (interfaces.size() == 0) ? null :  new TypeListItem(interfaces);
+        this.sourceFile = sourceFile;
+        this.classData = new ClassDataItem(thisClass);
+        this.staticValuesItem = null;
+        this.annotationsDirectory = new AnnotationsDirectoryItem();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_CLASS_DEF_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int writeSize() {
+        return WRITE_SIZE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        TypeIdsSection typeIds = file.getTypeIds();
+        MixedItemSection byteData = file.getByteData();
+        MixedItemSection wordData = file.getWordData();
+        MixedItemSection typeLists = file.getTypeLists();
+        StringIdsSection stringIds = file.getStringIds();
+
+        typeIds.intern(thisClass);
+
+        if (!classData.isEmpty()) {
+            MixedItemSection classDataSection = file.getClassData();
+            classDataSection.add(classData);
+
+            CstArray staticValues = classData.getStaticValuesConstant();
+            if (staticValues != null) {
+                staticValuesItem =
+                    byteData.intern(new EncodedArrayItem(staticValues));
+            }
+        }
+
+        if (superclass != null) {
+            typeIds.intern(superclass);
+        }
+
+        if (interfaces != null) {
+            interfaces = typeLists.intern(interfaces);
+        }
+
+        if (sourceFile != null) {
+            stringIds.intern(sourceFile);
+        }
+
+        if (! annotationsDirectory.isEmpty()) {
+            if (annotationsDirectory.isInternable()) {
+                annotationsDirectory = wordData.intern(annotationsDirectory);
+            } else {
+                wordData.add(annotationsDirectory);
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(DexFile file, AnnotatedOutput out) {
+        boolean annotates = out.annotates();
+        TypeIdsSection typeIds = file.getTypeIds();
+        int classIdx = typeIds.indexOf(thisClass);
+        int superIdx = (superclass == null) ? -1 :
+            typeIds.indexOf(superclass);
+        int interOff = OffsettedItem.getAbsoluteOffsetOr0(interfaces);
+        int annoOff = annotationsDirectory.isEmpty() ? 0 :
+            annotationsDirectory.getAbsoluteOffset();
+        int sourceFileIdx = (sourceFile == null) ? -1 :
+            file.getStringIds().indexOf(sourceFile);
+        int dataOff = classData.isEmpty()? 0 : classData.getAbsoluteOffset();
+        int staticValuesOff =
+            OffsettedItem.getAbsoluteOffsetOr0(staticValuesItem);
+
+        if (annotates) {
+            out.annotate(0, indexString() + ' ' + thisClass.toHuman());
+            out.annotate(4, "  class_idx:           " + Hex.u4(classIdx));
+            out.annotate(4, "  access_flags:        " +
+                         AccessFlags.classString(accessFlags));
+            out.annotate(4, "  superclass_idx:      " + Hex.u4(superIdx) +
+                         " // " + ((superclass == null) ? "<none>" :
+                          superclass.toHuman()));
+            out.annotate(4, "  interfaces_off:      " + Hex.u4(interOff));
+            if (interOff != 0) {
+                TypeList list = interfaces.getList();
+                int sz = list.size();
+                for (int i = 0; i < sz; i++) {
+                    out.annotate(0, "    " + list.getType(i).toHuman());
+                }
+            }
+            out.annotate(4, "  source_file_idx:     " + Hex.u4(sourceFileIdx) +
+                         " // " + ((sourceFile == null) ? "<none>" :
+                          sourceFile.toHuman()));
+            out.annotate(4, "  annotations_off:     " + Hex.u4(annoOff));
+            out.annotate(4, "  class_data_off:      " + Hex.u4(dataOff));
+            out.annotate(4, "  static_values_off:   " +
+                    Hex.u4(staticValuesOff));
+        }
+
+        out.writeInt(classIdx);
+        out.writeInt(accessFlags);
+        out.writeInt(superIdx);
+        out.writeInt(interOff);
+        out.writeInt(sourceFileIdx);
+        out.writeInt(annoOff);
+        out.writeInt(dataOff);
+        out.writeInt(staticValuesOff);
+    }
+
+    /**
+     * Gets the constant corresponding to this class.
+     *
+     * @return {@code non-null;} the constant
+     */
+    public CstType getThisClass() {
+        return thisClass;
+    }
+
+    /**
+     * Gets the access flags.
+     *
+     * @return the access flags
+     */
+    public int getAccessFlags() {
+        return accessFlags;
+    }
+
+    /**
+     * Gets the superclass.
+     *
+     * @return {@code null-ok;} the superclass or {@code null} if
+     * this class is a/the root class
+     */
+    public CstType getSuperclass() {
+        return superclass;
+    }
+
+    /**
+     * Gets the list of interfaces implemented.
+     *
+     * @return {@code non-null;} the interfaces list
+     */
+    public TypeList getInterfaces() {
+        if (interfaces == null) {
+            return StdTypeList.EMPTY;
+        }
+
+        return interfaces.getList();
+    }
+
+    /**
+     * Gets the source file name.
+     *
+     * @return {@code null-ok;} the source file name or {@code null} if unknown
+     */
+    public CstUtf8 getSourceFile() {
+        return sourceFile;
+    }
+
+    /**
+     * Adds a static field.
+     *
+     * @param field {@code non-null;} the field to add
+     * @param value {@code null-ok;} initial value for the field, if any
+     */
+    public void addStaticField(EncodedField field, Constant value) {
+        classData.addStaticField(field, value);
+    }
+
+    /**
+     * Adds an instance field.
+     *
+     * @param field {@code non-null;} the field to add
+     */
+    public void addInstanceField(EncodedField field) {
+        classData.addInstanceField(field);
+    }
+
+    /**
+     * Adds a direct ({@code static} and/or {@code private}) method.
+     *
+     * @param method {@code non-null;} the method to add
+     */
+    public void addDirectMethod(EncodedMethod method) {
+        classData.addDirectMethod(method);
+    }
+
+    /**
+     * Adds a virtual method.
+     *
+     * @param method {@code non-null;} the method to add
+     */
+    public void addVirtualMethod(EncodedMethod method) {
+        classData.addVirtualMethod(method);
+    }
+
+    /**
+     * Gets all the methods in this class. The returned list is not linked
+     * in any way to the underlying lists contained in this instance, but
+     * the objects contained in the list are shared.
+     *
+     * @return {@code non-null;} list of all methods
+     */
+    public ArrayList<EncodedMethod> getMethods() {
+        return classData.getMethods();
+    }
+
+    /**
+     * Sets the direct annotations on this class. These are annotations
+     * made on the class, per se, as opposed to on one of its members.
+     * It is only valid to call this method at most once per instance.
+     *
+     * @param annotations {@code non-null;} annotations to set for this class
+     */
+    public void setClassAnnotations(Annotations annotations) {
+        annotationsDirectory.setClassAnnotations(annotations);
+    }
+
+    /**
+     * Adds a field annotations item to this class.
+     *
+     * @param field {@code non-null;} field in question
+     * @param annotations {@code non-null;} associated annotations to add
+     */
+    public void addFieldAnnotations(CstFieldRef field,
+            Annotations annotations) {
+        annotationsDirectory.addFieldAnnotations(field, annotations);
+    }
+
+    /**
+     * Adds a method annotations item to this class.
+     *
+     * @param method {@code non-null;} method in question
+     * @param annotations {@code non-null;} associated annotations to add
+     */
+    public void addMethodAnnotations(CstMethodRef method,
+            Annotations annotations) {
+        annotationsDirectory.addMethodAnnotations(method, annotations);
+    }
+
+    /**
+     * Adds a parameter annotations item to this class.
+     *
+     * @param method {@code non-null;} method in question
+     * @param list {@code non-null;} associated list of annotation sets to add
+     */
+    public void addParameterAnnotations(CstMethodRef method,
+            AnnotationsList list) {
+        annotationsDirectory.addParameterAnnotations(method, list);
+    }
+
+    /**
+     * Gets the method annotations for a given method, if any. This is
+     * meant for use by debugging / dumping code.
+     *
+     * @param method {@code non-null;} the method
+     * @return {@code null-ok;} the method annotations, if any
+     */
+    public Annotations getMethodAnnotations(CstMethodRef method) {
+        return annotationsDirectory.getMethodAnnotations(method);
+    }
+
+    /**
+     * Gets the parameter annotations for a given method, if any. This is
+     * meant for use by debugging / dumping code.
+     *
+     * @param method {@code non-null;} the method
+     * @return {@code null-ok;} the parameter annotations, if any
+     */
+    public AnnotationsList getParameterAnnotations(CstMethodRef method) {
+        return annotationsDirectory.getParameterAnnotations(method);
+    }
+
+    /**
+     * Prints out the contents of this instance, in a debugging-friendly
+     * way.
+     *
+     * @param out {@code non-null;} where to output to
+     * @param verbose whether to be verbose with the output
+     */
+    public void debugPrint(Writer out, boolean verbose) {
+        PrintWriter pw = Writers.printWriterFor(out);
+
+        pw.println(getClass().getName() + " {");
+        pw.println("  accessFlags: " + Hex.u2(accessFlags));
+        pw.println("  superclass: " + superclass);
+        pw.println("  interfaces: " +
+                ((interfaces == null) ? "<none>" : interfaces));
+        pw.println("  sourceFile: " +
+                ((sourceFile == null) ? "<none>" : sourceFile.toQuoted()));
+
+        classData.debugPrint(out, verbose);
+        annotationsDirectory.debugPrint(pw);
+
+        pw.println("}");
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/ClassDefsSection.java b/dexgen/src/com/android/dexgen/dex/file/ClassDefsSection.java
new file mode 100644
index 0000000..a6392d4
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/ClassDefsSection.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeList;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.TreeMap;
+
+/**
+ * Class definitions list section of a {@code .dex} file.
+ */
+public final class ClassDefsSection extends UniformItemSection {
+    /**
+     * {@code non-null;} map from type constants for classes to {@link
+     * ClassDefItem} instances that define those classes
+     */
+    private final TreeMap<Type, ClassDefItem> classDefs;
+
+    /** {@code null-ok;} ordered list of classes; set in {@link #orderItems} */
+    private ArrayList<ClassDefItem> orderedDefs;
+
+    /**
+     * Constructs an instance. The file offset is initially unknown.
+     *
+     * @param file {@code non-null;} file that this instance is part of
+     */
+    public ClassDefsSection(DexFile file) {
+        super("class_defs", file, 4);
+
+        classDefs = new TreeMap<Type, ClassDefItem>();
+        orderedDefs = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection<? extends Item> items() {
+        if (orderedDefs != null) {
+            return orderedDefs;
+        }
+
+        return classDefs.values();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public IndexedItem get(Constant cst) {
+        if (cst == null) {
+            throw new NullPointerException("cst == null");
+        }
+
+        throwIfNotPrepared();
+
+        Type type = ((CstType) cst).getClassType();
+        IndexedItem result = classDefs.get(type);
+
+        if (result == null) {
+            throw new IllegalArgumentException("not found");
+        }
+
+        return result;
+    }
+
+    /**
+     * Writes the portion of the file header that refers to this instance.
+     *
+     * @param out {@code non-null;} where to write
+     */
+    public void writeHeaderPart(AnnotatedOutput out) {
+        throwIfNotPrepared();
+
+        int sz = classDefs.size();
+        int offset = (sz == 0) ? 0 : getFileOffset();
+
+        if (out.annotates()) {
+            out.annotate(4, "class_defs_size: " + Hex.u4(sz));
+            out.annotate(4, "class_defs_off:  " + Hex.u4(offset));
+        }
+
+        out.writeInt(sz);
+        out.writeInt(offset);
+    }
+
+    /**
+     * Adds an element to this instance. It is illegal to attempt to add more
+     * than one class with the same name.
+     *
+     * @param clazz {@code non-null;} the class def to add
+     */
+    public void add(ClassDefItem clazz) {
+        Type type;
+
+        try {
+            type = clazz.getThisClass().getClassType();
+        } catch (NullPointerException ex) {
+            // Elucidate the exception.
+            throw new NullPointerException("clazz == null");
+        }
+
+        throwIfPrepared();
+
+        if (classDefs.get(type) != null) {
+            throw new IllegalArgumentException("already added: " + type);
+        }
+
+        classDefs.put(type, clazz);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void orderItems() {
+        int sz = classDefs.size();
+        int idx = 0;
+
+        orderedDefs = new ArrayList<ClassDefItem>(sz);
+
+        /*
+         * Iterate over all the classes, recursively assigning an
+         * index to each, implicitly skipping the ones that have
+         * already been assigned by the time this (top-level)
+         * iteration reaches them.
+         */
+        for (Type type : classDefs.keySet()) {
+            idx = orderItems0(type, idx, sz - idx);
+        }
+    }
+
+    /**
+     * Helper for {@link #orderItems}, which recursively assigns indices
+     * to classes.
+     *
+     * @param type {@code null-ok;} type ref to assign, if any
+     * @param idx {@code >= 0;} the next index to assign
+     * @param maxDepth maximum recursion depth; if negative, this will
+     * throw an exception indicating class definition circularity
+     * @return {@code >= 0;} the next index to assign
+     */
+    private int orderItems0(Type type, int idx, int maxDepth) {
+        ClassDefItem c = classDefs.get(type);
+
+        if ((c == null) || (c.hasIndex())) {
+            return idx;
+        }
+
+        if (maxDepth < 0) {
+            throw new RuntimeException("class circularity with " + type);
+        }
+
+        maxDepth--;
+
+        CstType superclassCst = c.getSuperclass();
+        if (superclassCst != null) {
+            Type superclass = superclassCst.getClassType();
+            idx = orderItems0(superclass, idx, maxDepth);
+        }
+
+        TypeList interfaces = c.getInterfaces();
+        int sz = interfaces.size();
+        for (int i = 0; i < sz; i++) {
+            idx = orderItems0(interfaces.getType(i), idx, maxDepth);
+        }
+
+        c.setIndex(idx);
+        orderedDefs.add(c);
+        return idx + 1;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/CodeItem.java b/dexgen/src/com/android/dexgen/dex/file/CodeItem.java
new file mode 100644
index 0000000..1b305c7
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/CodeItem.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.dex.code.CatchTable;
+import com.android.dexgen.dex.code.CstInsn;
+import com.android.dexgen.dex.code.DalvCode;
+import com.android.dexgen.dex.code.DalvInsn;
+import com.android.dexgen.dex.code.DalvInsnList;
+import com.android.dexgen.dex.code.LocalList;
+import com.android.dexgen.dex.code.PositionList;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstMemberRef;
+import com.android.dexgen.rop.cst.CstMethodRef;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeList;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.ExceptionWithContext;
+import com.android.dexgen.util.Hex;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+
+/**
+ * Representation of all the parts needed for concrete methods in a
+ * {@code dex} file.
+ */
+public final class CodeItem extends OffsettedItem {
+    /** file alignment of this class, in bytes */
+    private static final int ALIGNMENT = 4;
+
+    /** write size of the header of this class, in bytes */
+    private static final int HEADER_SIZE = 16;
+
+    /** {@code non-null;} method that this code implements */
+    private final CstMethodRef ref;
+
+    /** {@code non-null;} the bytecode instructions and associated data */
+    private final DalvCode code;
+
+    /** {@code null-ok;} the catches, if needed; set in {@link #addContents} */
+    private CatchStructs catches;
+
+    /** whether this instance is for a {@code static} method */
+    private final boolean isStatic;
+
+    /**
+     * {@code non-null;} list of possibly-thrown exceptions; just used in
+     * generating debugging output (listings)
+     */
+    private final TypeList throwsList;
+
+    /**
+     * {@code null-ok;} the debug info or {@code null} if there is none;
+     * set in {@link #addContents}
+     */
+    private DebugInfoItem debugInfo;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param ref {@code non-null;} method that this code implements
+     * @param code {@code non-null;} the underlying code
+     * @param isStatic whether this instance is for a {@code static}
+     * method
+     * @param throwsList {@code non-null;} list of possibly-thrown exceptions,
+     * just used in generating debugging output (listings)
+     */
+    public CodeItem(CstMethodRef ref, DalvCode code, boolean isStatic,
+            TypeList throwsList) {
+        super(ALIGNMENT, -1);
+
+        if (ref == null) {
+            throw new NullPointerException("ref == null");
+        }
+
+        if (code == null) {
+            throw new NullPointerException("code == null");
+        }
+
+        if (throwsList == null) {
+            throw new NullPointerException("throwsList == null");
+        }
+
+        this.ref = ref;
+        this.code = code;
+        this.isStatic = isStatic;
+        this.throwsList = throwsList;
+        this.catches = null;
+        this.debugInfo = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_CODE_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    public void addContents(DexFile file) {
+        MixedItemSection byteData = file.getByteData();
+        TypeIdsSection typeIds = file.getTypeIds();
+
+        if (code.hasPositions() || code.hasLocals()) {
+            debugInfo = new DebugInfoItem(code, isStatic, ref);
+            byteData.add(debugInfo);
+        }
+
+        if (code.hasAnyCatches()) {
+            for (Type type : code.getCatchTypes()) {
+                typeIds.intern(type);
+            }
+            catches = new CatchStructs(code);
+        }
+
+        for (Constant c : code.getInsnConstants()) {
+            file.internIfAppropriate(c);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "CodeItem{" + toHuman() + "}";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toHuman() {
+        return ref.toHuman();
+    }
+
+    /**
+     * Gets the reference to the method this instance implements.
+     *
+     * @return {@code non-null;} the method reference
+     */
+    public CstMethodRef getRef() {
+        return ref;
+    }
+
+    /**
+     * Does a human-friendly dump of this instance.
+     *
+     * @param out {@code non-null;} where to dump
+     * @param prefix {@code non-null;} per-line prefix to use
+     * @param verbose whether to be verbose with the output
+     */
+    public void debugPrint(PrintWriter out, String prefix, boolean verbose) {
+        out.println(ref.toHuman() + ":");
+
+        DalvInsnList insns = code.getInsns();
+        out.println("regs: " + Hex.u2(getRegistersSize()) +
+                "; ins: " + Hex.u2(getInsSize()) + "; outs: " +
+                Hex.u2(getOutsSize()));
+
+        insns.debugPrint(out, prefix, verbose);
+
+        String prefix2 = prefix + "  ";
+
+        if (catches != null) {
+            out.print(prefix);
+            out.println("catches");
+            catches.debugPrint(out, prefix2);
+        }
+
+        if (debugInfo != null) {
+            out.print(prefix);
+            out.println("debug info");
+            debugInfo.debugPrint(out, prefix2);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void place0(Section addedTo, int offset) {
+        final DexFile file = addedTo.getFile();
+        int catchesSize;
+
+        /*
+         * In order to get the catches and insns, all the code's
+         * constants need to be assigned indices.
+         */
+        code.assignIndices(new DalvCode.AssignIndicesCallback() {
+                public int getIndex(Constant cst) {
+                    IndexedItem item = file.findItemOrNull(cst);
+                    if (item == null) {
+                        return -1;
+                    }
+                    return item.getIndex();
+                }
+            });
+
+        if (catches != null) {
+            catches.encode(file);
+            catchesSize = catches.writeSize();
+        } else {
+            catchesSize = 0;
+        }
+
+        /*
+         * The write size includes the header, two bytes per code
+         * unit, post-code padding if necessary, and however much
+         * space the catches need.
+         */
+
+        int insnsSize = code.getInsns().codeSize();
+        if ((insnsSize & 1) != 0) {
+            insnsSize++;
+        }
+
+        setWriteSize(HEADER_SIZE + (insnsSize * 2) + catchesSize);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void writeTo0(DexFile file, AnnotatedOutput out) {
+        boolean annotates = out.annotates();
+        int regSz = getRegistersSize();
+        int outsSz = getOutsSize();
+        int insSz = getInsSize();
+        int insnsSz = code.getInsns().codeSize();
+        boolean needPadding = (insnsSz & 1) != 0;
+        int triesSz = (catches == null) ? 0 : catches.triesSize();
+        int debugOff = (debugInfo == null) ? 0 : debugInfo.getAbsoluteOffset();
+
+        if (annotates) {
+            out.annotate(0, offsetString() + ' ' + ref.toHuman());
+            out.annotate(2, "  registers_size: " + Hex.u2(regSz));
+            out.annotate(2, "  ins_size:       " + Hex.u2(insSz));
+            out.annotate(2, "  outs_size:      " + Hex.u2(outsSz));
+            out.annotate(2, "  tries_size:     " + Hex.u2(triesSz));
+            out.annotate(4, "  debug_off:      " + Hex.u4(debugOff));
+            out.annotate(4, "  insns_size:     " + Hex.u4(insnsSz));
+
+            // This isn't represented directly here, but it is useful to see.
+            int size = throwsList.size();
+            if (size != 0) {
+                out.annotate(0, "  throws " + StdTypeList.toHuman(throwsList));
+            }
+        }
+
+        out.writeShort(regSz);
+        out.writeShort(insSz);
+        out.writeShort(outsSz);
+        out.writeShort(triesSz);
+        out.writeInt(debugOff);
+        out.writeInt(insnsSz);
+
+        writeCodes(file, out);
+
+        if (catches != null) {
+            if (needPadding) {
+                if (annotates) {
+                    out.annotate(2, "  padding: 0");
+                }
+                out.writeShort(0);
+            }
+
+            catches.writeTo(file, out);
+        }
+
+        if (annotates) {
+            /*
+             * These are pointed at in the code header (above), but it's less
+             * distracting to expand on them at the bottom of the code.
+             */
+            if (debugInfo != null) {
+                out.annotate(0, "  debug info");
+                debugInfo.annotateTo(file, out, "    ");
+            }
+        }
+    }
+
+    /**
+     * Helper for {@link #writeTo0} which writes out the actual bytecode.
+     *
+     * @param file {@code non-null;} file we are part of
+     * @param out {@code non-null;} where to write to
+     */
+    private void writeCodes(DexFile file, AnnotatedOutput out) {
+        DalvInsnList insns = code.getInsns();
+
+        try {
+            insns.writeTo(out);
+        } catch (RuntimeException ex) {
+            throw ExceptionWithContext.withContext(ex, "...while writing " +
+                    "instructions for " + ref.toHuman());
+        }
+    }
+
+    /**
+     * Get the in registers count.
+     *
+     * @return the count
+     */
+    private int getInsSize() {
+        return ref.getParameterWordCount(isStatic);
+    }
+
+    /**
+     * Get the out registers count.
+     *
+     * @return the count
+     */
+    private int getOutsSize() {
+        return code.getInsns().getOutsSize();
+    }
+
+    /**
+     * Get the total registers count.
+     *
+     * @return the count
+     */
+    private int getRegistersSize() {
+        return code.getInsns().getRegistersSize();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/DebugInfoConstants.java b/dexgen/src/com/android/dexgen/dex/file/DebugInfoConstants.java
new file mode 100644
index 0000000..780f350
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/DebugInfoConstants.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+/**
+ * Constants for the dex debug info state machine format.
+ */
+public interface DebugInfoConstants {
+
+    /*
+     * normal opcodes
+     */
+
+    /**
+     * Terminates a debug info sequence for a method.<p>
+     * Args: none
+     *
+     */
+    static final int DBG_END_SEQUENCE = 0x00;
+
+    /**
+     * Advances the program counter/address register without emitting
+     * a positions entry.<p>
+     *
+     * Args:
+     * <ol>
+     * <li>Unsigned LEB128 &mdash; amount to advance pc by
+     * </ol>
+     */
+    static final int DBG_ADVANCE_PC = 0x01;
+
+    /**
+     * Advances the line register without emitting
+     * a positions entry.<p>
+     *
+     * Args:
+     * <ol>
+     * <li>Signed LEB128 &mdash; amount to change line register by.
+     * </ol>
+     */
+    static final int DBG_ADVANCE_LINE = 0x02;
+
+    /**
+     * Introduces a local variable at the current address.<p>
+     *
+     * Args:
+     * <ol>
+     * <li>Unsigned LEB128 &mdash; register that will contain local.
+     * <li>Unsigned LEB128 &mdash; string index (shifted by 1) of local name.
+     * <li>Unsigned LEB128 &mdash; type index (shifted by 1) of type.
+     * </ol>
+     */
+    static final int DBG_START_LOCAL = 0x03;
+
+    /**
+     * Introduces a local variable at the current address with a type
+     * signature specified.<p>
+     *
+     * Args:
+     * <ol>
+     * <li>Unsigned LEB128 &mdash; register that will contain local.
+     * <li>Unsigned LEB128 &mdash; string index (shifted by 1) of local name.
+     * <li>Unsigned LEB128 &mdash; type index (shifted by 1) of type.
+     * <li>Unsigned LEB128 &mdash; string index (shifted by 1) of
+     * type signature.
+     * </ol>
+     */
+    static final int DBG_START_LOCAL_EXTENDED = 0x04;
+
+    /**
+     * Marks a currently-live local variable as out of scope at the
+     * current address.<p>
+     *
+     * Args:
+     * <ol>
+     * <li>Unsigned LEB128 &mdash; register that contained local
+     * </ol>
+     */
+    static final int DBG_END_LOCAL = 0x05;
+
+    /**
+     * Re-introduces a local variable at the current address. The name
+     * and type are the same as the last local that was live in the specified
+     * register.<p>
+     *
+     * Args:
+     * <ol>
+     * <li>Unsigned LEB128 &mdash; register to re-start.
+     * </ol>
+     */
+    static final int DBG_RESTART_LOCAL = 0x06;
+
+
+    /**
+     * Sets the "prologue_end" state machine register, indicating that the
+     * next position entry that is added should be considered the end of
+     * a method prologue (an appropriate place for a method breakpoint).<p>
+     *
+     * The prologue_end register is cleared by any special
+     * ({@code >= OPCODE_BASE}) opcode.
+     */
+    static final int DBG_SET_PROLOGUE_END = 0x07;
+
+    /**
+     * Sets the "epilogue_begin" state machine register, indicating that the
+     * next position entry that is added should be considered the beginning of
+     * a method epilogue (an appropriate place to suspend execution before
+     * method exit).<p>
+     *
+     * The epilogue_begin register is cleared by any special
+     * ({@code >= OPCODE_BASE}) opcode.
+     */
+    static final int DBG_SET_EPILOGUE_BEGIN = 0x08;
+
+    /**
+     * Sets the current file that that line numbers refer to. All subsequent
+     * line number entries make reference to this source file name, instead
+     * of the default name specified in code_item.
+     *
+     * Args:
+     * <ol>
+     * <li>Unsigned LEB128 &mdash; string index (shifted by 1) of source
+     * file name.
+     * </ol>
+     */
+    static final int DBG_SET_FILE = 0x09;
+
+    /* IF YOU ADD A NEW OPCODE, increase OPCODE_BASE */
+
+    /*
+     * "special opcode" configuration, essentially what's found in
+     * the line number program header in DWARFv3, Section 6.2.4
+     */
+
+    /** the smallest value a special opcode can take */
+    static final int DBG_FIRST_SPECIAL = 0x0a;
+    static final int DBG_LINE_BASE = -4;
+    static final int DBG_LINE_RANGE = 15;
+    // MIN_INSN_LENGTH is always 1
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/DebugInfoDecoder.java b/dexgen/src/com/android/dexgen/dex/file/DebugInfoDecoder.java
new file mode 100644
index 0000000..da614d2
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/DebugInfoDecoder.java
@@ -0,0 +1,653 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.dex.code.DalvCode;
+import com.android.dexgen.dex.code.DalvInsnList;
+import com.android.dexgen.dex.code.LocalList;
+import com.android.dexgen.dex.code.PositionList;
+import com.android.dexgen.rop.cst.CstMethodRef;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.rop.type.Prototype;
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.ExceptionWithContext;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.android.dexgen.dex.file.DebugInfoConstants.*;
+
+/**
+ * A decoder for the dex debug info state machine format.
+ * This code exists mostly as a reference implementation and test for
+ * for the {@code DebugInfoEncoder}
+ */
+public class DebugInfoDecoder {
+    /** encoded debug info */
+    private final byte[] encoded;
+
+    /** positions decoded */
+    private final ArrayList<PositionEntry> positions;
+
+    /** locals decoded */
+    private final ArrayList<LocalEntry> locals;
+
+    /** size of code block in code units */
+    private final int codesize;
+
+    /** indexed by register, the last local variable live in a reg */
+    private final LocalEntry[] lastEntryForReg;
+
+    /** method descriptor of method this debug info is for */
+    private final Prototype desc;
+
+    /** true if method is static */
+    private final boolean isStatic;
+
+    /** dex file this debug info will be stored in */
+    private final DexFile file;
+
+    /**
+     * register size, in register units, of the register space
+     * used by this method
+     */
+    private final int regSize;
+
+    /** current decoding state: line number */
+    private int line = 1;
+
+    /** current decoding state: bytecode address */
+    private int address = 0;
+
+    /** string index of the string "this" */
+    private final int thisStringIdx;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param encoded encoded debug info
+     * @param codesize size of code block in code units
+     * @param regSize register size, in register units, of the register space
+     * used by this method
+     * @param isStatic true if method is static
+     * @param ref method descriptor of method this debug info is for
+     * @param file dex file this debug info will be stored in
+     */
+    DebugInfoDecoder(byte[] encoded, int codesize, int regSize,
+            boolean isStatic, CstMethodRef ref, DexFile file) {
+        if (encoded == null) {
+            throw new NullPointerException("encoded == null");
+        }
+
+        this.encoded = encoded;
+        this.isStatic = isStatic;
+        this.desc = ref.getPrototype();
+        this.file = file;
+        this.regSize = regSize;
+
+        positions = new ArrayList<PositionEntry>();
+        locals = new ArrayList<LocalEntry>();
+        this.codesize = codesize;
+        lastEntryForReg = new LocalEntry[regSize];
+
+        int idx = -1;
+
+        try {
+            idx = file.getStringIds().indexOf(new CstUtf8("this"));
+        } catch (IllegalArgumentException ex) {
+            /*
+             * Silently tolerate not finding "this". It just means that
+             * no method has local variable info that looks like
+             * a standard instance method.
+             */
+        }
+
+        thisStringIdx = idx;
+    }
+
+    /**
+     * An entry in the resulting postions table
+     */
+    static private class PositionEntry {
+        /** bytecode address */
+        public int address;
+
+        /** line number */
+        public int line;
+
+        public PositionEntry(int address, int line) {
+            this.address = address;
+            this.line = line;
+        }
+    }
+
+    /**
+     * An entry in the resulting locals table
+     */
+    static private class LocalEntry {
+        /** address of event */
+        public int address;
+
+        /** {@code true} iff it's a local start */
+        public boolean isStart;
+
+        /** register number */
+        public int reg;
+
+        /** index of name in strings table */
+        public int nameIndex;
+
+        /** index of type in types table */
+        public int typeIndex;
+
+        /** index of type signature in strings table */
+        public int signatureIndex;
+
+        public LocalEntry(int address, boolean isStart, int reg, int nameIndex,
+                int typeIndex, int signatureIndex) {
+            this.address        = address;
+            this.isStart        = isStart;
+            this.reg            = reg;
+            this.nameIndex      = nameIndex;
+            this.typeIndex      = typeIndex;
+            this.signatureIndex = signatureIndex;
+        }
+
+        public String toString() {
+            return String.format("[%x %s v%d %04x %04x %04x]",
+                    address, isStart ? "start" : "end", reg,
+                    nameIndex, typeIndex, signatureIndex);
+        }
+    }
+
+    /**
+     * Gets the decoded positions list.
+     * Valid after calling {@code decode}.
+     *
+     * @return positions list in ascending address order.
+     */
+    public List<PositionEntry> getPositionList() {
+        return positions;
+    }
+
+    /**
+     * Gets the decoded locals list, in ascending start-address order.
+     * Valid after calling {@code decode}.
+     *
+     * @return locals list in ascending address order.
+     */
+    public List<LocalEntry> getLocals() {
+        return locals;
+    }
+
+    /**
+     * Decodes the debug info sequence.
+     */
+    public void decode() {
+        try {
+            decode0();
+        } catch (Exception ex) {
+            throw ExceptionWithContext.withContext(ex,
+                    "...while decoding debug info");
+        }
+    }
+
+    /**
+     * Reads a string index. String indicies are offset by 1, and a 0 value
+     * in the stream (-1 as returned by this method) means "null"
+     *
+     * @param bs
+     * @return index into file's string ids table, -1 means null
+     * @throws IOException
+     */
+    private int readStringIndex(InputStream bs) throws IOException {
+        int offsetIndex = readUnsignedLeb128(bs);
+
+        return offsetIndex - 1;
+    }
+
+    /**
+     * Gets the register that begins the method's parameter range (including
+     * the 'this' parameter for non-static methods). The range continues until
+     * {@code regSize}
+     *
+     * @return register as noted above.
+     */
+    private int getParamBase() {
+        return regSize
+                - desc.getParameterTypes().getWordCount() - (isStatic? 0 : 1);
+    }
+
+    private void decode0() throws IOException {
+        ByteArrayInputStream bs = new ByteArrayInputStream(encoded);
+
+        line = readUnsignedLeb128(bs);
+        int szParams = readUnsignedLeb128(bs);
+        StdTypeList params = desc.getParameterTypes();
+        int curReg = getParamBase();
+
+        if (szParams != params.size()) {
+            throw new RuntimeException(
+                    "Mismatch between parameters_size and prototype");
+        }
+
+        if (!isStatic) {
+            // Start off with implicit 'this' entry
+            LocalEntry thisEntry =
+                new LocalEntry(0, true, curReg, thisStringIdx, 0, 0);
+            locals.add(thisEntry);
+            lastEntryForReg[curReg] = thisEntry;
+            curReg++;
+        }
+
+        for (int i = 0; i < szParams; i++) {
+            Type paramType = params.getType(i);
+            LocalEntry le;
+
+            int nameIdx = readStringIndex(bs);
+
+            if (nameIdx == -1) {
+                /*
+                 * Unnamed parameter; often but not always filled in by an
+                 * extended start op after the prologue
+                 */
+                le = new LocalEntry(0, true, curReg, -1, 0, 0);
+            } else {
+                // TODO: Final 0 should be idx of paramType.getDescriptor().
+                le = new LocalEntry(0, true, curReg, nameIdx, 0, 0);
+            }
+
+            locals.add(le);
+            lastEntryForReg[curReg] = le;
+            curReg += paramType.getCategory();
+        }
+
+        for (;;) {
+            int opcode = bs.read();
+
+            if (opcode < 0) {
+                throw new RuntimeException
+                        ("Reached end of debug stream without "
+                                + "encountering end marker");
+            }
+
+            switch (opcode) {
+                case DBG_START_LOCAL: {
+                    int reg = readUnsignedLeb128(bs);
+                    int nameIdx = readStringIndex(bs);
+                    int typeIdx = readStringIndex(bs);
+                    LocalEntry le = new LocalEntry(
+                            address, true, reg, nameIdx, typeIdx, 0);
+
+                    locals.add(le);
+                    lastEntryForReg[reg] = le;
+                }
+                break;
+
+                case DBG_START_LOCAL_EXTENDED: {
+                    int reg = readUnsignedLeb128(bs);
+                    int nameIdx = readStringIndex(bs);
+                    int typeIdx = readStringIndex(bs);
+                    int sigIdx = readStringIndex(bs);
+                    LocalEntry le = new LocalEntry(
+                            address, true, reg, nameIdx, typeIdx, sigIdx);
+
+                    locals.add(le);
+                    lastEntryForReg[reg] = le;
+                }
+                break;
+
+                case DBG_RESTART_LOCAL: {
+                    int reg = readUnsignedLeb128(bs);
+                    LocalEntry prevle;
+                    LocalEntry le;
+
+                    try {
+                        prevle = lastEntryForReg[reg];
+
+                        if (prevle.isStart) {
+                            throw new RuntimeException("nonsensical "
+                                    + "RESTART_LOCAL on live register v"
+                                    + reg);
+                        }
+
+                        le = new LocalEntry(address, true, reg,
+                                prevle.nameIndex, prevle.typeIndex, 0);
+                    } catch (NullPointerException ex) {
+                        throw new RuntimeException(
+                                "Encountered RESTART_LOCAL on new v" + reg);
+                    }
+
+                    locals.add(le);
+                    lastEntryForReg[reg] = le;
+                }
+                break;
+
+                case DBG_END_LOCAL: {
+                    int reg = readUnsignedLeb128(bs);
+                    LocalEntry prevle;
+                    LocalEntry le;
+
+                    try {
+                        prevle = lastEntryForReg[reg];
+
+                        if (!prevle.isStart) {
+                            throw new RuntimeException("nonsensical "
+                                    + "END_LOCAL on dead register v" + reg);
+                        }
+
+                        le = new LocalEntry(address, false, reg,
+                                prevle.nameIndex, prevle.typeIndex,
+                                prevle.signatureIndex);
+                    } catch (NullPointerException ex) {
+                        throw new RuntimeException(
+                                "Encountered END_LOCAL on new v" + reg);
+                    }
+
+                    locals.add(le);
+                    lastEntryForReg[reg] = le;
+                }
+                break;
+
+                case DBG_END_SEQUENCE:
+                    // all done
+                return;
+
+                case DBG_ADVANCE_PC:
+                    address += readUnsignedLeb128(bs);
+                break;
+
+                case DBG_ADVANCE_LINE:
+                    line += readSignedLeb128(bs);
+                break;
+
+                case DBG_SET_PROLOGUE_END:
+                    //TODO do something with this.
+                break;
+
+                case DBG_SET_EPILOGUE_BEGIN:
+                    //TODO do something with this.
+                break;
+
+                case DBG_SET_FILE:
+                    //TODO do something with this.
+                break;
+
+                default:
+                    if (opcode < DBG_FIRST_SPECIAL) {
+                        throw new RuntimeException(
+                                "Invalid extended opcode encountered "
+                                        + opcode);
+                    }
+
+                    int adjopcode = opcode - DBG_FIRST_SPECIAL;
+
+                    address += adjopcode / DBG_LINE_RANGE;
+                    line += DBG_LINE_BASE + (adjopcode % DBG_LINE_RANGE);
+
+                    positions.add(new PositionEntry(address, line));
+                break;
+
+            }
+        }
+    }
+
+    /**
+     * Validates an encoded debug info stream against data used to encode it,
+     * throwing an exception if they do not match. Used to validate the
+     * encoder.
+     *
+     * @param info encoded debug info
+     * @param file {@code non-null;} file to refer to during decoding
+     * @param ref {@code non-null;} method whose info is being decoded
+     * @param code {@code non-null;} original code object that was encoded
+     * @param isStatic whether the method is static
+     */
+    public static void validateEncode(byte[] info, DexFile file,
+            CstMethodRef ref, DalvCode code, boolean isStatic) {
+        PositionList pl = code.getPositions();
+        LocalList ll = code.getLocals();
+        DalvInsnList insns = code.getInsns();
+        int codeSize = insns.codeSize();
+        int countRegisters = insns.getRegistersSize();
+
+        try {
+            validateEncode0(info, codeSize, countRegisters,
+                    isStatic, ref, file, pl, ll);
+        } catch (RuntimeException ex) {
+            System.err.println("instructions:");
+            insns.debugPrint(System.err, "  ", true);
+            System.err.println("local list:");
+            ll.debugPrint(System.err, "  ");
+            throw ExceptionWithContext.withContext(ex,
+                    "while processing " + ref.toHuman());
+        }
+    }
+
+    private static void validateEncode0(byte[] info, int codeSize,
+            int countRegisters, boolean isStatic, CstMethodRef ref,
+            DexFile file, PositionList pl, LocalList ll) {
+        DebugInfoDecoder decoder
+                = new DebugInfoDecoder(info, codeSize, countRegisters,
+                    isStatic, ref, file);
+
+        decoder.decode();
+
+        /*
+         * Go through the decoded position entries, matching up
+         * with original entries.
+         */
+
+        List<PositionEntry> decodedEntries = decoder.getPositionList();
+
+        if (decodedEntries.size() != pl.size()) {
+            throw new RuntimeException(
+                    "Decoded positions table not same size was "
+                    + decodedEntries.size() + " expected " + pl.size());
+        }
+
+        for (PositionEntry entry : decodedEntries) {
+            boolean found = false;
+            for (int i = pl.size() - 1; i >= 0; i--) {
+                PositionList.Entry ple = pl.get(i);
+
+                if (entry.line == ple.getPosition().getLine()
+                        && entry.address == ple.getAddress()) {
+                    found = true;
+                    break;
+                }
+            }
+
+            if (!found) {
+                throw new RuntimeException ("Could not match position entry: "
+                        + entry.address + ", " + entry.line);
+            }
+        }
+
+        /*
+         * Go through the original local list, in order, matching up
+         * with decoded entries.
+         */
+
+        List<LocalEntry> decodedLocals = decoder.getLocals();
+        int thisStringIdx = decoder.thisStringIdx;
+        int decodedSz = decodedLocals.size();
+        int paramBase = decoder.getParamBase();
+
+        /*
+         * Preflight to fill in any parameters that were skipped in
+         * the prologue (including an implied "this") but then
+         * identified by full signature.
+         */
+        for (int i = 0; i < decodedSz; i++) {
+            LocalEntry entry = decodedLocals.get(i);
+            int idx = entry.nameIndex;
+
+            if ((idx < 0) || (idx == thisStringIdx)) {
+                for (int j = i + 1; j < decodedSz; j++) {
+                    LocalEntry e2 = decodedLocals.get(j);
+                    if (e2.address != 0) {
+                        break;
+                    }
+                    if ((entry.reg == e2.reg) && e2.isStart) {
+                        decodedLocals.set(i, e2);
+                        decodedLocals.remove(j);
+                        decodedSz--;
+                        break;
+                    }
+                }
+            }
+        }
+
+        int origSz = ll.size();
+        int decodeAt = 0;
+        boolean problem = false;
+
+        for (int i = 0; i < origSz; i++) {
+            LocalList.Entry origEntry = ll.get(i);
+
+            if (origEntry.getDisposition()
+                    == LocalList.Disposition.END_REPLACED) {
+                /*
+                 * The encoded list doesn't represent replacements, so
+                 * ignore them for the sake of comparison.
+                 */
+                continue;
+            }
+
+            LocalEntry decodedEntry;
+
+            do {
+                decodedEntry = decodedLocals.get(decodeAt);
+                if (decodedEntry.nameIndex >= 0) {
+                    break;
+                }
+                /*
+                 * A negative name index means this is an anonymous
+                 * parameter, and we shouldn't expect to see it in the
+                 * original list. So, skip it.
+                 */
+                decodeAt++;
+            } while (decodeAt < decodedSz);
+
+            int decodedAddress = decodedEntry.address;
+
+            if (decodedEntry.reg != origEntry.getRegister()) {
+                System.err.println("local register mismatch at orig " + i +
+                        " / decoded " + decodeAt);
+                problem = true;
+                break;
+            }
+
+            if (decodedEntry.isStart != origEntry.isStart()) {
+                System.err.println("local start/end mismatch at orig " + i +
+                        " / decoded " + decodeAt);
+                problem = true;
+                break;
+            }
+
+            /*
+             * The secondary check here accounts for the fact that a
+             * parameter might not be marked as starting at 0 in the
+             * original list.
+             */
+            if ((decodedAddress != origEntry.getAddress())
+                    && !((decodedAddress == 0)
+                            && (decodedEntry.reg >= paramBase))) {
+                System.err.println("local address mismatch at orig " + i +
+                        " / decoded " + decodeAt);
+                problem = true;
+                break;
+            }
+
+            decodeAt++;
+        }
+
+        if (problem) {
+            System.err.println("decoded locals:");
+            for (LocalEntry e : decodedLocals) {
+                System.err.println("  " + e);
+            }
+            throw new RuntimeException("local table problem");
+        }
+    }
+
+    /**
+     * Reads a DWARFv3-style signed LEB128 integer to the specified stream.
+     * See DWARF v3 section 7.6. An invalid sequence produces an IOException.
+     *
+     * @param bs stream to input from
+     * @return read value
+     * @throws IOException on invalid sequence in addition to
+     * those caused by the InputStream
+     */
+    public static int readSignedLeb128(InputStream bs) throws IOException {
+        int result = 0;
+        int cur;
+        int count = 0;
+        int signBits = -1;
+
+        do {
+            cur = bs.read();
+            result |= (cur & 0x7f) << (count * 7);
+            signBits <<= 7;
+            count++;
+        } while (((cur & 0x80) == 0x80) && count < 5);
+
+        if ((cur & 0x80) == 0x80) {
+            throw new IOException ("invalid LEB128 sequence");
+        }
+
+        // Sign extend if appropriate
+        if (((signBits >> 1) & result) != 0 ) {
+            result |= signBits;
+        }
+
+        return result;
+    }
+
+    /**
+     * Reads a DWARFv3-style unsigned LEB128 integer to the specified stream.
+     * See DWARF v3 section 7.6. An invalid sequence produces an IOException.
+     *
+     * @param bs stream to input from
+     * @return read value, which should be treated as an unsigned value.
+     * @throws IOException on invalid sequence in addition to
+     * those caused by the InputStream
+     */
+    public static int readUnsignedLeb128(InputStream bs) throws IOException {
+        int result = 0;
+        int cur;
+        int count = 0;
+
+        do {
+            cur = bs.read();
+            result |= (cur & 0x7f) << (count * 7);
+            count++;
+        } while (((cur & 0x80) == 0x80) && count < 5);
+
+        if ((cur & 0x80) == 0x80) {
+            throw new IOException ("invalid LEB128 sequence");
+        }
+
+        return result;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/DebugInfoEncoder.java b/dexgen/src/com/android/dexgen/dex/file/DebugInfoEncoder.java
new file mode 100644
index 0000000..663de7e
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/DebugInfoEncoder.java
@@ -0,0 +1,920 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.dex.code.LocalList;
+import com.android.dexgen.dex.code.PositionList;
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.SourcePosition;
+import com.android.dexgen.rop.cst.CstMethodRef;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.rop.type.Prototype;
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.ByteArrayAnnotatedOutput;
+import com.android.dexgen.util.ExceptionWithContext;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.BitSet;
+
+import static com.android.dexgen.dex.file.DebugInfoConstants.*;
+
+/**
+ * An encoder for the dex debug info state machine format. The format
+ * for each method enrty is as follows:
+ * <ol>
+ * <li> signed LEB128: initial value for line register.
+ * <li> n instances of signed LEB128: string indicies (offset by 1)
+ * for each method argument in left-to-right order
+ * with {@code this} excluded. A value of '0' indicates "no name"
+ * <li> A sequence of special or normal opcodes as defined in
+ * {@code DebugInfoConstants}.
+ * <li> A single terminating {@code OP_END_SEQUENCE}
+ * </ol>
+ */
+public final class DebugInfoEncoder {
+    private static final boolean DEBUG = false;
+
+    /** {@code null-ok;} positions (line numbers) to encode */
+    private final PositionList positions;
+
+    /** {@code null-ok;} local variables to encode */
+    private final LocalList locals;
+
+    private final ByteArrayAnnotatedOutput output;
+    private final DexFile file;
+    private final int codeSize;
+    private final int regSize;
+
+    private final Prototype desc;
+    private final boolean isStatic;
+
+    /** current encoding state: bytecode address */
+    private int address = 0;
+
+    /** current encoding state: line number */
+    private int line = 1;
+
+    /**
+     * if non-null: the output to write annotations to. No normal
+     * output is written to this.
+     */
+    private AnnotatedOutput annotateTo;
+
+    /** if non-null: another possible output for annotations */
+    private PrintWriter debugPrint;
+
+    /** if non-null: the prefix for each annotation or debugPrint line */
+    private String prefix;
+
+    /** true if output should be consumed during annotation */
+    private boolean shouldConsume;
+
+    /** indexed by register; last local alive in register */
+    private final LocalList.Entry[] lastEntryForReg;
+
+    /**
+     * Creates an instance.
+     *
+     * @param positions {@code null-ok;} positions (line numbers) to encode
+     * @param locals {@code null-ok;} local variables to encode
+     * @param file {@code null-ok;} may only be {@code null} if simply using
+     * this class to do a debug print
+     * @param codeSize
+     * @param regSize
+     * @param isStatic
+     * @param ref
+     */
+    public DebugInfoEncoder(PositionList positions, LocalList locals,
+            DexFile file, int codeSize, int regSize,
+            boolean isStatic, CstMethodRef ref) {
+        this.positions = positions;
+        this.locals = locals;
+        this.file = file;
+        this.desc = ref.getPrototype();
+        this.isStatic = isStatic;
+        this.codeSize = codeSize;
+        this.regSize = regSize;
+
+        output = new ByteArrayAnnotatedOutput();
+        lastEntryForReg = new LocalList.Entry[regSize];
+    }
+
+    /**
+     * Annotates or writes a message to the {@code debugPrint} writer
+     * if applicable.
+     *
+     * @param length the number of bytes associated with this message
+     * @param message the message itself
+     */
+    private void annotate(int length, String message) {
+        if (prefix != null) {
+            message = prefix + message;
+        }
+
+        if (annotateTo != null) {
+            annotateTo.annotate(shouldConsume ? length : 0, message);
+        }
+
+        if (debugPrint != null) {
+            debugPrint.println(message);
+        }
+    }
+
+    /**
+     * Converts this (PositionList, LocalList) pair into a state machine
+     * sequence.
+     *
+     * @return {@code non-null;} encoded byte sequence without padding and
+     * terminated with a {@code 0x00} byte
+     */
+    public byte[] convert() {
+        try {
+            byte[] ret;
+            ret = convert0();
+
+            if (DEBUG) {
+                for (int i = 0 ; i < ret.length; i++) {
+                    System.err.printf("byte %02x\n", (0xff & ret[i]));
+                }
+            }
+
+            return ret;
+        } catch (IOException ex) {
+            throw ExceptionWithContext
+                    .withContext(ex, "...while encoding debug info");
+        }
+    }
+
+    /**
+     * Converts and produces annotations on a stream. Does not write
+     * actual bits to the {@code AnnotatedOutput}.
+     *
+     * @param prefix {@code null-ok;} prefix to attach to each line of output
+     * @param debugPrint {@code null-ok;} if specified, an alternate output for
+     * annotations
+     * @param out {@code null-ok;} if specified, where annotations should go
+     * @param consume whether to claim to have consumed output for
+     * {@code out}
+     * @return {@code non-null;} encoded output
+     */
+    public byte[] convertAndAnnotate(String prefix, PrintWriter debugPrint,
+            AnnotatedOutput out, boolean consume) {
+        this.prefix = prefix;
+        this.debugPrint = debugPrint;
+        annotateTo = out;
+        shouldConsume = consume;
+
+        byte[] result = convert();
+
+        return result;
+    }
+
+    private byte[] convert0() throws IOException {
+        ArrayList<PositionList.Entry> sortedPositions = buildSortedPositions();
+        ArrayList<LocalList.Entry> methodArgs = extractMethodArguments();
+
+        emitHeader(sortedPositions, methodArgs);
+
+        // TODO: Make this mark be the actual prologue end.
+        output.writeByte(DBG_SET_PROLOGUE_END);
+
+        if (annotateTo != null || debugPrint != null) {
+            annotate(1, String.format("%04x: prologue end",address));
+        }
+
+        int positionsSz = sortedPositions.size();
+        int localsSz = locals.size();
+
+        // Current index in sortedPositions
+        int curPositionIdx = 0;
+        // Current index in locals
+        int curLocalIdx = 0;
+
+        for (;;) {
+            /*
+             * Emit any information for the current address.
+             */
+
+            curLocalIdx = emitLocalsAtAddress(curLocalIdx);
+            curPositionIdx =
+                emitPositionsAtAddress(curPositionIdx, sortedPositions);
+
+            /*
+             * Figure out what the next important address is.
+             */
+
+            int nextAddrL = Integer.MAX_VALUE; // local variable
+            int nextAddrP = Integer.MAX_VALUE; // position (line number)
+
+            if (curLocalIdx < localsSz) {
+                nextAddrL = locals.get(curLocalIdx).getAddress();
+            }
+
+            if (curPositionIdx < positionsSz) {
+                nextAddrP = sortedPositions.get(curPositionIdx).getAddress();
+            }
+
+            int next = Math.min(nextAddrP, nextAddrL);
+
+            // No next important address == done.
+            if (next == Integer.MAX_VALUE) {
+                break;
+            }
+
+            /*
+             * If the only work remaining are local ends at the end of the
+             * block, stop here. Those are implied anyway.
+             */
+            if (next == codeSize
+                    && nextAddrL == Integer.MAX_VALUE
+                    && nextAddrP == Integer.MAX_VALUE) {
+                break;
+            }
+
+            if (next == nextAddrP) {
+                // Combined advance PC + position entry
+                emitPosition(sortedPositions.get(curPositionIdx++));
+            } else {
+                emitAdvancePc(next - address);
+            }
+        }
+
+        emitEndSequence();
+
+        return output.toByteArray();
+    }
+
+    /**
+     * Emits all local variable activity that occurs at the current
+     * {@link #address} starting at the given index into {@code
+     * locals} and including all subsequent activity at the same
+     * address.
+     *
+     * @param curLocalIdx Current index in locals
+     * @return new value for {@code curLocalIdx}
+     * @throws IOException
+     */
+    private int emitLocalsAtAddress(int curLocalIdx)
+            throws IOException {
+        int sz = locals.size();
+
+        // TODO: Don't emit ends implied by starts.
+
+        while ((curLocalIdx < sz)
+                && (locals.get(curLocalIdx).getAddress() == address)) {
+            LocalList.Entry entry = locals.get(curLocalIdx++);
+            int reg = entry.getRegister();
+            LocalList.Entry prevEntry = lastEntryForReg[reg];
+
+            if (entry == prevEntry) {
+                /*
+                 * Here we ignore locals entries for parameters,
+                 * which have already been represented and placed in the
+                 * lastEntryForReg array.
+                 */
+                continue;
+            }
+
+            // At this point we have a new entry one way or another.
+            lastEntryForReg[reg] = entry;
+
+            if (entry.isStart()) {
+                if ((prevEntry != null) && entry.matches(prevEntry)) {
+                    /*
+                     * The previous local in this register has the same
+                     * name and type as the one being introduced now, so
+                     * use the more efficient "restart" form.
+                     */
+                    if (prevEntry.isStart()) {
+                        /*
+                         * We should never be handed a start when a
+                         * a matching local is already active.
+                         */
+                        throw new RuntimeException("shouldn't happen");
+                    }
+                    emitLocalRestart(entry);
+                } else {
+                    emitLocalStart(entry);
+                }
+            } else {
+                /*
+                 * Only emit a local end if it is *not* due to a direct
+                 * replacement. Direct replacements imply an end of the
+                 * previous local in the same register.
+                 *
+                 * TODO: Make sure the runtime can deal with implied
+                 * local ends from category-2 interactions, and when so,
+                 * also stop emitting local ends for those cases.
+                 */
+                if (entry.getDisposition()
+                        != LocalList.Disposition.END_REPLACED) {
+                    emitLocalEnd(entry);
+                }
+            }
+        }
+
+        return curLocalIdx;
+    }
+
+    /**
+     * Emits all positions that occur at the current {@code address}
+     *
+     * @param curPositionIdx Current index in sortedPositions
+     * @param sortedPositions positions, sorted by ascending address
+     * @return new value for {@code curPositionIdx}
+     * @throws IOException
+     */
+    private int emitPositionsAtAddress(int curPositionIdx,
+            ArrayList<PositionList.Entry> sortedPositions)
+            throws IOException {
+        int positionsSz = sortedPositions.size();
+        while ((curPositionIdx < positionsSz)
+                && (sortedPositions.get(curPositionIdx).getAddress()
+                        == address)) {
+            emitPosition(sortedPositions.get(curPositionIdx++));
+        }
+        return curPositionIdx;
+    }
+
+    /**
+     * Emits the header sequence, which consists of LEB128-encoded initial
+     * line number and string indicies for names of all non-"this" arguments.
+     *
+     * @param sortedPositions positions, sorted by ascending address
+     * @param methodArgs local list entries for method argumens arguments,
+     * in left-to-right order omitting "this"
+     * @throws IOException
+     */
+    private void emitHeader(ArrayList<PositionList.Entry> sortedPositions,
+            ArrayList<LocalList.Entry> methodArgs) throws IOException {
+        boolean annotate = (annotateTo != null) || (debugPrint != null);
+        int mark = output.getCursor();
+
+        // Start by initializing the line number register.
+        if (sortedPositions.size() > 0) {
+            PositionList.Entry entry = sortedPositions.get(0);
+            line = entry.getPosition().getLine();
+        }
+        output.writeUnsignedLeb128(line);
+
+        if (annotate) {
+            annotate(output.getCursor() - mark, "line_start: " + line);
+        }
+
+        int curParam = getParamBase();
+        // paramTypes will not include 'this'
+        StdTypeList paramTypes = desc.getParameterTypes();
+        int szParamTypes = paramTypes.size();
+
+        /*
+         * Initialize lastEntryForReg to have an initial
+         * entry for the 'this' pointer.
+         */
+        if (!isStatic) {
+            for (LocalList.Entry arg : methodArgs) {
+                if (curParam == arg.getRegister()) {
+                    lastEntryForReg[curParam] = arg;
+                    break;
+                }
+            }
+            curParam++;
+        }
+
+        // Write out the number of parameter entries that will follow.
+        mark = output.getCursor();
+        output.writeUnsignedLeb128(szParamTypes);
+
+        if (annotate) {
+            annotate(output.getCursor() - mark,
+                    String.format("parameters_size: %04x", szParamTypes));
+        }
+
+        /*
+         * Then emit the string indicies of all the method parameters.
+         * Note that 'this', if applicable, is excluded.
+         */
+        for (int i = 0; i < szParamTypes; i++) {
+            Type pt = paramTypes.get(i);
+            LocalList.Entry found = null;
+
+            mark = output.getCursor();
+
+            for (LocalList.Entry arg : methodArgs) {
+                if (curParam == arg.getRegister()) {
+                    found = arg;
+
+                    if (arg.getSignature() != null) {
+                        /*
+                         * Parameters with signatures will be re-emitted
+                         * in complete as LOCAL_START_EXTENDED's below.
+                         */
+                        emitStringIndex(null);
+                    } else {
+                        emitStringIndex(arg.getName());
+                    }
+                    lastEntryForReg[curParam] = arg;
+
+                    break;
+                }
+            }
+
+            if (found == null) {
+                /*
+                 * Emit a null symbol for "unnamed." This is common
+                 * for, e.g., synthesized methods and inner-class
+                 * this$0 arguments.
+                 */
+                emitStringIndex(null);
+            }
+
+            if (annotate) {
+                String parameterName
+                        = (found == null || found.getSignature() != null)
+                                ? "<unnamed>" : found.getName().toHuman();
+                annotate(output.getCursor() - mark,
+                        "parameter " + parameterName + " "
+                                + RegisterSpec.PREFIX + curParam);
+            }
+
+            curParam += pt.getCategory();
+        }
+
+        /*
+         * If anything emitted above has a type signature, emit it again as
+         * a LOCAL_RESTART_EXTENDED
+         */
+
+        for (LocalList.Entry arg : lastEntryForReg) {
+            if (arg == null) {
+                continue;
+            }
+
+            CstUtf8 signature = arg.getSignature();
+
+            if (signature != null) {
+                emitLocalStartExtended(arg);
+            }
+        }
+    }
+
+    /**
+     * Builds a list of position entries, sorted by ascending address.
+     *
+     * @return A sorted positions list
+     */
+    private ArrayList<PositionList.Entry> buildSortedPositions() {
+        int sz = (positions == null) ? 0 : positions.size();
+        ArrayList<PositionList.Entry> result = new ArrayList(sz);
+
+        for (int i = 0; i < sz; i++) {
+            result.add(positions.get(i));
+        }
+
+        // Sort ascending by address.
+        Collections.sort (result, new Comparator<PositionList.Entry>() {
+            public int compare (PositionList.Entry a, PositionList.Entry b) {
+                return a.getAddress() - b.getAddress();
+            }
+
+            public boolean equals (Object obj) {
+               return obj == this;
+            }
+        });
+        return result;
+    }
+
+    /**
+     * Gets the register that begins the method's parameter range (including
+     * the 'this' parameter for non-static methods). The range continues until
+     * {@code regSize}
+     *
+     * @return register as noted above
+     */
+    private int getParamBase() {
+        return regSize
+                - desc.getParameterTypes().getWordCount() - (isStatic? 0 : 1);
+    }
+
+    /**
+     * Extracts method arguments from a locals list. These will be collected
+     * from the input list and sorted by ascending register in the
+     * returned list.
+     *
+     * @return list of non-{@code this} method argument locals,
+     * sorted by ascending register
+     */
+    private ArrayList<LocalList.Entry> extractMethodArguments() {
+        ArrayList<LocalList.Entry> result
+                = new ArrayList(desc.getParameterTypes().size());
+        int argBase = getParamBase();
+        BitSet seen = new BitSet(regSize - argBase);
+        int sz = locals.size();
+
+        for (int i = 0; i < sz; i++) {
+            LocalList.Entry e = locals.get(i);
+            int reg = e.getRegister();
+
+            if (reg < argBase) {
+                continue;
+            }
+
+            // only the lowest-start-address entry is included.
+            if (seen.get(reg - argBase)) {
+                continue;
+            }
+
+            seen.set(reg - argBase);
+            result.add(e);
+        }
+
+        // Sort by ascending register.
+        Collections.sort(result, new Comparator<LocalList.Entry>() {
+            public int compare(LocalList.Entry a, LocalList.Entry b) {
+                return a.getRegister() - b.getRegister();
+            }
+
+            public boolean equals(Object obj) {
+               return obj == this;
+            }
+        });
+
+        return result;
+    }
+
+    /**
+     * Returns a string representation of this LocalList entry that is
+     * appropriate for emitting as an annotation.
+     *
+     * @param e {@code non-null;} entry
+     * @return {@code non-null;} annotation string
+     */
+    private String entryAnnotationString(LocalList.Entry e) {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(RegisterSpec.PREFIX);
+        sb.append(e.getRegister());
+        sb.append(' ');
+
+        CstUtf8 name = e.getName();
+        if (name == null) {
+            sb.append("null");
+        } else {
+            sb.append(name.toHuman());
+        }
+        sb.append(' ');
+
+        CstType type = e.getType();
+        if (type == null) {
+            sb.append("null");
+        } else {
+            sb.append(type.toHuman());
+        }
+
+        CstUtf8 signature = e.getSignature();
+
+        if (signature != null) {
+            sb.append(' ');
+            sb.append(signature.toHuman());
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Emits a {@link DebugInfoConstants#DBG_RESTART_LOCAL DBG_RESTART_LOCAL}
+     * sequence.
+     *
+     * @param entry entry associated with this restart
+     * @throws IOException
+     */
+    private void emitLocalRestart(LocalList.Entry entry)
+            throws IOException {
+
+        int mark = output.getCursor();
+
+        output.writeByte(DBG_RESTART_LOCAL);
+        emitUnsignedLeb128(entry.getRegister());
+
+        if (annotateTo != null || debugPrint != null) {
+            annotate(output.getCursor() - mark,
+                    String.format("%04x: +local restart %s",
+                            address, entryAnnotationString(entry)));
+        }
+
+        if (DEBUG) {
+            System.err.println("emit local restart");
+        }
+    }
+
+    /**
+     * Emits a string index as an unsigned LEB128. The actual value written
+     * is shifted by 1, so that the '0' value is reserved for "null". The
+     * null symbol is used in some cases by the parameter name list
+     * at the beginning of the sequence.
+     *
+     * @param string {@code null-ok;} string to emit
+     * @throws IOException
+     */
+    private void emitStringIndex(CstUtf8 string) throws IOException {
+        if ((string == null) || (file == null)) {
+            output.writeUnsignedLeb128(0);
+        } else {
+            output.writeUnsignedLeb128(
+                1 + file.getStringIds().indexOf(string));
+        }
+
+        if (DEBUG) {
+            System.err.printf("Emit string %s\n",
+                    string == null ? "<null>" : string.toQuoted());
+        }
+    }
+
+    /**
+     * Emits a type index as an unsigned LEB128. The actual value written
+     * is shifted by 1, so that the '0' value is reserved for "null".
+     *
+     * @param type {@code null-ok;} type to emit
+     * @throws IOException
+     */
+    private void emitTypeIndex(CstType type) throws IOException {
+        if ((type == null) || (file == null)) {
+            output.writeUnsignedLeb128(0);
+        } else {
+            output.writeUnsignedLeb128(
+                1 + file.getTypeIds().indexOf(type));
+        }
+
+        if (DEBUG) {
+            System.err.printf("Emit type %s\n",
+                    type == null ? "<null>" : type.toHuman());
+        }
+    }
+
+    /**
+     * Emits a {@link DebugInfoConstants#DBG_START_LOCAL DBG_START_LOCAL} or
+     * {@link DebugInfoConstants#DBG_START_LOCAL_EXTENDED
+     * DBG_START_LOCAL_EXTENDED} sequence.
+     *
+     * @param entry entry to emit
+     * @throws IOException
+     */
+    private void emitLocalStart(LocalList.Entry entry)
+        throws IOException {
+
+        if (entry.getSignature() != null) {
+            emitLocalStartExtended(entry);
+            return;
+        }
+
+        int mark = output.getCursor();
+
+        output.writeByte(DBG_START_LOCAL);
+
+        emitUnsignedLeb128(entry.getRegister());
+        emitStringIndex(entry.getName());
+        emitTypeIndex(entry.getType());
+
+        if (annotateTo != null || debugPrint != null) {
+            annotate(output.getCursor() - mark,
+                    String.format("%04x: +local %s", address,
+                            entryAnnotationString(entry)));
+        }
+
+        if (DEBUG) {
+            System.err.println("emit local start");
+        }
+    }
+
+    /**
+     * Emits a {@link DebugInfoConstants#DBG_START_LOCAL_EXTENDED
+     * DBG_START_LOCAL_EXTENDED} sequence.
+     *
+     * @param entry entry to emit
+     * @throws IOException
+     */
+    private void emitLocalStartExtended(LocalList.Entry entry)
+        throws IOException {
+
+        int mark = output.getCursor();
+
+        output.writeByte(DBG_START_LOCAL_EXTENDED);
+
+        emitUnsignedLeb128(entry.getRegister());
+        emitStringIndex(entry.getName());
+        emitTypeIndex(entry.getType());
+        emitStringIndex(entry.getSignature());
+
+        if (annotateTo != null || debugPrint != null) {
+            annotate(output.getCursor() - mark,
+                    String.format("%04x: +localx %s", address,
+                            entryAnnotationString(entry)));
+        }
+
+        if (DEBUG) {
+            System.err.println("emit local start");
+        }
+    }
+
+    /**
+     * Emits a {@link DebugInfoConstants#DBG_END_LOCAL DBG_END_LOCAL} sequence.
+     *
+     * @param entry {@code entry non-null;} entry associated with end.
+     * @throws IOException
+     */
+    private void emitLocalEnd(LocalList.Entry entry)
+            throws IOException {
+
+        int mark = output.getCursor();
+
+        output.writeByte(DBG_END_LOCAL);
+        output.writeUnsignedLeb128(entry.getRegister());
+
+        if (annotateTo != null || debugPrint != null) {
+            annotate(output.getCursor() - mark,
+                    String.format("%04x: -local %s", address,
+                            entryAnnotationString(entry)));
+        }
+
+        if (DEBUG) {
+            System.err.println("emit local end");
+        }
+    }
+
+    /**
+     * Emits the necessary byte sequences to emit the given position table
+     * entry. This will typically be a single special opcode, although
+     * it may also require DBG_ADVANCE_PC or DBG_ADVANCE_LINE.
+     *
+     * @param entry position entry to emit.
+     * @throws IOException
+     */
+    private void emitPosition(PositionList.Entry entry)
+            throws IOException {
+
+        SourcePosition pos = entry.getPosition();
+        int newLine = pos.getLine();
+        int newAddress = entry.getAddress();
+
+        int opcode;
+
+        int deltaLines = newLine - line;
+        int deltaAddress = newAddress - address;
+
+        if (deltaAddress < 0) {
+            throw new RuntimeException(
+                    "Position entries must be in ascending address order");
+        }
+
+        if ((deltaLines < DBG_LINE_BASE)
+                || (deltaLines > (DBG_LINE_BASE + DBG_LINE_RANGE -1))) {
+            emitAdvanceLine(deltaLines);
+            deltaLines = 0;
+        }
+
+        opcode = computeOpcode (deltaLines, deltaAddress);
+
+        if ((opcode & ~0xff) > 0) {
+            emitAdvancePc(deltaAddress);
+            deltaAddress = 0;
+            opcode = computeOpcode (deltaLines, deltaAddress);
+
+            if ((opcode & ~0xff) > 0) {
+                emitAdvanceLine(deltaLines);
+                deltaLines = 0;
+                opcode = computeOpcode (deltaLines, deltaAddress);
+            }
+        }
+
+        output.writeByte(opcode);
+
+        line += deltaLines;
+        address += deltaAddress;
+
+        if (annotateTo != null || debugPrint != null) {
+            annotate(1,
+                    String.format("%04x: line %d", address, line));
+        }
+    }
+
+    /**
+     * Computes a special opcode that will encode the given position change.
+     * If the return value is > 0xff, then the request cannot be fulfilled.
+     * Essentially the same as described in "DWARF Debugging Format Version 3"
+     * section 6.2.5.1.
+     *
+     * @param deltaLines {@code >= DBG_LINE_BASE, <= DBG_LINE_BASE +
+     * DBG_LINE_RANGE;} the line change to encode
+     * @param deltaAddress {@code >= 0;} the address change to encode
+     * @return {@code <= 0xff} if in range, otherwise parameters are out
+     * of range
+     */
+    private static int computeOpcode(int deltaLines, int deltaAddress) {
+        if (deltaLines < DBG_LINE_BASE
+                || deltaLines > (DBG_LINE_BASE + DBG_LINE_RANGE -1)) {
+
+            throw new RuntimeException("Parameter out of range");
+        }
+
+        return (deltaLines - DBG_LINE_BASE)
+            + (DBG_LINE_RANGE * deltaAddress) + DBG_FIRST_SPECIAL;
+    }
+
+    /**
+     * Emits an {@link DebugInfoConstants#DBG_ADVANCE_LINE DBG_ADVANCE_LINE}
+     * sequence.
+     *
+     * @param deltaLines amount to change line number register by
+     * @throws IOException
+     */
+    private void emitAdvanceLine(int deltaLines) throws IOException {
+        int mark = output.getCursor();
+
+        output.writeByte(DBG_ADVANCE_LINE);
+        output.writeSignedLeb128(deltaLines);
+        line += deltaLines;
+
+        if (annotateTo != null || debugPrint != null) {
+            annotate(output.getCursor() - mark,
+                    String.format("line = %d", line));
+        }
+
+        if (DEBUG) {
+            System.err.printf("Emitting advance_line for %d\n", deltaLines);
+        }
+    }
+
+    /**
+     * Emits an  {@link DebugInfoConstants#DBG_ADVANCE_PC DBG_ADVANCE_PC}
+     * sequence.
+     *
+     * @param deltaAddress {@code >= 0;} amount to change program counter by
+     * @throws IOException
+     */
+    private void emitAdvancePc(int deltaAddress) throws IOException {
+        int mark = output.getCursor();
+
+        output.writeByte(DBG_ADVANCE_PC);
+        output.writeUnsignedLeb128(deltaAddress);
+        address += deltaAddress;
+
+        if (annotateTo != null || debugPrint != null) {
+            annotate(output.getCursor() - mark,
+                    String.format("%04x: advance pc", address));
+        }
+
+        if (DEBUG) {
+            System.err.printf("Emitting advance_pc for %d\n", deltaAddress);
+        }
+    }
+
+    /**
+     * Emits an unsigned LEB128 value.
+     *
+     * @param n {@code >= 0;} value to emit. Note that, although this can
+     * represent integers larger than Integer.MAX_VALUE, we currently don't
+     * allow that.
+     * @throws IOException
+     */
+    private void emitUnsignedLeb128(int n) throws IOException {
+        // We'll never need the top end of the unsigned range anyway.
+        if (n < 0) {
+            throw new RuntimeException(
+                    "Signed value where unsigned required: " + n);
+        }
+
+        output.writeUnsignedLeb128(n);
+    }
+
+    /**
+     * Emits the {@link DebugInfoConstants#DBG_END_SEQUENCE DBG_END_SEQUENCE}
+     * bytecode.
+     */
+    private void emitEndSequence() {
+        output.writeByte(DBG_END_SEQUENCE);
+
+        if (annotateTo != null || debugPrint != null) {
+            annotate(1, "end sequence");
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/DebugInfoItem.java b/dexgen/src/com/android/dexgen/dex/file/DebugInfoItem.java
new file mode 100644
index 0000000..82ad4441
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/DebugInfoItem.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.dex.code.DalvCode;
+import com.android.dexgen.dex.code.DalvInsnList;
+import com.android.dexgen.dex.code.LocalList;
+import com.android.dexgen.dex.code.PositionList;
+import com.android.dexgen.rop.cst.CstMethodRef;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.ExceptionWithContext;
+
+import java.io.PrintWriter;
+
+public class DebugInfoItem extends OffsettedItem {
+    /** the required alignment for instances of this class */
+    private static final int ALIGNMENT = 1;
+
+    private static final boolean ENABLE_ENCODER_SELF_CHECK = false;
+
+    /** {@code non-null;} the code this item represents */
+    private final DalvCode code;
+
+    private byte[] encoded;
+
+    private final boolean isStatic;
+    private final CstMethodRef ref;
+
+    public DebugInfoItem(DalvCode code, boolean isStatic, CstMethodRef ref) {
+        // We don't know the write size yet.
+        super (ALIGNMENT, -1);
+
+        if (code == null) {
+            throw new NullPointerException("code == null");
+        }
+
+        this.code = code;
+        this.isStatic = isStatic;
+        this.ref = ref;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_DEBUG_INFO_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        // No contents to add.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void place0(Section addedTo, int offset) {
+        // Encode the data and note the size.
+
+        try {
+            encoded = encode(addedTo.getFile(), null, null, null, false);
+            setWriteSize(encoded.length);
+        } catch (RuntimeException ex) {
+            throw ExceptionWithContext.withContext(ex,
+                    "...while placing debug info for " + ref.toHuman());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toHuman() {
+        throw new RuntimeException("unsupported");
+    }
+
+    /**
+     * Writes annotations for the elements of this list, as
+     * zero-length. This is meant to be used for dumping this instance
+     * directly after a code dump (with the real local list actually
+     * existing elsewhere in the output).
+     *
+     * @param file {@code non-null;} the file to use for referencing other sections
+     * @param out {@code non-null;} where to annotate to
+     * @param prefix {@code null-ok;} prefix to attach to each line of output
+     */
+    public void annotateTo(DexFile file, AnnotatedOutput out, String prefix) {
+        encode(file, prefix, null, out, false);
+    }
+
+    /**
+     * Does a human-friendly dump of this instance.
+     *
+     * @param out {@code non-null;} where to dump
+     * @param prefix {@code non-null;} prefix to attach to each line of output
+     */
+    public void debugPrint(PrintWriter out, String prefix) {
+        encode(null, prefix, out, null, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void writeTo0(DexFile file, AnnotatedOutput out) {
+        if (out.annotates()) {
+            /*
+             * Re-run the encoder to generate the annotations,
+             * but write the bits from the original encode
+             */
+
+            out.annotate(offsetString() + " debug info");
+            encode(file, null, null, out, true);
+        }
+
+        out.write(encoded);
+    }
+
+    /**
+     * Performs debug info encoding.
+     *
+     * @param file {@code null-ok;} file to refer to during encoding
+     * @param prefix {@code null-ok;} prefix to attach to each line of output
+     * @param debugPrint {@code null-ok;} if specified, an alternate output for
+     * annotations
+     * @param out {@code null-ok;} if specified, where annotations should go
+     * @param consume whether to claim to have consumed output for
+     * {@code out}
+     * @return {@code non-null;} the encoded array
+     */
+    private byte[] encode(DexFile file, String prefix, PrintWriter debugPrint,
+            AnnotatedOutput out, boolean consume) {
+        byte[] result = encode0(file, prefix, debugPrint, out, consume);
+
+        if (ENABLE_ENCODER_SELF_CHECK && (file != null)) {
+            try {
+                DebugInfoDecoder.validateEncode(result, file, ref, code,
+                        isStatic);
+            } catch (RuntimeException ex) {
+                // Reconvert, annotating to System.err.
+                encode0(file, "", new PrintWriter(System.err, true), null,
+                        false);
+                throw ex;
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Helper for {@link #encode} to do most of the work.
+     *
+     * @param file {@code null-ok;} file to refer to during encoding
+     * @param prefix {@code null-ok;} prefix to attach to each line of output
+     * @param debugPrint {@code null-ok;} if specified, an alternate output for
+     * annotations
+     * @param out {@code null-ok;} if specified, where annotations should go
+     * @param consume whether to claim to have consumed output for
+     * {@code out}
+     * @return {@code non-null;} the encoded array
+     */
+    private byte[] encode0(DexFile file, String prefix, PrintWriter debugPrint,
+            AnnotatedOutput out, boolean consume) {
+        PositionList positions = code.getPositions();
+        LocalList locals = code.getLocals();
+        DalvInsnList insns = code.getInsns();
+        int codeSize = insns.codeSize();
+        int regSize = insns.getRegistersSize();
+
+        DebugInfoEncoder encoder =
+            new DebugInfoEncoder(positions, locals,
+                    file, codeSize, regSize, isStatic, ref);
+
+        byte[] result;
+
+        if ((debugPrint == null) && (out == null)) {
+            result = encoder.convert();
+        } else {
+            result = encoder.convertAndAnnotate(prefix, debugPrint, out,
+                    consume);
+        }
+
+        return result;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/DexFile.java b/dexgen/src/com/android/dexgen/dex/file/DexFile.java
new file mode 100644
index 0000000..e92aa10
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/DexFile.java
@@ -0,0 +1,646 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.dex.file.MixedItemSection.SortType;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstBaseMethodRef;
+import com.android.dexgen.rop.cst.CstEnumRef;
+import com.android.dexgen.rop.cst.CstFieldRef;
+import com.android.dexgen.rop.cst.CstString;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.ByteArrayAnnotatedOutput;
+import com.android.dexgen.util.ExceptionWithContext;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.zip.Adler32;
+
+/**
+ * Representation of an entire {@code .dex} (Dalvik EXecutable)
+ * file, which itself consists of a set of Dalvik classes.
+ */
+public final class DexFile {
+    /** {@code non-null;} word data section */
+    private final MixedItemSection wordData;
+
+    /**
+     * {@code non-null;} type lists section. This is word data, but separating
+     * it from {@link #wordData} helps break what would otherwise be a
+     * circular dependency between the that and {@link #protoIds}.
+     */
+    private final MixedItemSection typeLists;
+
+    /**
+     * {@code non-null;} map section. The map needs to be in a section by itself
+     * for the self-reference mechanics to work in a reasonably
+     * straightforward way. See {@link MapItem#addMap} for more detail.
+     */
+    private final MixedItemSection map;
+
+    /** {@code non-null;} string data section */
+    private final MixedItemSection stringData;
+
+    /** {@code non-null;} string identifiers section */
+    private final StringIdsSection stringIds;
+
+    /** {@code non-null;} type identifiers section */
+    private final TypeIdsSection typeIds;
+
+    /** {@code non-null;} prototype identifiers section */
+    private final ProtoIdsSection protoIds;
+
+    /** {@code non-null;} field identifiers section */
+    private final FieldIdsSection fieldIds;
+
+    /** {@code non-null;} method identifiers section */
+    private final MethodIdsSection methodIds;
+
+    /** {@code non-null;} class definitions section */
+    private final ClassDefsSection classDefs;
+
+    /** {@code non-null;} class data section */
+    private final MixedItemSection classData;
+
+    /** {@code non-null;} byte data section */
+    private final MixedItemSection byteData;
+
+    /** {@code non-null;} file header */
+    private final HeaderSection header;
+
+    /**
+     * {@code non-null;} array of sections in the order they will appear in the
+     * final output file
+     */
+    private final Section[] sections;
+
+    /** {@code >= -1;} total file size or {@code -1} if unknown */
+    private int fileSize;
+
+    /** {@code >= 40;} maximum width of the file dump */
+    private int dumpWidth;
+
+    /**
+     * Constructs an instance. It is initially empty.
+     */
+    public DexFile() {
+        header = new HeaderSection(this);
+        typeLists = new MixedItemSection(null, this, 4, SortType.NONE);
+        wordData = new MixedItemSection("word_data", this, 4, SortType.TYPE);
+        stringData =
+            new MixedItemSection("string_data", this, 1, SortType.INSTANCE);
+        classData = new MixedItemSection(null, this, 1, SortType.NONE);
+        byteData = new MixedItemSection("byte_data", this, 1, SortType.TYPE);
+        stringIds = new StringIdsSection(this);
+        typeIds = new TypeIdsSection(this);
+        protoIds = new ProtoIdsSection(this);
+        fieldIds = new FieldIdsSection(this);
+        methodIds = new MethodIdsSection(this);
+        classDefs = new ClassDefsSection(this);
+        map = new MixedItemSection("map", this, 4, SortType.NONE);
+
+        /*
+         * This is the list of sections in the order they appear in
+         * the final output.
+         */
+        sections = new Section[] {
+            header, stringIds, typeIds, protoIds, fieldIds, methodIds,
+            classDefs, wordData, typeLists, stringData, byteData,
+            classData, map };
+
+        fileSize = -1;
+        dumpWidth = 79;
+    }
+
+    /**
+     * Adds a class to this instance. It is illegal to attempt to add more
+     * than one class with the same name.
+     *
+     * @param clazz {@code non-null;} the class to add
+     */
+    public void add(ClassDefItem clazz) {
+        classDefs.add(clazz);
+    }
+
+    /**
+     * Gets the class definition with the given name, if any.
+     *
+     * @param name {@code non-null;} the class name to look for
+     * @return {@code null-ok;} the class with the given name, or {@code null}
+     * if there is no such class
+     */
+    public ClassDefItem getClassOrNull(String name) {
+        try {
+            Type type = Type.internClassName(name);
+            return (ClassDefItem) classDefs.get(new CstType(type));
+        } catch (IllegalArgumentException ex) {
+            // Translate exception, per contract.
+            return null;
+        }
+    }
+
+    /**
+     * Writes the contents of this instance as either a binary or a
+     * human-readable form, or both.
+     *
+     * @param out {@code null-ok;} where to write to
+     * @param humanOut {@code null-ok;} where to write human-oriented output to
+     * @param verbose whether to be verbose when writing human-oriented output
+     */
+    public void writeTo(OutputStream out, Writer humanOut, boolean verbose)
+        throws IOException {
+        boolean annotate = (humanOut != null);
+        ByteArrayAnnotatedOutput result = toDex0(annotate, verbose);
+
+        if (out != null) {
+            out.write(result.getArray());
+        }
+
+        if (annotate) {
+            result.writeAnnotationsTo(humanOut);
+        }
+    }
+
+    /**
+     * Returns the contents of this instance as a {@code .dex} file,
+     * in {@code byte[]} form.
+     *
+     * @param humanOut {@code null-ok;} where to write human-oriented output to
+     * @param verbose whether to be verbose when writing human-oriented output
+     * @return {@code non-null;} a {@code .dex} file for this instance
+     */
+    public byte[] toDex(Writer humanOut, boolean verbose)
+        throws IOException {
+        boolean annotate = (humanOut != null);
+        ByteArrayAnnotatedOutput result = toDex0(annotate, verbose);
+
+        if (annotate) {
+            result.writeAnnotationsTo(humanOut);
+        }
+
+        return result.getArray();
+    }
+
+    /**
+     * Sets the maximum width of the human-oriented dump of the instance.
+     *
+     * @param dumpWidth {@code >= 40;} the width
+     */
+    public void setDumpWidth(int dumpWidth) {
+        if (dumpWidth < 40) {
+            throw new IllegalArgumentException("dumpWidth < 40");
+        }
+
+        this.dumpWidth = dumpWidth;
+    }
+
+    /**
+     * Gets the total file size, if known.
+     *
+     * <p>This is package-scope in order to allow
+     * the {@link HeaderSection} to set itself up properly.</p>
+     *
+     * @return {@code >= 0;} the total file size
+     * @throws RuntimeException thrown if the file size is not yet known
+     */
+    /*package*/ int getFileSize() {
+        if (fileSize < 0) {
+            throw new RuntimeException("file size not yet known");
+        }
+
+        return fileSize;
+    }
+
+    /**
+     * Gets the string data section.
+     *
+     * <p>This is package-scope in order to allow
+     * the various {@link Item} instances to add items to the
+     * instance.</p>
+     *
+     * @return {@code non-null;} the string data section
+     */
+    /*package*/ MixedItemSection getStringData() {
+        return stringData;
+    }
+
+    /**
+     * Gets the word data section.
+     *
+     * <p>This is package-scope in order to allow
+     * the various {@link Item} instances to add items to the
+     * instance.</p>
+     *
+     * @return {@code non-null;} the word data section
+     */
+    /*package*/ MixedItemSection getWordData() {
+        return wordData;
+    }
+
+    /**
+     * Gets the type lists section.
+     *
+     * <p>This is package-scope in order to allow
+     * the various {@link Item} instances to add items to the
+     * instance.</p>
+     *
+     * @return {@code non-null;} the word data section
+     */
+    /*package*/ MixedItemSection getTypeLists() {
+        return typeLists;
+    }
+
+    /**
+     * Gets the map section.
+     *
+     * <p>This is package-scope in order to allow the header section
+     * to query it.</p>
+     *
+     * @return {@code non-null;} the map section
+     */
+    /*package*/ MixedItemSection getMap() {
+        return map;
+    }
+
+    /**
+     * Gets the string identifiers section.
+     *
+     * <p>This is package-scope in order to allow
+     * the various {@link Item} instances to add items to the
+     * instance.</p>
+     *
+     * @return {@code non-null;} the string identifiers section
+     */
+    /*package*/ StringIdsSection getStringIds() {
+        return stringIds;
+    }
+
+    /**
+     * Gets the class definitions section.
+     *
+     * <p>This is package-scope in order to allow
+     * the various {@link Item} instances to add items to the
+     * instance.</p>
+     *
+     * @return {@code non-null;} the class definitions section
+     */
+    /*package*/ ClassDefsSection getClassDefs() {
+        return classDefs;
+    }
+
+    /**
+     * Gets the class data section.
+     *
+     * <p>This is package-scope in order to allow
+     * the various {@link Item} instances to add items to the
+     * instance.</p>
+     *
+     * @return {@code non-null;} the class data section
+     */
+    /*package*/ MixedItemSection getClassData() {
+        return classData;
+    }
+
+    /**
+     * Gets the type identifiers section.
+     *
+     * <p>This is package-scope in order to allow
+     * the various {@link Item} instances to add items to the
+     * instance.</p>
+     *
+     * @return {@code non-null;} the class identifiers section
+     */
+    /*package*/ TypeIdsSection getTypeIds() {
+        return typeIds;
+    }
+
+    /**
+     * Gets the prototype identifiers section.
+     *
+     * <p>This is package-scope in order to allow
+     * the various {@link Item} instances to add items to the
+     * instance.</p>
+     *
+     * @return {@code non-null;} the prototype identifiers section
+     */
+    /*package*/ ProtoIdsSection getProtoIds() {
+        return protoIds;
+    }
+
+    /**
+     * Gets the field identifiers section.
+     *
+     * <p>This is package-scope in order to allow
+     * the various {@link Item} instances to add items to the
+     * instance.</p>
+     *
+     * @return {@code non-null;} the field identifiers section
+     */
+    /*package*/ FieldIdsSection getFieldIds() {
+        return fieldIds;
+    }
+
+    /**
+     * Gets the method identifiers section.
+     *
+     * <p>This is package-scope in order to allow
+     * the various {@link Item} instances to add items to the
+     * instance.</p>
+     *
+     * @return {@code non-null;} the method identifiers section
+     */
+    /*package*/ MethodIdsSection getMethodIds() {
+        return methodIds;
+    }
+
+    /**
+     * Gets the byte data section.
+     *
+     * <p>This is package-scope in order to allow
+     * the various {@link Item} instances to add items to the
+     * instance.</p>
+     *
+     * @return {@code non-null;} the byte data section
+     */
+    /*package*/ MixedItemSection getByteData() {
+        return byteData;
+    }
+
+    /**
+     * Gets the first section of the file that is to be considered
+     * part of the data section.
+     *
+     * <p>This is package-scope in order to allow the header section
+     * to query it.</p>
+     *
+     * @return {@code non-null;} the section
+     */
+    /*package*/ Section getFirstDataSection() {
+        return wordData;
+    }
+
+    /**
+     * Gets the last section of the file that is to be considered
+     * part of the data section.
+     *
+     * <p>This is package-scope in order to allow the header section
+     * to query it.</p>
+     *
+     * @return {@code non-null;} the section
+     */
+    /*package*/ Section getLastDataSection() {
+        return map;
+    }
+
+    /**
+     * Interns the given constant in the appropriate section of this
+     * instance, or do nothing if the given constant isn't the sort
+     * that should be interned.
+     *
+     * @param cst {@code non-null;} constant to possibly intern
+     */
+    /*package*/ void internIfAppropriate(Constant cst) {
+        if (cst instanceof CstString) {
+            stringIds.intern((CstString) cst);
+        } else if (cst instanceof CstUtf8) {
+            stringIds.intern((CstUtf8) cst);
+        } else if (cst instanceof CstType) {
+            typeIds.intern((CstType) cst);
+        } else if (cst instanceof CstBaseMethodRef) {
+            methodIds.intern((CstBaseMethodRef) cst);
+        } else if (cst instanceof CstFieldRef) {
+            fieldIds.intern((CstFieldRef) cst);
+        } else if (cst instanceof CstEnumRef) {
+            fieldIds.intern(((CstEnumRef) cst).getFieldRef());
+        } else if (cst == null) {
+            throw new NullPointerException("cst == null");
+        }
+    }
+
+    /**
+     * Gets the {@link IndexedItem} corresponding to the given constant,
+     * if it is a constant that has such a correspondence, or return
+     * {@code null} if it isn't such a constant. This will throw
+     * an exception if the given constant <i>should</i> have been found
+     * but wasn't.
+     *
+     * @param cst {@code non-null;} the constant to look up
+     * @return {@code null-ok;} its corresponding item, if it has a corresponding
+     * item, or {@code null} if it's not that sort of constant
+     */
+    /*package*/ IndexedItem findItemOrNull(Constant cst) {
+        IndexedItem item;
+
+        if (cst instanceof CstString) {
+            return stringIds.get(cst);
+        } else if (cst instanceof CstType) {
+            return typeIds.get(cst);
+        } else if (cst instanceof CstBaseMethodRef) {
+            return methodIds.get(cst);
+        } else if (cst instanceof CstFieldRef) {
+            return fieldIds.get(cst);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the contents of this instance as a {@code .dex} file,
+     * in a {@link ByteArrayAnnotatedOutput} instance.
+     *
+     * @param annotate whether or not to keep annotations
+     * @param verbose if annotating, whether to be verbose
+     * @return {@code non-null;} a {@code .dex} file for this instance
+     */
+    private ByteArrayAnnotatedOutput toDex0(boolean annotate,
+            boolean verbose) {
+        /*
+         * The following is ordered so that the prepare() calls which
+         * add items happen before the calls to the sections that get
+         * added to.
+         */
+
+        classDefs.prepare();
+        classData.prepare();
+        wordData.prepare();
+        byteData.prepare();
+        methodIds.prepare();
+        fieldIds.prepare();
+        protoIds.prepare();
+        typeLists.prepare();
+        typeIds.prepare();
+        stringIds.prepare();
+        stringData.prepare();
+        header.prepare();
+
+        // Place the sections within the file.
+
+        int count = sections.length;
+        int offset = 0;
+
+        for (int i = 0; i < count; i++) {
+            Section one = sections[i];
+            int placedAt = one.setFileOffset(offset);
+            if (placedAt < offset) {
+                throw new RuntimeException("bogus placement for section " + i);
+            }
+
+            try {
+                if (one == map) {
+                    /*
+                     * Inform the map of all the sections, and add it
+                     * to the file. This can only be done after all
+                     * the other items have been sorted and placed.
+                     */
+                    MapItem.addMap(sections, map);
+                    map.prepare();
+                }
+
+                if (one instanceof MixedItemSection) {
+                    /*
+                     * Place the items of a MixedItemSection that just
+                     * got placed.
+                     */
+                    ((MixedItemSection) one).placeItems();
+                }
+
+                offset = placedAt + one.writeSize();
+            } catch (RuntimeException ex) {
+                throw ExceptionWithContext.withContext(ex,
+                        "...while writing section " + i);
+            }
+        }
+
+        // Write out all the sections.
+
+        fileSize = offset;
+        byte[] barr = new byte[fileSize];
+        ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(barr);
+
+        if (annotate) {
+            out.enableAnnotations(dumpWidth, verbose);
+        }
+
+        for (int i = 0; i < count; i++) {
+            try {
+                Section one = sections[i];
+                int zeroCount = one.getFileOffset() - out.getCursor();
+                if (zeroCount < 0) {
+                    throw new ExceptionWithContext("excess write of " +
+                            (-zeroCount));
+                }
+                out.writeZeroes(one.getFileOffset() - out.getCursor());
+                one.writeTo(out);
+            } catch (RuntimeException ex) {
+                ExceptionWithContext ec;
+                if (ex instanceof ExceptionWithContext) {
+                    ec = (ExceptionWithContext) ex;
+                } else {
+                    ec = new ExceptionWithContext(ex);
+                }
+                ec.addContext("...while writing section " + i);
+                throw ec;
+            }
+        }
+
+        if (out.getCursor() != fileSize) {
+            throw new RuntimeException("foreshortened write");
+        }
+
+        // Perform final bookkeeping.
+
+        calcSignature(barr);
+        calcChecksum(barr);
+
+        if (annotate) {
+            wordData.writeIndexAnnotation(out, ItemType.TYPE_CODE_ITEM,
+                    "\nmethod code index:\n\n");
+            getStatistics().writeAnnotation(out);
+            out.finishAnnotating();
+        }
+
+        return out;
+    }
+
+    /**
+     * Generates and returns statistics for all the items in the file.
+     *
+     * @return {@code non-null;} the statistics
+     */
+    public Statistics getStatistics() {
+        Statistics stats = new Statistics();
+
+        for (Section s : sections) {
+            stats.addAll(s);
+        }
+
+        return stats;
+    }
+
+    /**
+     * Calculates the signature for the {@code .dex} file in the
+     * given array, and modify the array to contain it.
+     *
+     * @param bytes {@code non-null;} the bytes of the file
+     */
+    private static void calcSignature(byte[] bytes) {
+        MessageDigest md;
+
+        try {
+            md = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException ex) {
+            throw new RuntimeException(ex);
+        }
+
+        md.update(bytes, 32, bytes.length - 32);
+
+        try {
+            int amt = md.digest(bytes, 12, 20);
+            if (amt != 20) {
+                throw new RuntimeException("unexpected digest write: " + amt +
+                                           " bytes");
+            }
+        } catch (DigestException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    /**
+     * Calculates the checksum for the {@code .dex} file in the
+     * given array, and modify the array to contain it.
+     *
+     * @param bytes {@code non-null;} the bytes of the file
+     */
+    private static void calcChecksum(byte[] bytes) {
+        Adler32 a32 = new Adler32();
+
+        a32.update(bytes, 12, bytes.length - 12);
+
+        int sum = (int) a32.getValue();
+
+        bytes[8]  = (byte) sum;
+        bytes[9]  = (byte) (sum >> 8);
+        bytes[10] = (byte) (sum >> 16);
+        bytes[11] = (byte) (sum >> 24);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/EncodedArrayItem.java b/dexgen/src/com/android/dexgen/dex/file/EncodedArrayItem.java
new file mode 100644
index 0000000..cef2375
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/EncodedArrayItem.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.annotation.Annotation;
+import com.android.dexgen.rop.annotation.AnnotationVisibility;
+import com.android.dexgen.rop.annotation.NameValuePair;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstAnnotation;
+import com.android.dexgen.rop.cst.CstArray;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.ByteArrayAnnotatedOutput;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Encoded array of constant values.
+ */
+public final class EncodedArrayItem extends OffsettedItem {
+    /** the required alignment for instances of this class */
+    private static final int ALIGNMENT = 1;
+
+    /** {@code non-null;} the array to represent */
+    private final CstArray array;
+
+    /**
+     * {@code null-ok;} encoded form, ready for writing to a file; set during
+     * {@link #place0}
+     */
+    private byte[] encodedForm;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param array {@code non-null;} array to represent
+     */
+    public EncodedArrayItem(CstArray array) {
+        /*
+         * The write size isn't known up-front because (the variable-lengthed)
+         * leb128 type is used to represent some things.
+         */
+        super(ALIGNMENT, -1);
+
+        if (array == null) {
+            throw new NullPointerException("array == null");
+        }
+
+        this.array = array;
+        this.encodedForm = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_ENCODED_ARRAY_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return array.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int compareTo0(OffsettedItem other) {
+        EncodedArrayItem otherArray = (EncodedArrayItem) other;
+
+        return array.compareTo(otherArray.array);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toHuman() {
+        return array.toHuman();
+    }
+
+    /** {@inheritDoc} */
+    public void addContents(DexFile file) {
+        ValueEncoder.addContents(file, array);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void place0(Section addedTo, int offset) {
+        // Encode the data and note the size.
+
+        ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
+        ValueEncoder encoder = new ValueEncoder(addedTo.getFile(), out);
+
+        encoder.writeArray(array, false);
+        encodedForm = out.toByteArray();
+        setWriteSize(encodedForm.length);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void writeTo0(DexFile file, AnnotatedOutput out) {
+        boolean annotates = out.annotates();
+
+        if (annotates) {
+            out.annotate(0, offsetString() + " encoded array");
+
+            /*
+             * The output is to be annotated, so redo the work previously
+             * done by place0(), except this time annotations will actually
+             * get emitted.
+             */
+            ValueEncoder encoder = new ValueEncoder(file, out);
+            encoder.writeArray(array, true);
+        } else {
+            out.write(encodedForm);
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/EncodedField.java b/dexgen/src/com/android/dexgen/dex/file/EncodedField.java
new file mode 100644
index 0000000..5af2b1f
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/EncodedField.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.code.AccessFlags;
+import com.android.dexgen.rop.cst.CstFieldRef;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.Leb128Utils;
+
+import java.io.PrintWriter;
+
+/**
+ * Representation of a field of a class, of any sort.
+ */
+public final class EncodedField extends EncodedMember
+        implements Comparable<EncodedField> {
+    /** {@code non-null;} constant for the field */
+    private final CstFieldRef field;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param field {@code non-null;} constant for the field
+     * @param accessFlags access flags
+     */
+    public EncodedField(CstFieldRef field, int accessFlags) {
+        super(accessFlags);
+
+        if (field == null) {
+            throw new NullPointerException("field == null");
+        }
+
+        /*
+         * TODO: Maybe check accessFlags, at least for
+         * easily-checked stuff?
+         */
+
+        this.field = field;
+    }
+
+    /** {@inheritDoc} */
+    public int hashCode() {
+        return field.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    public boolean equals(Object other) {
+        if (! (other instanceof EncodedField)) {
+            return false;
+        }
+
+        return compareTo((EncodedField) other) == 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><b>Note:</b> This compares the method constants only,
+     * ignoring any associated code, because it should never be the
+     * case that two different items with the same method constant
+     * ever appear in the same list (or same file, even).</p>
+     */
+    public int compareTo(EncodedField other) {
+        return field.compareTo(other.field);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer(100);
+
+        sb.append(getClass().getName());
+        sb.append('{');
+        sb.append(Hex.u2(getAccessFlags()));
+        sb.append(' ');
+        sb.append(field);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        FieldIdsSection fieldIds = file.getFieldIds();
+        fieldIds.intern(field);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public CstUtf8 getName() {
+        return field.getNat().getName();
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return field.toHuman();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void debugPrint(PrintWriter out, boolean verbose) {
+        // TODO: Maybe put something better here?
+        out.println(toString());
+    }
+
+    /**
+     * Gets the constant for the field.
+     *
+     * @return {@code non-null;} the constant
+     */
+    public CstFieldRef getRef() {
+        return field;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int encode(DexFile file, AnnotatedOutput out,
+            int lastIndex, int dumpSeq) {
+        int fieldIdx = file.getFieldIds().indexOf(field);
+        int diff = fieldIdx - lastIndex;
+        int accessFlags = getAccessFlags();
+
+        if (out.annotates()) {
+            out.annotate(0, String.format("  [%x] %s", dumpSeq,
+                            field.toHuman()));
+            out.annotate(Leb128Utils.unsignedLeb128Size(diff),
+                    "    field_idx:    " + Hex.u4(fieldIdx));
+            out.annotate(Leb128Utils.unsignedLeb128Size(accessFlags),
+                    "    access_flags: " +
+                    AccessFlags.fieldString(accessFlags));
+        }
+
+        out.writeUnsignedLeb128(diff);
+        out.writeUnsignedLeb128(accessFlags);
+
+        return fieldIdx;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/EncodedMember.java b/dexgen/src/com/android/dexgen/dex/file/EncodedMember.java
new file mode 100644
index 0000000..6c31704
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/EncodedMember.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.ToHuman;
+
+import java.io.PrintWriter;
+
+/**
+ * Representation of a member (field or method) of a class, for the
+ * purposes of encoding it inside a {@link ClassDataItem}.
+ */
+public abstract class EncodedMember implements ToHuman {
+    /** access flags */
+    private final int accessFlags;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param accessFlags access flags for the member
+     */
+    public EncodedMember(int accessFlags) {
+        this.accessFlags = accessFlags;
+    }
+
+    /**
+     * Gets the access flags.
+     *
+     * @return the access flags
+     */
+    public final int getAccessFlags() {
+        return accessFlags;
+    }
+
+    /**
+     * Gets the name.
+     *
+     * @return {@code non-null;} the name
+     */
+    public abstract CstUtf8 getName();
+
+    /**
+     * Does a human-friendly dump of this instance.
+     *
+     * @param out {@code non-null;} where to dump
+     * @param verbose whether to be verbose with the output
+     */
+    public abstract void debugPrint(PrintWriter out, boolean verbose);
+
+    /**
+     * Populates a {@link DexFile} with items from within this instance.
+     *
+     * @param file {@code non-null;} the file to populate
+     */
+    public abstract void addContents(DexFile file);
+
+    /**
+     * Encodes this instance to the given output.
+     *
+     * @param file {@code non-null;} file this instance is part of
+     * @param out {@code non-null;} where to write to
+     * @param lastIndex {@code >= 0;} the previous member index value encoded, or
+     * {@code 0} if this is the first element to encode
+     * @param dumpSeq {@code >= 0;} sequence number of this instance for
+     * annotation purposes
+     * @return {@code >= 0;} the member index value that was encoded
+     */
+    public abstract int encode(DexFile file, AnnotatedOutput out,
+            int lastIndex, int dumpSeq);
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/EncodedMethod.java b/dexgen/src/com/android/dexgen/dex/file/EncodedMethod.java
new file mode 100644
index 0000000..a35ca2c
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/EncodedMethod.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.dex.code.DalvCode;
+import com.android.dexgen.rop.code.AccessFlags;
+import com.android.dexgen.rop.cst.CstMethodRef;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.rop.type.TypeList;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.Leb128Utils;
+
+import java.io.PrintWriter;
+
+/**
+ * Class that representats a method of a class.
+ */
+public final class EncodedMethod extends EncodedMember
+        implements Comparable<EncodedMethod> {
+    /** {@code non-null;} constant for the method */
+    private final CstMethodRef method;
+
+    /**
+     * {@code null-ok;} code for the method, if the method is neither
+     * {@code abstract} nor {@code native}
+     */
+    private final CodeItem code;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param method {@code non-null;} constant for the method
+     * @param accessFlags access flags
+     * @param code {@code null-ok;} code for the method, if it is neither
+     * {@code abstract} nor {@code native}
+     * @param throwsList {@code non-null;} list of possibly-thrown exceptions,
+     * just used in generating debugging output (listings)
+     */
+    public EncodedMethod(CstMethodRef method, int accessFlags,
+            DalvCode code, TypeList throwsList) {
+        super(accessFlags);
+
+        if (method == null) {
+            throw new NullPointerException("method == null");
+        }
+
+        this.method = method;
+
+        if (code == null) {
+            this.code = null;
+        } else {
+            boolean isStatic = (accessFlags & AccessFlags.ACC_STATIC) != 0;
+            this.code = new CodeItem(method, code, isStatic, throwsList);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public boolean equals(Object other) {
+        if (! (other instanceof EncodedMethod)) {
+            return false;
+        }
+
+        return compareTo((EncodedMethod) other) == 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><b>Note:</b> This compares the method constants only,
+     * ignoring any associated code, because it should never be the
+     * case that two different items with the same method constant
+     * ever appear in the same list (or same file, even).</p>
+     */
+    public int compareTo(EncodedMethod other) {
+        return method.compareTo(other.method);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer(100);
+
+        sb.append(getClass().getName());
+        sb.append('{');
+        sb.append(Hex.u2(getAccessFlags()));
+        sb.append(' ');
+        sb.append(method);
+
+        if (code != null) {
+            sb.append(' ');
+            sb.append(code);
+        }
+
+        sb.append('}');
+
+        return sb.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        MethodIdsSection methodIds = file.getMethodIds();
+        MixedItemSection wordData = file.getWordData();
+
+        methodIds.intern(method);
+
+        if (code != null) {
+            wordData.add(code);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public final String toHuman() {
+        return method.toHuman();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final CstUtf8 getName() {
+        return method.getNat().getName();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void debugPrint(PrintWriter out, boolean verbose) {
+        if (code == null) {
+            out.println(getRef().toHuman() + ": abstract or native");
+        } else {
+            code.debugPrint(out, "  ", verbose);
+        }
+    }
+
+    /**
+     * Gets the constant for the method.
+     *
+     * @return {@code non-null;} the constant
+     */
+    public final CstMethodRef getRef() {
+        return method;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int encode(DexFile file, AnnotatedOutput out,
+            int lastIndex, int dumpSeq) {
+        int methodIdx = file.getMethodIds().indexOf(method);
+        int diff = methodIdx - lastIndex;
+        int accessFlags = getAccessFlags();
+        int codeOff = OffsettedItem.getAbsoluteOffsetOr0(code);
+        boolean hasCode = (codeOff != 0);
+        boolean shouldHaveCode = (accessFlags &
+                (AccessFlags.ACC_ABSTRACT | AccessFlags.ACC_NATIVE)) == 0;
+
+        /*
+         * Verify that code appears if and only if a method is
+         * declared to have it.
+         */
+        if (hasCode != shouldHaveCode) {
+            throw new UnsupportedOperationException(
+                    "code vs. access_flags mismatch");
+        }
+
+        if (out.annotates()) {
+            out.annotate(0, String.format("  [%x] %s", dumpSeq,
+                            method.toHuman()));
+            out.annotate(Leb128Utils.unsignedLeb128Size(diff),
+                    "    method_idx:   " + Hex.u4(methodIdx));
+            out.annotate(Leb128Utils.unsignedLeb128Size(accessFlags),
+                    "    access_flags: " +
+                    AccessFlags.methodString(accessFlags));
+            out.annotate(Leb128Utils.unsignedLeb128Size(codeOff),
+                    "    code_off:     " + Hex.u4(codeOff));
+        }
+
+        out.writeUnsignedLeb128(diff);
+        out.writeUnsignedLeb128(accessFlags);
+        out.writeUnsignedLeb128(codeOff);
+
+        return methodIdx;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/FieldAnnotationStruct.java b/dexgen/src/com/android/dexgen/dex/file/FieldAnnotationStruct.java
new file mode 100644
index 0000000..95e4dbc
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/FieldAnnotationStruct.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.annotation.Annotations;
+import com.android.dexgen.rop.cst.CstFieldRef;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.ToHuman;
+
+/**
+ * Association of a field and its annotations.
+ */
+public final class FieldAnnotationStruct
+        implements ToHuman, Comparable<FieldAnnotationStruct> {
+    /** {@code non-null;} the field in question */
+    private final CstFieldRef field;
+
+    /** {@code non-null;} the associated annotations */
+    private AnnotationSetItem annotations;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param field {@code non-null;} the field in question
+     * @param annotations {@code non-null;} the associated annotations
+     */
+    public FieldAnnotationStruct(CstFieldRef field,
+            AnnotationSetItem annotations) {
+        if (field == null) {
+            throw new NullPointerException("field == null");
+        }
+
+        if (annotations == null) {
+            throw new NullPointerException("annotations == null");
+        }
+
+        this.field = field;
+        this.annotations = annotations;
+    }
+
+    /** {@inheritDoc} */
+    public int hashCode() {
+        return field.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    public boolean equals(Object other) {
+        if (! (other instanceof FieldAnnotationStruct)) {
+            return false;
+        }
+
+        return field.equals(((FieldAnnotationStruct) other).field);
+    }
+
+    /** {@inheritDoc} */
+    public int compareTo(FieldAnnotationStruct other) {
+        return field.compareTo(other.field);
+    }
+
+    /** {@inheritDoc} */
+    public void addContents(DexFile file) {
+        FieldIdsSection fieldIds = file.getFieldIds();
+        MixedItemSection wordData = file.getWordData();
+
+        fieldIds.intern(field);
+        annotations = wordData.intern(annotations);
+    }
+
+    /** {@inheritDoc} */
+    public void writeTo(DexFile file, AnnotatedOutput out) {
+        int fieldIdx = file.getFieldIds().indexOf(field);
+        int annotationsOff = annotations.getAbsoluteOffset();
+
+        if (out.annotates()) {
+            out.annotate(0, "    " + field.toHuman());
+            out.annotate(4, "      field_idx:       " + Hex.u4(fieldIdx));
+            out.annotate(4, "      annotations_off: " +
+                    Hex.u4(annotationsOff));
+        }
+
+        out.writeInt(fieldIdx);
+        out.writeInt(annotationsOff);
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return field.toHuman() + ": " + annotations;
+    }
+
+    /**
+     * Gets the field this item is for.
+     *
+     * @return {@code non-null;} the field
+     */
+    public CstFieldRef getField() {
+        return field;
+    }
+
+    /**
+     * Gets the associated annotations.
+     *
+     * @return {@code non-null;} the annotations
+     */
+    public Annotations getAnnotations() {
+        return annotations.getAnnotations();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/FieldIdItem.java b/dexgen/src/com/android/dexgen/dex/file/FieldIdItem.java
new file mode 100644
index 0000000..4d3721e
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/FieldIdItem.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.CstFieldRef;
+
+/**
+ * Representation of a field reference inside a Dalvik file.
+ */
+public final class FieldIdItem extends MemberIdItem {
+    /**
+     * Constructs an instance.
+     *
+     * @param field {@code non-null;} the constant for the field
+     */
+    public FieldIdItem(CstFieldRef field) {
+        super(field);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_FIELD_ID_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        super.addContents(file);
+
+        TypeIdsSection typeIds = file.getTypeIds();
+        typeIds.intern(getFieldRef().getType());
+    }
+
+    /**
+     * Gets the field constant.
+     *
+     * @return {@code non-null;} the constant
+     */
+    public CstFieldRef getFieldRef() {
+        return (CstFieldRef) getRef();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int getTypoidIdx(DexFile file) {
+        TypeIdsSection typeIds = file.getTypeIds();
+        return typeIds.indexOf(getFieldRef().getType());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String getTypoidName() {
+        return "type_idx";
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/FieldIdsSection.java b/dexgen/src/com/android/dexgen/dex/file/FieldIdsSection.java
new file mode 100644
index 0000000..65177e4
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/FieldIdsSection.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstFieldRef;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+import java.util.Collection;
+import java.util.TreeMap;
+
+/**
+ * Field refs list section of a {@code .dex} file.
+ */
+public final class FieldIdsSection extends MemberIdsSection {
+    /**
+     * {@code non-null;} map from field constants to {@link
+     * FieldIdItem} instances
+     */
+    private final TreeMap<CstFieldRef, FieldIdItem> fieldIds;
+
+    /**
+     * Constructs an instance. The file offset is initially unknown.
+     *
+     * @param file {@code non-null;} file that this instance is part of
+     */
+    public FieldIdsSection(DexFile file) {
+        super("field_ids", file);
+
+        fieldIds = new TreeMap<CstFieldRef, FieldIdItem>();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection<? extends Item> items() {
+        return fieldIds.values();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public IndexedItem get(Constant cst) {
+        if (cst == null) {
+            throw new NullPointerException("cst == null");
+        }
+
+        throwIfNotPrepared();
+
+        IndexedItem result = fieldIds.get((CstFieldRef) cst);
+
+        if (result == null) {
+            throw new IllegalArgumentException("not found");
+        }
+
+        return result;
+    }
+
+    /**
+     * Writes the portion of the file header that refers to this instance.
+     *
+     * @param out {@code non-null;} where to write
+     */
+    public void writeHeaderPart(AnnotatedOutput out) {
+        throwIfNotPrepared();
+
+        int sz = fieldIds.size();
+        int offset = (sz == 0) ? 0 : getFileOffset();
+
+        if (out.annotates()) {
+            out.annotate(4, "field_ids_size:  " + Hex.u4(sz));
+            out.annotate(4, "field_ids_off:   " + Hex.u4(offset));
+        }
+
+        out.writeInt(sz);
+        out.writeInt(offset);
+    }
+
+    /**
+     * Interns an element into this instance.
+     *
+     * @param field {@code non-null;} the reference to intern
+     * @return {@code non-null;} the interned reference
+     */
+    public FieldIdItem intern(CstFieldRef field) {
+        if (field == null) {
+            throw new NullPointerException("field == null");
+        }
+
+        throwIfPrepared();
+
+        FieldIdItem result = fieldIds.get(field);
+
+        if (result == null) {
+            result = new FieldIdItem(field);
+            fieldIds.put(field, result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Gets the index of the given reference, which must have been added
+     * to this instance.
+     *
+     * @param ref {@code non-null;} the reference to look up
+     * @return {@code >= 0;} the reference's index
+     */
+    public int indexOf(CstFieldRef ref) {
+        if (ref == null) {
+            throw new NullPointerException("ref == null");
+        }
+
+        throwIfNotPrepared();
+
+        FieldIdItem item = fieldIds.get(ref);
+
+        if (item == null) {
+            throw new IllegalArgumentException("not found");
+        }
+
+        return item.getIndex();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/HeaderItem.java b/dexgen/src/com/android/dexgen/dex/file/HeaderItem.java
new file mode 100644
index 0000000..ed04e25
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/HeaderItem.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+/**
+ * File header section of a {@code .dex} file.
+ */
+public final class HeaderItem extends IndexedItem {
+    /**
+     * {@code non-null;} the file format magic number, represented as the
+     * low-order bytes of a string
+     */
+    private static final String MAGIC = "dex\n035\0";
+
+    /** size of this section, in bytes */
+    private static final int HEADER_SIZE = 0x70;
+
+    /** the endianness tag */
+    private static final int ENDIAN_TAG = 0x12345678;
+
+    /**
+     * Constructs an instance.
+     */
+    public HeaderItem() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_HEADER_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int writeSize() {
+        return HEADER_SIZE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        // Nothing to do here.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(DexFile file, AnnotatedOutput out) {
+        int mapOff = file.getMap().getFileOffset();
+        Section firstDataSection = file.getFirstDataSection();
+        Section lastDataSection = file.getLastDataSection();
+        int dataOff = firstDataSection.getFileOffset();
+        int dataSize = lastDataSection.getFileOffset() +
+            lastDataSection.writeSize() - dataOff;
+
+        if (out.annotates()) {
+            out.annotate(8, "magic: " + new CstUtf8(MAGIC).toQuoted());
+            out.annotate(4, "checksum");
+            out.annotate(20, "signature");
+            out.annotate(4, "file_size:       " +
+                         Hex.u4(file.getFileSize()));
+            out.annotate(4, "header_size:     " + Hex.u4(HEADER_SIZE));
+            out.annotate(4, "endian_tag:      " + Hex.u4(ENDIAN_TAG));
+            out.annotate(4, "link_size:       0");
+            out.annotate(4, "link_off:        0");
+            out.annotate(4, "map_off:         " + Hex.u4(mapOff));
+        }
+
+        // Write the magic number.
+        for (int i = 0; i < 8; i++) {
+            out.writeByte(MAGIC.charAt(i));
+        }
+
+        // Leave space for the checksum and signature.
+        out.writeZeroes(24);
+
+        out.writeInt(file.getFileSize());
+        out.writeInt(HEADER_SIZE);
+        out.writeInt(ENDIAN_TAG);
+
+        /*
+         * Write zeroes for the link size and data, as the output
+         * isn't a staticly linked file.
+         */
+        out.writeZeroes(8);
+
+        out.writeInt(mapOff);
+
+        // Write out each section's respective header part.
+        file.getStringIds().writeHeaderPart(out);
+        file.getTypeIds().writeHeaderPart(out);
+        file.getProtoIds().writeHeaderPart(out);
+        file.getFieldIds().writeHeaderPart(out);
+        file.getMethodIds().writeHeaderPart(out);
+        file.getClassDefs().writeHeaderPart(out);
+
+        if (out.annotates()) {
+            out.annotate(4, "data_size:       " + Hex.u4(dataSize));
+            out.annotate(4, "data_off:        " + Hex.u4(dataOff));
+        }
+
+        out.writeInt(dataSize);
+        out.writeInt(dataOff);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/HeaderSection.java b/dexgen/src/com/android/dexgen/dex/file/HeaderSection.java
new file mode 100644
index 0000000..967a90a
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/HeaderSection.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.Constant;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * File header section of a {@code .dex} file.
+ */
+public final class HeaderSection extends UniformItemSection {
+    /** {@code non-null;} the list of the one item in the section */
+    private final List<HeaderItem> list;
+
+    /**
+     * Constructs an instance. The file offset is initially unknown.
+     *
+     * @param file {@code non-null;} file that this instance is part of
+     */
+    public HeaderSection(DexFile file) {
+        super(null, file, 4);
+
+        HeaderItem item = new HeaderItem();
+        item.setIndex(0);
+
+        this.list = Collections.singletonList(item);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public IndexedItem get(Constant cst) {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection<? extends Item> items() {
+        return list;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void orderItems() {
+        // Nothing to do here.
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/IdItem.java b/dexgen/src/com/android/dexgen/dex/file/IdItem.java
new file mode 100644
index 0000000..0f8301e
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/IdItem.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.CstType;
+
+/**
+ * Representation of a reference to an item inside a Dalvik file.
+ */
+public abstract class IdItem extends IndexedItem {
+    /**
+     * {@code non-null;} the type constant for the defining class of
+     * the reference
+     */
+    private final CstType type;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param type {@code non-null;} the type constant for the defining
+     * class of the reference
+     */
+    public IdItem(CstType type) {
+        if (type == null) {
+            throw new NullPointerException("type == null");
+        }
+
+        this.type = type;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        TypeIdsSection typeIds = file.getTypeIds();
+        typeIds.intern(type);
+    }
+
+    /**
+     * Gets the type constant for the defining class of the
+     * reference.
+     *
+     * @return {@code non-null;} the type constant
+     */
+    public final CstType getDefiningClass() {
+        return type;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/IndexedItem.java b/dexgen/src/com/android/dexgen/dex/file/IndexedItem.java
new file mode 100644
index 0000000..cdc73cb
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/IndexedItem.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+/**
+ * An item in a Dalvik file which is referenced by index.
+ */
+public abstract class IndexedItem extends Item {
+    /** {@code >= -1;} assigned index of the item, or {@code -1} if not
+     * yet assigned */
+    private int index;
+
+    /**
+     * Constructs an instance. The index is initially unassigned.
+     */
+    public IndexedItem() {
+        index = -1;
+    }
+
+    /**
+     * Gets whether or not this instance has been assigned an index.
+     *
+     * @return {@code true} iff this instance has been assigned an index
+     */
+    public final boolean hasIndex() {
+        return (index >= 0);
+    }
+
+    /**
+     * Gets the item index.
+     *
+     * @return {@code >= 0;} the index
+     * @throws RuntimeException thrown if the item index is not yet assigned
+     */
+    public final int getIndex() {
+        if (index < 0) {
+            throw new RuntimeException("index not yet set");
+        }
+
+        return index;
+    }
+
+    /**
+     * Sets the item index. This method may only ever be called once
+     * per instance, and this will throw a {@code RuntimeException} if
+     * called a second (or subsequent) time.
+     *
+     * @param index {@code >= 0;} the item index
+     */
+    public final void setIndex(int index) {
+        if (this.index != -1) {
+            throw new RuntimeException("index already set");
+        }
+
+        this.index = index;
+    }
+
+    /**
+     * Gets the index of this item as a string, suitable for including in
+     * annotations.
+     *
+     * @return {@code non-null;} the index string
+     */
+    public final String indexString() {
+        return '[' + Integer.toHexString(index) + ']';
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/Item.java b/dexgen/src/com/android/dexgen/dex/file/Item.java
new file mode 100644
index 0000000..45cdc94
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/Item.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.util.AnnotatedOutput;
+
+/**
+ * Base class for any structurally-significant and (potentially)
+ * repeated piece of a Dalvik file.
+ */
+public abstract class Item {
+    /**
+     * Constructs an instance.
+     */
+    public Item() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Returns the item type for this instance.
+     *
+     * @return {@code non-null;} the item type
+     */
+    public abstract ItemType itemType();
+
+    /**
+     * Returns the human name for the particular type of item this
+     * instance is.
+     *
+     * @return {@code non-null;} the name
+     */
+    public final String typeName() {
+        return itemType().toHuman();
+    }
+
+    /**
+     * Gets the size of this instance when written, in bytes.
+     *
+     * @return {@code >= 0;} the write size
+     */
+    public abstract int writeSize();
+
+    /**
+     * Populates a {@link DexFile} with items from within this instance.
+     * This will <i>not</i> add an item to the file for this instance itself
+     * (which should have been done by whatever refers to this instance).
+     *
+     * <p><b>Note:</b> Subclasses must override this to do something
+     * appropriate.</p>
+     *
+     * @param file {@code non-null;} the file to populate
+     */
+    public abstract void addContents(DexFile file);
+
+    /**
+     * Writes the representation of this instance to the given data section,
+     * using the given {@link DexFile} to look things up as needed.
+     * If this instance keeps track of its offset, then this method will
+     * note the written offset and will also throw an exception if this
+     * instance has already been written.
+     *
+     * @param file {@code non-null;} the file to use for reference
+     * @param out {@code non-null;} where to write to
+     */
+    public abstract void writeTo(DexFile file, AnnotatedOutput out);
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/ItemType.java b/dexgen/src/com/android/dexgen/dex/file/ItemType.java
new file mode 100644
index 0000000..b3e32d0
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/ItemType.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.util.ToHuman;
+
+/**
+ * Enumeration of all the top-level item types.
+ */
+public enum ItemType implements ToHuman {
+    TYPE_HEADER_ITEM(               0x0000, "header_item"),
+    TYPE_STRING_ID_ITEM(            0x0001, "string_id_item"),
+    TYPE_TYPE_ID_ITEM(              0x0002, "type_id_item"),
+    TYPE_PROTO_ID_ITEM(             0x0003, "proto_id_item"),
+    TYPE_FIELD_ID_ITEM(             0x0004, "field_id_item"),
+    TYPE_METHOD_ID_ITEM(            0x0005, "method_id_item"),
+    TYPE_CLASS_DEF_ITEM(            0x0006, "class_def_item"),
+    TYPE_MAP_LIST(                  0x1000, "map_list"),
+    TYPE_TYPE_LIST(                 0x1001, "type_list"),
+    TYPE_ANNOTATION_SET_REF_LIST(   0x1002, "annotation_set_ref_list"),
+    TYPE_ANNOTATION_SET_ITEM(       0x1003, "annotation_set_item"),
+    TYPE_CLASS_DATA_ITEM(           0x2000, "class_data_item"),
+    TYPE_CODE_ITEM(                 0x2001, "code_item"),
+    TYPE_STRING_DATA_ITEM(          0x2002, "string_data_item"),
+    TYPE_DEBUG_INFO_ITEM(           0x2003, "debug_info_item"),
+    TYPE_ANNOTATION_ITEM(           0x2004, "annotation_item"),
+    TYPE_ENCODED_ARRAY_ITEM(        0x2005, "encoded_array_item"),
+    TYPE_ANNOTATIONS_DIRECTORY_ITEM(0x2006, "annotations_directory_item"),
+    TYPE_MAP_ITEM(                  -1,     "map_item"),
+    TYPE_TYPE_ITEM(                 -1,     "type_item"),
+    TYPE_EXCEPTION_HANDLER_ITEM(    -1,     "exception_handler_item"),
+    TYPE_ANNOTATION_SET_REF_ITEM(   -1,     "annotation_set_ref_item");
+
+    /** value when represented in a {@link MapItem} */
+    private final int mapValue;
+
+    /** {@code non-null;} name of the type */
+    private final String typeName;
+
+    /** {@code non-null;} the short human name */
+    private final String humanName;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param mapValue value when represented in a {@link MapItem}
+     * @param typeName {@code non-null;} name of the type
+     */
+    private ItemType(int mapValue, String typeName) {
+        this.mapValue = mapValue;
+        this.typeName = typeName;
+
+        // Make the human name.
+        String human = typeName;
+        if (human.endsWith("_item")) {
+            human = human.substring(0, human.length() - 5);
+        }
+        this.humanName = human.replace('_', ' ');
+    }
+
+    /**
+     * Gets the map value.
+     *
+     * @return the map value
+     */
+    public int getMapValue() {
+        return mapValue;
+    }
+
+    /**
+     * Gets the type name.
+     *
+     * @return {@code non-null;} the type name
+     */
+    public String getTypeName() {
+        return typeName;
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return humanName;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/MapItem.java b/dexgen/src/com/android/dexgen/dex/file/MapItem.java
new file mode 100644
index 0000000..02472d4
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/MapItem.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+import java.util.ArrayList;
+
+/**
+ * Class that represents a map item.
+ */
+public final class MapItem extends OffsettedItem {
+    /** file alignment of this class, in bytes */
+    private static final int ALIGNMENT = 4;
+
+    /** write size of this class, in bytes: three {@code uint}s */
+    private static final int WRITE_SIZE = (4 * 3);
+
+    /** {@code non-null;} item type this instance covers */
+    private final ItemType type;
+
+    /** {@code non-null;} section this instance covers */
+    private final Section section;
+
+    /**
+     * {@code null-ok;} first item covered or {@code null} if this is
+     * a self-reference
+     */
+    private final Item firstItem;
+
+    /**
+     * {@code null-ok;} last item covered or {@code null} if this is
+     * a self-reference
+     */
+    private final Item lastItem;
+
+    /**
+     * {@code > 0;} count of items covered; {@code 1} if this
+     * is a self-reference
+     */
+    private final int itemCount;
+
+    /**
+     * Constructs a list item with instances of this class representing
+     * the contents of the given array of sections, adding it to the
+     * given map section.
+     *
+     * @param sections {@code non-null;} the sections
+     * @param mapSection {@code non-null;} the section that the resulting map
+     * should be added to; it should be empty on entry to this method
+     */
+    public static void addMap(Section[] sections,
+            MixedItemSection mapSection) {
+        if (sections == null) {
+            throw new NullPointerException("sections == null");
+        }
+
+        if (mapSection.items().size() != 0) {
+            throw new IllegalArgumentException(
+                    "mapSection.items().size() != 0");
+        }
+
+        ArrayList<MapItem> items = new ArrayList<MapItem>(50);
+
+        for (Section section : sections) {
+            ItemType currentType = null;
+            Item firstItem = null;
+            Item lastItem = null;
+            int count = 0;
+
+            for (Item item : section.items()) {
+                ItemType type = item.itemType();
+                if (type != currentType) {
+                    if (count != 0) {
+                        items.add(new MapItem(currentType, section,
+                                        firstItem, lastItem, count));
+                    }
+                    currentType = type;
+                    firstItem = item;
+                    count = 0;
+                }
+                lastItem = item;
+                count++;
+            }
+
+            if (count != 0) {
+                // Add a MapItem for the final items in the section.
+                items.add(new MapItem(currentType, section,
+                                firstItem, lastItem, count));
+            } else if (section == mapSection) {
+                // Add a MapItem for the self-referential section.
+                items.add(new MapItem(mapSection));
+            }
+        }
+
+        mapSection.add(
+                new UniformListItem<MapItem>(ItemType.TYPE_MAP_LIST, items));
+    }
+
+    /**
+     * Constructs an instance.
+     *
+     * @param type {@code non-null;} item type this instance covers
+     * @param section {@code non-null;} section this instance covers
+     * @param firstItem {@code non-null;} first item covered
+     * @param lastItem {@code non-null;} last item covered
+     * @param itemCount {@code > 0;} count of items covered
+     */
+    private MapItem(ItemType type, Section section, Item firstItem,
+            Item lastItem, int itemCount) {
+        super(ALIGNMENT, WRITE_SIZE);
+
+        if (type == null) {
+            throw new NullPointerException("type == null");
+        }
+
+        if (section == null) {
+            throw new NullPointerException("section == null");
+        }
+
+        if (firstItem == null) {
+            throw new NullPointerException("firstItem == null");
+        }
+
+        if (lastItem == null) {
+            throw new NullPointerException("lastItem == null");
+        }
+
+        if (itemCount <= 0) {
+            throw new IllegalArgumentException("itemCount <= 0");
+        }
+
+        this.type = type;
+        this.section = section;
+        this.firstItem = firstItem;
+        this.lastItem = lastItem;
+        this.itemCount = itemCount;
+    }
+
+    /**
+     * Constructs a self-referential instance. This instance is meant to
+     * represent the section containing the {@code map_list}.
+     *
+     * @param section {@code non-null;} section this instance covers
+     */
+    private MapItem(Section section) {
+        super(ALIGNMENT, WRITE_SIZE);
+
+        if (section == null) {
+            throw new NullPointerException("section == null");
+        }
+
+        this.type = ItemType.TYPE_MAP_LIST;
+        this.section = section;
+        this.firstItem = null;
+        this.lastItem = null;
+        this.itemCount = 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_MAP_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer(100);
+
+        sb.append(getClass().getName());
+        sb.append('{');
+        sb.append(section.toString());
+        sb.append(' ');
+        sb.append(type.toHuman());
+        sb.append('}');
+
+        return sb.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        // We have nothing to add.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final String toHuman() {
+        return toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void writeTo0(DexFile file, AnnotatedOutput out) {
+        int value = type.getMapValue();
+        int offset;
+
+        if (firstItem == null) {
+            offset = section.getFileOffset();
+        } else {
+            offset = section.getAbsoluteItemOffset(firstItem);
+        }
+
+        if (out.annotates()) {
+            out.annotate(0, offsetString() + ' ' + type.getTypeName() +
+                    " map");
+            out.annotate(2, "  type:   " + Hex.u2(value) + " // " +
+                    type.toString());
+            out.annotate(2, "  unused: 0");
+            out.annotate(4, "  size:   " + Hex.u4(itemCount));
+            out.annotate(4, "  offset: " + Hex.u4(offset));
+        }
+
+        out.writeShort(value);
+        out.writeShort(0); // unused
+        out.writeInt(itemCount);
+        out.writeInt(offset);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/MemberIdItem.java b/dexgen/src/com/android/dexgen/dex/file/MemberIdItem.java
new file mode 100644
index 0000000..d638f07
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/MemberIdItem.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.CstMemberRef;
+import com.android.dexgen.rop.cst.CstNat;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Representation of a member (field or method) reference inside a
+ * Dalvik file.
+ */
+public abstract class MemberIdItem extends IdItem {
+    /** size of instances when written out to a file, in bytes */
+    public static final int WRITE_SIZE = 8;
+
+    /** {@code non-null;} the constant for the member */
+    private final CstMemberRef cst;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param cst {@code non-null;} the constant for the member
+     */
+    public MemberIdItem(CstMemberRef cst) {
+        super(cst.getDefiningClass());
+
+        this.cst = cst;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int writeSize() {
+        return WRITE_SIZE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        super.addContents(file);
+
+        StringIdsSection stringIds = file.getStringIds();
+        stringIds.intern(getRef().getNat().getName());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final void writeTo(DexFile file, AnnotatedOutput out) {
+        TypeIdsSection typeIds = file.getTypeIds();
+        StringIdsSection stringIds = file.getStringIds();
+        CstNat nat = cst.getNat();
+        int classIdx = typeIds.indexOf(getDefiningClass());
+        int nameIdx = stringIds.indexOf(nat.getName());
+        int typoidIdx = getTypoidIdx(file);
+
+        if (out.annotates()) {
+            out.annotate(0, indexString() + ' ' + cst.toHuman());
+            out.annotate(2, "  class_idx: " + Hex.u2(classIdx));
+            out.annotate(2, String.format("  %-10s %s", getTypoidName() + ':',
+                            Hex.u2(typoidIdx)));
+            out.annotate(4, "  name_idx:  " + Hex.u4(nameIdx));
+        }
+
+        out.writeShort(classIdx);
+        out.writeShort(typoidIdx);
+        out.writeInt(nameIdx);
+    }
+
+    /**
+     * Returns the index of the type-like thing associated with
+     * this item, in order that it may be written out. Subclasses must
+     * override this to get whatever it is they need to store.
+     *
+     * @param file {@code non-null;} the file being written
+     * @return the index in question
+     */
+    protected abstract int getTypoidIdx(DexFile file);
+
+    /**
+     * Returns the field name of the type-like thing associated with
+     * this item, for listing-generating purposes. Subclasses must override
+     * this.
+     *
+     * @return {@code non-null;} the name in question
+     */
+    protected abstract String getTypoidName();
+
+    /**
+     * Gets the member constant.
+     *
+     * @return {@code non-null;} the constant
+     */
+    public final CstMemberRef getRef() {
+        return cst;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/MemberIdsSection.java b/dexgen/src/com/android/dexgen/dex/file/MemberIdsSection.java
new file mode 100644
index 0000000..dcfca30
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/MemberIdsSection.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+/**
+ * Member (field or method) refs list section of a {@code .dex} file.
+ */
+public abstract class MemberIdsSection extends UniformItemSection {
+    /**
+     * Constructs an instance. The file offset is initially unknown.
+     *
+     * @param name {@code null-ok;} the name of this instance, for annotation
+     * purposes
+     * @param file {@code non-null;} file that this instance is part of
+     */
+    public MemberIdsSection(String name, DexFile file) {
+        super(name, file, 4);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void orderItems() {
+        int idx = 0;
+
+        for (Object i : items()) {
+            ((MemberIdItem) i).setIndex(idx);
+            idx++;
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/MethodAnnotationStruct.java b/dexgen/src/com/android/dexgen/dex/file/MethodAnnotationStruct.java
new file mode 100644
index 0000000..e511f10
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/MethodAnnotationStruct.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.annotation.Annotations;
+import com.android.dexgen.rop.cst.CstMethodRef;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.ToHuman;
+
+/**
+ * Association of a method and its annotations.
+ */
+public final class MethodAnnotationStruct
+        implements ToHuman, Comparable<MethodAnnotationStruct> {
+    /** {@code non-null;} the method in question */
+    private final CstMethodRef method;
+
+    /** {@code non-null;} the associated annotations */
+    private AnnotationSetItem annotations;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param method {@code non-null;} the method in question
+     * @param annotations {@code non-null;} the associated annotations
+     */
+    public MethodAnnotationStruct(CstMethodRef method,
+            AnnotationSetItem annotations) {
+        if (method == null) {
+            throw new NullPointerException("method == null");
+        }
+
+        if (annotations == null) {
+            throw new NullPointerException("annotations == null");
+        }
+
+        this.method = method;
+        this.annotations = annotations;
+    }
+
+    /** {@inheritDoc} */
+    public int hashCode() {
+        return method.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    public boolean equals(Object other) {
+        if (! (other instanceof MethodAnnotationStruct)) {
+            return false;
+        }
+
+        return method.equals(((MethodAnnotationStruct) other).method);
+    }
+
+    /** {@inheritDoc} */
+    public int compareTo(MethodAnnotationStruct other) {
+        return method.compareTo(other.method);
+    }
+
+    /** {@inheritDoc} */
+    public void addContents(DexFile file) {
+        MethodIdsSection methodIds = file.getMethodIds();
+        MixedItemSection wordData = file.getWordData();
+
+        methodIds.intern(method);
+        annotations = wordData.intern(annotations);
+    }
+
+    /** {@inheritDoc} */
+    public void writeTo(DexFile file, AnnotatedOutput out) {
+        int methodIdx = file.getMethodIds().indexOf(method);
+        int annotationsOff = annotations.getAbsoluteOffset();
+
+        if (out.annotates()) {
+            out.annotate(0, "    " + method.toHuman());
+            out.annotate(4, "      method_idx:      " + Hex.u4(methodIdx));
+            out.annotate(4, "      annotations_off: " +
+                    Hex.u4(annotationsOff));
+        }
+
+        out.writeInt(methodIdx);
+        out.writeInt(annotationsOff);
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return method.toHuman() + ": " + annotations;
+    }
+
+    /**
+     * Gets the method this item is for.
+     *
+     * @return {@code non-null;} the method
+     */
+    public CstMethodRef getMethod() {
+        return method;
+    }
+
+    /**
+     * Gets the associated annotations.
+     *
+     * @return {@code non-null;} the annotations
+     */
+    public Annotations getAnnotations() {
+        return annotations.getAnnotations();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/MethodIdItem.java b/dexgen/src/com/android/dexgen/dex/file/MethodIdItem.java
new file mode 100644
index 0000000..da14e19
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/MethodIdItem.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.CstBaseMethodRef;
+
+/**
+ * Representation of a method reference inside a Dalvik file.
+ */
+public final class MethodIdItem extends MemberIdItem {
+    /**
+     * Constructs an instance.
+     *
+     * @param method {@code non-null;} the constant for the method
+     */
+    public MethodIdItem(CstBaseMethodRef method) {
+        super(method);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_METHOD_ID_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        super.addContents(file);
+
+        ProtoIdsSection protoIds = file.getProtoIds();
+        protoIds.intern(getMethodRef().getPrototype());
+    }
+
+    /**
+     * Gets the method constant.
+     *
+     * @return {@code non-null;} the constant
+     */
+    public CstBaseMethodRef getMethodRef() {
+        return (CstBaseMethodRef) getRef();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int getTypoidIdx(DexFile file) {
+        ProtoIdsSection protoIds = file.getProtoIds();
+        return protoIds.indexOf(getMethodRef().getPrototype());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected String getTypoidName() {
+        return "proto_idx";
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/MethodIdsSection.java b/dexgen/src/com/android/dexgen/dex/file/MethodIdsSection.java
new file mode 100644
index 0000000..3a06af7
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/MethodIdsSection.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstBaseMethodRef;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+import java.util.Collection;
+import java.util.TreeMap;
+
+/**
+ * Method refs list section of a {@code .dex} file.
+ */
+public final class MethodIdsSection extends MemberIdsSection {
+    /**
+     * {@code non-null;} map from method constants to {@link
+     * MethodIdItem} instances
+     */
+    private final TreeMap<CstBaseMethodRef, MethodIdItem> methodIds;
+
+    /**
+     * Constructs an instance. The file offset is initially unknown.
+     *
+     * @param file {@code non-null;} file that this instance is part of
+     */
+    public MethodIdsSection(DexFile file) {
+        super("method_ids", file);
+
+        methodIds = new TreeMap<CstBaseMethodRef, MethodIdItem>();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection<? extends Item> items() {
+        return methodIds.values();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public IndexedItem get(Constant cst) {
+        if (cst == null) {
+            throw new NullPointerException("cst == null");
+        }
+
+        throwIfNotPrepared();
+
+        IndexedItem result = methodIds.get((CstBaseMethodRef) cst);
+
+        if (result == null) {
+            throw new IllegalArgumentException("not found");
+        }
+
+        return result;
+    }
+
+    /**
+     * Writes the portion of the file header that refers to this instance.
+     *
+     * @param out {@code non-null;} where to write
+     */
+    public void writeHeaderPart(AnnotatedOutput out) {
+        throwIfNotPrepared();
+
+        int sz = methodIds.size();
+        int offset = (sz == 0) ? 0 : getFileOffset();
+
+        if (out.annotates()) {
+            out.annotate(4, "method_ids_size: " + Hex.u4(sz));
+            out.annotate(4, "method_ids_off:  " + Hex.u4(offset));
+        }
+
+        out.writeInt(sz);
+        out.writeInt(offset);
+    }
+
+    /**
+     * Interns an element into this instance.
+     *
+     * @param method {@code non-null;} the reference to intern
+     * @return {@code non-null;} the interned reference
+     */
+    public MethodIdItem intern(CstBaseMethodRef method) {
+        if (method == null) {
+            throw new NullPointerException("method == null");
+        }
+
+        throwIfPrepared();
+
+        MethodIdItem result = methodIds.get(method);
+
+        if (result == null) {
+            result = new MethodIdItem(method);
+            methodIds.put(method, result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Gets the index of the given reference, which must have been added
+     * to this instance.
+     *
+     * @param ref {@code non-null;} the reference to look up
+     * @return {@code >= 0;} the reference's index
+     */
+    public int indexOf(CstBaseMethodRef ref) {
+        if (ref == null) {
+            throw new NullPointerException("ref == null");
+        }
+
+        throwIfNotPrepared();
+
+        MethodIdItem item = methodIds.get(ref);
+
+        if (item == null) {
+            throw new IllegalArgumentException("not found");
+        }
+
+        return item.getIndex();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/MixedItemSection.java b/dexgen/src/com/android/dexgen/dex/file/MixedItemSection.java
new file mode 100644
index 0000000..2fda33b
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/MixedItemSection.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.ExceptionWithContext;
+import com.android.dexgen.util.Hex;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.TreeMap;
+
+/**
+ * A section of a {@code .dex} file which consists of a sequence of
+ * {@link OffsettedItem} objects, which may each be of a different concrete
+ * class and/or size.
+ *
+ * <b>Note:</b> It is invalid for an item in an instance of this class to
+ * have a larger alignment requirement than the alignment of this instance.
+ */
+public final class MixedItemSection extends Section {
+    static enum SortType {
+        /** no sorting */
+        NONE,
+
+        /** sort by type only */
+        TYPE,
+
+        /** sort in class-major order, with instances sorted per-class */
+        INSTANCE;
+    };
+
+    /** {@code non-null;} sorter which sorts instances by type */
+    private static final Comparator<OffsettedItem> TYPE_SORTER =
+        new Comparator<OffsettedItem>() {
+        public int compare(OffsettedItem item1, OffsettedItem item2) {
+            ItemType type1 = item1.itemType();
+            ItemType type2 = item2.itemType();
+            return type1.compareTo(type2);
+        }
+    };
+
+    /** {@code non-null;} the items in this part */
+    private final ArrayList<OffsettedItem> items;
+
+    /** {@code non-null;} items that have been explicitly interned */
+    private final HashMap<OffsettedItem, OffsettedItem> interns;
+
+    /** {@code non-null;} how to sort the items */
+    private final SortType sort;
+
+    /**
+     * {@code >= -1;} the current size of this part, in bytes, or {@code -1}
+     * if not yet calculated
+     */
+    private int writeSize;
+
+    /**
+     * Constructs an instance. The file offset is initially unknown.
+     *
+     * @param name {@code null-ok;} the name of this instance, for annotation
+     * purposes
+     * @param file {@code non-null;} file that this instance is part of
+     * @param alignment {@code > 0;} alignment requirement for the final output;
+     * must be a power of 2
+     * @param sort how the items should be sorted in the final output
+     */
+    public MixedItemSection(String name, DexFile file, int alignment,
+            SortType sort) {
+        super(name, file, alignment);
+
+        this.items = new ArrayList<OffsettedItem>(100);
+        this.interns = new HashMap<OffsettedItem, OffsettedItem>(100);
+        this.sort = sort;
+        this.writeSize = -1;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection<? extends Item> items() {
+        return items;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int writeSize() {
+        throwIfNotPrepared();
+        return writeSize;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getAbsoluteItemOffset(Item item) {
+        OffsettedItem oi = (OffsettedItem) item;
+        return oi.getAbsoluteOffset();
+    }
+
+    /**
+     * Gets the size of this instance, in items.
+     *
+     * @return {@code >= 0;} the size
+     */
+    public int size() {
+        return items.size();
+    }
+
+    /**
+     * Writes the portion of the file header that refers to this instance.
+     *
+     * @param out {@code non-null;} where to write
+     */
+    public void writeHeaderPart(AnnotatedOutput out) {
+        throwIfNotPrepared();
+
+        if (writeSize == -1) {
+            throw new RuntimeException("write size not yet set");
+        }
+
+        int sz = writeSize;
+        int offset = (sz == 0) ? 0 : getFileOffset();
+        String name = getName();
+
+        if (name == null) {
+            name = "<unnamed>";
+        }
+
+        int spaceCount = 15 - name.length();
+        char[] spaceArr = new char[spaceCount];
+        Arrays.fill(spaceArr, ' ');
+        String spaces = new String(spaceArr);
+
+        if (out.annotates()) {
+            out.annotate(4, name + "_size:" + spaces + Hex.u4(sz));
+            out.annotate(4, name + "_off: " + spaces + Hex.u4(offset));
+        }
+
+        out.writeInt(sz);
+        out.writeInt(offset);
+    }
+
+    /**
+     * Adds an item to this instance. This will in turn tell the given item
+     * that it has been added to this instance. It is invalid to add the
+     * same item to more than one instance, nor to add the same items
+     * multiple times to a single instance.
+     *
+     * @param item {@code non-null;} the item to add
+     */
+    public void add(OffsettedItem item) {
+        throwIfPrepared();
+
+        try {
+            if (item.getAlignment() > getAlignment()) {
+                throw new IllegalArgumentException(
+                        "incompatible item alignment");
+            }
+        } catch (NullPointerException ex) {
+            // Elucidate the exception.
+            throw new NullPointerException("item == null");
+        }
+
+        items.add(item);
+    }
+
+    /**
+     * Interns an item in this instance, returning the interned instance
+     * (which may not be the one passed in). This will add the item if no
+     * equal item has been added.
+     *
+     * @param item {@code non-null;} the item to intern
+     * @return {@code non-null;} the equivalent interned instance
+     */
+    public <T extends OffsettedItem> T intern(T item) {
+        throwIfPrepared();
+
+        OffsettedItem result = interns.get(item);
+
+        if (result != null) {
+            return (T) result;
+        }
+
+        add(item);
+        interns.put(item, item);
+        return item;
+    }
+
+    /**
+     * Gets an item which was previously interned.
+     *
+     * @param item {@code non-null;} the item to look for
+     * @return {@code non-null;} the equivalent already-interned instance
+     */
+    public <T extends OffsettedItem> T get(T item) {
+        throwIfNotPrepared();
+
+        OffsettedItem result = interns.get(item);
+
+        if (result != null) {
+            return (T) result;
+        }
+
+        throw new NoSuchElementException(item.toString());
+    }
+
+    /**
+     * Writes an index of contents of the items in this instance of the
+     * given type. If there are none, this writes nothing. If there are any,
+     * then the index is preceded by the given intro string.
+     *
+     * @param out {@code non-null;} where to write to
+     * @param itemType {@code non-null;} the item type of interest
+     * @param intro {@code non-null;} the introductory string for non-empty indices
+     */
+    public void writeIndexAnnotation(AnnotatedOutput out, ItemType itemType,
+            String intro) {
+        throwIfNotPrepared();
+
+        TreeMap<String, OffsettedItem> index =
+            new TreeMap<String, OffsettedItem>();
+
+        for (OffsettedItem item : items) {
+            if (item.itemType() == itemType) {
+                String label = item.toHuman();
+                index.put(label, item);
+            }
+        }
+
+        if (index.size() == 0) {
+            return;
+        }
+
+        out.annotate(0, intro);
+
+        for (Map.Entry<String, OffsettedItem> entry : index.entrySet()) {
+            String label = entry.getKey();
+            OffsettedItem item = entry.getValue();
+            out.annotate(0, item.offsetString() + ' ' + label + '\n');
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void prepare0() {
+        DexFile file = getFile();
+
+        /*
+         * It's okay for new items to be added as a result of an
+         * addContents() call; we just have to deal with the possibility.
+         */
+
+        int i = 0;
+        for (;;) {
+            int sz = items.size();
+            if (i >= sz) {
+                break;
+            }
+
+            for (/*i*/; i < sz; i++) {
+                OffsettedItem one = items.get(i);
+                one.addContents(file);
+            }
+        }
+    }
+
+    /**
+     * Places all the items in this instance at particular offsets. This
+     * will call {@link OffsettedItem#place} on each item. If an item
+     * does not know its write size before the call to {@code place},
+     * it is that call which is responsible for setting the write size.
+     * This method may only be called once per instance; subsequent calls
+     * will throw an exception.
+     */
+    public void placeItems() {
+        throwIfNotPrepared();
+
+        switch (sort) {
+            case INSTANCE: {
+                Collections.sort(items);
+                break;
+            }
+            case TYPE: {
+                Collections.sort(items, TYPE_SORTER);
+                break;
+            }
+        }
+
+        int sz = items.size();
+        int outAt = 0;
+        for (int i = 0; i < sz; i++) {
+            OffsettedItem one = items.get(i);
+            try {
+                int placedAt = one.place(this, outAt);
+
+                if (placedAt < outAt) {
+                    throw new RuntimeException("bogus place() result for " +
+                            one);
+                }
+
+                outAt = placedAt + one.writeSize();
+            } catch (RuntimeException ex) {
+                throw ExceptionWithContext.withContext(ex,
+                        "...while placing " + one);
+            }
+        }
+
+        writeSize = outAt;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void writeTo0(AnnotatedOutput out) {
+        boolean annotates = out.annotates();
+        boolean first = true;
+        DexFile file = getFile();
+        int at = 0;
+
+        for (OffsettedItem one : items) {
+            if (annotates) {
+                if (first) {
+                    first = false;
+                } else {
+                    out.annotate(0, "\n");
+                }
+            }
+
+            int alignMask = one.getAlignment() - 1;
+            int writeAt = (at + alignMask) & ~alignMask;
+
+            if (at != writeAt) {
+                out.writeZeroes(writeAt - at);
+                at = writeAt;
+            }
+
+            one.writeTo(file, out);
+            at += one.writeSize();
+        }
+
+        if (at != writeSize) {
+            throw new RuntimeException("output size mismatch");
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/OffsettedItem.java b/dexgen/src/com/android/dexgen/dex/file/OffsettedItem.java
new file mode 100644
index 0000000..246f903
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/OffsettedItem.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.ExceptionWithContext;
+
+/**
+ * An item in a Dalvik file which is referenced by absolute offset.
+ */
+public abstract class OffsettedItem extends Item
+        implements Comparable<OffsettedItem> {
+    /** {@code > 0;} alignment requirement */
+    private final int alignment;
+
+    /** {@code >= -1;} the size of this instance when written, in bytes, or
+     * {@code -1} if not yet known */
+    private int writeSize;
+
+    /**
+     * {@code null-ok;} section the item was added to, or {@code null} if
+     * not yet added
+     */
+    private Section addedTo;
+
+    /**
+     * {@code >= -1;} assigned offset of the item from the start of its section,
+     * or {@code -1} if not yet assigned
+     */
+    private int offset;
+
+    /**
+     * Gets the absolute offset of the given item, returning {@code 0}
+     * if handed {@code null}.
+     *
+     * @param item {@code null-ok;} the item in question
+     * @return {@code >= 0;} the item's absolute offset, or {@code 0}
+     * if {@code item == null}
+     */
+    public static int getAbsoluteOffsetOr0(OffsettedItem item) {
+        if (item == null) {
+            return 0;
+        }
+
+        return item.getAbsoluteOffset();
+    }
+
+    /**
+     * Constructs an instance. The offset is initially unassigned.
+     *
+     * @param alignment {@code > 0;} output alignment requirement; must be a
+     * power of 2
+     * @param writeSize {@code >= -1;} the size of this instance when written,
+     * in bytes, or {@code -1} if not immediately known
+     */
+    public OffsettedItem(int alignment, int writeSize) {
+        Section.validateAlignment(alignment);
+
+        if (writeSize < -1) {
+            throw new IllegalArgumentException("writeSize < -1");
+        }
+
+        this.alignment = alignment;
+        this.writeSize = writeSize;
+        this.addedTo = null;
+        this.offset = -1;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Comparisons for this class are defined to be type-major (if the
+     * types don't match then the objects are not equal), with
+     * {@link #compareTo0} deciding same-type comparisons.
+     */
+    @Override
+    public final boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        OffsettedItem otherItem = (OffsettedItem) other;
+        ItemType thisType = itemType();
+        ItemType otherType = otherItem.itemType();
+
+        if (thisType != otherType) {
+            return false;
+        }
+
+        return (compareTo0(otherItem) == 0);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Comparisons for this class are defined to be class-major (if the
+     * classes don't match then the objects are not equal), with
+     * {@link #compareTo0} deciding same-class comparisons.
+     */
+    public final int compareTo(OffsettedItem other) {
+        if (this == other) {
+            return 0;
+        }
+
+        ItemType thisType = itemType();
+        ItemType otherType = other.itemType();
+
+        if (thisType != otherType) {
+            return thisType.compareTo(otherType);
+        }
+
+        return compareTo0(other);
+    }
+
+    /**
+     * Sets the write size of this item. This may only be called once
+     * per instance, and only if the size was unknown upon instance
+     * creation.
+     *
+     * @param writeSize {@code > 0;} the write size, in bytes
+     */
+    public final void setWriteSize(int writeSize) {
+        if (writeSize < 0) {
+            throw new IllegalArgumentException("writeSize < 0");
+        }
+
+        if (this.writeSize >= 0) {
+            throw new UnsupportedOperationException("writeSize already set");
+        }
+
+        this.writeSize = writeSize;
+    }
+
+    /** {@inheritDoc}
+     *
+     * @throws UnsupportedOperationException thrown if the write size
+     * is not yet known
+     */
+    @Override
+    public final int writeSize() {
+        if (writeSize < 0) {
+            throw new UnsupportedOperationException("writeSize is unknown");
+        }
+
+        return writeSize;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final void writeTo(DexFile file, AnnotatedOutput out) {
+        out.alignTo(alignment);
+
+        try {
+            if (writeSize < 0) {
+                throw new UnsupportedOperationException(
+                        "writeSize is unknown");
+            }
+            out.assertCursor(getAbsoluteOffset());
+        } catch (RuntimeException ex) {
+            throw ExceptionWithContext.withContext(ex,
+                    "...while writing " + this);
+        }
+
+        writeTo0(file, out);
+    }
+
+    /**
+     * Gets the relative item offset. The offset is from the start of
+     * the section which the instance was written to.
+     *
+     * @return {@code >= 0;} the offset
+     * @throws RuntimeException thrown if the offset is not yet known
+     */
+    public final int getRelativeOffset() {
+        if (offset < 0) {
+            throw new RuntimeException("offset not yet known");
+        }
+
+        return offset;
+    }
+
+    /**
+     * Gets the absolute item offset. The offset is from the start of
+     * the file which the instance was written to.
+     *
+     * @return {@code >= 0;} the offset
+     * @throws RuntimeException thrown if the offset is not yet known
+     */
+    public final int getAbsoluteOffset() {
+        if (offset < 0) {
+            throw new RuntimeException("offset not yet known");
+        }
+
+        return addedTo.getAbsoluteOffset(offset);
+    }
+
+    /**
+     * Indicates that this item has been added to the given section at
+     * the given offset. It is only valid to call this method once per
+     * instance.
+     *
+     * @param addedTo {@code non-null;} the section this instance has
+     * been added to
+     * @param offset {@code >= 0;} the desired offset from the start of the
+     * section where this instance was placed
+     * @return {@code >= 0;} the offset that this instance should be placed at
+     * in order to meet its alignment constraint
+     */
+    public final int place(Section addedTo, int offset) {
+        if (addedTo == null) {
+            throw new NullPointerException("addedTo == null");
+        }
+
+        if (offset < 0) {
+            throw new IllegalArgumentException("offset < 0");
+        }
+
+        if (this.addedTo != null) {
+            throw new RuntimeException("already written");
+        }
+
+        int mask = alignment - 1;
+        offset = (offset + mask) & ~mask;
+
+        this.addedTo = addedTo;
+        this.offset = offset;
+
+        place0(addedTo, offset);
+
+        return offset;
+    }
+
+    /**
+     * Gets the alignment requirement of this instance. An instance should
+     * only be written when so aligned.
+     *
+     * @return {@code > 0;} the alignment requirement; must be a power of 2
+     */
+    public final int getAlignment() {
+        return alignment;
+    }
+
+    /**
+     * Gets the absolute offset of this item as a string, suitable for
+     * including in annotations.
+     *
+     * @return {@code non-null;} the offset string
+     */
+    public final String offsetString() {
+        return '[' + Integer.toHexString(getAbsoluteOffset()) + ']';
+    }
+
+    /**
+     * Gets a short human-readable string representing this instance.
+     *
+     * @return {@code non-null;} the human form
+     */
+    public abstract String toHuman();
+
+    /**
+     * Compares this instance to another which is guaranteed to be of
+     * the same class. The default implementation of this method is to
+     * throw an exception (unsupported operation). If a particular
+     * class needs to actually sort, then it should override this
+     * method.
+     *
+     * @param other {@code non-null;} instance to compare to
+     * @return {@code -1}, {@code 0}, or {@code 1}, depending
+     * on the sort order of this instance and the other
+     */
+    protected int compareTo0(OffsettedItem other) {
+        throw new UnsupportedOperationException("unsupported");
+    }
+
+    /**
+     * Does additional work required when placing an instance. The
+     * default implementation of this method is a no-op. If a
+     * particular class needs to do something special, then it should
+     * override this method. In particular, if this instance did not
+     * know its write size up-front, then this method is responsible
+     * for setting it.
+     *
+     * @param addedTo {@code non-null;} the section this instance has been added to
+     * @param offset {@code >= 0;} the offset from the start of the
+     * section where this instance was placed
+     */
+    protected void place0(Section addedTo, int offset) {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Performs the actual write of the contents of this instance to
+     * the given data section. This is called by {@link #writeTo},
+     * which will have taken care of ensuring alignment.
+     *
+     * @param file {@code non-null;} the file to use for reference
+     * @param out {@code non-null;} where to write to
+     */
+    protected abstract void writeTo0(DexFile file, AnnotatedOutput out);
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/ParameterAnnotationStruct.java b/dexgen/src/com/android/dexgen/dex/file/ParameterAnnotationStruct.java
new file mode 100644
index 0000000..440da1c
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/ParameterAnnotationStruct.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.annotation.Annotations;
+import com.android.dexgen.rop.annotation.AnnotationsList;
+import com.android.dexgen.rop.cst.CstMethodRef;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.ToHuman;
+
+import java.util.ArrayList;
+
+/**
+ * Association of a method and its parameter annotations.
+ */
+public final class ParameterAnnotationStruct
+        implements ToHuman, Comparable<ParameterAnnotationStruct> {
+    /** {@code non-null;} the method in question */
+    private final CstMethodRef method;
+
+    /** {@code non-null;} the associated annotations list */
+    private final AnnotationsList annotationsList;
+
+    /** {@code non-null;} the associated annotations list, as an item */
+    private final UniformListItem<AnnotationSetRefItem> annotationsItem;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param method {@code non-null;} the method in question
+     * @param annotationsList {@code non-null;} the associated annotations list
+     */
+    public ParameterAnnotationStruct(CstMethodRef method,
+            AnnotationsList annotationsList) {
+        if (method == null) {
+            throw new NullPointerException("method == null");
+        }
+
+        if (annotationsList == null) {
+            throw new NullPointerException("annotationsList == null");
+        }
+
+        this.method = method;
+        this.annotationsList = annotationsList;
+
+        /*
+         * Construct an item for the annotations list. TODO: This
+         * requires way too much copying; fix it.
+         */
+
+        int size = annotationsList.size();
+        ArrayList<AnnotationSetRefItem> arrayList = new
+            ArrayList<AnnotationSetRefItem>(size);
+
+        for (int i = 0; i < size; i++) {
+            Annotations annotations = annotationsList.get(i);
+            AnnotationSetItem item = new AnnotationSetItem(annotations);
+            arrayList.add(new AnnotationSetRefItem(item));
+        }
+
+        this.annotationsItem = new UniformListItem<AnnotationSetRefItem>(
+                ItemType.TYPE_ANNOTATION_SET_REF_LIST, arrayList);
+    }
+
+    /** {@inheritDoc} */
+    public int hashCode() {
+        return method.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    public boolean equals(Object other) {
+        if (! (other instanceof ParameterAnnotationStruct)) {
+            return false;
+        }
+
+        return method.equals(((ParameterAnnotationStruct) other).method);
+    }
+
+    /** {@inheritDoc} */
+    public int compareTo(ParameterAnnotationStruct other) {
+        return method.compareTo(other.method);
+    }
+
+    /** {@inheritDoc} */
+    public void addContents(DexFile file) {
+        MethodIdsSection methodIds = file.getMethodIds();
+        MixedItemSection wordData = file.getWordData();
+
+        methodIds.intern(method);
+        wordData.add(annotationsItem);
+    }
+
+    /** {@inheritDoc} */
+    public void writeTo(DexFile file, AnnotatedOutput out) {
+        int methodIdx = file.getMethodIds().indexOf(method);
+        int annotationsOff = annotationsItem.getAbsoluteOffset();
+
+        if (out.annotates()) {
+            out.annotate(0, "    " + method.toHuman());
+            out.annotate(4, "      method_idx:      " + Hex.u4(methodIdx));
+            out.annotate(4, "      annotations_off: " +
+                    Hex.u4(annotationsOff));
+        }
+
+        out.writeInt(methodIdx);
+        out.writeInt(annotationsOff);
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(method.toHuman());
+        sb.append(": ");
+
+        boolean first = true;
+        for (AnnotationSetRefItem item : annotationsItem.getItems()) {
+            if (first) {
+                first = false;
+            } else {
+                sb.append(", ");
+            }
+            sb.append(item.toHuman());
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Gets the method this item is for.
+     *
+     * @return {@code non-null;} the method
+     */
+    public CstMethodRef getMethod() {
+        return method;
+    }
+
+    /**
+     * Gets the associated annotations list.
+     *
+     * @return {@code non-null;} the annotations list
+     */
+    public AnnotationsList getAnnotationsList() {
+        return annotationsList;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/ProtoIdItem.java b/dexgen/src/com/android/dexgen/dex/file/ProtoIdItem.java
new file mode 100644
index 0000000..ef48cd4
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/ProtoIdItem.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.rop.type.Prototype;
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Representation of a method prototype reference inside a Dalvik file.
+ */
+public final class ProtoIdItem extends IndexedItem {
+    /** size of instances when written out to a file, in bytes */
+    public static final int WRITE_SIZE = 12;
+
+    /** {@code non-null;} the wrapped prototype */
+    private final Prototype prototype;
+
+    /** {@code non-null;} the short-form of the prototype */
+    private final CstUtf8 shortForm;
+
+    /**
+     * {@code null-ok;} the list of parameter types or {@code null} if this
+     * prototype has no parameters
+     */
+    private TypeListItem parameterTypes;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param prototype {@code non-null;} the constant for the prototype
+     */
+    public ProtoIdItem(Prototype prototype) {
+        if (prototype == null) {
+            throw new NullPointerException("prototype == null");
+        }
+
+        this.prototype = prototype;
+        this.shortForm = makeShortForm(prototype);
+
+        StdTypeList parameters = prototype.getParameterTypes();
+        this.parameterTypes = (parameters.size() == 0) ? null
+            : new TypeListItem(parameters);
+    }
+
+    /**
+     * Creates the short-form of the given prototype.
+     *
+     * @param prototype {@code non-null;} the prototype
+     * @return {@code non-null;} the short form
+     */
+    private static CstUtf8 makeShortForm(Prototype prototype) {
+        StdTypeList parameters = prototype.getParameterTypes();
+        int size = parameters.size();
+        StringBuilder sb = new StringBuilder(size + 1);
+
+        sb.append(shortFormCharFor(prototype.getReturnType()));
+
+        for (int i = 0; i < size; i++) {
+            sb.append(shortFormCharFor(parameters.getType(i)));
+        }
+
+        return new CstUtf8(sb.toString());
+    }
+
+    /**
+     * Gets the short-form character for the given type.
+     *
+     * @param type {@code non-null;} the type
+     * @return the corresponding short-form character
+     */
+    private static char shortFormCharFor(Type type) {
+        char descriptorChar = type.getDescriptor().charAt(0);
+
+        if (descriptorChar == '[') {
+            return 'L';
+        }
+
+        return descriptorChar;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_PROTO_ID_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int writeSize() {
+        return WRITE_SIZE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        StringIdsSection stringIds = file.getStringIds();
+        TypeIdsSection typeIds = file.getTypeIds();
+        MixedItemSection typeLists = file.getTypeLists();
+
+        typeIds.intern(prototype.getReturnType());
+        stringIds.intern(shortForm);
+
+        if (parameterTypes != null) {
+            parameterTypes = typeLists.intern(parameterTypes);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(DexFile file, AnnotatedOutput out) {
+        int shortyIdx = file.getStringIds().indexOf(shortForm);
+        int returnIdx = file.getTypeIds().indexOf(prototype.getReturnType());
+        int paramsOff = OffsettedItem.getAbsoluteOffsetOr0(parameterTypes);
+
+        if (out.annotates()) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(prototype.getReturnType().toHuman());
+            sb.append(" proto(");
+
+            StdTypeList params = prototype.getParameterTypes();
+            int size = params.size();
+
+            for (int i = 0; i < size; i++) {
+                if (i != 0) {
+                    sb.append(", ");
+                }
+                sb.append(params.getType(i).toHuman());
+            }
+
+            sb.append(")");
+            out.annotate(0, indexString() + ' ' + sb.toString());
+            out.annotate(4, "  shorty_idx:      " + Hex.u4(shortyIdx) +
+                    " // " + shortForm.toQuoted());
+            out.annotate(4, "  return_type_idx: " + Hex.u4(returnIdx) +
+                    " // " + prototype.getReturnType().toHuman());
+            out.annotate(4, "  parameters_off:  " + Hex.u4(paramsOff));
+        }
+
+        out.writeInt(shortyIdx);
+        out.writeInt(returnIdx);
+        out.writeInt(paramsOff);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/ProtoIdsSection.java b/dexgen/src/com/android/dexgen/dex/file/ProtoIdsSection.java
new file mode 100644
index 0000000..b2af84e
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/ProtoIdsSection.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.type.Prototype;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+import java.util.Collection;
+import java.util.TreeMap;
+
+/**
+ * Proto (method prototype) identifiers list section of a
+ * {@code .dex} file.
+ */
+public final class ProtoIdsSection extends UniformItemSection {
+    /**
+     * {@code non-null;} map from method prototypes to {@link ProtoIdItem} instances
+     */
+    private final TreeMap<Prototype, ProtoIdItem> protoIds;
+
+    /**
+     * Constructs an instance. The file offset is initially unknown.
+     *
+     * @param file {@code non-null;} file that this instance is part of
+     */
+    public ProtoIdsSection(DexFile file) {
+        super("proto_ids", file, 4);
+
+        protoIds = new TreeMap<Prototype, ProtoIdItem>();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection<? extends Item> items() {
+        return protoIds.values();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public IndexedItem get(Constant cst) {
+        throw new UnsupportedOperationException("unsupported");
+    }
+
+    /**
+     * Writes the portion of the file header that refers to this instance.
+     *
+     * @param out {@code non-null;} where to write
+     */
+    public void writeHeaderPart(AnnotatedOutput out) {
+        throwIfNotPrepared();
+
+        int sz = protoIds.size();
+        int offset = (sz == 0) ? 0 : getFileOffset();
+
+        if (sz > 65536) {
+            throw new UnsupportedOperationException("too many proto ids");
+        }
+
+        if (out.annotates()) {
+            out.annotate(4, "proto_ids_size:  " + Hex.u4(sz));
+            out.annotate(4, "proto_ids_off:   " + Hex.u4(offset));
+        }
+
+        out.writeInt(sz);
+        out.writeInt(offset);
+    }
+
+    /**
+     * Interns an element into this instance.
+     *
+     * @param prototype {@code non-null;} the prototype to intern
+     * @return {@code non-null;} the interned reference
+     */
+    public ProtoIdItem intern(Prototype prototype) {
+        if (prototype == null) {
+            throw new NullPointerException("prototype == null");
+        }
+
+        throwIfPrepared();
+
+        ProtoIdItem result = protoIds.get(prototype);
+
+        if (result == null) {
+            result = new ProtoIdItem(prototype);
+            protoIds.put(prototype, result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Gets the index of the given prototype, which must have
+     * been added to this instance.
+     *
+     * @param prototype {@code non-null;} the prototype to look up
+     * @return {@code >= 0;} the reference's index
+     */
+    public int indexOf(Prototype prototype) {
+        if (prototype == null) {
+            throw new NullPointerException("prototype == null");
+        }
+
+        throwIfNotPrepared();
+
+        ProtoIdItem item = protoIds.get(prototype);
+
+        if (item == null) {
+            throw new IllegalArgumentException("not found");
+        }
+
+        return item.getIndex();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void orderItems() {
+        int idx = 0;
+
+        for (Object i : items()) {
+            ((ProtoIdItem) i).setIndex(idx);
+            idx++;
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/Section.java b/dexgen/src/com/android/dexgen/dex/file/Section.java
new file mode 100644
index 0000000..7efaf6b
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/Section.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.util.AnnotatedOutput;
+
+import java.util.Collection;
+
+/**
+ * A section of a {@code .dex} file. Each section consists of a list
+ * of items of some sort or other.
+ */
+public abstract class Section {
+    /** {@code null-ok;} name of this part, for annotation purposes */
+    private final String name;
+
+    /** {@code non-null;} file that this instance is part of */
+    private final DexFile file;
+
+    /** {@code > 0;} alignment requirement for the final output;
+     * must be a power of 2 */
+    private final int alignment;
+
+    /** {@code >= -1;} offset from the start of the file to this part, or
+     * {@code -1} if not yet known */
+    private int fileOffset;
+
+    /** whether {@link #prepare} has been called successfully on this
+     * instance */
+    private boolean prepared;
+
+    /**
+     * Validates an alignment.
+     *
+     * @param alignment the alignment
+     * @throws IllegalArgumentException thrown if {@code alignment}
+     * isn't a positive power of 2
+     */
+    public static void validateAlignment(int alignment) {
+        if ((alignment <= 0) ||
+            (alignment & (alignment - 1)) != 0) {
+            throw new IllegalArgumentException("invalid alignment");
+        }
+    }
+
+    /**
+     * Constructs an instance. The file offset is initially unknown.
+     *
+     * @param name {@code null-ok;} the name of this instance, for annotation
+     * purposes
+     * @param file {@code non-null;} file that this instance is part of
+     * @param alignment {@code > 0;} alignment requirement for the final output;
+     * must be a power of 2
+     */
+    public Section(String name, DexFile file, int alignment) {
+        if (file == null) {
+            throw new NullPointerException("file == null");
+        }
+
+        validateAlignment(alignment);
+
+        this.name = name;
+        this.file = file;
+        this.alignment = alignment;
+        this.fileOffset = -1;
+        this.prepared = false;
+    }
+
+    /**
+     * Gets the file that this instance is part of.
+     *
+     * @return {@code non-null;} the file
+     */
+    public final DexFile getFile() {
+        return file;
+    }
+
+    /**
+     * Gets the alignment for this instance's final output.
+     *
+     * @return {@code > 0;} the alignment
+     */
+    public final int getAlignment() {
+        return alignment;
+    }
+
+    /**
+     * Gets the offset from the start of the file to this part. This
+     * throws an exception if the offset has not yet been set.
+     *
+     * @return {@code >= 0;} the file offset
+     */
+    public final int getFileOffset() {
+        if (fileOffset < 0) {
+            throw new RuntimeException("fileOffset not set");
+        }
+
+        return fileOffset;
+    }
+
+    /**
+     * Sets the file offset. It is only valid to call this method once
+     * once per instance.
+     *
+     * @param fileOffset {@code >= 0;} the desired offset from the start of the
+     * file where this for this instance
+     * @return {@code >= 0;} the offset that this instance should be placed at
+     * in order to meet its alignment constraint
+     */
+    public final int setFileOffset(int fileOffset) {
+        if (fileOffset < 0) {
+            throw new IllegalArgumentException("fileOffset < 0");
+        }
+
+        if (this.fileOffset >= 0) {
+            throw new RuntimeException("fileOffset already set");
+        }
+
+        int mask = alignment - 1;
+        fileOffset = (fileOffset + mask) & ~mask;
+
+        this.fileOffset = fileOffset;
+
+        return fileOffset;
+    }
+
+    /**
+     * Writes this instance to the given raw data object.
+     *
+     * @param out {@code non-null;} where to write to
+     */
+    public final void writeTo(AnnotatedOutput out) {
+        throwIfNotPrepared();
+        align(out);
+
+        int cursor = out.getCursor();
+
+        if (fileOffset < 0) {
+            fileOffset = cursor;
+        } else if (fileOffset != cursor) {
+            throw new RuntimeException("alignment mismatch: for " + this +
+                                       ", at " + cursor +
+                                       ", but expected " + fileOffset);
+        }
+
+        if (out.annotates()) {
+            if (name != null) {
+                out.annotate(0, "\n" + name + ":");
+            } else if (cursor != 0) {
+                out.annotate(0, "\n");
+            }
+        }
+
+        writeTo0(out);
+    }
+
+    /**
+     * Returns the absolute file offset, given an offset from the
+     * start of this instance's output. This is only valid to call
+     * once this instance has been assigned a file offset (via {@link
+     * #setFileOffset}).
+     *
+     * @param relative {@code >= 0;} the relative offset
+     * @return {@code >= 0;} the corresponding absolute file offset
+     */
+    public final int getAbsoluteOffset(int relative) {
+        if (relative < 0) {
+            throw new IllegalArgumentException("relative < 0");
+        }
+
+        if (fileOffset < 0) {
+            throw new RuntimeException("fileOffset not yet set");
+        }
+
+        return fileOffset + relative;
+    }
+
+    /**
+     * Returns the absolute file offset of the given item which must
+     * be contained in this section. This is only valid to call
+     * once this instance has been assigned a file offset (via {@link
+     * #setFileOffset}).
+     *
+     * <p><b>Note:</b> Subclasses must implement this as appropriate for
+     * their contents.</p>
+     *
+     * @param item {@code non-null;} the item in question
+     * @return {@code >= 0;} the item's absolute file offset
+     */
+    public abstract int getAbsoluteItemOffset(Item item);
+
+    /**
+     * Prepares this instance for writing. This performs any necessary
+     * prerequisites, including particularly adding stuff to other
+     * sections. This method may only be called once per instance;
+     * subsequent calls will throw an exception.
+     */
+    public final void prepare() {
+        throwIfPrepared();
+        prepare0();
+        prepared = true;
+    }
+
+    /**
+     * Gets the collection of all the items in this section.
+     * It is not valid to attempt to change the returned list.
+     *
+     * @return {@code non-null;} the items
+     */
+    public abstract Collection<? extends Item> items();
+
+    /**
+     * Does the main work of {@link #prepare}.
+     */
+    protected abstract void prepare0();
+
+    /**
+     * Gets the size of this instance when output, in bytes.
+     *
+     * @return {@code >= 0;} the size of this instance, in bytes
+     */
+    public abstract int writeSize();
+
+    /**
+     * Throws an exception if {@link #prepare} has not been
+     * called on this instance.
+     */
+    protected final void throwIfNotPrepared() {
+        if (!prepared) {
+            throw new RuntimeException("not prepared");
+        }
+    }
+
+    /**
+     * Throws an exception if {@link #prepare} has already been called
+     * on this instance.
+     */
+    protected final void throwIfPrepared() {
+        if (prepared) {
+            throw new RuntimeException("already prepared");
+        }
+    }
+
+    /**
+     * Aligns the output of the given data to the alignment of this instance.
+     *
+     * @param out {@code non-null;} the output to align
+     */
+    protected final void align(AnnotatedOutput out) {
+        out.alignTo(alignment);
+    }
+
+    /**
+     * Writes this instance to the given raw data object. This gets
+     * called by {@link #writeTo} after aligning the cursor of
+     * {@code out} and verifying that either the assigned file
+     * offset matches the actual cursor {@code out} or that the
+     * file offset was not previously assigned, in which case it gets
+     * assigned to {@code out}'s cursor.
+     *
+     * @param out {@code non-null;} where to write to
+     */
+    protected abstract void writeTo0(AnnotatedOutput out);
+
+    /**
+     * Returns the name of this section, for annotation purposes.
+     *
+     * @return {@code null-ok;} name of this part, for annotation purposes
+     */
+    protected final String getName() {
+        return name;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/Statistics.java b/dexgen/src/com/android/dexgen/dex/file/Statistics.java
new file mode 100644
index 0000000..1ec2f93
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/Statistics.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.util.AnnotatedOutput;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.TreeMap;
+
+/**
+ * Statistics about the contents of a file.
+ */
+public final class Statistics {
+    /** {@code non-null;} data about each type of item */
+    private final HashMap<String, Data> dataMap;
+
+    /**
+     * Constructs an instance.
+     */
+    public Statistics() {
+        dataMap = new HashMap<String, Data>(50);
+    }
+
+    /**
+     * Adds the given item to the statistics.
+     *
+     * @param item {@code non-null;} the item to add
+     */
+    public void add(Item item) {
+        String typeName = item.typeName();
+        Data data = dataMap.get(typeName);
+
+        if (data == null) {
+            dataMap.put(typeName, new Data(item, typeName));
+        } else {
+            data.add(item);
+        }
+    }
+
+    /**
+     * Adds the given list of items to the statistics.
+     *
+     * @param list {@code non-null;} the list of items to add
+     */
+    public void addAll(Section list) {
+        Collection<? extends Item> items = list.items();
+        for (Item item : items) {
+            add(item);
+        }
+    }
+
+    /**
+     * Writes the statistics as an annotation.
+     *
+     * @param out {@code non-null;} where to write to
+     */
+    public final void writeAnnotation(AnnotatedOutput out) {
+        if (dataMap.size() == 0) {
+            return;
+        }
+
+        out.annotate(0, "\nstatistics:\n");
+
+        TreeMap<String, Data> sortedData = new TreeMap<String, Data>();
+
+        for (Data data : dataMap.values()) {
+            sortedData.put(data.name, data);
+        }
+
+        for (Data data : sortedData.values()) {
+            data.writeAnnotation(out);
+        }
+    }
+
+    public String toHuman() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("Statistics:\n");
+
+        TreeMap<String, Data> sortedData = new TreeMap<String, Data>();
+
+        for (Data data : dataMap.values()) {
+            sortedData.put(data.name, data);
+        }
+
+        for (Data data : sortedData.values()) {
+            sb.append(data.toHuman());
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Statistical data about a particular class.
+     */
+    private static class Data {
+        /** {@code non-null;} name to use as a label */
+        private final String name;
+
+        /** {@code >= 0;} number of instances */
+        private int count;
+
+        /** {@code >= 0;} total size of instances in bytes */
+        private int totalSize;
+
+        /** {@code >= 0;} largest size of any individual item */
+        private int largestSize;
+
+        /** {@code >= 0;} smallest size of any individual item */
+        private int smallestSize;
+
+        /**
+         * Constructs an instance for the given item.
+         *
+         * @param item {@code non-null;} item in question
+         * @param name {@code non-null;} type name to use
+         */
+        public Data(Item item, String name) {
+            int size = item.writeSize();
+
+            this.name = name;
+            this.count = 1;
+            this.totalSize = size;
+            this.largestSize = size;
+            this.smallestSize = size;
+        }
+
+        /**
+         * Incorporates a new item. This assumes the type name matches.
+         *
+         * @param item {@code non-null;} item to incorporate
+         */
+        public void add(Item item) {
+            int size = item.writeSize();
+
+            count++;
+            totalSize += size;
+
+            if (size > largestSize) {
+                largestSize = size;
+            }
+
+            if (size < smallestSize) {
+                smallestSize = size;
+            }
+        }
+
+        /**
+         * Writes this instance as an annotation.
+         *
+         * @param out {@code non-null;} where to write to
+         */
+        public void writeAnnotation(AnnotatedOutput out) {
+            out.annotate(toHuman());
+        }
+
+        /**
+         * Generates a human-readable string for this data item.
+         *
+         * @return string for human consumption.
+         */
+        public String toHuman() {
+            StringBuilder sb = new StringBuilder();
+
+            sb.append("  " + name + ": " +
+                         count + " item" + (count == 1 ? "" : "s") + "; " +
+                         totalSize + " bytes total\n");
+
+            if (smallestSize == largestSize) {
+                sb.append("    " + smallestSize + " bytes/item\n");
+            } else {
+                int average = totalSize / count;
+                sb.append("    " + smallestSize + ".." + largestSize +
+                             " bytes/item; average " + average + "\n");
+            }
+
+            return sb.toString();
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/StringDataItem.java b/dexgen/src/com/android/dexgen/dex/file/StringDataItem.java
new file mode 100644
index 0000000..7e28323
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/StringDataItem.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.ByteArray;
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.Leb128Utils;
+
+/**
+ * Representation of string data for a particular string, in a Dalvik file.
+ */
+public final class StringDataItem extends OffsettedItem {
+    /** {@code non-null;} the string value */
+    private final CstUtf8 value;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param value {@code non-null;} the string value
+     */
+    public StringDataItem(CstUtf8 value) {
+        super(1, writeSize(value));
+
+        this.value = value;
+    }
+
+    /**
+     * Gets the write size for a given value.
+     *
+     * @param value {@code non-null;} the string value
+     * @return {@code >= 2}; the write size, in bytes
+     */
+    private static int writeSize(CstUtf8 value) {
+        int utf16Size = value.getUtf16Size();
+
+        // The +1 is for the '\0' termination byte.
+        return Leb128Utils.unsignedLeb128Size(utf16Size)
+            + value.getUtf8Size() + 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_STRING_DATA_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        // Nothing to do here.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo0(DexFile file, AnnotatedOutput out) {
+        ByteArray bytes = value.getBytes();
+        int utf16Size = value.getUtf16Size();
+
+        if (out.annotates()) {
+            out.annotate(Leb128Utils.unsignedLeb128Size(utf16Size),
+                    "utf16_size: " + Hex.u4(utf16Size));
+            out.annotate(bytes.size() + 1, value.toQuoted());
+        }
+
+        out.writeUnsignedLeb128(utf16Size);
+        out.write(bytes);
+        out.writeByte(0);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toHuman() {
+        return value.toQuoted();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int compareTo0(OffsettedItem other) {
+        StringDataItem otherData = (StringDataItem) other;
+
+        return value.compareTo(otherData.value);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/StringIdItem.java b/dexgen/src/com/android/dexgen/dex/file/StringIdItem.java
new file mode 100644
index 0000000..30f31d4
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/StringIdItem.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Representation of a string inside a Dalvik file.
+ */
+public final class StringIdItem
+        extends IndexedItem implements Comparable {
+    /** size of instances when written out to a file, in bytes */
+    public static final int WRITE_SIZE = 4;
+
+    /** {@code non-null;} the string value */
+    private final CstUtf8 value;
+
+    /** {@code null-ok;} associated string data object, if known */
+    private StringDataItem data;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param value {@code non-null;} the string value
+     */
+    public StringIdItem(CstUtf8 value) {
+        if (value == null) {
+            throw new NullPointerException("value == null");
+        }
+
+        this.value = value;
+        this.data = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof StringIdItem)) {
+            return false;
+        }
+
+        StringIdItem otherString = (StringIdItem) other;
+        return value.equals(otherString.value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return value.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    public int compareTo(Object other) {
+        StringIdItem otherString = (StringIdItem) other;
+        return value.compareTo(otherString.value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_STRING_ID_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int writeSize() {
+        return WRITE_SIZE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        if (data == null) {
+            // The string data hasn't yet been added, so add it.
+            MixedItemSection stringData = file.getStringData();
+            data = new StringDataItem(value);
+            stringData.add(data);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(DexFile file, AnnotatedOutput out) {
+        int dataOff = data.getAbsoluteOffset();
+
+        if (out.annotates()) {
+            out.annotate(0, indexString() + ' ' + value.toQuoted(100));
+            out.annotate(4, "  string_data_off: " + Hex.u4(dataOff));
+        }
+
+        out.writeInt(dataOff);
+    }
+
+    /**
+     * Gets the string value.
+     *
+     * @return {@code non-null;} the value
+     */
+    public CstUtf8 getValue() {
+        return value;
+    }
+
+    /**
+     * Gets the associated data object for this instance, if known.
+     *
+     * @return {@code null-ok;} the associated data object or {@code null}
+     * if not yet known
+     */
+    public StringDataItem getData() {
+        return data;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/StringIdsSection.java b/dexgen/src/com/android/dexgen/dex/file/StringIdsSection.java
new file mode 100644
index 0000000..9047fb9
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/StringIdsSection.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstNat;
+import com.android.dexgen.rop.cst.CstString;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+import java.util.Collection;
+import java.util.TreeMap;
+
+/**
+ * Strings list section of a {@code .dex} file.
+ */
+public final class StringIdsSection
+        extends UniformItemSection {
+    /**
+     * {@code non-null;} map from string constants to {@link
+     * StringIdItem} instances
+     */
+    private final TreeMap<CstUtf8, StringIdItem> strings;
+
+    /**
+     * Constructs an instance. The file offset is initially unknown.
+     *
+     * @param file {@code non-null;} file that this instance is part of
+     */
+    public StringIdsSection(DexFile file) {
+        super("string_ids", file, 4);
+
+        strings = new TreeMap<CstUtf8, StringIdItem>();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection<? extends Item> items() {
+        return strings.values();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public IndexedItem get(Constant cst) {
+        if (cst == null) {
+            throw new NullPointerException("cst == null");
+        }
+
+        throwIfNotPrepared();
+
+        if (cst instanceof CstString) {
+            cst = ((CstString) cst).getString();
+        }
+
+        IndexedItem result = strings.get((CstUtf8) cst);
+
+        if (result == null) {
+            throw new IllegalArgumentException("not found");
+        }
+
+        return result;
+    }
+
+    /**
+     * Writes the portion of the file header that refers to this instance.
+     *
+     * @param out {@code non-null;} where to write
+     */
+    public void writeHeaderPart(AnnotatedOutput out) {
+        throwIfNotPrepared();
+
+        int sz = strings.size();
+        int offset = (sz == 0) ? 0 : getFileOffset();
+
+        if (out.annotates()) {
+            out.annotate(4, "string_ids_size: " + Hex.u4(sz));
+            out.annotate(4, "string_ids_off:  " + Hex.u4(offset));
+        }
+
+        out.writeInt(sz);
+        out.writeInt(offset);
+    }
+
+    /**
+     * Interns an element into this instance.
+     *
+     * @param string {@code non-null;} the string to intern, as a regular Java
+     * {@code String}
+     * @return {@code non-null;} the interned string
+     */
+    public StringIdItem intern(String string) {
+        CstUtf8 utf8 = new CstUtf8(string);
+        return intern(new StringIdItem(utf8));
+    }
+
+    /**
+     * Interns an element into this instance.
+     *
+     * @param string {@code non-null;} the string to intern, as a {@link CstString}
+     * @return {@code non-null;} the interned string
+     */
+    public StringIdItem intern(CstString string) {
+        CstUtf8 utf8 = string.getString();
+        return intern(new StringIdItem(utf8));
+    }
+
+    /**
+     * Interns an element into this instance.
+     *
+     * @param string {@code non-null;} the string to intern, as a constant
+     * @return {@code non-null;} the interned string
+     */
+    public StringIdItem intern(CstUtf8 string) {
+        return intern(new StringIdItem(string));
+    }
+
+    /**
+     * Interns an element into this instance.
+     *
+     * @param string {@code non-null;} the string to intern
+     * @return {@code non-null;} the interned string
+     */
+    public StringIdItem intern(StringIdItem string) {
+        if (string == null) {
+            throw new NullPointerException("string == null");
+        }
+
+        throwIfPrepared();
+
+        CstUtf8 value = string.getValue();
+        StringIdItem already = strings.get(value);
+
+        if (already != null) {
+            return already;
+        }
+
+        strings.put(value, string);
+        return string;
+    }
+
+    /**
+     * Interns the components of a name-and-type into this instance.
+     *
+     * @param nat {@code non-null;} the name-and-type
+     */
+    public void intern(CstNat nat) {
+        intern(nat.getName());
+        intern(nat.getDescriptor());
+    }
+
+    /**
+     * Gets the index of the given string, which must have been added
+     * to this instance.
+     *
+     * @param string {@code non-null;} the string to look up
+     * @return {@code >= 0;} the string's index
+     */
+    public int indexOf(CstUtf8 string) {
+        if (string == null) {
+            throw new NullPointerException("string == null");
+        }
+
+        throwIfNotPrepared();
+
+        StringIdItem s = strings.get(string);
+
+        if (s == null) {
+            throw new IllegalArgumentException("not found");
+        }
+
+        return s.getIndex();
+    }
+
+    /**
+     * Gets the index of the given string, which must have been added
+     * to this instance.
+     *
+     * @param string {@code non-null;} the string to look up
+     * @return {@code >= 0;} the string's index
+     */
+    public int indexOf(CstString string) {
+        return indexOf(string.getString());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void orderItems() {
+        int idx = 0;
+
+        for (StringIdItem s : strings.values()) {
+            s.setIndex(idx);
+            idx++;
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/TypeIdItem.java b/dexgen/src/com/android/dexgen/dex/file/TypeIdItem.java
new file mode 100644
index 0000000..2c029b0
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/TypeIdItem.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Representation of a type reference inside a Dalvik file.
+ */
+public final class TypeIdItem extends IdItem {
+    /** size of instances when written out to a file, in bytes */
+    public static final int WRITE_SIZE = 4;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param type {@code non-null;} the constant for the type
+     */
+    public TypeIdItem(CstType type) {
+        super(type);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_TYPE_ID_ITEM;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int writeSize() {
+        return WRITE_SIZE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        file.getStringIds().intern(getDefiningClass().getDescriptor());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeTo(DexFile file, AnnotatedOutput out) {
+        CstType type = getDefiningClass();
+        CstUtf8 descriptor = type.getDescriptor();
+        int idx = file.getStringIds().indexOf(descriptor);
+
+        if (out.annotates()) {
+            out.annotate(0, indexString() + ' ' + descriptor.toHuman());
+            out.annotate(4, "  descriptor_idx: " + Hex.u4(idx));
+        }
+
+        out.writeInt(idx);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/TypeIdsSection.java b/dexgen/src/com/android/dexgen/dex/file/TypeIdsSection.java
new file mode 100644
index 0000000..b02b592
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/TypeIdsSection.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+import java.util.Collection;
+import java.util.TreeMap;
+
+/**
+ * Type identifiers list section of a {@code .dex} file.
+ */
+public final class TypeIdsSection extends UniformItemSection {
+    /**
+     * {@code non-null;} map from types to {@link TypeIdItem} instances
+     */
+    private final TreeMap<Type, TypeIdItem> typeIds;
+
+    /**
+     * Constructs an instance. The file offset is initially unknown.
+     *
+     * @param file {@code non-null;} file that this instance is part of
+     */
+    public TypeIdsSection(DexFile file) {
+        super("type_ids", file, 4);
+
+        typeIds = new TreeMap<Type, TypeIdItem>();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection<? extends Item> items() {
+        return typeIds.values();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public IndexedItem get(Constant cst) {
+        if (cst == null) {
+            throw new NullPointerException("cst == null");
+        }
+
+        throwIfNotPrepared();
+
+        Type type = ((CstType) cst).getClassType();
+        IndexedItem result = typeIds.get(type);
+
+        if (result == null) {
+            throw new IllegalArgumentException("not found: " + cst);
+        }
+
+        return result;
+    }
+
+    /**
+     * Writes the portion of the file header that refers to this instance.
+     *
+     * @param out {@code non-null;} where to write
+     */
+    public void writeHeaderPart(AnnotatedOutput out) {
+        throwIfNotPrepared();
+
+        int sz = typeIds.size();
+        int offset = (sz == 0) ? 0 : getFileOffset();
+
+        if (sz > 65536) {
+            throw new UnsupportedOperationException("too many type ids");
+        }
+
+        if (out.annotates()) {
+            out.annotate(4, "type_ids_size:   " + Hex.u4(sz));
+            out.annotate(4, "type_ids_off:    " + Hex.u4(offset));
+        }
+
+        out.writeInt(sz);
+        out.writeInt(offset);
+    }
+
+    /**
+     * Interns an element into this instance.
+     *
+     * @param type {@code non-null;} the type to intern
+     * @return {@code non-null;} the interned reference
+     */
+    public TypeIdItem intern(Type type) {
+        if (type == null) {
+            throw new NullPointerException("type == null");
+        }
+
+        throwIfPrepared();
+
+        TypeIdItem result = typeIds.get(type);
+
+        if (result == null) {
+            result = new TypeIdItem(new CstType(type));
+            typeIds.put(type, result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Interns an element into this instance.
+     *
+     * @param type {@code non-null;} the type to intern
+     * @return {@code non-null;} the interned reference
+     */
+    public TypeIdItem intern(CstType type) {
+        if (type == null) {
+            throw new NullPointerException("type == null");
+        }
+
+        throwIfPrepared();
+
+        Type typePerSe = type.getClassType();
+        TypeIdItem result = typeIds.get(typePerSe);
+
+        if (result == null) {
+            result = new TypeIdItem(type);
+            typeIds.put(typePerSe, result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Gets the index of the given type, which must have
+     * been added to this instance.
+     *
+     * @param type {@code non-null;} the type to look up
+     * @return {@code >= 0;} the reference's index
+     */
+    public int indexOf(Type type) {
+        if (type == null) {
+            throw new NullPointerException("type == null");
+        }
+
+        throwIfNotPrepared();
+
+        TypeIdItem item = typeIds.get(type);
+
+        if (item == null) {
+            throw new IllegalArgumentException("not found: " + type);
+        }
+
+        return item.getIndex();
+    }
+
+    /**
+     * Gets the index of the given type, which must have
+     * been added to this instance.
+     *
+     * @param type {@code non-null;} the type to look up
+     * @return {@code >= 0;} the reference's index
+     */
+    public int indexOf(CstType type) {
+        if (type == null) {
+            throw new NullPointerException("type == null");
+        }
+
+        return indexOf(type.getClassType());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void orderItems() {
+        int idx = 0;
+
+        for (Object i : items()) {
+            ((TypeIdItem) i).setIndex(idx);
+            idx++;
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/TypeListItem.java b/dexgen/src/com/android/dexgen/dex/file/TypeListItem.java
new file mode 100644
index 0000000..a78c63d
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/TypeListItem.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeList;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Representation of a list of class references.
+ */
+public final class TypeListItem extends OffsettedItem {
+    /** alignment requirement */
+    private static final int ALIGNMENT = 4;
+
+    /** element size in bytes */
+    private static final int ELEMENT_SIZE = 2;
+
+    /** header size in bytes */
+    private static final int HEADER_SIZE = 4;
+
+    /** {@code non-null;} the actual list */
+    private final TypeList list;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param list {@code non-null;} the actual list
+     */
+    public TypeListItem(TypeList list) {
+        super(ALIGNMENT, (list.size() * ELEMENT_SIZE) + HEADER_SIZE);
+
+        this.list = list;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return StdTypeList.hashContents(list);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return ItemType.TYPE_TYPE_LIST;
+    }
+
+    /** {@inheritDoc} */
+    public void addContents(DexFile file) {
+        TypeIdsSection typeIds = file.getTypeIds();
+        int sz = list.size();
+
+        for (int i = 0; i < sz; i++) {
+            typeIds.intern(list.getType(i));
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toHuman() {
+        throw new RuntimeException("unsupported");
+    }
+
+    /**
+     * Gets the underlying list.
+     *
+     * @return {@code non-null;} the list
+     */
+    public TypeList getList() {
+        return list;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void writeTo0(DexFile file, AnnotatedOutput out) {
+        TypeIdsSection typeIds = file.getTypeIds();
+        int sz = list.size();
+
+        if (out.annotates()) {
+            out.annotate(0, offsetString() + " type_list");
+            out.annotate(HEADER_SIZE, "  size: " + Hex.u4(sz));
+            for (int i = 0; i < sz; i++) {
+                Type one = list.getType(i);
+                int idx = typeIds.indexOf(one);
+                out.annotate(ELEMENT_SIZE,
+                             "  " + Hex.u2(idx) + " // " + one.toHuman());
+            }
+        }
+
+        out.writeInt(sz);
+
+        for (int i = 0; i < sz; i++) {
+            out.writeShort(typeIds.indexOf(list.getType(i)));
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int compareTo0(OffsettedItem other) {
+        TypeList thisList = this.list;
+        TypeList otherList = ((TypeListItem) other).list;
+
+        return StdTypeList.compareContents(thisList, otherList);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/UniformItemSection.java b/dexgen/src/com/android/dexgen/dex/file/UniformItemSection.java
new file mode 100644
index 0000000..63ba36b
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/UniformItemSection.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.util.AnnotatedOutput;
+
+import java.util.Collection;
+
+/**
+ * A section of a {@code .dex} file which consists of a sequence of
+ * {@link Item} objects. Each of the items must have the same size in
+ * the output.
+ */
+public abstract class UniformItemSection extends Section {
+    /**
+     * Constructs an instance. The file offset is initially unknown.
+     *
+     * @param name {@code null-ok;} the name of this instance, for annotation
+     * purposes
+     * @param file {@code non-null;} file that this instance is part of
+     * @param alignment {@code > 0;} alignment requirement for the final output;
+     * must be a power of 2
+     */
+    public UniformItemSection(String name, DexFile file, int alignment) {
+        super(name, file, alignment);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final int writeSize() {
+        Collection<? extends Item> items = items();
+        int sz = items.size();
+
+        if (sz == 0) {
+            return 0;
+        }
+
+        // Since each item has to be the same size, we can pick any.
+        return sz * items.iterator().next().writeSize();
+    }
+
+    /**
+     * Gets the item corresponding to the given {@link Constant}. This
+     * will throw an exception if the constant is not found, including
+     * if this instance isn't the sort that maps constants to {@link
+     * IndexedItem} instances.
+     *
+     * @param cst {@code non-null;} constant to look for
+     * @return {@code non-null;} the corresponding item found in this instance
+     */
+    public abstract IndexedItem get(Constant cst);
+
+    /** {@inheritDoc} */
+    @Override
+    protected final void prepare0() {
+        DexFile file = getFile();
+
+        orderItems();
+
+        for (Item one : items()) {
+            one.addContents(file);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected final void writeTo0(AnnotatedOutput out) {
+        DexFile file = getFile();
+        int alignment = getAlignment();
+
+        for (Item one : items()) {
+            one.writeTo(file, out);
+            out.alignTo(alignment);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final int getAbsoluteItemOffset(Item item) {
+        /*
+         * Since all items must be the same size, we can use the size
+         * of the one we're given to calculate its offset.
+         */
+        IndexedItem ii = (IndexedItem) item;
+        int relativeOffset = ii.getIndex() * ii.writeSize();
+
+        return getAbsoluteOffset(relativeOffset);
+    }
+
+    /**
+     * Alters or picks the order for items in this instance if desired,
+     * so that subsequent calls to {@link #items} will yield a
+     * so-ordered collection. If the items in this instance are indexed,
+     * then this method should also assign indices.
+     */
+    protected abstract void orderItems();
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/UniformListItem.java b/dexgen/src/com/android/dexgen/dex/file/UniformListItem.java
new file mode 100644
index 0000000..88a120d
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/UniformListItem.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Class that represents a contiguous list of uniform items. Each
+ * item in the list, in particular, must have the same write size and
+ * alignment.
+ *
+ * <p>This class inherits its alignment from its items, bumped up to
+ * {@code 4} if the items have a looser alignment requirement. If
+ * it is more than {@code 4}, then there will be a gap after the
+ * output list size (which is four bytes) and before the first item.</p>
+ *
+ * @param <T> type of element contained in an instance
+ */
+public final class UniformListItem<T extends OffsettedItem>
+        extends OffsettedItem {
+    /** the size of the list header */
+    private static final int HEADER_SIZE = 4;
+
+    /** {@code non-null;} the item type */
+    private final ItemType itemType;
+
+    /** {@code non-null;} the contents */
+    private final List<T> items;
+
+    /**
+     * Constructs an instance. It is illegal to modify the given list once
+     * it is used to construct an instance of this class.
+     *
+     * @param itemType {@code non-null;} the type of the item
+     * @param items {@code non-null and non-empty;} list of items to represent
+     */
+    public UniformListItem(ItemType itemType, List<T> items) {
+        super(getAlignment(items), writeSize(items));
+
+        if (itemType == null) {
+            throw new NullPointerException("itemType == null");
+        }
+
+        this.items = items;
+        this.itemType = itemType;
+    }
+
+    /**
+     * Helper for {@link #UniformListItem}, which returns the alignment
+     * requirement implied by the given list. See the header comment for
+     * more details.
+     *
+     * @param items {@code non-null;} list of items being represented
+     * @return {@code >= 4;} the alignment requirement
+     */
+    private static int getAlignment(List<? extends OffsettedItem> items) {
+        try {
+            // Since they all must have the same alignment, any one will do.
+            return Math.max(HEADER_SIZE, items.get(0).getAlignment());
+        } catch (IndexOutOfBoundsException ex) {
+            // Translate the exception.
+            throw new IllegalArgumentException("items.size() == 0");
+        } catch (NullPointerException ex) {
+            // Translate the exception.
+            throw new NullPointerException("items == null");
+        }
+    }
+
+    /**
+     * Calculates the write size for the given list.
+     *
+     * @param items {@code non-null;} the list in question
+     * @return {@code >= 0;} the write size
+     */
+    private static int writeSize(List<? extends OffsettedItem> items) {
+        /*
+         * This class assumes all included items are the same size,
+         * an assumption which is verified in place0().
+         */
+        OffsettedItem first = items.get(0);
+        return (items.size() * first.writeSize()) + getAlignment(items);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ItemType itemType() {
+        return itemType;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer(100);
+
+        sb.append(getClass().getName());
+        sb.append(items);
+
+        return sb.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addContents(DexFile file) {
+        for (OffsettedItem i : items) {
+            i.addContents(file);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final String toHuman() {
+        StringBuffer sb = new StringBuffer(100);
+        boolean first = true;
+
+        sb.append("{");
+
+        for (OffsettedItem i : items) {
+            if (first) {
+                first = false;
+            } else {
+                sb.append(", ");
+            }
+            sb.append(i.toHuman());
+        }
+
+        sb.append("}");
+        return sb.toString();
+    }
+
+    /**
+     * Gets the underlying list of items.
+     *
+     * @return {@code non-null;} the list
+     */
+    public final List<T> getItems() {
+        return items;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void place0(Section addedTo, int offset) {
+        offset += headerSize();
+
+        boolean first = true;
+        int theSize = -1;
+        int theAlignment = -1;
+
+        for (OffsettedItem i : items) {
+            int size = i.writeSize();
+            if (first) {
+                theSize = size;
+                theAlignment = i.getAlignment();
+                first = false;
+            } else {
+                if (size != theSize) {
+                    throw new UnsupportedOperationException(
+                            "item size mismatch");
+                }
+                if (i.getAlignment() != theAlignment) {
+                    throw new UnsupportedOperationException(
+                            "item alignment mismatch");
+                }
+            }
+
+            offset = i.place(addedTo, offset) + size;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void writeTo0(DexFile file, AnnotatedOutput out) {
+        int size = items.size();
+
+        if (out.annotates()) {
+            out.annotate(0, offsetString() + " " + typeName());
+            out.annotate(4, "  size: " + Hex.u4(size));
+        }
+
+        out.writeInt(size);
+
+        for (OffsettedItem i : items) {
+            i.writeTo(file, out);
+        }
+    }
+
+    /**
+     * Get the size of the header of this list.
+     *
+     * @return {@code >= 0;} the header size
+     */
+    private int headerSize() {
+        /*
+         * Because of how this instance was set up, this is the same
+         * as the alignment.
+         */
+        return getAlignment();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/dex/file/ValueEncoder.java b/dexgen/src/com/android/dexgen/dex/file/ValueEncoder.java
new file mode 100644
index 0000000..7f30779
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/dex/file/ValueEncoder.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.dex.file;
+
+import com.android.dexgen.rop.annotation.Annotation;
+import com.android.dexgen.rop.annotation.NameValuePair;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstAnnotation;
+import com.android.dexgen.rop.cst.CstArray;
+import com.android.dexgen.rop.cst.CstBoolean;
+import com.android.dexgen.rop.cst.CstByte;
+import com.android.dexgen.rop.cst.CstChar;
+import com.android.dexgen.rop.cst.CstDouble;
+import com.android.dexgen.rop.cst.CstEnumRef;
+import com.android.dexgen.rop.cst.CstFieldRef;
+import com.android.dexgen.rop.cst.CstFloat;
+import com.android.dexgen.rop.cst.CstInteger;
+import com.android.dexgen.rop.cst.CstKnownNull;
+import com.android.dexgen.rop.cst.CstLiteralBits;
+import com.android.dexgen.rop.cst.CstLong;
+import com.android.dexgen.rop.cst.CstMethodRef;
+import com.android.dexgen.rop.cst.CstShort;
+import com.android.dexgen.rop.cst.CstString;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.util.AnnotatedOutput;
+import com.android.dexgen.util.Hex;
+
+import java.util.Collection;
+
+/**
+ * Handler for writing out {@code encoded_values} and parts
+ * thereof.
+ */
+public final class ValueEncoder {
+    /** annotation value type constant: {@code byte} */
+    private static final int VALUE_BYTE = 0x00;
+
+    /** annotation value type constant: {@code short} */
+    private static final int VALUE_SHORT = 0x02;
+
+    /** annotation value type constant: {@code char} */
+    private static final int VALUE_CHAR = 0x03;
+
+    /** annotation value type constant: {@code int} */
+    private static final int VALUE_INT = 0x04;
+
+    /** annotation value type constant: {@code long} */
+    private static final int VALUE_LONG = 0x06;
+
+    /** annotation value type constant: {@code float} */
+    private static final int VALUE_FLOAT = 0x10;
+
+    /** annotation value type constant: {@code double} */
+    private static final int VALUE_DOUBLE = 0x11;
+
+    /** annotation value type constant: {@code string} */
+    private static final int VALUE_STRING = 0x17;
+
+    /** annotation value type constant: {@code type} */
+    private static final int VALUE_TYPE = 0x18;
+
+    /** annotation value type constant: {@code field} */
+    private static final int VALUE_FIELD = 0x19;
+
+    /** annotation value type constant: {@code method} */
+    private static final int VALUE_METHOD = 0x1a;
+
+    /** annotation value type constant: {@code enum} */
+    private static final int VALUE_ENUM = 0x1b;
+
+    /** annotation value type constant: {@code array} */
+    private static final int VALUE_ARRAY = 0x1c;
+
+    /** annotation value type constant: {@code annotation} */
+    private static final int VALUE_ANNOTATION = 0x1d;
+
+    /** annotation value type constant: {@code null} */
+    private static final int VALUE_NULL = 0x1e;
+
+    /** annotation value type constant: {@code boolean} */
+    private static final int VALUE_BOOLEAN = 0x1f;
+
+    /** {@code non-null;} file being written */
+    private final DexFile file;
+
+    /** {@code non-null;} output stream to write to */
+    private final AnnotatedOutput out;
+
+    /**
+     * Construct an instance.
+     *
+     * @param file {@code non-null;} file being written
+     * @param out {@code non-null;} output stream to write to
+     */
+    public ValueEncoder(DexFile file, AnnotatedOutput out) {
+        if (file == null) {
+            throw new NullPointerException("file == null");
+        }
+
+        if (out == null) {
+            throw new NullPointerException("out == null");
+        }
+
+        this.file = file;
+        this.out = out;
+    }
+
+    /**
+     * Writes out the encoded form of the given constant.
+     *
+     * @param cst {@code non-null;} the constant to write
+     */
+    public void writeConstant(Constant cst) {
+        int type = constantToValueType(cst);
+        int arg;
+
+        switch (type) {
+            case VALUE_BYTE:
+            case VALUE_SHORT:
+            case VALUE_INT:
+            case VALUE_LONG: {
+                long value = ((CstLiteralBits) cst).getLongBits();
+                writeSignedIntegralValue(type, value);
+                break;
+            }
+            case VALUE_CHAR: {
+                long value = ((CstLiteralBits) cst).getLongBits();
+                writeUnsignedIntegralValue(type, value);
+                break;
+            }
+            case VALUE_FLOAT: {
+                // Shift value left 32 so that right-zero-extension works.
+                long value = ((CstFloat) cst).getLongBits() << 32;
+                writeRightZeroExtendedValue(type, value);
+                break;
+            }
+            case VALUE_DOUBLE: {
+                long value = ((CstDouble) cst).getLongBits();
+                writeRightZeroExtendedValue(type, value);
+                break;
+            }
+            case VALUE_STRING: {
+                int index = file.getStringIds().indexOf((CstString) cst);
+                writeUnsignedIntegralValue(type, (long) index);
+                break;
+            }
+            case VALUE_TYPE: {
+                int index = file.getTypeIds().indexOf((CstType) cst);
+                writeUnsignedIntegralValue(type, (long) index);
+                break;
+            }
+            case VALUE_FIELD: {
+                int index = file.getFieldIds().indexOf((CstFieldRef) cst);
+                writeUnsignedIntegralValue(type, (long) index);
+                break;
+            }
+            case VALUE_METHOD: {
+                int index = file.getMethodIds().indexOf((CstMethodRef) cst);
+                writeUnsignedIntegralValue(type, (long) index);
+                break;
+            }
+            case VALUE_ENUM: {
+                CstFieldRef fieldRef = ((CstEnumRef) cst).getFieldRef();
+                int index = file.getFieldIds().indexOf(fieldRef);
+                writeUnsignedIntegralValue(type, (long) index);
+                break;
+            }
+            case VALUE_ARRAY: {
+                out.writeByte(type);
+                writeArray((CstArray) cst, false);
+                break;
+            }
+            case VALUE_ANNOTATION: {
+                out.writeByte(type);
+                writeAnnotation(((CstAnnotation) cst).getAnnotation(),
+                        false);
+                break;
+            }
+            case VALUE_NULL: {
+                out.writeByte(type);
+                break;
+            }
+            case VALUE_BOOLEAN: {
+                int value = ((CstBoolean) cst).getIntBits();
+                out.writeByte(type | (value << 5));
+                break;
+            }
+            default: {
+                throw new RuntimeException("Shouldn't happen");
+            }
+        }
+    }
+
+    /**
+     * Gets the value type for the given constant.
+     *
+     * @param cst {@code non-null;} the constant
+     * @return the value type; one of the {@code VALUE_*} constants
+     * defined by this class
+     */
+    private static int constantToValueType(Constant cst) {
+        /*
+         * TODO: Constant should probable have an associated enum, so this
+         * can be a switch().
+         */
+        if (cst instanceof CstByte) {
+            return VALUE_BYTE;
+        } else if (cst instanceof CstShort) {
+            return VALUE_SHORT;
+        } else if (cst instanceof CstChar) {
+            return VALUE_CHAR;
+        } else if (cst instanceof CstInteger) {
+            return VALUE_INT;
+        } else if (cst instanceof CstLong) {
+            return VALUE_LONG;
+        } else if (cst instanceof CstFloat) {
+            return VALUE_FLOAT;
+        } else if (cst instanceof CstDouble) {
+            return VALUE_DOUBLE;
+        } else if (cst instanceof CstString) {
+            return VALUE_STRING;
+        } else if (cst instanceof CstType) {
+            return VALUE_TYPE;
+        } else if (cst instanceof CstFieldRef) {
+            return VALUE_FIELD;
+        } else if (cst instanceof CstMethodRef) {
+            return VALUE_METHOD;
+        } else if (cst instanceof CstEnumRef) {
+            return VALUE_ENUM;
+        } else if (cst instanceof CstArray) {
+            return VALUE_ARRAY;
+        } else if (cst instanceof CstAnnotation) {
+            return VALUE_ANNOTATION;
+        } else if (cst instanceof CstKnownNull) {
+            return VALUE_NULL;
+        } else if (cst instanceof CstBoolean) {
+            return VALUE_BOOLEAN;
+        } else {
+            throw new RuntimeException("Shouldn't happen");
+        }
+    }
+
+    /**
+     * Writes out the encoded form of the given array, that is, as
+     * an {@code encoded_array} and not including a
+     * {@code value_type} prefix. If the output stream keeps
+     * (debugging) annotations and {@code topLevel} is
+     * {@code true}, then this method will write (debugging)
+     * annotations.
+     *
+     * @param array {@code non-null;} array instance to write
+     * @param topLevel {@code true} iff the given annotation is the
+     * top-level annotation or {@code false} if it is a sub-annotation
+     * of some other annotation
+     */
+    public void writeArray(CstArray array, boolean topLevel) {
+        boolean annotates = topLevel && out.annotates();
+        CstArray.List list = ((CstArray) array).getList();
+        int size = list.size();
+
+        if (annotates) {
+            out.annotate("  size: " + Hex.u4(size));
+        }
+
+        out.writeUnsignedLeb128(size);
+
+        for (int i = 0; i < size; i++) {
+            Constant cst = list.get(i);
+            if (annotates) {
+                out.annotate("  [" + Integer.toHexString(i) + "] " +
+                        constantToHuman(cst));
+            }
+            writeConstant(cst);
+        }
+
+        if (annotates) {
+            out.endAnnotation();
+        }
+    }
+
+    /**
+     * Writes out the encoded form of the given annotation, that is,
+     * as an {@code encoded_annotation} and not including a
+     * {@code value_type} prefix. If the output stream keeps
+     * (debugging) annotations and {@code topLevel} is
+     * {@code true}, then this method will write (debugging)
+     * annotations.
+     *
+     * @param annotation {@code non-null;} annotation instance to write
+     * @param topLevel {@code true} iff the given annotation is the
+     * top-level annotation or {@code false} if it is a sub-annotation
+     * of some other annotation
+     */
+    public void writeAnnotation(Annotation annotation, boolean topLevel) {
+        boolean annotates = topLevel && out.annotates();
+        StringIdsSection stringIds = file.getStringIds();
+        TypeIdsSection typeIds = file.getTypeIds();
+
+        CstType type = annotation.getType();
+        int typeIdx = typeIds.indexOf(type);
+
+        if (annotates) {
+            out.annotate("  type_idx: " + Hex.u4(typeIdx) + " // " +
+                    type.toHuman());
+        }
+
+        out.writeUnsignedLeb128(typeIds.indexOf(annotation.getType()));
+
+        Collection<NameValuePair> pairs = annotation.getNameValuePairs();
+        int size = pairs.size();
+
+        if (annotates) {
+            out.annotate("  size: " + Hex.u4(size));
+        }
+
+        out.writeUnsignedLeb128(size);
+
+        int at = 0;
+        for (NameValuePair pair : pairs) {
+            CstUtf8 name = pair.getName();
+            int nameIdx = stringIds.indexOf(name);
+            Constant value = pair.getValue();
+
+            if (annotates) {
+                out.annotate(0, "  elements[" + at + "]:");
+                at++;
+                out.annotate("    name_idx: " + Hex.u4(nameIdx) + " // " +
+                        name.toHuman());
+            }
+
+            out.writeUnsignedLeb128(nameIdx);
+
+            if (annotates) {
+                out.annotate("    value: " + constantToHuman(value));
+            }
+
+            writeConstant(value);
+        }
+
+        if (annotates) {
+            out.endAnnotation();
+        }
+    }
+
+    /**
+     * Gets the colloquial type name and human form of the type of the
+     * given constant, when used as an encoded value.
+     *
+     * @param cst {@code non-null;} the constant
+     * @return {@code non-null;} its type name and human form
+     */
+    public static String constantToHuman(Constant cst) {
+        int type = constantToValueType(cst);
+
+        if (type == VALUE_NULL) {
+            return "null";
+        }
+
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(cst.typeName());
+        sb.append(' ');
+        sb.append(cst.toHuman());
+
+        return sb.toString();
+    }
+
+    /**
+     * Helper for {@link #writeConstant}, which writes out the value
+     * for any signed integral type.
+     *
+     * @param type the type constant
+     * @param value {@code long} bits of the value
+     */
+    private void writeSignedIntegralValue(int type, long value) {
+        /*
+         * Figure out how many bits are needed to represent the value,
+         * including a sign bit: The bit count is subtracted from 65
+         * and not 64 to account for the sign bit. The xor operation
+         * has the effect of leaving non-negative values alone and
+         * unary complementing negative values (so that a leading zero
+         * count always returns a useful number for our present
+         * purpose).
+         */
+        int requiredBits =
+            65 - Long.numberOfLeadingZeros(value ^ (value >> 63));
+
+        // Round up the requiredBits to a number of bytes.
+        int requiredBytes = (requiredBits + 0x07) >> 3;
+
+        /*
+         * Write the header byte, which includes the type and
+         * requiredBytes - 1.
+         */
+        out.writeByte(type | ((requiredBytes - 1) << 5));
+
+        // Write the value, per se.
+        while (requiredBytes > 0) {
+            out.writeByte((byte) value);
+            value >>= 8;
+            requiredBytes--;
+        }
+    }
+
+    /**
+     * Helper for {@link #writeConstant}, which writes out the value
+     * for any unsigned integral type.
+     *
+     * @param type the type constant
+     * @param value {@code long} bits of the value
+     */
+    private void writeUnsignedIntegralValue(int type, long value) {
+        // Figure out how many bits are needed to represent the value.
+        int requiredBits = 64 - Long.numberOfLeadingZeros(value);
+        if (requiredBits == 0) {
+            requiredBits = 1;
+        }
+
+        // Round up the requiredBits to a number of bytes.
+        int requiredBytes = (requiredBits + 0x07) >> 3;
+
+        /*
+         * Write the header byte, which includes the type and
+         * requiredBytes - 1.
+         */
+        out.writeByte(type | ((requiredBytes - 1) << 5));
+
+        // Write the value, per se.
+        while (requiredBytes > 0) {
+            out.writeByte((byte) value);
+            value >>= 8;
+            requiredBytes--;
+        }
+    }
+
+    /**
+     * Helper for {@link #writeConstant}, which writes out a
+     * right-zero-extended value.
+     *
+     * @param type the type constant
+     * @param value {@code long} bits of the value
+     */
+    private void writeRightZeroExtendedValue(int type, long value) {
+        // Figure out how many bits are needed to represent the value.
+        int requiredBits = 64 - Long.numberOfTrailingZeros(value);
+        if (requiredBits == 0) {
+            requiredBits = 1;
+        }
+
+        // Round up the requiredBits to a number of bytes.
+        int requiredBytes = (requiredBits + 0x07) >> 3;
+
+        // Scootch the first bits to be written down to the low-order bits.
+        value >>= 64 - (requiredBytes * 8);
+
+        /*
+         * Write the header byte, which includes the type and
+         * requiredBytes - 1.
+         */
+        out.writeByte(type | ((requiredBytes - 1) << 5));
+
+        // Write the value, per se.
+        while (requiredBytes > 0) {
+            out.writeByte((byte) value);
+            value >>= 8;
+            requiredBytes--;
+        }
+    }
+
+
+    /**
+     * Helper for {@code addContents()} methods, which adds
+     * contents for a particular {@link Annotation}, calling itself
+     * recursively should it encounter a nested annotation.
+     *
+     * @param file {@code non-null;} the file to add to
+     * @param annotation {@code non-null;} the annotation to add contents for
+     */
+    public static void addContents(DexFile file, Annotation annotation) {
+        TypeIdsSection typeIds = file.getTypeIds();
+        StringIdsSection stringIds = file.getStringIds();
+
+        typeIds.intern(annotation.getType());
+
+        for (NameValuePair pair : annotation.getNameValuePairs()) {
+            stringIds.intern(pair.getName());
+            addContents(file, pair.getValue());
+        }
+    }
+
+    /**
+     * Helper for {@code addContents()} methods, which adds
+     * contents for a particular constant, calling itself recursively
+     * should it encounter a {@link CstArray} and calling {@link
+     * #addContents(DexFile,Annotation)} recursively should it
+     * encounter a {@link CstAnnotation}.
+     *
+     * @param file {@code non-null;} the file to add to
+     * @param cst {@code non-null;} the constant to add contents for
+     */
+    public static void addContents(DexFile file, Constant cst) {
+        if (cst instanceof CstAnnotation) {
+            addContents(file, ((CstAnnotation) cst).getAnnotation());
+        } else if (cst instanceof CstArray) {
+            CstArray.List list = ((CstArray) cst).getList();
+            int size = list.size();
+            for (int i = 0; i < size; i++) {
+                addContents(file, list.get(i));
+            }
+        } else {
+            file.internIfAppropriate(cst);
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/AttConstantValue.java b/dexgen/src/com/android/dexgen/rop/AttConstantValue.java
new file mode 100644
index 0000000..189dd75
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/AttConstantValue.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop;
+
+import com.android.dexgen.rop.cst.CstDouble;
+import com.android.dexgen.rop.cst.CstFloat;
+import com.android.dexgen.rop.cst.CstInteger;
+import com.android.dexgen.rop.cst.CstLong;
+import com.android.dexgen.rop.cst.CstString;
+import com.android.dexgen.rop.cst.TypedConstant;
+
+/**
+ * Attribute class for standard {@code ConstantValue} attributes.
+ */
+public final class AttConstantValue extends BaseAttribute {
+    /** {@code non-null;} attribute name for attributes of this type */
+    public static final String ATTRIBUTE_NAME = "ConstantValue";
+
+    /** {@code non-null;} the constant value */
+    private final TypedConstant constantValue;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param constantValue {@code non-null;} the constant value, which must
+     * be an instance of one of: {@code CstString},
+     * {@code CstInteger}, {@code CstLong},
+     * {@code CstFloat}, or {@code CstDouble}
+     */
+    public AttConstantValue(TypedConstant constantValue) {
+        super(ATTRIBUTE_NAME);
+
+        if (!((constantValue instanceof CstString) ||
+               (constantValue instanceof CstInteger) ||
+               (constantValue instanceof CstLong) ||
+               (constantValue instanceof CstFloat) ||
+               (constantValue instanceof CstDouble))) {
+            if (constantValue == null) {
+                throw new NullPointerException("constantValue == null");
+            }
+            throw new IllegalArgumentException("bad type for constantValue");
+        }
+
+        this.constantValue = constantValue;
+    }
+
+    /** {@inheritDoc} */
+    public int byteLength() {
+        return 8;
+    }
+
+    /**
+     * Gets the constant value of this instance. The returned value
+     * is an instance of one of: {@code CstString},
+     * {@code CstInteger}, {@code CstLong},
+     * {@code CstFloat}, or {@code CstDouble}.
+     *
+     * @return {@code non-null;} the constant value
+     */
+    public TypedConstant getConstantValue() {
+        return constantValue;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/Attribute.java b/dexgen/src/com/android/dexgen/rop/Attribute.java
new file mode 100644
index 0000000..02f1e14
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/Attribute.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop;
+
+/**
+ * Interface representing attributes of class files (directly or indirectly).
+ */
+public interface Attribute {
+    /**
+     * Get the name of the attribute.
+     *
+     * @return {@code non-null;} the name
+     */
+    public String getName();
+
+    /**
+     * Get the total length of the attribute in bytes, including the
+     * header. Since the header is always six bytes, the result of
+     * this method is always at least {@code 6}.
+     *
+     * @return {@code >= 6;} the total length, in bytes
+     */
+    public int byteLength();
+}
diff --git a/dexgen/src/com/android/dexgen/rop/AttributeList.java b/dexgen/src/com/android/dexgen/rop/AttributeList.java
new file mode 100644
index 0000000..205b9b7
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/AttributeList.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop;
+
+/**
+ * Interface for lists of attributes.
+ */
+public interface AttributeList {
+    /**
+     * Get whether this instance is mutable. Note that the
+     * {@code AttributeList} interface itself doesn't provide any means
+     * of mutation, but that doesn't mean that there isn't a non-interface
+     * way of mutating an instance.
+     *
+     * @return {@code true} iff this instance is somehow mutable
+     */
+    public boolean isMutable();
+
+    /**
+     * Get the number of attributes in the list.
+     *
+     * @return the size
+     */
+    public int size();
+
+    /**
+     * Get the {@code n}th attribute.
+     *
+     * @param n {@code n >= 0, n < size();} which attribute
+     * @return {@code non-null;} the attribute in question
+     */
+    public Attribute get(int n);
+
+    /**
+     * Get the total length of this list in bytes, when part of a
+     * class file. The returned value includes the two bytes for the
+     * {@code attributes_count} length indicator.
+     *
+     * @return {@code >= 2;} the total length, in bytes
+     */
+    public int byteLength();
+
+    /**
+     * Get the first attribute in the list with the given name, if any.
+     *
+     * @param name {@code non-null;} attribute name
+     * @return {@code null-ok;} first attribute in the list with the given name,
+     * or {@code null} if there is none
+     */
+    public Attribute findFirst(String name);
+
+    /**
+     * Get the next attribute in the list after the given one, with the same
+     * name, if any.
+     *
+     * @param attrib {@code non-null;} attribute to start looking after
+     * @return {@code null-ok;} next attribute after {@code attrib} with the
+     * same name as {@code attrib}
+     */
+    public Attribute findNext(Attribute attrib);
+}
diff --git a/dexgen/src/com/android/dexgen/rop/BaseAttribute.java b/dexgen/src/com/android/dexgen/rop/BaseAttribute.java
new file mode 100644
index 0000000..7ce88c0
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/BaseAttribute.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop;
+
+
+/**
+ * Base implementation of {@link Attribute}, which directly stores
+ * the attribute name but leaves the rest up to subclasses.
+ */
+public abstract class BaseAttribute implements Attribute {
+    /** {@code non-null;} attribute name */
+    private final String name;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param name {@code non-null;} attribute name
+     */
+    public BaseAttribute(String name) {
+        if (name == null) {
+            throw new NullPointerException("name == null");
+        }
+
+        this.name = name;
+    }
+
+    /** {@inheritDoc} */
+    public String getName() {
+        return name;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/ByteBlock.java b/dexgen/src/com/android/dexgen/rop/ByteBlock.java
new file mode 100644
index 0000000..bdeeb0b
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/ByteBlock.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop;
+
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.IntList;
+import com.android.dexgen.util.LabeledItem;
+
+/**
+ * Representation of a basic block in a bytecode array.
+ */
+public final class ByteBlock implements LabeledItem {
+    /** {@code >= 0;} label for this block */
+    private final int label;
+
+    /** {@code >= 0;} bytecode offset (inclusive) of the start of the block */
+    private final int start;
+
+    /** {@code > start;} bytecode offset (exclusive) of the end of the block */
+    private final int end;
+
+    /** {@code non-null;} list of successors that this block may branch to */
+    private final IntList successors;
+
+    /** {@code non-null;} list of exceptions caught and their handler targets */
+    private final ByteCatchList catches;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param label {@code >= 0;} target label for this block
+     * @param start {@code >= 0;} bytecode offset (inclusive) of the start
+     * of the block
+     * @param end {@code > start;} bytecode offset (exclusive) of the end
+     * of the block
+     * @param successors {@code non-null;} list of successors that this block may
+     * branch to
+     * @param catches {@code non-null;} list of exceptions caught and their
+     * handler targets
+     */
+    public ByteBlock(int label, int start, int end, IntList successors,
+                     ByteCatchList catches) {
+        if (label < 0) {
+            throw new IllegalArgumentException("label < 0");
+        }
+
+        if (start < 0) {
+            throw new IllegalArgumentException("start < 0");
+        }
+
+        if (end <= start) {
+            throw new IllegalArgumentException("end <= start");
+        }
+
+        if (successors == null) {
+            throw new NullPointerException("targets == null");
+        }
+
+        int sz = successors.size();
+        for (int i = 0; i < sz; i++) {
+            if (successors.get(i) < 0) {
+                throw new IllegalArgumentException("successors[" + i +
+                                                   "] == " +
+                                                   successors.get(i));
+            }
+        }
+
+        if (catches == null) {
+            throw new NullPointerException("catches == null");
+        }
+
+        this.label = label;
+        this.start = start;
+        this.end = end;
+        this.successors = successors;
+        this.catches = catches;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return '{' + Hex.u2(label) + ": " + Hex.u2(start) + ".." +
+            Hex.u2(end) + '}';
+    }
+
+    /**
+     * Gets the label of this block.
+     *
+     * @return {@code >= 0;} the label
+     */
+    public int getLabel() {
+        return label;
+    }
+
+    /**
+     * Gets the bytecode offset (inclusive) of the start of this block.
+     *
+     * @return {@code >= 0;} the start offset
+     */
+    public int getStart() {
+        return start;
+    }
+
+    /**
+     * Gets the bytecode offset (exclusive) of the end of this block.
+     *
+     * @return {@code > getStart();} the end offset
+     */
+    public int getEnd() {
+        return end;
+    }
+
+    /**
+     * Gets the list of successors that this block may branch to
+     * non-exceptionally.
+     *
+     * @return {@code non-null;} the successor list
+     */
+    public IntList getSuccessors() {
+        return successors;
+    }
+
+    /**
+     * Gets the list of exceptions caught and their handler targets.
+     *
+     * @return {@code non-null;} the catch list
+     */
+    public ByteCatchList getCatches() {
+        return catches;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/ByteCatchList.java b/dexgen/src/com/android/dexgen/rop/ByteCatchList.java
new file mode 100644
index 0000000..d2a4857
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/ByteCatchList.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop;
+
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.TypeList;
+import com.android.dexgen.util.FixedSizeList;
+import com.android.dexgen.util.IntList;
+
+/**
+ * List of catch entries, that is, the elements of an "exception table,"
+ * which is part of a standard {@code Code} attribute.
+ */
+public final class ByteCatchList extends FixedSizeList {
+    /** {@code non-null;} convenient zero-entry instance */
+    public static final ByteCatchList EMPTY = new ByteCatchList(0);
+
+    /**
+     * Constructs an instance.
+     *
+     * @param count the number of elements to be in the table
+     */
+    public ByteCatchList(int count) {
+        super(count);
+    }
+
+    /**
+     * Gets the total length of this structure in bytes, when included in
+     * a {@code Code} attribute. The returned value includes the
+     * two bytes for {@code exception_table_length}.
+     *
+     * @return {@code >= 2;} the total length, in bytes
+     */
+    public int byteLength() {
+        return 2 + size() * 8;
+    }
+
+    /**
+     * Gets the indicated item.
+     *
+     * @param n {@code >= 0;} which item
+     * @return {@code null-ok;} the indicated item
+     */
+    public Item get(int n) {
+        return (Item) get0(n);
+    }
+
+    /**
+     * Sets the item at the given index.
+     *
+     * @param n {@code >= 0, < size();} which entry to set
+     * @param item {@code non-null;} the item
+     */
+    public void set(int n, Item item) {
+        if (item == null) {
+            throw new NullPointerException("item == null");
+        }
+
+        set0(n, item);
+    }
+
+    /**
+     * Sets the item at the given index.
+     *
+     * @param n {@code >= 0, < size();} which entry to set
+     * @param startPc {@code >= 0;} the start pc (inclusive) of the handler's range
+     * @param endPc {@code >= startPc;} the end pc (exclusive) of the
+     * handler's range
+     * @param handlerPc {@code >= 0;} the pc of the exception handler
+     * @param exceptionClass {@code null-ok;} the exception class or
+     * {@code null} to catch all exceptions with this handler
+     */
+    public void set(int n, int startPc, int endPc, int handlerPc,
+            CstType exceptionClass) {
+        set0(n, new Item(startPc, endPc, handlerPc, exceptionClass));
+    }
+
+    /**
+     * Gets the list of items active at the given address. The result is
+     * automatically made immutable.
+     *
+     * @param pc which address
+     * @return {@code non-null;} list of exception handlers active at
+     * {@code pc}
+     */
+    public ByteCatchList listFor(int pc) {
+        int sz = size();
+        Item[] resultArr = new Item[sz];
+        int resultSz = 0;
+
+        for (int i = 0; i < sz; i++) {
+            Item one = get(i);
+            if (one.covers(pc) && typeNotFound(one, resultArr, resultSz)) {
+                resultArr[resultSz] = one;
+                resultSz++;
+            }
+        }
+
+        if (resultSz == 0) {
+            return EMPTY;
+        }
+
+        ByteCatchList result = new ByteCatchList(resultSz);
+        for (int i = 0; i < resultSz; i++) {
+            result.set(i, resultArr[i]);
+        }
+
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Helper method for {@link #listFor}, which tells whether a match
+     * is <i>not</i> found for the exception type of the given item in
+     * the given array. A match is considered to be either an exact type
+     * match or the class {@code Object} which represents a catch-all.
+     *
+     * @param item {@code non-null;} item with the exception type to look for
+     * @param arr {@code non-null;} array to search in
+     * @param count {@code non-null;} maximum number of elements in the array to check
+     * @return {@code true} iff the exception type is <i>not</i> found
+     */
+    private static boolean typeNotFound(Item item, Item[] arr, int count) {
+        CstType type = item.getExceptionClass();
+
+        for (int i = 0; i < count; i++) {
+            CstType one = arr[i].getExceptionClass();
+            if ((one == type) || (one == CstType.OBJECT)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns a target list corresponding to this instance. The result
+     * is a list of all the exception handler addresses, with the given
+     * {@code noException} address appended if appropriate. The
+     * result is automatically made immutable.
+     *
+     * @param noException {@code >= -1;} the no-exception address to append, or
+     * {@code -1} not to append anything
+     * @return {@code non-null;} list of exception targets, with
+     * {@code noException} appended if necessary
+     */
+    public IntList toTargetList(int noException) {
+        if (noException < -1) {
+            throw new IllegalArgumentException("noException < -1");
+        }
+
+        boolean hasDefault = (noException >= 0);
+        int sz = size();
+
+        if (sz == 0) {
+            if (hasDefault) {
+                /*
+                 * The list is empty, but there is a no-exception
+                 * address; so, the result is just that address.
+                 */
+                return IntList.makeImmutable(noException);
+            }
+            /*
+             * The list is empty and there isn't even a no-exception
+             * address.
+             */
+            return IntList.EMPTY;
+        }
+
+        IntList result = new IntList(sz + (hasDefault ? 1 : 0));
+
+        for (int i = 0; i < sz; i++) {
+            result.add(get(i).getHandlerPc());
+        }
+
+        if (hasDefault) {
+            result.add(noException);
+        }
+
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Returns a rop-style catches list equivalent to this one.
+     *
+     * @return {@code non-null;} the converted instance
+     */
+    public TypeList toRopCatchList() {
+        int sz = size();
+        if (sz == 0) {
+            return StdTypeList.EMPTY;
+        }
+
+        StdTypeList result = new StdTypeList(sz);
+
+        for (int i = 0; i < sz; i++) {
+            result.set(i, get(i).getExceptionClass().getClassType());
+        }
+
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Item in an exception handler list.
+     */
+    public static class Item {
+        /** {@code >= 0;} the start pc (inclusive) of the handler's range */
+        private final int startPc;
+
+        /** {@code >= startPc;} the end pc (exclusive) of the handler's range */
+        private final int endPc;
+
+        /** {@code >= 0;} the pc of the exception handler */
+        private final int handlerPc;
+
+        /** {@code null-ok;} the exception class or {@code null} to catch all
+         * exceptions with this handler */
+        private final CstType exceptionClass;
+
+        /**
+         * Constructs an instance.
+         *
+         * @param startPc {@code >= 0;} the start pc (inclusive) of the
+         * handler's range
+         * @param endPc {@code >= startPc;} the end pc (exclusive) of the
+         * handler's range
+         * @param handlerPc {@code >= 0;} the pc of the exception handler
+         * @param exceptionClass {@code null-ok;} the exception class or
+         * {@code null} to catch all exceptions with this handler
+         */
+        public Item(int startPc, int endPc, int handlerPc,
+                CstType exceptionClass) {
+            if (startPc < 0) {
+                throw new IllegalArgumentException("startPc < 0");
+            }
+
+            if (endPc < startPc) {
+                throw new IllegalArgumentException("endPc < startPc");
+            }
+
+            if (handlerPc < 0) {
+                throw new IllegalArgumentException("handlerPc < 0");
+            }
+
+            this.startPc = startPc;
+            this.endPc = endPc;
+            this.handlerPc = handlerPc;
+            this.exceptionClass = exceptionClass;
+        }
+
+        /**
+         * Gets the start pc (inclusive) of the handler's range.
+         *
+         * @return {@code >= 0;} the start pc (inclusive) of the handler's range.
+         */
+        public int getStartPc() {
+            return startPc;
+        }
+
+        /**
+         * Gets the end pc (exclusive) of the handler's range.
+         *
+         * @return {@code >= startPc;} the end pc (exclusive) of the
+         * handler's range.
+         */
+        public int getEndPc() {
+            return endPc;
+        }
+
+        /**
+         * Gets the pc of the exception handler.
+         *
+         * @return {@code >= 0;} the pc of the exception handler
+         */
+        public int getHandlerPc() {
+            return handlerPc;
+        }
+
+        /**
+         * Gets the class of exception handled.
+         *
+         * @return {@code non-null;} the exception class; {@link CstType#OBJECT}
+         * if this entry handles all possible exceptions
+         */
+        public CstType getExceptionClass() {
+            return (exceptionClass != null) ?
+                exceptionClass : CstType.OBJECT;
+        }
+
+        /**
+         * Returns whether the given address is in the range of this item.
+         *
+         * @param pc the address
+         * @return {@code true} iff this item covers {@code pc}
+         */
+        public boolean covers(int pc) {
+            return (pc >= startPc) && (pc < endPc);
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/Field.java b/dexgen/src/com/android/dexgen/rop/Field.java
new file mode 100644
index 0000000..3e0364b
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/Field.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop;
+
+import com.android.dexgen.rop.cst.TypedConstant;
+
+/**
+ * Interface representing fields of class files.
+ */
+public interface Field
+        extends Member {
+    /**
+     * Get the constant value for this field, if any. This only returns
+     * non-{@code null} for a {@code static final} field which
+     * includes a {@code ConstantValue} attribute.
+     *
+     * @return {@code null-ok;} the constant value, or {@code null} if this
+     * field isn't a constant
+     */
+    public TypedConstant getConstantValue();
+}
diff --git a/dexgen/src/com/android/dexgen/rop/FieldList.java b/dexgen/src/com/android/dexgen/rop/FieldList.java
new file mode 100644
index 0000000..ab4f28f
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/FieldList.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop;
+
+/**
+ * Interface for lists of fields.
+ */
+public interface FieldList
+{
+    /**
+     * Get whether this instance is mutable. Note that the
+     * {@code FieldList} interface itself doesn't provide any means
+     * of mutation, but that doesn't mean that there isn't a non-interface
+     * way of mutating an instance.
+     *
+     * @return {@code true} iff this instance is somehow mutable
+     */
+    public boolean isMutable();
+
+    /**
+     * Get the number of fields in the list.
+     *
+     * @return the size
+     */
+    public int size();
+
+    /**
+     * Get the {@code n}th field.
+     *
+     * @param n {@code n >= 0, n < size();} which field
+     * @return {@code non-null;} the field in question
+     */
+    public Field get(int n);
+}
diff --git a/dexgen/src/com/android/dexgen/rop/LineNumberList.java b/dexgen/src/com/android/dexgen/rop/LineNumberList.java
new file mode 100644
index 0000000..f780066
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/LineNumberList.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop;
+
+import com.android.dexgen.util.FixedSizeList;
+
+/**
+ * List of "line number" entries, which are the contents of
+ * {@code LineNumberTable} attributes.
+ */
+public final class LineNumberList extends FixedSizeList {
+    /** {@code non-null;} zero-size instance */
+    public static final LineNumberList EMPTY = new LineNumberList(0);
+
+    /**
+     * Returns an instance which is the concatenation of the two given
+     * instances.
+     *
+     * @param list1 {@code non-null;} first instance
+     * @param list2 {@code non-null;} second instance
+     * @return {@code non-null;} combined instance
+     */
+    public static LineNumberList concat(LineNumberList list1,
+                                        LineNumberList list2) {
+        if (list1 == EMPTY) {
+            // easy case
+            return list2;
+        }
+
+        int sz1 = list1.size();
+        int sz2 = list2.size();
+        LineNumberList result = new LineNumberList(sz1 + sz2);
+
+        for (int i = 0; i < sz1; i++) {
+            result.set(i, list1.get(i));
+        }
+
+        for (int i = 0; i < sz2; i++) {
+            result.set(sz1 + i, list2.get(i));
+        }
+
+        return result;
+    }
+
+    /**
+     * Constructs an instance.
+     *
+     * @param count the number of elements to be in the list
+     */
+    public LineNumberList(int count) {
+        super(count);
+    }
+
+    /**
+     * Gets the indicated item.
+     *
+     * @param n {@code >= 0;} which item
+     * @return {@code null-ok;} the indicated item
+     */
+    public Item get(int n) {
+        return (Item) get0(n);
+    }
+
+    /**
+     * Sets the item at the given index.
+     *
+     * @param n {@code >= 0, < size();} which element
+     * @param item {@code non-null;} the item
+     */
+    public void set(int n, Item item) {
+        if (item == null) {
+            throw new NullPointerException("item == null");
+        }
+
+        set0(n, item);
+    }
+
+    /**
+     * Sets the item at the given index.
+     *
+     * @param n {@code >= 0, < size();} which element
+     * @param startPc {@code >= 0;} start pc of this item
+     * @param lineNumber {@code >= 0;} corresponding line number
+     */
+    public void set(int n, int startPc, int lineNumber) {
+        set0(n, new Item(startPc, lineNumber));
+    }
+
+    /**
+     * Gets the line number associated with the given address.
+     *
+     * @param pc {@code >= 0;} the address to look up
+     * @return {@code >= -1;} the associated line number, or {@code -1} if
+     * none is known
+     */
+    public int pcToLine(int pc) {
+        /*
+         * Line number entries don't have to appear in any particular
+         * order, so we have to do a linear search. TODO: If
+         * this turns out to be a bottleneck, consider sorting the
+         * list prior to use.
+         */
+        int sz = size();
+        int bestPc = -1;
+        int bestLine = -1;
+
+        for (int i = 0; i < sz; i++) {
+            Item one = get(i);
+            int onePc = one.getStartPc();
+            if ((onePc <= pc) && (onePc > bestPc)) {
+                bestPc = onePc;
+                bestLine = one.getLineNumber();
+                if (bestPc == pc) {
+                    // We can't do better than this
+                    break;
+                }
+            }
+        }
+
+        return bestLine;
+    }
+
+    /**
+     * Item in a line number table.
+     */
+    public static class Item {
+        /** {@code >= 0;} start pc of this item */
+        private final int startPc;
+
+        /** {@code >= 0;} corresponding line number */
+        private final int lineNumber;
+
+        /**
+         * Constructs an instance.
+         *
+         * @param startPc {@code >= 0;} start pc of this item
+         * @param lineNumber {@code >= 0;} corresponding line number
+         */
+        public Item(int startPc, int lineNumber) {
+            if (startPc < 0) {
+                throw new IllegalArgumentException("startPc < 0");
+            }
+
+            if (lineNumber < 0) {
+                throw new IllegalArgumentException("lineNumber < 0");
+            }
+
+            this.startPc = startPc;
+            this.lineNumber = lineNumber;
+        }
+
+        /**
+         * Gets the start pc of this item.
+         *
+         * @return the start pc
+         */
+        public int getStartPc() {
+            return startPc;
+        }
+
+        /**
+         * Gets the line number of this item.
+         *
+         * @return the line number
+         */
+        public int getLineNumber() {
+            return lineNumber;
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/Member.java b/dexgen/src/com/android/dexgen/rop/Member.java
new file mode 100644
index 0000000..dfc17be
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/Member.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop;
+
+import com.android.dexgen.rop.cst.CstNat;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.CstUtf8;
+
+/**
+ * Interface representing members of class files (that is, fields and methods).
+ */
+public interface Member {
+    /**
+     * Get the defining class.
+     *
+     * @return {@code non-null;} the defining class
+     */
+    public CstType getDefiningClass();
+
+    /**
+     * Get the field {@code access_flags}.
+     *
+     * @return the access flags
+     */
+    public int getAccessFlags();
+
+    /**
+     * Get the field {@code name_index} of the member. This is
+     * just a convenient shorthand for {@code getNat().getName()}.
+     *
+     * @return {@code non-null;} the name
+     */
+    public CstUtf8 getName();
+
+    /**
+     * Get the field {@code descriptor_index} of the member. This is
+     * just a convenient shorthand for {@code getNat().getDescriptor()}.
+     *
+     * @return {@code non-null;} the descriptor
+     */
+    public CstUtf8 getDescriptor();
+
+    /**
+     * Get the name and type associated with this member. This is a
+     * combination of the fields {@code name_index} and
+     * {@code descriptor_index} in the original classfile, interpreted
+     * via the constant pool.
+     *
+     * @return {@code non-null;} the name and type
+     */
+    public CstNat getNat();
+
+    /**
+     * Get the field {@code attributes} (along with
+     * {@code attributes_count}).
+     *
+     * @return {@code non-null;} the constant pool
+     */
+    public AttributeList getAttributes();
+}
diff --git a/dexgen/src/com/android/dexgen/rop/StdAttributeList.java b/dexgen/src/com/android/dexgen/rop/StdAttributeList.java
new file mode 100644
index 0000000..bebee21
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/StdAttributeList.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop;
+
+import com.android.dexgen.util.FixedSizeList;
+
+/**
+ * Standard implementation of {@link AttributeList}, which directly stores
+ * an array of {@link Attribute} objects and can be made immutable.
+ */
+public final class StdAttributeList extends FixedSizeList
+        implements AttributeList {
+    /**
+     * Constructs an instance. All indices initially contain {@code null}.
+     *
+     * @param size the size of the list
+     */
+    public StdAttributeList(int size) {
+        super(size);
+    }
+
+    /** {@inheritDoc} */
+    public Attribute get(int n) {
+        return (Attribute) get0(n);
+    }
+
+    /** {@inheritDoc} */
+    public int byteLength() {
+        int sz = size();
+        int result = 2; // u2 attributes_count
+
+        for (int i = 0; i < sz; i++) {
+            result += get(i).byteLength();
+        }
+
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public Attribute findFirst(String name) {
+        int sz = size();
+
+        for (int i = 0; i < sz; i++) {
+            Attribute att = get(i);
+            if (att.getName().equals(name)) {
+                return att;
+            }
+        }
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public Attribute findNext(Attribute attrib) {
+        int sz = size();
+        int at;
+
+        outer: {
+            for (at = 0; at < sz; at++) {
+                Attribute att = get(at);
+                if (att == attrib) {
+                    break outer;
+                }
+            }
+
+            return null;
+        }
+
+        String name = attrib.getName();
+
+        for (at++; at < sz; at++) {
+            Attribute att = get(at);
+            if (att.getName().equals(name)) {
+                return att;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Sets the attribute at the given index.
+     *
+     * @param n {@code >= 0, < size();} which attribute
+     * @param attribute {@code null-ok;} the attribute object
+     */
+    public void set(int n, Attribute attribute) {
+        set0(n, attribute);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/StdField.java b/dexgen/src/com/android/dexgen/rop/StdField.java
new file mode 100644
index 0000000..3084dcc
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/StdField.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop;
+
+import com.android.dexgen.rop.cst.CstNat;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.TypedConstant;
+
+/**
+ * Standard implementation of {@link Field}, which directly stores
+ * all the associated data.
+ */
+public final class StdField extends StdMember implements Field {
+    /**
+     * Constructs an instance.
+     *
+     * @param definingClass {@code non-null;} the defining class
+     * @param accessFlags access flags
+     * @param nat {@code non-null;} member name and type (descriptor)
+     * @param attributes {@code non-null;} list of associated attributes
+     */
+    public StdField(CstType definingClass, int accessFlags, CstNat nat,
+                    AttributeList attributes) {
+        super(definingClass, accessFlags, nat, attributes);
+    }
+
+    /** {@inheritDoc} */
+    public TypedConstant getConstantValue() {
+        AttributeList attribs = getAttributes();
+        AttConstantValue cval = (AttConstantValue)
+            attribs.findFirst(AttConstantValue.ATTRIBUTE_NAME);
+
+        if (cval == null) {
+            return null;
+        }
+
+        return cval.getConstantValue();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/StdFieldList.java b/dexgen/src/com/android/dexgen/rop/StdFieldList.java
new file mode 100644
index 0000000..ccb7465
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/StdFieldList.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop;
+
+import com.android.dexgen.util.FixedSizeList;
+
+/**
+ * Standard implementation of {@link FieldList}, which directly stores
+ * an array of {@link Field} objects and can be made immutable.
+ */
+public final class StdFieldList extends FixedSizeList implements FieldList {
+    /**
+     * Constructs an instance. All indices initially contain {@code null}.
+     *
+     * @param size the size of the list
+     */
+    public StdFieldList(int size) {
+        super(size);
+    }
+
+    /** {@inheritDoc} */
+    public Field get(int n) {
+        return (Field) get0(n);
+    }
+
+    /**
+     * Sets the field at the given index.
+     *
+     * @param n {@code >= 0, < size();} which field
+     * @param field {@code null-ok;} the field object
+     */
+    public void set(int n, Field field) {
+        set0(n, field);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/StdMember.java b/dexgen/src/com/android/dexgen/rop/StdMember.java
new file mode 100644
index 0000000..6c46051
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/StdMember.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop;
+
+import com.android.dexgen.rop.cst.CstNat;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.CstUtf8;
+
+/**
+ * Standard implementation of {@link Member}, which directly stores
+ * all the associated data.
+ */
+public abstract class StdMember implements Member {
+    /** {@code non-null;} the defining class */
+    private final CstType definingClass;
+
+    /** access flags */
+    private final int accessFlags;
+
+    /** {@code non-null;} member name and type */
+    private final CstNat nat;
+
+    /** {@code non-null;} list of associated attributes */
+    private final AttributeList attributes;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param definingClass {@code non-null;} the defining class
+     * @param accessFlags access flags
+     * @param nat {@code non-null;} member name and type (descriptor)
+     * @param attributes {@code non-null;} list of associated attributes
+     */
+    public StdMember(CstType definingClass, int accessFlags, CstNat nat,
+                     AttributeList attributes) {
+        if (definingClass == null) {
+            throw new NullPointerException("definingClass == null");
+        }
+
+        if (nat == null) {
+            throw new NullPointerException("nat == null");
+        }
+
+        if (attributes == null) {
+            throw new NullPointerException("attributes == null");
+        }
+
+        this.definingClass = definingClass;
+        this.accessFlags = accessFlags;
+        this.nat = nat;
+        this.attributes = attributes;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer(100);
+
+        sb.append(getClass().getName());
+        sb.append('{');
+        sb.append(nat.toHuman());
+        sb.append('}');
+
+        return sb.toString();
+    }
+
+    /** {@inheritDoc} */
+    public final CstType getDefiningClass() {
+        return definingClass;
+    }
+
+    /** {@inheritDoc} */
+    public final int getAccessFlags() {
+        return accessFlags;
+    }
+
+    /** {@inheritDoc} */
+    public final CstNat getNat() {
+        return nat;
+    }
+
+    /** {@inheritDoc} */
+    public final CstUtf8 getName() {
+        return nat.getName();
+    }
+
+    /** {@inheritDoc} */
+    public final CstUtf8 getDescriptor() {
+        return nat.getDescriptor();
+    }
+
+    /** {@inheritDoc} */
+    public final AttributeList getAttributes() {
+        return attributes;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/annotation/Annotation.java b/dexgen/src/com/android/dexgen/rop/annotation/Annotation.java
new file mode 100644
index 0000000..918d2bc
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/annotation/Annotation.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.annotation;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstAnnotation;
+import com.android.dexgen.rop.cst.CstFieldRef;
+import com.android.dexgen.rop.cst.CstLiteralBits;
+import com.android.dexgen.rop.cst.CstMethodRef;
+import com.android.dexgen.rop.cst.CstNat;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.rop.cst.TypedConstant;
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.MutabilityControl;
+import com.android.dexgen.util.ToHuman;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.TreeMap;
+
+/**
+ * An annotation on an element of a class. Annotations have an
+ * associated type and additionally consist of a set of (name, value)
+ * pairs, where the names are unique.
+ */
+public final class Annotation extends MutabilityControl
+        implements Comparable<Annotation>, ToHuman {
+    /** {@code non-null;} type of the annotation */
+    private final CstType type;
+
+    /** {@code non-null;} the visibility of the annotation */
+    private final AnnotationVisibility visibility;
+
+    /** {@code non-null;} map from names to {@link NameValuePair} instances */
+    private final TreeMap<CstUtf8, NameValuePair> elements;
+
+    /**
+     * Construct an instance. It initially contains no elements.
+     *
+     * @param type {@code non-null;} type of the annotation
+     * @param visibility {@code non-null;} the visibility of the annotation
+     */
+    public Annotation(CstType type, AnnotationVisibility visibility) {
+        if (type == null) {
+            throw new NullPointerException("type == null");
+        }
+
+        if (visibility == null) {
+            throw new NullPointerException("visibility == null");
+        }
+
+        this.type = type;
+        this.visibility = visibility;
+        this.elements = new TreeMap<CstUtf8, NameValuePair>();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (! (other instanceof Annotation)) {
+            return false;
+        }
+
+        Annotation otherAnnotation = (Annotation) other;
+
+        if (! (type.equals(otherAnnotation.type)
+                        && (visibility == otherAnnotation.visibility))) {
+            return false;
+        }
+
+        return elements.equals(otherAnnotation.elements);
+    }
+
+    /** {@inheritDoc} */
+    public int hashCode() {
+        int hash = type.hashCode();
+        hash = (hash * 31) + elements.hashCode();
+        hash = (hash * 31) + visibility.hashCode();
+        return hash;
+    }
+
+    /** {@inheritDoc} */
+    public int compareTo(Annotation other) {
+        int result = type.compareTo(other.type);
+
+        if (result != 0) {
+            return result;
+        }
+
+        result = visibility.compareTo(other.visibility);
+
+        if (result != 0) {
+            return result;
+        }
+
+        Iterator<NameValuePair> thisIter = elements.values().iterator();
+        Iterator<NameValuePair> otherIter = other.elements.values().iterator();
+
+        while (thisIter.hasNext() && otherIter.hasNext()) {
+            NameValuePair thisOne = thisIter.next();
+            NameValuePair otherOne = otherIter.next();
+
+            result = thisOne.compareTo(otherOne);
+            if (result != 0) {
+                return result;
+            }
+        }
+
+        if (thisIter.hasNext()) {
+            return 1;
+        } else if (otherIter.hasNext()) {
+            return -1;
+        }
+
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return toHuman();
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(visibility.toHuman());
+        sb.append("-annotation ");
+        sb.append(type.toHuman());
+        sb.append(" {");
+
+        boolean first = true;
+        for (NameValuePair pair : elements.values()) {
+            if (first) {
+                first = false;
+            } else {
+                sb.append(", ");
+            }
+            sb.append(pair.getName().toHuman());
+            sb.append(": ");
+            sb.append(pair.getValue().toHuman());
+        }
+
+        sb.append("}");
+        return sb.toString();
+    }
+
+    /**
+     * Gets the type of this instance.
+     *
+     * @return {@code non-null;} the type
+     */
+    public CstType getType() {
+        return type;
+    }
+
+    /**
+     * Gets the visibility of this instance.
+     *
+     * @return {@code non-null;} the visibility
+     */
+    public AnnotationVisibility getVisibility() {
+        return visibility;
+    }
+
+    /**
+     * Put an element into the set of (name, value) pairs for this instance.
+     * If there is a preexisting element with the same name, it will be
+     * replaced by this method.
+     *
+     * @param pair {@code non-null;} the (name, value) pair to place into this instance
+     */
+    public void put(NameValuePair pair) {
+        throwIfImmutable();
+
+        if (pair == null) {
+            throw new NullPointerException("pair == null");
+        }
+
+        elements.put(pair.getName(), pair);
+    }
+
+    /**
+     * Add an element to the set of (name, value) pairs for this instance.
+     * It is an error to call this method if there is a preexisting element
+     * with the same name.
+     *
+     * @param pair {@code non-null;} the (name, value) pair to add to this instance
+     */
+    public void add(NameValuePair pair) {
+        throwIfImmutable();
+
+        if (pair == null) {
+            throw new NullPointerException("pair == null");
+        }
+
+        CstUtf8 name = pair.getName();
+
+        if (elements.get(name) != null) {
+            throw new IllegalArgumentException("name already added: " + name);
+        }
+
+        elements.put(name, pair);
+    }
+
+    /**
+     * Gets the set of name-value pairs contained in this instance. The
+     * result is always unmodifiable.
+     *
+     * @return {@code non-null;} the set of name-value pairs
+     */
+    public Collection<NameValuePair> getNameValuePairs() {
+        return Collections.unmodifiableCollection(elements.values());
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/annotation/AnnotationVisibility.java b/dexgen/src/com/android/dexgen/rop/annotation/AnnotationVisibility.java
new file mode 100644
index 0000000..5239164
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/annotation/AnnotationVisibility.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.annotation;
+
+import com.android.dexgen.util.ToHuman;
+
+/**
+ * Visibility scope of an annotation.
+ */
+public enum AnnotationVisibility implements ToHuman {
+    RUNTIME("runtime"),
+    BUILD("build"),
+    SYSTEM("system"),
+    EMBEDDED("embedded");
+
+    /** {@code non-null;} the human-oriented string representation */
+    private final String human;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param human {@code non-null;} the human-oriented string representation
+     */
+    private AnnotationVisibility(String human) {
+        this.human = human;
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return human;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/annotation/Annotations.java b/dexgen/src/com/android/dexgen/rop/annotation/Annotations.java
new file mode 100644
index 0000000..a7eca04
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/annotation/Annotations.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.annotation;
+
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.util.MutabilityControl;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.TreeMap;
+
+/**
+ * List of {@link Annotation} instances.
+ */
+public final class Annotations extends MutabilityControl
+        implements Comparable<Annotations> {
+    /** {@code non-null;} immutable empty instance */
+    public static final Annotations EMPTY = new Annotations();
+
+    static {
+        EMPTY.setImmutable();
+    }
+
+    /** {@code non-null;} map from types to annotations */
+    private final TreeMap<CstType, Annotation> annotations;
+
+    /**
+     * Constructs an immutable instance which is the combination of the
+     * two given instances. The two instances must contain disjoint sets
+     * of types.
+     *
+     * @param a1 {@code non-null;} an instance
+     * @param a2 {@code non-null;} the other instance
+     * @return {@code non-null;} the combination
+     * @throws IllegalArgumentException thrown if there is a duplicate type
+     */
+    public static Annotations combine(Annotations a1, Annotations a2) {
+        Annotations result = new Annotations();
+
+        result.addAll(a1);
+        result.addAll(a2);
+        result.setImmutable();
+
+        return result;
+    }
+
+    /**
+     * Constructs an immutable instance which is the combination of the
+     * given instance with the given additional annotation. The latter's
+     * type must not already appear in the former.
+     *
+     * @param annotations {@code non-null;} the instance to augment
+     * @param annotation {@code non-null;} the additional annotation
+     * @return {@code non-null;} the combination
+     * @throws IllegalArgumentException thrown if there is a duplicate type
+     */
+    public static Annotations combine(Annotations annotations,
+            Annotation annotation) {
+        Annotations result = new Annotations();
+
+        result.addAll(annotations);
+        result.add(annotation);
+        result.setImmutable();
+
+        return result;
+    }
+
+    /**
+     * Constructs an empty instance.
+     */
+    public Annotations() {
+        annotations = new TreeMap<CstType, Annotation>();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return annotations.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (! (other instanceof Annotations)) {
+            return false;
+        }
+
+        Annotations otherAnnotations = (Annotations) other;
+
+        return annotations.equals(otherAnnotations.annotations);
+    }
+
+    /** {@inheritDoc} */
+    public int compareTo(Annotations other) {
+        Iterator<Annotation> thisIter = annotations.values().iterator();
+        Iterator<Annotation> otherIter = other.annotations.values().iterator();
+
+        while (thisIter.hasNext() && otherIter.hasNext()) {
+            Annotation thisOne = thisIter.next();
+            Annotation otherOne = otherIter.next();
+
+            int result = thisOne.compareTo(otherOne);
+            if (result != 0) {
+                return result;
+            }
+        }
+
+        if (thisIter.hasNext()) {
+            return 1;
+        } else if (otherIter.hasNext()) {
+            return -1;
+        }
+
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        boolean first = true;
+
+        sb.append("annotations{");
+
+        for (Annotation a : annotations.values()) {
+            if (first) {
+                first = false;
+            } else {
+                sb.append(", ");
+            }
+            sb.append(a.toHuman());
+        }
+
+        sb.append("}");
+        return sb.toString();
+    }
+
+    /**
+     * Gets the number of elements in this instance.
+     *
+     * @return {@code >= 0;} the size
+     */
+    public int size() {
+        return annotations.size();
+    }
+
+    /**
+     * Adds an element to this instance. There must not already be an
+     * element of the same type.
+     *
+     * @param annotation {@code non-null;} the element to add
+     * @throws IllegalArgumentException thrown if there is a duplicate type
+     */
+    public void add(Annotation annotation) {
+        throwIfImmutable();
+
+        if (annotation == null) {
+            throw new NullPointerException("annotation == null");
+        }
+
+        CstType type = annotation.getType();
+
+        if (annotations.containsKey(type)) {
+            throw new IllegalArgumentException("duplicate type: " +
+                    type.toHuman());
+        }
+
+        annotations.put(type, annotation);
+    }
+
+    /**
+     * Adds all of the elements of the given instance to this one. The
+     * instances must not have any duplicate types.
+     *
+     * @param toAdd {@code non-null;} the annotations to add
+     * @throws IllegalArgumentException thrown if there is a duplicate type
+     */
+    public void addAll(Annotations toAdd) {
+        throwIfImmutable();
+
+        if (toAdd == null) {
+            throw new NullPointerException("toAdd == null");
+        }
+
+        for (Annotation a : toAdd.annotations.values()) {
+            add(a);
+        }
+    }
+
+    /**
+     * Gets the set of annotations contained in this instance. The
+     * result is always unmodifiable.
+     *
+     * @return {@code non-null;} the set of annotations
+     */
+    public Collection<Annotation> getAnnotations() {
+        return Collections.unmodifiableCollection(annotations.values());
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/annotation/AnnotationsList.java b/dexgen/src/com/android/dexgen/rop/annotation/AnnotationsList.java
new file mode 100644
index 0000000..1159932
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/annotation/AnnotationsList.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.annotation;
+
+import com.android.dexgen.util.FixedSizeList;
+
+/**
+ * List of {@link Annotations} instances.
+ */
+public final class AnnotationsList
+        extends FixedSizeList {
+    /** {@code non-null;} immutable empty instance */
+    public static final AnnotationsList EMPTY = new AnnotationsList(0);
+
+    /**
+     * Constructs an immutable instance which is the combination of
+     * the two given instances. The two instances must each have the
+     * same number of elements, and each pair of elements must contain
+     * disjoint sets of types.
+     *
+     * @param list1 {@code non-null;} an instance
+     * @param list2 {@code non-null;} the other instance
+     * @return {@code non-null;} the combination
+     */
+    public static AnnotationsList combine(AnnotationsList list1,
+            AnnotationsList list2) {
+        int size = list1.size();
+
+        if (size != list2.size()) {
+            throw new IllegalArgumentException("list1.size() != list2.size()");
+        }
+
+        AnnotationsList result = new AnnotationsList(size);
+
+        for (int i = 0; i < size; i++) {
+            Annotations a1 = list1.get(i);
+            Annotations a2 = list2.get(i);
+            result.set(i, Annotations.combine(a1, a2));
+        }
+
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Constructs an instance. All indices initially contain {@code null}.
+     *
+     * @param size the size of the list
+     */
+    public AnnotationsList(int size) {
+        super(size);
+    }
+
+    /**
+     * Gets the element at the given index. It is an error to call
+     * this with the index for an element which was never set; if you
+     * do that, this will throw {@code NullPointerException}.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @return {@code non-null;} element at that index
+     */
+    public Annotations get(int n) {
+        return (Annotations) get0(n);
+    }
+
+    /**
+     * Sets the element at the given index. The given element must be
+     * immutable.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @param a {@code null-ok;} the element to set at {@code n}
+     */
+    public void set(int n, Annotations a) {
+        a.throwIfMutable();
+        set0(n, a);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/annotation/NameValuePair.java b/dexgen/src/com/android/dexgen/rop/annotation/NameValuePair.java
new file mode 100644
index 0000000..9f96f14
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/annotation/NameValuePair.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.annotation;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstString;
+import com.android.dexgen.rop.cst.CstUtf8;
+
+/**
+ * A (name, value) pair. These are used as the contents of an annotation.
+ */
+public final class NameValuePair implements Comparable<NameValuePair> {
+    /** {@code non-null;} the name */
+    private final CstUtf8 name;
+
+    /** {@code non-null;} the value */
+    private final Constant value;
+
+    /**
+     * Construct an instance.
+     *
+     * @param name {@code non-null;} the name
+     * @param value {@code non-null;} the value
+     */
+    public NameValuePair(CstUtf8 name, Constant value) {
+        if (name == null) {
+            throw new NullPointerException("name == null");
+        }
+
+        if (value == null) {
+            throw new NullPointerException("value == null");
+        }
+
+        // Reject CstUtf8 values. (They should be CstStrings.)
+        if (value instanceof CstUtf8) {
+            throw new IllegalArgumentException("bad value: " + value);
+        }
+
+        this.name = name;
+        this.value = value;
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        return name.toHuman() + ":" + value;
+    }
+
+    /** {@inheritDoc} */
+    public int hashCode() {
+        return name.hashCode() * 31 + value.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    public boolean equals(Object other) {
+        if (! (other instanceof NameValuePair)) {
+            return false;
+        }
+
+        NameValuePair otherPair = (NameValuePair) other;
+
+        return name.equals(otherPair.name)
+            && value.equals(otherPair.value);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Instances of this class compare in name-major and value-minor
+     * order.</p>
+     */
+    public int compareTo(NameValuePair other) {
+        int result = name.compareTo(other.name);
+
+        if (result != 0) {
+            return result;
+        }
+
+        return value.compareTo(other.value);
+    }
+
+    /**
+     * Gets the name.
+     *
+     * @return {@code non-null;} the name
+     */
+    public CstUtf8 getName() {
+        return name;
+    }
+
+    /**
+     * Gets the value.
+     *
+     * @return {@code non-null;} the value
+     */
+    public Constant getValue() {
+        return value;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/AccessFlags.java b/dexgen/src/com/android/dexgen/rop/code/AccessFlags.java
new file mode 100644
index 0000000..4a8b435
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/AccessFlags.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.util.Hex;
+
+/**
+ * Constants used as "access flags" in various places in classes, and
+ * related utilities. Although, at the rop layer, flags are generally
+ * ignored, this is the layer of communication, and as such, this
+ * package is where these definitions belong. The flag definitions are
+ * identical to Java access flags, but {@code ACC_SUPER} isn't
+ * used at all in translated code, and {@code ACC_SYNCHRONIZED}
+ * is only used in a very limited way.
+ */
+public final class AccessFlags {
+    /** public member / class */
+    public static final int ACC_PUBLIC = 0x0001;
+
+    /** private member */
+    public static final int ACC_PRIVATE = 0x0002;
+
+    /** protected member */
+    public static final int ACC_PROTECTED = 0x0004;
+
+    /** static member */
+    public static final int ACC_STATIC = 0x0008;
+
+    /** final member / class */
+    public static final int ACC_FINAL = 0x0010;
+
+    /**
+     * synchronized method; only valid in dex files for {@code native}
+     * methods
+     */
+    public static final int ACC_SYNCHRONIZED = 0x0020;
+
+    /**
+     * class with new-style {@code invokespecial} for superclass
+     * method access
+     */
+    public static final int ACC_SUPER = 0x0020;
+
+    /** volatile field */
+    public static final int ACC_VOLATILE = 0x0040;
+
+    /** bridge method (generated) */
+    public static final int ACC_BRIDGE = 0x0040;
+
+    /** transient field */
+    public static final int ACC_TRANSIENT = 0x0080;
+
+    /** varargs method */
+    public static final int ACC_VARARGS = 0x0080;
+
+    /** native method */
+    public static final int ACC_NATIVE = 0x0100;
+
+    /** "class" is in fact an public static final interface */
+    public static final int ACC_INTERFACE = 0x0200;
+
+    /** abstract method / class */
+    public static final int ACC_ABSTRACT = 0x0400;
+
+    /**
+     * method with strict floating point ({@code strictfp})
+     * behavior
+     */
+    public static final int ACC_STRICT = 0x0800;
+
+    /** synthetic member */
+    public static final int ACC_SYNTHETIC = 0x1000;
+
+    /** class is an annotation type */
+    public static final int ACC_ANNOTATION = 0x2000;
+
+    /**
+     * class is an enumerated type; field is an element of an enumerated
+     * type
+     */
+    public static final int ACC_ENUM = 0x4000;
+
+    /** method is a constructor */
+    public static final int ACC_CONSTRUCTOR = 0x10000;
+
+    /**
+     * method was declared {@code synchronized}; has no effect on
+     * execution (other than inspecting this flag, per se)
+     */
+    public static final int ACC_DECLARED_SYNCHRONIZED = 0x20000;
+
+    /** flags defined on classes */
+    public static final int CLASS_FLAGS =
+        ACC_PUBLIC | ACC_FINAL | ACC_SUPER | ACC_INTERFACE | ACC_ABSTRACT |
+        ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM;
+
+    /** flags defined on inner classes */
+    public static final int INNER_CLASS_FLAGS =
+        ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL |
+        ACC_INTERFACE | ACC_ABSTRACT | ACC_SYNTHETIC | ACC_ANNOTATION |
+        ACC_ENUM;
+
+    /** flags defined on fields */
+    public static final int FIELD_FLAGS =
+        ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL |
+        ACC_VOLATILE | ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM;
+
+    /** flags defined on methods */
+    public static final int METHOD_FLAGS =
+        ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL |
+        ACC_SYNCHRONIZED | ACC_BRIDGE | ACC_VARARGS | ACC_NATIVE |
+        ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC | ACC_CONSTRUCTOR |
+        ACC_DECLARED_SYNCHRONIZED;
+
+    /** indicates conversion of class flags */
+    private static final int CONV_CLASS = 1;
+
+    /** indicates conversion of field flags */
+    private static final int CONV_FIELD = 2;
+
+    /** indicates conversion of method flags */
+    private static final int CONV_METHOD = 3;
+
+    /**
+     * This class is uninstantiable.
+     */
+    private AccessFlags() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Returns a human-oriented string representing the given access flags,
+     * as defined on classes (not fields or methods).
+     *
+     * @param flags the flags
+     * @return {@code non-null;} human-oriented string
+     */
+    public static String classString(int flags) {
+        return humanHelper(flags, CLASS_FLAGS, CONV_CLASS);
+    }
+
+    /**
+     * Returns a human-oriented string representing the given access flags,
+     * as defined on inner classes.
+     *
+     * @param flags the flags
+     * @return {@code non-null;} human-oriented string
+     */
+    public static String innerClassString(int flags) {
+        return humanHelper(flags, INNER_CLASS_FLAGS, CONV_CLASS);
+    }
+
+    /**
+     * Returns a human-oriented string representing the given access flags,
+     * as defined on fields (not classes or methods).
+     *
+     * @param flags the flags
+     * @return {@code non-null;} human-oriented string
+     */
+    public static String fieldString(int flags) {
+        return humanHelper(flags, FIELD_FLAGS, CONV_FIELD);
+    }
+
+    /**
+     * Returns a human-oriented string representing the given access flags,
+     * as defined on methods (not classes or fields).
+     *
+     * @param flags the flags
+     * @return {@code non-null;} human-oriented string
+     */
+    public static String methodString(int flags) {
+        return humanHelper(flags, METHOD_FLAGS, CONV_METHOD);
+    }
+
+    /**
+     * Returns whether the flag {@code ACC_PUBLIC} is on in the given
+     * flags.
+     *
+     * @param flags the flags to check
+     * @return the value of the {@code ACC_PUBLIC} flag
+     */
+    public static boolean isPublic(int flags) {
+        return (flags & ACC_PUBLIC) != 0;
+    }
+
+    /**
+     * Returns whether the flag {@code ACC_PROTECTED} is on in the given
+     * flags.
+     *
+     * @param flags the flags to check
+     * @return the value of the {@code ACC_PROTECTED} flag
+     */
+    public static boolean isProtected(int flags) {
+        return (flags & ACC_PROTECTED) != 0;
+    }
+
+    /**
+     * Returns whether the flag {@code ACC_PRIVATE} is on in the given
+     * flags.
+     *
+     * @param flags the flags to check
+     * @return the value of the {@code ACC_PRIVATE} flag
+     */
+    public static boolean isPrivate(int flags) {
+        return (flags & ACC_PRIVATE) != 0;
+    }
+
+    /**
+     * Returns whether the flag {@code ACC_STATIC} is on in the given
+     * flags.
+     *
+     * @param flags the flags to check
+     * @return the value of the {@code ACC_STATIC} flag
+     */
+    public static boolean isStatic(int flags) {
+        return (flags & ACC_STATIC) != 0;
+    }
+
+    /**
+     * Returns whether the flag {@code ACC_SYNCHRONIZED} is on in
+     * the given flags.
+     *
+     * @param flags the flags to check
+     * @return the value of the {@code ACC_SYNCHRONIZED} flag
+     */
+    public static boolean isSynchronized(int flags) {
+        return (flags & ACC_SYNCHRONIZED) != 0;
+    }
+
+    /**
+     * Returns whether the flag {@code ACC_ABSTRACT} is on in the given
+     * flags.
+     *
+     * @param flags the flags to check
+     * @return the value of the {@code ACC_ABSTRACT} flag
+     */
+    public static boolean isAbstract(int flags) {
+        return (flags & ACC_ABSTRACT) != 0;
+    }
+
+    /**
+     * Returns whether the flag {@code ACC_NATIVE} is on in the given
+     * flags.
+     *
+     * @param flags the flags to check
+     * @return the value of the {@code ACC_NATIVE} flag
+     */
+    public static boolean isNative(int flags) {
+        return (flags & ACC_NATIVE) != 0;
+    }
+
+    /**
+     * Returns whether the flag {@code ACC_ANNOTATION} is on in the given
+     * flags.
+     *
+     * @param flags the flags to check
+     * @return the value of the {@code ACC_ANNOTATION} flag
+     */
+    public static boolean isAnnotation(int flags) {
+        return (flags & ACC_ANNOTATION) != 0;
+    }
+
+    /**
+     * Returns whether the flag {@code ACC_DECLARED_SYNCHRONIZED} is
+     * on in the given flags.
+     *
+     * @param flags the flags to check
+     * @return the value of the {@code ACC_DECLARED_SYNCHRONIZED} flag
+     */
+    public static boolean isDeclaredSynchronized(int flags) {
+        return (flags & ACC_DECLARED_SYNCHRONIZED) != 0;
+    }
+
+    /**
+     * Helper to return a human-oriented string representing the given
+     * access flags.
+     *
+     * @param flags the defined flags
+     * @param mask mask for the "defined" bits
+     * @param what what the flags represent (one of {@code CONV_*})
+     * @return {@code non-null;} human-oriented string
+     */
+    private static String humanHelper(int flags, int mask, int what) {
+        StringBuffer sb = new StringBuffer(80);
+        int extra = flags & ~mask;
+
+        flags &= mask;
+
+        if ((flags & ACC_PUBLIC) != 0) {
+            sb.append("|public");
+        }
+        if ((flags & ACC_PRIVATE) != 0) {
+            sb.append("|private");
+        }
+        if ((flags & ACC_PROTECTED) != 0) {
+            sb.append("|protected");
+        }
+        if ((flags & ACC_STATIC) != 0) {
+            sb.append("|static");
+        }
+        if ((flags & ACC_FINAL) != 0) {
+            sb.append("|final");
+        }
+        if ((flags & ACC_SYNCHRONIZED) != 0) {
+            if (what == CONV_CLASS) {
+                sb.append("|super");
+            } else {
+                sb.append("|synchronized");
+            }
+        }
+        if ((flags & ACC_VOLATILE) != 0) {
+            if (what == CONV_METHOD) {
+                sb.append("|bridge");
+            } else {
+                sb.append("|volatile");
+            }
+        }
+        if ((flags & ACC_TRANSIENT) != 0) {
+            if (what == CONV_METHOD) {
+                sb.append("|varargs");
+            } else {
+                sb.append("|transient");
+            }
+        }
+        if ((flags & ACC_NATIVE) != 0) {
+            sb.append("|native");
+        }
+        if ((flags & ACC_INTERFACE) != 0) {
+            sb.append("|interface");
+        }
+        if ((flags & ACC_ABSTRACT) != 0) {
+            sb.append("|abstract");
+        }
+        if ((flags & ACC_STRICT) != 0) {
+            sb.append("|strictfp");
+        }
+        if ((flags & ACC_SYNTHETIC) != 0) {
+            sb.append("|synthetic");
+        }
+        if ((flags & ACC_ANNOTATION) != 0) {
+            sb.append("|annotation");
+        }
+        if ((flags & ACC_ENUM) != 0) {
+            sb.append("|enum");
+        }
+        if ((flags & ACC_CONSTRUCTOR) != 0) {
+            sb.append("|constructor");
+        }
+        if ((flags & ACC_DECLARED_SYNCHRONIZED) != 0) {
+            sb.append("|declared_synchronized");
+        }
+
+        if ((extra != 0) || (sb.length() == 0)) {
+            sb.append('|');
+            sb.append(Hex.u2(extra));
+        }
+
+        return sb.substring(1);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/BasicBlock.java b/dexgen/src/com/android/dexgen/rop/code/BasicBlock.java
new file mode 100644
index 0000000..0f7a59e
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/BasicBlock.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.type.TypeList;
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.IntList;
+import com.android.dexgen.util.LabeledItem;
+
+/**
+ * Basic block of register-based instructions.
+ */
+public final class BasicBlock implements LabeledItem {
+    /** {@code >= 0;} target label for this block */
+    private final int label;
+
+    /** {@code non-null;} list of instructions in this block */
+    private final InsnList insns;
+
+    /**
+     * {@code non-null;} full list of successors that this block may
+     * branch to
+     */
+    private final IntList successors;
+
+    /**
+     * {@code >= -1;} the primary / standard-flow / "default" successor, or
+     * {@code -1} if this block has no successors (that is, it
+     * exits the function/method)
+     */
+    private final int primarySuccessor;
+
+    /**
+     * Constructs an instance. The predecessor set is set to {@code null}.
+     *
+     * @param label {@code >= 0;} target label for this block
+     * @param insns {@code non-null;} list of instructions in this block
+     * @param successors {@code non-null;} full list of successors that this
+     * block may branch to
+     * @param primarySuccessor {@code >= -1;} the primary / standard-flow /
+     * "default" successor, or {@code -1} if this block has no
+     * successors (that is, it exits the function/method or is an
+     * unconditional throw)
+     */
+    public BasicBlock(int label, InsnList insns, IntList successors,
+                      int primarySuccessor) {
+        if (label < 0) {
+            throw new IllegalArgumentException("label < 0");
+        }
+
+        try {
+            insns.throwIfMutable();
+        } catch (NullPointerException ex) {
+            // Elucidate exception.
+            throw new NullPointerException("insns == null");
+        }
+
+        int sz = insns.size();
+
+        if (sz == 0) {
+            throw new IllegalArgumentException("insns.size() == 0");
+        }
+
+        for (int i = sz - 2; i >= 0; i--) {
+            Rop one = insns.get(i).getOpcode();
+            if (one.getBranchingness() != Rop.BRANCH_NONE) {
+                throw new IllegalArgumentException("insns[" + i + "] is a " +
+                                                   "branch or can throw");
+            }
+        }
+
+        Insn lastInsn = insns.get(sz - 1);
+        if (lastInsn.getOpcode().getBranchingness() == Rop.BRANCH_NONE) {
+            throw new IllegalArgumentException("insns does not end with " +
+                                               "a branch or throwing " +
+                                               "instruction");
+        }
+
+        try {
+            successors.throwIfMutable();
+        } catch (NullPointerException ex) {
+            // Elucidate exception.
+            throw new NullPointerException("successors == null");
+        }
+
+        if (primarySuccessor < -1) {
+            throw new IllegalArgumentException("primarySuccessor < -1");
+        }
+
+        if (primarySuccessor >= 0 && !successors.contains(primarySuccessor)) {
+            throw new IllegalArgumentException(
+                    "primarySuccessor not in successors");
+        }
+
+        this.label = label;
+        this.insns = insns;
+        this.successors = successors;
+        this.primarySuccessor = primarySuccessor;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Instances of this class compare by identity. That is,
+     * {@code x.equals(y)} is only true if {@code x == y}.
+     */
+    @Override
+    public boolean equals(Object other) {
+        return (this == other);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Return the identity hashcode of this instance. This is proper,
+     * since instances of this class compare by identity (see {@link #equals}).
+     */
+    @Override
+    public int hashCode() {
+        return System.identityHashCode(this);
+    }
+
+    /**
+     * Gets the target label of this block.
+     *
+     * @return {@code >= 0;} the label
+     */
+    public int getLabel() {
+        return label;
+    }
+
+    /**
+     * Gets the list of instructions inside this block.
+     *
+     * @return {@code non-null;} the instruction list
+     */
+    public InsnList getInsns() {
+        return insns;
+    }
+
+    /**
+     * Gets the list of successors that this block may branch to.
+     *
+     * @return {@code non-null;} the successors list
+     */
+    public IntList getSuccessors() {
+        return successors;
+    }
+
+    /**
+     * Gets the primary successor of this block.
+     *
+     * @return {@code >= -1;} the primary successor, or {@code -1} if this
+     * block has no successors at all
+     */
+    public int getPrimarySuccessor() {
+        return primarySuccessor;
+    }
+
+    /**
+     * Gets the secondary successor of this block. It is only valid to call
+     * this method on blocks that have exactly two successors.
+     *
+     * @return {@code >= 0;} the secondary successor
+     */
+    public int getSecondarySuccessor() {
+        if (successors.size() != 2) {
+            throw new UnsupportedOperationException(
+                    "block doesn't have exactly two successors");
+        }
+
+        int succ = successors.get(0);
+        if (succ == primarySuccessor) {
+            succ = successors.get(1);
+        }
+
+        return succ;
+    }
+
+    /**
+     * Gets the first instruction of this block. This is just a
+     * convenient shorthand for {@code getInsns().get(0)}.
+     *
+     * @return {@code non-null;} the first instruction
+     */
+    public Insn getFirstInsn() {
+        return insns.get(0);
+    }
+
+    /**
+     * Gets the last instruction of this block. This is just a
+     * convenient shorthand for {@code getInsns().getLast()}.
+     *
+     * @return {@code non-null;} the last instruction
+     */
+    public Insn getLastInsn() {
+        return insns.getLast();
+    }
+
+    /**
+     * Returns whether this block might throw an exception. This is
+     * just a convenient shorthand for {@code getLastInsn().canThrow()}.
+     *
+     * @return {@code true} iff this block might throw an
+     * exception
+     */
+    public boolean canThrow() {
+        return insns.getLast().canThrow();
+    }
+
+    /**
+     * Returns whether this block has any associated exception handlers.
+     * This is just a shorthand for inspecting the last instruction in
+     * the block to see if it could throw, and if so, whether it in fact
+     * has any associated handlers.
+     *
+     * @return {@code true} iff this block has any associated
+     * exception handlers
+     */
+    public boolean hasExceptionHandlers() {
+        Insn lastInsn = insns.getLast();
+        return lastInsn.getCatches().size() != 0;
+    }
+
+    /**
+     * Returns the exception handler types associated with this block,
+     * if any. This is just a shorthand for inspecting the last
+     * instruction in the block to see if it could throw, and if so,
+     * grabbing the catch list out of it. If not, this returns an
+     * empty list (not {@code null}).
+     *
+     * @return {@code non-null;} the exception handler types associated with
+     * this block
+     */
+    public TypeList getExceptionHandlerTypes() {
+        Insn lastInsn = insns.getLast();
+        return lastInsn.getCatches();
+    }
+
+    /**
+     * Returns an instance that is identical to this one, except that
+     * the registers in each instruction are offset by the given
+     * amount.
+     *
+     * @param delta the amount to offset register numbers by
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public BasicBlock withRegisterOffset(int delta) {
+        return new BasicBlock(label, insns.withRegisterOffset(delta),
+                              successors, primarySuccessor);
+    }
+
+    public String toString() {
+        return '{' + Hex.u2(label) + '}';
+    }
+
+    /**
+     * BasicBlock visitor interface
+     */
+    public interface Visitor {
+        /**
+         * Visits a basic block
+         * @param b block visited
+         */
+        public void visitBlock (BasicBlock b);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/BasicBlockList.java b/dexgen/src/com/android/dexgen/rop/code/BasicBlockList.java
new file mode 100644
index 0000000..f01c588
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/BasicBlockList.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.TypeList;
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.IntList;
+import com.android.dexgen.util.LabeledList;
+
+/**
+ * List of {@link BasicBlock} instances.
+ */
+public final class BasicBlockList extends LabeledList {
+    /**
+     * {@code >= -1;} the count of registers required by this method or
+     * {@code -1} if not yet calculated
+     */
+    private int regCount;
+
+    /**
+     * Constructs an instance. All indices initially contain {@code null},
+     * and the first-block label is initially {@code -1}.
+     *
+     * @param size the size of the list
+     */
+    public BasicBlockList(int size) {
+        super(size);
+
+        regCount = -1;
+    }
+
+    /**
+     * Constructs a mutable copy for {@code getMutableCopy()}.
+     *
+     * @param old block to copy
+     */
+    private BasicBlockList (BasicBlockList old) {
+        super(old);
+        regCount = old.regCount;
+    }
+
+
+    /**
+     * Gets the element at the given index. It is an error to call
+     * this with the index for an element which was never set; if you
+     * do that, this will throw {@code NullPointerException}.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @return {@code non-null;} element at that index
+     */
+    public BasicBlock get(int n) {
+        return (BasicBlock) get0(n);
+    }
+
+    /**
+     * Sets the basic block at the given index.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @param bb {@code null-ok;} the element to set at {@code n}
+     */
+    public void set(int n, BasicBlock bb) {
+        super.set(n, bb);
+
+        // Reset regCount, since it will need to be recalculated.
+        regCount = -1;
+    }
+
+    /**
+     * Returns how many registers this method requires. This is simply
+     * the maximum of register-number-plus-category referred to by this
+     * instance's instructions (indirectly through {@link BasicBlock}
+     * instances).
+     *
+     * @return {@code >= 0;} the register count
+     */
+    public int getRegCount() {
+        if (regCount == -1) {
+            RegCountVisitor visitor = new RegCountVisitor();
+            forEachInsn(visitor);
+            regCount = visitor.getRegCount();
+        }
+
+        return regCount;
+    }
+
+    /**
+     * Gets the total instruction count for this instance. This is the
+     * sum of the instruction counts of each block.
+     *
+     * @return {@code >= 0;} the total instruction count
+     */
+    public int getInstructionCount() {
+        int sz = size();
+        int result = 0;
+
+        for (int i = 0; i < sz; i++) {
+            BasicBlock one = (BasicBlock) getOrNull0(i);
+            if (one != null) {
+                result += one.getInsns().size();
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Gets the total instruction count for this instance, ignoring
+     * mark-local instructions which are not actually emitted.
+     *
+     * @return {@code >= 0;} the total instruction count
+     */
+    public int getEffectiveInstructionCount() {
+        int sz = size();
+        int result = 0;
+
+        for (int i = 0; i < sz; i++) {
+            BasicBlock one = (BasicBlock) getOrNull0(i);
+            if (one != null) {
+                InsnList insns = one.getInsns();
+                int insnsSz = insns.size();
+
+                for (int j = 0; j < insnsSz; j++) {
+                    Insn insn = insns.get(j);
+
+                    if (insn.getOpcode().getOpcode() != RegOps.MARK_LOCAL) {
+                        result++;
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+
+
+    /**
+     * Gets the first block in the list with the given label, if any.
+     *
+     * @param label {@code >= 0;} the label to look for
+     * @return {@code non-null;} the so-labelled block
+     * @throws IllegalArgumentException thrown if the label isn't found
+     */
+    public BasicBlock labelToBlock(int label) {
+        int idx = indexOfLabel(label);
+
+        if (idx < 0) {
+            throw new IllegalArgumentException("no such label: "
+                    + Hex.u2(label));
+        }
+
+        return get(idx);
+    }
+
+    /**
+     * Visits each instruction of each block in the list, in order.
+     *
+     * @param visitor {@code non-null;} visitor to use
+     */
+    public void forEachInsn(Insn.Visitor visitor) {
+        int sz = size();
+
+        for (int i = 0; i < sz; i++) {
+            BasicBlock one = get(i);
+            InsnList insns = one.getInsns();
+            insns.forEach(visitor);
+        }
+    }
+
+    /**
+     * Returns an instance that is identical to this one, except that
+     * the registers in each instruction are offset by the given
+     * amount. Mutability of the result is inherited from the
+     * original.
+     *
+     * @param delta the amount to offset register numbers by
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public BasicBlockList withRegisterOffset(int delta) {
+        int sz = size();
+        BasicBlockList result = new BasicBlockList(sz);
+
+        for (int i = 0; i < sz; i++) {
+            BasicBlock one = (BasicBlock) get0(i);
+            if (one != null) {
+                result.set(i, one.withRegisterOffset(delta));
+            }
+        }
+
+        if (isImmutable()) {
+            result.setImmutable();
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns a mutable copy of this list.
+     *
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public BasicBlockList getMutableCopy() {
+        return new BasicBlockList(this);
+    }
+
+    /**
+     * Gets the preferred successor for the given block. If the block
+     * only has one successor, then that is the preferred successor.
+     * Otherwise, if the block has a primay successor, then that is
+     * the preferred successor. If the block has no successors, then
+     * this returns {@code null}.
+     *
+     * @param block {@code non-null;} the block in question
+     * @return {@code null-ok;} the preferred successor, if any
+     */
+    public BasicBlock preferredSuccessorOf(BasicBlock block) {
+        int primarySuccessor = block.getPrimarySuccessor();
+        IntList successors = block.getSuccessors();
+        int succSize = successors.size();
+
+        switch (succSize) {
+            case 0: {
+                return null;
+            }
+            case 1: {
+                return labelToBlock(successors.get(0));
+            }
+        }
+
+        if (primarySuccessor != -1) {
+            return labelToBlock(primarySuccessor);
+        } else {
+            return labelToBlock(successors.get(0));
+        }
+    }
+
+    /**
+     * Compares the catches of two blocks for equality. This includes
+     * both the catch types and target labels.
+     *
+     * @param block1 {@code non-null;} one block to compare
+     * @param block2 {@code non-null;} the other block to compare
+     * @return {@code true} if the two blocks' non-primary successors
+     * are identical
+     */
+    public boolean catchesEqual(BasicBlock block1,
+            BasicBlock block2) {
+        TypeList catches1 = block1.getExceptionHandlerTypes();
+        TypeList catches2 = block2.getExceptionHandlerTypes();
+
+        if (!StdTypeList.equalContents(catches1, catches2)) {
+            return false;
+        }
+
+        IntList succ1 = block1.getSuccessors();
+        IntList succ2 = block2.getSuccessors();
+        int size = succ1.size(); // Both are guaranteed to be the same size.
+
+        int primary1 = block1.getPrimarySuccessor();
+        int primary2 = block2.getPrimarySuccessor();
+
+        if (((primary1 == -1) || (primary2 == -1))
+                && (primary1 != primary2)) {
+            /*
+             * For the current purpose, both blocks in question must
+             * either both have a primary or both not have a primary to
+             * be considered equal, and it turns out here that that's not
+             * the case.
+             */
+            return false;
+        }
+
+        for (int i = 0; i < size; i++) {
+            int label1 = succ1.get(i);
+            int label2 = succ2.get(i);
+
+            if (label1 == primary1) {
+                /*
+                 * It should be the case that block2's primary is at the
+                 * same index. If not, we consider the blocks unequal for
+                 * the current purpose.
+                 */
+                if (label2 != primary2) {
+                    return false;
+                }
+                continue;
+            }
+
+            if (label1 != label2) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Instruction visitor class for counting registers used.
+     */
+    private static class RegCountVisitor
+            implements Insn.Visitor {
+        /** {@code >= 0;} register count in-progress */
+        private int regCount;
+
+        /**
+         * Constructs an instance.
+         */
+        public RegCountVisitor() {
+            regCount = 0;
+        }
+
+        /**
+         * Gets the register count.
+         *
+         * @return {@code >= 0;} the count
+         */
+        public int getRegCount() {
+            return regCount;
+        }
+
+        /** {@inheritDoc} */
+        public void visitPlainInsn(PlainInsn insn) {
+            visit(insn);
+        }
+
+        /** {@inheritDoc} */
+        public void visitPlainCstInsn(PlainCstInsn insn) {
+            visit(insn);
+        }
+
+        /** {@inheritDoc} */
+        public void visitSwitchInsn(SwitchInsn insn) {
+            visit(insn);
+        }
+
+        /** {@inheritDoc} */
+        public void visitThrowingCstInsn(ThrowingCstInsn insn) {
+            visit(insn);
+        }
+
+        /** {@inheritDoc} */
+        public void visitThrowingInsn(ThrowingInsn insn) {
+            visit(insn);
+        }
+
+        /** {@inheritDoc} */
+        public void visitFillArrayDataInsn(FillArrayDataInsn insn) {
+            visit(insn);
+        }
+
+        /**
+         * Helper for all the {@code visit*} methods.
+         *
+         * @param insn {@code non-null;} instruction being visited
+         */
+        private void visit(Insn insn) {
+            RegisterSpec result = insn.getResult();
+
+            if (result != null) {
+                processReg(result);
+            }
+
+            RegisterSpecList sources = insn.getSources();
+            int sz = sources.size();
+
+            for (int i = 0; i < sz; i++) {
+                processReg(sources.get(i));
+            }
+        }
+
+        /**
+         * Processes the given register spec.
+         *
+         * @param spec {@code non-null;} the register spec
+         */
+        private void processReg(RegisterSpec spec) {
+            int reg = spec.getNextReg();
+
+            if (reg > regCount) {
+                regCount = reg;
+            }
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/ConservativeTranslationAdvice.java b/dexgen/src/com/android/dexgen/rop/code/ConservativeTranslationAdvice.java
new file mode 100644
index 0000000..080432b
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/ConservativeTranslationAdvice.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+/**
+ * Implementation of {@link TranslationAdvice} which conservatively answers
+ * {@code false} to all methods.
+ */
+public final class ConservativeTranslationAdvice
+        implements TranslationAdvice {
+    /** {@code non-null;} standard instance of this class */
+    public static final ConservativeTranslationAdvice THE_ONE =
+        new ConservativeTranslationAdvice();
+
+    /**
+     * This class is not publicly instantiable. Use {@link #THE_ONE}.
+     */
+    private ConservativeTranslationAdvice() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    public boolean hasConstantOperation(Rop opcode,
+            RegisterSpec sourceA, RegisterSpec sourceB) {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean requiresSourcesInOrder(Rop opcode,
+            RegisterSpecList sources) {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxOptimalRegisterCount() {
+        return Integer.MAX_VALUE;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/CstInsn.java b/dexgen/src/com/android/dexgen/rop/code/CstInsn.java
new file mode 100644
index 0000000..dc5ed39
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/CstInsn.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.cst.Constant;
+
+/**
+ * Instruction which contains an explicit reference to a constant.
+ */
+public abstract class CstInsn
+        extends Insn {
+    /** {@code non-null;} the constant */
+    private final Constant cst;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param opcode {@code non-null;} the opcode
+     * @param position {@code non-null;} source position
+     * @param result {@code null-ok;} spec for the result, if any
+     * @param sources {@code non-null;} specs for all the sources
+     * @param cst {@code non-null;} constant
+     */
+    public CstInsn(Rop opcode, SourcePosition position, RegisterSpec result,
+                   RegisterSpecList sources, Constant cst) {
+        super(opcode, position, result, sources);
+
+        if (cst == null) {
+            throw new NullPointerException("cst == null");
+        }
+
+        this.cst = cst;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getInlineString() {
+        return cst.toHuman();
+    }
+
+    /**
+     * Gets the constant.
+     *
+     * @return {@code non-null;} the constant
+     */
+    public Constant getConstant() {
+        return cst;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean contentEquals(Insn b) {
+        /*
+         * The cast (CstInsn)b below should always succeed since
+         * Insn.contentEquals compares classes of this and b.
+         */
+        return super.contentEquals(b)
+                && cst.equals(((CstInsn)b).getConstant());
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/DexTranslationAdvice.java b/dexgen/src/com/android/dexgen/rop/code/DexTranslationAdvice.java
new file mode 100644
index 0000000..b46182d
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/DexTranslationAdvice.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.cst.CstInteger;
+import com.android.dexgen.rop.type.Type;
+
+/**
+ * Implementation of {@link TranslationAdvice} which represents what
+ * the dex format will be able to represent.
+ */
+public final class DexTranslationAdvice
+        implements TranslationAdvice {
+    /** {@code non-null;} standard instance of this class */
+    public static final DexTranslationAdvice THE_ONE =
+        new DexTranslationAdvice();
+
+    /** debug advice for disabling invoke-range optimization */
+    public static final DexTranslationAdvice NO_SOURCES_IN_ORDER =
+        new DexTranslationAdvice(true);
+
+    /**
+     * The minimum source width, in register units, for an invoke
+     * instruction that requires its sources to be in order and contiguous.
+     */
+    private static final int MIN_INVOKE_IN_ORDER = 6;
+
+    /** when true: always returns false for requiresSourcesInOrder */
+    private final boolean disableSourcesInOrder;
+
+    /**
+     * This class is not publicly instantiable. Use {@link #THE_ONE}.
+     */
+    private DexTranslationAdvice() {
+        disableSourcesInOrder = false;
+    }
+
+    private DexTranslationAdvice(boolean disableInvokeRange) {
+        this.disableSourcesInOrder = disableInvokeRange;
+    }
+
+    /** {@inheritDoc} */
+    public boolean hasConstantOperation(Rop opcode,
+            RegisterSpec sourceA, RegisterSpec sourceB) {
+        if (sourceA.getType() != Type.INT) {
+            return false;
+        }
+
+        if (! (sourceB.getTypeBearer() instanceof CstInteger)) {
+            return false;
+        }
+
+        CstInteger cst = (CstInteger) sourceB.getTypeBearer();
+
+        // TODO handle rsub
+        switch (opcode.getOpcode()) {
+            // These have 8 and 16 bit cst representations
+            case RegOps.REM:
+            case RegOps.ADD:
+            case RegOps.MUL:
+            case RegOps.DIV:
+            case RegOps.AND:
+            case RegOps.OR:
+            case RegOps.XOR:
+                return cst.fitsIn16Bits();
+            // These only have 8 bit cst reps
+            case RegOps.SHL:
+            case RegOps.SHR:
+            case RegOps.USHR:
+                return cst.fitsIn8Bits();
+            default:
+                return false;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public boolean requiresSourcesInOrder(Rop opcode,
+            RegisterSpecList sources) {
+
+        return !disableSourcesInOrder && opcode.isCallLike()
+                && totalRopWidth(sources) >= MIN_INVOKE_IN_ORDER;
+    }
+
+    /**
+     * Calculates the total rop width of the list of SSA registers
+     *
+     * @param sources {@code non-null;} list of SSA registers
+     * @return {@code >= 0;} rop-form width in register units
+     */
+    private int totalRopWidth(RegisterSpecList sources) {
+        int sz = sources.size();
+        int total = 0;
+
+        for (int i = 0; i < sz; i++) {
+            total += sources.get(i).getCategory();
+        }
+
+        return total;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxOptimalRegisterCount() {
+        return 16;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/Exceptions.java b/dexgen/src/com/android/dexgen/rop/code/Exceptions.java
new file mode 100644
index 0000000..d6584d0
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/Exceptions.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.Type;
+
+/**
+ * Common exception types.
+ */
+public final class Exceptions {
+    /** {@code non-null;} the type {@code java.lang.ArithmeticException} */
+    public static final Type TYPE_ArithmeticException =
+        Type.intern("Ljava/lang/ArithmeticException;");
+
+    /**
+     * {@code non-null;} the type
+     * {@code java.lang.ArrayIndexOutOfBoundsException}
+     */
+    public static final Type TYPE_ArrayIndexOutOfBoundsException =
+        Type.intern("Ljava/lang/ArrayIndexOutOfBoundsException;");
+
+    /** {@code non-null;} the type {@code java.lang.ArrayStoreException} */
+    public static final Type TYPE_ArrayStoreException =
+        Type.intern("Ljava/lang/ArrayStoreException;");
+
+    /** {@code non-null;} the type {@code java.lang.ClassCastException} */
+    public static final Type TYPE_ClassCastException =
+        Type.intern("Ljava/lang/ClassCastException;");
+
+    /** {@code non-null;} the type {@code java.lang.Error} */
+    public static final Type TYPE_Error = Type.intern("Ljava/lang/Error;");
+
+    /**
+     * {@code non-null;} the type
+     * {@code java.lang.IllegalMonitorStateException}
+     */
+    public static final Type TYPE_IllegalMonitorStateException =
+        Type.intern("Ljava/lang/IllegalMonitorStateException;");
+
+    /** {@code non-null;} the type {@code java.lang.NegativeArraySizeException} */
+    public static final Type TYPE_NegativeArraySizeException =
+        Type.intern("Ljava/lang/NegativeArraySizeException;");
+
+    /** {@code non-null;} the type {@code java.lang.NullPointerException} */
+    public static final Type TYPE_NullPointerException =
+        Type.intern("Ljava/lang/NullPointerException;");
+
+    /** {@code non-null;} the list {@code [java.lang.Error]} */
+    public static final StdTypeList LIST_Error = StdTypeList.make(TYPE_Error);
+
+    /**
+     * {@code non-null;} the list {@code[java.lang.Error,
+     * java.lang.ArithmeticException]}
+     */
+    public static final StdTypeList LIST_Error_ArithmeticException =
+        StdTypeList.make(TYPE_Error, TYPE_ArithmeticException);
+
+    /**
+     * {@code non-null;} the list {@code[java.lang.Error,
+     * java.lang.ClassCastException]}
+     */
+    public static final StdTypeList LIST_Error_ClassCastException =
+        StdTypeList.make(TYPE_Error, TYPE_ClassCastException);
+
+    /**
+     * {@code non-null;} the list {@code [java.lang.Error,
+     * java.lang.NegativeArraySizeException]}
+     */
+    public static final StdTypeList LIST_Error_NegativeArraySizeException =
+        StdTypeList.make(TYPE_Error, TYPE_NegativeArraySizeException);
+
+    /**
+     * {@code non-null;} the list {@code [java.lang.Error,
+     * java.lang.NullPointerException]}
+     */
+    public static final StdTypeList LIST_Error_NullPointerException =
+        StdTypeList.make(TYPE_Error, TYPE_NullPointerException);
+
+    /**
+     * {@code non-null;} the list {@code [java.lang.Error,
+     * java.lang.NullPointerException,
+     * java.lang.ArrayIndexOutOfBoundsException]}
+     */
+    public static final StdTypeList LIST_Error_Null_ArrayIndexOutOfBounds =
+        StdTypeList.make(TYPE_Error,
+                      TYPE_NullPointerException,
+                      TYPE_ArrayIndexOutOfBoundsException);
+
+    /**
+     * {@code non-null;} the list {@code [java.lang.Error,
+     * java.lang.NullPointerException,
+     * java.lang.ArrayIndexOutOfBoundsException,
+     * java.lang.ArrayStoreException]}
+     */
+    public static final StdTypeList LIST_Error_Null_ArrayIndex_ArrayStore =
+        StdTypeList.make(TYPE_Error,
+                      TYPE_NullPointerException,
+                      TYPE_ArrayIndexOutOfBoundsException,
+                      TYPE_ArrayStoreException);
+
+    /**
+     * {@code non-null;} the list {@code [java.lang.Error,
+     * java.lang.NullPointerException,
+     * java.lang.IllegalMonitorStateException]}
+     */
+    public static final StdTypeList
+        LIST_Error_Null_IllegalMonitorStateException =
+        StdTypeList.make(TYPE_Error,
+                      TYPE_NullPointerException,
+                      TYPE_IllegalMonitorStateException);
+
+    /**
+     * This class is uninstantiable.
+     */
+    private Exceptions() {
+        // This space intentionally left blank.
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/FillArrayDataInsn.java b/dexgen/src/com/android/dexgen/rop/code/FillArrayDataInsn.java
new file mode 100644
index 0000000..3289b58
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/FillArrayDataInsn.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeList;
+
+import java.util.ArrayList;
+
+/**
+ * Instruction which fills a newly created array with a predefined list of
+ * constant values.
+ */
+public final class FillArrayDataInsn
+        extends Insn {
+
+    /** non-null: initial values to fill the newly created array */
+    private final ArrayList<Constant> initValues;
+
+    /**
+     * non-null: type of the array. Will be used to determine the width of
+     * elements in the array-data table.
+     */
+    private final Constant arrayType;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param opcode {@code non-null;} the opcode
+     * @param position {@code non-null;} source position
+     * @param sources {@code non-null;} specs for all the sources
+     * @param initValues {@code non-null;} list of initial values to fill the array
+     * @param cst {@code non-null;} type of the new array
+     */
+    public FillArrayDataInsn(Rop opcode, SourcePosition position,
+                             RegisterSpecList sources,
+                             ArrayList<Constant> initValues,
+                             Constant cst) {
+        super(opcode, position, null, sources);
+
+        if (opcode.getBranchingness() != Rop.BRANCH_NONE) {
+            throw new IllegalArgumentException("bogus branchingness");
+        }
+
+        this.initValues = initValues;
+        this.arrayType = cst;
+    }
+
+
+    /** {@inheritDoc} */
+    @Override
+    public TypeList getCatches() {
+        return StdTypeList.EMPTY;
+    }
+
+    /**
+     * Return the list of init values
+     * @return {@code non-null;} list of init values
+     */
+    public ArrayList<Constant> getInitValues() {
+        return initValues;
+    }
+
+    /**
+     * Return the type of the newly created array
+     * @return {@code non-null;} array type
+     */
+    public Constant getConstant() {
+        return arrayType;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void accept(Visitor visitor) {
+        visitor.visitFillArrayDataInsn(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withAddedCatch(Type type) {
+        throw new  UnsupportedOperationException("unsupported");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withRegisterOffset(int delta) {
+        return new FillArrayDataInsn(getOpcode(), getPosition(),
+                                     getSources().withOffset(delta),
+                                     initValues, arrayType);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withNewRegisters(RegisterSpec result,
+            RegisterSpecList sources) {
+
+        return new FillArrayDataInsn(getOpcode(), getPosition(),
+                                     sources, initValues, arrayType);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/Insn.java b/dexgen/src/com/android/dexgen/rop/code/Insn.java
new file mode 100644
index 0000000..4bb10d2
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/Insn.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeList;
+import com.android.dexgen.util.ToHuman;
+
+/**
+ * A register-based instruction. An instruction is the combination of
+ * an opcode (which specifies operation and source/result types), a
+ * list of actual sources and result registers/values, and additional
+ * information.
+ */
+public abstract class Insn implements ToHuman {
+    /** {@code non-null;} opcode */
+    private final Rop opcode;
+
+    /** {@code non-null;} source position */
+    private final SourcePosition position;
+
+    /** {@code null-ok;} spec for the result of this instruction, if any */
+    private final RegisterSpec result;
+
+    /** {@code non-null;} specs for all the sources of this instruction */
+    private final RegisterSpecList sources;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param opcode {@code non-null;} the opcode
+     * @param position {@code non-null;} source position
+     * @param result {@code null-ok;} spec for the result, if any
+     * @param sources {@code non-null;} specs for all the sources
+     */
+    public Insn(Rop opcode, SourcePosition position, RegisterSpec result,
+                RegisterSpecList sources) {
+        if (opcode == null) {
+            throw new NullPointerException("opcode == null");
+        }
+
+        if (position == null) {
+            throw new NullPointerException("position == null");
+        }
+
+        if (sources == null) {
+            throw new NullPointerException("sources == null");
+        }
+
+        this.opcode = opcode;
+        this.position = position;
+        this.result = result;
+        this.sources = sources;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Instances of this class compare by identity. That is,
+     * {@code x.equals(y)} is only true if {@code x == y}.
+     */
+    @Override
+    public final boolean equals(Object other) {
+        return (this == other);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * This implementation returns the identity hashcode of this
+     * instance. This is proper, since instances of this class compare
+     * by identity (see {@link #equals}).
+     */
+    @Override
+    public final int hashCode() {
+        return System.identityHashCode(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return toStringWithInline(getInlineString());
+    }
+
+    /**
+     * Gets a human-oriented (and slightly lossy) string for this instance.
+     *
+     * @return {@code non-null;} the human string form
+     */
+    public String toHuman() {
+        return toHumanWithInline(getInlineString());
+    }
+
+    /**
+     * Gets an "inline" string portion for toHuman(), if available. This
+     * is the portion that appears after the Rop opcode
+     *
+     * @return {@code null-ok;} if non-null, the inline text for toHuman()
+     */
+    public String getInlineString() {
+        return null;
+    }
+
+    /**
+     * Gets the opcode.
+     *
+     * @return {@code non-null;} the opcode
+     */
+    public final Rop getOpcode() {
+        return opcode;
+    }
+
+    /**
+     * Gets the source position.
+     *
+     * @return {@code non-null;} the source position
+     */
+    public final SourcePosition getPosition() {
+        return position;
+    }
+
+    /**
+     * Gets the result spec, if any. A return value of {@code null}
+     * means this instruction returns nothing.
+     *
+     * @return {@code null-ok;} the result spec, if any
+     */
+    public final RegisterSpec getResult() {
+        return result;
+    }
+
+    /**
+     * Gets the spec of a local variable assignment that occurs at this
+     * instruction, or null if no local variable assignment occurs. This
+     * may be the result register, or for {@code mark-local} insns
+     * it may be the source.
+     *
+     * @return {@code null-ok;} a named register spec or null
+     */
+    public final RegisterSpec getLocalAssignment() {
+        RegisterSpec assignment;
+        if (opcode.getOpcode() == RegOps.MARK_LOCAL) {
+            assignment = sources.get(0);
+        } else {
+            assignment = result;
+        }
+
+        if (assignment == null) {
+            return null;
+        }
+
+        LocalItem localItem = assignment.getLocalItem();
+
+        if (localItem == null) {
+            return null;
+        }
+
+        return assignment;
+    }
+
+    /**
+     * Gets the source specs.
+     *
+     * @return {@code non-null;} the source specs
+     */
+    public final RegisterSpecList getSources() {
+        return sources;
+    }
+
+    /**
+     * Gets whether this instruction can possibly throw an exception. This
+     * is just a convenient wrapper for {@code getOpcode().canThrow()}.
+     *
+     * @return {@code true} iff this instruction can possibly throw
+     */
+    public final boolean canThrow() {
+        return opcode.canThrow();
+    }
+
+    /**
+     * Gets the list of possibly-caught exceptions. This returns {@link
+     * StdTypeList#EMPTY} if this instruction has no handlers,
+     * which can be <i>either</i> if this instruction can't possibly
+     * throw or if it merely doesn't handle any of its possible
+     * exceptions. To determine whether this instruction can throw,
+     * use {@link #canThrow}.
+     *
+     * @return {@code non-null;} the catches list
+     */
+    public abstract TypeList getCatches();
+
+    /**
+     * Calls the appropriate method on the given visitor, depending on the
+     * class of this instance. Subclasses must override this.
+     *
+     * @param visitor {@code non-null;} the visitor to call on
+     */
+    public abstract void accept(Visitor visitor);
+
+    /**
+     * Returns an instance that is just like this one, except that it
+     * has a catch list with the given item appended to the end. This
+     * method throws an exception if this instance can't possibly
+     * throw. To determine whether this instruction can throw, use
+     * {@link #canThrow}.
+     *
+     * @param type {@code non-null;} type to append to the catch list
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public abstract Insn withAddedCatch(Type type);
+
+    /**
+     * Returns an instance that is just like this one, except that all
+     * register references have been offset by the given delta.
+     *
+     * @param delta the amount to offset register references by
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public abstract Insn withRegisterOffset(int delta);
+
+    /**
+     * Returns an instance that is just like this one, except that, if
+     * possible, the insn is converted into a version in which the last
+     * source (if it is a constant) is represented directly rather than
+     * as a register reference. {@code this} is returned in cases where
+     * the translation is not possible.
+     *
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public Insn withLastSourceLiteral() {
+        return this;
+    }
+
+    /**
+     * Returns an exact copy of this Insn
+     *
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public Insn copy() {
+        return withRegisterOffset(0);
+    }
+
+
+    /**
+     * Compares, handling nulls safely
+     *
+     * @param a first object
+     * @param b second object
+     * @return true if they're equal or both null.
+     */
+    private static boolean equalsHandleNulls (Object a, Object b) {
+        return (a == b) || ((a != null) && a.equals(b));
+    }
+
+    /**
+     * Compares Insn contents, since {@code Insn.equals()} is defined
+     * to be an identity compare. Insn's are {@code contentEquals()}
+     * if they have the same opcode, registers, source position, and other
+     * metadata.
+     *
+     * @return true in the case described above
+     */
+    public boolean contentEquals(Insn b) {
+        return opcode == b.getOpcode()
+                && position.equals(b.getPosition())
+                && (getClass() == b.getClass())
+                && equalsHandleNulls(result, b.getResult())
+                && equalsHandleNulls(sources, b.getSources())
+                && StdTypeList.equalContents(getCatches(), b.getCatches());
+    }
+
+    /**
+     * Returns an instance that is just like this one, except
+     * with new result and source registers.
+     *
+     * @param result {@code null-ok;} new result register
+     * @param sources {@code non-null;} new sources registers
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public abstract Insn withNewRegisters(RegisterSpec result,
+            RegisterSpecList sources);
+
+    /**
+     * Returns the string form of this instance, with the given bit added in
+     * the standard location for an inline argument.
+     *
+     * @param extra {@code null-ok;} the inline argument string
+     * @return {@code non-null;} the string form
+     */
+    protected final String toStringWithInline(String extra) {
+        StringBuffer sb = new StringBuffer(80);
+
+        sb.append("Insn{");
+        sb.append(position);
+        sb.append(' ');
+        sb.append(opcode);
+
+        if (extra != null) {
+            sb.append(' ');
+            sb.append(extra);
+        }
+
+        sb.append(" :: ");
+
+        if (result != null) {
+            sb.append(result);
+            sb.append(" <- ");
+        }
+
+        sb.append(sources);
+        sb.append('}');
+
+        return sb.toString();
+    }
+
+    /**
+     * Returns the human string form of this instance, with the given
+     * bit added in the standard location for an inline argument.
+     *
+     * @param extra {@code null-ok;} the inline argument string
+     * @return {@code non-null;} the human string form
+     */
+    protected final String toHumanWithInline(String extra) {
+        StringBuffer sb = new StringBuffer(80);
+
+        sb.append(position);
+        sb.append(": ");
+        sb.append(opcode.getNickname());
+
+        if (extra != null) {
+            sb.append("(");
+            sb.append(extra);
+            sb.append(")");
+        }
+
+        if (result == null) {
+            sb.append(" .");
+        } else {
+            sb.append(" ");
+            sb.append(result.toHuman());
+        }
+
+        sb.append(" <-");
+
+        int sz = sources.size();
+        if (sz == 0) {
+            sb.append(" .");
+        } else {
+            for (int i = 0; i < sz; i++) {
+                sb.append(" ");
+                sb.append(sources.get(i).toHuman());
+            }
+        }
+
+        return sb.toString();
+    }
+
+
+    /**
+     * Visitor interface for this (outer) class.
+     */
+    public static interface Visitor {
+        /**
+         * Visits a {@link PlainInsn}.
+         *
+         * @param insn {@code non-null;} the instruction to visit
+         */
+        public void visitPlainInsn(PlainInsn insn);
+
+        /**
+         * Visits a {@link PlainCstInsn}.
+         *
+         * @param insn {@code non-null;} the instruction to visit
+         */
+        public void visitPlainCstInsn(PlainCstInsn insn);
+
+        /**
+         * Visits a {@link SwitchInsn}.
+         *
+         * @param insn {@code non-null;} the instruction to visit
+         */
+        public void visitSwitchInsn(SwitchInsn insn);
+
+        /**
+         * Visits a {@link ThrowingCstInsn}.
+         *
+         * @param insn {@code non-null;} the instruction to visit
+         */
+        public void visitThrowingCstInsn(ThrowingCstInsn insn);
+
+        /**
+         * Visits a {@link ThrowingInsn}.
+         *
+         * @param insn {@code non-null;} the instruction to visit
+         */
+        public void visitThrowingInsn(ThrowingInsn insn);
+
+        /**
+         * Visits a {@link FillArrayDataInsn}.
+         *
+         * @param insn {@code non-null;} the instruction to visit
+         */
+        public void visitFillArrayDataInsn(FillArrayDataInsn insn);
+    }
+
+    /**
+     * Base implementation of {@link Visitor}, which has empty method
+     * bodies for all methods.
+     */
+    public static class BaseVisitor implements Visitor {
+        /** {@inheritDoc} */
+        public void visitPlainInsn(PlainInsn insn) {
+            // This space intentionally left blank.
+        }
+
+        /** {@inheritDoc} */
+        public void visitPlainCstInsn(PlainCstInsn insn) {
+            // This space intentionally left blank.
+        }
+
+        /** {@inheritDoc} */
+        public void visitSwitchInsn(SwitchInsn insn) {
+            // This space intentionally left blank.
+        }
+
+        /** {@inheritDoc} */
+        public void visitThrowingCstInsn(ThrowingCstInsn insn) {
+            // This space intentionally left blank.
+        }
+
+        /** {@inheritDoc} */
+        public void visitThrowingInsn(ThrowingInsn insn) {
+            // This space intentionally left blank.
+        }
+
+        /** {@inheritDoc} */
+        public void visitFillArrayDataInsn(FillArrayDataInsn insn) {
+            // This space intentionally left blank.
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/InsnList.java b/dexgen/src/com/android/dexgen/rop/code/InsnList.java
new file mode 100644
index 0000000..0046972
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/InsnList.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.util.FixedSizeList;
+
+/**
+ * List of {@link Insn} instances.
+ */
+public final class InsnList
+        extends FixedSizeList {
+    /**
+     * Constructs an instance. All indices initially contain {@code null}.
+     *
+     * @param size the size of the list
+     */
+    public InsnList(int size) {
+        super(size);
+    }
+
+    /**
+     * Gets the element at the given index. It is an error to call
+     * this with the index for an element which was never set; if you
+     * do that, this will throw {@code NullPointerException}.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @return {@code non-null;} element at that index
+     */
+    public Insn get(int n) {
+        return (Insn) get0(n);
+    }
+
+    /**
+     * Sets the instruction at the given index.
+     *
+     * @param n {@code >= 0, < size();} which index
+     * @param insn {@code non-null;} the instruction to set at {@code n}
+     */
+    public void set(int n, Insn insn) {
+        set0(n, insn);
+    }
+
+    /**
+     * Gets the last instruction. This is just a convenient shorthand for
+     * {@code get(size() - 1)}.
+     *
+     * @return {@code non-null;} the last instruction
+     */
+    public Insn getLast() {
+        return get(size() - 1);
+    }
+
+    /**
+     * Visits each instruction in the list, in order.
+     *
+     * @param visitor {@code non-null;} visitor to use
+     */
+    public void forEach(Insn.Visitor visitor) {
+        int sz = size();
+
+        for (int i = 0; i < sz; i++) {
+            get(i).accept(visitor);
+        }
+    }
+
+    /**
+     * Compares the contents of this {@code InsnList} with another.
+     * The blocks must have the same number of insns, and each Insn must
+     * also return true to {@code Insn.contentEquals()}.
+     *
+     * @param b to compare
+     * @return true in the case described above.
+     */
+    public boolean contentEquals(InsnList b) {
+        if (b == null) return false;
+
+        int sz = size();
+
+        if (sz != b.size()) return false;
+
+        for (int i = 0; i < sz; i++) {
+            if (!get(i).contentEquals(b.get(i))) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns an instance that is identical to this one, except that
+     * the registers in each instruction are offset by the given
+     * amount. Mutability of the result is inherited from the
+     * original.
+     *
+     * @param delta the amount to offset register numbers by
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public InsnList withRegisterOffset(int delta) {
+        int sz = size();
+        InsnList result = new InsnList(sz);
+
+        for (int i = 0; i < sz; i++) {
+            Insn one = (Insn) get0(i);
+            if (one != null) {
+                result.set0(i, one.withRegisterOffset(delta));
+            }
+        }
+
+        if (isImmutable()) {
+            result.setImmutable();
+        }
+
+        return result;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/LocalItem.java b/dexgen/src/com/android/dexgen/rop/code/LocalItem.java
new file mode 100644
index 0000000..78386f1
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/LocalItem.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.cst.CstUtf8;
+
+/**
+ * A local variable item: either a name or a signature or both.
+ */
+public class LocalItem implements Comparable<LocalItem> {
+    /** {@code null-ok;} local variable name */
+    private final CstUtf8 name;
+
+    /** {@code null-ok;} local variable signature */
+    private final CstUtf8 signature;
+
+    /**
+     * Make a new item. If both name and signature are null, null is returned.
+     *
+     * TODO: intern these
+     *
+     * @param name {@code null-ok;} local variable name
+     * @param signature {@code null-ok;} local variable signature
+     * @return {@code non-null;} appropriate instance.
+     */
+    public static LocalItem make(CstUtf8 name, CstUtf8 signature) {
+        if (name == null && signature == null) {
+            return null;
+        }
+
+        return new LocalItem (name, signature);
+    }
+
+    /**
+     * Constructs instance.
+     *
+     * @param name {@code null-ok;} local variable name
+     * @param signature {@code null-ok;} local variable signature
+     */
+    private LocalItem(CstUtf8 name, CstUtf8 signature) {
+        this.name = name;
+        this.signature = signature;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof LocalItem)) {
+            return false;
+        }
+
+        LocalItem local = (LocalItem) other;
+
+        return 0 == compareTo(local);
+    }
+
+    /**
+     * Compares two strings like String.compareTo(), excepts treats a null
+     * as the least-possible string value.
+     *
+     * @return negative integer, zero, or positive integer in accordance
+     * with Comparable.compareTo()
+     */
+    private static int compareHandlesNulls(CstUtf8 a, CstUtf8 b) {
+        if (a == b) {
+            return 0;
+        } else if (a == null) {
+            return -1;
+        } else if (b == null) {
+            return 1;
+        } else {
+            return a.compareTo(b);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public int compareTo(LocalItem local) {
+        int ret;
+
+        ret = compareHandlesNulls(name, local.name);
+
+        if (ret != 0) {
+            return ret;
+        }
+
+        ret = compareHandlesNulls(signature, local.signature);
+
+        return ret;
+    }
+
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return (name == null ? 0 : name.hashCode()) * 31
+                + (signature == null ? 0 : signature.hashCode());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        if (name != null && signature == null) {
+            return name.toQuoted();
+        } else if (name == null && signature == null) {
+            return "";
+        }
+
+        return "[" + (name == null ? "" : name.toQuoted())
+                + "|" + (signature == null ? "" : signature.toQuoted());
+    }
+
+    /**
+     * Gets name.
+     *
+     * @return {@code null-ok;} name
+     */
+    public CstUtf8 getName() {
+        return name;
+    }
+
+    /**
+     * Gets signature.
+     *
+     * @return {@code null-ok;} signature
+     */
+    public CstUtf8 getSignature() {
+        return signature;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/LocalVariableExtractor.java b/dexgen/src/com/android/dexgen/rop/code/LocalVariableExtractor.java
new file mode 100644
index 0000000..14f5f15
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/LocalVariableExtractor.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.util.Bits;
+import com.android.dexgen.util.IntList;
+
+/**
+ * Code to figure out which local variables are active at which points in
+ * a method.
+ */
+public final class LocalVariableExtractor {
+    /** {@code non-null;} method being extracted from */
+    private final RopMethod method;
+
+    /** {@code non-null;} block list for the method */
+    private final BasicBlockList blocks;
+
+    /** {@code non-null;} result in-progress */
+    private final LocalVariableInfo resultInfo;
+
+    /** {@code non-null;} work set indicating blocks needing to be processed */
+    private final int[] workSet;
+
+    /**
+     * Extracts out all the local variable information from the given method.
+     *
+     * @param method {@code non-null;} the method to extract from
+     * @return {@code non-null;} the extracted information
+     */
+    public static LocalVariableInfo extract(RopMethod method) {
+        LocalVariableExtractor lve = new LocalVariableExtractor(method);
+        return lve.doit();
+    }
+
+    /**
+     * Constructs an instance. This method is private. Use {@link #extract}.
+     *
+     * @param method {@code non-null;} the method to extract from
+     */
+    private LocalVariableExtractor(RopMethod method) {
+        if (method == null) {
+            throw new NullPointerException("method == null");
+        }
+
+        BasicBlockList blocks = method.getBlocks();
+        int maxLabel = blocks.getMaxLabel();
+
+        this.method = method;
+        this.blocks = blocks;
+        this.resultInfo = new LocalVariableInfo(method);
+        this.workSet = Bits.makeBitSet(maxLabel);
+    }
+
+    /**
+     * Does the extraction.
+     *
+     * @return {@code non-null;} the extracted information
+     */
+    private LocalVariableInfo doit() {
+        for (int label = method.getFirstLabel();
+             label >= 0;
+             label = Bits.findFirst(workSet, 0)) {
+            Bits.clear(workSet, label);
+            processBlock(label);
+        }
+
+        resultInfo.setImmutable();
+        return resultInfo;
+    }
+
+    /**
+     * Processes a single block.
+     *
+     * @param label {@code >= 0;} label of the block to process
+     */
+    private void processBlock(int label) {
+        RegisterSpecSet primaryState = resultInfo.mutableCopyOfStarts(label);
+        BasicBlock block = blocks.labelToBlock(label);
+        InsnList insns = block.getInsns();
+        int insnSz = insns.size();
+
+        /*
+         * We may have to treat the last instruction specially: If it
+         * can (but doesn't always) throw, and the exception can be
+         * caught within the same method, then we need to use the
+         * state *before* executing it to be what is merged into
+         * exception targets.
+         */
+        boolean canThrowDuringLastInsn = block.hasExceptionHandlers() &&
+            (insns.getLast().getResult() != null);
+        int freezeSecondaryStateAt = insnSz - 1;
+        RegisterSpecSet secondaryState = primaryState;
+
+        /*
+         * Iterate over the instructions, adding information for each place
+         * that the active variable set changes.
+         */
+
+        for (int i = 0; i < insnSz; i++) {
+            if (canThrowDuringLastInsn && (i == freezeSecondaryStateAt)) {
+                // Until this point, primaryState == secondaryState.
+                primaryState.setImmutable();
+                primaryState = primaryState.mutableCopy();
+            }
+
+            Insn insn = insns.get(i);
+            RegisterSpec result;
+
+            result = insn.getLocalAssignment();
+
+            if (result == null) {
+                /*
+                 * If an assignment assigns over an existing local, make
+                 * sure to mark the local as going out of scope.
+                 */
+
+                result = insn.getResult();
+
+                if (result != null
+                        && primaryState.get(result.getReg()) != null) {
+                    primaryState.remove(primaryState.get(result.getReg()));
+                }
+                continue;
+            }
+
+            result = result.withSimpleType();
+
+            RegisterSpec already = primaryState.get(result);
+            /*
+             * The equals() check ensures we only add new info if
+             * the instruction causes a change to the set of
+             * active variables.
+             */
+            if (!result.equals(already)) {
+                /*
+                 * If this insn represents a local moving from one register
+                 * to another, remove the association between the old register
+                 * and the local.
+                 */
+                RegisterSpec previous
+                        = primaryState.localItemToSpec(result.getLocalItem());
+
+                if (previous != null
+                        && (previous.getReg() != result.getReg())) {
+
+                    primaryState.remove(previous);
+                }
+
+                resultInfo.addAssignment(insn, result);
+                primaryState.put(result);
+            }
+        }
+
+        primaryState.setImmutable();
+
+        /*
+         * Merge this state into the start state for each successor,
+         * and update the work set where required (that is, in cases
+         * where the start state for a block changes).
+         */
+
+        IntList successors = block.getSuccessors();
+        int succSz = successors.size();
+        int primarySuccessor = block.getPrimarySuccessor();
+
+        for (int i = 0; i < succSz; i++) {
+            int succ = successors.get(i);
+            RegisterSpecSet state = (succ == primarySuccessor) ?
+                primaryState : secondaryState;
+
+            if (resultInfo.mergeStarts(succ, state)) {
+                Bits.set(workSet, succ);
+            }
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/LocalVariableInfo.java b/dexgen/src/com/android/dexgen/rop/code/LocalVariableInfo.java
new file mode 100644
index 0000000..b126a4c
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/LocalVariableInfo.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.type.TypeBearer;
+import com.android.dexgen.util.MutabilityControl;
+
+import java.util.HashMap;
+
+/**
+ * Container for local variable information for a particular {@link
+ * RopMethod}.
+ */
+public final class LocalVariableInfo
+        extends MutabilityControl {
+    /** {@code >= 0;} the register count for the method */
+    private final int regCount;
+
+    /**
+     * {@code non-null;} {@link RegisterSpecSet} to use when indicating a block
+     * that has no locals; it is empty and immutable but has an appropriate
+     * max size for the method
+     */
+    private final RegisterSpecSet emptySet;
+
+    /**
+     * {@code non-null;} array consisting of register sets representing the
+     * sets of variables already assigned upon entry to each block,
+     * where array indices correspond to block labels
+     */
+    private final RegisterSpecSet[] blockStarts;
+
+    /** {@code non-null;} map from instructions to the variable each assigns */
+    private final HashMap<Insn, RegisterSpec> insnAssignments;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param method {@code non-null;} the method being represented by this instance
+     */
+    public LocalVariableInfo(RopMethod method) {
+        if (method == null) {
+            throw new NullPointerException("method == null");
+        }
+
+        BasicBlockList blocks = method.getBlocks();
+        int maxLabel = blocks.getMaxLabel();
+
+        this.regCount = blocks.getRegCount();
+        this.emptySet = new RegisterSpecSet(regCount);
+        this.blockStarts = new RegisterSpecSet[maxLabel];
+        this.insnAssignments =
+            new HashMap<Insn, RegisterSpec>(blocks.getInstructionCount());
+
+        emptySet.setImmutable();
+    }
+
+    /**
+     * Sets the register set associated with the start of the block with
+     * the given label.
+     *
+     * @param label {@code >= 0;} the block label
+     * @param specs {@code non-null;} the register set to associate with the block
+     */
+    public void setStarts(int label, RegisterSpecSet specs) {
+        throwIfImmutable();
+
+        if (specs == null) {
+            throw new NullPointerException("specs == null");
+        }
+
+        try {
+            blockStarts[label] = specs;
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            // Translate the exception.
+            throw new IllegalArgumentException("bogus label");
+        }
+    }
+
+    /**
+     * Merges the given register set into the set for the block with the
+     * given label. If there was not already an associated set, then this
+     * is the same as calling {@link #setStarts}. Otherwise, this will
+     * merge the two sets and call {@link #setStarts} on the result of the
+     * merge.
+     *
+     * @param label {@code >= 0;} the block label
+     * @param specs {@code non-null;} the register set to merge into the start set
+     * for the block
+     * @return {@code true} if the merge resulted in an actual change
+     * to the associated set (including storing one for the first time) or
+     * {@code false} if there was no change
+     */
+    public boolean mergeStarts(int label, RegisterSpecSet specs) {
+        RegisterSpecSet start = getStarts0(label);
+        boolean changed = false;
+
+        if (start == null) {
+            setStarts(label, specs);
+            return true;
+        }
+
+        RegisterSpecSet newStart = start.mutableCopy();
+        newStart.intersect(specs, true);
+
+        if (start.equals(newStart)) {
+            return false;
+        }
+
+        newStart.setImmutable();
+        setStarts(label, newStart);
+
+        return true;
+    }
+
+    /**
+     * Gets the register set associated with the start of the block
+     * with the given label. This returns an empty set with the appropriate
+     * max size if no set was associated with the block in question.
+     *
+     * @param label {@code >= 0;} the block label
+     * @return {@code non-null;} the associated register set
+     */
+    public RegisterSpecSet getStarts(int label) {
+        RegisterSpecSet result = getStarts0(label);
+
+        return (result != null) ? result : emptySet;
+    }
+
+    /**
+     * Gets the register set associated with the start of the given
+     * block. This is just convenient shorthand for
+     * {@code getStarts(block.getLabel())}.
+     *
+     * @param block {@code non-null;} the block in question
+     * @return {@code non-null;} the associated register set
+     */
+    public RegisterSpecSet getStarts(BasicBlock block) {
+        return getStarts(block.getLabel());
+    }
+
+    /**
+     * Gets a mutable copy of the register set associated with the
+     * start of the block with the given label. This returns a
+     * newly-allocated empty {@link RegisterSpecSet} of appropriate
+     * max size if there is not yet any set associated with the block.
+     *
+     * @param label {@code >= 0;} the block label
+     * @return {@code non-null;} the associated register set
+     */
+    public RegisterSpecSet mutableCopyOfStarts(int label) {
+        RegisterSpecSet result = getStarts0(label);
+
+        return (result != null) ?
+            result.mutableCopy() : new RegisterSpecSet(regCount);
+    }
+
+    /**
+     * Adds an assignment association for the given instruction and
+     * register spec. This throws an exception if the instruction
+     * doesn't actually perform a named variable assignment.
+     *
+     * <b>Note:</b> Although the instruction contains its own spec for
+     * the result, it still needs to be passed in explicitly to this
+     * method, since the spec that is stored here should always have a
+     * simple type and the one in the instruction can be an arbitrary
+     * {@link TypeBearer} (such as a constant value).
+     *
+     * @param insn {@code non-null;} the instruction in question
+     * @param spec {@code non-null;} the associated register spec
+     */
+    public void addAssignment(Insn insn, RegisterSpec spec) {
+        throwIfImmutable();
+
+        if (insn == null) {
+            throw new NullPointerException("insn == null");
+        }
+
+        if (spec == null) {
+            throw new NullPointerException("spec == null");
+        }
+
+        insnAssignments.put(insn, spec);
+    }
+
+    /**
+     * Gets the named register being assigned by the given instruction, if
+     * previously stored in this instance.
+     *
+     * @param insn {@code non-null;} instruction in question
+     * @return {@code null-ok;} the named register being assigned, if any
+     */
+    public RegisterSpec getAssignment(Insn insn) {
+        return insnAssignments.get(insn);
+    }
+
+    /**
+     * Gets the number of assignments recorded by this instance.
+     *
+     * @return {@code >= 0;} the number of assignments
+     */
+    public int getAssignmentCount() {
+        return insnAssignments.size();
+    }
+
+    public void debugDump() {
+        for (int label = 0 ; label < blockStarts.length; label++) {
+            if (blockStarts[label] == null) {
+                continue;
+            }
+
+            if (blockStarts[label] == emptySet) {
+                System.out.printf("%04x: empty set\n", label);
+            } else {
+                System.out.printf("%04x: %s\n", label, blockStarts[label]);
+            }
+        }
+    }
+
+    /**
+     * Helper method, to get the starts for a label, throwing the
+     * right exception for range problems.
+     *
+     * @param label {@code >= 0;} the block label
+     * @return {@code null-ok;} associated register set or {@code null} if there
+     * is none
+     */
+    private RegisterSpecSet getStarts0(int label) {
+        try {
+            return blockStarts[label];
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            // Translate the exception.
+            throw new IllegalArgumentException("bogus label");
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/PlainCstInsn.java b/dexgen/src/com/android/dexgen/rop/code/PlainCstInsn.java
new file mode 100644
index 0000000..5f8f753
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/PlainCstInsn.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeList;
+
+/**
+ * Instruction which contains an explicit reference to a constant
+ * but which cannot throw an exception.
+ */
+public final class PlainCstInsn
+        extends CstInsn {
+    /**
+     * Constructs an instance.
+     *
+     * @param opcode {@code non-null;} the opcode
+     * @param position {@code non-null;} source position
+     * @param result {@code null-ok;} spec for the result, if any
+     * @param sources {@code non-null;} specs for all the sources
+     * @param cst {@code non-null;} the constant
+     */
+    public PlainCstInsn(Rop opcode, SourcePosition position,
+                        RegisterSpec result, RegisterSpecList sources,
+                        Constant cst) {
+        super(opcode, position, result, sources, cst);
+
+        if (opcode.getBranchingness() != Rop.BRANCH_NONE) {
+            throw new IllegalArgumentException("bogus branchingness");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public TypeList getCatches() {
+        return StdTypeList.EMPTY;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void accept(Visitor visitor) {
+        visitor.visitPlainCstInsn(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withAddedCatch(Type type) {
+        throw new UnsupportedOperationException("unsupported");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withRegisterOffset(int delta) {
+        return new PlainCstInsn(getOpcode(), getPosition(),
+                                getResult().withOffset(delta),
+                                getSources().withOffset(delta),
+                                getConstant());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withNewRegisters(RegisterSpec result,
+            RegisterSpecList sources) {
+
+        return new PlainCstInsn(getOpcode(), getPosition(),
+                                result,
+                                sources,
+                                getConstant());
+
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/PlainInsn.java b/dexgen/src/com/android/dexgen/rop/code/PlainInsn.java
new file mode 100644
index 0000000..c79e7c1
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/PlainInsn.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeBearer;
+import com.android.dexgen.rop.type.TypeList;
+
+/**
+ * Plain instruction, which has no embedded data and which cannot possibly
+ * throw an exception.
+ */
+public final class PlainInsn
+        extends Insn {
+    /**
+     * Constructs an instance.
+     *
+     * @param opcode {@code non-null;} the opcode
+     * @param position {@code non-null;} source position
+     * @param result {@code null-ok;} spec for the result, if any
+     * @param sources {@code non-null;} specs for all the sources
+     */
+    public PlainInsn(Rop opcode, SourcePosition position,
+                     RegisterSpec result, RegisterSpecList sources) {
+        super(opcode, position, result, sources);
+
+        switch (opcode.getBranchingness()) {
+            case Rop.BRANCH_SWITCH:
+            case Rop.BRANCH_THROW: {
+                throw new IllegalArgumentException("bogus branchingness");
+            }
+        }
+
+        if (result != null && opcode.getBranchingness() != Rop.BRANCH_NONE) {
+            // move-result-pseudo is required here
+            throw new IllegalArgumentException
+                    ("can't mix branchingness with result");
+        }
+    }
+
+    /**
+     * Constructs a single-source instance.
+     *
+     * @param opcode {@code non-null;} the opcode
+     * @param position {@code non-null;} source position
+     * @param result {@code null-ok;} spec for the result, if any
+     * @param source {@code non-null;} spec for the source
+     */
+    public PlainInsn(Rop opcode, SourcePosition position, RegisterSpec result,
+                     RegisterSpec source) {
+        this(opcode, position, result, RegisterSpecList.make(source));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public TypeList getCatches() {
+        return StdTypeList.EMPTY;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void accept(Visitor visitor) {
+        visitor.visitPlainInsn(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withAddedCatch(Type type) {
+        throw new UnsupportedOperationException("unsupported");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withRegisterOffset(int delta) {
+        return new PlainInsn(getOpcode(), getPosition(),
+                             getResult().withOffset(delta),
+                             getSources().withOffset(delta));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withLastSourceLiteral() {
+        RegisterSpecList sources = getSources();
+        int szSources = sources.size();
+
+        if (szSources == 0) {
+            return this;
+        }
+
+        TypeBearer lastType = sources.get(szSources - 1).getTypeBearer();
+
+        if (!lastType.isConstant()) {
+            return this;
+        }
+
+        Constant cst = (Constant) lastType;
+
+        RegisterSpecList newSources = sources.withoutLast();
+
+        Rop newRop;
+        try {
+            newRop = Rops.ropFor(getOpcode().getOpcode(),
+                    getResult(), newSources, (Constant)lastType);
+        } catch (IllegalArgumentException ex) {
+            // There's no rop for this case
+            return this;
+        }
+
+        return new PlainCstInsn(newRop, getPosition(),
+                getResult(), newSources, cst);
+    }
+
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withNewRegisters(RegisterSpec result,
+            RegisterSpecList sources) {
+
+        return new PlainInsn(getOpcode(), getPosition(),
+                             result,
+                             sources);
+
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/RegOps.java b/dexgen/src/com/android/dexgen/rop/code/RegOps.java
new file mode 100644
index 0000000..3af8b7d
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/RegOps.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.util.Hex;
+
+/**
+ * All the register-based opcodes, and related utilities.
+ *
+ * <p><b>Note:</b> Opcode descriptions use a rough pseudocode. {@code r}
+ * is the result register, {@code x} is the first argument,
+ * {@code y} is the second argument, and {@code z} is the
+ * third argument. The expression which describes
+ * the operation uses Java-ish syntax but is preceded by type indicators for
+ * each of the values.
+ */
+public final class RegOps {
+    /** {@code nop()} */
+    public static final int NOP = 1;
+
+    /** {@code T: any type; r,x: T :: r = x;} */
+    public static final int MOVE = 2;
+
+    /** {@code T: any type; r,param(x): T :: r = param(x)} */
+    public static final int MOVE_PARAM = 3;
+
+    /**
+     * {@code T: Throwable; r: T :: r = caught_exception}.
+     * <b>Note:</b> This opcode should only ever be used in the
+     * first instruction of a block, and such blocks must be
+     * the start of an exception handler.
+     */
+    public static final int MOVE_EXCEPTION = 4;
+
+    /** {@code T: any type; r, literal: T :: r = literal;} */
+    public static final int CONST = 5;
+
+    /** {@code goto label} */
+    public static final int GOTO = 6;
+
+    /**
+     * {@code T: int or Object; x,y: T :: if (x == y) goto
+     * label}
+     */
+    public static final int IF_EQ = 7;
+
+    /**
+     * {@code T: int or Object; x,y: T :: if (x != y) goto
+     * label}
+     */
+    public static final int IF_NE = 8;
+
+    /** {@code x,y: int :: if (x < y) goto label} */
+    public static final int IF_LT = 9;
+
+    /** {@code x,y: int :: if (x >= y) goto label} */
+    public static final int IF_GE = 10;
+
+    /** {@code x,y: int :: if (x <= y) goto label} */
+    public static final int IF_LE = 11;
+
+    /** {@code x,y: int :: if (x > y) goto label} */
+    public static final int IF_GT = 12;
+
+    /** {@code x: int :: goto table[x]} */
+    public static final int SWITCH = 13;
+
+    /** {@code T: any numeric type; r,x,y: T :: r = x + y} */
+    public static final int ADD = 14;
+
+    /** {@code T: any numeric type; r,x,y: T :: r = x - y} */
+    public static final int SUB = 15;
+
+    /** {@code T: any numeric type; r,x,y: T :: r = x * y} */
+    public static final int MUL = 16;
+
+    /** {@code T: any numeric type; r,x,y: T :: r = x / y} */
+    public static final int DIV = 17;
+
+    /**
+     * {@code T: any numeric type; r,x,y: T :: r = x % y}
+     * (Java-style remainder)
+     */
+    public static final int REM = 18;
+
+    /** {@code T: any numeric type; r,x: T :: r = -x} */
+    public static final int NEG = 19;
+
+    /** {@code T: any integral type; r,x,y: T :: r = x & y} */
+    public static final int AND = 20;
+
+    /** {@code T: any integral type; r,x,y: T :: r = x | y} */
+    public static final int OR = 21;
+
+    /** {@code T: any integral type; r,x,y: T :: r = x ^ y} */
+    public static final int XOR = 22;
+
+    /**
+     * {@code T: any integral type; r,x: T; y: int :: r = x << y}
+     */
+    public static final int SHL = 23;
+
+    /**
+     * {@code T: any integral type; r,x: T; y: int :: r = x >> y}
+     * (signed right-shift)
+     */
+    public static final int SHR = 24;
+
+    /**
+     * {@code T: any integral type; r,x: T; y: int :: r = x >>> y}
+     * (unsigned right-shift)
+     */
+    public static final int USHR = 25;
+
+    /** {@code T: any integral type; r,x: T :: r = ~x} */
+    public static final int NOT = 26;
+
+    /**
+     * {@code T: any numeric type; r: int; x,y: T :: r = (x == y) ? 0
+     * : (x > y) ? 1 : -1} (Java-style "cmpl" where a NaN is
+     * considered "less than" all other values; also used for integral
+     * comparisons)
+     */
+    public static final int CMPL = 27;
+
+    /**
+     * {@code T: any floating point type; r: int; x,y: T :: r = (x == y) ? 0
+     * : (x < y) ? -1 : 1} (Java-style "cmpg" where a NaN is
+     * considered "greater than" all other values)
+     */
+    public static final int CMPG = 28;
+
+    /**
+     * {@code T: any numeric type; U: any numeric type; r: T; x: U ::
+     * r = (T) x} (numeric type conversion between the four
+     * "real" numeric types)
+     */
+    public static final int CONV = 29;
+
+    /**
+     * {@code r,x: int :: r = (x << 24) >> 24} (Java-style
+     * convert int to byte)
+     */
+    public static final int TO_BYTE = 30;
+
+    /**
+     * {@code r,x: int :: r = x & 0xffff} (Java-style convert int to char)
+     */
+    public static final int TO_CHAR = 31;
+
+    /**
+     * {@code r,x: int :: r = (x << 16) >> 16} (Java-style
+     * convert int to short)
+     */
+    public static final int TO_SHORT = 32;
+
+    /** {@code T: return type for the method; x: T; return x} */
+    public static final int RETURN = 33;
+
+    /** {@code T: any type; r: int; x: T[]; :: r = x.length} */
+    public static final int ARRAY_LENGTH = 34;
+
+    /** {@code x: Throwable :: throw(x)} */
+    public static final int THROW = 35;
+
+    /** {@code x: Object :: monitorenter(x)} */
+    public static final int MONITOR_ENTER = 36;
+
+    /** {@code x: Object :: monitorexit(x)} */
+    public static final int MONITOR_EXIT = 37;
+
+    /** {@code T: any type; r: T; x: T[]; y: int :: r = x[y]} */
+    public static final int AGET = 38;
+
+    /** {@code T: any type; x: T; y: T[]; z: int :: x[y] = z} */
+    public static final int APUT = 39;
+
+    /**
+     * {@code T: any non-array object type :: r =
+     * alloc(T)} (allocate heap space for an object)
+     */
+    public static final int NEW_INSTANCE = 40;
+
+    /** {@code T: any array type; r: T; x: int :: r = new T[x]} */
+    public static final int NEW_ARRAY = 41;
+
+    /**
+     * {@code T: any array type; r: T; x: int; v0..vx: T :: r = new T[x]
+     * {v0, ..., vx}}
+     */
+    public static final int FILLED_NEW_ARRAY = 42;
+
+    /**
+     * {@code T: any object type; x: Object :: (T) x} (can
+     * throw {@code ClassCastException})
+     */
+    public static final int CHECK_CAST = 43;
+
+    /**
+     * {@code T: any object type; x: Object :: x instanceof T}
+     */
+    public static final int INSTANCE_OF = 44;
+
+    /**
+     * {@code T: any type; r: T; x: Object; f: instance field spec of
+     * type T :: r = x.f}
+     */
+    public static final int GET_FIELD = 45;
+
+    /**
+     * {@code T: any type; r: T; f: static field spec of type T :: r =
+     * f}
+     */
+    public static final int GET_STATIC = 46;
+
+    /**
+     * {@code T: any type; x: T; y: Object; f: instance field spec of type
+     * T :: y.f = x}
+     */
+    public static final int PUT_FIELD = 47;
+
+    /**
+     * {@code T: any type; f: static field spec of type T; x: T :: f = x}
+     */
+    public static final int PUT_STATIC = 48;
+
+    /**
+     * {@code Tr, T0, T1...: any types; r: Tr; m: static method spec;
+     * y0: T0; y1: T1 ... :: r = m(y0, y1, ...)} (call static
+     * method)
+     */
+    public static final int INVOKE_STATIC = 49;
+
+    /**
+     * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: instance method
+     * spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, ...)} (call normal
+     * virtual method)
+     */
+    public static final int INVOKE_VIRTUAL = 50;
+
+    /**
+     * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: instance method
+     * spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, ...)} (call
+     * superclass virtual method)
+     */
+    public static final int INVOKE_SUPER = 51;
+
+    /**
+     * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: instance method
+     * spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, ...)} (call
+     * direct/special method)
+     */
+    public static final int INVOKE_DIRECT = 52;
+
+    /**
+     * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: interface
+     * (instance) method spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1,
+     * ...)} (call interface method)
+     */
+    public static final int INVOKE_INTERFACE = 53;
+
+    /**
+     * {@code T0: any type; name: local variable name  :: mark(name,T0)}
+     * (mark beginning or end of local variable name)
+     */
+    public static final int MARK_LOCAL = 54;
+
+    /**
+     * {@code T: Any type; r: T :: r = return_type}.
+     * <b>Note:</b> This opcode should only ever be used in the
+     * first instruction of a block following an invoke-*.
+     */
+    public static final int MOVE_RESULT = 55;
+
+    /**
+     * {@code T: Any type; r: T :: r = return_type}.
+     * <b>Note:</b> This opcode should only ever be used in the
+     * first instruction of a block following a non-invoke throwing insn
+     */
+    public static final int MOVE_RESULT_PSEUDO = 56;
+
+    /** {@code T: Any primitive type; v0..vx: T :: {v0, ..., vx}} */
+    public static final int FILL_ARRAY_DATA = 57;
+
+    /**
+     * This class is uninstantiable.
+     */
+    private RegOps() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Gets the name of the given opcode.
+     *
+     * @param opcode {@code >= 0, <= 255;} the opcode
+     * @return {@code non-null;} its name
+     */
+    public static String opName(int opcode) {
+        switch (opcode) {
+            case NOP: return "nop";
+            case MOVE: return "move";
+            case MOVE_PARAM: return "move-param";
+            case MOVE_EXCEPTION: return "move-exception";
+            case CONST: return "const";
+            case GOTO: return "goto";
+            case IF_EQ: return "if-eq";
+            case IF_NE: return "if-ne";
+            case IF_LT: return "if-lt";
+            case IF_GE: return "if-ge";
+            case IF_LE: return "if-le";
+            case IF_GT: return "if-gt";
+            case SWITCH: return "switch";
+            case ADD: return "add";
+            case SUB: return "sub";
+            case MUL: return "mul";
+            case DIV: return "div";
+            case REM: return "rem";
+            case NEG: return "neg";
+            case AND: return "and";
+            case OR: return "or";
+            case XOR: return "xor";
+            case SHL: return "shl";
+            case SHR: return "shr";
+            case USHR: return "ushr";
+            case NOT: return "not";
+            case CMPL: return "cmpl";
+            case CMPG: return "cmpg";
+            case CONV: return "conv";
+            case TO_BYTE: return "to-byte";
+            case TO_CHAR: return "to-char";
+            case TO_SHORT: return "to-short";
+            case RETURN: return "return";
+            case ARRAY_LENGTH: return "array-length";
+            case THROW: return "throw";
+            case MONITOR_ENTER: return "monitor-enter";
+            case MONITOR_EXIT: return "monitor-exit";
+            case AGET: return "aget";
+            case APUT: return "aput";
+            case NEW_INSTANCE: return "new-instance";
+            case NEW_ARRAY: return "new-array";
+            case FILLED_NEW_ARRAY: return "filled-new-array";
+            case CHECK_CAST: return "check-cast";
+            case INSTANCE_OF: return "instance-of";
+            case GET_FIELD: return "get-field";
+            case GET_STATIC: return "get-static";
+            case PUT_FIELD: return "put-field";
+            case PUT_STATIC: return "put-static";
+            case INVOKE_STATIC: return "invoke-static";
+            case INVOKE_VIRTUAL: return "invoke-virtual";
+            case INVOKE_SUPER: return "invoke-super";
+            case INVOKE_DIRECT: return "invoke-direct";
+            case INVOKE_INTERFACE: return "invoke-interface";
+            case MOVE_RESULT: return "move-result";
+            case MOVE_RESULT_PSEUDO: return "move-result-pseudo";
+            case FILL_ARRAY_DATA: return "fill-array-data";
+        }
+
+        return "unknown-" + Hex.u1(opcode);
+    }
+
+    /**
+     * Given an IF_* RegOp, returns the right-to-left flipped version. For
+     * example, IF_GT becomes IF_LT.
+     *
+     * @param opcode An IF_* RegOp
+     * @return flipped IF Regop
+     */
+    public static int flippedIfOpcode(final int opcode) {
+        switch (opcode) {
+            case RegOps.IF_EQ:
+            case RegOps.IF_NE:
+                return opcode;
+            case RegOps.IF_LT:
+                return RegOps.IF_GT;
+            case RegOps.IF_GE:
+                return RegOps.IF_LE;
+            case RegOps.IF_LE:
+                return RegOps.IF_GE;
+            case RegOps.IF_GT:
+                return RegOps.IF_LT;
+            default:
+                throw new RuntimeException("Unrecognized IF regop: " + opcode);
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/RegisterSpec.java b/dexgen/src/com/android/dexgen/rop/code/RegisterSpec.java
new file mode 100644
index 0000000..30deeca
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/RegisterSpec.java
@@ -0,0 +1,650 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeBearer;
+import com.android.dexgen.util.ToHuman;
+
+import java.util.HashMap;
+
+/**
+ * Combination of a register number and a type, used as the sources and
+ * destinations of register-based operations.
+ */
+public final class RegisterSpec
+        implements TypeBearer, ToHuman, Comparable<RegisterSpec> {
+    /** {@code non-null;} string to prefix register numbers with */
+    public static final String PREFIX = "v";
+
+    /** {@code non-null;} intern table for instances */
+    private static final HashMap<Object, RegisterSpec> theInterns =
+        new HashMap<Object, RegisterSpec>(1000);
+
+    /** {@code non-null;} common comparison instance used while interning */
+    private static final ForComparison theInterningItem = new ForComparison();
+
+    /** {@code >= 0;} register number */
+    private final int reg;
+
+    /** {@code non-null;} type loaded or stored */
+    private final TypeBearer type;
+
+    /** {@code null-ok;} local variable info associated with this register, if any */
+    private final LocalItem local;
+
+    /**
+     * Intern the given triple as an instance of this class.
+     *
+     * @param reg {@code >= 0;} the register number
+     * @param type {@code non-null;} the type (or possibly actual value) which
+     * is loaded from or stored to the indicated register
+     * @param local {@code null-ok;} the associated local variable, if any
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    private static RegisterSpec intern(int reg, TypeBearer type,
+            LocalItem local) {
+        theInterningItem.set(reg, type, local);
+        RegisterSpec found = theInterns.get(theInterningItem);
+
+        if (found != null) {
+            return found;
+        }
+
+        found = theInterningItem.toRegisterSpec();
+        theInterns.put(found, found);
+        return found;
+    }
+
+    /**
+     * Returns an instance for the given register number and type, with
+     * no variable info. This method is allowed to return shared
+     * instances (but doesn't necessarily do so).
+     *
+     * @param reg {@code >= 0;} the register number
+     * @param type {@code non-null;} the type (or possibly actual value) which
+     * is loaded from or stored to the indicated register
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public static RegisterSpec make(int reg, TypeBearer type) {
+        return intern(reg, type, null);
+    }
+
+    /**
+     * Returns an instance for the given register number, type, and
+     * variable info. This method is allowed to return shared
+     * instances (but doesn't necessarily do so).
+     *
+     * @param reg {@code >= 0;} the register number
+     * @param type {@code non-null;} the type (or possibly actual value) which
+     * is loaded from or stored to the indicated register
+     * @param local {@code non-null;} the associated local variable
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public static RegisterSpec make(int reg, TypeBearer type,
+            LocalItem local) {
+        if (local == null) {
+            throw new NullPointerException("local  == null");
+        }
+
+        return intern(reg, type, local);
+    }
+
+    /**
+     * Returns an instance for the given register number, type, and
+     * variable info. This method is allowed to return shared
+     * instances (but doesn't necessarily do so).
+     *
+     * @param reg {@code >= 0;} the register number
+     * @param type {@code non-null;} the type (or possibly actual value) which
+     * is loaded from or stored to the indicated register
+     * @param local {@code null-ok;} the associated variable info or null for
+     * none
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public static RegisterSpec makeLocalOptional(
+            int reg, TypeBearer type, LocalItem local) {
+
+        return intern(reg, type, local);
+    }
+
+    /**
+     * Gets the string form for the given register number.
+     *
+     * @param reg {@code >= 0;} the register number
+     * @return {@code non-null;} the string form
+     */
+    public static String regString(int reg) {
+        return PREFIX + reg;
+    }
+
+    /**
+     * Constructs an instance. This constructor is private. Use
+     * {@link #make}.
+     *
+     * @param reg {@code >= 0;} the register number
+     * @param type {@code non-null;} the type (or possibly actual value) which
+     * is loaded from or stored to the indicated register
+     * @param local {@code null-ok;} the associated local variable, if any
+     */
+    private RegisterSpec(int reg, TypeBearer type, LocalItem local) {
+        if (reg < 0) {
+            throw new IllegalArgumentException("reg < 0");
+        }
+
+        if (type == null) {
+            throw new NullPointerException("type == null");
+        }
+
+        this.reg = reg;
+        this.type = type;
+        this.local = local;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof RegisterSpec)) {
+            if (other instanceof ForComparison) {
+                ForComparison fc = (ForComparison) other;
+                return equals(fc.reg, fc.type, fc.local);
+            }
+            return false;
+        }
+
+        RegisterSpec spec = (RegisterSpec) other;
+        return equals(spec.reg, spec.type, spec.local);
+    }
+
+    /**
+     * Like {@code equals}, but only consider the simple types of the
+     * registers. That is, this compares {@code getType()} on the types
+     * to ignore whatever arbitrary extra stuff might be carried around
+     * by an outer {@link TypeBearer}.
+     *
+     * @param other {@code null-ok;} spec to compare to
+     * @return {@code true} iff {@code this} and {@code other} are equal
+     * in the stated way
+     */
+    public boolean equalsUsingSimpleType(RegisterSpec other) {
+        if (!matchesVariable(other)) {
+            return false;
+        }
+
+        return (reg == other.reg);
+    }
+
+    /**
+     * Like {@link #equalsUsingSimpleType} but ignoring the register number.
+     * This is useful to determine if two instances refer to the "same"
+     * local variable.
+     *
+     * @param other {@code null-ok;} spec to compare to
+     * @return {@code true} iff {@code this} and {@code other} are equal
+     * in the stated way
+     */
+    public boolean matchesVariable(RegisterSpec other) {
+        if (other == null) {
+            return false;
+        }
+
+        return type.getType().equals(other.type.getType())
+            && ((local == other.local)
+                    || ((local != null) && local.equals(other.local)));
+    }
+
+    /**
+     * Helper for {@link #equals} and {@link #ForComparison.equals},
+     * which actually does the test.
+     *
+     * @param reg value of the instance variable, for another instance
+     * @param type value of the instance variable, for another instance
+     * @param local value of the instance variable, for another instance
+     * @return whether this instance is equal to one with the given
+     * values
+     */
+    private boolean equals(int reg, TypeBearer type, LocalItem local) {
+        return (this.reg == reg)
+            && this.type.equals(type)
+            && ((this.local == local)
+                    || ((this.local != null) && this.local.equals(local)));
+    }
+
+    /**
+     * Compares by (in priority order) register number, unwrapped type
+     * (that is types not {@link TypeBearer}s, and local info.
+     *
+     * @param other {@code non-null;} spec to compare to
+     * @return {@code -1..1;} standard result of comparison
+     */
+    public int compareTo(RegisterSpec other) {
+        if (this.reg < other.reg) {
+            return -1;
+        } else if (this.reg > other.reg) {
+            return 1;
+        }
+
+        int compare = type.getType().compareTo(other.type.getType());
+
+        if (compare != 0) {
+            return compare;
+        }
+
+        if (this.local == null) {
+            return (other.local == null) ? 0 : -1;
+        } else if (other.local == null) {
+            return 1;
+        }
+
+        return this.local.compareTo(other.local);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return hashCodeOf(reg, type, local);
+    }
+
+    /**
+     * Helper for {@link #hashCode} and {@link #ForComparison.hashCode},
+     * which actually does the calculation.
+     *
+     * @param reg value of the instance variable
+     * @param type value of the instance variable
+     * @param local value of the instance variable
+     * @return the hash code
+     */
+    private static int hashCodeOf(int reg, TypeBearer type, LocalItem local) {
+        int hash = (local != null) ? local.hashCode() : 0;
+
+        hash = (hash * 31 + type.hashCode()) * 31 + reg;
+        return hash;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return toString0(false);
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return toString0(true);
+    }
+
+    /** {@inheritDoc} */
+    public Type getType() {
+        return type.getType();
+    }
+
+    /** {@inheritDoc} */
+    public TypeBearer getFrameType() {
+        return type.getFrameType();
+    }
+
+    /** {@inheritDoc} */
+    public final int getBasicType() {
+        return type.getBasicType();
+    }
+
+    /** {@inheritDoc} */
+    public final int getBasicFrameType() {
+        return type.getBasicFrameType();
+    }
+
+    /** {@inheritDoc} */
+    public final boolean isConstant() {
+        return false;
+    }
+
+    /**
+     * Gets the register number.
+     *
+     * @return {@code >= 0;} the register number
+     */
+    public int getReg() {
+        return reg;
+    }
+
+    /**
+     * Gets the type (or actual value) which is loaded from or stored
+     * to the register associated with this instance.
+     *
+     * @return {@code non-null;} the type
+     */
+    public TypeBearer getTypeBearer() {
+        return type;
+    }
+
+    /**
+     * Gets the variable info associated with this instance, if any.
+     *
+     * @return {@code null-ok;} the variable info, or {@code null} if this
+     * instance has none
+     */
+    public LocalItem getLocalItem() {
+        return local;
+    }
+
+    /**
+     * Gets the next available register number after the one in this
+     * instance. This is equal to the register number plus the width
+     * (category) of the type used. Among other things, this may also
+     * be used to determine the minimum required register count
+     * implied by this instance.
+     *
+     * @return {@code >= 0;} the required registers size
+     */
+    public int getNextReg() {
+        return reg + getCategory();
+    }
+
+    /**
+     * Gets the category of this instance's type. This is just a convenient
+     * shorthand for {@code getType().getCategory()}.
+     *
+     * @see #isCategory1
+     * @see #isCategory2
+     * @return {@code 1..2;} the category of this instance's type
+     */
+    public int getCategory() {
+        return type.getType().getCategory();
+    }
+
+    /**
+     * Gets whether this instance's type is category 1. This is just a
+     * convenient shorthand for {@code getType().isCategory1()}.
+     *
+     * @see #getCategory
+     * @see #isCategory2
+     * @return whether or not this instance's type is of category 1
+     */
+    public boolean isCategory1() {
+        return type.getType().isCategory1();
+    }
+
+    /**
+     * Gets whether this instance's type is category 2. This is just a
+     * convenient shorthand for {@code getType().isCategory2()}.
+     *
+     * @see #getCategory
+     * @see #isCategory1
+     * @return whether or not this instance's type is of category 2
+     */
+    public boolean isCategory2() {
+        return type.getType().isCategory2();
+    }
+
+    /**
+     * Gets the string form for just the register number of this instance.
+     *
+     * @return {@code non-null;} the register string form
+     */
+    public String regString() {
+        return regString(reg);
+    }
+
+    /**
+     * Returns an instance that is the intersection between this instance
+     * and the given one, if any. The intersection is defined as follows:
+     *
+     * <ul>
+     *   <li>If {@code other} is {@code null}, then the result
+     *     is {@code null}.
+     *   <li>If the register numbers don't match, then the intersection
+     *     is {@code null}. Otherwise, the register number of the
+     *     intersection is the same as the one in the two instances.</li>
+     *   <li>If the types returned by {@code getType()} are not
+     *     {@code equals()}, then the intersection is null.</li>
+     *   <li>If the type bearers returned by {@code getTypeBearer()}
+     *     are {@code equals()}, then the intersection's type bearer
+     *     is the one from this instance. Otherwise, the intersection's
+     *     type bearer is the {@code getType()} of this instance.</li>
+     *   <li>If the locals are {@code equals()}, then the local info
+     *     of the intersection is the local info of this instance. Otherwise,
+     *     the local info of the intersection is {@code null}.</li>
+     * </ul>
+     *
+     * @param other {@code null-ok;} instance to intersect with (or {@code null})
+     * @param localPrimary whether local variables are primary to the
+     * intersection; if {@code true}, then the only non-null
+     * results occur when registers being intersected have equal local
+     * infos (or both have {@code null} local infos)
+     * @return {@code null-ok;} the intersection
+     */
+    public RegisterSpec intersect(RegisterSpec other, boolean localPrimary) {
+        if (this == other) {
+            // Easy out.
+            return this;
+        }
+
+        if ((other == null) || (reg != other.getReg())) {
+            return null;
+        }
+
+        LocalItem resultLocal =
+            ((local == null) || !local.equals(other.getLocalItem()))
+            ? null : local;
+        boolean sameName = (resultLocal == local);
+
+        if (localPrimary && !sameName) {
+            return null;
+        }
+
+        Type thisType = getType();
+        Type otherType = other.getType();
+
+        // Note: Types are always interned.
+        if (thisType != otherType) {
+            return null;
+        }
+
+        TypeBearer resultTypeBearer =
+            type.equals(other.getTypeBearer()) ? type : thisType;
+
+        if ((resultTypeBearer == type) && sameName) {
+            // It turns out that the intersection is "this" after all.
+            return this;
+        }
+
+        return (resultLocal == null) ? make(reg, resultTypeBearer) :
+            make(reg, resultTypeBearer, resultLocal);
+    }
+
+    /**
+     * Returns an instance that is identical to this one, except that the
+     * register number is replaced by the given one.
+     *
+     * @param newReg {@code >= 0;} the new register number
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public RegisterSpec withReg(int newReg) {
+        if (reg == newReg) {
+            return this;
+        }
+
+        return makeLocalOptional(newReg, type, local);
+    }
+
+    /**
+     * Returns an instance that is identical to this one, except that
+     * the type is replaced by the given one.
+     *
+     * @param newType {@code non-null;} the new type
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public RegisterSpec withType(TypeBearer newType) {
+        return makeLocalOptional(reg, newType, local);
+    }
+
+    /**
+     * Returns an instance that is identical to this one, except that the
+     * register number is offset by the given amount.
+     *
+     * @param delta the amount to offset the register number by
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public RegisterSpec withOffset(int delta) {
+        if (delta == 0) {
+            return this;
+        }
+
+        return withReg(reg + delta);
+    }
+
+    /**
+     * Returns an instance that is identical to this one, except that
+     * the type bearer is replaced by the actual underlying type
+     * (thereby stripping off non-type information) with any
+     * initialization information stripped away as well.
+     *
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public RegisterSpec withSimpleType() {
+        TypeBearer orig = type;
+        Type newType;
+
+        if (orig instanceof Type) {
+            newType = (Type) orig;
+        } else {
+            newType = orig.getType();
+        }
+
+        if (newType.isUninitialized()) {
+            newType = newType.getInitializedType();
+        }
+
+        if (newType == orig) {
+            return this;
+        }
+
+        return makeLocalOptional(reg, newType, local);
+    }
+
+    /**
+     * Returns an instance that is identical to this one except that the
+     * local variable is as specified in the parameter.
+     *
+     * @param local {@code null-ok;} the local item or null for none
+     * @return an appropriate instance
+     */
+    public RegisterSpec withLocalItem(LocalItem local) {
+        if ((this.local== local)
+                    || ((this.local != null) && this.local.equals(local))) {
+
+            return this;
+        }
+
+        return makeLocalOptional(reg, type, local);
+    }
+
+
+    /**
+     * Helper for {@link #toString} and {@link #toHuman}.
+     *
+     * @param human whether to be human-oriented
+     * @return {@code non-null;} the string form
+     */
+    private String toString0(boolean human) {
+        StringBuffer sb = new StringBuffer(40);
+
+        sb.append(regString());
+        sb.append(":");
+
+        if (local != null) {
+            sb.append(local.toString());
+        }
+
+        Type justType = type.getType();
+        sb.append(justType);
+
+        if (justType != type) {
+            sb.append("=");
+            if (human && (type instanceof Constant)) {
+                sb.append(((Constant) type).toHuman());
+            } else {
+                sb.append(type);
+            }
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Holder of register spec data for the purposes of comparison (so that
+     * {@code RegisterSpec} itself can still keep {@code final}
+     * instance variables.
+     */
+    private static class ForComparison {
+        /** {@code >= 0;} register number */
+        private int reg;
+
+        /** {@code non-null;} type loaded or stored */
+        private TypeBearer type;
+
+        /**
+         * {@code null-ok;} local variable associated with this
+         * register, if any
+         */
+        private LocalItem local;
+
+        /**
+         * Set all the instance variables.
+         *
+         * @param reg {@code >= 0;} the register number
+         * @param type {@code non-null;} the type (or possibly actual
+         * value) which is loaded from or stored to the indicated
+         * register
+         * @param local {@code null-ok;} the associated local variable, if any
+         * @return {@code non-null;} an appropriately-constructed instance
+         */
+        public void set(int reg, TypeBearer type, LocalItem local) {
+            this.reg = reg;
+            this.type = type;
+            this.local = local;
+        }
+
+        /**
+         * Construct a {@code RegisterSpec} of this instance's
+         * contents.
+         *
+         * @return {@code non-null;} an appropriately-constructed instance
+         */
+        public RegisterSpec toRegisterSpec() {
+            return new RegisterSpec(reg, type, local);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof RegisterSpec)) {
+                return false;
+            }
+
+            RegisterSpec spec = (RegisterSpec) other;
+            return spec.equals(reg, type, local);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int hashCode() {
+            return hashCodeOf(reg, type, local);
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/RegisterSpecList.java b/dexgen/src/com/android/dexgen/rop/code/RegisterSpecList.java
new file mode 100644
index 0000000..a0f7a24
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/RegisterSpecList.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeList;
+import com.android.dexgen.util.FixedSizeList;
+
+/**
+ * List of {@link RegisterSpec} instances.
+ */
+public final class RegisterSpecList
+        extends FixedSizeList implements TypeList {
+    /** {@code non-null;} no-element instance */
+    public static final RegisterSpecList EMPTY = new RegisterSpecList(0);
+
+    /**
+     * Makes a single-element instance.
+     *
+     * @param spec {@code non-null;} the element
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public static RegisterSpecList make(RegisterSpec spec) {
+        RegisterSpecList result = new RegisterSpecList(1);
+        result.set(0, spec);
+        return result;
+    }
+
+    /**
+     * Makes a two-element instance.
+     *
+     * @param spec0 {@code non-null;} the first element
+     * @param spec1 {@code non-null;} the second element
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public static RegisterSpecList make(RegisterSpec spec0,
+                                        RegisterSpec spec1) {
+        RegisterSpecList result = new RegisterSpecList(2);
+        result.set(0, spec0);
+        result.set(1, spec1);
+        return result;
+    }
+
+    /**
+     * Makes a three-element instance.
+     *
+     * @param spec0 {@code non-null;} the first element
+     * @param spec1 {@code non-null;} the second element
+     * @param spec2 {@code non-null;} the third element
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public static RegisterSpecList make(RegisterSpec spec0, RegisterSpec spec1,
+                                        RegisterSpec spec2) {
+        RegisterSpecList result = new RegisterSpecList(3);
+        result.set(0, spec0);
+        result.set(1, spec1);
+        result.set(2, spec2);
+        return result;
+    }
+
+    /**
+     * Makes a four-element instance.
+     *
+     * @param spec0 {@code non-null;} the first element
+     * @param spec1 {@code non-null;} the second element
+     * @param spec2 {@code non-null;} the third element
+     * @param spec3 {@code non-null;} the fourth element
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public static RegisterSpecList make(RegisterSpec spec0, RegisterSpec spec1,
+                                        RegisterSpec spec2,
+                                        RegisterSpec spec3) {
+        RegisterSpecList result = new RegisterSpecList(4);
+        result.set(0, spec0);
+        result.set(1, spec1);
+        result.set(2, spec2);
+        result.set(3, spec3);
+        return result;
+    }
+
+    /**
+     * Constructs an instance. All indices initially contain {@code null}.
+     *
+     * @param size the size of the list
+     */
+    public RegisterSpecList(int size) {
+        super(size);
+    }
+
+    /** {@inheritDoc} */
+    public Type getType(int n) {
+        return get(n).getType().getType();
+    }
+
+    /** {@inheritDoc} */
+    public int getWordCount() {
+        int sz = size();
+        int result = 0;
+
+        for (int i = 0; i < sz; i++) {
+            result += getType(i).getCategory();
+        }
+
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public TypeList withAddedType(Type type) {
+        throw new UnsupportedOperationException("unsupported");
+    }
+
+    /**
+     * Gets the indicated element. It is an error to call this with the
+     * index for an element which was never set; if you do that, this
+     * will throw {@code NullPointerException}.
+     *
+     * @param n {@code >= 0, < size();} which element
+     * @return {@code non-null;} the indicated element
+     */
+    public RegisterSpec get(int n) {
+        return (RegisterSpec) get0(n);
+    }
+
+    /**
+     * Returns a RegisterSpec in this list that uses the specified register,
+     * or null if there is none in this list.
+     * @param reg Register to find
+     * @return RegisterSpec that uses argument or null.
+     */
+    public RegisterSpec specForRegister(int reg) {
+        int sz = size();
+        for (int i = 0; i < sz; i++) {
+            RegisterSpec rs;
+
+            rs = get(i);
+
+            if (rs.getReg() == reg) {
+                return rs;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the index of a RegisterSpec in this list that uses the specified
+     * register, or -1 if none in this list uses the register.
+     * @param reg Register to find
+     * @return index of RegisterSpec or -1
+     */
+    public int indexOfRegister(int reg) {
+        int sz = size();
+        for (int i = 0; i < sz; i++) {
+            RegisterSpec rs;
+
+            rs = get(i);
+
+            if (rs.getReg() == reg) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Sets the element at the given index.
+     *
+     * @param n {@code >= 0, < size();} which element
+     * @param spec {@code non-null;} the value to store
+     */
+    public void set(int n, RegisterSpec spec) {
+        set0(n, spec);
+    }
+
+    /**
+     * Gets the minimum required register count implied by this
+     * instance. This is equal to the highest register number referred
+     * to plus the widest width (largest category) of the type used in
+     * that register.
+     *
+     * @return {@code >= 0;} the required registers size
+     */
+    public int getRegistersSize() {
+        int sz = size();
+        int result = 0;
+
+        for (int i = 0; i < sz; i++) {
+            RegisterSpec spec = (RegisterSpec) get0(i);
+            if (spec != null) {
+                int min = spec.getNextReg();
+                if (min > result) {
+                    result = min;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns a new instance, which is the same as this instance,
+     * except that it has an additional element prepended to the original.
+     * Mutability of the result is inherited from the original.
+     *
+     * @param spec {@code non-null;} the new first spec (to prepend)
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public RegisterSpecList withFirst(RegisterSpec spec) {
+        int sz = size();
+        RegisterSpecList result = new RegisterSpecList(sz + 1);
+
+        for (int i = 0; i < sz; i++) {
+            result.set0(i + 1, get0(i));
+        }
+
+        result.set0(0, spec);
+        if (isImmutable()) {
+            result.setImmutable();
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns a new instance, which is the same as this instance,
+     * except that its first element is removed. Mutability of the
+     * result is inherited from the original.
+     *
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public RegisterSpecList withoutFirst() {
+        int newSize = size() - 1;
+
+        if (newSize == 0) {
+            return EMPTY;
+        }
+
+        RegisterSpecList result = new RegisterSpecList(newSize);
+
+        for (int i = 0; i < newSize; i++) {
+            result.set0(i, get0(i + 1));
+        }
+
+        if (isImmutable()) {
+            result.setImmutable();
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns a new instance, which is the same as this instance,
+     * except that its last element is removed. Mutability of the
+     * result is inherited from the original.
+     *
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public RegisterSpecList withoutLast() {
+        int newSize = size() - 1;
+
+        if (newSize == 0) {
+            return EMPTY;
+        }
+
+        RegisterSpecList result = new RegisterSpecList(newSize);
+
+        for (int i = 0; i < newSize; i++) {
+            result.set0(i, get0(i));
+        }
+
+        if (isImmutable()) {
+            result.setImmutable();
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns an instance that is identical to this one, except that
+     * all register numbers are offset by the given amount. Mutability
+     * of the result is inherited from the original.
+     *
+     * @param delta the amount to offset the register numbers by
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public RegisterSpecList withOffset(int delta) {
+        int sz = size();
+
+        if (sz == 0) {
+            // Don't bother making a new zero-element instance.
+            return this;
+        }
+
+        RegisterSpecList result = new RegisterSpecList(sz);
+
+        for (int i = 0; i < sz; i++) {
+            RegisterSpec one = (RegisterSpec) get0(i);
+            if (one != null) {
+                result.set0(i, one.withOffset(delta));
+            }
+        }
+
+        if (isImmutable()) {
+            result.setImmutable();
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns an instance that is identical to this one, except that
+     * all register numbers are renumbered sequentially from the given
+     * base, with the first number duplicated if indicated.
+     *
+     * @param base the base register number
+     * @param duplicateFirst whether to duplicate the first number
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public RegisterSpecList withSequentialRegisters(int base,
+                                                    boolean duplicateFirst) {
+        int sz = size();
+
+        if (sz == 0) {
+            // Don't bother making a new zero-element instance.
+            return this;
+        }
+
+        RegisterSpecList result = new RegisterSpecList(sz);
+
+        for (int i = 0; i < sz; i++) {
+            RegisterSpec one = (RegisterSpec) get0(i);
+            result.set0(i, one.withReg(base));
+            if (duplicateFirst) {
+                duplicateFirst = false;
+            } else {
+                base += one.getCategory();
+            }
+        }
+
+        if (isImmutable()) {
+            result.setImmutable();
+        }
+
+        return result;
+    }
+
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/RegisterSpecSet.java b/dexgen/src/com/android/dexgen/rop/code/RegisterSpecSet.java
new file mode 100644
index 0000000..69e67e9
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/RegisterSpecSet.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.util.MutabilityControl;
+
+/**
+ * Set of {@link RegisterSpec} instances, where a given register number
+ * may appear only once in the set.
+ */
+public final class RegisterSpecSet
+        extends MutabilityControl {
+    /** {@code non-null;} no-element instance */
+    public static final RegisterSpecSet EMPTY = new RegisterSpecSet(0);
+
+    /**
+     * {@code non-null;} array of register specs, where each element is
+     * {@code null} or is an instance whose {@code reg}
+     * matches the array index
+     */
+    private final RegisterSpec[] specs;
+
+    /** {@code >= -1;} size of the set or {@code -1} if not yet calculated */
+    private int size;
+
+    /**
+     * Constructs an instance. The instance is initially empty.
+     *
+     * @param maxSize {@code >= 0;} the maximum register number (exclusive) that
+     * may be represented in this instance
+     */
+    public RegisterSpecSet(int maxSize) {
+        super(maxSize != 0);
+
+        this.specs = new RegisterSpec[maxSize];
+        this.size = 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof RegisterSpecSet)) {
+            return false;
+        }
+
+        RegisterSpecSet otherSet = (RegisterSpecSet) other;
+        RegisterSpec[] otherSpecs = otherSet.specs;
+        int len = specs.length;
+
+        if ((len != otherSpecs.length) || (size() != otherSet.size())) {
+            return false;
+        }
+
+        for (int i = 0; i < len; i++) {
+            RegisterSpec s1 = specs[i];
+            RegisterSpec s2 = otherSpecs[i];
+
+            if (s1 == s2) {
+                continue;
+            }
+
+            if ((s1 == null) || !s1.equals(s2)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        int len = specs.length;
+        int hash = 0;
+
+        for (int i = 0; i < len; i++) {
+            RegisterSpec spec = specs[i];
+            int oneHash = (spec == null) ? 0 : spec.hashCode();
+            hash = (hash * 31) + oneHash;
+        }
+
+        return hash;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        int len = specs.length;
+        StringBuffer sb = new StringBuffer(len * 25);
+
+        sb.append('{');
+
+        boolean any = false;
+        for (int i = 0; i < len; i++) {
+            RegisterSpec spec = specs[i];
+            if (spec != null) {
+                if (any) {
+                    sb.append(", ");
+                } else {
+                    any = true;
+                }
+                sb.append(spec);
+            }
+        }
+
+        sb.append('}');
+        return sb.toString();
+    }
+
+    /**
+     * Gets the maximum number of registers that may be in this instance, which
+     * is also the maximum-plus-one of register numbers that may be
+     * represented.
+     *
+     * @return {@code >= 0;} the maximum size
+     */
+    public int getMaxSize() {
+        return specs.length;
+    }
+
+    /**
+     * Gets the current size of this instance.
+     *
+     * @return {@code >= 0;} the size
+     */
+    public int size() {
+        int result = size;
+
+        if (result < 0) {
+            int len = specs.length;
+
+            result = 0;
+            for (int i = 0; i < len; i++) {
+                if (specs[i] != null) {
+                    result++;
+                }
+            }
+
+            size = result;
+        }
+
+        return result;
+    }
+
+    /**
+     * Gets the element with the given register number, if any.
+     *
+     * @param reg {@code >= 0;} the desired register number
+     * @return {@code null-ok;} the element with the given register number or
+     * {@code null} if there is none
+     */
+    public RegisterSpec get(int reg) {
+        try {
+            return specs[reg];
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            // Translate the exception.
+            throw new IllegalArgumentException("bogus reg");
+        }
+    }
+
+    /**
+     * Gets the element with the same register number as the given
+     * spec, if any. This is just a convenient shorthand for
+     * {@code get(spec.getReg())}.
+     *
+     * @param spec {@code non-null;} spec with the desired register number
+     * @return {@code null-ok;} the element with the matching register number or
+     * {@code null} if there is none
+     */
+    public RegisterSpec get(RegisterSpec spec) {
+        return get(spec.getReg());
+    }
+
+    /**
+     * Returns the spec in this set that's currently associated with a
+     * given local (type, name, and signature), or {@code null} if there is
+     * none. This ignores the register number of the given spec but
+     * matches on everything else.
+     *
+     * @param spec {@code non-null;} local to look for
+     * @return {@code null-ok;} first register found that matches, if any
+     */
+    public RegisterSpec findMatchingLocal(RegisterSpec spec) {
+        int length = specs.length;
+
+        for (int reg = 0; reg < length; reg++) {
+            RegisterSpec s = specs[reg];
+
+            if (s == null) {
+                continue;
+            }
+
+            if (spec.matchesVariable(s)) {
+                return s;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the spec in this set that's currently associated with a given
+     * local (name and signature), or {@code null} if there is none.
+     *
+     * @param local {@code non-null;} local item to search for
+     * @return {@code null-ok;} first register found with matching name and signature
+     */
+    public RegisterSpec localItemToSpec(LocalItem local) {
+        int length = specs.length;
+
+        for (int reg = 0; reg < length; reg++) {
+            RegisterSpec spec = specs[reg];
+
+            if ((spec != null) && local.equals(spec.getLocalItem())) {
+                return spec;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Removes a spec from the set. Only the register number
+     * of the parameter is significant.
+     *
+     * @param toRemove {@code non-null;} register to remove.
+     */
+    public void remove(RegisterSpec toRemove) {
+        try {
+            specs[toRemove.getReg()] = null;
+            size = -1;
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            // Translate the exception.
+            throw new IllegalArgumentException("bogus reg");
+        }
+    }
+
+    /**
+     * Puts the given spec into the set. If there is already an element in
+     * the set with the same register number, it is replaced. Additionally,
+     * if the previous element is for a category-2 register, then that
+     * previous element is nullified. Finally, if the given spec is for
+     * a category-2 register, then the immediately subsequent element
+     * is nullified.
+     *
+     * @param spec {@code non-null;} the register spec to put in the instance
+     */
+    public void put(RegisterSpec spec) {
+        throwIfImmutable();
+
+        if (spec == null) {
+            throw new NullPointerException("spec == null");
+        }
+
+        size = -1;
+
+        try {
+            int reg = spec.getReg();
+            specs[reg] = spec;
+
+            if (reg > 0) {
+                int prevReg = reg - 1;
+                RegisterSpec prevSpec = specs[prevReg];
+                if ((prevSpec != null) && (prevSpec.getCategory() == 2)) {
+                    specs[prevReg] = null;
+                }
+            }
+
+            if (spec.getCategory() == 2) {
+                specs[reg + 1] = null;
+            }
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            // Translate the exception.
+            throw new IllegalArgumentException("spec.getReg() out of range");
+        }
+    }
+
+    /**
+     * Put the entire contents of the given set into this one.
+     *
+     * @param set {@code non-null;} the set to put into this instance
+     */
+    public void putAll(RegisterSpecSet set) {
+        int max = set.getMaxSize();
+
+        for (int i = 0; i < max; i++) {
+            RegisterSpec spec = set.get(i);
+            if (spec != null) {
+                put(spec);
+            }
+        }
+    }
+
+    /**
+     * Intersects this instance with the given one, modifying this
+     * instance. The intersection consists of the pairwise
+     * {@link RegisterSpec#intersect} of corresponding elements from
+     * this instance and the given one where both are non-null.
+     *
+     * @param other {@code non-null;} set to intersect with
+     * @param localPrimary whether local variables are primary to
+     * the intersection; if {@code true}, then the only non-null
+     * result elements occur when registers being intersected have
+     * equal names (or both have {@code null} names)
+     */
+    public void intersect(RegisterSpecSet other, boolean localPrimary) {
+        throwIfImmutable();
+
+        RegisterSpec[] otherSpecs = other.specs;
+        int thisLen = specs.length;
+        int len = Math.min(thisLen, otherSpecs.length);
+
+        size = -1;
+
+        for (int i = 0; i < len; i++) {
+            RegisterSpec spec = specs[i];
+
+            if (spec == null) {
+                continue;
+            }
+
+            RegisterSpec intersection =
+                spec.intersect(otherSpecs[i], localPrimary);
+            if (intersection != spec) {
+                specs[i] = intersection;
+            }
+        }
+
+        for (int i = len; i < thisLen; i++) {
+            specs[i] = null;
+        }
+    }
+
+    /**
+     * Returns an instance that is identical to this one, except that
+     * all register numbers are offset by the given amount. Mutability
+     * of the result is inherited from the original.
+     *
+     * @param delta the amount to offset the register numbers by
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public RegisterSpecSet withOffset(int delta) {
+        int len = specs.length;
+        RegisterSpecSet result = new RegisterSpecSet(len + delta);
+
+        for (int i = 0; i < len; i++) {
+            RegisterSpec spec = specs[i];
+            if (spec != null) {
+                result.put(spec.withOffset(delta));
+            }
+        }
+
+        result.size = size;
+
+        if (isImmutable()) {
+            result.setImmutable();
+        }
+
+        return result;
+    }
+
+    /**
+     * Makes and return a mutable copy of this instance.
+     *
+     * @return {@code non-null;} the mutable copy
+     */
+    public RegisterSpecSet mutableCopy() {
+        int len = specs.length;
+        RegisterSpecSet copy = new RegisterSpecSet(len);
+
+        for (int i = 0; i < len; i++) {
+            RegisterSpec spec = specs[i];
+            if (spec != null) {
+                copy.put(spec);
+            }
+        }
+
+        copy.size = size;
+
+        return copy;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/Rop.java b/dexgen/src/com/android/dexgen/rop/code/Rop.java
new file mode 100644
index 0000000..db9a6c2
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/Rop.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeList;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Class that describes all the immutable parts of register-based operations.
+ */
+public final class Rop {
+    /** minimum {@code BRANCH_*} value */
+    public static final int BRANCH_MIN = 1;
+
+    /** indicates a non-branching op */
+    public static final int BRANCH_NONE = 1;
+
+    /** indicates a function/method return */
+    public static final int BRANCH_RETURN = 2;
+
+    /** indicates an unconditional goto */
+    public static final int BRANCH_GOTO = 3;
+
+    /** indicates a two-way branch */
+    public static final int BRANCH_IF = 4;
+
+    /** indicates a switch-style branch */
+    public static final int BRANCH_SWITCH = 5;
+
+    /** indicates a throw-style branch (both always-throws and may-throw) */
+    public static final int BRANCH_THROW = 6;
+
+    /** maximum {@code BRANCH_*} value */
+    public static final int BRANCH_MAX = 6;
+
+    /** the opcode; one of the constants in {@link RegOps} */
+    private final int opcode;
+
+    /**
+     * {@code non-null;} result type of this operation; {@link Type#VOID} for
+     * no-result operations
+     */
+    private final Type result;
+
+    /** {@code non-null;} types of all the sources of this operation */
+    private final TypeList sources;
+
+    /** {@code non-null;} list of possible types thrown by this operation */
+    private final TypeList exceptions;
+
+    /**
+     * the branchingness of this op; one of the {@code BRANCH_*}
+     * constants in this class
+     */
+    private final int branchingness;
+
+    /** whether this is a function/method call op or similar */
+    private final boolean isCallLike;
+
+    /** {@code null-ok;} nickname, if specified (used for debugging) */
+    private final String nickname;
+
+    /**
+     * Constructs an instance. This method is private. Use one of the
+     * public constructors.
+     *
+     * @param opcode the opcode; one of the constants in {@link RegOps}
+     * @param result {@code non-null;} result type of this operation; {@link
+     * Type#VOID} for no-result operations
+     * @param sources {@code non-null;} types of all the sources of this operation
+     * @param exceptions {@code non-null;} list of possible types thrown by this
+     * operation
+     * @param branchingness the branchingness of this op; one of the
+     * {@code BRANCH_*} constants
+     * @param isCallLike whether the op is a function/method call or similar
+     * @param nickname {@code null-ok;} optional nickname (used for debugging)
+     */
+    public Rop(int opcode, Type result, TypeList sources,
+               TypeList exceptions, int branchingness, boolean isCallLike,
+               String nickname) {
+        if (result == null) {
+            throw new NullPointerException("result == null");
+        }
+
+        if (sources == null) {
+            throw new NullPointerException("sources == null");
+        }
+
+        if (exceptions == null) {
+            throw new NullPointerException("exceptions == null");
+        }
+
+        if ((branchingness < BRANCH_MIN) || (branchingness > BRANCH_MAX)) {
+            throw new IllegalArgumentException("bogus branchingness");
+        }
+
+        if ((exceptions.size() != 0) && (branchingness != BRANCH_THROW)) {
+            throw new IllegalArgumentException("exceptions / branchingness " +
+                                               "mismatch");
+        }
+
+        this.opcode = opcode;
+        this.result = result;
+        this.sources = sources;
+        this.exceptions = exceptions;
+        this.branchingness = branchingness;
+        this.isCallLike = isCallLike;
+        this.nickname = nickname;
+    }
+
+    /**
+     * Constructs an instance. The constructed instance is never a
+     * call-like op (see {@link #isCallLike}).
+     *
+     * @param opcode the opcode; one of the constants in {@link RegOps}
+     * @param result {@code non-null;} result type of this operation; {@link
+     * Type#VOID} for no-result operations
+     * @param sources {@code non-null;} types of all the sources of this operation
+     * @param exceptions {@code non-null;} list of possible types thrown by this
+     * operation
+     * @param branchingness the branchingness of this op; one of the
+     * {@code BRANCH_*} constants
+     * @param nickname {@code null-ok;} optional nickname (used for debugging)
+     */
+    public Rop(int opcode, Type result, TypeList sources,
+               TypeList exceptions, int branchingness, String nickname) {
+        this(opcode, result, sources, exceptions, branchingness, false,
+             nickname);
+    }
+
+    /**
+     * Constructs a no-exception instance. The constructed instance is never a
+     * call-like op (see {@link #isCallLike}).
+     *
+     * @param opcode the opcode; one of the constants in {@link RegOps}
+     * @param result {@code non-null;} result type of this operation; {@link
+     * Type#VOID} for no-result operations
+     * @param sources {@code non-null;} types of all the sources of this operation
+     * @param branchingness the branchingness of this op; one of the
+     * {@code BRANCH_*} constants
+     * @param nickname {@code null-ok;} optional nickname (used for debugging)
+     */
+    public Rop(int opcode, Type result, TypeList sources, int branchingness,
+               String nickname) {
+        this(opcode, result, sources, StdTypeList.EMPTY, branchingness, false,
+             nickname);
+    }
+
+    /**
+     * Constructs a non-branching no-exception instance. The
+     * {@code branchingness} is always {@code BRANCH_NONE},
+     * and it is never a call-like op (see {@link #isCallLike}).
+     *
+     * @param opcode the opcode; one of the constants in {@link RegOps}
+     * @param result {@code non-null;} result type of this operation; {@link
+     * Type#VOID} for no-result operations
+     * @param sources {@code non-null;} types of all the sources of this operation
+     * @param nickname {@code null-ok;} optional nickname (used for debugging)
+     */
+    public Rop(int opcode, Type result, TypeList sources, String nickname) {
+        this(opcode, result, sources, StdTypeList.EMPTY, Rop.BRANCH_NONE,
+             false, nickname);
+    }
+
+    /**
+     * Constructs a non-empty exceptions instance. Its
+     * {@code branchingness} is always {@code BRANCH_THROW},
+     * but it is never a call-like op (see {@link #isCallLike}).
+     *
+     * @param opcode the opcode; one of the constants in {@link RegOps}
+     * @param result {@code non-null;} result type of this operation; {@link
+     * Type#VOID} for no-result operations
+     * @param sources {@code non-null;} types of all the sources of this operation
+     * @param exceptions {@code non-null;} list of possible types thrown by this
+     * operation
+     * @param nickname {@code null-ok;} optional nickname (used for debugging)
+     */
+    public Rop(int opcode, Type result, TypeList sources, TypeList exceptions,
+               String nickname) {
+        this(opcode, result, sources, exceptions, Rop.BRANCH_THROW, false,
+             nickname);
+    }
+
+    /**
+     * Constructs a non-nicknamed instance with non-empty exceptions, which
+     * is always a call-like op (see {@link #isCallLike}). Its
+     * {@code branchingness} is always {@code BRANCH_THROW}.
+     *
+     * @param opcode the opcode; one of the constants in {@link RegOps}
+     * @param sources {@code non-null;} types of all the sources of this operation
+     * @param exceptions {@code non-null;} list of possible types thrown by this
+     * operation
+     */
+    public Rop(int opcode, TypeList sources, TypeList exceptions) {
+        this(opcode, Type.VOID, sources, exceptions, Rop.BRANCH_THROW, true,
+             null);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            // Easy out.
+            return true;
+        }
+
+        if (!(other instanceof Rop)) {
+            return false;
+        }
+
+        Rop rop = (Rop) other;
+
+        return (opcode == rop.opcode) &&
+            (branchingness == rop.branchingness) &&
+            (result == rop.result) &&
+            sources.equals(rop.sources) &&
+            exceptions.equals(rop.exceptions);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        int h = (opcode * 31) + branchingness;
+        h = (h * 31) + result.hashCode();
+        h = (h * 31) + sources.hashCode();
+        h = (h * 31) + exceptions.hashCode();
+
+        return h;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer(40);
+
+        sb.append("Rop{");
+
+        sb.append(RegOps.opName(opcode));
+
+        if (result != Type.VOID) {
+            sb.append(" ");
+            sb.append(result);
+        } else {
+            sb.append(" .");
+        }
+
+        sb.append(" <-");
+
+        int sz = sources.size();
+        if (sz == 0) {
+            sb.append(" .");
+        } else {
+            for (int i = 0; i < sz; i++) {
+                sb.append(' ');
+                sb.append(sources.getType(i));
+            }
+        }
+
+        if (isCallLike) {
+            sb.append(" call");
+        }
+
+        sz = exceptions.size();
+        if (sz != 0) {
+            sb.append(" throws");
+            for (int i = 0; i < sz; i++) {
+                sb.append(' ');
+                Type one = exceptions.getType(i);
+                if (one == Type.THROWABLE) {
+                    sb.append("<any>");
+                } else {
+                    sb.append(exceptions.getType(i));
+                }
+            }
+        } else {
+            switch (branchingness) {
+                case BRANCH_NONE:   sb.append(" flows"); break;
+                case BRANCH_RETURN: sb.append(" returns"); break;
+                case BRANCH_GOTO:   sb.append(" gotos"); break;
+                case BRANCH_IF:     sb.append(" ifs"); break;
+                case BRANCH_SWITCH: sb.append(" switches"); break;
+                default: sb.append(" " + Hex.u1(branchingness)); break;
+            }
+        }
+
+        sb.append('}');
+
+        return sb.toString();
+    }
+
+    /**
+     * Gets the opcode.
+     *
+     * @return the opcode
+     */
+    public int getOpcode() {
+        return opcode;
+    }
+
+    /**
+     * Gets the result type. A return value of {@link Type#VOID}
+     * means this operation returns nothing.
+     *
+     * @return {@code null-ok;} the result spec
+     */
+    public Type getResult() {
+        return result;
+    }
+
+    /**
+     * Gets the source types.
+     *
+     * @return {@code non-null;} the source types
+     */
+    public TypeList getSources() {
+        return sources;
+    }
+
+    /**
+     * Gets the list of exception types that might be thrown.
+     *
+     * @return {@code non-null;} the list of exception types
+     */
+    public TypeList getExceptions() {
+        return exceptions;
+    }
+
+    /**
+     * Gets the branchingness of this instance.
+     *
+     * @return the branchingness
+     */
+    public int getBranchingness() {
+        return branchingness;
+    }
+
+    /**
+     * Gets whether this opcode is a function/method call or similar.
+     *
+     * @return {@code true} iff this opcode is call-like
+     */
+    public boolean isCallLike() {
+        return isCallLike;
+    }
+
+
+    /**
+     * Gets whether this opcode is commutative (the order of its sources are
+     * unimportant) or not. All commutative Rops have exactly two sources and
+     * have no branchiness.
+     *
+     * @return true if rop is commutative
+     */
+    public boolean isCommutative() {
+        switch (opcode) {
+            case RegOps.AND:
+            case RegOps.OR:
+            case RegOps.XOR:
+            case RegOps.ADD:
+            case RegOps.MUL:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Gets the nickname. If this instance has no nickname, this returns
+     * the result of calling {@link #toString}.
+     *
+     * @return {@code non-null;} the nickname
+     */
+    public String getNickname() {
+        if (nickname != null) {
+            return nickname;
+        }
+
+        return toString();
+    }
+
+    /**
+     * Gets whether this operation can possibly throw an exception. This
+     * is just a convenient wrapper for
+     * {@code getExceptions().size() != 0}.
+     *
+     * @return {@code true} iff this operation can possibly throw
+     */
+    public final boolean canThrow() {
+        return (exceptions.size() != 0);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/RopMethod.java b/dexgen/src/com/android/dexgen/rop/code/RopMethod.java
new file mode 100644
index 0000000..ba65b3f
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/RopMethod.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.util.Bits;
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.IntList;
+
+/**
+ * All of the parts that make up a method at the rop layer.
+ */
+public final class RopMethod {
+    /** {@code non-null;} basic block list of the method */
+    private final BasicBlockList blocks;
+
+    /** {@code >= 0;} label for the block which starts the method */
+    private final int firstLabel;
+
+    /**
+     * {@code null-ok;} array of predecessors for each block, indexed by block
+     * label
+     */
+    private IntList[] predecessors;
+
+    /**
+     * {@code null-ok;} the predecessors for the implicit "exit" block, that is
+     * the labels for the blocks that return, if calculated
+     */
+    private IntList exitPredecessors;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param blocks {@code non-null;} basic block list of the method
+     * @param firstLabel {@code >= 0;} the label of the first block to execute
+     */
+    public RopMethod(BasicBlockList blocks, int firstLabel) {
+        if (blocks == null) {
+            throw new NullPointerException("blocks == null");
+        }
+
+        if (firstLabel < 0) {
+            throw new IllegalArgumentException("firstLabel < 0");
+        }
+
+        this.blocks = blocks;
+        this.firstLabel = firstLabel;
+
+        this.predecessors = null;
+        this.exitPredecessors = null;
+    }
+
+    /**
+     * Gets the basic block list for this method.
+     *
+     * @return {@code non-null;} the list
+     */
+    public BasicBlockList getBlocks() {
+        return blocks;
+    }
+
+    /**
+     * Gets the label for the first block in the method that this list
+     * represents.
+     *
+     * @return {@code >= 0;} the first-block label
+     */
+    public int getFirstLabel() {
+        return firstLabel;
+    }
+
+    /**
+     * Gets the predecessors associated with the given block. This throws
+     * an exception if there is no block with the given label.
+     *
+     * @param label {@code >= 0;} the label of the block in question
+     * @return {@code non-null;} the predecessors of that block
+     */
+    public IntList labelToPredecessors(int label) {
+        if (exitPredecessors == null) {
+            calcPredecessors();
+        }
+
+        IntList result = predecessors[label];
+
+        if (result == null) {
+            throw new RuntimeException("no such block: " + Hex.u2(label));
+        }
+
+        return result;
+    }
+
+    /**
+     * Gets the exit predecessors for this instance.
+     *
+     * @return {@code non-null;} the exit predecessors
+     */
+    public IntList getExitPredecessors() {
+        if (exitPredecessors == null) {
+            calcPredecessors();
+        }
+
+        return exitPredecessors;
+    }
+
+
+    /**
+     * Returns an instance that is identical to this one, except that
+     * the registers in each instruction are offset by the given
+     * amount.
+     *
+     * @param delta the amount to offset register numbers by
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public RopMethod withRegisterOffset(int delta) {
+        RopMethod result = new RopMethod(blocks.withRegisterOffset(delta),
+                                         firstLabel);
+
+        if (exitPredecessors != null) {
+            /*
+             * The predecessors have been calculated. It's safe to
+             * inject these into the new instance, since the
+             * transformation being applied doesn't affect the
+             * predecessors.
+             */
+            result.exitPredecessors = exitPredecessors;
+            result.predecessors = predecessors;
+        }
+
+        return result;
+    }
+
+    /**
+     * Calculates the predecessor sets for each block as well as for the
+     * exit.
+     */
+    private void calcPredecessors() {
+        int maxLabel = blocks.getMaxLabel();
+        IntList[] predecessors = new IntList[maxLabel];
+        IntList exitPredecessors = new IntList(10);
+        int sz = blocks.size();
+
+        /*
+         * For each block, find its successors, and add the block's label to
+         * the successor's predecessors.
+         */
+        for (int i = 0; i < sz; i++) {
+            BasicBlock one = blocks.get(i);
+            int label = one.getLabel();
+            IntList successors = one.getSuccessors();
+            int ssz = successors.size();
+            if (ssz == 0) {
+                // This block exits.
+                exitPredecessors.add(label);
+            } else {
+                for (int j = 0; j < ssz; j++) {
+                    int succLabel = successors.get(j);
+                    IntList succPreds = predecessors[succLabel];
+                    if (succPreds == null) {
+                        succPreds = new IntList(10);
+                        predecessors[succLabel] = succPreds;
+                    }
+                    succPreds.add(label);
+                }
+            }
+        }
+
+        // Sort and immutablize all the predecessor lists.
+        for (int i = 0; i < maxLabel; i++) {
+            IntList preds = predecessors[i];
+            if (preds != null) {
+                preds.sort();
+                preds.setImmutable();
+            }
+        }
+
+        exitPredecessors.sort();
+        exitPredecessors.setImmutable();
+
+        /*
+         * The start label might not ever have had any predecessors
+         * added to it (probably doesn't, because of how Java gets
+         * translated into rop form). So, check for this and rectify
+         * the situation if required.
+         */
+        if (predecessors[firstLabel] == null) {
+            predecessors[firstLabel] = IntList.EMPTY;
+        }
+
+        this.predecessors = predecessors;
+        this.exitPredecessors = exitPredecessors;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/Rops.java b/dexgen/src/com/android/dexgen/rop/code/Rops.java
new file mode 100644
index 0000000..ad9327e
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/Rops.java
@@ -0,0 +1,2086 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstBaseMethodRef;
+import com.android.dexgen.rop.cst.CstMethodRef;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.type.Prototype;
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeBearer;
+import com.android.dexgen.rop.type.TypeList;
+
+/**
+ * Standard instances of {@link Rop}.
+ */
+public final class Rops {
+    /** {@code nop()} */
+    public static final Rop NOP =
+        new Rop(RegOps.NOP, Type.VOID, StdTypeList.EMPTY, "nop");
+
+    /** {@code r,x: int :: r = x;} */
+    public static final Rop MOVE_INT =
+        new Rop(RegOps.MOVE, Type.INT, StdTypeList.INT, "move-int");
+
+    /** {@code r,x: long :: r = x;} */
+    public static final Rop MOVE_LONG =
+        new Rop(RegOps.MOVE, Type.LONG, StdTypeList.LONG, "move-long");
+
+    /** {@code r,x: float :: r = x;} */
+    public static final Rop MOVE_FLOAT =
+        new Rop(RegOps.MOVE, Type.FLOAT, StdTypeList.FLOAT, "move-float");
+
+    /** {@code r,x: double :: r = x;} */
+    public static final Rop MOVE_DOUBLE =
+        new Rop(RegOps.MOVE, Type.DOUBLE, StdTypeList.DOUBLE, "move-double");
+
+    /** {@code r,x: Object :: r = x;} */
+    public static final Rop MOVE_OBJECT =
+        new Rop(RegOps.MOVE, Type.OBJECT, StdTypeList.OBJECT, "move-object");
+
+    /**
+     * {@code r,x: ReturnAddress :: r = x;}
+     *
+     * Note that this rop-form instruction has no dex-form equivilent and
+     * must be removed before the dex conversion.
+     */
+    public static final Rop MOVE_RETURN_ADDRESS =
+        new Rop(RegOps.MOVE, Type.RETURN_ADDRESS,
+                StdTypeList.RETURN_ADDRESS, "move-return-address");
+
+    /** {@code r,param(x): int :: r = param(x);} */
+    public static final Rop MOVE_PARAM_INT =
+        new Rop(RegOps.MOVE_PARAM, Type.INT, StdTypeList.EMPTY,
+                "move-param-int");
+
+    /** {@code r,param(x): long :: r = param(x);} */
+    public static final Rop MOVE_PARAM_LONG =
+        new Rop(RegOps.MOVE_PARAM, Type.LONG, StdTypeList.EMPTY,
+                "move-param-long");
+
+    /** {@code r,param(x): float :: r = param(x);} */
+    public static final Rop MOVE_PARAM_FLOAT =
+        new Rop(RegOps.MOVE_PARAM, Type.FLOAT, StdTypeList.EMPTY,
+                "move-param-float");
+
+    /** {@code r,param(x): double :: r = param(x);} */
+    public static final Rop MOVE_PARAM_DOUBLE =
+        new Rop(RegOps.MOVE_PARAM, Type.DOUBLE, StdTypeList.EMPTY,
+                "move-param-double");
+
+    /** {@code r,param(x): Object :: r = param(x);} */
+    public static final Rop MOVE_PARAM_OBJECT =
+        new Rop(RegOps.MOVE_PARAM, Type.OBJECT, StdTypeList.EMPTY,
+                "move-param-object");
+
+    /** {@code r, literal: int :: r = literal;} */
+    public static final Rop CONST_INT =
+        new Rop(RegOps.CONST, Type.INT, StdTypeList.EMPTY, "const-int");
+
+    /** {@code r, literal: long :: r = literal;} */
+    public static final Rop CONST_LONG =
+        new Rop(RegOps.CONST, Type.LONG, StdTypeList.EMPTY, "const-long");
+
+    /** {@code r, literal: float :: r = literal;} */
+    public static final Rop CONST_FLOAT =
+        new Rop(RegOps.CONST, Type.FLOAT, StdTypeList.EMPTY, "const-float");
+
+    /** {@code r, literal: double :: r = literal;} */
+    public static final Rop CONST_DOUBLE =
+        new Rop(RegOps.CONST, Type.DOUBLE, StdTypeList.EMPTY, "const-double");
+
+    /** {@code r, literal: Object :: r = literal;} */
+    public static final Rop CONST_OBJECT =
+        new Rop(RegOps.CONST, Type.OBJECT, StdTypeList.EMPTY,
+                Exceptions.LIST_Error, "const-object");
+
+    /** {@code r, literal: Object :: r = literal;} */
+    public static final Rop CONST_OBJECT_NOTHROW =
+        new Rop(RegOps.CONST, Type.OBJECT, StdTypeList.EMPTY,
+                "const-object-nothrow");
+
+    /** {@code goto label} */
+    public static final Rop GOTO =
+        new Rop(RegOps.GOTO, Type.VOID, StdTypeList.EMPTY, Rop.BRANCH_GOTO,
+                "goto");
+
+    /** {@code x: int :: if (x == 0) goto label} */
+    public static final Rop IF_EQZ_INT =
+        new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF,
+                "if-eqz-int");
+
+    /** {@code x: int :: if (x != 0) goto label} */
+    public static final Rop IF_NEZ_INT =
+        new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF,
+                "if-nez-int");
+
+    /** {@code x: int :: if (x < 0) goto label} */
+    public static final Rop IF_LTZ_INT =
+        new Rop(RegOps.IF_LT, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF,
+                "if-ltz-int");
+
+    /** {@code x: int :: if (x >= 0) goto label} */
+    public static final Rop IF_GEZ_INT =
+        new Rop(RegOps.IF_GE, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF,
+                "if-gez-int");
+
+    /** {@code x: int :: if (x <= 0) goto label} */
+    public static final Rop IF_LEZ_INT =
+        new Rop(RegOps.IF_LE, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF,
+                "if-lez-int");
+
+    /** {@code x: int :: if (x > 0) goto label} */
+    public static final Rop IF_GTZ_INT =
+        new Rop(RegOps.IF_GT, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF,
+                "if-gtz-int");
+
+    /** {@code x: Object :: if (x == null) goto label} */
+    public static final Rop IF_EQZ_OBJECT =
+        new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.OBJECT, Rop.BRANCH_IF,
+                "if-eqz-object");
+
+    /** {@code x: Object :: if (x != null) goto label} */
+    public static final Rop IF_NEZ_OBJECT =
+        new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.OBJECT, Rop.BRANCH_IF,
+                "if-nez-object");
+
+    /** {@code x,y: int :: if (x == y) goto label} */
+    public static final Rop IF_EQ_INT =
+        new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF,
+                "if-eq-int");
+
+    /** {@code x,y: int :: if (x != y) goto label} */
+    public static final Rop IF_NE_INT =
+        new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF,
+                "if-ne-int");
+
+    /** {@code x,y: int :: if (x < y) goto label} */
+    public static final Rop IF_LT_INT =
+        new Rop(RegOps.IF_LT, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF,
+                "if-lt-int");
+
+    /** {@code x,y: int :: if (x >= y) goto label} */
+    public static final Rop IF_GE_INT =
+        new Rop(RegOps.IF_GE, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF,
+                "if-ge-int");
+
+    /** {@code x,y: int :: if (x <= y) goto label} */
+    public static final Rop IF_LE_INT =
+        new Rop(RegOps.IF_LE, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF,
+                "if-le-int");
+
+    /** {@code x,y: int :: if (x > y) goto label} */
+    public static final Rop IF_GT_INT =
+        new Rop(RegOps.IF_GT, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF,
+                "if-gt-int");
+
+    /** {@code x,y: Object :: if (x == y) goto label} */
+    public static final Rop IF_EQ_OBJECT =
+        new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.OBJECT_OBJECT,
+                Rop.BRANCH_IF, "if-eq-object");
+
+    /** {@code x,y: Object :: if (x != y) goto label} */
+    public static final Rop IF_NE_OBJECT =
+        new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.OBJECT_OBJECT,
+                Rop.BRANCH_IF, "if-ne-object");
+
+    /** {@code x: int :: goto switchtable[x]} */
+    public static final Rop SWITCH =
+        new Rop(RegOps.SWITCH, Type.VOID, StdTypeList.INT, Rop.BRANCH_SWITCH,
+                "switch");
+
+    /** {@code r,x,y: int :: r = x + y;} */
+    public static final Rop ADD_INT =
+        new Rop(RegOps.ADD, Type.INT, StdTypeList.INT_INT, "add-int");
+
+    /** {@code r,x,y: long :: r = x + y;} */
+    public static final Rop ADD_LONG =
+        new Rop(RegOps.ADD, Type.LONG, StdTypeList.LONG_LONG, "add-long");
+
+    /** {@code r,x,y: float :: r = x + y;} */
+    public static final Rop ADD_FLOAT =
+        new Rop(RegOps.ADD, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "add-float");
+
+    /** {@code r,x,y: double :: r = x + y;} */
+    public static final Rop ADD_DOUBLE =
+        new Rop(RegOps.ADD, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE,
+                Rop.BRANCH_NONE, "add-double");
+
+    /** {@code r,x,y: int :: r = x - y;} */
+    public static final Rop SUB_INT =
+        new Rop(RegOps.SUB, Type.INT, StdTypeList.INT_INT, "sub-int");
+
+    /** {@code r,x,y: long :: r = x - y;} */
+    public static final Rop SUB_LONG =
+        new Rop(RegOps.SUB, Type.LONG, StdTypeList.LONG_LONG, "sub-long");
+
+    /** {@code r,x,y: float :: r = x - y;} */
+    public static final Rop SUB_FLOAT =
+        new Rop(RegOps.SUB, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "sub-float");
+
+    /** {@code r,x,y: double :: r = x - y;} */
+    public static final Rop SUB_DOUBLE =
+        new Rop(RegOps.SUB, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE,
+                Rop.BRANCH_NONE, "sub-double");
+
+    /** {@code r,x,y: int :: r = x * y;} */
+    public static final Rop MUL_INT =
+        new Rop(RegOps.MUL, Type.INT, StdTypeList.INT_INT, "mul-int");
+
+    /** {@code r,x,y: long :: r = x * y;} */
+    public static final Rop MUL_LONG =
+        new Rop(RegOps.MUL, Type.LONG, StdTypeList.LONG_LONG, "mul-long");
+
+    /** {@code r,x,y: float :: r = x * y;} */
+    public static final Rop MUL_FLOAT =
+        new Rop(RegOps.MUL, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "mul-float");
+
+    /** {@code r,x,y: double :: r = x * y;} */
+    public static final Rop MUL_DOUBLE =
+        new Rop(RegOps.MUL, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE,
+                Rop.BRANCH_NONE, "mul-double");
+
+    /** {@code r,x,y: int :: r = x / y;} */
+    public static final Rop DIV_INT =
+        new Rop(RegOps.DIV, Type.INT, StdTypeList.INT_INT,
+                Exceptions.LIST_Error_ArithmeticException, "div-int");
+
+    /** {@code r,x,y: long :: r = x / y;} */
+    public static final Rop DIV_LONG =
+        new Rop(RegOps.DIV, Type.LONG, StdTypeList.LONG_LONG,
+                Exceptions.LIST_Error_ArithmeticException, "div-long");
+
+    /** {@code r,x,y: float :: r = x / y;} */
+    public static final Rop DIV_FLOAT =
+        new Rop(RegOps.DIV, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "div-float");
+
+    /** {@code r,x,y: double :: r = x / y;} */
+    public static final Rop DIV_DOUBLE =
+        new Rop(RegOps.DIV, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE,
+                "div-double");
+
+    /** {@code r,x,y: int :: r = x % y;} */
+    public static final Rop REM_INT =
+        new Rop(RegOps.REM, Type.INT, StdTypeList.INT_INT,
+                Exceptions.LIST_Error_ArithmeticException, "rem-int");
+
+    /** {@code r,x,y: long :: r = x % y;} */
+    public static final Rop REM_LONG =
+        new Rop(RegOps.REM, Type.LONG, StdTypeList.LONG_LONG,
+                Exceptions.LIST_Error_ArithmeticException, "rem-long");
+
+    /** {@code r,x,y: float :: r = x % y;} */
+    public static final Rop REM_FLOAT =
+        new Rop(RegOps.REM, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "rem-float");
+
+    /** {@code r,x,y: double :: r = x % y;} */
+    public static final Rop REM_DOUBLE =
+        new Rop(RegOps.REM, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE,
+                "rem-double");
+
+    /** {@code r,x: int :: r = -x;} */
+    public static final Rop NEG_INT =
+        new Rop(RegOps.NEG, Type.INT, StdTypeList.INT, "neg-int");
+
+    /** {@code r,x: long :: r = -x;} */
+    public static final Rop NEG_LONG =
+        new Rop(RegOps.NEG, Type.LONG, StdTypeList.LONG, "neg-long");
+
+    /** {@code r,x: float :: r = -x;} */
+    public static final Rop NEG_FLOAT =
+        new Rop(RegOps.NEG, Type.FLOAT, StdTypeList.FLOAT, "neg-float");
+
+    /** {@code r,x: double :: r = -x;} */
+    public static final Rop NEG_DOUBLE =
+        new Rop(RegOps.NEG, Type.DOUBLE, StdTypeList.DOUBLE, "neg-double");
+
+    /** {@code r,x,y: int :: r = x & y;} */
+    public static final Rop AND_INT =
+        new Rop(RegOps.AND, Type.INT, StdTypeList.INT_INT, "and-int");
+
+    /** {@code r,x,y: long :: r = x & y;} */
+    public static final Rop AND_LONG =
+        new Rop(RegOps.AND, Type.LONG, StdTypeList.LONG_LONG, "and-long");
+
+    /** {@code r,x,y: int :: r = x | y;} */
+    public static final Rop OR_INT =
+        new Rop(RegOps.OR, Type.INT, StdTypeList.INT_INT, "or-int");
+
+    /** {@code r,x,y: long :: r = x | y;} */
+    public static final Rop OR_LONG =
+        new Rop(RegOps.OR, Type.LONG, StdTypeList.LONG_LONG, "or-long");
+
+    /** {@code r,x,y: int :: r = x ^ y;} */
+    public static final Rop XOR_INT =
+        new Rop(RegOps.XOR, Type.INT, StdTypeList.INT_INT, "xor-int");
+
+    /** {@code r,x,y: long :: r = x ^ y;} */
+    public static final Rop XOR_LONG =
+        new Rop(RegOps.XOR, Type.LONG, StdTypeList.LONG_LONG, "xor-long");
+
+    /** {@code r,x,y: int :: r = x << y;} */
+    public static final Rop SHL_INT =
+        new Rop(RegOps.SHL, Type.INT, StdTypeList.INT_INT, "shl-int");
+
+    /** {@code r,x: long; y: int :: r = x << y;} */
+    public static final Rop SHL_LONG =
+        new Rop(RegOps.SHL, Type.LONG, StdTypeList.LONG_INT, "shl-long");
+
+    /** {@code r,x,y: int :: r = x >> y;} */
+    public static final Rop SHR_INT =
+        new Rop(RegOps.SHR, Type.INT, StdTypeList.INT_INT, "shr-int");
+
+    /** {@code r,x: long; y: int :: r = x >> y;} */
+    public static final Rop SHR_LONG =
+        new Rop(RegOps.SHR, Type.LONG, StdTypeList.LONG_INT, "shr-long");
+
+    /** {@code r,x,y: int :: r = x >>> y;} */
+    public static final Rop USHR_INT =
+        new Rop(RegOps.USHR, Type.INT, StdTypeList.INT_INT, "ushr-int");
+
+    /** {@code r,x: long; y: int :: r = x >>> y;} */
+    public static final Rop USHR_LONG =
+        new Rop(RegOps.USHR, Type.LONG, StdTypeList.LONG_INT, "ushr-long");
+
+    /** {@code r,x: int :: r = ~x;} */
+    public static final Rop NOT_INT =
+        new Rop(RegOps.NOT, Type.INT, StdTypeList.INT, "not-int");
+
+    /** {@code r,x: long :: r = ~x;} */
+    public static final Rop NOT_LONG =
+        new Rop(RegOps.NOT, Type.LONG, StdTypeList.LONG, "not-long");
+
+    /** {@code r,x,c: int :: r = x + c;} */
+    public static final Rop ADD_CONST_INT =
+        new Rop(RegOps.ADD, Type.INT, StdTypeList.INT, "add-const-int");
+
+    /** {@code r,x,c: long :: r = x + c;} */
+    public static final Rop ADD_CONST_LONG =
+        new Rop(RegOps.ADD, Type.LONG, StdTypeList.LONG, "add-const-long");
+
+    /** {@code r,x,c: float :: r = x + c;} */
+    public static final Rop ADD_CONST_FLOAT =
+        new Rop(RegOps.ADD, Type.FLOAT, StdTypeList.FLOAT, "add-const-float");
+
+    /** {@code r,x,c: double :: r = x + c;} */
+    public static final Rop ADD_CONST_DOUBLE =
+        new Rop(RegOps.ADD, Type.DOUBLE, StdTypeList.DOUBLE,
+                "add-const-double");
+
+    /** {@code r,x,c: int :: r = x - c;} */
+    public static final Rop SUB_CONST_INT =
+        new Rop(RegOps.SUB, Type.INT, StdTypeList.INT, "sub-const-int");
+
+    /** {@code r,x,c: long :: r = x - c;} */
+    public static final Rop SUB_CONST_LONG =
+        new Rop(RegOps.SUB, Type.LONG, StdTypeList.LONG, "sub-const-long");
+
+    /** {@code r,x,c: float :: r = x - c;} */
+    public static final Rop SUB_CONST_FLOAT =
+        new Rop(RegOps.SUB, Type.FLOAT, StdTypeList.FLOAT, "sub-const-float");
+
+    /** {@code r,x,c: double :: r = x - c;} */
+    public static final Rop SUB_CONST_DOUBLE =
+        new Rop(RegOps.SUB, Type.DOUBLE, StdTypeList.DOUBLE,
+                "sub-const-double");
+
+    /** {@code r,x,c: int :: r = x * c;} */
+    public static final Rop MUL_CONST_INT =
+        new Rop(RegOps.MUL, Type.INT, StdTypeList.INT, "mul-const-int");
+
+    /** {@code r,x,c: long :: r = x * c;} */
+    public static final Rop MUL_CONST_LONG =
+        new Rop(RegOps.MUL, Type.LONG, StdTypeList.LONG, "mul-const-long");
+
+    /** {@code r,x,c: float :: r = x * c;} */
+    public static final Rop MUL_CONST_FLOAT =
+        new Rop(RegOps.MUL, Type.FLOAT, StdTypeList.FLOAT, "mul-const-float");
+
+    /** {@code r,x,c: double :: r = x * c;} */
+    public static final Rop MUL_CONST_DOUBLE =
+        new Rop(RegOps.MUL, Type.DOUBLE, StdTypeList.DOUBLE,
+                "mul-const-double");
+
+    /** {@code r,x,c: int :: r = x / c;} */
+    public static final Rop DIV_CONST_INT =
+        new Rop(RegOps.DIV, Type.INT, StdTypeList.INT,
+                Exceptions.LIST_Error_ArithmeticException, "div-const-int");
+
+    /** {@code r,x,c: long :: r = x / c;} */
+    public static final Rop DIV_CONST_LONG =
+        new Rop(RegOps.DIV, Type.LONG, StdTypeList.LONG,
+                Exceptions.LIST_Error_ArithmeticException, "div-const-long");
+
+    /** {@code r,x,c: float :: r = x / c;} */
+    public static final Rop DIV_CONST_FLOAT =
+        new Rop(RegOps.DIV, Type.FLOAT, StdTypeList.FLOAT, "div-const-float");
+
+    /** {@code r,x,c: double :: r = x / c;} */
+    public static final Rop DIV_CONST_DOUBLE =
+        new Rop(RegOps.DIV, Type.DOUBLE, StdTypeList.DOUBLE,
+                "div-const-double");
+
+    /** {@code r,x,c: int :: r = x % c;} */
+    public static final Rop REM_CONST_INT =
+        new Rop(RegOps.REM, Type.INT, StdTypeList.INT,
+                Exceptions.LIST_Error_ArithmeticException, "rem-const-int");
+
+    /** {@code r,x,c: long :: r = x % c;} */
+    public static final Rop REM_CONST_LONG =
+        new Rop(RegOps.REM, Type.LONG, StdTypeList.LONG,
+                Exceptions.LIST_Error_ArithmeticException, "rem-const-long");
+
+    /** {@code r,x,c: float :: r = x % c;} */
+    public static final Rop REM_CONST_FLOAT =
+        new Rop(RegOps.REM, Type.FLOAT, StdTypeList.FLOAT, "rem-const-float");
+
+    /** {@code r,x,c: double :: r = x % c;} */
+    public static final Rop REM_CONST_DOUBLE =
+        new Rop(RegOps.REM, Type.DOUBLE, StdTypeList.DOUBLE,
+                "rem-const-double");
+
+    /** {@code r,x,c: int :: r = x & c;} */
+    public static final Rop AND_CONST_INT =
+        new Rop(RegOps.AND, Type.INT, StdTypeList.INT, "and-const-int");
+
+    /** {@code r,x,c: long :: r = x & c;} */
+    public static final Rop AND_CONST_LONG =
+        new Rop(RegOps.AND, Type.LONG, StdTypeList.LONG, "and-const-long");
+
+    /** {@code r,x,c: int :: r = x | c;} */
+    public static final Rop OR_CONST_INT =
+        new Rop(RegOps.OR, Type.INT, StdTypeList.INT, "or-const-int");
+
+    /** {@code r,x,c: long :: r = x | c;} */
+    public static final Rop OR_CONST_LONG =
+        new Rop(RegOps.OR, Type.LONG, StdTypeList.LONG, "or-const-long");
+
+    /** {@code r,x,c: int :: r = x ^ c;} */
+    public static final Rop XOR_CONST_INT =
+        new Rop(RegOps.XOR, Type.INT, StdTypeList.INT, "xor-const-int");
+
+    /** {@code r,x,c: long :: r = x ^ c;} */
+    public static final Rop XOR_CONST_LONG =
+        new Rop(RegOps.XOR, Type.LONG, StdTypeList.LONG, "xor-const-long");
+
+    /** {@code r,x,c: int :: r = x << c;} */
+    public static final Rop SHL_CONST_INT =
+        new Rop(RegOps.SHL, Type.INT, StdTypeList.INT, "shl-const-int");
+
+    /** {@code r,x: long; c: int :: r = x << c;} */
+    public static final Rop SHL_CONST_LONG =
+        new Rop(RegOps.SHL, Type.LONG, StdTypeList.INT, "shl-const-long");
+
+    /** {@code r,x,c: int :: r = x >> c;} */
+    public static final Rop SHR_CONST_INT =
+        new Rop(RegOps.SHR, Type.INT, StdTypeList.INT, "shr-const-int");
+
+    /** {@code r,x: long; c: int :: r = x >> c;} */
+    public static final Rop SHR_CONST_LONG =
+        new Rop(RegOps.SHR, Type.LONG, StdTypeList.INT, "shr-const-long");
+
+    /** {@code r,x,c: int :: r = x >>> c;} */
+    public static final Rop USHR_CONST_INT =
+        new Rop(RegOps.USHR, Type.INT, StdTypeList.INT, "ushr-const-int");
+
+    /** {@code r,x: long; c: int :: r = x >>> c;} */
+    public static final Rop USHR_CONST_LONG =
+        new Rop(RegOps.USHR, Type.LONG, StdTypeList.INT, "ushr-const-long");
+
+    /** {@code r: int; x,y: long :: r = cmp(x, y);} */
+    public static final Rop CMPL_LONG =
+        new Rop(RegOps.CMPL, Type.INT, StdTypeList.LONG_LONG, "cmpl-long");
+
+    /** {@code r: int; x,y: float :: r = cmpl(x, y);} */
+    public static final Rop CMPL_FLOAT =
+        new Rop(RegOps.CMPL, Type.INT, StdTypeList.FLOAT_FLOAT, "cmpl-float");
+
+    /** {@code r: int; x,y: double :: r = cmpl(x, y);} */
+    public static final Rop CMPL_DOUBLE =
+        new Rop(RegOps.CMPL, Type.INT, StdTypeList.DOUBLE_DOUBLE,
+                "cmpl-double");
+
+    /** {@code r: int; x,y: float :: r = cmpg(x, y);} */
+    public static final Rop CMPG_FLOAT =
+        new Rop(RegOps.CMPG, Type.INT, StdTypeList.FLOAT_FLOAT, "cmpg-float");
+
+    /** {@code r: int; x,y: double :: r = cmpg(x, y);} */
+    public static final Rop CMPG_DOUBLE =
+        new Rop(RegOps.CMPG, Type.INT, StdTypeList.DOUBLE_DOUBLE,
+                "cmpg-double");
+
+    /** {@code r: int; x: long :: r = (int) x} */
+    public static final Rop CONV_L2I =
+        new Rop(RegOps.CONV, Type.INT, StdTypeList.LONG, "conv-l2i");
+
+    /** {@code r: int; x: float :: r = (int) x} */
+    public static final Rop CONV_F2I =
+        new Rop(RegOps.CONV, Type.INT, StdTypeList.FLOAT, "conv-f2i");
+
+    /** {@code r: int; x: double :: r = (int) x} */
+    public static final Rop CONV_D2I =
+        new Rop(RegOps.CONV, Type.INT, StdTypeList.DOUBLE, "conv-d2i");
+
+    /** {@code r: long; x: int :: r = (long) x} */
+    public static final Rop CONV_I2L =
+        new Rop(RegOps.CONV, Type.LONG, StdTypeList.INT, "conv-i2l");
+
+    /** {@code r: long; x: float :: r = (long) x} */
+    public static final Rop CONV_F2L =
+        new Rop(RegOps.CONV, Type.LONG, StdTypeList.FLOAT, "conv-f2l");
+
+    /** {@code r: long; x: double :: r = (long) x} */
+    public static final Rop CONV_D2L =
+        new Rop(RegOps.CONV, Type.LONG, StdTypeList.DOUBLE, "conv-d2l");
+
+    /** {@code r: float; x: int :: r = (float) x} */
+    public static final Rop CONV_I2F =
+        new Rop(RegOps.CONV, Type.FLOAT, StdTypeList.INT, "conv-i2f");
+
+    /** {@code r: float; x: long :: r = (float) x} */
+    public static final Rop CONV_L2F =
+        new Rop(RegOps.CONV, Type.FLOAT, StdTypeList.LONG, "conv-l2f");
+
+    /** {@code r: float; x: double :: r = (float) x} */
+    public static final Rop CONV_D2F =
+        new Rop(RegOps.CONV, Type.FLOAT, StdTypeList.DOUBLE, "conv-d2f");
+
+    /** {@code r: double; x: int :: r = (double) x} */
+    public static final Rop CONV_I2D =
+        new Rop(RegOps.CONV, Type.DOUBLE, StdTypeList.INT, "conv-i2d");
+
+    /** {@code r: double; x: long :: r = (double) x} */
+    public static final Rop CONV_L2D =
+        new Rop(RegOps.CONV, Type.DOUBLE, StdTypeList.LONG, "conv-l2d");
+
+    /** {@code r: double; x: float :: r = (double) x} */
+    public static final Rop CONV_F2D =
+        new Rop(RegOps.CONV, Type.DOUBLE, StdTypeList.FLOAT, "conv-f2d");
+
+    /**
+     * {@code r,x: int :: r = (x << 24) >> 24} (Java-style
+     * convert int to byte)
+     */
+    public static final Rop TO_BYTE =
+        new Rop(RegOps.TO_BYTE, Type.INT, StdTypeList.INT, "to-byte");
+
+    /**
+     * {@code r,x: int :: r = x & 0xffff} (Java-style
+     * convert int to char)
+     */
+    public static final Rop TO_CHAR =
+        new Rop(RegOps.TO_CHAR, Type.INT, StdTypeList.INT, "to-char");
+
+    /**
+     * {@code r,x: int :: r = (x << 16) >> 16} (Java-style
+     * convert int to short)
+     */
+    public static final Rop TO_SHORT =
+        new Rop(RegOps.TO_SHORT, Type.INT, StdTypeList.INT, "to-short");
+
+    /** {@code return void} */
+    public static final Rop RETURN_VOID =
+        new Rop(RegOps.RETURN, Type.VOID, StdTypeList.EMPTY, Rop.BRANCH_RETURN,
+                "return-void");
+
+    /** {@code x: int; return x} */
+    public static final Rop RETURN_INT =
+        new Rop(RegOps.RETURN, Type.VOID, StdTypeList.INT, Rop.BRANCH_RETURN,
+                "return-int");
+
+    /** {@code x: long; return x} */
+    public static final Rop RETURN_LONG =
+        new Rop(RegOps.RETURN, Type.VOID, StdTypeList.LONG, Rop.BRANCH_RETURN,
+                "return-long");
+
+    /** {@code x: float; return x} */
+    public static final Rop RETURN_FLOAT =
+        new Rop(RegOps.RETURN, Type.VOID, StdTypeList.FLOAT, Rop.BRANCH_RETURN,
+                "return-float");
+
+    /** {@code x: double; return x} */
+    public static final Rop RETURN_DOUBLE =
+        new Rop(RegOps.RETURN, Type.VOID, StdTypeList.DOUBLE,
+                Rop.BRANCH_RETURN, "return-double");
+
+    /** {@code x: Object; return x} */
+    public static final Rop RETURN_OBJECT =
+        new Rop(RegOps.RETURN, Type.VOID, StdTypeList.OBJECT,
+                Rop.BRANCH_RETURN, "return-object");
+
+    /** {@code T: any type; r: int; x: T[]; :: r = x.length} */
+    public static final Rop ARRAY_LENGTH =
+        new Rop(RegOps.ARRAY_LENGTH, Type.INT, StdTypeList.OBJECT,
+                Exceptions.LIST_Error_NullPointerException, "array-length");
+
+    /** {@code x: Throwable :: throw(x)} */
+    public static final Rop THROW =
+        new Rop(RegOps.THROW, Type.VOID, StdTypeList.THROWABLE,
+                StdTypeList.THROWABLE, "throw");
+
+    /** {@code x: Object :: monitorenter(x)} */
+    public static final Rop MONITOR_ENTER =
+        new Rop(RegOps.MONITOR_ENTER, Type.VOID, StdTypeList.OBJECT,
+                Exceptions.LIST_Error_NullPointerException, "monitor-enter");
+
+    /** {@code x: Object :: monitorexit(x)} */
+    public static final Rop MONITOR_EXIT =
+        new Rop(RegOps.MONITOR_EXIT, Type.VOID, StdTypeList.OBJECT,
+                Exceptions.LIST_Error_Null_IllegalMonitorStateException,
+                "monitor-exit");
+
+    /** {@code r,y: int; x: int[] :: r = x[y]} */
+    public static final Rop AGET_INT =
+        new Rop(RegOps.AGET, Type.INT, StdTypeList.INTARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds,
+                "aget-int");
+
+    /** {@code r: long; x: long[]; y: int :: r = x[y]} */
+    public static final Rop AGET_LONG =
+        new Rop(RegOps.AGET, Type.LONG, StdTypeList.LONGARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds,
+                "aget-long");
+
+    /** {@code r: float; x: float[]; y: int :: r = x[y]} */
+    public static final Rop AGET_FLOAT =
+        new Rop(RegOps.AGET, Type.FLOAT, StdTypeList.FLOATARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds,
+                "aget-float");
+
+    /** {@code r: double; x: double[]; y: int :: r = x[y]} */
+    public static final Rop AGET_DOUBLE =
+        new Rop(RegOps.AGET, Type.DOUBLE, StdTypeList.DOUBLEARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds,
+                "aget-double");
+
+    /** {@code r: Object; x: Object[]; y: int :: r = x[y]} */
+    public static final Rop AGET_OBJECT =
+        new Rop(RegOps.AGET, Type.OBJECT, StdTypeList.OBJECTARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds,
+                "aget-object");
+
+    /** {@code r: boolean; x: boolean[]; y: int :: r = x[y]} */
+    public static final Rop AGET_BOOLEAN =
+        new Rop(RegOps.AGET, Type.INT, StdTypeList.BOOLEANARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds,
+                "aget-boolean");
+
+    /** {@code r: byte; x: byte[]; y: int :: r = x[y]} */
+    public static final Rop AGET_BYTE =
+        new Rop(RegOps.AGET, Type.INT, StdTypeList.BYTEARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aget-byte");
+
+    /** {@code r: char; x: char[]; y: int :: r = x[y]} */
+    public static final Rop AGET_CHAR =
+        new Rop(RegOps.AGET, Type.INT, StdTypeList.CHARARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aget-char");
+
+    /** {@code r: short; x: short[]; y: int :: r = x[y]} */
+    public static final Rop AGET_SHORT =
+        new Rop(RegOps.AGET, Type.INT, StdTypeList.SHORTARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds,
+                "aget-short");
+
+    /** {@code x,z: int; y: int[] :: y[z] = x} */
+    public static final Rop APUT_INT =
+        new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_INTARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aput-int");
+
+    /** {@code x: long; y: long[]; z: int :: y[z] = x} */
+    public static final Rop APUT_LONG =
+        new Rop(RegOps.APUT, Type.VOID, StdTypeList.LONG_LONGARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aput-long");
+
+    /** {@code x: float; y: float[]; z: int :: y[z] = x} */
+    public static final Rop APUT_FLOAT =
+        new Rop(RegOps.APUT, Type.VOID, StdTypeList.FLOAT_FLOATARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds,
+                "aput-float");
+
+    /** {@code x: double; y: double[]; z: int :: y[z] = x} */
+    public static final Rop APUT_DOUBLE =
+        new Rop(RegOps.APUT, Type.VOID, StdTypeList.DOUBLE_DOUBLEARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds,
+                "aput-double");
+
+    /** {@code x: Object; y: Object[]; z: int :: y[z] = x} */
+    public static final Rop APUT_OBJECT =
+        new Rop(RegOps.APUT, Type.VOID, StdTypeList.OBJECT_OBJECTARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore,
+                "aput-object");
+
+    /** {@code x: boolean; y: boolean[]; z: int :: y[z] = x} */
+    public static final Rop APUT_BOOLEAN =
+        new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_BOOLEANARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore,
+                "aput-boolean");
+
+    /** {@code x: byte; y: byte[]; z: int :: y[z] = x} */
+    public static final Rop APUT_BYTE =
+        new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_BYTEARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, "aput-byte");
+
+    /** {@code x: char; y: char[]; z: int :: y[z] = x} */
+    public static final Rop APUT_CHAR =
+        new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_CHARARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, "aput-char");
+
+    /** {@code x: short; y: short[]; z: int :: y[z] = x} */
+    public static final Rop APUT_SHORT =
+        new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_SHORTARR_INT,
+                Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore,
+                "aput-short");
+
+    /**
+     * {@code T: any non-array object type :: r =
+     * alloc(T)} (allocate heap space for an object)
+     */
+    public static final Rop NEW_INSTANCE =
+        new Rop(RegOps.NEW_INSTANCE, Type.OBJECT, StdTypeList.EMPTY,
+                Exceptions.LIST_Error, "new-instance");
+
+    /** {@code r: int[]; x: int :: r = new int[x]} */
+    public static final Rop NEW_ARRAY_INT =
+        new Rop(RegOps.NEW_ARRAY, Type.INT_ARRAY, StdTypeList.INT,
+                Exceptions.LIST_Error_NegativeArraySizeException,
+                "new-array-int");
+
+    /** {@code r: long[]; x: int :: r = new long[x]} */
+    public static final Rop NEW_ARRAY_LONG =
+        new Rop(RegOps.NEW_ARRAY, Type.LONG_ARRAY, StdTypeList.INT,
+                Exceptions.LIST_Error_NegativeArraySizeException,
+                "new-array-long");
+
+    /** {@code r: float[]; x: int :: r = new float[x]} */
+    public static final Rop NEW_ARRAY_FLOAT =
+        new Rop(RegOps.NEW_ARRAY, Type.FLOAT_ARRAY, StdTypeList.INT,
+                Exceptions.LIST_Error_NegativeArraySizeException,
+                "new-array-float");
+
+    /** {@code r: double[]; x: int :: r = new double[x]} */
+    public static final Rop NEW_ARRAY_DOUBLE =
+        new Rop(RegOps.NEW_ARRAY, Type.DOUBLE_ARRAY, StdTypeList.INT,
+                Exceptions.LIST_Error_NegativeArraySizeException,
+                "new-array-double");
+
+    /** {@code r: boolean[]; x: int :: r = new boolean[x]} */
+    public static final Rop NEW_ARRAY_BOOLEAN =
+        new Rop(RegOps.NEW_ARRAY, Type.BOOLEAN_ARRAY, StdTypeList.INT,
+                Exceptions.LIST_Error_NegativeArraySizeException,
+                "new-array-boolean");
+
+    /** {@code r: byte[]; x: int :: r = new byte[x]} */
+    public static final Rop NEW_ARRAY_BYTE =
+        new Rop(RegOps.NEW_ARRAY, Type.BYTE_ARRAY, StdTypeList.INT,
+                Exceptions.LIST_Error_NegativeArraySizeException,
+                "new-array-byte");
+
+    /** {@code r: char[]; x: int :: r = new char[x]} */
+    public static final Rop NEW_ARRAY_CHAR =
+        new Rop(RegOps.NEW_ARRAY, Type.CHAR_ARRAY, StdTypeList.INT,
+                Exceptions.LIST_Error_NegativeArraySizeException,
+                "new-array-char");
+
+    /** {@code r: short[]; x: int :: r = new short[x]} */
+    public static final Rop NEW_ARRAY_SHORT =
+        new Rop(RegOps.NEW_ARRAY, Type.SHORT_ARRAY, StdTypeList.INT,
+                Exceptions.LIST_Error_NegativeArraySizeException,
+                "new-array-short");
+
+    /**
+     * {@code T: any non-array object type; x: Object :: (T) x} (can
+     * throw {@code ClassCastException})
+     */
+    public static final Rop CHECK_CAST =
+        new Rop(RegOps.CHECK_CAST, Type.VOID, StdTypeList.OBJECT,
+                Exceptions.LIST_Error_ClassCastException, "check-cast");
+
+    /**
+     * {@code T: any non-array object type; x: Object :: x instanceof
+     * T}. Note: This is listed as throwing {@code Error}
+     * explicitly because the op <i>can</i> throw, but there are no
+     * other predefined exceptions for it.
+     */
+    public static final Rop INSTANCE_OF =
+        new Rop(RegOps.INSTANCE_OF, Type.INT, StdTypeList.OBJECT,
+                Exceptions.LIST_Error, "instance-of");
+
+    /**
+     * {@code r: int; x: Object; f: instance field spec of
+     * type int :: r = x.f}
+     */
+    public static final Rop GET_FIELD_INT =
+        new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT,
+                Exceptions.LIST_Error_NullPointerException, "get-field-int");
+
+    /**
+     * {@code r: long; x: Object; f: instance field spec of
+     * type long :: r = x.f}
+     */
+    public static final Rop GET_FIELD_LONG =
+        new Rop(RegOps.GET_FIELD, Type.LONG, StdTypeList.OBJECT,
+                Exceptions.LIST_Error_NullPointerException, "get-field-long");
+
+    /**
+     * {@code r: float; x: Object; f: instance field spec of
+     * type float :: r = x.f}
+     */
+    public static final Rop GET_FIELD_FLOAT =
+        new Rop(RegOps.GET_FIELD, Type.FLOAT, StdTypeList.OBJECT,
+                Exceptions.LIST_Error_NullPointerException,
+                "get-field-float");
+
+    /**
+     * {@code r: double; x: Object; f: instance field spec of
+     * type double :: r = x.f}
+     */
+    public static final Rop GET_FIELD_DOUBLE =
+        new Rop(RegOps.GET_FIELD, Type.DOUBLE, StdTypeList.OBJECT,
+                Exceptions.LIST_Error_NullPointerException,
+                "get-field-double");
+
+    /**
+     * {@code r: Object; x: Object; f: instance field spec of
+     * type Object :: r = x.f}
+     */
+    public static final Rop GET_FIELD_OBJECT =
+        new Rop(RegOps.GET_FIELD, Type.OBJECT, StdTypeList.OBJECT,
+                Exceptions.LIST_Error_NullPointerException,
+                "get-field-object");
+
+    /**
+     * {@code r: boolean; x: Object; f: instance field spec of
+     * type boolean :: r = x.f}
+     */
+    public static final Rop GET_FIELD_BOOLEAN =
+        new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT,
+                Exceptions.LIST_Error_NullPointerException,
+                "get-field-boolean");
+
+    /**
+     * {@code r: byte; x: Object; f: instance field spec of
+     * type byte :: r = x.f}
+     */
+    public static final Rop GET_FIELD_BYTE =
+        new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT,
+                Exceptions.LIST_Error_NullPointerException,
+                "get-field-byte");
+
+    /**
+     * {@code r: char; x: Object; f: instance field spec of
+     * type char :: r = x.f}
+     */
+    public static final Rop GET_FIELD_CHAR =
+        new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT,
+                Exceptions.LIST_Error_NullPointerException,
+                "get-field-char");
+
+    /**
+     * {@code r: short; x: Object; f: instance field spec of
+     * type short :: r = x.f}
+     */
+    public static final Rop GET_FIELD_SHORT =
+        new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT,
+                Exceptions.LIST_Error_NullPointerException,
+                "get-field-short");
+
+    /** {@code r: int; f: static field spec of type int :: r = f} */
+    public static final Rop GET_STATIC_INT =
+        new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY,
+                Exceptions.LIST_Error, "get-static-int");
+
+    /** {@code r: long; f: static field spec of type long :: r = f} */
+    public static final Rop GET_STATIC_LONG =
+        new Rop(RegOps.GET_STATIC, Type.LONG, StdTypeList.EMPTY,
+                Exceptions.LIST_Error, "get-static-long");
+
+    /** {@code r: float; f: static field spec of type float :: r = f} */
+    public static final Rop GET_STATIC_FLOAT =
+        new Rop(RegOps.GET_STATIC, Type.FLOAT, StdTypeList.EMPTY,
+                Exceptions.LIST_Error, "get-static-float");
+
+    /** {@code r: double; f: static field spec of type double :: r = f} */
+    public static final Rop GET_STATIC_DOUBLE =
+        new Rop(RegOps.GET_STATIC, Type.DOUBLE, StdTypeList.EMPTY,
+                Exceptions.LIST_Error, "get-static-double");
+
+    /** {@code r: Object; f: static field spec of type Object :: r = f} */
+    public static final Rop GET_STATIC_OBJECT =
+        new Rop(RegOps.GET_STATIC, Type.OBJECT, StdTypeList.EMPTY,
+                Exceptions.LIST_Error, "get-static-object");
+
+    /** {@code r: boolean; f: static field spec of type boolean :: r = f} */
+    public static final Rop GET_STATIC_BOOLEAN =
+        new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY,
+                Exceptions.LIST_Error, "get-field-boolean");
+
+    /** {@code r: byte; f: static field spec of type byte :: r = f} */
+    public static final Rop GET_STATIC_BYTE =
+        new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY,
+                Exceptions.LIST_Error, "get-field-byte");
+
+    /** {@code r: char; f: static field spec of type char :: r = f} */
+    public static final Rop GET_STATIC_CHAR =
+        new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY,
+                Exceptions.LIST_Error, "get-field-char");
+
+    /** {@code r: short; f: static field spec of type short :: r = f} */
+    public static final Rop GET_STATIC_SHORT =
+        new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY,
+                Exceptions.LIST_Error, "get-field-short");
+
+    /**
+     * {@code x: int; y: Object; f: instance field spec of type
+     * int :: y.f = x}
+     */
+    public static final Rop PUT_FIELD_INT =
+        new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT,
+                Exceptions.LIST_Error_NullPointerException, "put-field-int");
+
+    /**
+     * {@code x: long; y: Object; f: instance field spec of type
+     * long :: y.f = x}
+     */
+    public static final Rop PUT_FIELD_LONG =
+        new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.LONG_OBJECT,
+                Exceptions.LIST_Error_NullPointerException, "put-field-long");
+
+    /**
+     * {@code x: float; y: Object; f: instance field spec of type
+     * float :: y.f = x}
+     */
+    public static final Rop PUT_FIELD_FLOAT =
+        new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.FLOAT_OBJECT,
+                Exceptions.LIST_Error_NullPointerException,
+                "put-field-float");
+
+    /**
+     * {@code x: double; y: Object; f: instance field spec of type
+     * double :: y.f = x}
+     */
+    public static final Rop PUT_FIELD_DOUBLE =
+        new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.DOUBLE_OBJECT,
+                Exceptions.LIST_Error_NullPointerException,
+                "put-field-double");
+
+    /**
+     * {@code x: Object; y: Object; f: instance field spec of type
+     * Object :: y.f = x}
+     */
+    public static final Rop PUT_FIELD_OBJECT =
+        new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.OBJECT_OBJECT,
+                Exceptions.LIST_Error_NullPointerException,
+                "put-field-object");
+
+    /**
+     * {@code x: int; y: Object; f: instance field spec of type
+     * boolean :: y.f = x}
+     */
+    public static final Rop PUT_FIELD_BOOLEAN =
+        new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT,
+                Exceptions.LIST_Error_NullPointerException,
+                "put-field-boolean");
+
+    /**
+     * {@code x: int; y: Object; f: instance field spec of type
+     * byte :: y.f = x}
+     */
+    public static final Rop PUT_FIELD_BYTE =
+        new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT,
+                Exceptions.LIST_Error_NullPointerException,
+                "put-field-byte");
+
+    /**
+     * {@code x: int; y: Object; f: instance field spec of type
+     * char :: y.f = x}
+     */
+    public static final Rop PUT_FIELD_CHAR =
+        new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT,
+                Exceptions.LIST_Error_NullPointerException,
+                "put-field-char");
+
+    /**
+     * {@code x: int; y: Object; f: instance field spec of type
+     * short :: y.f = x}
+     */
+    public static final Rop PUT_FIELD_SHORT =
+        new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT,
+                Exceptions.LIST_Error_NullPointerException,
+                "put-field-short");
+
+    /** {@code f: static field spec of type int; x: int :: f = x} */
+    public static final Rop PUT_STATIC_INT =
+        new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT,
+                Exceptions.LIST_Error, "put-static-int");
+
+    /** {@code f: static field spec of type long; x: long :: f = x} */
+    public static final Rop PUT_STATIC_LONG =
+        new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.LONG,
+                Exceptions.LIST_Error, "put-static-long");
+
+    /** {@code f: static field spec of type float; x: float :: f = x} */
+    public static final Rop PUT_STATIC_FLOAT =
+        new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.FLOAT,
+                Exceptions.LIST_Error, "put-static-float");
+
+    /** {@code f: static field spec of type double; x: double :: f = x} */
+    public static final Rop PUT_STATIC_DOUBLE =
+        new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.DOUBLE,
+                Exceptions.LIST_Error, "put-static-double");
+
+    /** {@code f: static field spec of type Object; x: Object :: f = x} */
+    public static final Rop PUT_STATIC_OBJECT =
+        new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.OBJECT,
+                Exceptions.LIST_Error, "put-static-object");
+
+    /**
+     * {@code f: static field spec of type boolean; x: boolean :: f =
+     * x}
+     */
+    public static final Rop PUT_STATIC_BOOLEAN =
+        new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT,
+                Exceptions.LIST_Error, "put-static-boolean");
+
+    /** {@code f: static field spec of type byte; x: byte :: f = x} */
+    public static final Rop PUT_STATIC_BYTE =
+        new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT,
+                Exceptions.LIST_Error, "put-static-byte");
+
+    /** {@code f: static field spec of type char; x: char :: f = x} */
+    public static final Rop PUT_STATIC_CHAR =
+        new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT,
+                Exceptions.LIST_Error, "put-static-char");
+
+    /** {@code f: static field spec of type short; x: short :: f = x} */
+    public static final Rop PUT_STATIC_SHORT =
+        new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT,
+                Exceptions.LIST_Error, "put-static-short");
+
+    /** {@code x: Int :: local variable begins in x} */
+    public static final Rop MARK_LOCAL_INT =
+            new Rop (RegOps.MARK_LOCAL, Type.VOID,
+                    StdTypeList.INT, "mark-local-int");
+
+    /** {@code x: Long :: local variable begins in x} */
+    public static final Rop MARK_LOCAL_LONG =
+            new Rop (RegOps.MARK_LOCAL, Type.VOID,
+                    StdTypeList.LONG, "mark-local-long");
+
+    /** {@code x: Float :: local variable begins in x} */
+    public static final Rop MARK_LOCAL_FLOAT =
+            new Rop (RegOps.MARK_LOCAL, Type.VOID,
+                    StdTypeList.FLOAT, "mark-local-float");
+
+    /** {@code x: Double :: local variable begins in x} */
+    public static final Rop MARK_LOCAL_DOUBLE =
+            new Rop (RegOps.MARK_LOCAL, Type.VOID,
+                    StdTypeList.DOUBLE, "mark-local-double");
+
+    /** {@code x: Object :: local variable begins in x} */
+    public static final Rop MARK_LOCAL_OBJECT =
+            new Rop (RegOps.MARK_LOCAL, Type.VOID,
+                    StdTypeList.OBJECT, "mark-local-object");
+
+    /** {@code T: Any primitive type; v0..vx: T :: {v0, ..., vx}} */
+    public static final Rop FILL_ARRAY_DATA =
+        new Rop(RegOps.FILL_ARRAY_DATA, Type.VOID, StdTypeList.EMPTY,
+                "fill-array-data");
+
+    /**
+     * Returns the appropriate rop for the given opcode, destination,
+     * and sources. The result is typically, but not necessarily, a
+     * shared instance.
+     *
+     * <p><b>Note:</b> This method does not do complete error checking on
+     * its arguments, and so it may return an instance which seemed "right
+     * enough" even though in actuality the passed arguments don't quite
+     * match what is returned. TODO: Revisit this issue.</p>
+     *
+     * @param opcode the opcode
+     * @param dest {@code non-null;} destination (result) type, or
+     * {@link Type#VOID} if none
+     * @param sources {@code non-null;} list of source types
+     * @param cst {@code null-ok;} associated constant, if any
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop ropFor(int opcode, TypeBearer dest, TypeList sources,
+            Constant cst) {
+        switch (opcode) {
+            case RegOps.NOP: return NOP;
+            case RegOps.MOVE: return opMove(dest);
+            case RegOps.MOVE_PARAM: return opMoveParam(dest);
+            case RegOps.MOVE_EXCEPTION: return opMoveException(dest);
+            case RegOps.CONST: return opConst(dest);
+            case RegOps.GOTO: return GOTO;
+            case RegOps.IF_EQ: return opIfEq(sources);
+            case RegOps.IF_NE: return opIfNe(sources);
+            case RegOps.IF_LT: return opIfLt(sources);
+            case RegOps.IF_GE: return opIfGe(sources);
+            case RegOps.IF_LE: return opIfLe(sources);
+            case RegOps.IF_GT: return opIfGt(sources);
+            case RegOps.SWITCH: return SWITCH;
+            case RegOps.ADD: return opAdd(sources);
+            case RegOps.SUB: return opSub(sources);
+            case RegOps.MUL: return opMul(sources);
+            case RegOps.DIV: return opDiv(sources);
+            case RegOps.REM: return opRem(sources);
+            case RegOps.NEG: return opNeg(dest);
+            case RegOps.AND: return opAnd(sources);
+            case RegOps.OR: return opOr(sources);
+            case RegOps.XOR: return opXor(sources);
+            case RegOps.SHL: return opShl(sources);
+            case RegOps.SHR: return opShr(sources);
+            case RegOps.USHR: return opUshr(sources);
+            case RegOps.NOT: return opNot(dest);
+            case RegOps.CMPL: return opCmpl(sources.getType(0));
+            case RegOps.CMPG: return opCmpg(sources.getType(0));
+            case RegOps.CONV: return opConv(dest, sources.getType(0));
+            case RegOps.TO_BYTE: return TO_BYTE;
+            case RegOps.TO_CHAR: return TO_CHAR;
+            case RegOps.TO_SHORT: return TO_SHORT;
+            case RegOps.RETURN: {
+                if (sources.size() == 0) {
+                    return RETURN_VOID;
+                }
+                return opReturn(sources.getType(0));
+            }
+            case RegOps.ARRAY_LENGTH: return ARRAY_LENGTH;
+            case RegOps.THROW: return THROW;
+            case RegOps.MONITOR_ENTER: return MONITOR_ENTER;
+            case RegOps.MONITOR_EXIT: return MONITOR_EXIT;
+            case RegOps.AGET: {
+                Type source = sources.getType(0);
+                Type componentType;
+                if (source == Type.KNOWN_NULL) {
+                    /*
+                     * Treat a known-null as an array of the expected
+                     * result type.
+                     */
+                    componentType = dest.getType();
+                } else {
+                    componentType = source.getComponentType();
+                }
+                return opAget(componentType);
+            }
+            case RegOps.APUT: {
+                Type source = sources.getType(1);
+                Type componentType;
+                if (source == Type.KNOWN_NULL) {
+                    /*
+                     * Treat a known-null as an array of the type being
+                     * stored.
+                     */
+                    componentType = sources.getType(0);
+                } else {
+                    componentType = source.getComponentType();
+                }
+                return opAput(componentType);
+            }
+            case RegOps.NEW_INSTANCE: return NEW_INSTANCE;
+            case RegOps.NEW_ARRAY: return opNewArray(dest.getType());
+            case RegOps.CHECK_CAST: return CHECK_CAST;
+            case RegOps.INSTANCE_OF: return INSTANCE_OF;
+            case RegOps.GET_FIELD: return opGetField(dest);
+            case RegOps.GET_STATIC: return opGetStatic(dest);
+            case RegOps.PUT_FIELD: return opPutField(sources.getType(0));
+            case RegOps.PUT_STATIC: return opPutStatic(sources.getType(0));
+            case RegOps.INVOKE_STATIC: {
+                return opInvokeStatic(((CstMethodRef) cst).getPrototype());
+            }
+            case RegOps.INVOKE_VIRTUAL: {
+                CstBaseMethodRef cstMeth = (CstMethodRef) cst;
+                Prototype meth = cstMeth.getPrototype();
+                CstType definer = cstMeth.getDefiningClass();
+                meth = meth.withFirstParameter(definer.getClassType());
+                return opInvokeVirtual(meth);
+            }
+            case RegOps.INVOKE_SUPER: {
+                CstBaseMethodRef cstMeth = (CstMethodRef) cst;
+                Prototype meth = cstMeth.getPrototype();
+                CstType definer = cstMeth.getDefiningClass();
+                meth = meth.withFirstParameter(definer.getClassType());
+                return opInvokeSuper(meth);
+            }
+            case RegOps.INVOKE_DIRECT: {
+                CstBaseMethodRef cstMeth = (CstMethodRef) cst;
+                Prototype meth = cstMeth.getPrototype();
+                CstType definer = cstMeth.getDefiningClass();
+                meth = meth.withFirstParameter(definer.getClassType());
+                return opInvokeDirect(meth);
+            }
+            case RegOps.INVOKE_INTERFACE: {
+                CstBaseMethodRef cstMeth = (CstMethodRef) cst;
+                Prototype meth = cstMeth.getPrototype();
+                CstType definer = cstMeth.getDefiningClass();
+                meth = meth.withFirstParameter(definer.getClassType());
+                return opInvokeInterface(meth);
+            }
+        }
+
+        throw new RuntimeException("unknown opcode " + RegOps.opName(opcode));
+    }
+
+    /**
+     * Returns the appropriate {@code move} rop for the given type. The
+     * result is a shared instance.
+     *
+     * @param type {@code non-null;} type of value being moved
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opMove(TypeBearer type) {
+        switch (type.getBasicFrameType()) {
+            case Type.BT_INT:    return MOVE_INT;
+            case Type.BT_LONG:   return MOVE_LONG;
+            case Type.BT_FLOAT:  return MOVE_FLOAT;
+            case Type.BT_DOUBLE: return MOVE_DOUBLE;
+            case Type.BT_OBJECT: return MOVE_OBJECT;
+            case Type.BT_ADDR:   return MOVE_RETURN_ADDRESS;
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * Returns the appropriate {@code move-param} rop for the
+     * given type. The result is a shared instance.
+     *
+     * @param type {@code non-null;} type of value being moved
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opMoveParam(TypeBearer type) {
+        switch (type.getBasicFrameType()) {
+            case Type.BT_INT:    return MOVE_PARAM_INT;
+            case Type.BT_LONG:   return MOVE_PARAM_LONG;
+            case Type.BT_FLOAT:  return MOVE_PARAM_FLOAT;
+            case Type.BT_DOUBLE: return MOVE_PARAM_DOUBLE;
+            case Type.BT_OBJECT: return MOVE_PARAM_OBJECT;
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * Returns the appropriate {@code move-exception} rop for the
+     * given type. The result may be a shared instance.
+     *
+     * @param type {@code non-null;} type of the exception
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opMoveException(TypeBearer type) {
+        return new Rop(RegOps.MOVE_EXCEPTION, type.getType(),
+                       StdTypeList.EMPTY, (String) null);
+    }
+
+    /**
+     * Returns the appropriate {@code move-result} rop for the
+     * given type. The result may be a shared instance.
+     *
+     * @param type {@code non-null;} type of the parameter
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opMoveResult(TypeBearer type) {
+        return new Rop(RegOps.MOVE_RESULT, type.getType(),
+                       StdTypeList.EMPTY, (String) null);
+    }
+
+    /**
+     * Returns the appropriate {@code move-result-pseudo} rop for the
+     * given type. The result may be a shared instance.
+     *
+     * @param type {@code non-null;} type of the parameter
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opMoveResultPseudo(TypeBearer type) {
+        return new Rop(RegOps.MOVE_RESULT_PSEUDO, type.getType(),
+                       StdTypeList.EMPTY, (String) null);
+    }
+
+    /**
+     * Returns the appropriate {@code const} rop for the given
+     * type. The result is a shared instance.
+     *
+     * @param type {@code non-null;} type of the constant
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opConst(TypeBearer type) {
+        if (type.getType() == Type.KNOWN_NULL) {
+            return CONST_OBJECT_NOTHROW;
+        }
+
+        switch (type.getBasicFrameType()) {
+            case Type.BT_INT:    return CONST_INT;
+            case Type.BT_LONG:   return CONST_LONG;
+            case Type.BT_FLOAT:  return CONST_FLOAT;
+            case Type.BT_DOUBLE: return CONST_DOUBLE;
+            case Type.BT_OBJECT: return CONST_OBJECT;
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * Returns the appropriate {@code if-eq} rop for the given
+     * sources. The result is a shared instance.
+     *
+     * @param types {@code non-null;} source types
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opIfEq(TypeList types) {
+        return pickIf(types, IF_EQZ_INT, IF_EQZ_OBJECT,
+                      IF_EQ_INT, IF_EQ_OBJECT);
+    }
+
+    /**
+     * Returns the appropriate {@code if-ne} rop for the given
+     * sources. The result is a shared instance.
+     *
+     * @param types {@code non-null;} source types
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opIfNe(TypeList types) {
+        return pickIf(types, IF_NEZ_INT, IF_NEZ_OBJECT,
+                      IF_NE_INT, IF_NE_OBJECT);
+    }
+
+    /**
+     * Returns the appropriate {@code if-lt} rop for the given
+     * sources. The result is a shared instance.
+     *
+     * @param types {@code non-null;} source types
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opIfLt(TypeList types) {
+        return pickIf(types, IF_LTZ_INT, null, IF_LT_INT, null);
+    }
+
+    /**
+     * Returns the appropriate {@code if-ge} rop for the given
+     * sources. The result is a shared instance.
+     *
+     * @param types {@code non-null;} source types
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opIfGe(TypeList types) {
+        return pickIf(types, IF_GEZ_INT, null, IF_GE_INT, null);
+    }
+
+    /**
+     * Returns the appropriate {@code if-gt} rop for the given
+     * sources. The result is a shared instance.
+     *
+     * @param types {@code non-null;} source types
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opIfGt(TypeList types) {
+        return pickIf(types, IF_GTZ_INT, null, IF_GT_INT, null);
+    }
+
+    /**
+     * Returns the appropriate {@code if-le} rop for the given
+     * sources. The result is a shared instance.
+     *
+     * @param types {@code non-null;} source types
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opIfLe(TypeList types) {
+        return pickIf(types, IF_LEZ_INT, null, IF_LE_INT, null);
+    }
+
+    /**
+     * Helper for all the {@code if*}-related methods, which
+     * checks types and picks one of the four variants, throwing if
+     * there's a problem.
+     *
+     * @param types {@code non-null;} the types
+     * @param intZ {@code non-null;} the int-to-0 comparison
+     * @param objZ {@code null-ok;} the object-to-null comparison
+     * @param intInt {@code non-null;} the int-to-int comparison
+     * @param objObj {@code non-null;} the object-to-object comparison
+     * @return {@code non-null;} the appropriate instance
+     */
+    private static Rop pickIf(TypeList types, Rop intZ, Rop objZ, Rop intInt,
+                              Rop objObj) {
+        switch(types.size()) {
+            case 1: {
+                switch (types.getType(0).getBasicFrameType()) {
+                    case Type.BT_INT: {
+                        return intZ;
+                    }
+                    case Type.BT_OBJECT: {
+                        if (objZ != null) {
+                            return objZ;
+                        }
+                    }
+                }
+                break;
+            }
+            case 2: {
+                int bt = types.getType(0).getBasicFrameType();
+                if (bt == types.getType(1).getBasicFrameType()) {
+                    switch (bt) {
+                        case Type.BT_INT: {
+                            return intInt;
+                        }
+                        case Type.BT_OBJECT: {
+                            if (objObj != null) {
+                                return objObj;
+                            }
+                        }
+                    }
+                }
+                break;
+            }
+        }
+
+        return throwBadTypes(types);
+    }
+
+    /**
+     * Returns the appropriate {@code add} rop for the given
+     * types. The result is a shared instance.
+     *
+     * @param types {@code non-null;} types of the sources
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opAdd(TypeList types) {
+        return pickBinaryOp(types, ADD_CONST_INT, ADD_CONST_LONG,
+                            ADD_CONST_FLOAT, ADD_CONST_DOUBLE, ADD_INT,
+                            ADD_LONG, ADD_FLOAT, ADD_DOUBLE);
+    }
+
+    /**
+     * Returns the appropriate {@code sub} rop for the given
+     * types. The result is a shared instance.
+     *
+     * @param types {@code non-null;} types of the sources
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opSub(TypeList types) {
+        return pickBinaryOp(types, SUB_CONST_INT, SUB_CONST_LONG,
+                            SUB_CONST_FLOAT, SUB_CONST_DOUBLE, SUB_INT,
+                            SUB_LONG, SUB_FLOAT, SUB_DOUBLE);
+    }
+
+    /**
+     * Returns the appropriate {@code mul} rop for the given
+     * types. The result is a shared instance.
+     *
+     * @param types {@code non-null;} types of the sources
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opMul(TypeList types) {
+        return pickBinaryOp(types, MUL_CONST_INT, MUL_CONST_LONG,
+                            MUL_CONST_FLOAT, MUL_CONST_DOUBLE, MUL_INT,
+                            MUL_LONG, MUL_FLOAT, MUL_DOUBLE);
+    }
+
+    /**
+     * Returns the appropriate {@code div} rop for the given
+     * types. The result is a shared instance.
+     *
+     * @param types {@code non-null;} types of the sources
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opDiv(TypeList types) {
+        return pickBinaryOp(types, DIV_CONST_INT, DIV_CONST_LONG,
+                            DIV_CONST_FLOAT, DIV_CONST_DOUBLE, DIV_INT,
+                            DIV_LONG, DIV_FLOAT, DIV_DOUBLE);
+    }
+
+    /**
+     * Returns the appropriate {@code rem} rop for the given
+     * types. The result is a shared instance.
+     *
+     * @param types {@code non-null;} types of the sources
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opRem(TypeList types) {
+        return pickBinaryOp(types, REM_CONST_INT, REM_CONST_LONG,
+                            REM_CONST_FLOAT, REM_CONST_DOUBLE, REM_INT,
+                            REM_LONG, REM_FLOAT, REM_DOUBLE);
+    }
+
+    /**
+     * Returns the appropriate {@code and} rop for the given
+     * types. The result is a shared instance.
+     *
+     * @param types {@code non-null;} types of the sources
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opAnd(TypeList types) {
+        return pickBinaryOp(types, AND_CONST_INT, AND_CONST_LONG, null, null,
+                            AND_INT, AND_LONG, null, null);
+    }
+
+    /**
+     * Returns the appropriate {@code or} rop for the given
+     * types. The result is a shared instance.
+     *
+     * @param types {@code non-null;} types of the sources
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opOr(TypeList types) {
+        return pickBinaryOp(types, OR_CONST_INT, OR_CONST_LONG, null, null,
+                            OR_INT, OR_LONG, null, null);
+    }
+
+    /**
+     * Returns the appropriate {@code xor} rop for the given
+     * types. The result is a shared instance.
+     *
+     * @param types {@code non-null;} types of the sources
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opXor(TypeList types) {
+        return pickBinaryOp(types, XOR_CONST_INT, XOR_CONST_LONG, null, null,
+                            XOR_INT, XOR_LONG, null, null);
+    }
+
+    /**
+     * Returns the appropriate {@code shl} rop for the given
+     * types. The result is a shared instance.
+     *
+     * @param types {@code non-null;} types of the sources
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opShl(TypeList types) {
+        return pickBinaryOp(types, SHL_CONST_INT, SHL_CONST_LONG, null, null,
+                            SHL_INT, SHL_LONG, null, null);
+    }
+
+    /**
+     * Returns the appropriate {@code shr} rop for the given
+     * types. The result is a shared instance.
+     *
+     * @param types {@code non-null;} types of the sources
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opShr(TypeList types) {
+        return pickBinaryOp(types, SHR_CONST_INT, SHR_CONST_LONG, null, null,
+                            SHR_INT, SHR_LONG, null, null);
+    }
+
+    /**
+     * Returns the appropriate {@code ushr} rop for the given
+     * types. The result is a shared instance.
+     *
+     * @param types {@code non-null;} types of the sources
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opUshr(TypeList types) {
+        return pickBinaryOp(types, USHR_CONST_INT, USHR_CONST_LONG, null, null,
+                            USHR_INT, USHR_LONG, null, null);
+    }
+
+    /**
+     * Returns the appropriate binary arithmetic rop for the given type
+     * and arguments. The result is a shared instance.
+     *
+     * @param types {@code non-null;} sources of the operation
+     * @param int1 {@code non-null;} the int-to-constant rop
+     * @param long1 {@code non-null;} the long-to-constant rop
+     * @param float1 {@code null-ok;} the float-to-constant rop, if any
+     * @param double1 {@code null-ok;} the double-to-constant rop, if any
+     * @param int2 {@code non-null;} the int-to-int rop
+     * @param long2 {@code non-null;} the long-to-long or long-to-int rop
+     * @param float2 {@code null-ok;} the float-to-float rop, if any
+     * @param double2 {@code null-ok;} the double-to-double rop, if any
+     * @return {@code non-null;} an appropriate instance
+     */
+    private static Rop pickBinaryOp(TypeList types, Rop int1, Rop long1,
+                                    Rop float1, Rop double1, Rop int2,
+                                    Rop long2, Rop float2, Rop double2) {
+        int bt1 = types.getType(0).getBasicFrameType();
+        Rop result = null;
+
+        switch (types.size()) {
+            case 1: {
+                switch(bt1) {
+                    case Type.BT_INT:    return int1;
+                    case Type.BT_LONG:   return long1;
+                    case Type.BT_FLOAT:  result = float1; break;
+                    case Type.BT_DOUBLE: result = double1; break;
+                }
+                break;
+            }
+            case 2: {
+                switch(bt1) {
+                    case Type.BT_INT:    return int2;
+                    case Type.BT_LONG:   return long2;
+                    case Type.BT_FLOAT:  result = float2; break;
+                    case Type.BT_DOUBLE: result = double2; break;
+                }
+                break;
+            }
+        }
+
+        if (result == null) {
+            return throwBadTypes(types);
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns the appropriate {@code neg} rop for the given type. The
+     * result is a shared instance.
+     *
+     * @param type {@code non-null;} type of value being operated on
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opNeg(TypeBearer type) {
+        switch (type.getBasicFrameType()) {
+            case Type.BT_INT:    return NEG_INT;
+            case Type.BT_LONG:   return NEG_LONG;
+            case Type.BT_FLOAT:  return NEG_FLOAT;
+            case Type.BT_DOUBLE: return NEG_DOUBLE;
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * Returns the appropriate {@code not} rop for the given type. The
+     * result is a shared instance.
+     *
+     * @param type {@code non-null;} type of value being operated on
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opNot(TypeBearer type) {
+        switch (type.getBasicFrameType()) {
+            case Type.BT_INT:  return NOT_INT;
+            case Type.BT_LONG: return NOT_LONG;
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * Returns the appropriate {@code cmpl} rop for the given type. The
+     * result is a shared instance.
+     *
+     * @param type {@code non-null;} type of value being compared
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opCmpl(TypeBearer type) {
+        switch (type.getBasicType()) {
+            case Type.BT_LONG:   return CMPL_LONG;
+            case Type.BT_FLOAT:  return CMPL_FLOAT;
+            case Type.BT_DOUBLE: return CMPL_DOUBLE;
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * Returns the appropriate {@code cmpg} rop for the given type. The
+     * result is a shared instance.
+     *
+     * @param type {@code non-null;} type of value being compared
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opCmpg(TypeBearer type) {
+        switch (type.getBasicType()) {
+            case Type.BT_FLOAT:  return CMPG_FLOAT;
+            case Type.BT_DOUBLE: return CMPG_DOUBLE;
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * Returns the appropriate {@code conv} rop for the given types. The
+     * result is a shared instance.
+     *
+     * @param dest {@code non-null;} target value type
+     * @param source {@code non-null;} source value type
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opConv(TypeBearer dest, TypeBearer source) {
+        int dbt = dest.getBasicFrameType();
+        switch (source.getBasicFrameType()) {
+            case Type.BT_INT: {
+                switch (dbt) {
+                    case Type.BT_LONG:   return CONV_I2L;
+                    case Type.BT_FLOAT:  return CONV_I2F;
+                    case Type.BT_DOUBLE: return CONV_I2D;
+                }
+            }
+            case Type.BT_LONG: {
+                switch (dbt) {
+                    case Type.BT_INT:    return CONV_L2I;
+                    case Type.BT_FLOAT:  return CONV_L2F;
+                    case Type.BT_DOUBLE: return CONV_L2D;
+                }
+            }
+            case Type.BT_FLOAT: {
+                switch (dbt) {
+                    case Type.BT_INT:    return CONV_F2I;
+                    case Type.BT_LONG:   return CONV_F2L;
+                    case Type.BT_DOUBLE: return CONV_F2D;
+                }
+            }
+            case Type.BT_DOUBLE: {
+                switch (dbt) {
+                    case Type.BT_INT:   return CONV_D2I;
+                    case Type.BT_LONG:  return CONV_D2L;
+                    case Type.BT_FLOAT: return CONV_D2F;
+                }
+            }
+        }
+
+        return throwBadTypes(StdTypeList.make(dest.getType(),
+                                              source.getType()));
+    }
+
+    /**
+     * Returns the appropriate {@code return} rop for the given type. The
+     * result is a shared instance.
+     *
+     * @param type {@code non-null;} type of value being returned
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opReturn(TypeBearer type) {
+        switch (type.getBasicFrameType()) {
+            case Type.BT_INT:    return RETURN_INT;
+            case Type.BT_LONG:   return RETURN_LONG;
+            case Type.BT_FLOAT:  return RETURN_FLOAT;
+            case Type.BT_DOUBLE: return RETURN_DOUBLE;
+            case Type.BT_OBJECT: return RETURN_OBJECT;
+            case Type.BT_VOID:   return RETURN_VOID;
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * Returns the appropriate {@code aget} rop for the given type. The
+     * result is a shared instance.
+     *
+     * @param type {@code non-null;} element type of array being accessed
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opAget(TypeBearer type) {
+        switch (type.getBasicType()) {
+            case Type.BT_INT:     return AGET_INT;
+            case Type.BT_LONG:    return AGET_LONG;
+            case Type.BT_FLOAT:   return AGET_FLOAT;
+            case Type.BT_DOUBLE:  return AGET_DOUBLE;
+            case Type.BT_OBJECT:  return AGET_OBJECT;
+            case Type.BT_BOOLEAN: return AGET_BOOLEAN;
+            case Type.BT_BYTE:    return AGET_BYTE;
+            case Type.BT_CHAR:    return AGET_CHAR;
+            case Type.BT_SHORT:   return AGET_SHORT;
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * Returns the appropriate {@code aput} rop for the given type. The
+     * result is a shared instance.
+     *
+     * @param type {@code non-null;} element type of array being accessed
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opAput(TypeBearer type) {
+        switch (type.getBasicType()) {
+            case Type.BT_INT:     return APUT_INT;
+            case Type.BT_LONG:    return APUT_LONG;
+            case Type.BT_FLOAT:   return APUT_FLOAT;
+            case Type.BT_DOUBLE:  return APUT_DOUBLE;
+            case Type.BT_OBJECT:  return APUT_OBJECT;
+            case Type.BT_BOOLEAN: return APUT_BOOLEAN;
+            case Type.BT_BYTE:    return APUT_BYTE;
+            case Type.BT_CHAR:    return APUT_CHAR;
+            case Type.BT_SHORT:   return APUT_SHORT;
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * Returns the appropriate {@code new-array} rop for the given
+     * type. The result is a shared instance.
+     *
+     * @param arrayType {@code non-null;} array type of array being created
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opNewArray(TypeBearer arrayType) {
+        Type type = arrayType.getType();
+        Type elementType = type.getComponentType();
+
+        switch (elementType.getBasicType()) {
+            case Type.BT_INT:     return NEW_ARRAY_INT;
+            case Type.BT_LONG:    return NEW_ARRAY_LONG;
+            case Type.BT_FLOAT:   return NEW_ARRAY_FLOAT;
+            case Type.BT_DOUBLE:  return NEW_ARRAY_DOUBLE;
+            case Type.BT_BOOLEAN: return NEW_ARRAY_BOOLEAN;
+            case Type.BT_BYTE:    return NEW_ARRAY_BYTE;
+            case Type.BT_CHAR:    return NEW_ARRAY_CHAR;
+            case Type.BT_SHORT:   return NEW_ARRAY_SHORT;
+            case Type.BT_OBJECT: {
+                return new Rop(RegOps.NEW_ARRAY, type, StdTypeList.INT,
+                        Exceptions.LIST_Error_NegativeArraySizeException,
+                        "new-array-object");
+            }
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * Returns the appropriate {@code filled-new-array} rop for the given
+     * type. The result may be a shared instance.
+     *
+     * @param arrayType {@code non-null;} type of array being created
+     * @param count {@code >= 0;} number of elements that the array should have
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opFilledNewArray(TypeBearer arrayType, int count) {
+        Type type = arrayType.getType();
+        Type elementType = type.getComponentType();
+
+        if (elementType.isCategory2()) {
+            return throwBadType(arrayType);
+        }
+
+        if (count < 0) {
+            throw new IllegalArgumentException("count < 0");
+        }
+
+        StdTypeList sourceTypes = new StdTypeList(count);
+
+        for (int i = 0; i < count; i++) {
+            sourceTypes.set(i, elementType);
+        }
+
+        // Note: The resulting rop is considered call-like.
+        return new Rop(RegOps.FILLED_NEW_ARRAY,
+                       sourceTypes,
+                       Exceptions.LIST_Error);
+    }
+
+    /**
+     * Returns the appropriate {@code get-field} rop for the given
+     * type. The result is a shared instance.
+     *
+     * @param type {@code non-null;} type of the field in question
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opGetField(TypeBearer type) {
+        switch (type.getBasicType()) {
+            case Type.BT_INT:     return GET_FIELD_INT;
+            case Type.BT_LONG:    return GET_FIELD_LONG;
+            case Type.BT_FLOAT:   return GET_FIELD_FLOAT;
+            case Type.BT_DOUBLE:  return GET_FIELD_DOUBLE;
+            case Type.BT_OBJECT:  return GET_FIELD_OBJECT;
+            case Type.BT_BOOLEAN: return GET_FIELD_BOOLEAN;
+            case Type.BT_BYTE:    return GET_FIELD_BYTE;
+            case Type.BT_CHAR:    return GET_FIELD_CHAR;
+            case Type.BT_SHORT:   return GET_FIELD_SHORT;
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * Returns the appropriate {@code put-field} rop for the given
+     * type. The result is a shared instance.
+     *
+     * @param type {@code non-null;} type of the field in question
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opPutField(TypeBearer type) {
+        switch (type.getBasicType()) {
+            case Type.BT_INT:     return PUT_FIELD_INT;
+            case Type.BT_LONG:    return PUT_FIELD_LONG;
+            case Type.BT_FLOAT:   return PUT_FIELD_FLOAT;
+            case Type.BT_DOUBLE:  return PUT_FIELD_DOUBLE;
+            case Type.BT_OBJECT:  return PUT_FIELD_OBJECT;
+            case Type.BT_BOOLEAN: return PUT_FIELD_BOOLEAN;
+            case Type.BT_BYTE:    return PUT_FIELD_BYTE;
+            case Type.BT_CHAR:    return PUT_FIELD_CHAR;
+            case Type.BT_SHORT:   return PUT_FIELD_SHORT;
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * Returns the appropriate {@code get-static} rop for the given
+     * type. The result is a shared instance.
+     *
+     * @param type {@code non-null;} type of the field in question
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opGetStatic(TypeBearer type) {
+        switch (type.getBasicType()) {
+            case Type.BT_INT:     return GET_STATIC_INT;
+            case Type.BT_LONG:    return GET_STATIC_LONG;
+            case Type.BT_FLOAT:   return GET_STATIC_FLOAT;
+            case Type.BT_DOUBLE:  return GET_STATIC_DOUBLE;
+            case Type.BT_OBJECT:  return GET_STATIC_OBJECT;
+            case Type.BT_BOOLEAN: return GET_STATIC_BOOLEAN;
+            case Type.BT_BYTE:    return GET_STATIC_BYTE;
+            case Type.BT_CHAR:    return GET_STATIC_CHAR;
+            case Type.BT_SHORT:   return GET_STATIC_SHORT;
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * Returns the appropriate {@code put-static} rop for the given
+     * type. The result is a shared instance.
+     *
+     * @param type {@code non-null;} type of the field in question
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opPutStatic(TypeBearer type) {
+        switch (type.getBasicType()) {
+            case Type.BT_INT:     return PUT_STATIC_INT;
+            case Type.BT_LONG:    return PUT_STATIC_LONG;
+            case Type.BT_FLOAT:   return PUT_STATIC_FLOAT;
+            case Type.BT_DOUBLE:  return PUT_STATIC_DOUBLE;
+            case Type.BT_OBJECT:  return PUT_STATIC_OBJECT;
+            case Type.BT_BOOLEAN: return PUT_STATIC_BOOLEAN;
+            case Type.BT_BYTE:    return PUT_STATIC_BYTE;
+            case Type.BT_CHAR:    return PUT_STATIC_CHAR;
+            case Type.BT_SHORT:   return PUT_STATIC_SHORT;
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * Returns the appropriate {@code invoke-static} rop for the
+     * given type. The result is typically a newly-allocated instance.
+     *
+     * @param meth {@code non-null;} descriptor of the method
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opInvokeStatic(Prototype meth) {
+        return new Rop(RegOps.INVOKE_STATIC,
+                       meth.getParameterFrameTypes(),
+                       StdTypeList.THROWABLE);
+    }
+
+    /**
+     * Returns the appropriate {@code invoke-virtual} rop for the
+     * given type. The result is typically a newly-allocated instance.
+     *
+     * @param meth {@code non-null;} descriptor of the method, including the
+     * {@code this} parameter
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opInvokeVirtual(Prototype meth) {
+        return new Rop(RegOps.INVOKE_VIRTUAL,
+                       meth.getParameterFrameTypes(),
+                       StdTypeList.THROWABLE);
+    }
+
+    /**
+     * Returns the appropriate {@code invoke-super} rop for the
+     * given type. The result is typically a newly-allocated instance.
+     *
+     * @param meth {@code non-null;} descriptor of the method, including the
+     * {@code this} parameter
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opInvokeSuper(Prototype meth) {
+        return new Rop(RegOps.INVOKE_SUPER,
+                       meth.getParameterFrameTypes(),
+                       StdTypeList.THROWABLE);
+    }
+
+    /**
+     * Returns the appropriate {@code invoke-direct} rop for the
+     * given type. The result is typically a newly-allocated instance.
+     *
+     * @param meth {@code non-null;} descriptor of the method, including the
+     * {@code this} parameter
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opInvokeDirect(Prototype meth) {
+        return new Rop(RegOps.INVOKE_DIRECT,
+                       meth.getParameterFrameTypes(),
+                       StdTypeList.THROWABLE);
+    }
+
+    /**
+     * Returns the appropriate {@code invoke-interface} rop for the
+     * given type. The result is typically a newly-allocated instance.
+     *
+     * @param meth {@code non-null;} descriptor of the method, including the
+     * {@code this} parameter
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opInvokeInterface(Prototype meth) {
+        return new Rop(RegOps.INVOKE_INTERFACE,
+                       meth.getParameterFrameTypes(),
+                       StdTypeList.THROWABLE);
+    }
+
+    /**
+     * Returns the appropriate {@code mark-local} rop for the given type.
+     * The result is a shared instance.
+     *
+     * @param type {@code non-null;} type of value being marked
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static Rop opMarkLocal(TypeBearer type) {
+        switch (type.getBasicFrameType()) {
+            case Type.BT_INT:    return MARK_LOCAL_INT;
+            case Type.BT_LONG:   return MARK_LOCAL_LONG;
+            case Type.BT_FLOAT:  return MARK_LOCAL_FLOAT;
+            case Type.BT_DOUBLE: return MARK_LOCAL_DOUBLE;
+            case Type.BT_OBJECT: return MARK_LOCAL_OBJECT;
+        }
+
+        return throwBadType(type);
+    }
+
+    /**
+     * This class is uninstantiable.
+     */
+    private Rops() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Throws the right exception to complain about a bogus type.
+     *
+     * @param type {@code non-null;} the bad type
+     * @return never
+     */
+    private static Rop throwBadType(TypeBearer type) {
+        throw new IllegalArgumentException("bad type: " + type);
+    }
+
+    /**
+     * Throws the right exception to complain about a bogus list of types.
+     *
+     * @param types {@code non-null;} the bad types
+     * @return never
+     */
+    private static Rop throwBadTypes(TypeList types) {
+        throw new IllegalArgumentException("bad types: " + types);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/SourcePosition.java b/dexgen/src/com/android/dexgen/rop/code/SourcePosition.java
new file mode 100644
index 0000000..cd0ea25
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/SourcePosition.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Information about a source position for code, which includes both a
+ * line number and original bytecode address.
+ */
+public final class SourcePosition {
+    /** {@code non-null;} convenient "no information known" instance */
+    public static final SourcePosition NO_INFO =
+        new SourcePosition(null, -1, -1);
+
+    /** {@code null-ok;} name of the file of origin or {@code null} if unknown */
+    private final CstUtf8 sourceFile;
+
+    /**
+     * {@code >= -1;} the bytecode address, or {@code -1} if that
+     * information is unknown
+     */
+    private final int address;
+
+    /**
+     * {@code >= -1;} the line number, or {@code -1} if that
+     * information is unknown
+     */
+    private final int line;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param sourceFile {@code null-ok;} name of the file of origin or
+     * {@code null} if unknown
+     * @param address {@code >= -1;} original bytecode address or {@code -1}
+     * if unknown
+     * @param line {@code >= -1;} original line number or {@code -1} if
+     * unknown
+     */
+    public SourcePosition(CstUtf8 sourceFile, int address, int line) {
+        if (address < -1) {
+            throw new IllegalArgumentException("address < -1");
+        }
+
+        if (line < -1) {
+            throw new IllegalArgumentException("line < -1");
+        }
+
+        this.sourceFile = sourceFile;
+        this.address = address;
+        this.line = line;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer(50);
+
+        if (sourceFile != null) {
+            sb.append(sourceFile.toHuman());
+            sb.append(":");
+        }
+
+        if (line >= 0) {
+            sb.append(line);
+        }
+
+        sb.append('@');
+
+        if (address < 0) {
+            sb.append("????");
+        } else {
+            sb.append(Hex.u2(address));
+        }
+
+        return sb.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof SourcePosition)) {
+            return false;
+        }
+
+        if (this == other) {
+            return true;
+        }
+
+        SourcePosition pos = (SourcePosition) other;
+
+        return (address == pos.address) && sameLineAndFile(pos);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return sourceFile.hashCode() + address + line;
+    }
+
+    /**
+     * Returns whether the lines match between this instance and
+     * the one given.
+     *
+     * @param other {@code non-null;} the instance to compare to
+     * @return {@code true} iff the lines match
+     */
+    public boolean sameLine(SourcePosition other) {
+        return (line == other.line);
+    }
+
+    /**
+     * Returns whether the lines and files match between this instance and
+     * the one given.
+     *
+     * @param other {@code non-null;} the instance to compare to
+     * @return {@code true} iff the lines and files match
+     */
+    public boolean sameLineAndFile(SourcePosition other) {
+        return (line == other.line) &&
+            ((sourceFile == other.sourceFile) ||
+             ((sourceFile != null) && sourceFile.equals(other.sourceFile)));
+    }
+
+    /**
+     * Gets the source file, if known.
+     *
+     * @return {@code null-ok;} the source file or {@code null} if unknown
+     */
+    public CstUtf8 getSourceFile() {
+        return sourceFile;
+    }
+
+    /**
+     * Gets the original bytecode address.
+     *
+     * @return {@code >= -1;} the address or {@code -1} if unknown
+     */
+    public int getAddress() {
+        return address;
+    }
+
+    /**
+     * Gets the original line number.
+     *
+     * @return {@code >= -1;} the original line number or {@code -1} if
+     * unknown
+     */
+    public int getLine() {
+        return line;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/SwitchInsn.java b/dexgen/src/com/android/dexgen/rop/code/SwitchInsn.java
new file mode 100644
index 0000000..ee4f4b6
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/SwitchInsn.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.type.StdTypeList;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeList;
+import com.android.dexgen.util.IntList;
+
+/**
+ * Instruction which contains switch cases.
+ */
+public final class SwitchInsn
+        extends Insn {
+    /** {@code non-null;} list of switch cases */
+    private final IntList cases;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param opcode {@code non-null;} the opcode
+     * @param position {@code non-null;} source position
+     * @param result {@code null-ok;} spec for the result, if any
+     * @param sources {@code non-null;} specs for all the sources
+     * @param cases {@code non-null;} list of switch cases
+     */
+    public SwitchInsn(Rop opcode, SourcePosition position, RegisterSpec result,
+                      RegisterSpecList sources, IntList cases) {
+        super(opcode, position, result, sources);
+
+        if (opcode.getBranchingness() != Rop.BRANCH_SWITCH) {
+            throw new IllegalArgumentException("bogus branchingness");
+        }
+
+        if (cases == null) {
+            throw new NullPointerException("cases == null");
+        }
+
+        this.cases = cases;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getInlineString() {
+        return cases.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public TypeList getCatches() {
+        return StdTypeList.EMPTY;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void accept(Visitor visitor) {
+        visitor.visitSwitchInsn(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withAddedCatch(Type type) {
+        throw new UnsupportedOperationException("unsupported");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withRegisterOffset(int delta) {
+        return new SwitchInsn(getOpcode(), getPosition(),
+                              getResult().withOffset(delta),
+                              getSources().withOffset(delta),
+                              cases);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p> SwitchInsn always compares false. The current use for this method
+     * never encounters {@code SwitchInsn}s
+     */
+    @Override
+    public boolean contentEquals(Insn b) {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withNewRegisters(RegisterSpec result,
+            RegisterSpecList sources) {
+
+        return new SwitchInsn(getOpcode(), getPosition(),
+                              result,
+                              sources,
+                              cases);
+    }
+
+    /**
+     * Gets the list of switch cases.
+     *
+     * @return {@code non-null;} the case list
+     */
+    public IntList getCases() {
+        return cases;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/ThrowingCstInsn.java b/dexgen/src/com/android/dexgen/rop/code/ThrowingCstInsn.java
new file mode 100644
index 0000000..7262254
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/ThrowingCstInsn.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeList;
+
+/**
+ * Instruction which contains an explicit reference to a constant
+ * and which might throw an exception.
+ */
+public final class ThrowingCstInsn
+        extends CstInsn {
+    /** {@code non-null;} list of exceptions caught */
+    private final TypeList catches;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param opcode {@code non-null;} the opcode
+     * @param position {@code non-null;} source position
+     * @param sources {@code non-null;} specs for all the sources
+     * @param catches {@code non-null;} list of exceptions caught
+     * @param cst {@code non-null;} the constant
+     */
+    public ThrowingCstInsn(Rop opcode, SourcePosition position,
+                           RegisterSpecList sources,
+                           TypeList catches, Constant cst) {
+        super(opcode, position, null, sources, cst);
+
+        if (opcode.getBranchingness() != Rop.BRANCH_THROW) {
+            throw new IllegalArgumentException("bogus branchingness");
+        }
+
+        if (catches == null) {
+            throw new NullPointerException("catches == null");
+        }
+
+        this.catches = catches;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getInlineString() {
+        return getConstant().toHuman() + " " +
+                                 ThrowingInsn.toCatchString(catches);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public TypeList getCatches() {
+        return catches;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void accept(Visitor visitor) {
+        visitor.visitThrowingCstInsn(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withAddedCatch(Type type) {
+        return new ThrowingCstInsn(getOpcode(), getPosition(),
+                                   getSources(), catches.withAddedType(type),
+                                   getConstant());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withRegisterOffset(int delta) {
+        return new ThrowingCstInsn(getOpcode(), getPosition(),
+                                   getSources().withOffset(delta),
+                                   catches,
+                                   getConstant());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withNewRegisters(RegisterSpec result,
+            RegisterSpecList sources) {
+
+        return new ThrowingCstInsn(getOpcode(), getPosition(),
+                                   sources,
+                                   catches,
+                                   getConstant());
+    }
+
+
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/ThrowingInsn.java b/dexgen/src/com/android/dexgen/rop/code/ThrowingInsn.java
new file mode 100644
index 0000000..24611ad
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/ThrowingInsn.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeList;
+
+/**
+ * Instruction which possibly throws. The {@code successors} list in the
+ * basic block an instance of this class is inside corresponds in-order to
+ * the list of exceptions handled by this instruction, with the
+ * no-exception case appended as the final target.
+ */
+public final class ThrowingInsn
+        extends Insn {
+    /** {@code non-null;} list of exceptions caught */
+    private final TypeList catches;
+
+    /**
+     * Gets the string form of a register spec list to be used as a catches
+     * list.
+     *
+     * @param catches {@code non-null;} the catches list
+     * @return {@code non-null;} the string form
+     */
+    public static String toCatchString(TypeList catches) {
+        StringBuffer sb = new StringBuffer(100);
+
+        sb.append("catch");
+
+        int sz = catches.size();
+        for (int i = 0; i < sz; i++) {
+            sb.append(" ");
+            sb.append(catches.getType(i).toHuman());
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Constructs an instance.
+     *
+     * @param opcode {@code non-null;} the opcode
+     * @param position {@code non-null;} source position
+     * @param sources {@code non-null;} specs for all the sources
+     * @param catches {@code non-null;} list of exceptions caught
+     */
+    public ThrowingInsn(Rop opcode, SourcePosition position,
+                        RegisterSpecList sources,
+                        TypeList catches) {
+        super(opcode, position, null, sources);
+
+        if (opcode.getBranchingness() != Rop.BRANCH_THROW) {
+            throw new IllegalArgumentException("bogus branchingness");
+        }
+
+        if (catches == null) {
+            throw new NullPointerException("catches == null");
+        }
+
+        this.catches = catches;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getInlineString() {
+        return toCatchString(catches);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public TypeList getCatches() {
+        return catches;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void accept(Visitor visitor) {
+        visitor.visitThrowingInsn(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withAddedCatch(Type type) {
+        return new ThrowingInsn(getOpcode(), getPosition(),
+                                getSources(), catches.withAddedType(type));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withRegisterOffset(int delta) {
+        return new ThrowingInsn(getOpcode(), getPosition(),
+                                getSources().withOffset(delta),
+                                catches);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Insn withNewRegisters(RegisterSpec result,
+            RegisterSpecList sources) {
+
+        return new ThrowingInsn(getOpcode(), getPosition(),
+                                sources,
+                                catches);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/code/TranslationAdvice.java b/dexgen/src/com/android/dexgen/rop/code/TranslationAdvice.java
new file mode 100644
index 0000000..9edd248
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/code/TranslationAdvice.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.code;
+
+/**
+ * Interface for "advice" passed from the late stage of translation back
+ * to the early stage. This allows for the final target architecture to
+ * exert its influence early in the translation process without having
+ * the early stage code be explicitly tied to the target.
+ */
+public interface TranslationAdvice {
+    /**
+     * Returns an indication of whether the target can directly represent an
+     * instruction with the given opcode operating on the given arguments,
+     * where the last source argument is used as a constant. (That is, the
+     * last argument must have a type which indicates it is a known constant.)
+     * The instruction associated must have exactly two sources.
+     *
+     * @param opcode {@code non-null;} the opcode
+     * @param sourceA {@code non-null;} the first source
+     * @param sourceB {@code non-null;} the second source
+     * @return {@code true} iff the target can represent the operation
+     * using a constant for the last argument
+     */
+    public boolean hasConstantOperation(Rop opcode,
+            RegisterSpec sourceA, RegisterSpec sourceB);
+
+    /**
+     * Returns true if the translation target requires the sources of the
+     * specified opcode to be in order and contiguous (eg, for an invoke-range)
+     *
+     * @param opcode {@code non-null;} opcode
+     * @param sources {@code non-null;} source list
+     * @return {@code true} iff the target requires the sources to be
+     * in order and contiguous.
+     */
+    public boolean requiresSourcesInOrder(Rop opcode, RegisterSpecList sources);
+
+    /**
+     * Gets the maximum register width that can be represented optimally.
+     * For example, Dex bytecode does not have instruction forms that take
+     * register numbers larger than 15 for all instructions so
+     * DexTranslationAdvice returns 15 here.
+     *
+     * @return register count noted above
+     */
+    public int getMaxOptimalRegisterCount();
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/Constant.java b/dexgen/src/com/android/dexgen/rop/cst/Constant.java
new file mode 100644
index 0000000..deaa5f4
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/Constant.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.util.ToHuman;
+
+/**
+ * Base class for constants of all sorts.
+ */
+public abstract class Constant
+        implements ToHuman, Comparable<Constant> {
+    /**
+     * Returns {@code true} if this instance is a category-2 constant,
+     * meaning it takes up two slots in the constant pool, or
+     * {@code false} if this instance is category-1.
+     *
+     * @return {@code true} iff this instance is category-2
+     */
+    public abstract boolean isCategory2();
+
+    /**
+     * Returns the human name for the particular type of constant
+     * this instance is.
+     *
+     * @return {@code non-null;} the name
+     */
+    public abstract String typeName();
+
+    /**
+     * {@inheritDoc}
+     *
+     * This compares in class-major and value-minor order.
+     */
+    public final int compareTo(Constant other) {
+        Class clazz = getClass();
+        Class otherClazz = other.getClass();
+
+        if (clazz != otherClazz) {
+            return clazz.getName().compareTo(otherClazz.getName());
+        }
+
+        return compareTo0(other);
+    }
+
+    /**
+     * Compare the values of this and another instance, which are guaranteed
+     * to be of the same class. Subclasses must implement this.
+     *
+     * @param other {@code non-null;} the instance to compare to
+     * @return {@code -1}, {@code 0}, or {@code 1}, as usual
+     * for a comparison
+     */
+    protected abstract int compareTo0(Constant other);
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/ConstantPool.java b/dexgen/src/com/android/dexgen/rop/cst/ConstantPool.java
new file mode 100644
index 0000000..1ea188a
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/ConstantPool.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+/**
+ * Interface for constant pools, which are, more or less, just lists of
+ * {@link Constant} objects.
+ */
+public interface ConstantPool {
+    /**
+     * Get the "size" of the constant pool. This corresponds to the
+     * class file field {@code constant_pool_count}, and is in fact
+     * always at least one more than the actual size of the constant pool,
+     * as element {@code 0} is always invalid.
+     *
+     * @return {@code >= 1;} the size
+     */
+    public int size();
+
+    /**
+     * Get the {@code n}th entry in the constant pool, which must
+     * be valid.
+     *
+     * @param n {@code n >= 0, n < size();} the constant pool index
+     * @return {@code non-null;} the corresponding entry
+     * @throws IllegalArgumentException thrown if {@code n} is
+     * in-range but invalid
+     */
+    public Constant get(int n);
+
+    /**
+     * Get the {@code n}th entry in the constant pool, which must
+     * be valid unless {@code n == 0}, in which case {@code null}
+     * is returned.
+     *
+     * @param n {@code n >= 0, n < size();} the constant pool index
+     * @return {@code null-ok;} the corresponding entry, if {@code n != 0}
+     * @throws IllegalArgumentException thrown if {@code n} is
+     * in-range and non-zero but invalid
+     */
+    public Constant get0Ok(int n);
+
+    /**
+     * Get the {@code n}th entry in the constant pool, or
+     * {@code null} if the index is in-range but invalid. In
+     * particular, {@code null} is returned for index {@code 0}
+     * as well as the index after any entry which is defined to take up
+     * two slots (that is, {@code Long} and {@code Double}
+     * entries).
+     *
+     * @param n {@code n >= 0, n < size();} the constant pool index
+     * @return {@code null-ok;} the corresponding entry, or {@code null} if
+     * the index is in-range but invalid
+     */
+    public Constant getOrNull(int n);
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstAnnotation.java b/dexgen/src/com/android/dexgen/rop/cst/CstAnnotation.java
new file mode 100644
index 0000000..89b4fd8
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstAnnotation.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.annotation.Annotation;
+
+/**
+ * Constant type that represents an annotation.
+ */
+public final class CstAnnotation extends Constant {
+    /** {@code non-null;} the actual annotation */
+    private final Annotation annotation;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param annotation {@code non-null;} the annotation to hold
+     */
+    public CstAnnotation(Annotation annotation) {
+        if (annotation == null) {
+            throw new NullPointerException("annotation == null");
+        }
+
+        annotation.throwIfMutable();
+
+        this.annotation = annotation;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (! (other instanceof CstAnnotation)) {
+            return false;
+        }
+
+        return annotation.equals(((CstAnnotation) other).annotation);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return annotation.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int compareTo0(Constant other) {
+        return annotation.compareTo(((CstAnnotation) other).annotation);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return annotation.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "annotation";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCategory2() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return annotation.toString();
+    }
+
+    /**
+     * Get the underlying annotation.
+     *
+     * @return {@code non-null;} the annotation
+     */
+    public Annotation getAnnotation() {
+        return annotation;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstArray.java b/dexgen/src/com/android/dexgen/rop/cst/CstArray.java
new file mode 100644
index 0000000..2fad35e
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstArray.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.FixedSizeList;
+
+/**
+ * Constant type to represent a fixed array of other constants. The contents
+ * may be of any type <i>other</i> than {@link CstUtf8}.
+ */
+public final class CstArray extends Constant {
+    /** {@code non-null;} the actual list of contents */
+    private final List list;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param list {@code non-null;} the actual list of contents
+     */
+    public CstArray(List list) {
+        if (list == null) {
+            throw new NullPointerException("list == null");
+        }
+
+        list.throwIfMutable();
+
+        this.list = list;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (! (other instanceof CstArray)) {
+            return false;
+        }
+
+        return list.equals(((CstArray) other).list);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return list.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int compareTo0(Constant other) {
+        return list.compareTo(((CstArray) other).list);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return list.toString("array{", ", ", "}");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "array";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCategory2() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return list.toHuman("{", ", ", "}");
+    }
+
+    /**
+     * Get the underlying list.
+     *
+     * @return {@code non-null;} the list
+     */
+    public List getList() {
+        return list;
+    }
+
+    /**
+     * List of {@link Constant} instances.
+     */
+    public static final class List
+            extends FixedSizeList implements Comparable<List> {
+        /**
+         * Constructs an instance. All indices initially contain
+         * {@code null}.
+         *
+         * @param size the size of the list
+         */
+        public List(int size) {
+            super(size);
+        }
+
+        /** {@inheritDoc} */
+        public int compareTo(List other) {
+            int thisSize = size();
+            int otherSize = other.size();
+            int compareSize = (thisSize < otherSize) ? thisSize : otherSize;
+
+            for (int i = 0; i < compareSize; i++) {
+                Constant thisItem = (Constant) get0(i);
+                Constant otherItem = (Constant) other.get0(i);
+                int compare = thisItem.compareTo(otherItem);
+                if (compare != 0) {
+                    return compare;
+                }
+            }
+
+            if (thisSize < otherSize) {
+                return -1;
+            } else if (thisSize > otherSize) {
+                return 1;
+            }
+
+            return 0;
+        }
+
+        /**
+         * Gets the element at the given index. It is an error to call
+         * this with the index for an element which was never set; if you
+         * do that, this will throw {@code NullPointerException}.
+         *
+         * @param n {@code >= 0, < size();} which index
+         * @return {@code non-null;} element at that index
+         */
+        public Constant get(int n) {
+            return (Constant) get0(n);
+        }
+
+        /**
+         * Sets the element at the given index.
+         *
+         * @param n {@code >= 0, < size();} which index
+         * @param a {@code null-ok;} the element to set at {@code n}
+         */
+        public void set(int n, Constant a) {
+            if (a instanceof CstUtf8) {
+                throw new IllegalArgumentException("bad value: " + a);
+            }
+
+            set0(n, a);
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstBaseMethodRef.java b/dexgen/src/com/android/dexgen/rop/cst/CstBaseMethodRef.java
new file mode 100644
index 0000000..3914272
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstBaseMethodRef.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Prototype;
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.rop.type.TypeBearer;
+
+/**
+ * Base class for constants of "methodish" type.
+ *
+ * <p><b>Note:</b> As a {@link TypeBearer}, this class bears the return type
+ * of the method.</p>
+ */
+public abstract class CstBaseMethodRef
+        extends CstMemberRef {
+    /** {@code non-null;} the raw prototype for this method */
+    private final Prototype prototype;
+
+    /**
+     * {@code null-ok;} the prototype for this method taken to be an instance
+     * method, or {@code null} if not yet calculated
+     */
+    private Prototype instancePrototype;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param definingClass {@code non-null;} the type of the defining class
+     * @param nat {@code non-null;} the name-and-type
+     */
+    /*package*/ CstBaseMethodRef(CstType definingClass, CstNat nat) {
+        super(definingClass, nat);
+
+        String descriptor = getNat().getDescriptor().getString();
+        this.prototype = Prototype.intern(descriptor);
+        this.instancePrototype = null;
+    }
+
+    /**
+     * Gets the raw prototype of this method. This doesn't include a
+     * {@code this} argument.
+     *
+     * @return {@code non-null;} the method prototype
+     */
+    public final Prototype getPrototype() {
+        return prototype;
+    }
+
+    /**
+     * Gets the prototype of this method as either a
+     * {@code static} or instance method. In the case of a
+     * {@code static} method, this is the same as the raw
+     * prototype. In the case of an instance method, this has an
+     * appropriately-typed {@code this} argument as the first
+     * one.
+     *
+     * @param isStatic whether the method should be considered static
+     * @return {@code non-null;} the method prototype
+     */
+    public final Prototype getPrototype(boolean isStatic) {
+        if (isStatic) {
+            return prototype;
+        } else {
+            if (instancePrototype == null) {
+                Type thisType = getDefiningClass().getClassType();
+                instancePrototype = prototype.withFirstParameter(thisType);
+            }
+            return instancePrototype;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected final int compareTo0(Constant other) {
+        int cmp = super.compareTo0(other);
+
+        if (cmp != 0) {
+            return cmp;
+        }
+
+        CstBaseMethodRef otherMethod = (CstBaseMethodRef) other;
+        return prototype.compareTo(otherMethod.prototype);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * In this case, this method returns the <i>return type</i> of this method.
+     *
+     * @return {@code non-null;} the method's return type
+     */
+    public final Type getType() {
+        return prototype.getReturnType();
+    }
+
+    /**
+     * Gets the number of words of parameters required by this
+     * method's descriptor. Since instances of this class have no way
+     * to know if they will be used in a {@code static} or
+     * instance context, one has to indicate this explicitly as an
+     * argument. This method is just a convenient shorthand for
+     * {@code getPrototype().getParameterTypes().getWordCount()},
+     * plus {@code 1} if the method is to be treated as an
+     * instance method.
+     *
+     * @param isStatic whether the method should be considered static
+     * @return {@code >= 0;} the argument word count
+     */
+    public final int getParameterWordCount(boolean isStatic) {
+        return getPrototype(isStatic).getParameterTypes().getWordCount();
+    }
+
+    /**
+     * Gets whether this is a reference to an instance initialization
+     * method. This is just a convenient shorthand for
+     * {@code getNat().isInstanceInit()}.
+     *
+     * @return {@code true} iff this is a reference to an
+     * instance initialization method
+     */
+    public final boolean isInstanceInit() {
+        return getNat().isInstanceInit();
+    }
+
+    /**
+     * Gets whether this is a reference to a class initialization
+     * method. This is just a convenient shorthand for
+     * {@code getNat().isClassInit()}.
+     *
+     * @return {@code true} iff this is a reference to an
+     * instance initialization method
+     */
+    public final boolean isClassInit() {
+        return getNat().isClassInit();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstBoolean.java b/dexgen/src/com/android/dexgen/rop/cst/CstBoolean.java
new file mode 100644
index 0000000..a7501e3
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstBoolean.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+
+/**
+ * Constants of type {@code boolean}.
+ */
+public final class CstBoolean
+        extends CstLiteral32 {
+    /** {@code non-null;} instance representing {@code false} */
+    public static final CstBoolean VALUE_FALSE = new CstBoolean(false);
+
+    /** {@code non-null;} instance representing {@code true} */
+    public static final CstBoolean VALUE_TRUE = new CstBoolean(true);
+
+    /**
+     * Makes an instance for the given value. This will return an
+     * already-allocated instance.
+     *
+     * @param value the {@code boolean} value
+     * @return {@code non-null;} the appropriate instance
+     */
+    public static CstBoolean make(boolean value) {
+        return value ? VALUE_TRUE : VALUE_FALSE;
+    }
+
+    /**
+     * Makes an instance for the given {@code int} value. This
+     * will return an already-allocated instance.
+     *
+     * @param value must be either {@code 0} or {@code 1}
+     * @return {@code non-null;} the appropriate instance
+     */
+    public static CstBoolean make(int value) {
+        if (value == 0) {
+            return VALUE_FALSE;
+        } else if (value == 1) {
+            return VALUE_TRUE;
+        } else {
+            throw new IllegalArgumentException("bogus value: " + value);
+        }
+    }
+
+    /**
+     * Constructs an instance. This constructor is private; use {@link #make}.
+     *
+     * @param value the {@code boolean} value
+     */
+    private CstBoolean(boolean value) {
+        super(value ? 1 : 0);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return getValue() ? "boolean{true}" : "boolean{false}";
+    }
+
+    /** {@inheritDoc} */
+    public Type getType() {
+        return Type.BOOLEAN;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "boolean";
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return getValue() ? "true" : "false";
+    }
+
+    /**
+     * Gets the {@code boolean} value.
+     *
+     * @return the value
+     */
+    public boolean getValue() {
+        return (getIntBits() == 0) ? false : true;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstByte.java b/dexgen/src/com/android/dexgen/rop/cst/CstByte.java
new file mode 100644
index 0000000..f9b97cb
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstByte.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Constants of type {@code byte}.
+ */
+public final class CstByte
+        extends CstLiteral32 {
+    /** {@code non-null;} the value {@code 0} as an instance of this class */
+    public static final CstByte VALUE_0 = make((byte) 0);
+
+    /**
+     * Makes an instance for the given value. This may (but does not
+     * necessarily) return an already-allocated instance.
+     *
+     * @param value the {@code byte} value
+     */
+    public static CstByte make(byte value) {
+        return new CstByte(value);
+    }
+
+    /**
+     * Makes an instance for the given {@code int} value. This
+     * may (but does not necessarily) return an already-allocated
+     * instance.
+     *
+     * @param value the value, which must be in range for a {@code byte}
+     * @return {@code non-null;} the appropriate instance
+     */
+    public static CstByte make(int value) {
+        byte cast = (byte) value;
+
+        if (cast != value) {
+            throw new IllegalArgumentException("bogus byte value: " +
+                    value);
+        }
+
+        return make(cast);
+    }
+
+    /**
+     * Constructs an instance. This constructor is private; use {@link #make}.
+     *
+     * @param value the {@code byte} value
+     */
+    private CstByte(byte value) {
+        super(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        int value = getIntBits();
+        return "byte{0x" + Hex.u1(value) + " / " + value + '}';
+    }
+
+    /** {@inheritDoc} */
+    public Type getType() {
+        return Type.BYTE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "byte";
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return Integer.toString(getIntBits());
+    }
+
+    /**
+     * Gets the {@code byte} value.
+     *
+     * @return the value
+     */
+    public byte getValue() {
+        return (byte) getIntBits();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstChar.java b/dexgen/src/com/android/dexgen/rop/cst/CstChar.java
new file mode 100644
index 0000000..d006525
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstChar.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Constants of type {@code char}.
+ */
+public final class CstChar
+        extends CstLiteral32 {
+    /** {@code non-null;} the value {@code 0} as an instance of this class */
+    public static final CstChar VALUE_0 = make((char) 0);
+
+    /**
+     * Makes an instance for the given value. This may (but does not
+     * necessarily) return an already-allocated instance.
+     *
+     * @param value the {@code char} value
+     */
+    public static CstChar make(char value) {
+        return new CstChar(value);
+    }
+
+    /**
+     * Makes an instance for the given {@code int} value. This
+     * may (but does not necessarily) return an already-allocated
+     * instance.
+     *
+     * @param value the value, which must be in range for a {@code char}
+     * @return {@code non-null;} the appropriate instance
+     */
+    public static CstChar make(int value) {
+        char cast = (char) value;
+
+        if (cast != value) {
+            throw new IllegalArgumentException("bogus char value: " +
+                    value);
+        }
+
+        return make(cast);
+    }
+
+    /**
+     * Constructs an instance. This constructor is private; use {@link #make}.
+     *
+     * @param value the {@code char} value
+     */
+    private CstChar(char value) {
+        super(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        int value = getIntBits();
+        return "char{0x" + Hex.u2(value) + " / " + value + '}';
+    }
+
+    /** {@inheritDoc} */
+    public Type getType() {
+        return Type.CHAR;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "char";
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return Integer.toString(getIntBits());
+    }
+
+    /**
+     * Gets the {@code char} value.
+     *
+     * @return the value
+     */
+    public char getValue() {
+        return (char) getIntBits();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstDouble.java b/dexgen/src/com/android/dexgen/rop/cst/CstDouble.java
new file mode 100644
index 0000000..84a53e6
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstDouble.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Constants of type {@code CONSTANT_Double_info}.
+ */
+public final class CstDouble
+        extends CstLiteral64 {
+    /** {@code non-null;} instance representing {@code 0} */
+    public static final CstDouble VALUE_0 =
+        new CstDouble(Double.doubleToLongBits(0.0));
+
+    /** {@code non-null;} instance representing {@code 1} */
+    public static final CstDouble VALUE_1 =
+        new CstDouble(Double.doubleToLongBits(1.0));
+
+    /**
+     * Makes an instance for the given value. This may (but does not
+     * necessarily) return an already-allocated instance.
+     *
+     * @param bits the {@code double} value as {@code long} bits
+     */
+    public static CstDouble make(long bits) {
+        /*
+         * Note: Javadoc notwithstanding, this implementation always
+         * allocates.
+         */
+        return new CstDouble(bits);
+    }
+
+    /**
+     * Constructs an instance. This constructor is private; use {@link #make}.
+     *
+     * @param bits the {@code double} value as {@code long} bits
+     */
+    private CstDouble(long bits) {
+        super(bits);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        long bits = getLongBits();
+        return "double{0x" + Hex.u8(bits) + " / " +
+            Double.longBitsToDouble(bits) + '}';
+    }
+
+    /** {@inheritDoc} */
+    public Type getType() {
+        return Type.DOUBLE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "double";
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return Double.toString(Double.longBitsToDouble(getLongBits()));
+    }
+
+    /**
+     * Gets the {@code double} value.
+     *
+     * @return the value
+     */
+    public double getValue() {
+        return Double.longBitsToDouble(getLongBits());
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstEnumRef.java b/dexgen/src/com/android/dexgen/rop/cst/CstEnumRef.java
new file mode 100644
index 0000000..d566946
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstEnumRef.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+
+/**
+ * Constant type to represent a reference to a particular constant
+ * value of an enumerated type.
+ */
+public final class CstEnumRef extends CstMemberRef {
+    /** {@code null-ok;} the corresponding field ref, lazily initialized */
+    private CstFieldRef fieldRef;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param nat {@code non-null;} the name-and-type; the defining class is derived
+     * from this
+     */
+    public CstEnumRef(CstNat nat) {
+        super(new CstType(nat.getFieldType()), nat);
+
+        fieldRef = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "enum";
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <b>Note:</b> This returns the enumerated type.
+     */
+    public Type getType() {
+        return getDefiningClass().getClassType();
+    }
+
+    /**
+     * Get a {@link CstFieldRef} that corresponds with this instance.
+     *
+     * @return {@code non-null;} the corresponding field reference
+     */
+    public CstFieldRef getFieldRef() {
+        if (fieldRef == null) {
+            fieldRef = new CstFieldRef(getDefiningClass(), getNat());
+        }
+
+        return fieldRef;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstFieldRef.java b/dexgen/src/com/android/dexgen/rop/cst/CstFieldRef.java
new file mode 100644
index 0000000..6a6218c
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstFieldRef.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+
+/**
+ * Constants of type {@code CONSTANT_Fieldref_info}.
+ */
+public final class CstFieldRef extends CstMemberRef {
+    /**
+     * Returns an instance of this class that represents the static
+     * field which should hold the class corresponding to a given
+     * primitive type. For example, if given {@link Type#INT}, this
+     * method returns an instance corresponding to the field
+     * {@code java.lang.Integer.TYPE}.
+     *
+     * @param primitiveType {@code non-null;} the primitive type
+     * @return {@code non-null;} the corresponding static field
+     */
+    public static CstFieldRef forPrimitiveType(Type primitiveType) {
+        return new CstFieldRef(CstType.forBoxedPrimitiveType(primitiveType),
+                CstNat.PRIMITIVE_TYPE_NAT);
+    }
+
+    /**
+     * Constructs an instance.
+     *
+     * @param definingClass {@code non-null;} the type of the defining class
+     * @param nat {@code non-null;} the name-and-type
+     */
+    public CstFieldRef(CstType definingClass, CstNat nat) {
+        super(definingClass, nat);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "field";
+    }
+
+    /**
+     * Returns the type of this field.
+     *
+     * @return {@code non-null;} the field's type
+     */
+    public Type getType() {
+        return getNat().getFieldType();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int compareTo0(Constant other) {
+        int cmp = super.compareTo0(other);
+
+        if (cmp != 0) {
+            return cmp;
+        }
+
+        CstFieldRef otherField = (CstFieldRef) other;
+        CstUtf8 thisDescriptor = getNat().getDescriptor();
+        CstUtf8 otherDescriptor = otherField.getNat().getDescriptor();
+        return thisDescriptor.compareTo(otherDescriptor);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstFloat.java b/dexgen/src/com/android/dexgen/rop/cst/CstFloat.java
new file mode 100644
index 0000000..6490f8f
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstFloat.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Constants of type {@code CONSTANT_Float_info}.
+ */
+public final class CstFloat
+        extends CstLiteral32 {
+    /** {@code non-null;} instance representing {@code 0} */
+    public static final CstFloat VALUE_0 = make(Float.floatToIntBits(0.0f));
+
+    /** {@code non-null;} instance representing {@code 1} */
+    public static final CstFloat VALUE_1 = make(Float.floatToIntBits(1.0f));
+
+    /** {@code non-null;} instance representing {@code 2} */
+    public static final CstFloat VALUE_2 = make(Float.floatToIntBits(2.0f));
+
+    /**
+     * Makes an instance for the given value. This may (but does not
+     * necessarily) return an already-allocated instance.
+     *
+     * @param bits the {@code float} value as {@code int} bits
+     */
+    public static CstFloat make(int bits) {
+        /*
+         * Note: Javadoc notwithstanding, this implementation always
+         * allocates.
+         */
+        return new CstFloat(bits);
+    }
+
+    /**
+     * Constructs an instance. This constructor is private; use {@link #make}.
+     *
+     * @param bits the {@code float} value as {@code int} bits
+     */
+    private CstFloat(int bits) {
+        super(bits);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        int bits = getIntBits();
+        return "float{0x" + Hex.u4(bits) + " / " +
+            Float.intBitsToFloat(bits) + '}';
+    }
+
+    /** {@inheritDoc} */
+    public Type getType() {
+        return Type.FLOAT;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "float";
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return Float.toString(Float.intBitsToFloat(getIntBits()));
+    }
+
+    /**
+     * Gets the {@code float} value.
+     *
+     * @return the value
+     */
+    public float getValue() {
+        return Float.intBitsToFloat(getIntBits());
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstInteger.java b/dexgen/src/com/android/dexgen/rop/cst/CstInteger.java
new file mode 100644
index 0000000..41ef0a6
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstInteger.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Constants of type {@code CONSTANT_Integer_info}.
+ */
+public final class CstInteger
+        extends CstLiteral32 {
+    /** {@code non-null;} array of cached instances */
+    private static final CstInteger[] cache = new CstInteger[511];
+
+    /** {@code non-null;} instance representing {@code -1} */
+    public static final CstInteger VALUE_M1 = make(-1);
+
+    /** {@code non-null;} instance representing {@code 0} */
+    public static final CstInteger VALUE_0 = make(0);
+
+    /** {@code non-null;} instance representing {@code 1} */
+    public static final CstInteger VALUE_1 = make(1);
+
+    /** {@code non-null;} instance representing {@code 2} */
+    public static final CstInteger VALUE_2 = make(2);
+
+    /** {@code non-null;} instance representing {@code 3} */
+    public static final CstInteger VALUE_3 = make(3);
+
+    /** {@code non-null;} instance representing {@code 4} */
+    public static final CstInteger VALUE_4 = make(4);
+
+    /** {@code non-null;} instance representing {@code 5} */
+    public static final CstInteger VALUE_5 = make(5);
+
+    /**
+     * Makes an instance for the given value. This may (but does not
+     * necessarily) return an already-allocated instance.
+     *
+     * @param value the {@code int} value
+     * @return {@code non-null;} the appropriate instance
+     */
+    public static CstInteger make(int value) {
+        /*
+         * Note: No need to synchronize, since we don't make any sort
+         * of guarantee about ==, and it's okay to overwrite existing
+         * entries too.
+         */
+        int idx = (value & 0x7fffffff) % cache.length;
+        CstInteger obj = cache[idx];
+
+        if ((obj != null) && (obj.getValue() == value)) {
+            return obj;
+        }
+
+        obj = new CstInteger(value);
+        cache[idx] = obj;
+        return obj;
+    }
+
+    /**
+     * Constructs an instance. This constructor is private; use {@link #make}.
+     *
+     * @param value the {@code int} value
+     */
+    private CstInteger(int value) {
+        super(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        int value = getIntBits();
+        return "int{0x" + Hex.u4(value) + " / " + value + '}';
+    }
+
+    /** {@inheritDoc} */
+    public Type getType() {
+        return Type.INT;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "int";
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return Integer.toString(getIntBits());
+    }
+
+    /**
+     * Gets the {@code int} value.
+     *
+     * @return the value
+     */
+    public int getValue() {
+        return getIntBits();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstInterfaceMethodRef.java b/dexgen/src/com/android/dexgen/rop/cst/CstInterfaceMethodRef.java
new file mode 100644
index 0000000..c514b84
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstInterfaceMethodRef.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+/**
+ * Constants of type {@code CONSTANT_InterfaceMethodref_info}.
+ */
+public final class CstInterfaceMethodRef
+        extends CstBaseMethodRef {
+    /**
+     * {@code null-ok;} normal {@link CstMethodRef} that corresponds to this
+     * instance, if calculated
+     */
+    private CstMethodRef methodRef;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param definingClass {@code non-null;} the type of the defining class
+     * @param nat {@code non-null;} the name-and-type
+     */
+    public CstInterfaceMethodRef(CstType definingClass, CstNat nat) {
+        super(definingClass, nat);
+        methodRef = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "ifaceMethod";
+    }
+
+    /**
+     * Gets a normal (non-interface) {@link CstMethodRef} that corresponds to
+     * this instance.
+     *
+     * @return {@code non-null;} an appropriate instance
+     */
+    public CstMethodRef toMethodRef() {
+        if (methodRef == null) {
+            methodRef = new CstMethodRef(getDefiningClass(), getNat());
+        }
+
+        return methodRef;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstKnownNull.java b/dexgen/src/com/android/dexgen/rop/cst/CstKnownNull.java
new file mode 100644
index 0000000..58d6933
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstKnownNull.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+
+/**
+ * Constant type to represent a known-{@code null} value.
+ */
+public final class CstKnownNull extends CstLiteralBits {
+    /** {@code non-null;} unique instance of this class */
+    public static final CstKnownNull THE_ONE = new CstKnownNull();
+
+    /**
+     * Constructs an instance. This class is not publicly instantiable. Use
+     * {@link #THE_ONE}.
+     */
+    private CstKnownNull() {
+        // This space intentionally left blank.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        return (other instanceof CstKnownNull);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return 0x4466757a;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int compareTo0(Constant other) {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "known-null";
+    }
+
+    /** {@inheritDoc} */
+    public Type getType() {
+        return Type.KNOWN_NULL;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "known-null";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCategory2() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return "null";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean fitsInInt() {
+        // See comment in getIntBits().
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * As "literal bits," a known-null is always represented as the
+     * number zero.
+     */
+    @Override
+    public int getIntBits() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * As "literal bits," a known-null is always represented as the
+     * number zero.
+     */
+    @Override
+    public long getLongBits() {
+        return 0;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstLiteral32.java b/dexgen/src/com/android/dexgen/rop/cst/CstLiteral32.java
new file mode 100644
index 0000000..f7f9199
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstLiteral32.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+/**
+ * Constants which are literal 32-bit values of some sort.
+ */
+public abstract class CstLiteral32
+        extends CstLiteralBits {
+    /** the value as {@code int} bits */
+    private final int bits;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param bits the value as {@code int} bits
+     */
+    /*package*/ CstLiteral32(int bits) {
+        this.bits = bits;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final boolean equals(Object other) {
+        return (other != null) &&
+            (getClass() == other.getClass()) &&
+            bits == ((CstLiteral32) other).bits;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final int hashCode() {
+        return bits;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int compareTo0(Constant other) {
+        int otherBits = ((CstLiteral32) other).bits;
+
+        if (bits < otherBits) {
+            return -1;
+        } else if (bits > otherBits) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final boolean isCategory2() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final boolean fitsInInt() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final int getIntBits() {
+        return bits;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final long getLongBits() {
+        return (long) bits;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstLiteral64.java b/dexgen/src/com/android/dexgen/rop/cst/CstLiteral64.java
new file mode 100644
index 0000000..0bf3152
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstLiteral64.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+/**
+ * Constants which are literal 64-bit values of some sort.
+ */
+public abstract class CstLiteral64
+        extends CstLiteralBits {
+    /** the value as {@code long} bits */
+    private final long bits;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param bits the value as {@code long} bits
+     */
+    /*package*/ CstLiteral64(long bits) {
+        this.bits = bits;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final boolean equals(Object other) {
+        return (other != null) &&
+            (getClass() == other.getClass()) &&
+            bits == ((CstLiteral64) other).bits;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final int hashCode() {
+        return (int) bits ^ (int) (bits >> 32);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int compareTo0(Constant other) {
+        long otherBits = ((CstLiteral64) other).bits;
+
+        if (bits < otherBits) {
+            return -1;
+        } else if (bits > otherBits) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final boolean isCategory2() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final boolean fitsInInt() {
+        return (int) bits == bits;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final int getIntBits() {
+        return (int) bits;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final long getLongBits() {
+        return bits;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstLiteralBits.java b/dexgen/src/com/android/dexgen/rop/cst/CstLiteralBits.java
new file mode 100644
index 0000000..97e8bd1
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstLiteralBits.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+/**
+ * Constants which are literal bitwise values of some sort.
+ */
+public abstract class CstLiteralBits
+        extends TypedConstant {
+    /**
+     * Returns whether or not this instance's value may be accurately
+     * represented as an {@code int}. The rule is that if there
+     * is an {@code int} which may be sign-extended to yield this
+     * instance's value, then this method returns {@code true}.
+     * Otherwise, it returns {@code false}.
+     *
+     * @return {@code true} iff this instance fits in an {@code int}
+     */
+    public abstract boolean fitsInInt();
+
+    /**
+     * Gets the value as {@code int} bits. If this instance contains
+     * more bits than fit in an {@code int}, then this returns only
+     * the low-order bits.
+     *
+     * @return the bits
+     */
+    public abstract int getIntBits();
+
+    /**
+     * Gets the value as {@code long} bits. If this instance contains
+     * fewer bits than fit in a {@code long}, then the result of this
+     * method is the sign extension of the value.
+     *
+     * @return the bits
+     */
+    public abstract long getLongBits();
+
+    /**
+     * Returns true if this value can fit in 16 bits with sign-extension.
+     *
+     * @return true if the sign-extended lower 16 bits are the same as
+     * the value.
+     */
+    public boolean fitsIn16Bits() {
+        if (! fitsInInt()) {
+            return false;
+        }
+
+        int bits = getIntBits();
+        return (short) bits == bits;
+    }
+
+    /**
+     * Returns true if this value can fit in 8 bits with sign-extension.
+     *
+     * @return true if the sign-extended lower 8 bits are the same as
+     * the value.
+     */
+    public boolean fitsIn8Bits() {
+        if (! fitsInInt()) {
+            return false;
+        }
+
+        int bits = getIntBits();
+        return (byte) bits == bits;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstLong.java b/dexgen/src/com/android/dexgen/rop/cst/CstLong.java
new file mode 100644
index 0000000..f737094
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstLong.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Constants of type {@code CONSTANT_Long_info}.
+ */
+public final class CstLong
+        extends CstLiteral64 {
+    /** {@code non-null;} instance representing {@code 0} */
+    public static final CstLong VALUE_0 = make(0);
+
+    /** {@code non-null;} instance representing {@code 1} */
+    public static final CstLong VALUE_1 = make(1);
+
+    /**
+     * Makes an instance for the given value. This may (but does not
+     * necessarily) return an already-allocated instance.
+     *
+     * @param value the {@code long} value
+     */
+    public static CstLong make(long value) {
+        /*
+         * Note: Javadoc notwithstanding, this implementation always
+         * allocates.
+         */
+        return new CstLong(value);
+    }
+
+    /**
+     * Constructs an instance. This constructor is private; use {@link #make}.
+     *
+     * @param value the {@code long} value
+     */
+    private CstLong(long value) {
+        super(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        long value = getLongBits();
+        return "long{0x" + Hex.u8(value) + " / " + value + '}';
+    }
+
+    /** {@inheritDoc} */
+    public Type getType() {
+        return Type.LONG;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "long";
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return Long.toString(getLongBits());
+    }
+
+    /**
+     * Gets the {@code long} value.
+     *
+     * @return the value
+     */
+    public long getValue() {
+        return getLongBits();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstMemberRef.java b/dexgen/src/com/android/dexgen/rop/cst/CstMemberRef.java
new file mode 100644
index 0000000..5abca4b
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstMemberRef.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+/**
+ * Constants of type {@code CONSTANT_*ref_info}.
+ */
+public abstract class CstMemberRef extends TypedConstant {
+    /** {@code non-null;} the type of the defining class */
+    private final CstType definingClass;
+
+    /** {@code non-null;} the name-and-type */
+    private final CstNat nat;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param definingClass {@code non-null;} the type of the defining class
+     * @param nat {@code non-null;} the name-and-type
+     */
+    /*package*/ CstMemberRef(CstType definingClass, CstNat nat) {
+        if (definingClass == null) {
+            throw new NullPointerException("definingClass == null");
+        }
+
+        if (nat == null) {
+            throw new NullPointerException("nat == null");
+        }
+
+        this.definingClass = definingClass;
+        this.nat = nat;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final boolean equals(Object other) {
+        if ((other == null) || (getClass() != other.getClass())) {
+            return false;
+        }
+
+        CstMemberRef otherRef = (CstMemberRef) other;
+        return definingClass.equals(otherRef.definingClass) &&
+            nat.equals(otherRef.nat);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final int hashCode() {
+        return (definingClass.hashCode() * 31) ^ nat.hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><b>Note:</b> This implementation just compares the defining
+     * class and name, and it is up to subclasses to compare the rest
+     * after calling {@code super.compareTo0()}.</p>
+     */
+    @Override
+    protected int compareTo0(Constant other) {
+        CstMemberRef otherMember = (CstMemberRef) other;
+        int cmp = definingClass.compareTo(otherMember.definingClass);
+
+        if (cmp != 0) {
+            return cmp;
+        }
+
+        CstUtf8 thisName = nat.getName();
+        CstUtf8 otherName = otherMember.nat.getName();
+
+        return thisName.compareTo(otherName);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final String toString() {
+        return typeName() + '{' + toHuman() + '}';
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final boolean isCategory2() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public final String toHuman() {
+        return definingClass.toHuman() + '.' + nat.toHuman();
+    }
+
+    /**
+     * Gets the type of the defining class.
+     *
+     * @return {@code non-null;} the type of defining class
+     */
+    public final CstType getDefiningClass() {
+        return definingClass;
+    }
+
+    /**
+     * Gets the defining name-and-type.
+     *
+     * @return {@code non-null;} the name-and-type
+     */
+    public final CstNat getNat() {
+        return nat;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstMethodRef.java b/dexgen/src/com/android/dexgen/rop/cst/CstMethodRef.java
new file mode 100644
index 0000000..0bf3851
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstMethodRef.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+/**
+ * Constants of type {@code CONSTANT_Methodref_info}.
+ */
+public final class CstMethodRef
+        extends CstBaseMethodRef {
+    /**
+     * Constructs an instance.
+     *
+     * @param definingClass {@code non-null;} the type of the defining class
+     * @param nat {@code non-null;} the name-and-type
+     */
+    public CstMethodRef(CstType definingClass, CstNat nat) {
+        super(definingClass, nat);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "method";
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstNat.java b/dexgen/src/com/android/dexgen/rop/cst/CstNat.java
new file mode 100644
index 0000000..34d2bfc
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstNat.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+
+/**
+ * Constants of type {@code CONSTANT_NameAndType_info}.
+ */
+public final class CstNat extends Constant {
+    /**
+     * {@code non-null;} the instance for name {@code TYPE} and descriptor
+     * {@code java.lang.Class}, which is useful when dealing with
+     * wrapped primitives
+     */
+    public static final CstNat PRIMITIVE_TYPE_NAT =
+        new CstNat(new CstUtf8("TYPE"),
+                   new CstUtf8("Ljava/lang/Class;"));
+
+    /** {@code non-null;} the name */
+    private final CstUtf8 name;
+
+    /** {@code non-null;} the descriptor (type) */
+    private final CstUtf8 descriptor;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param name {@code non-null;} the name
+     * @param descriptor {@code non-null;} the descriptor
+     */
+    public CstNat(CstUtf8 name, CstUtf8 descriptor) {
+        if (name == null) {
+            throw new NullPointerException("name == null");
+        }
+
+        if (descriptor == null) {
+            throw new NullPointerException("descriptor == null");
+        }
+
+        this.name = name;
+        this.descriptor = descriptor;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof CstNat)) {
+            return false;
+        }
+
+        CstNat otherNat = (CstNat) other;
+        return name.equals(otherNat.name) &&
+            descriptor.equals(otherNat.descriptor);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return (name.hashCode() * 31) ^ descriptor.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int compareTo0(Constant other) {
+        CstNat otherNat = (CstNat) other;
+        int cmp = name.compareTo(otherNat.name);
+
+        if (cmp != 0) {
+            return cmp;
+        }
+
+        return descriptor.compareTo(otherNat.descriptor);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "nat{" + toHuman() + '}';
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "nat";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCategory2() {
+        return false;
+    }
+
+    /**
+     * Gets the name.
+     *
+     * @return {@code non-null;} the name
+     */
+    public CstUtf8 getName() {
+        return name;
+    }
+
+    /**
+     * Gets the descriptor.
+     *
+     * @return {@code non-null;} the descriptor
+     */
+    public CstUtf8 getDescriptor() {
+        return descriptor;
+    }
+
+    /**
+     * Returns an unadorned but human-readable version of the name-and-type
+     * value.
+     *
+     * @return {@code non-null;} the human form
+     */
+    public String toHuman() {
+        return name.toHuman() + ':' + descriptor.toHuman();
+    }
+
+    /**
+     * Gets the field type corresponding to this instance's descriptor.
+     * This method is only valid to call if the descriptor in fact describes
+     * a field (and not a method).
+     *
+     * @return {@code non-null;} the field type
+     */
+    public Type getFieldType() {
+        return Type.intern(descriptor.getString());
+    }
+
+    /**
+     * Gets whether this instance has the name of a standard instance
+     * initialization method. This is just a convenient shorthand for
+     * {@code getName().getString().equals("<init>")}.
+     *
+     * @return {@code true} iff this is a reference to an
+     * instance initialization method
+     */
+    public final boolean isInstanceInit() {
+        return name.getString().equals("<init>");
+    }
+
+    /**
+     * Gets whether this instance has the name of a standard class
+     * initialization method. This is just a convenient shorthand for
+     * {@code getName().getString().equals("<clinit>")}.
+     *
+     * @return {@code true} iff this is a reference to an
+     * instance initialization method
+     */
+    public final boolean isClassInit() {
+        return name.getString().equals("<clinit>");
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstShort.java b/dexgen/src/com/android/dexgen/rop/cst/CstShort.java
new file mode 100644
index 0000000..c81a589
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstShort.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Constants of type {@code short}.
+ */
+public final class CstShort
+        extends CstLiteral32 {
+    /** {@code non-null;} the value {@code 0} as an instance of this class */
+    public static final CstShort VALUE_0 = make((short) 0);
+
+    /**
+     * Makes an instance for the given value. This may (but does not
+     * necessarily) return an already-allocated instance.
+     *
+     * @param value the {@code short} value
+     * @return {@code non-null;} the appropriate instance
+     */
+    public static CstShort make(short value) {
+        return new CstShort(value);
+    }
+
+    /**
+     * Makes an instance for the given {@code int} value. This
+     * may (but does not necessarily) return an already-allocated
+     * instance.
+     *
+     * @param value the value, which must be in range for a {@code short}
+     * @return {@code non-null;} the appropriate instance
+     */
+    public static CstShort make(int value) {
+        short cast = (short) value;
+
+        if (cast != value) {
+            throw new IllegalArgumentException("bogus short value: " +
+                    value);
+        }
+
+        return make(cast);
+    }
+
+    /**
+     * Constructs an instance. This constructor is private; use {@link #make}.
+     *
+     * @param value the {@code short} value
+     */
+    private CstShort(short value) {
+        super(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        int value = getIntBits();
+        return "short{0x" + Hex.u2(value) + " / " + value + '}';
+    }
+
+    /** {@inheritDoc} */
+    public Type getType() {
+        return Type.SHORT;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "short";
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return Integer.toString(getIntBits());
+    }
+
+    /**
+     * Gets the {@code short} value.
+     *
+     * @return the value
+     */
+    public short getValue() {
+        return (short) getIntBits();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstString.java b/dexgen/src/com/android/dexgen/rop/cst/CstString.java
new file mode 100644
index 0000000..a2babf4
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstString.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+
+/**
+ * Constants of type {@code CONSTANT_String_info}.
+ */
+public final class CstString
+        extends TypedConstant {
+    /** {@code non-null;} the string value */
+    private final CstUtf8 string;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param string {@code non-null;} the string value
+     */
+    public CstString(CstUtf8 string) {
+        if (string == null) {
+            throw new NullPointerException("string == null");
+        }
+
+        this.string = string;
+    }
+
+    /**
+     * Constructs an instance.
+     *
+     * @param string {@code non-null;} the string value
+     */
+    public CstString(String string) {
+        this(new CstUtf8(string));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof CstString)) {
+            return false;
+        }
+
+        return string.equals(((CstString) other).string);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return string.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int compareTo0(Constant other) {
+        return string.compareTo(((CstString) other).string);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "string{" + toHuman() + '}';
+    }
+
+    /** {@inheritDoc} */
+    public Type getType() {
+        return Type.STRING;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "string";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCategory2() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return string.toQuoted();
+    }
+
+    /**
+     * Gets the string value.
+     *
+     * @return {@code non-null;} the string value
+     */
+    public CstUtf8 getString() {
+        return string;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstType.java b/dexgen/src/com/android/dexgen/rop/cst/CstType.java
new file mode 100644
index 0000000..3a92e7a
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstType.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+
+import java.util.HashMap;
+
+/**
+ * Constants that represent an arbitrary type (reference or primitive).
+ */
+public final class CstType extends TypedConstant {
+    /** {@code non-null;} map of interned types */
+    private static final HashMap<Type, CstType> interns =
+        new HashMap<Type, CstType>(100);
+
+    /** {@code non-null;} instance corresponding to the class {@code Object} */
+    public static final CstType OBJECT = intern(Type.OBJECT);
+
+    /** {@code non-null;} instance corresponding to the class {@code Boolean} */
+    public static final CstType BOOLEAN = intern(Type.BOOLEAN_CLASS);
+
+    /** {@code non-null;} instance corresponding to the class {@code Byte} */
+    public static final CstType BYTE = intern(Type.BYTE_CLASS);
+
+    /** {@code non-null;} instance corresponding to the class {@code Character} */
+    public static final CstType CHARACTER = intern(Type.CHARACTER_CLASS);
+
+    /** {@code non-null;} instance corresponding to the class {@code Double} */
+    public static final CstType DOUBLE = intern(Type.DOUBLE_CLASS);
+
+    /** {@code non-null;} instance corresponding to the class {@code Float} */
+    public static final CstType FLOAT = intern(Type.FLOAT_CLASS);
+
+    /** {@code non-null;} instance corresponding to the class {@code Long} */
+    public static final CstType LONG = intern(Type.LONG_CLASS);
+
+    /** {@code non-null;} instance corresponding to the class {@code Integer} */
+    public static final CstType INTEGER = intern(Type.INTEGER_CLASS);
+
+    /** {@code non-null;} instance corresponding to the class {@code Short} */
+    public static final CstType SHORT = intern(Type.SHORT_CLASS);
+
+    /** {@code non-null;} instance corresponding to the class {@code Void} */
+    public static final CstType VOID = intern(Type.VOID_CLASS);
+
+    /** {@code non-null;} instance corresponding to the type {@code boolean[]} */
+    public static final CstType BOOLEAN_ARRAY = intern(Type.BOOLEAN_ARRAY);
+
+    /** {@code non-null;} instance corresponding to the type {@code byte[]} */
+    public static final CstType BYTE_ARRAY = intern(Type.BYTE_ARRAY);
+
+    /** {@code non-null;} instance corresponding to the type {@code char[]} */
+    public static final CstType CHAR_ARRAY = intern(Type.CHAR_ARRAY);
+
+    /** {@code non-null;} instance corresponding to the type {@code double[]} */
+    public static final CstType DOUBLE_ARRAY = intern(Type.DOUBLE_ARRAY);
+
+    /** {@code non-null;} instance corresponding to the type {@code float[]} */
+    public static final CstType FLOAT_ARRAY = intern(Type.FLOAT_ARRAY);
+
+    /** {@code non-null;} instance corresponding to the type {@code long[]} */
+    public static final CstType LONG_ARRAY = intern(Type.LONG_ARRAY);
+
+    /** {@code non-null;} instance corresponding to the type {@code int[]} */
+    public static final CstType INT_ARRAY = intern(Type.INT_ARRAY);
+
+    /** {@code non-null;} instance corresponding to the type {@code short[]} */
+    public static final CstType SHORT_ARRAY = intern(Type.SHORT_ARRAY);
+
+    /** {@code non-null;} the underlying type */
+    private final Type type;
+
+    /**
+     * {@code null-ok;} the type descriptor corresponding to this instance, if
+     * calculated
+     */
+    private CstUtf8 descriptor;
+
+    /**
+     * Returns an instance of this class that represents the wrapper
+     * class corresponding to a given primitive type. For example, if
+     * given {@link Type#INT}, this method returns the class reference
+     * {@code java.lang.Integer}.
+     *
+     * @param primitiveType {@code non-null;} the primitive type
+     * @return {@code non-null;} the corresponding wrapper class
+     */
+    public static CstType forBoxedPrimitiveType(Type primitiveType) {
+        switch (primitiveType.getBasicType()) {
+            case Type.BT_BOOLEAN: return BOOLEAN;
+            case Type.BT_BYTE:    return BYTE;
+            case Type.BT_CHAR:    return CHARACTER;
+            case Type.BT_DOUBLE:  return DOUBLE;
+            case Type.BT_FLOAT:   return FLOAT;
+            case Type.BT_INT:     return INTEGER;
+            case Type.BT_LONG:    return LONG;
+            case Type.BT_SHORT:   return SHORT;
+            case Type.BT_VOID:    return VOID;
+        }
+
+        throw new IllegalArgumentException("not primitive: " + primitiveType);
+    }
+
+    /**
+     * Returns an interned instance of this class for the given type.
+     *
+     * @param type {@code non-null;} the underlying type
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public static CstType intern(Type type) {
+        CstType cst = interns.get(type);
+
+        if (cst == null) {
+            cst = new CstType(type);
+            interns.put(type, cst);
+        }
+
+        return cst;
+    }
+
+    /**
+     * Constructs an instance.
+     *
+     * @param type {@code non-null;} the underlying type
+     */
+    public CstType(Type type) {
+        if (type == null) {
+            throw new NullPointerException("type == null");
+        }
+
+        if (type == type.KNOWN_NULL) {
+            throw new UnsupportedOperationException(
+                    "KNOWN_NULL is not representable");
+        }
+
+        this.type = type;
+        this.descriptor = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof CstType)) {
+            return false;
+        }
+
+        return type == ((CstType) other).type;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return type.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int compareTo0(Constant other) {
+        String thisDescriptor = type.getDescriptor();
+        String otherDescriptor = ((CstType) other).type.getDescriptor();
+        return thisDescriptor.compareTo(otherDescriptor);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "type{" + toHuman() + '}';
+    }
+
+    /** {@inheritDoc} */
+    public Type getType() {
+        return Type.CLASS;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "type";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCategory2() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        return type.toHuman();
+    }
+
+    /**
+     * Gets the underlying type (as opposed to the type corresponding
+     * to this instance as a constant, which is always
+     * {@code Class}).
+     *
+     * @return {@code non-null;} the type corresponding to the name
+     */
+    public Type getClassType() {
+        return type;
+    }
+
+    /**
+     * Gets the type descriptor for this instance.
+     *
+     * @return {@code non-null;} the descriptor
+     */
+    public CstUtf8 getDescriptor() {
+        if (descriptor == null) {
+            descriptor = new CstUtf8(type.getDescriptor());
+        }
+
+        return descriptor;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstUtf8.java b/dexgen/src/com/android/dexgen/rop/cst/CstUtf8.java
new file mode 100644
index 0000000..161a57b
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/CstUtf8.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.util.ByteArray;
+import com.android.dexgen.util.Hex;
+
+/**
+ * Constants of type {@code CONSTANT_Utf8_info}.
+ */
+public final class CstUtf8 extends Constant {
+    /**
+     * {@code non-null;} instance representing {@code ""}, that is, the
+     * empty string
+     */
+    public static final CstUtf8 EMPTY_STRING = new CstUtf8("");
+
+    /** {@code non-null;} the UTF-8 value as a string */
+    private final String string;
+
+    /** {@code non-null;} the UTF-8 value as bytes */
+    private final ByteArray bytes;
+
+    /**
+     * Converts a string into its Java-style UTF-8 form. Java-style UTF-8
+     * differs from normal UTF-8 in the handling of character '\0' and
+     * surrogate pairs.
+     *
+     * @param string {@code non-null;} the string to convert
+     * @return {@code non-null;} the UTF-8 bytes for it
+     */
+    public static byte[] stringToUtf8Bytes(String string) {
+        int len = string.length();
+        byte[] bytes = new byte[len * 3]; // Avoid having to reallocate.
+        int outAt = 0;
+
+        for (int i = 0; i < len; i++) {
+            char c = string.charAt(i);
+            if ((c != 0) && (c < 0x80)) {
+                bytes[outAt] = (byte) c;
+                outAt++;
+            } else if (c < 0x800) {
+                bytes[outAt] = (byte) (((c >> 6) & 0x1f) | 0xc0);
+                bytes[outAt + 1] = (byte) ((c & 0x3f) | 0x80);
+                outAt += 2;
+            } else {
+                bytes[outAt] = (byte) (((c >> 12) & 0x0f) | 0xe0);
+                bytes[outAt + 1] = (byte) (((c >> 6) & 0x3f) | 0x80);
+                bytes[outAt + 2] = (byte) ((c & 0x3f) | 0x80);
+                outAt += 3;
+            }
+        }
+
+        byte[] result = new byte[outAt];
+        System.arraycopy(bytes, 0, result, 0, outAt);
+        return result;
+    }
+
+    /**
+     * Converts an array of UTF-8 bytes into a string.
+     *
+     * @param bytes {@code non-null;} the bytes to convert
+     * @return {@code non-null;} the converted string
+     */
+    public static String utf8BytesToString(ByteArray bytes) {
+        int length = bytes.size();
+        char[] chars = new char[length]; // This is sized to avoid a realloc.
+        int outAt = 0;
+
+        for (int at = 0; length > 0; /*at*/) {
+            int v0 = bytes.getUnsignedByte(at);
+            char out;
+            switch (v0 >> 4) {
+                case 0x00: case 0x01: case 0x02: case 0x03:
+                case 0x04: case 0x05: case 0x06: case 0x07: {
+                    // 0XXXXXXX -- single-byte encoding
+                    length--;
+                    if (v0 == 0) {
+                        // A single zero byte is illegal.
+                        return throwBadUtf8(v0, at);
+                    }
+                    out = (char) v0;
+                    at++;
+                    break;
+                }
+                case 0x0c: case 0x0d: {
+                    // 110XXXXX -- two-byte encoding
+                    length -= 2;
+                    if (length < 0) {
+                        return throwBadUtf8(v0, at);
+                    }
+                    int v1 = bytes.getUnsignedByte(at + 1);
+                    if ((v1 & 0xc0) != 0x80) {
+                        return throwBadUtf8(v1, at + 1);
+                    }
+                    int value = ((v0 & 0x1f) << 6) | (v1 & 0x3f);
+                    if ((value != 0) && (value < 0x80)) {
+                        /*
+                         * This should have been represented with
+                         * one-byte encoding.
+                         */
+                        return throwBadUtf8(v1, at + 1);
+                    }
+                    out = (char) value;
+                    at += 2;
+                    break;
+                }
+                case 0x0e: {
+                    // 1110XXXX -- three-byte encoding
+                    length -= 3;
+                    if (length < 0) {
+                        return throwBadUtf8(v0, at);
+                    }
+                    int v1 = bytes.getUnsignedByte(at + 1);
+                    if ((v1 & 0xc0) != 0x80) {
+                        return throwBadUtf8(v1, at + 1);
+                    }
+                    int v2 = bytes.getUnsignedByte(at + 2);
+                    if ((v1 & 0xc0) != 0x80) {
+                        return throwBadUtf8(v2, at + 2);
+                    }
+                    int value = ((v0 & 0x0f) << 12) | ((v1 & 0x3f) << 6) |
+                        (v2 & 0x3f);
+                    if (value < 0x800) {
+                        /*
+                         * This should have been represented with one- or
+                         * two-byte encoding.
+                         */
+                        return throwBadUtf8(v2, at + 2);
+                    }
+                    out = (char) value;
+                    at += 3;
+                    break;
+                }
+                default: {
+                    // 10XXXXXX, 1111XXXX -- illegal
+                    return throwBadUtf8(v0, at);
+                }
+            }
+            chars[outAt] = out;
+            outAt++;
+        }
+
+        return new String(chars, 0, outAt);
+    }
+
+    /**
+     * Helper for {@link #utf8BytesToString}, which throws the right
+     * exception for a bogus utf-8 byte.
+     *
+     * @param value the byte value
+     * @param offset the file offset
+     * @return never
+     * @throws IllegalArgumentException always thrown
+     */
+    private static String throwBadUtf8(int value, int offset) {
+        throw new IllegalArgumentException("bad utf-8 byte " + Hex.u1(value) +
+                                           " at offset " + Hex.u4(offset));
+    }
+
+    /**
+     * Constructs an instance from a {@code String}.
+     *
+     * @param string {@code non-null;} the UTF-8 value as a string
+     */
+    public CstUtf8(String string) {
+        if (string == null) {
+            throw new NullPointerException("string == null");
+        }
+
+        this.string = string.intern();
+        this.bytes = new ByteArray(stringToUtf8Bytes(string));
+    }
+
+    /**
+     * Constructs an instance from some UTF-8 bytes.
+     *
+     * @param bytes {@code non-null;} array of the UTF-8 bytes
+     */
+    public CstUtf8(ByteArray bytes) {
+        if (bytes == null) {
+            throw new NullPointerException("bytes == null");
+        }
+
+        this.bytes = bytes;
+        this.string = utf8BytesToString(bytes).intern();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof CstUtf8)) {
+            return false;
+        }
+
+        return string.equals(((CstUtf8) other).string);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return string.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int compareTo0(Constant other) {
+        return string.compareTo(((CstUtf8) other).string);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "utf8{\"" + toHuman() + "\"}";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String typeName() {
+        return "utf8";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCategory2() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        int len = string.length();
+        StringBuilder sb = new StringBuilder(len * 3 / 2);
+
+        for (int i = 0; i < len; i++) {
+            char c = string.charAt(i);
+            if ((c >= ' ') && (c < 0x7f)) {
+                if ((c == '\'') || (c == '\"') || (c == '\\')) {
+                    sb.append('\\');
+                }
+                sb.append(c);
+            } else if (c <= 0x7f) {
+                switch (c) {
+                    case '\n': sb.append("\\n"); break;
+                    case '\r': sb.append("\\r"); break;
+                    case '\t': sb.append("\\t"); break;
+                    default: {
+                        /*
+                         * Represent the character as an octal escape.
+                         * If the next character is a valid octal
+                         * digit, disambiguate by using the
+                         * three-digit form.
+                         */
+                        char nextChar =
+                            (i < (len - 1)) ? string.charAt(i + 1) : 0;
+                        boolean displayZero =
+                            (nextChar >= '0') && (nextChar <= '7');
+                        sb.append('\\');
+                        for (int shift = 6; shift >= 0; shift -= 3) {
+                            char outChar = (char) (((c >> shift) & 7) + '0');
+                            if ((outChar != '0') || displayZero) {
+                                sb.append(outChar);
+                                displayZero = true;
+                            }
+                        }
+                        if (! displayZero) {
+                            // Ironic edge case: The original value was 0.
+                            sb.append('0');
+                        }
+                        break;
+                    }
+                }
+            } else {
+                sb.append("\\u");
+                sb.append(Character.forDigit(c >> 12, 16));
+                sb.append(Character.forDigit((c >> 8) & 0x0f, 16));
+                sb.append(Character.forDigit((c >> 4) & 0x0f, 16));
+                sb.append(Character.forDigit(c & 0x0f, 16));
+            }
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Gets the value as a human-oriented string, surrounded by double
+     * quotes.
+     *
+     * @return {@code non-null;} the quoted string
+     */
+    public String toQuoted() {
+        return '\"' + toHuman() + '\"';
+    }
+
+    /**
+     * Gets the value as a human-oriented string, surrounded by double
+     * quotes, but ellipsizes the result if it is longer than the given
+     * maximum length
+     *
+     * @param maxLength {@code >= 5;} the maximum length of the string to return
+     * @return {@code non-null;} the quoted string
+     */
+    public String toQuoted(int maxLength) {
+        String string = toHuman();
+        int length = string.length();
+        String ellipses;
+
+        if (length <= (maxLength - 2)) {
+            ellipses = "";
+        } else {
+            string = string.substring(0, maxLength - 5);
+            ellipses = "...";
+        }
+
+        return '\"' + string + ellipses + '\"';
+    }
+
+    /**
+     * Gets the UTF-8 value as a string.
+     * The returned string is always already interned.
+     *
+     * @return {@code non-null;} the UTF-8 value as a string
+     */
+    public String getString() {
+        return string;
+    }
+
+    /**
+     * Gets the UTF-8 value as UTF-8 encoded bytes.
+     *
+     * @return {@code non-null;} an array of the UTF-8 bytes
+     */
+    public ByteArray getBytes() {
+        return bytes;
+    }
+
+    /**
+     * Gets the size of this instance as UTF-8 code points. That is,
+     * get the number of bytes in the UTF-8 encoding of this instance.
+     *
+     * @return {@code >= 0;} the UTF-8 size
+     */
+    public int getUtf8Size() {
+        return bytes.size();
+    }
+
+    /**
+     * Gets the size of this instance as UTF-16 code points. That is,
+     * get the number of 16-bit chars in the UTF-16 encoding of this
+     * instance. This is the same as the {@code length} of the
+     * Java {@code String} representation of this instance.
+     *
+     * @return {@code >= 0;} the UTF-16 size
+     */
+    public int getUtf16Size() {
+        return string.length();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/StdConstantPool.java b/dexgen/src/com/android/dexgen/rop/cst/StdConstantPool.java
new file mode 100644
index 0000000..5f1728a
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/StdConstantPool.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.util.ExceptionWithContext;
+import com.android.dexgen.util.Hex;
+import com.android.dexgen.util.MutabilityControl;
+
+/**
+ * Standard implementation of {@link ConstantPool}, which directly stores
+ * an array of {@link Constant} objects and can be made immutable.
+ */
+public final class StdConstantPool
+        extends MutabilityControl implements ConstantPool {
+    /** {@code non-null;} array of entries */
+    private final Constant[] entries;
+
+    /**
+     * Constructs an instance. All indices initially contain {@code null}.
+     *
+     * @param size the size of the pool; this corresponds to the
+     * class file field {@code constant_pool_count}, and is in fact
+     * always at least one more than the actual size of the constant pool,
+     * as element {@code 0} is always invalid.
+     */
+    public StdConstantPool(int size) {
+        super(size > 1);
+
+        if (size < 1) {
+            throw new IllegalArgumentException("size < 1");
+        }
+
+        entries = new Constant[size];
+    }
+
+    /** {@inheritDoc} */
+    public int size() {
+        return entries.length;
+    }
+
+    /** {@inheritDoc} */
+    public Constant getOrNull(int n) {
+        try {
+            return entries[n];
+        } catch (IndexOutOfBoundsException ex) {
+            // Translate the exception.
+            return throwInvalid(n);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Constant get0Ok(int n) {
+        if (n == 0) {
+            return null;
+        }
+
+        return get(n);
+    }
+
+    /** {@inheritDoc} */
+    public Constant get(int n) {
+        try {
+            Constant result = entries[n];
+
+            if (result == null) {
+                throwInvalid(n);
+            }
+
+            return result;
+        } catch (IndexOutOfBoundsException ex) {
+            // Translate the exception.
+            return throwInvalid(n);
+        }
+    }
+
+    /**
+     * Sets the entry at the given index.
+     *
+     * @param n {@code >= 1, < size();} which entry
+     * @param cst {@code null-ok;} the constant to store
+     */
+    public void set(int n, Constant cst) {
+        throwIfImmutable();
+
+        boolean cat2 = (cst != null) && cst.isCategory2();
+
+        if (n < 1) {
+            throw new IllegalArgumentException("n < 1");
+        }
+
+        if (cat2) {
+            // Storing a category-2 entry nulls out the next index.
+            if (n == (entries.length - 1)) {
+                throw new IllegalArgumentException("(n == size - 1) && " +
+                                                   "cst.isCategory2()");
+            }
+            entries[n + 1] = null;
+        }
+
+        if ((cst != null) && (entries[n] == null)) {
+            /*
+             * Overwriting the second half of a category-2 entry nulls out
+             * the first half.
+             */
+            Constant prev = entries[n - 1];
+            if ((prev != null) && prev.isCategory2()) {
+                entries[n - 1] = null;
+            }
+        }
+
+        entries[n] = cst;
+    }
+
+    /**
+     * Throws the right exception for an invalid cpi.
+     *
+     * @param idx the bad cpi
+     * @return never
+     * @throws ExceptionWithContext always thrown
+     */
+    private static Constant throwInvalid(int idx) {
+        throw new ExceptionWithContext("invalid constant pool index " +
+                                       Hex.u2(idx));
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/TypedConstant.java b/dexgen/src/com/android/dexgen/rop/cst/TypedConstant.java
new file mode 100644
index 0000000..251f057
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/TypedConstant.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.TypeBearer;
+
+/**
+ * Base class for constants which implement {@link TypeBearer}.
+ */
+public abstract class TypedConstant
+        extends Constant implements TypeBearer {
+    /**
+     * {@inheritDoc}
+     *
+     * This implentation always returns {@code this}.
+     */
+    public final TypeBearer getFrameType() {
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public final int getBasicType() {
+        return getType().getBasicType();
+    }
+
+    /** {@inheritDoc} */
+    public final int getBasicFrameType() {
+        return getType().getBasicFrameType();
+    }
+
+    /** {@inheritDoc} */
+    public final boolean isConstant() {
+        return true;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/cst/Zeroes.java b/dexgen/src/com/android/dexgen/rop/cst/Zeroes.java
new file mode 100644
index 0000000..28da7db
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/cst/Zeroes.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.cst;
+
+import com.android.dexgen.rop.type.Type;
+
+/**
+ * Utility for turning types into zeroes.
+ */
+public final class Zeroes {
+    /**
+     * This class is uninstantiable.
+     */
+    private Zeroes() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Gets the "zero" (or {@code null}) value for the given type.
+     *
+     * @param type {@code non-null;} the type in question
+     * @return {@code non-null;} its "zero" value
+     */
+    public static Constant zeroFor(Type type) {
+        switch (type.getBasicType()) {
+            case Type.BT_BOOLEAN: return CstBoolean.VALUE_FALSE;
+            case Type.BT_BYTE:    return CstByte.VALUE_0;
+            case Type.BT_CHAR:    return CstChar.VALUE_0;
+            case Type.BT_DOUBLE:  return CstDouble.VALUE_0;
+            case Type.BT_FLOAT:   return CstFloat.VALUE_0;
+            case Type.BT_INT:     return CstInteger.VALUE_0;
+            case Type.BT_LONG:    return CstLong.VALUE_0;
+            case Type.BT_SHORT:   return CstShort.VALUE_0;
+            case Type.BT_OBJECT:  return CstKnownNull.THE_ONE;
+            default: {
+                throw new UnsupportedOperationException("no zero for type: " +
+                        type.toHuman());
+            }
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/type/Prototype.java b/dexgen/src/com/android/dexgen/rop/type/Prototype.java
new file mode 100644
index 0000000..33fa918
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/type/Prototype.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.type;
+
+import java.util.HashMap;
+
+/**
+ * Representation of a method decriptor. Instances of this class are
+ * generally interned and may be usefully compared with each other
+ * using {@code ==}.
+ */
+public final class Prototype implements Comparable<Prototype> {
+    /** {@code non-null;} intern table mapping string descriptors to instances */
+    private static final HashMap<String, Prototype> internTable =
+        new HashMap<String, Prototype>(500);
+
+    /** {@code non-null;} method descriptor */
+    private final String descriptor;
+
+    /** {@code non-null;} return type */
+    private final Type returnType;
+
+    /** {@code non-null;} list of parameter types */
+    private final StdTypeList parameterTypes;
+
+    /** {@code null-ok;} list of parameter frame types, if calculated */
+    private StdTypeList parameterFrameTypes;
+
+    /**
+     * Returns the unique instance corresponding to the
+     * given method descriptor. See vmspec-2 sec4.3.3 for details on the
+     * field descriptor syntax.
+     *
+     * @param descriptor {@code non-null;} the descriptor
+     * @return {@code non-null;} the corresponding instance
+     * @throws IllegalArgumentException thrown if the descriptor has
+     * invalid syntax
+     */
+    public static Prototype intern(String descriptor) {
+        if (descriptor == null) {
+            throw new NullPointerException("descriptor == null");
+        }     
+        Prototype result = internTable.get(descriptor);
+        if (result != null) {
+            return result;
+        }
+
+        Type[] params = makeParameterArray(descriptor);
+        int paramCount = 0;
+        int at = 1;
+
+        for (;;) {
+            int startAt = at;
+            char c = descriptor.charAt(at);
+            if (c == ')') {
+                at++;
+                break;
+            }
+
+            // Skip array markers.
+            while (c == '[') {
+                at++;
+                c = descriptor.charAt(at);
+            }
+
+            if (c == 'L') {
+                // It looks like the start of a class name; find the end.
+                int endAt = descriptor.indexOf(';', at);
+                if (endAt == -1) {
+                    throw new IllegalArgumentException("bad descriptor");
+                }
+                at = endAt + 1;
+            } else {
+                at++;
+            }
+
+            params[paramCount] =
+                Type.intern(descriptor.substring(startAt, at));
+            paramCount++;
+        }
+
+        Type returnType = Type.internReturnType(descriptor.substring(at));
+        StdTypeList parameterTypes = new StdTypeList(paramCount);
+
+        for (int i = 0; i < paramCount; i++) {
+            parameterTypes.set(i, params[i]);
+        }
+
+        result = new Prototype(descriptor, returnType, parameterTypes);
+        return putIntern(result);
+    }
+
+    /**
+     * Helper for {@link #intern} which returns an empty array to
+     * populate with parsed parameter types, and which also ensures
+     * that there is a '(' at the start of the descriptor and a
+     * single ')' somewhere before the end.
+     *
+     * @param descriptor {@code non-null;} the descriptor string
+     * @return {@code non-null;} array large enough to hold all parsed parameter
+     * types, but which is likely actually larger than needed
+     */
+    private static Type[] makeParameterArray(String descriptor) {
+        int length = descriptor.length();
+
+        if (descriptor.charAt(0) != '(') {
+            throw new IllegalArgumentException("bad descriptor");
+        }
+
+        /*
+         * This is a cheesy way to establish an upper bound on the
+         * number of parameters: Just count capital letters.
+         */
+        int closeAt = 0;
+        int maxParams = 0;
+        for (int i = 1; i < length; i++) {
+            char c = descriptor.charAt(i);
+            if (c == ')') {
+                closeAt = i;
+                break;
+            }
+            if ((c >= 'A') && (c <= 'Z')) {
+                maxParams++;
+            }
+        }
+
+        if ((closeAt == 0) || (closeAt == (length - 1))) {
+            throw new IllegalArgumentException("bad descriptor");
+        }
+
+        if (descriptor.indexOf(')', closeAt + 1) != -1) {
+            throw new IllegalArgumentException("bad descriptor");
+        }
+
+        return new Type[maxParams];
+    }
+
+    /**
+     * Interns an instance, adding to the descriptor as necessary based
+     * on the given definer, name, and flags. For example, an init
+     * method has an uninitialized object of type {@code definer}
+     * as its first argument.
+     *
+     * @param descriptor {@code non-null;} the descriptor string
+     * @param definer {@code non-null;} class the method is defined on
+     * @param isStatic whether this is a static method
+     * @param isInit whether this is an init method
+     * @return {@code non-null;} the interned instance
+     */
+    public static Prototype intern(String descriptor, Type definer,
+            boolean isStatic, boolean isInit) {
+        Prototype base = intern(descriptor);
+
+        if (isStatic) {
+            return base;
+        }
+
+        if (isInit) {
+            definer = definer.asUninitialized(Integer.MAX_VALUE);
+        }
+
+        return base.withFirstParameter(definer);
+    }
+
+    /**
+     * Interns an instance which consists of the given number of
+     * {@code int}s along with the given return type
+     *
+     * @param returnType {@code non-null;} the return type
+     * @param count {@code > 0;} the number of elements in the prototype
+     * @return {@code non-null;} the interned instance
+     */
+    public static Prototype internInts(Type returnType, int count) {
+        // Make the descriptor...
+
+        StringBuffer sb = new StringBuffer(100);
+
+        sb.append('(');
+
+        for (int i = 0; i < count; i++) {
+            sb.append('I');
+        }
+
+        sb.append(')');
+        sb.append(returnType.getDescriptor());
+
+        // ...and intern it.
+        return intern(sb.toString());
+    }
+
+    /**
+     * Constructs an instance. This is a private constructor; use one
+     * of the public static methods to get instances.
+     *
+     * @param descriptor {@code non-null;} the descriptor string
+     */
+    private Prototype(String descriptor, Type returnType,
+            StdTypeList parameterTypes) {
+        if (descriptor == null) {
+            throw new NullPointerException("descriptor == null");
+        }
+
+        if (returnType == null) {
+            throw new NullPointerException("returnType == null");
+        }
+
+        if (parameterTypes == null) {
+            throw new NullPointerException("parameterTypes == null");
+        }
+
+        this.descriptor = descriptor;
+        this.returnType = returnType;
+        this.parameterTypes = parameterTypes;
+        this.parameterFrameTypes = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            /*
+             * Since externally-visible instances are interned, this
+             * check helps weed out some easy cases.
+             */
+            return true;
+        }
+
+        if (!(other instanceof Prototype)) {
+            return false;
+        }
+
+        return descriptor.equals(((Prototype) other).descriptor);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return descriptor.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    public int compareTo(Prototype other) {
+        if (this == other) {
+            return 0;
+        }
+
+        /*
+         * The return type is the major order, and then args in order,
+         * and then the shorter list comes first (similar to string
+         * sorting).
+         */
+
+        int result = returnType.compareTo(other.returnType);
+
+        if (result != 0) {
+            return result;
+        }
+
+        int thisSize = parameterTypes.size();
+        int otherSize = other.parameterTypes.size();
+        int size = Math.min(thisSize, otherSize);
+
+        for (int i = 0; i < size; i++) {
+            Type thisType = parameterTypes.get(i);
+            Type otherType = other.parameterTypes.get(i);
+
+            result = thisType.compareTo(otherType);
+
+            if (result != 0) {
+                return result;
+            }
+        }
+
+        if (thisSize < otherSize) {
+            return -1;
+        } else if (thisSize > otherSize) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return descriptor;
+    }
+
+    /**
+     * Gets the descriptor string.
+     *
+     * @return {@code non-null;} the descriptor
+     */
+    public String getDescriptor() {
+        return descriptor;
+    }
+
+    /**
+     * Gets the return type.
+     *
+     * @return {@code non-null;} the return type
+     */
+    public Type getReturnType() {
+        return returnType;
+    }
+
+    /**
+     * Gets the list of parameter types.
+     *
+     * @return {@code non-null;} the list of parameter types
+     */
+    public StdTypeList getParameterTypes() {
+        return parameterTypes;
+    }
+
+    /**
+     * Gets the list of frame types corresponding to the list of parameter
+     * types. The difference between the two lists (if any) is that all
+     * "intlike" types (see {@link Type#isIntlike}) are replaced by
+     * {@link Type#INT}.
+     *
+     * @return {@code non-null;} the list of parameter frame types
+     */
+    public StdTypeList getParameterFrameTypes() {
+        if (parameterFrameTypes == null) {
+            int sz = parameterTypes.size();
+            StdTypeList list = new StdTypeList(sz);
+            boolean any = false;
+            for (int i = 0; i < sz; i++) {
+                Type one = parameterTypes.get(i);
+                if (one.isIntlike()) {
+                    any = true;
+                    one = Type.INT;
+                }
+                list.set(i, one);
+            }
+            parameterFrameTypes = any ? list : parameterTypes;
+        }
+
+        return parameterFrameTypes;
+    }
+
+    /**
+     * Returns a new interned instance, which is the same as this instance,
+     * except that it has an additional parameter prepended to the original's
+     * argument list.
+     *
+     * @param param {@code non-null;} the new first parameter
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public Prototype withFirstParameter(Type param) {
+        String newDesc = "(" + param.getDescriptor() + descriptor.substring(1);
+        StdTypeList newParams = parameterTypes.withFirst(param);
+
+        newParams.setImmutable();
+
+        Prototype result =
+            new Prototype(newDesc, returnType, newParams);
+
+        return putIntern(result);
+    }
+
+    /**
+     * Puts the given instance in the intern table if it's not already
+     * there. If a conflicting value is already in the table, then leave it.
+     * Return the interned value.
+     *
+     * @param desc {@code non-null;} instance to make interned
+     * @return {@code non-null;} the actual interned object
+     */
+    private static Prototype putIntern(Prototype desc) {
+        synchronized (internTable) {
+            String descriptor = desc.getDescriptor();
+            Prototype already = internTable.get(descriptor);
+            if (already != null) {
+                return already;
+            }
+            internTable.put(descriptor, desc);
+            return desc;
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/type/StdTypeList.java b/dexgen/src/com/android/dexgen/rop/type/StdTypeList.java
new file mode 100644
index 0000000..a3e81ff
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/type/StdTypeList.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.type;
+
+import com.android.dexgen.util.FixedSizeList;
+
+/**
+ * Standard implementation of {@link TypeList}.
+ */
+public final class StdTypeList
+        extends FixedSizeList implements TypeList {
+    /** {@code non-null;} no-element instance */
+    public static final StdTypeList EMPTY = new StdTypeList(0);
+
+    /** {@code non-null;} the list {@code [int]} */
+    public static final StdTypeList INT = StdTypeList.make(Type.INT);
+
+    /** {@code non-null;} the list {@code [long]} */
+    public static final StdTypeList LONG = StdTypeList.make(Type.LONG);
+
+    /** {@code non-null;} the list {@code [float]} */
+    public static final StdTypeList FLOAT = StdTypeList.make(Type.FLOAT);
+
+    /** {@code non-null;} the list {@code [double]} */
+    public static final StdTypeList DOUBLE = StdTypeList.make(Type.DOUBLE);
+
+    /** {@code non-null;} the list {@code [Object]} */
+    public static final StdTypeList OBJECT = StdTypeList.make(Type.OBJECT);
+
+    /** {@code non-null;} the list {@code [ReturnAddress]} */
+    public static final StdTypeList RETURN_ADDRESS
+            = StdTypeList.make(Type.RETURN_ADDRESS);
+
+    /** {@code non-null;} the list {@code [Throwable]} */
+    public static final StdTypeList THROWABLE =
+        StdTypeList.make(Type.THROWABLE);
+
+    /** {@code non-null;} the list {@code [int, int]} */
+    public static final StdTypeList INT_INT =
+        StdTypeList.make(Type.INT, Type.INT);
+
+    /** {@code non-null;} the list {@code [long, long]} */
+    public static final StdTypeList LONG_LONG =
+        StdTypeList.make(Type.LONG, Type.LONG);
+
+    /** {@code non-null;} the list {@code [float, float]} */
+    public static final StdTypeList FLOAT_FLOAT =
+        StdTypeList.make(Type.FLOAT, Type.FLOAT);
+
+    /** {@code non-null;} the list {@code [double, double]} */
+    public static final StdTypeList DOUBLE_DOUBLE =
+        StdTypeList.make(Type.DOUBLE, Type.DOUBLE);
+
+    /** {@code non-null;} the list {@code [Object, Object]} */
+    public static final StdTypeList OBJECT_OBJECT =
+        StdTypeList.make(Type.OBJECT, Type.OBJECT);
+
+    /** {@code non-null;} the list {@code [int, Object]} */
+    public static final StdTypeList INT_OBJECT =
+        StdTypeList.make(Type.INT, Type.OBJECT);
+
+    /** {@code non-null;} the list {@code [long, Object]} */
+    public static final StdTypeList LONG_OBJECT =
+        StdTypeList.make(Type.LONG, Type.OBJECT);
+
+    /** {@code non-null;} the list {@code [float, Object]} */
+    public static final StdTypeList FLOAT_OBJECT =
+        StdTypeList.make(Type.FLOAT, Type.OBJECT);
+
+    /** {@code non-null;} the list {@code [double, Object]} */
+    public static final StdTypeList DOUBLE_OBJECT =
+        StdTypeList.make(Type.DOUBLE, Type.OBJECT);
+
+    /** {@code non-null;} the list {@code [long, int]} */
+    public static final StdTypeList LONG_INT =
+        StdTypeList.make(Type.LONG, Type.INT);
+
+    /** {@code non-null;} the list {@code [int[], int]} */
+    public static final StdTypeList INTARR_INT =
+        StdTypeList.make(Type.INT_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [long[], int]} */
+    public static final StdTypeList LONGARR_INT =
+        StdTypeList.make(Type.LONG_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [float[], int]} */
+    public static final StdTypeList FLOATARR_INT =
+        StdTypeList.make(Type.FLOAT_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [double[], int]} */
+    public static final StdTypeList DOUBLEARR_INT =
+        StdTypeList.make(Type.DOUBLE_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [Object[], int]} */
+    public static final StdTypeList OBJECTARR_INT =
+        StdTypeList.make(Type.OBJECT_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [boolean[], int]} */
+    public static final StdTypeList BOOLEANARR_INT =
+        StdTypeList.make(Type.BOOLEAN_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [byte[], int]} */
+    public static final StdTypeList BYTEARR_INT =
+        StdTypeList.make(Type.BYTE_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [char[], int]} */
+    public static final StdTypeList CHARARR_INT =
+        StdTypeList.make(Type.CHAR_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [short[], int]} */
+    public static final StdTypeList SHORTARR_INT =
+        StdTypeList.make(Type.SHORT_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [int, int[], int]} */
+    public static final StdTypeList INT_INTARR_INT =
+        StdTypeList.make(Type.INT, Type.INT_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [long, long[], int]} */
+    public static final StdTypeList LONG_LONGARR_INT =
+        StdTypeList.make(Type.LONG, Type.LONG_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [float, float[], int]} */
+    public static final StdTypeList FLOAT_FLOATARR_INT =
+        StdTypeList.make(Type.FLOAT, Type.FLOAT_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [double, double[], int]} */
+    public static final StdTypeList DOUBLE_DOUBLEARR_INT =
+        StdTypeList.make(Type.DOUBLE, Type.DOUBLE_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [Object, Object[], int]} */
+    public static final StdTypeList OBJECT_OBJECTARR_INT =
+        StdTypeList.make(Type.OBJECT, Type.OBJECT_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [int, boolean[], int]} */
+    public static final StdTypeList INT_BOOLEANARR_INT =
+        StdTypeList.make(Type.INT, Type.BOOLEAN_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [int, byte[], int]} */
+    public static final StdTypeList INT_BYTEARR_INT =
+        StdTypeList.make(Type.INT, Type.BYTE_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [int, char[], int]} */
+    public static final StdTypeList INT_CHARARR_INT =
+        StdTypeList.make(Type.INT, Type.CHAR_ARRAY, Type.INT);
+
+    /** {@code non-null;} the list {@code [int, short[], int]} */
+    public static final StdTypeList INT_SHORTARR_INT =
+        StdTypeList.make(Type.INT, Type.SHORT_ARRAY, Type.INT);
+
+    /**
+     * Makes a single-element instance.
+     *
+     * @param type {@code non-null;} the element
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public static StdTypeList make(Type type) {
+        StdTypeList result = new StdTypeList(1);
+        result.set(0, type);
+        return result;
+    }
+
+    /**
+     * Makes a two-element instance.
+     *
+     * @param type0 {@code non-null;} the first element
+     * @param type1 {@code non-null;} the second element
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public static StdTypeList make(Type type0, Type type1) {
+        StdTypeList result = new StdTypeList(2);
+        result.set(0, type0);
+        result.set(1, type1);
+        return result;
+    }
+
+    /**
+     * Makes a three-element instance.
+     *
+     * @param type0 {@code non-null;} the first element
+     * @param type1 {@code non-null;} the second element
+     * @param type2 {@code non-null;} the third element
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public static StdTypeList make(Type type0, Type type1, Type type2) {
+        StdTypeList result = new StdTypeList(3);
+        result.set(0, type0);
+        result.set(1, type1);
+        result.set(2, type2);
+        return result;
+    }
+
+    /**
+     * Makes a four-element instance.
+     *
+     * @param type0 {@code non-null;} the first element
+     * @param type1 {@code non-null;} the second element
+     * @param type2 {@code non-null;} the third element
+     * @param type3 {@code non-null;} the fourth element
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public static StdTypeList make(Type type0, Type type1, Type type2,
+                                   Type type3) {
+        StdTypeList result = new StdTypeList(4);
+        result.set(0, type0);
+        result.set(1, type1);
+        result.set(2, type2);
+        result.set(3, type3);
+        return result;
+    }
+
+    /**
+     * Returns the given list as a comma-separated list of human forms. This
+     * is a static method so as to work on arbitrary {@link TypeList}
+     * instances.
+     *
+     * @param list {@code non-null;} the list to convert
+     * @return {@code non-null;} the human form
+     */
+    public static String toHuman(TypeList list) {
+        int size = list.size();
+
+        if (size == 0) {
+            return "<empty>";
+        }
+
+        StringBuffer sb = new StringBuffer(100);
+
+        for (int i = 0; i < size; i++) {
+            if (i != 0) {
+                sb.append(", ");
+            }
+            sb.append(list.getType(i).toHuman());
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Returns a hashcode of the contents of the given list. This
+     * is a static method so as to work on arbitrary {@link TypeList}
+     * instances.
+     *
+     * @param list {@code non-null;} the list to inspect
+     * @return {@code non-null;} the hash code
+     */
+    public static int hashContents(TypeList list) {
+        int size = list.size();
+        int hash = 0;
+
+        for (int i = 0; i < size; i++) {
+            hash = (hash * 31) + list.getType(i).hashCode();
+        }
+
+        return hash;
+    }
+
+    /**
+     * Compares the contents of the given two instances for equality. This
+     * is a static method so as to work on arbitrary {@link TypeList}
+     * instances.
+     *
+     * @param list1 {@code non-null;} one list to compare
+     * @param list2 {@code non-null;} another list to compare
+     * @return whether the two lists contain corresponding equal elements
+     */
+    public static boolean equalContents(TypeList list1, TypeList list2) {
+        int size = list1.size();
+
+        if (list2.size() != size) {
+            return false;
+        }
+
+        for (int i = 0; i < size; i++) {
+            if (! list1.getType(i).equals(list2.getType(i))) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Compares the contents of the given two instances for ordering. This
+     * is a static method so as to work on arbitrary {@link TypeList}
+     * instances.
+     *
+     * @param list1 {@code non-null;} one list to compare
+     * @param list2 {@code non-null;} another list to compare
+     * @return the order of the two lists
+     */
+    public static int compareContents(TypeList list1, TypeList list2) {
+        int size1 = list1.size();
+        int size2 = list2.size();
+        int size = Math.min(size1, size2);
+
+        for (int i = 0; i < size; i++) {
+            int comparison = list1.getType(i).compareTo(list2.getType(i));
+            if (comparison != 0) {
+                return comparison;
+            }
+        }
+
+        if (size1 == size2) {
+            return 0;
+        } else if (size1 < size2) {
+            return -1;
+        } else {
+            return 1;
+        }
+    }
+
+    /**
+     * Constructs an instance. All indices initially contain {@code null}.
+     *
+     * @param size the size of the list
+     */
+    public StdTypeList(int size) {
+        super(size);
+    }
+
+    /** {@inheritDoc} */
+    public Type getType(int n) {
+        return get(n);
+    }
+
+    /** {@inheritDoc} */
+    public int getWordCount() {
+        int sz = size();
+        int result = 0;
+
+        for (int i = 0; i < sz; i++) {
+            result += get(i).getCategory();
+        }
+
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public TypeList withAddedType(Type type) {
+        int sz = size();
+        StdTypeList result = new StdTypeList(sz + 1);
+
+        for (int i = 0; i < sz; i++) {
+            result.set0(i, get0(i));
+        }
+
+        result.set(sz, type);
+        result.setImmutable();
+        return result;
+    }
+
+    /**
+     * Gets the indicated element. It is an error to call this with the
+     * index for an element which was never set; if you do that, this
+     * will throw {@code NullPointerException}.
+     *
+     * @param n {@code >= 0, < size();} which element
+     * @return {@code non-null;} the indicated element
+     */
+    public Type get(int n) {
+        return (Type) get0(n);
+    }
+
+    /**
+     * Sets the type at the given index.
+     *
+     * @param n {@code >= 0, < size();} which element
+     * @param type {@code non-null;} the type to store
+     */
+    public void set(int n, Type type) {
+        set0(n, type);
+    }
+
+    /**
+     * Returns a new instance, which is the same as this instance,
+     * except that it has an additional type prepended to the
+     * original.
+     *
+     * @param type {@code non-null;} the new first element
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public StdTypeList withFirst(Type type) {
+        int sz = size();
+        StdTypeList result = new StdTypeList(sz + 1);
+
+        result.set0(0, type);
+        for (int i = 0; i < sz; i++) {
+            result.set0(i + 1, getOrNull0(i));
+        }
+
+        return result;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/type/Type.java b/dexgen/src/com/android/dexgen/rop/type/Type.java
new file mode 100644
index 0000000..62f3f14
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/type/Type.java
@@ -0,0 +1,856 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.type;
+
+import com.android.dexgen.util.Hex;
+
+import java.util.HashMap;
+
+/**
+ * Representation of a value type, such as may appear in a field, in a
+ * local, on a stack, or in a method descriptor. Instances of this
+ * class are generally interned and may be usefully compared with each
+ * other using {@code ==}.
+ */
+public final class Type implements TypeBearer, Comparable<Type> {
+    /** {@code non-null;} intern table mapping string descriptors to instances */
+    private static final HashMap<String, Type> internTable =
+        new HashMap<String, Type>(500);
+
+    /** basic type constant for {@code void} */
+    public static final int BT_VOID = 0;
+
+    /** basic type constant for {@code boolean} */
+    public static final int BT_BOOLEAN = 1;
+
+    /** basic type constant for {@code byte} */
+    public static final int BT_BYTE = 2;
+
+    /** basic type constant for {@code char} */
+    public static final int BT_CHAR = 3;
+
+    /** basic type constant for {@code double} */
+    public static final int BT_DOUBLE = 4;
+
+    /** basic type constant for {@code float} */
+    public static final int BT_FLOAT = 5;
+
+    /** basic type constant for {@code int} */
+    public static final int BT_INT = 6;
+
+    /** basic type constant for {@code long} */
+    public static final int BT_LONG = 7;
+
+    /** basic type constant for {@code short} */
+    public static final int BT_SHORT = 8;
+
+    /** basic type constant for {@code Object} */
+    public static final int BT_OBJECT = 9;
+
+    /** basic type constant for a return address */
+    public static final int BT_ADDR = 10;
+
+    /** count of basic type constants */
+    public static final int BT_COUNT = 11;
+
+    /** {@code non-null;} instance representing {@code boolean} */
+    public static final Type BOOLEAN = new Type("Z", BT_BOOLEAN);
+
+    /** {@code non-null;} instance representing {@code byte} */
+    public static final Type BYTE = new Type("B", BT_BYTE);
+
+    /** {@code non-null;} instance representing {@code char} */
+    public static final Type CHAR = new Type("C", BT_CHAR);
+
+    /** {@code non-null;} instance representing {@code double} */
+    public static final Type DOUBLE = new Type("D", BT_DOUBLE);
+
+    /** {@code non-null;} instance representing {@code float} */
+    public static final Type FLOAT = new Type("F", BT_FLOAT);
+
+    /** {@code non-null;} instance representing {@code int} */
+    public static final Type INT = new Type("I", BT_INT);
+
+    /** {@code non-null;} instance representing {@code long} */
+    public static final Type LONG = new Type("J", BT_LONG);
+
+    /** {@code non-null;} instance representing {@code short} */
+    public static final Type SHORT = new Type("S", BT_SHORT);
+
+    /** {@code non-null;} instance representing {@code void} */
+    public static final Type VOID = new Type("V", BT_VOID);
+
+    /** {@code non-null;} instance representing a known-{@code null} */
+    public static final Type KNOWN_NULL = new Type("<null>", BT_OBJECT);
+
+    /** {@code non-null;} instance representing a subroutine return address */
+    public static final Type RETURN_ADDRESS = new Type("<addr>", BT_ADDR);
+
+    static {
+        /*
+         * Put all the primitive types into the intern table. This needs
+         * to happen before the array types below get interned.
+         */
+        putIntern(BOOLEAN);
+        putIntern(BYTE);
+        putIntern(CHAR);
+        putIntern(DOUBLE);
+        putIntern(FLOAT);
+        putIntern(INT);
+        putIntern(LONG);
+        putIntern(SHORT);
+        /*
+         * Note: VOID isn't put in the intern table, since it's special and
+         * shouldn't be found by a normal call to intern().
+         */
+    }
+
+    /**
+     * {@code non-null;} instance representing
+     * {@code java.lang.annotation.Annotation}
+     */
+    public static final Type ANNOTATION =
+        intern("Ljava/lang/annotation/Annotation;");
+
+    /** {@code non-null;} instance representing {@code java.lang.Class} */
+    public static final Type CLASS = intern("Ljava/lang/Class;");
+
+    /** {@code non-null;} instance representing {@code java.lang.Cloneable} */
+    public static final Type CLONEABLE = intern("Ljava/lang/Cloneable;");
+
+    /** {@code non-null;} instance representing {@code java.lang.Object} */
+    public static final Type OBJECT = intern("Ljava/lang/Object;");
+
+    /** {@code non-null;} instance representing {@code java.io.Serializable} */
+    public static final Type SERIALIZABLE = intern("Ljava/io/Serializable;");
+
+    /** {@code non-null;} instance representing {@code java.lang.String} */
+    public static final Type STRING = intern("Ljava/lang/String;");
+
+    /** {@code non-null;} instance representing {@code java.lang.Throwable} */
+    public static final Type THROWABLE = intern("Ljava/lang/Throwable;");
+
+    /**
+     * {@code non-null;} instance representing {@code java.lang.Boolean}; the
+     * suffix on the name helps disambiguate this from the instance
+     * representing a primitive type
+     */
+    public static final Type BOOLEAN_CLASS = intern("Ljava/lang/Boolean;");
+
+    /**
+     * {@code non-null;} instance representing {@code java.lang.Byte}; the
+     * suffix on the name helps disambiguate this from the instance
+     * representing a primitive type
+     */
+    public static final Type BYTE_CLASS = intern("Ljava/lang/Byte;");
+
+    /**
+     * {@code non-null;} instance representing {@code java.lang.Character}; the
+     * suffix on the name helps disambiguate this from the instance
+     * representing a primitive type
+     */
+    public static final Type CHARACTER_CLASS = intern("Ljava/lang/Character;");
+
+    /**
+     * {@code non-null;} instance representing {@code java.lang.Double}; the
+     * suffix on the name helps disambiguate this from the instance
+     * representing a primitive type
+     */
+    public static final Type DOUBLE_CLASS = intern("Ljava/lang/Double;");
+
+    /**
+     * {@code non-null;} instance representing {@code java.lang.Float}; the
+     * suffix on the name helps disambiguate this from the instance
+     * representing a primitive type
+     */
+    public static final Type FLOAT_CLASS = intern("Ljava/lang/Float;");
+
+    /**
+     * {@code non-null;} instance representing {@code java.lang.Integer}; the
+     * suffix on the name helps disambiguate this from the instance
+     * representing a primitive type
+     */
+    public static final Type INTEGER_CLASS = intern("Ljava/lang/Integer;");
+
+    /**
+     * {@code non-null;} instance representing {@code java.lang.Long}; the
+     * suffix on the name helps disambiguate this from the instance
+     * representing a primitive type
+     */
+    public static final Type LONG_CLASS = intern("Ljava/lang/Long;");
+
+    /**
+     * {@code non-null;} instance representing {@code java.lang.Short}; the
+     * suffix on the name helps disambiguate this from the instance
+     * representing a primitive type
+     */
+    public static final Type SHORT_CLASS = intern("Ljava/lang/Short;");
+
+    /**
+     * {@code non-null;} instance representing {@code java.lang.Void}; the
+     * suffix on the name helps disambiguate this from the instance
+     * representing a primitive type
+     */
+    public static final Type VOID_CLASS = intern("Ljava/lang/Void;");
+
+    /** {@code non-null;} instance representing {@code boolean[]} */
+    public static final Type BOOLEAN_ARRAY = BOOLEAN.getArrayType();
+
+    /** {@code non-null;} instance representing {@code byte[]} */
+    public static final Type BYTE_ARRAY = BYTE.getArrayType();
+
+    /** {@code non-null;} instance representing {@code char[]} */
+    public static final Type CHAR_ARRAY = CHAR.getArrayType();
+
+    /** {@code non-null;} instance representing {@code double[]} */
+    public static final Type DOUBLE_ARRAY = DOUBLE.getArrayType();
+
+    /** {@code non-null;} instance representing {@code float[]} */
+    public static final Type FLOAT_ARRAY = FLOAT.getArrayType();
+
+    /** {@code non-null;} instance representing {@code int[]} */
+    public static final Type INT_ARRAY = INT.getArrayType();
+
+    /** {@code non-null;} instance representing {@code long[]} */
+    public static final Type LONG_ARRAY = LONG.getArrayType();
+
+    /** {@code non-null;} instance representing {@code Object[]} */
+    public static final Type OBJECT_ARRAY = OBJECT.getArrayType();
+
+    /** {@code non-null;} instance representing {@code short[]} */
+    public static final Type SHORT_ARRAY = SHORT.getArrayType();
+
+    /** {@code non-null;} field descriptor for the type */
+    private final String descriptor;
+
+    /**
+     * basic type corresponding to this type; one of the
+     * {@code BT_*} constants
+     */
+    private final int basicType;
+
+    /**
+     * {@code >= -1;} for an uninitialized type, bytecode index that this
+     * instance was allocated at; {@code Integer.MAX_VALUE} if it
+     * was an incoming uninitialized instance; {@code -1} if this
+     * is an <i>inititialized</i> instance
+     */
+    private final int newAt;
+
+    /**
+     * {@code null-ok;} the internal-form class name corresponding to this type, if
+     * calculated; only valid if {@code this} is a reference type and
+     * additionally not a return address
+     */
+    private String className;
+
+    /**
+     * {@code null-ok;} the type corresponding to an array of this type, if
+     * calculated
+     */
+    private Type arrayType;
+
+    /**
+     * {@code null-ok;} the type corresponding to elements of this type, if
+     * calculated; only valid if {@code this} is an array type
+     */
+    private Type componentType;
+
+    /**
+     * {@code null-ok;} the type corresponding to the initialized version of
+     * this type, if this instance is in fact an uninitialized type
+     */
+    private Type initializedType;
+
+    /**
+     * Returns the unique instance corresponding to the type with the
+     * given descriptor. See vmspec-2 sec4.3.2 for details on the
+     * field descriptor syntax. This method does <i>not</i> allow
+     * {@code "V"} (that is, type {@code void}) as a valid
+     * descriptor.
+     *
+     * @param descriptor {@code non-null;} the descriptor
+     * @return {@code non-null;} the corresponding instance
+     * @throws IllegalArgumentException thrown if the descriptor has
+     * invalid syntax
+     */
+    public static Type intern(String descriptor) {
+        
+        Type result = internTable.get(descriptor);
+        if (result != null) {
+            return result;
+        }
+
+        char firstChar;
+        try {
+            firstChar = descriptor.charAt(0);
+        } catch (IndexOutOfBoundsException ex) {
+            // Translate the exception.
+            throw new IllegalArgumentException("descriptor is empty");
+        } catch (NullPointerException ex) {
+            // Elucidate the exception.
+            throw new NullPointerException("descriptor == null");
+        }
+
+        if (firstChar == '[') {
+            /*
+             * Recursively strip away array markers to get at the underlying
+             * type, and build back on to form the result.
+             */
+            result = intern(descriptor.substring(1));
+            return result.getArrayType();
+        }
+
+        /*
+         * If the first character isn't '[' and it wasn't found in the
+         * intern cache, then it had better be the descriptor for a class.
+         */
+
+        int length = descriptor.length();
+        if ((firstChar != 'L') ||
+            (descriptor.charAt(length - 1) != ';')) {
+            throw new IllegalArgumentException("bad descriptor" + descriptor);
+        }
+
+        /*
+         * Validate the characters of the class name itself. Note that
+         * vmspec-2 does not have a coherent definition for valid
+         * internal-form class names, and the definition here is fairly
+         * liberal: A name is considered valid as long as it doesn't
+         * contain any of '[' ';' '.' '(' ')', and it has no more than one
+         * '/' in a row, and no '/' at either end.
+         */
+
+        int limit = (length - 1); // Skip the final ';'.
+        for (int i = 1; i < limit; i++) {
+            char c = descriptor.charAt(i);
+            switch (c) {
+                case '[':
+                case ';':
+                case '.':
+                case '(':
+                case ')': {
+                    throw new IllegalArgumentException("bad descriptor" + descriptor);
+                }
+                case '/': {
+                    if ((i == 1) ||
+                        (i == (length - 1)) ||
+                        (descriptor.charAt(i - 1) == '/')) {
+                        throw new IllegalArgumentException("bad descriptor");
+                    }
+                    break;
+                }
+            }
+        }
+
+        result = new Type(descriptor, BT_OBJECT);
+        return putIntern(result);
+    }
+
+    /**
+     * Returns the unique instance corresponding to the type with the
+     * given descriptor, allowing {@code "V"} to return the type
+     * for {@code void}. Other than that one caveat, this method
+     * is identical to {@link #intern}.
+     *
+     * @param descriptor {@code non-null;} the descriptor
+     * @return {@code non-null;} the corresponding instance
+     * @throws IllegalArgumentException thrown if the descriptor has
+     * invalid syntax
+     */
+    public static Type internReturnType(String descriptor) {
+        try {
+            if (descriptor.equals("V")) {
+                // This is the one special case where void may be returned.
+                return VOID;
+            }
+        } catch (NullPointerException ex) {
+            // Elucidate the exception.
+            throw new NullPointerException("descriptor == null");
+        }
+
+        return intern(descriptor);
+    }
+
+    /**
+     * Returns the unique instance corresponding to the type of the
+     * class with the given name. Calling this method is equivalent to
+     * calling {@code intern(name)} if {@code name} begins
+     * with {@code "["} and calling {@code intern("L" + name + ";")}
+     * in all other cases.
+     *
+     * @param name {@code non-null;} the name of the class whose type is desired
+     * @return {@code non-null;} the corresponding type
+     * @throws IllegalArgumentException thrown if the name has
+     * invalid syntax
+     */
+    public static Type internClassName(String name) {
+        if (name == null) {
+            throw new NullPointerException("name == null");
+        }
+
+        if (name.startsWith("[")) {
+            return intern(name);
+        }
+
+        return intern('L' + name + ';');
+    }
+
+    /**
+     * Constructs an instance corresponding to an "uninitialized type."
+     * This is a private constructor; use one of the public static
+     * methods to get instances.
+     *
+     * @param descriptor {@code non-null;} the field descriptor for the type
+     * @param basicType basic type corresponding to this type; one of the
+     * {@code BT_*} constants
+     * @param newAt {@code >= -1;} allocation bytecode index
+     */
+    private Type(String descriptor, int basicType, int newAt) {
+        if (descriptor == null) {
+            throw new NullPointerException("descriptor == null");
+        }
+
+        if ((basicType < 0) || (basicType >= BT_COUNT)) {
+            throw new IllegalArgumentException("bad basicType");
+        }
+
+        if (newAt < -1) {
+            throw new IllegalArgumentException("newAt < -1");
+        }
+
+        this.descriptor = descriptor;
+        this.basicType = basicType;
+        this.newAt = newAt;
+        this.arrayType = null;
+        this.componentType = null;
+        this.initializedType = null;
+    }
+
+    /**
+     * Constructs an instance corresponding to an "initialized type."
+     * This is a private constructor; use one of the public static
+     * methods to get instances.
+     *
+     * @param descriptor {@code non-null;} the field descriptor for the type
+     * @param basicType basic type corresponding to this type; one of the
+     * {@code BT_*} constants
+     */
+    private Type(String descriptor, int basicType) {
+        this(descriptor, basicType, -1);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            /*
+             * Since externally-visible types are interned, this check
+             * helps weed out some easy cases.
+             */
+            return true;
+        }
+
+        if (!(other instanceof Type)) {
+            return false;
+        }
+
+        return descriptor.equals(((Type) other).descriptor);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return descriptor.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    public int compareTo(Type other) {
+        return descriptor.compareTo(other.descriptor);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return descriptor;
+    }
+
+    /** {@inheritDoc} */
+    public String toHuman() {
+        switch (basicType) {
+            case BT_VOID:    return "void";
+            case BT_BOOLEAN: return "boolean";
+            case BT_BYTE:    return "byte";
+            case BT_CHAR:    return "char";
+            case BT_DOUBLE:  return "double";
+            case BT_FLOAT:   return "float";
+            case BT_INT:     return "int";
+            case BT_LONG:    return "long";
+            case BT_SHORT:   return "short";
+            case BT_OBJECT:  break;
+            default:         return descriptor;
+        }
+
+        if (isArray()) {
+            return getComponentType().toHuman() + "[]";
+        }
+
+        // Remove the "L...;" around the type and convert "/" to ".".
+        return getClassName().replace("/", ".");
+    }
+
+    /** {@inheritDoc} */
+    public Type getType() {
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public Type getFrameType() {
+        switch (basicType) {
+            case BT_BOOLEAN:
+            case BT_BYTE:
+            case BT_CHAR:
+            case BT_INT:
+            case BT_SHORT: {
+                return INT;
+            }
+        }
+
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public int getBasicType() {
+        return basicType;
+    }
+
+    /** {@inheritDoc} */
+    public int getBasicFrameType() {
+        switch (basicType) {
+            case BT_BOOLEAN:
+            case BT_BYTE:
+            case BT_CHAR:
+            case BT_INT:
+            case BT_SHORT: {
+                return BT_INT;
+            }
+        }
+
+        return basicType;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isConstant() {
+        return false;
+    }
+
+    /**
+     * Gets the descriptor.
+     *
+     * @return {@code non-null;} the descriptor
+     */
+    public String getDescriptor() {
+        return descriptor;
+    }
+
+    /**
+     * Gets the name of the class this type corresponds to, in internal
+     * form. This method is only valid if this instance is for a
+     * normal reference type (that is, a reference type and
+     * additionally not a return address).
+     *
+     * @return {@code non-null;} the internal-form class name
+     */
+    public String getClassName() {
+        if (className == null) {
+            if (!isReference()) {
+                throw new IllegalArgumentException("not an object type: " +
+                                                   descriptor);
+            }
+
+            if (descriptor.charAt(0) == '[') {
+                className = descriptor;
+            } else {
+                className = descriptor.substring(1, descriptor.length() - 1);
+            }
+        }
+
+        return className;
+    }
+
+    /**
+     * Gets the category. Most instances are category 1. {@code long}
+     * and {@code double} are the only category 2 types.
+     *
+     * @see #isCategory1
+     * @see #isCategory2
+     * @return the category
+     */
+    public int getCategory() {
+        switch (basicType) {
+            case BT_LONG:
+            case BT_DOUBLE: {
+                return 2;
+            }
+        }
+
+        return 1;
+    }
+
+    /**
+     * Returns whether or not this is a category 1 type.
+     *
+     * @see #getCategory
+     * @see #isCategory2
+     * @return whether or not this is a category 1 type
+     */
+    public boolean isCategory1() {
+        switch (basicType) {
+            case BT_LONG:
+            case BT_DOUBLE: {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns whether or not this is a category 2 type.
+     *
+     * @see #getCategory
+     * @see #isCategory1
+     * @return whether or not this is a category 2 type
+     */
+    public boolean isCategory2() {
+        switch (basicType) {
+            case BT_LONG:
+            case BT_DOUBLE: {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Gets whether this type is "intlike." An intlike type is one which, when
+     * placed on a stack or in a local, is automatically converted to an
+     * {@code int}.
+     *
+     * @return whether this type is "intlike"
+     */
+    public boolean isIntlike() {
+        switch (basicType) {
+            case BT_BOOLEAN:
+            case BT_BYTE:
+            case BT_CHAR:
+            case BT_INT:
+            case BT_SHORT: {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Gets whether this type is a primitive type. All types are either
+     * primitive or reference types.
+     *
+     * @return whether this type is primitive
+     */
+    public boolean isPrimitive() {
+        switch (basicType) {
+            case BT_BOOLEAN:
+            case BT_BYTE:
+            case BT_CHAR:
+            case BT_DOUBLE:
+            case BT_FLOAT:
+            case BT_INT:
+            case BT_LONG:
+            case BT_SHORT:
+            case BT_VOID: {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Gets whether this type is a normal reference type. A normal
+     * reference type is a reference type that is not a return
+     * address. This method is just convenient shorthand for
+     * {@code getBasicType() == Type.BT_OBJECT}.
+     *
+     * @return whether this type is a normal reference type
+     */
+    public boolean isReference() {
+        return (basicType == BT_OBJECT);
+    }
+
+    /**
+     * Gets whether this type is an array type. If this method returns
+     * {@code true}, then it is safe to use {@link #getComponentType}
+     * to determine the component type.
+     *
+     * @return whether this type is an array type
+     */
+    public boolean isArray() {
+        return (descriptor.charAt(0) == '[');
+    }
+
+    /**
+     * Gets whether this type is an array type or is a known-null, and
+     * hence is compatible with array types.
+     *
+     * @return whether this type is an array type
+     */
+    public boolean isArrayOrKnownNull() {
+        return isArray() || equals(KNOWN_NULL);
+    }
+
+    /**
+     * Gets whether this type represents an uninitialized instance. An
+     * uninitialized instance is what one gets back from the {@code new}
+     * opcode, and remains uninitialized until a valid constructor is
+     * invoked on it.
+     *
+     * @return whether this type is "uninitialized"
+     */
+    public boolean isUninitialized() {
+        return (newAt >= 0);
+    }
+
+    /**
+     * Gets the bytecode index at which this uninitialized type was
+     * allocated.  This returns {@code Integer.MAX_VALUE} if this
+     * type is an uninitialized incoming parameter (i.e., the
+     * {@code this} of an {@code <init>} method) or
+     * {@code -1} if this type is in fact <i>initialized</i>.
+     *
+     * @return {@code >= -1;} the allocation bytecode index
+     */
+    public int getNewAt() {
+        return newAt;
+    }
+
+    /**
+     * Gets the initialized type corresponding to this instance, but only
+     * if this instance is in fact an uninitialized object type.
+     *
+     * @return {@code non-null;} the initialized type
+     */
+    public Type getInitializedType() {
+        if (initializedType == null) {
+            throw new IllegalArgumentException("initialized type: " +
+                                               descriptor);
+        }
+
+        return initializedType;
+    }
+
+    /**
+     * Gets the type corresponding to an array of this type.
+     *
+     * @return {@code non-null;} the array type
+     */
+    public Type getArrayType() {
+        if (arrayType == null) {
+            arrayType = putIntern(new Type('[' + descriptor, BT_OBJECT));
+        }
+
+        return arrayType;
+    }
+
+    /**
+     * Gets the component type of this type. This method is only valid on
+     * array types.
+     *
+     * @return {@code non-null;} the component type
+     */
+    public Type getComponentType() {
+        if (componentType == null) {
+            if (descriptor.charAt(0) != '[') {
+                throw new IllegalArgumentException("not an array type: " +
+                                                   descriptor);
+            }
+            componentType = intern(descriptor.substring(1));
+        }
+
+        return componentType;
+    }
+
+    /**
+     * Returns a new interned instance which is identical to this one, except
+     * it is indicated as uninitialized and allocated at the given bytecode
+     * index. This instance must be an initialized object type.
+     *
+     * @param newAt {@code >= 0;} the allocation bytecode index
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public Type asUninitialized(int newAt) {
+        if (newAt < 0) {
+            throw new IllegalArgumentException("newAt < 0");
+        }
+
+        if (!isReference()) {
+            throw new IllegalArgumentException("not a reference type: " +
+                                               descriptor);
+        }
+
+        if (isUninitialized()) {
+            /*
+             * Dealing with uninitialized types as a starting point is
+             * a pain, and it's not clear that it'd ever be used, so
+             * just disallow it.
+             */
+            throw new IllegalArgumentException("already uninitialized: " +
+                                               descriptor);
+        }
+
+        /*
+         * Create a new descriptor that is unique and shouldn't conflict
+         * with "normal" type descriptors
+         */
+        String newDesc = 'N' + Hex.u2(newAt) + descriptor;
+        Type result = new Type(newDesc, BT_OBJECT, newAt);
+        result.initializedType = this;
+        return putIntern(result);
+    }
+
+    /**
+     * Puts the given instance in the intern table if it's not already
+     * there. If a conflicting value is already in the table, then leave it.
+     * Return the interned value.
+     *
+     * @param type {@code non-null;} instance to make interned
+     * @return {@code non-null;} the actual interned object
+     */
+    private static Type putIntern(Type type) {
+        synchronized (internTable) {
+            String descriptor = type.getDescriptor();
+            Type already = internTable.get(descriptor);
+            if (already != null) {
+                return already;
+            }
+            internTable.put(descriptor, type);
+            return type;
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/rop/type/TypeBearer.java b/dexgen/src/com/android/dexgen/rop/type/TypeBearer.java
new file mode 100644
index 0000000..da7a7ef
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/type/TypeBearer.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.type;
+
+import com.android.dexgen.util.ToHuman;
+
+/**
+ * Object which has an associated type, possibly itself.
+ */
+public interface TypeBearer
+        extends ToHuman {
+    /**
+     * Gets the type associated with this instance.
+     *
+     * @return {@code non-null;} the type
+     */
+    public Type getType();
+
+    /**
+     * Gets the frame type corresponding to this type. This method returns
+     * {@code this}, except if {@link Type#isIntlike} on the underlying
+     * type returns {@code true} but the underlying type is not in
+     * fact {@link Type#INT}, in which case this method returns an instance
+     * whose underlying type <i>is</i> {@code INT}.
+     *
+     * @return {@code non-null;} the frame type for this instance
+     */
+    public TypeBearer getFrameType();
+
+    /**
+     * Gets the basic type corresponding to this instance.
+     *
+     * @return the basic type; one of the {@code BT_*} constants
+     * defined by {@link Type}
+     */
+    public int getBasicType();
+
+    /**
+     * Gets the basic type corresponding to this instance's frame type. This
+     * is equivalent to {@code getFrameType().getBasicType()}, and
+     * is the same as calling {@code getFrameType()} unless this
+     * instance is an int-like type, in which case this method returns
+     * {@code BT_INT}.
+     *
+     * @see #getBasicType
+     * @see #getFrameType
+     *
+     * @return the basic frame type; one of the {@code BT_*} constants
+     * defined by {@link Type}
+     */
+    public int getBasicFrameType();
+
+    /**
+     * Returns whether this instance represents a constant value.
+     *
+     * @return {@code true} if this instance represents a constant value
+     * and {@code false} if not
+     */
+    public boolean isConstant();
+}
diff --git a/dexgen/src/com/android/dexgen/rop/type/TypeList.java b/dexgen/src/com/android/dexgen/rop/type/TypeList.java
new file mode 100644
index 0000000..dedcbc9
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/rop/type/TypeList.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.rop.type;
+
+/**
+ * List of {@link Type} instances (or of things that contain types).
+ */
+public interface TypeList {
+    /**
+     * Returns whether this instance is mutable. Note that the
+     * {@code TypeList} interface itself doesn't provide any
+     * means of mutation, but that doesn't mean that there isn't an
+     * extra-interface way of mutating an instance.
+     *
+     * @return {@code true} if this instance is mutable or
+     * {@code false} if it is immutable
+     */
+    public boolean isMutable();
+
+    /**
+     * Gets the size of this list.
+     *
+     * @return {@code >= 0;} the size
+     */
+    public int size();
+
+    /**
+     * Gets the indicated element. It is an error to call this with the
+     * index for an element which was never set; if you do that, this
+     * will throw {@code NullPointerException}.
+     *
+     * @param n {@code >= 0, < size();} which element
+     * @return {@code non-null;} the indicated element
+     */
+    public Type getType(int n);
+
+    /**
+     * Gets the number of 32-bit words required to hold instances of
+     * all the elements of this list. This is a sum of the widths (categories)
+     * of all the elements.
+     *
+     * @return {@code >= 0;} the required number of words
+     */
+    public int getWordCount();
+
+    /**
+     * Returns a new instance which is identical to this one, except that
+     * the given item is appended to the end and it is guaranteed to be
+     * immutable.
+     *
+     * @param type {@code non-null;} item to append
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public TypeList withAddedType(Type type);
+}
diff --git a/dexgen/src/com/android/dexgen/util/AnnotatedOutput.java b/dexgen/src/com/android/dexgen/util/AnnotatedOutput.java
new file mode 100644
index 0000000..3ff4cf5
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/AnnotatedOutput.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+/**
+ * Interface for a binary output destination that may be augmented
+ * with textual annotations.
+ */
+public interface AnnotatedOutput
+        extends Output {
+    /**
+     * Get whether this instance will actually keep annotations.
+     *
+     * @return {@code true} iff annotations are being kept
+     */
+    public boolean annotates();
+
+    /**
+     * Get whether this instance is intended to keep verbose annotations.
+     * Annotators may use the result of calling this method to inform their
+     * annotation activity.
+     *
+     * @return {@code true} iff annotations are to be verbose
+     */
+    public boolean isVerbose();
+
+    /**
+     * Add an annotation for the subsequent output. Any previously
+     * open annotation will be closed by this call, and the new
+     * annotation marks all subsequent output until another annotation
+     * call.
+     *
+     * @param msg {@code non-null;} the annotation message
+     */
+    public void annotate(String msg);
+
+    /**
+     * Add an annotation for a specified amount of subsequent
+     * output. Any previously open annotation will be closed by this
+     * call. If there is already pending annotation from one or more
+     * previous calls to this method, the new call "consumes" output
+     * after all the output covered by the previous calls.
+     *
+     * @param amt {@code >= 0;} the amount of output for this annotation to
+     * cover
+     * @param msg {@code non-null;} the annotation message
+     */
+    public void annotate(int amt, String msg);
+
+    /**
+     * End the most recent annotation. Subsequent output will be unannotated,
+     * until the next call to {@link #annotate}.
+     */
+    public void endAnnotation();
+
+    /**
+     * Get the maximum width of the annotated output. This is advisory:
+     * Implementations of this interface are encouraged to deal with too-wide
+     * output, but annotaters are encouraged to attempt to avoid exceeding
+     * the indicated width.
+     *
+     * @return {@code >= 1;} the maximum width
+     */
+    public int getAnnotationWidth();
+}
diff --git a/dexgen/src/com/android/dexgen/util/BitIntSet.java b/dexgen/src/com/android/dexgen/util/BitIntSet.java
new file mode 100644
index 0000000..0cf9fc6
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/BitIntSet.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+import java.util.NoSuchElementException;
+
+/**
+ * A set of integers, represented by a bit set
+ */
+public class BitIntSet implements IntSet {
+
+    /** also accessed in ListIntSet */
+    int[] bits;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param max the maximum value of ints in this set.
+     */
+    public BitIntSet(int max) {
+        bits = Bits.makeBitSet(max);
+    }
+
+    /** @inheritDoc */
+    public void add(int value) {
+        ensureCapacity(value);
+        Bits.set(bits, value, true);
+    }
+
+    /**
+     * Ensures that the bit set has the capacity to represent the given value.
+     *
+     * @param value {@code >= 0;} value to represent
+     */
+    private void ensureCapacity(int value) {
+        if (value >= Bits.getMax(bits)) {
+            int[] newBits = Bits.makeBitSet(
+                    Math.max(value + 1, 2 * Bits.getMax(bits)));
+            System.arraycopy(bits, 0, newBits, 0, bits.length);
+            bits = newBits;
+        }
+    }
+
+    /** @inheritDoc */
+    public void remove(int value) {
+        if (value < Bits.getMax(bits)) {
+            Bits.set(bits, value, false);
+        }
+    }
+
+    /** @inheritDoc */
+    public boolean has(int value) {
+        return (value < Bits.getMax(bits)) && Bits.get(bits, value);
+    }
+
+    /** @inheritDoc */
+    public void merge(IntSet other) {
+        if (other instanceof BitIntSet) {
+            BitIntSet o = (BitIntSet) other;
+            ensureCapacity(Bits.getMax(o.bits) + 1);
+            Bits.or(bits, o.bits);
+        } else if (other instanceof ListIntSet) {
+            ListIntSet o = (ListIntSet) other;
+            int sz = o.ints.size();
+
+            if (sz > 0) {
+                ensureCapacity(o.ints.get(sz - 1));
+            }
+            for (int i = 0; i < o.ints.size(); i++) {
+                Bits.set(bits, o.ints.get(i), true);
+            }
+        } else {
+            IntIterator iter = other.iterator();
+            while (iter.hasNext()) {
+                add(iter.next());
+            }
+        }
+    }
+
+    /** @inheritDoc */
+    public int elements() {
+        return Bits.bitCount(bits);
+    }
+
+    /** @inheritDoc */
+    public IntIterator iterator() {
+        return new IntIterator() {
+            private int idx = Bits.findFirst(bits, 0);
+
+            /** @inheritDoc */
+            public boolean hasNext() {
+                return idx >= 0;
+            }
+
+            /** @inheritDoc */
+            public int next() {
+                if (!hasNext()) {
+                    throw new NoSuchElementException();
+                }
+
+                int ret = idx;
+
+                idx = Bits.findFirst(bits, idx+1);
+
+                return ret;
+            }
+        };
+    }
+
+    /** @inheritDoc */
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append('{');
+
+        boolean first = true;
+        for (int i = Bits.findFirst(bits, 0)
+                ; i >= 0
+                ; i = Bits.findFirst(bits, i + 1)) {
+            if (!first) {
+                sb.append(", ");
+            }
+            first = false;
+            sb.append(i);
+        }
+
+        sb.append('}');
+
+        return sb.toString();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/Bits.java b/dexgen/src/com/android/dexgen/util/Bits.java
new file mode 100644
index 0000000..5c97cc9
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/Bits.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+/**
+ * Utilities for treating {@code int[]}s as bit sets.
+ */
+public final class Bits {
+    /**
+     * This class is uninstantiable.
+     */
+    private Bits() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Constructs a bit set to contain bits up to the given index (exclusive).
+     *
+     * @param max {@code >= 0;} the maximum bit index (exclusive)
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public static int[] makeBitSet(int max) {
+        int size = (max + 0x1f) >> 5;
+        return new int[size];
+    }
+
+    /**
+     * Gets the maximum index (exclusive) for the given bit set.
+     *
+     * @param bits {@code non-null;} bit set in question
+     * @return {@code >= 0;} the maximum index (exclusive) that may be set
+     */
+    public static int getMax(int[] bits) {
+        return bits.length * 0x20;
+    }
+
+    /**
+     * Gets the value of the bit at the given index.
+     *
+     * @param bits {@code non-null;} bit set to operate on
+     * @param idx {@code >= 0, < getMax(set);} which bit
+     * @return the value of the indicated bit
+     */
+    public static boolean get(int[] bits, int idx) {
+        int arrayIdx = idx >> 5;
+        int bit = 1 << (idx & 0x1f);
+        return (bits[arrayIdx] & bit) != 0;
+    }
+
+    /**
+     * Sets the given bit to the given value.
+     *
+     * @param bits {@code non-null;} bit set to operate on
+     * @param idx {@code >= 0, < getMax(set);} which bit
+     * @param value the new value for the bit
+     */
+    public static void set(int[] bits, int idx, boolean value) {
+        int arrayIdx = idx >> 5;
+        int bit = 1 << (idx & 0x1f);
+
+        if (value) {
+            bits[arrayIdx] |= bit;
+        } else {
+            bits[arrayIdx] &= ~bit;
+        }
+    }
+
+    /**
+     * Sets the given bit to {@code true}.
+     *
+     * @param bits {@code non-null;} bit set to operate on
+     * @param idx {@code >= 0, < getMax(set);} which bit
+     */
+    public static void set(int[] bits, int idx) {
+        int arrayIdx = idx >> 5;
+        int bit = 1 << (idx & 0x1f);
+        bits[arrayIdx] |= bit;
+    }
+
+    /**
+     * Sets the given bit to {@code false}.
+     *
+     * @param bits {@code non-null;} bit set to operate on
+     * @param idx {@code >= 0, < getMax(set);} which bit
+     */
+    public static void clear(int[] bits, int idx) {
+        int arrayIdx = idx >> 5;
+        int bit = 1 << (idx & 0x1f);
+        bits[arrayIdx] &= ~bit;
+    }
+
+    /**
+     * Returns whether or not the given bit set is empty, that is, whether
+     * no bit is set to {@code true}.
+     *
+     * @param bits {@code non-null;} bit set to operate on
+     * @return {@code true} iff all bits are {@code false}
+     */
+    public static boolean isEmpty(int[] bits) {
+        int len = bits.length;
+
+        for (int i = 0; i < len; i++) {
+            if (bits[i] != 0) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Gets the number of bits set to {@code true} in the given bit set.
+     *
+     * @param bits {@code non-null;} bit set to operate on
+     * @return {@code >= 0;} the bit count (aka population count) of the set
+     */
+    public static int bitCount(int[] bits) {
+        int len = bits.length;
+        int count = 0;
+
+        for (int i = 0; i < len; i++) {
+            count += Integer.bitCount(bits[i]);
+        }
+
+        return count;
+    }
+
+    /**
+     * Returns whether any bits are set to {@code true} in the
+     * specified range.
+     *
+     * @param bits {@code non-null;} bit set to operate on
+     * @param start {@code >= 0;} index of the first bit in the range (inclusive)
+     * @param end {@code >= 0;} index of the last bit in the range (exclusive)
+     * @return {@code true} if any bit is set to {@code true} in
+     * the indicated range
+     */
+    public static boolean anyInRange(int[] bits, int start, int end) {
+        int idx = findFirst(bits, start);
+        return (idx >= 0) && (idx < end);
+    }
+
+    /**
+     * Finds the lowest-order bit set at or after the given index in the
+     * given bit set.
+     *
+     * @param bits {@code non-null;} bit set to operate on
+     * @param idx {@code >= 0;} minimum index to return
+     * @return {@code >= -1;} lowest-order bit set at or after {@code idx},
+     * or {@code -1} if there is no appropriate bit index to return
+     */
+    public static int findFirst(int[] bits, int idx) {
+        int len = bits.length;
+        int minBit = idx & 0x1f;
+
+        for (int arrayIdx = idx >> 5; arrayIdx < len; arrayIdx++) {
+            int word = bits[arrayIdx];
+            if (word != 0) {
+                int bitIdx = findFirst(word, minBit);
+                if (bitIdx >= 0) {
+                    return (arrayIdx << 5) + bitIdx;
+                }
+            }
+            minBit = 0;
+        }
+
+        return -1;
+    }
+
+    /**
+     * Finds the lowest-order bit set at or after the given index in the
+     * given {@code int}.
+     *
+     * @param value the value in question
+     * @param idx 0..31 the minimum bit index to return
+     * @return {@code >= -1;} lowest-order bit set at or after {@code idx},
+     * or {@code -1} if there is no appropriate bit index to return
+     */
+    public static int findFirst(int value, int idx) {
+        value &= ~((1 << idx) - 1); // Mask off too-low bits.
+        int result = Integer.numberOfTrailingZeros(value);
+        return (result == 32) ? -1 : result;
+    }
+
+    /**
+     * Ors bit array {@code b} into bit array {@code a}.
+     * {@code a.length} must be greater than or equal to
+     * {@code b.length}.
+     *
+     * @param a {@code non-null;} int array to be ored with other argument. This
+     * argument is modified.
+     * @param b {@code non-null;} int array to be ored into {@code a}. This
+     * argument is not modified.
+     */
+    public static void or(int[] a, int[] b) {
+        for (int i = 0; i < b.length; i++) {
+            a[i] |= b[i];
+        }
+    }
+
+    public static String toHuman(int[] bits) {
+        StringBuilder sb = new StringBuilder();
+
+        boolean needsComma = false;
+
+        sb.append('{');
+
+        int bitsLength = 32 * bits.length;
+        for (int i = 0; i < bitsLength; i++) {
+            if (Bits.get(bits, i)) {
+                if (needsComma) {
+                    sb.append(',');
+                }
+                needsComma = true;
+                sb.append(i);
+            }
+        }
+        sb.append('}');
+
+        return sb.toString();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/ByteArray.java b/dexgen/src/com/android/dexgen/util/ByteArray.java
new file mode 100644
index 0000000..93144b3
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/ByteArray.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Wrapper for a {@code byte[]}, which provides read-only access and
+ * can "reveal" a partial slice of the underlying array.
+ *
+ * <b>Note:</b> Multibyte accessors all use big-endian order.
+ */
+public final class ByteArray {
+    /** {@code non-null;} underlying array */
+    private final byte[] bytes;
+
+    /** {@code >= 0}; start index of the slice (inclusive) */
+    private final int start;
+
+    /** {@code >= 0, <= bytes.length}; size computed as
+     * {@code end - start} (in the constructor) */
+    private final int size;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param bytes {@code non-null;} the underlying array
+     * @param start {@code >= 0;} start index of the slice (inclusive)
+     * @param end {@code >= start, <= bytes.length;} end index of
+     * the slice (exclusive)
+     */
+    public ByteArray(byte[] bytes, int start, int end) {
+        if (bytes == null) {
+            throw new NullPointerException("bytes == null");
+        }
+
+        if (start < 0) {
+            throw new IllegalArgumentException("start < 0");
+        }
+
+        if (end < start) {
+            throw new IllegalArgumentException("end < start");
+        }
+
+        if (end > bytes.length) {
+            throw new IllegalArgumentException("end > bytes.length");
+        }
+
+        this.bytes = bytes;
+        this.start = start;
+        this.size = end - start;
+    }
+
+    /**
+     * Constructs an instance from an entire {@code byte[]}.
+     *
+     * @param bytes {@code non-null;} the underlying array
+     */
+    public ByteArray(byte[] bytes) {
+        this(bytes, 0, bytes.length);
+    }
+
+    /**
+     * Gets the size of the array, in bytes.
+     *
+     * @return {@code >= 0;} the size
+     */
+    public int size() {
+        return size;
+    }
+
+    /**
+     * Returns a slice (that is, a sub-array) of this instance.
+     *
+     * @param start {@code >= 0;} start index of the slice (inclusive)
+     * @param end {@code >= start, <= size();} end index of
+     * the slice (exclusive)
+     * @return {@code non-null;} the slice
+     */
+    public ByteArray slice(int start, int end) {
+        checkOffsets(start, end);
+        return new ByteArray(bytes, start + this.start, end + this.start);
+    }
+
+    /**
+     * Returns the offset into the given array represented by the given
+     * offset into this instance.
+     *
+     * @param offset offset into this instance
+     * @param bytes {@code non-null;} (alleged) underlying array
+     * @return corresponding offset into {@code bytes}
+     * @throws IllegalArgumentException thrown if {@code bytes} is
+     * not the underlying array of this instance
+     */
+    public int underlyingOffset(int offset, byte[] bytes) {
+        if (bytes != this.bytes) {
+            throw new IllegalArgumentException("wrong bytes");
+        }
+
+        return start + offset;
+    }
+
+    /**
+     * Gets the {@code signed byte} value at a particular offset.
+     *
+     * @param off {@code >= 0, < size();} offset to fetch
+     * @return {@code signed byte} at that offset
+     */
+    public int getByte(int off) {
+        checkOffsets(off, off + 1);
+        return getByte0(off);
+    }
+
+    /**
+     * Gets the {@code signed short} value at a particular offset.
+     *
+     * @param off {@code >= 0, < (size() - 1);} offset to fetch
+     * @return {@code signed short} at that offset
+     */
+    public int getShort(int off) {
+        checkOffsets(off, off + 2);
+        return (getByte0(off) << 8) | getUnsignedByte0(off + 1);
+    }
+
+    /**
+     * Gets the {@code signed int} value at a particular offset.
+     *
+     * @param off {@code >= 0, < (size() - 3);} offset to fetch
+     * @return {@code signed int} at that offset
+     */
+    public int getInt(int off) {
+        checkOffsets(off, off + 4);
+        return (getByte0(off) << 24) |
+            (getUnsignedByte0(off + 1) << 16) |
+            (getUnsignedByte0(off + 2) << 8) |
+            getUnsignedByte0(off + 3);
+    }
+
+    /**
+     * Gets the {@code signed long} value at a particular offset.
+     *
+     * @param off {@code >= 0, < (size() - 7);} offset to fetch
+     * @return {@code signed int} at that offset
+     */
+    public long getLong(int off) {
+        checkOffsets(off, off + 8);
+        int part1 = (getByte0(off) << 24) |
+            (getUnsignedByte0(off + 1) << 16) |
+            (getUnsignedByte0(off + 2) << 8) |
+            getUnsignedByte0(off + 3);
+        int part2 = (getByte0(off + 4) << 24) |
+            (getUnsignedByte0(off + 5) << 16) |
+            (getUnsignedByte0(off + 6) << 8) |
+            getUnsignedByte0(off + 7);
+
+        return (part2 & 0xffffffffL) | ((long) part1) << 32;
+    }
+
+    /**
+     * Gets the {@code unsigned byte} value at a particular offset.
+     *
+     * @param off {@code >= 0, < size();} offset to fetch
+     * @return {@code unsigned byte} at that offset
+     */
+    public int getUnsignedByte(int off) {
+        checkOffsets(off, off + 1);
+        return getUnsignedByte0(off);
+    }
+
+    /**
+     * Gets the {@code unsigned short} value at a particular offset.
+     *
+     * @param off {@code >= 0, < (size() - 1);} offset to fetch
+     * @return {@code unsigned short} at that offset
+     */
+    public int getUnsignedShort(int off) {
+        checkOffsets(off, off + 2);
+        return (getUnsignedByte0(off) << 8) | getUnsignedByte0(off + 1);
+    }
+
+    /**
+     * Copies the contents of this instance into the given raw
+     * {@code byte[]} at the given offset. The given array must be
+     * large enough.
+     *
+     * @param out {@code non-null;} array to hold the output
+     * @param offset {@code non-null;} index into {@code out} for the first
+     * byte of output
+     */
+    public void getBytes(byte[] out, int offset) {
+        if ((out.length - offset) < size) {
+            throw new IndexOutOfBoundsException("(out.length - offset) < " +
+                                                "size()");
+        }
+
+        System.arraycopy(bytes, start, out, offset, size);
+    }
+
+    /**
+     * Checks a range of offsets for validity, throwing if invalid.
+     *
+     * @param s start offset (inclusive)
+     * @param e end offset (exclusive)
+     */
+    private void checkOffsets(int s, int e) {
+        if ((s < 0) || (e < s) || (e > size)) {
+            throw new IllegalArgumentException("bad range: " + s + ".." + e +
+                                               "; actual size " + size);
+        }
+    }
+
+    /**
+     * Gets the {@code signed byte} value at the given offset,
+     * without doing any argument checking.
+     *
+     * @param off offset to fetch
+     * @return byte at that offset
+     */
+    private int getByte0(int off) {
+        return bytes[start + off];
+    }
+
+    /**
+     * Gets the {@code unsigned byte} value at the given offset,
+     * without doing any argument checking.
+     *
+     * @param off offset to fetch
+     * @return byte at that offset
+     */
+    private int getUnsignedByte0(int off) {
+        return bytes[start + off] & 0xff;
+    }
+
+    /**
+     * Gets a {@code DataInputStream} that reads from this instance,
+     * with the cursor starting at the beginning of this instance's data.
+     * <b>Note:</b> The returned instance may be cast to {@link #GetCursor}
+     * if needed.
+     *
+     * @return {@code non-null;} an appropriately-constructed
+     * {@code DataInputStream} instance
+     */
+    public MyDataInputStream makeDataInputStream() {
+        return new MyDataInputStream(makeInputStream());
+    }
+
+    /**
+     * Gets a {@code InputStream} that reads from this instance,
+     * with the cursor starting at the beginning of this instance's data.
+     * <b>Note:</b> The returned instance may be cast to {@link #GetCursor}
+     * if needed.
+     *
+     * @return {@code non-null;} an appropriately-constructed
+     * {@code InputStream} instancex
+     */
+    public MyInputStream makeInputStream() {
+        return new MyInputStream();
+    }
+
+    /**
+     * Helper interface that allows one to get the cursor (of a stream).
+     */
+    public interface GetCursor {
+        /**
+         * Gets the current cursor.
+         *
+         * @return {@code 0..size();} the cursor
+         */
+        public int getCursor();
+    }
+
+    /**
+     * Helper class for {@link #makeInputStream}, which implements the
+     * stream functionality.
+     */
+    public class MyInputStream extends InputStream {
+        /** 0..size; the cursor */
+        private int cursor;
+
+        /** 0..size; the mark */
+        private int mark;
+
+        public MyInputStream() {
+            cursor = 0;
+            mark = 0;
+        }
+
+        public int read() throws IOException {
+            if (cursor >= size) {
+                return -1;
+            }
+
+            int result = getUnsignedByte0(cursor);
+            cursor++;
+            return result;
+        }
+
+        public int read(byte[] arr, int offset, int length) {
+            if ((offset + length) > arr.length) {
+                length = arr.length - offset;
+            }
+
+            int maxLength = size - cursor;
+            if (length > maxLength) {
+                length = maxLength;
+            }
+
+            System.arraycopy(bytes, cursor + start, arr, offset, length);
+            cursor += length;
+            return length;
+        }
+
+        public int available() {
+            return size - cursor;
+        }
+
+        public void mark(int reserve) {
+            mark = cursor;
+        }
+
+        public void reset() {
+            cursor = mark;
+        }
+
+        public boolean markSupported() {
+            return true;
+        }
+    }
+
+    /**
+     * Helper class for {@link #makeDataInputStream}. This is used
+     * simply so that the cursor of a wrapped {@link #MyInputStream}
+     * instance may be easily determined.
+     */
+    public static class MyDataInputStream extends DataInputStream {
+        /** {@code non-null;} the underlying {@link #MyInputStream} */
+        private final MyInputStream wrapped;
+
+        public MyDataInputStream(MyInputStream wrapped) {
+            super(wrapped);
+
+            this.wrapped = wrapped;
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/ByteArrayAnnotatedOutput.java b/dexgen/src/com/android/dexgen/util/ByteArrayAnnotatedOutput.java
new file mode 100644
index 0000000..5fad9a9
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/ByteArrayAnnotatedOutput.java
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+
+/**
+ * Implementation of {@link AnnotatedOutput} which stores the written data
+ * into a {@code byte[]}.
+ *
+ * <p><b>Note:</b> As per the {@link Output} interface, multi-byte
+ * writes all use little-endian order.</p>
+ */
+public final class ByteArrayAnnotatedOutput
+        implements AnnotatedOutput {
+    /** default size for stretchy instances */
+    private static final int DEFAULT_SIZE = 1000;
+
+    /**
+     * whether the instance is stretchy, that is, whether its array
+     * may be resized to increase capacity
+     */
+    private final boolean stretchy;
+
+    /** {@code non-null;} the data itself */
+    private byte[] data;
+
+    /** {@code >= 0;} current output cursor */
+    private int cursor;
+
+    /** whether annotations are to be verbose */
+    private boolean verbose;
+
+    /**
+     * {@code null-ok;} list of annotations, or {@code null} if this instance
+     * isn't keeping them
+     */
+    private ArrayList<Annotation> annotations;
+
+    /** {@code >= 40 (if used);} the desired maximum annotation width */
+    private int annotationWidth;
+
+    /**
+     * {@code >= 8 (if used);} the number of bytes of hex output to use
+     * in annotations
+     */
+    private int hexCols;
+
+    /**
+     * Constructs an instance with a fixed maximum size. Note that the
+     * given array is the only one that will be used to store data. In
+     * particular, no reallocation will occur in order to expand the
+     * capacity of the resulting instance. Also, the constructed
+     * instance does not keep annotations by default.
+     *
+     * @param data {@code non-null;} data array to use for output
+     */
+    public ByteArrayAnnotatedOutput(byte[] data) {
+        this(data, false);
+    }
+
+    /**
+     * Constructs a "stretchy" instance. The underlying array may be
+     * reallocated. The constructed instance does not keep annotations
+     * by default.
+     */
+    public ByteArrayAnnotatedOutput() {
+        this(new byte[DEFAULT_SIZE], true);
+    }
+
+    /**
+     * Internal constructor.
+     *
+     * @param data {@code non-null;} data array to use for output
+     * @param stretchy whether the instance is to be stretchy
+     */
+    private ByteArrayAnnotatedOutput(byte[] data, boolean stretchy) {
+        if (data == null) {
+            throw new NullPointerException("data == null");
+        }
+
+        this.stretchy = stretchy;
+        this.data = data;
+        this.cursor = 0;
+        this.verbose = false;
+        this.annotations = null;
+        this.annotationWidth = 0;
+        this.hexCols = 0;
+    }
+
+    /**
+     * Gets the underlying {@code byte[]} of this instance, which
+     * may be larger than the number of bytes written
+     *
+     * @see #toByteArray
+     *
+     * @return {@code non-null;} the {@code byte[]}
+     */
+    public byte[] getArray() {
+        return data;
+    }
+
+    /**
+     * Constructs and returns a new {@code byte[]} that contains
+     * the written contents exactly (that is, with no extra unwritten
+     * bytes at the end).
+     *
+     * @see #getArray
+     *
+     * @return {@code non-null;} an appropriately-constructed array
+     */
+    public byte[] toByteArray() {
+        byte[] result = new byte[cursor];
+        System.arraycopy(data, 0, result, 0, cursor);
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public int getCursor() {
+        return cursor;
+    }
+
+    /** {@inheritDoc} */
+    public void assertCursor(int expectedCursor) {
+        if (cursor != expectedCursor) {
+            throw new ExceptionWithContext("expected cursor " +
+                    expectedCursor + "; actual value: " + cursor);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void writeByte(int value) {
+        int writeAt = cursor;
+        int end = writeAt + 1;
+
+        if (stretchy) {
+            ensureCapacity(end);
+        } else if (end > data.length) {
+            throwBounds();
+            return;
+        }
+
+        data[writeAt] = (byte) value;
+        cursor = end;
+    }
+
+    /** {@inheritDoc} */
+    public void writeShort(int value) {
+        int writeAt = cursor;
+        int end = writeAt + 2;
+
+        if (stretchy) {
+            ensureCapacity(end);
+        } else if (end > data.length) {
+            throwBounds();
+            return;
+        }
+
+        data[writeAt] = (byte) value;
+        data[writeAt + 1] = (byte) (value >> 8);
+        cursor = end;
+    }
+
+    /** {@inheritDoc} */
+    public void writeInt(int value) {
+        int writeAt = cursor;
+        int end = writeAt + 4;
+
+        if (stretchy) {
+            ensureCapacity(end);
+        } else if (end > data.length) {
+            throwBounds();
+            return;
+        }
+
+        data[writeAt] = (byte) value;
+        data[writeAt + 1] = (byte) (value >> 8);
+        data[writeAt + 2] = (byte) (value >> 16);
+        data[writeAt + 3] = (byte) (value >> 24);
+        cursor = end;
+    }
+
+    /** {@inheritDoc} */
+    public void writeLong(long value) {
+        int writeAt = cursor;
+        int end = writeAt + 8;
+
+        if (stretchy) {
+            ensureCapacity(end);
+        } else if (end > data.length) {
+            throwBounds();
+            return;
+        }
+
+        int half = (int) value;
+        data[writeAt] = (byte) half;
+        data[writeAt + 1] = (byte) (half >> 8);
+        data[writeAt + 2] = (byte) (half >> 16);
+        data[writeAt + 3] = (byte) (half >> 24);
+
+        half = (int) (value >> 32);
+        data[writeAt + 4] = (byte) half;
+        data[writeAt + 5] = (byte) (half >> 8);
+        data[writeAt + 6] = (byte) (half >> 16);
+        data[writeAt + 7] = (byte) (half >> 24);
+
+        cursor = end;
+    }
+
+    /** {@inheritDoc} */
+    public int writeUnsignedLeb128(int value) {
+        int remaining = value >> 7;
+        int count = 0;
+
+        while (remaining != 0) {
+            writeByte((value & 0x7f) | 0x80);
+            value = remaining;
+            remaining >>= 7;
+            count++;
+        }
+
+        writeByte(value & 0x7f);
+        return count + 1;
+    }
+
+    /** {@inheritDoc} */
+    public int writeSignedLeb128(int value) {
+        int remaining = value >> 7;
+        int count = 0;
+        boolean hasMore = true;
+        int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1;
+
+        while (hasMore) {
+            hasMore = (remaining != end)
+                || ((remaining & 1) != ((value >> 6) & 1));
+
+            writeByte((value & 0x7f) | (hasMore ? 0x80 : 0));
+            value = remaining;
+            remaining >>= 7;
+            count++;
+        }
+
+        return count;
+    }
+
+    /** {@inheritDoc} */
+    public void write(ByteArray bytes) {
+        int blen = bytes.size();
+        int writeAt = cursor;
+        int end = writeAt + blen;
+
+        if (stretchy) {
+            ensureCapacity(end);
+        } else if (end > data.length) {
+            throwBounds();
+            return;
+        }
+
+        bytes.getBytes(data, writeAt);
+        cursor = end;
+    }
+
+    /** {@inheritDoc} */
+    public void write(byte[] bytes, int offset, int length) {
+        int writeAt = cursor;
+        int end = writeAt + length;
+        int bytesEnd = offset + length;
+
+        // twos-complement math trick: ((x < 0) || (y < 0)) <=> ((x|y) < 0)
+        if (((offset | length | end) < 0) || (bytesEnd > bytes.length)) {
+            throw new IndexOutOfBoundsException("bytes.length " +
+                                                bytes.length + "; " +
+                                                offset + "..!" + end);
+        }
+
+        if (stretchy) {
+            ensureCapacity(end);
+        } else if (end > data.length) {
+            throwBounds();
+            return;
+        }
+
+        System.arraycopy(bytes, offset, data, writeAt, length);
+        cursor = end;
+    }
+
+    /** {@inheritDoc} */
+    public void write(byte[] bytes) {
+        write(bytes, 0, bytes.length);
+    }
+
+    /** {@inheritDoc} */
+    public void writeZeroes(int count) {
+        if (count < 0) {
+            throw new IllegalArgumentException("count < 0");
+        }
+
+        int end = cursor + count;
+
+        if (stretchy) {
+            ensureCapacity(end);
+        } else if (end > data.length) {
+            throwBounds();
+            return;
+        }
+
+        /*
+         * There is no need to actually write zeroes, since the array is
+         * already preinitialized with zeroes.
+         */
+
+        cursor = end;
+    }
+
+    /** {@inheritDoc} */
+    public void alignTo(int alignment) {
+        int mask = alignment - 1;
+
+        if ((alignment < 0) || ((mask & alignment) != 0)) {
+            throw new IllegalArgumentException("bogus alignment");
+        }
+
+        int end = (cursor + mask) & ~mask;
+
+        if (stretchy) {
+            ensureCapacity(end);
+        } else if (end > data.length) {
+            throwBounds();
+            return;
+        }
+
+        /*
+         * There is no need to actually write zeroes, since the array is
+         * already preinitialized with zeroes.
+         */
+
+        cursor = end;
+    }
+
+    /** {@inheritDoc} */
+    public boolean annotates() {
+        return (annotations != null);
+    }
+
+    /** {@inheritDoc} */
+    public boolean isVerbose() {
+        return verbose;
+    }
+
+    /** {@inheritDoc} */
+    public void annotate(String msg) {
+        if (annotations == null) {
+            return;
+        }
+
+        endAnnotation();
+        annotations.add(new Annotation(cursor, msg));
+    }
+
+    /** {@inheritDoc} */
+    public void annotate(int amt, String msg) {
+        if (annotations == null) {
+            return;
+        }
+
+        endAnnotation();
+
+        int asz = annotations.size();
+        int lastEnd = (asz == 0) ? 0 : annotations.get(asz - 1).getEnd();
+        int startAt;
+
+        if (lastEnd <= cursor) {
+            startAt = cursor;
+        } else {
+            startAt = lastEnd;
+        }
+
+        annotations.add(new Annotation(startAt, startAt + amt, msg));
+    }
+
+    /** {@inheritDoc} */
+    public void endAnnotation() {
+        if (annotations == null) {
+            return;
+        }
+
+        int sz = annotations.size();
+
+        if (sz != 0) {
+            annotations.get(sz - 1).setEndIfUnset(cursor);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public int getAnnotationWidth() {
+        int leftWidth = 8 + (hexCols * 2) + (hexCols / 2);
+
+        return annotationWidth - leftWidth;
+    }
+
+    /**
+     * Indicates that this instance should keep annotations. This method may
+     * be called only once per instance, and only before any data has been
+     * written to the it.
+     *
+     * @param annotationWidth {@code >= 40;} the desired maximum annotation width
+     * @param verbose whether or not to indicate verbose annotations
+     */
+    public void enableAnnotations(int annotationWidth, boolean verbose) {
+        if ((annotations != null) || (cursor != 0)) {
+            throw new RuntimeException("cannot enable annotations");
+        }
+
+        if (annotationWidth < 40) {
+            throw new IllegalArgumentException("annotationWidth < 40");
+        }
+
+        int hexCols = (((annotationWidth - 7) / 15) + 1) & ~1;
+        if (hexCols < 6) {
+            hexCols = 6;
+        } else if (hexCols > 10) {
+            hexCols = 10;
+        }
+
+        this.annotations = new ArrayList<Annotation>(1000);
+        this.annotationWidth = annotationWidth;
+        this.hexCols = hexCols;
+        this.verbose = verbose;
+    }
+
+    /**
+     * Finishes up annotation processing. This closes off any open
+     * annotations and removes annotations that don't refer to written
+     * data.
+     */
+    public void finishAnnotating() {
+        // Close off the final annotation, if any.
+        endAnnotation();
+
+        // Remove annotations that refer to unwritten data.
+        if (annotations != null) {
+            int asz = annotations.size();
+            while (asz > 0) {
+                Annotation last = annotations.get(asz - 1);
+                if (last.getStart() > cursor) {
+                    annotations.remove(asz - 1);
+                    asz--;
+                } else if (last.getEnd() > cursor) {
+                    last.setEnd(cursor);
+                    break;
+                } else {
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Writes the annotated content of this instance to the given writer.
+     *
+     * @param out {@code non-null;} where to write to
+     */
+    public void writeAnnotationsTo(Writer out) throws IOException {
+        int width2 = getAnnotationWidth();
+        int width1 = annotationWidth - width2 - 1;
+
+        TwoColumnOutput twoc = new TwoColumnOutput(out, width1, width2, "|");
+        Writer left = twoc.getLeft();
+        Writer right = twoc.getRight();
+        int leftAt = 0; // left-hand byte output cursor
+        int rightAt = 0; // right-hand annotation index
+        int rightSz = annotations.size();
+
+        while ((leftAt < cursor) && (rightAt < rightSz)) {
+            Annotation a = annotations.get(rightAt);
+            int start = a.getStart();
+            int end;
+            String text;
+
+            if (leftAt < start) {
+                // This is an area with no annotation.
+                end = start;
+                start = leftAt;
+                text = "";
+            } else {
+                // This is an area with an annotation.
+                end = a.getEnd();
+                text = a.getText();
+                rightAt++;
+            }
+
+            left.write(Hex.dump(data, start, end - start, start, hexCols, 6));
+            right.write(text);
+            twoc.flush();
+            leftAt = end;
+        }
+
+        if (leftAt < cursor) {
+            // There is unannotated output at the end.
+            left.write(Hex.dump(data, leftAt, cursor - leftAt, leftAt,
+                                hexCols, 6));
+        }
+
+        while (rightAt < rightSz) {
+            // There are zero-byte annotations at the end.
+            right.write(annotations.get(rightAt).getText());
+            rightAt++;
+        }
+
+        twoc.flush();
+    }
+
+    /**
+     * Throws the excpetion for when an attempt is made to write past the
+     * end of the instance.
+     */
+    private static void throwBounds() {
+        throw new IndexOutOfBoundsException("attempt to write past the end");
+    }
+
+    /**
+     * Reallocates the underlying array if necessary. Calls to this method
+     * should be guarded by a test of {@link #stretchy}.
+     *
+     * @param desiredSize {@code >= 0;} the desired minimum total size of the array
+     */
+    private void ensureCapacity(int desiredSize) {
+        if (data.length < desiredSize) {
+            byte[] newData = new byte[desiredSize * 2 + 1000];
+            System.arraycopy(data, 0, newData, 0, cursor);
+            data = newData;
+        }
+    }
+
+    /**
+     * Annotation on output.
+     */
+    private static class Annotation {
+        /** {@code >= 0;} start of annotated range (inclusive) */
+        private final int start;
+
+        /**
+         * {@code >= 0;} end of annotated range (exclusive);
+         * {@code Integer.MAX_VALUE} if unclosed
+         */
+        private int end;
+
+        /** {@code non-null;} annotation text */
+        private final String text;
+
+        /**
+         * Constructs an instance.
+         *
+         * @param start {@code >= 0;} start of annotated range
+         * @param end {@code >= start;} end of annotated range (exclusive) or
+         * {@code Integer.MAX_VALUE} if unclosed
+         * @param text {@code non-null;} annotation text
+         */
+        public Annotation(int start, int end, String text) {
+            this.start = start;
+            this.end = end;
+            this.text = text;
+        }
+
+        /**
+         * Constructs an instance. It is initally unclosed.
+         *
+         * @param start {@code >= 0;} start of annotated range
+         * @param text {@code non-null;} annotation text
+         */
+        public Annotation(int start, String text) {
+            this(start, Integer.MAX_VALUE, text);
+        }
+
+        /**
+         * Sets the end as given, but only if the instance is unclosed;
+         * otherwise, do nothing.
+         *
+         * @param end {@code >= start;} the end
+         */
+        public void setEndIfUnset(int end) {
+            if (this.end == Integer.MAX_VALUE) {
+                this.end = end;
+            }
+        }
+
+        /**
+         * Sets the end as given.
+         *
+         * @param end {@code >= start;} the end
+         */
+        public void setEnd(int end) {
+            this.end = end;
+        }
+
+        /**
+         * Gets the start.
+         *
+         * @return the start
+         */
+        public int getStart() {
+            return start;
+        }
+
+        /**
+         * Gets the end.
+         *
+         * @return the end
+         */
+        public int getEnd() {
+            return end;
+        }
+
+        /**
+         * Gets the text.
+         *
+         * @return {@code non-null;} the text
+         */
+        public String getText() {
+            return text;
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/ExceptionWithContext.java b/dexgen/src/com/android/dexgen/util/ExceptionWithContext.java
new file mode 100644
index 0000000..67e7f72
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/ExceptionWithContext.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+/**
+ * Exception which carries around structured context.
+ */
+public class ExceptionWithContext
+        extends RuntimeException {
+    /** {@code non-null;} human-oriented context of the exception */
+    private StringBuffer context;
+
+    /**
+     * Augments the given exception with the given context, and return the
+     * result. The result is either the given exception if it was an
+     * {@link ExceptionWithContext}, or a newly-constructed exception if it
+     * was not.
+     *
+     * @param ex {@code non-null;} the exception to augment
+     * @param str {@code non-null;} context to add
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static ExceptionWithContext withContext(Throwable ex, String str) {
+        ExceptionWithContext ewc;
+
+        if (ex instanceof ExceptionWithContext) {
+            ewc = (ExceptionWithContext) ex;
+        } else {
+            ewc = new ExceptionWithContext(ex);
+        }
+
+        ewc.addContext(str);
+        return ewc;
+    }
+
+    /**
+     * Constructs an instance.
+     *
+     * @param message human-oriented message
+     */
+    public ExceptionWithContext(String message) {
+        this(message, null);
+    }
+
+    /**
+     * Constructs an instance.
+     *
+     * @param cause {@code null-ok;} exception that caused this one
+     */
+    public ExceptionWithContext(Throwable cause) {
+        this(null, cause);
+    }
+
+    /**
+     * Constructs an instance.
+     *
+     * @param message human-oriented message
+     * @param cause {@code null-ok;} exception that caused this one
+     */
+    public ExceptionWithContext(String message, Throwable cause) {
+        super((message != null) ? message :
+              (cause != null) ? cause.getMessage() : null,
+              cause);
+
+        if (cause instanceof ExceptionWithContext) {
+            String ctx = ((ExceptionWithContext) cause).context.toString();
+            context = new StringBuffer(ctx.length() + 200);
+            context.append(ctx);
+        } else {
+            context = new StringBuffer(200);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void printStackTrace(PrintStream out) {
+        super.printStackTrace(out);
+        out.println(context);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void printStackTrace(PrintWriter out) {
+        super.printStackTrace(out);
+        out.println(context);
+    }
+
+    /**
+     * Adds a line of context to this instance.
+     *
+     * @param str {@code non-null;} new context
+     */
+    public void addContext(String str) {
+        if (str == null) {
+            throw new NullPointerException("str == null");
+        }
+
+        context.append(str);
+        if (!str.endsWith("\n")) {
+            context.append('\n');
+        }
+    }
+
+    /**
+     * Gets the context.
+     *
+     * @return {@code non-null;} the context
+     */
+    public String getContext() {
+        return context.toString();
+    }
+
+    /**
+     * Prints the message and context.
+     *
+     * @param out {@code non-null;} where to print to
+     */
+    public void printContext(PrintStream out) {
+        out.println(getMessage());
+        out.print(context);
+    }
+
+    /**
+     * Prints the message and context.
+     *
+     * @param out {@code non-null;} where to print to
+     */
+    public void printContext(PrintWriter out) {
+        out.println(getMessage());
+        out.print(context);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/FileUtils.java b/dexgen/src/com/android/dexgen/util/FileUtils.java
new file mode 100644
index 0000000..a5bbff4
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/FileUtils.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * File I/O utilities.
+ */
+public final class FileUtils {
+    /**
+     * This class is uninstantiable.
+     */
+    private FileUtils() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Reads the named file, translating {@link IOException} to a
+     * {@link RuntimeException} of some sort.
+     *
+     * @param fileName {@code non-null;} name of the file to read
+     * @return {@code non-null;} contents of the file
+     */
+    public static byte[] readFile(String fileName) {
+        File file = new File(fileName);
+        return readFile(file);
+    }
+
+    /**
+     * Reads the given file, translating {@link IOException} to a
+     * {@link RuntimeException} of some sort.
+     *
+     * @param file {@code non-null;} the file to read
+     * @return {@code non-null;} contents of the file
+     */
+    public static byte[] readFile(File file) {
+        if (!file.exists()) {
+            throw new RuntimeException(file + ": file not found");
+        }
+
+        if (!file.isFile()) {
+            throw new RuntimeException(file + ": not a file");
+        }
+
+        if (!file.canRead()) {
+            throw new RuntimeException(file + ": file not readable");
+        }
+
+        long longLength = file.length();
+        int length = (int) longLength;
+        if (length != longLength) {
+            throw new RuntimeException(file + ": file too long");
+        }
+
+        byte[] result = new byte[length];
+
+        try {
+            FileInputStream in = new FileInputStream(file);
+            int at = 0;
+            while (length > 0) {
+                int amt = in.read(result, at, length);
+                if (amt == -1) {
+                    throw new RuntimeException(file + ": unexpected EOF");
+                }
+                at += amt;
+                length -= amt;
+            }
+            in.close();
+        } catch (IOException ex) {
+            throw new RuntimeException(file + ": trouble reading", ex);
+        }
+
+        return result;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/FixedSizeList.java b/dexgen/src/com/android/dexgen/util/FixedSizeList.java
new file mode 100644
index 0000000..039b5b0
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/FixedSizeList.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+import java.util.Arrays;
+
+/**
+ * Simple (mostly) fixed-size list of objects, which may be made immutable.
+ */
+public class FixedSizeList
+        extends MutabilityControl implements ToHuman {
+    /** {@code non-null;} array of elements */
+    private Object[] arr;
+
+    /**
+     * Constructs an instance. All indices initially contain {@code null}.
+     *
+     * @param size the size of the list
+     */
+    public FixedSizeList(int size) {
+        super(size != 0);
+
+        try {
+            arr = new Object[size];
+        } catch (NegativeArraySizeException ex) {
+            // Translate the exception.
+            throw new IllegalArgumentException("size < 0");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            // Easy out.
+            return true;
+        }
+
+        if ((other == null) || (getClass() != other.getClass())) {
+            // Another easy out.
+            return false;
+        }
+
+        FixedSizeList list = (FixedSizeList) other;
+        return Arrays.equals(arr, list.arr);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(arr);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        String name = getClass().getName();
+
+        return toString0(name.substring(name.lastIndexOf('.') + 1) + '{',
+                         ", ",
+                         "}",
+                         false);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * This method will only work if every element of the list
+     * implements {@link ToHuman}.
+     */
+    public String toHuman() {
+        String name = getClass().getName();
+
+        return toString0(name.substring(name.lastIndexOf('.') + 1) + '{',
+                         ", ",
+                         "}",
+                         true);
+    }
+
+    /**
+     * Gets a customized string form for this instance.
+     *
+     * @param prefix {@code null-ok;} prefix for the start of the result
+     * @param separator {@code null-ok;} separator to insert between each item
+     * @param suffix {@code null-ok;} suffix for the end of the result
+     * @return {@code non-null;} the custom string
+     */
+    public String toString(String prefix, String separator, String suffix) {
+        return toString0(prefix, separator, suffix, false);
+    }
+
+    /**
+     * Gets a customized human string for this instance. This method will
+     * only work if every element of the list implements {@link
+     * ToHuman}.
+     *
+     * @param prefix {@code null-ok;} prefix for the start of the result
+     * @param separator {@code null-ok;} separator to insert between each item
+     * @param suffix {@code null-ok;} suffix for the end of the result
+     * @return {@code non-null;} the custom string
+     */
+    public String toHuman(String prefix, String separator, String suffix) {
+        return toString0(prefix, separator, suffix, true);
+    }
+
+    /**
+     * Gets the number of elements in this list.
+     */
+    public final int size() {
+        return arr.length;
+    }
+
+    /**
+     * Shrinks this instance to fit, by removing any unset
+     * ({@code null}) elements, leaving the remaining elements in
+     * their original order.
+     */
+    public void shrinkToFit() {
+        int sz = arr.length;
+        int newSz = 0;
+
+        for (int i = 0; i < sz; i++) {
+            if (arr[i] != null) {
+                newSz++;
+            }
+        }
+
+        if (sz == newSz) {
+            return;
+        }
+
+        throwIfImmutable();
+
+        Object[] newa = new Object[newSz];
+        int at = 0;
+
+        for (int i = 0; i < sz; i++) {
+            Object one = arr[i];
+            if (one != null) {
+                newa[at] = one;
+                at++;
+            }
+        }
+
+        arr = newa;
+        if (newSz == 0) {
+            setImmutable();
+        }
+    }
+
+    /**
+     * Gets the indicated element. It is an error to call this with the
+     * index for an element which was never set; if you do that, this
+     * will throw {@code NullPointerException}. This method is
+     * protected so that subclasses may offer a safe type-checked
+     * public interface to their clients.
+     *
+     * @param n {@code >= 0, < size();} which element
+     * @return {@code non-null;} the indicated element
+     */
+    protected final Object get0(int n) {
+        try {
+            Object result = arr[n];
+
+            if (result == null) {
+                throw new NullPointerException("unset: " + n);
+            }
+
+            return result;
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            // Translate the exception.
+            return throwIndex(n);
+        }
+    }
+
+    /**
+     * Gets the indicated element, allowing {@code null}s to be
+     * returned. This method is protected so that subclasses may
+     * (optionally) offer a safe type-checked public interface to
+     * their clients.
+     *
+     * @param n {@code >= 0, < size();} which element
+     * @return {@code null-ok;} the indicated element
+     */
+    protected final Object getOrNull0(int n) {
+        return arr[n];
+    }
+
+    /**
+     * Sets the element at the given index, but without doing any type
+     * checks on the element. This method is protected so that
+     * subclasses may offer a safe type-checked public interface to
+     * their clients.
+     *
+     * @param n {@code >= 0, < size();} which element
+     * @param obj {@code null-ok;} the value to store
+     */
+    protected final void set0(int n, Object obj) {
+        throwIfImmutable();
+
+        try {
+            arr[n] = obj;
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            // Translate the exception.
+            throwIndex(n);
+        }
+    }
+
+    /**
+     * Throws the appropriate exception for the given index value.
+     *
+     * @param n the index value
+     * @return never
+     * @throws IndexOutOfBoundsException always thrown
+     */
+    private Object throwIndex(int n) {
+        if (n < 0) {
+            throw new IndexOutOfBoundsException("n < 0");
+        }
+
+        throw new IndexOutOfBoundsException("n >= size()");
+    }
+
+    /**
+     * Helper for {@link #toString} and {@link #toHuman}, which both of
+     * those call to pretty much do everything.
+     *
+     * @param prefix {@code null-ok;} prefix for the start of the result
+     * @param separator {@code null-ok;} separator to insert between each item
+     * @param suffix {@code null-ok;} suffix for the end of the result
+     * @param human whether the output is to be human
+     * @return {@code non-null;} the custom string
+     */
+    private String toString0(String prefix, String separator, String suffix,
+                             boolean human) {
+        int len = arr.length;
+        StringBuffer sb = new StringBuffer(len * 10 + 10);
+
+        if (prefix != null) {
+            sb.append(prefix);
+        }
+
+        for (int i = 0; i < len; i++) {
+            if ((i != 0) && (separator != null)) {
+                sb.append(separator);
+            }
+
+            if (human) {
+                sb.append(((ToHuman) arr[i]).toHuman());
+            } else {
+                sb.append(arr[i]);
+            }
+        }
+
+        if (suffix != null) {
+            sb.append(suffix);
+        }
+
+        return sb.toString();
+    }
+
+}
diff --git a/dexgen/src/com/android/dexgen/util/Hex.java b/dexgen/src/com/android/dexgen/util/Hex.java
new file mode 100644
index 0000000..4dafb77
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/Hex.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+/**
+ * Utilities for formatting numbers as hexadecimal.
+ */
+public final class Hex {
+    /**
+     * This class is uninstantiable.
+     */
+    private Hex() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Formats a {@code long} as an 8-byte unsigned hex value.
+     *
+     * @param v value to format
+     * @return {@code non-null;} formatted form
+     */
+    public static String u8(long v) {
+        char[] result = new char[16];
+        for (int i = 0; i < 16; i++) {
+            result[15 - i] = Character.forDigit((int) v & 0x0f, 16);
+            v >>= 4;
+        }
+
+        return new String(result);
+    }
+
+    /**
+     * Formats an {@code int} as a 4-byte unsigned hex value.
+     *
+     * @param v value to format
+     * @return {@code non-null;} formatted form
+     */
+    public static String u4(int v) {
+        char[] result = new char[8];
+        for (int i = 0; i < 8; i++) {
+            result[7 - i] = Character.forDigit(v & 0x0f, 16);
+            v >>= 4;
+        }
+
+        return new String(result);
+    }
+
+    /**
+     * Formats an {@code int} as a 3-byte unsigned hex value.
+     *
+     * @param v value to format
+     * @return {@code non-null;} formatted form
+     */
+    public static String u3(int v) {
+        char[] result = new char[6];
+        for (int i = 0; i < 6; i++) {
+            result[5 - i] = Character.forDigit(v & 0x0f, 16);
+            v >>= 4;
+        }
+
+        return new String(result);
+    }
+
+    /**
+     * Formats an {@code int} as a 2-byte unsigned hex value.
+     *
+     * @param v value to format
+     * @return {@code non-null;} formatted form
+     */
+    public static String u2(int v) {
+        char[] result = new char[4];
+        for (int i = 0; i < 4; i++) {
+            result[3 - i] = Character.forDigit(v & 0x0f, 16);
+            v >>= 4;
+        }
+
+        return new String(result);
+    }
+
+    /**
+     * Formats an {@code int} as either a 2-byte unsigned hex value
+     * (if the value is small enough) or a 4-byte unsigned hex value (if
+     * not).
+     *
+     * @param v value to format
+     * @return {@code non-null;} formatted form
+     */
+    public static String u2or4(int v) {
+        if (v == (char) v) {
+            return u2(v);
+        } else {
+            return u4(v);
+        }
+    }
+
+    /**
+     * Formats an {@code int} as a 1-byte unsigned hex value.
+     *
+     * @param v value to format
+     * @return {@code non-null;} formatted form
+     */
+    public static String u1(int v) {
+        char[] result = new char[2];
+        for (int i = 0; i < 2; i++) {
+            result[1 - i] = Character.forDigit(v & 0x0f, 16);
+            v >>= 4;
+        }
+
+        return new String(result);
+    }
+
+    /**
+     * Formats an {@code int} as a 4-bit unsigned hex nibble.
+     *
+     * @param v value to format
+     * @return {@code non-null;} formatted form
+     */
+    public static String uNibble(int v) {
+        char[] result = new char[1];
+
+        result[0] = Character.forDigit(v & 0x0f, 16);
+        return new String(result);
+    }
+
+    /**
+     * Formats a {@code long} as an 8-byte signed hex value.
+     *
+     * @param v value to format
+     * @return {@code non-null;} formatted form
+     */
+    public static String s8(long v) {
+        char[] result = new char[17];
+
+        if (v < 0) {
+            result[0] = '-';
+            v = -v;
+        } else {
+            result[0] = '+';
+        }
+
+        for (int i = 0; i < 16; i++) {
+            result[16 - i] = Character.forDigit((int) v & 0x0f, 16);
+            v >>= 4;
+        }
+
+        return new String(result);
+    }
+
+    /**
+     * Formats an {@code int} as a 4-byte signed hex value.
+     *
+     * @param v value to format
+     * @return {@code non-null;} formatted form
+     */
+    public static String s4(int v) {
+        char[] result = new char[9];
+
+        if (v < 0) {
+            result[0] = '-';
+            v = -v;
+        } else {
+            result[0] = '+';
+        }
+
+        for (int i = 0; i < 8; i++) {
+            result[8 - i] = Character.forDigit(v & 0x0f, 16);
+            v >>= 4;
+        }
+
+        return new String(result);
+    }
+
+    /**
+     * Formats an {@code int} as a 2-byte signed hex value.
+     *
+     * @param v value to format
+     * @return {@code non-null;} formatted form
+     */
+    public static String s2(int v) {
+        char[] result = new char[5];
+
+        if (v < 0) {
+            result[0] = '-';
+            v = -v;
+        } else {
+            result[0] = '+';
+        }
+
+        for (int i = 0; i < 4; i++) {
+            result[4 - i] = Character.forDigit(v & 0x0f, 16);
+            v >>= 4;
+        }
+
+        return new String(result);
+    }
+
+    /**
+     * Formats an {@code int} as a 1-byte signed hex value.
+     *
+     * @param v value to format
+     * @return {@code non-null;} formatted form
+     */
+    public static String s1(int v) {
+        char[] result = new char[3];
+
+        if (v < 0) {
+            result[0] = '-';
+            v = -v;
+        } else {
+            result[0] = '+';
+        }
+
+        for (int i = 0; i < 2; i++) {
+            result[2 - i] = Character.forDigit(v & 0x0f, 16);
+            v >>= 4;
+        }
+
+        return new String(result);
+    }
+
+    /**
+     * Formats a hex dump of a portion of a {@code byte[]}. The result
+     * is always newline-terminated, unless the passed-in length was zero,
+     * in which case the result is always the empty string ({@code ""}).
+     *
+     * @param arr {@code non-null;} array to format
+     * @param offset {@code >= 0;} offset to the part to dump
+     * @param length {@code >= 0;} number of bytes to dump
+     * @param outOffset {@code >= 0;} first output offset to print
+     * @param bpl {@code >= 0;} number of bytes of output per line
+     * @param addressLength {@code {2,4,6,8};} number of characters for each address
+     * header
+     * @return {@code non-null;} a string of the dump
+     */
+    public static String dump(byte[] arr, int offset, int length,
+                              int outOffset, int bpl, int addressLength) {
+        int end = offset + length;
+
+        // twos-complement math trick: ((x < 0) || (y < 0)) <=> ((x|y) < 0)
+        if (((offset | length | end) < 0) || (end > arr.length)) {
+            throw new IndexOutOfBoundsException("arr.length " +
+                                                arr.length + "; " +
+                                                offset + "..!" + end);
+        }
+
+        if (outOffset < 0) {
+            throw new IllegalArgumentException("outOffset < 0");
+        }
+
+        if (length == 0) {
+            return "";
+        }
+
+        StringBuffer sb = new StringBuffer(length * 4 + 6);
+        boolean bol = true;
+        int col = 0;
+
+        while (length > 0) {
+            if (col == 0) {
+                String astr;
+                switch (addressLength) {
+                    case 2:  astr = Hex.u1(outOffset); break;
+                    case 4:  astr = Hex.u2(outOffset); break;
+                    case 6:  astr = Hex.u3(outOffset); break;
+                    default: astr = Hex.u4(outOffset); break;
+                }
+                sb.append(astr);
+                sb.append(": ");
+            } else if ((col & 1) == 0) {
+                sb.append(' ');
+            }
+            sb.append(Hex.u1(arr[offset]));
+            outOffset++;
+            offset++;
+            col++;
+            if (col == bpl) {
+                sb.append('\n');
+                col = 0;
+            }
+            length--;
+        }
+
+        if (col != 0) {
+            sb.append('\n');
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/HexParser.java b/dexgen/src/com/android/dexgen/util/HexParser.java
new file mode 100644
index 0000000..cc4f909
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/HexParser.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+/**
+ * Utilities for parsing hexadecimal text.
+ */
+public final class HexParser {
+    /**
+     * This class is uninstantiable.
+     */
+    private HexParser() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Parses the given text as hex, returning a {@code byte[]}
+     * corresponding to the text. The format is simple: Each line may
+     * start with a hex offset followed by a colon (which is verified
+     * and presumably used just as a comment), and then consists of
+     * hex digits freely interspersed with whitespace. If a pound sign
+     * is encountered, it and the rest of the line are ignored as a
+     * comment. If a double quote is encountered, then the ASCII value
+     * of the subsequent characters is used, until the next double
+     * quote. Quoted strings may not span multiple lines.
+     *
+     * @param src {@code non-null;} the source string
+     * @return {@code non-null;} the parsed form
+     */
+    public static byte[] parse(String src) {
+        int len = src.length();
+        byte[] result = new byte[len / 2];
+        int at = 0;
+        int outAt = 0;
+
+        while (at < len) {
+            int nlAt = src.indexOf('\n', at);
+            if (nlAt < 0) {
+                nlAt = len;
+            }
+            int poundAt = src.indexOf('#', at);
+
+            String line;
+            if ((poundAt >= 0) && (poundAt < nlAt)) {
+                line = src.substring(at, poundAt);
+            } else {
+                line = src.substring(at, nlAt);
+            }
+            at = nlAt + 1;
+
+            int colonAt = line.indexOf(':');
+
+            atCheck:
+            if (colonAt != -1) {
+                int quoteAt = line.indexOf('\"');
+                if ((quoteAt != -1) && (quoteAt < colonAt)) {
+                    break atCheck;
+                }
+
+                String atStr = line.substring(0, colonAt).trim();
+                line = line.substring(colonAt + 1);
+                int alleged = Integer.parseInt(atStr, 16);
+                if (alleged != outAt) {
+                    throw new RuntimeException("bogus offset marker: " +
+                                               atStr);
+                }
+            }
+
+            int lineLen = line.length();
+            int value = -1;
+            boolean quoteMode = false;
+
+            for (int i = 0; i < lineLen; i++) {
+                char c = line.charAt(i);
+
+                if (quoteMode) {
+                    if (c == '\"') {
+                        quoteMode = false;
+                    } else {
+                        result[outAt] = (byte) c;
+                        outAt++;
+                    }
+                    continue;
+                }
+
+                if (c <= ' ') {
+                    continue;
+                }
+                if (c == '\"') {
+                    if (value != -1) {
+                        throw new RuntimeException("spare digit around " +
+                                                   "offset " + Hex.u4(outAt));
+                    }
+                    quoteMode = true;
+                    continue;
+                }
+
+                int digVal = Character.digit(c, 16);
+                if (digVal == -1) {
+                    throw new RuntimeException("bogus digit character: \"" +
+                                               c + "\"");
+                }
+                if (value == -1) {
+                    value = digVal;
+                } else {
+                    result[outAt] = (byte) ((value << 4) | digVal);
+                    outAt++;
+                    value = -1;
+                }
+            }
+
+            if (value != -1) {
+                throw new RuntimeException("spare digit around offset " +
+                                           Hex.u4(outAt));
+            }
+
+            if (quoteMode) {
+                throw new RuntimeException("unterminated quote around " +
+                                           "offset " + Hex.u4(outAt));
+            }
+        }
+
+        if (outAt < result.length) {
+            byte[] newr = new byte[outAt];
+            System.arraycopy(result, 0, newr, 0, outAt);
+            result = newr;
+        }
+
+        return result;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/IndentingWriter.java b/dexgen/src/com/android/dexgen/util/IndentingWriter.java
new file mode 100644
index 0000000..05d4b0c
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/IndentingWriter.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Writer that wraps another writer and passes width-limited and
+ * optionally-prefixed output to its subordinate. When lines are
+ * wrapped they are automatically indented based on the start of the
+ * line.
+ */
+public final class IndentingWriter extends FilterWriter {
+    /** {@code null-ok;} optional prefix for every line */
+    private final String prefix;
+
+    /** {@code > 0;} the maximum output width */
+    private final int width;
+
+    /** {@code > 0;} the maximum indent */
+    private final int maxIndent;
+
+    /** {@code >= 0;} current output column (zero-based) */
+    private int column;
+
+    /** whether indent spaces are currently being collected */
+    private boolean collectingIndent;
+
+    /** {@code >= 0;} current indent amount */
+    private int indent;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param out {@code non-null;} writer to send final output to
+     * @param width {@code >= 0;} the maximum output width (not including
+     * {@code prefix}), or {@code 0} for no maximum
+     * @param prefix {@code non-null;} the prefix for each line
+     */
+    public IndentingWriter(Writer out, int width, String prefix) {
+        super(out);
+
+        if (out == null) {
+            throw new NullPointerException("out == null");
+        }
+
+        if (width < 0) {
+            throw new IllegalArgumentException("width < 0");
+        }
+
+        if (prefix == null) {
+            throw new NullPointerException("prefix == null");
+        }
+
+        this.width = (width != 0) ? width : Integer.MAX_VALUE;
+        this.maxIndent = width >> 1;
+        this.prefix = (prefix.length() == 0) ? null : prefix;
+
+        bol();
+    }
+
+    /**
+     * Constructs a no-prefix instance.
+     *
+     * @param out {@code non-null;} writer to send final output to
+     * @param width {@code >= 0;} the maximum output width (not including
+     * {@code prefix}), or {@code 0} for no maximum
+     */
+    public IndentingWriter(Writer out, int width) {
+        this(out, width, "");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void write(int c) throws IOException {
+        synchronized (lock) {
+            if (collectingIndent) {
+                if (c == ' ') {
+                    indent++;
+                    if (indent >= maxIndent) {
+                        indent = maxIndent;
+                        collectingIndent = false;
+                    }
+                } else {
+                    collectingIndent = false;
+                }
+            }
+
+            if ((column == width) && (c != '\n')) {
+                out.write('\n');
+                column = 0;
+                /*
+                 * Note: No else, so this should fall through to the next
+                 * if statement.
+                 */
+            }
+
+            if (column == 0) {
+                if (prefix != null) {
+                    out.write(prefix);
+                }
+
+                if (!collectingIndent) {
+                    for (int i = 0; i < indent; i++) {
+                        out.write(' ');
+                    }
+                    column = indent;
+                }
+            }
+
+            out.write(c);
+
+            if (c == '\n') {
+                bol();
+            } else {
+                column++;
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void write(char[] cbuf, int off, int len) throws IOException {
+        synchronized (lock) {
+            while (len > 0) {
+                write(cbuf[off]);
+                off++;
+                len--;
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void write(String str, int off, int len) throws IOException {
+        synchronized (lock) {
+            while (len > 0) {
+                write(str.charAt(off));
+                off++;
+                len--;
+            }
+        }
+    }
+
+    /**
+     * Indicates that output is at the beginning of a line.
+     */
+    private void bol() {
+        column = 0;
+        collectingIndent = (maxIndent != 0);
+        indent = 0;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/IntIterator.java b/dexgen/src/com/android/dexgen/util/IntIterator.java
new file mode 100644
index 0000000..42d92fa
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/IntIterator.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+/**
+ * An iterator for a list of ints.
+ */
+public interface IntIterator {
+
+    /**
+     * Checks to see if the iterator has a next value.
+     *
+     * @return true if next() will succeed
+     */
+    boolean hasNext();
+
+    /**
+     * Returns the next value in the iterator.
+     *
+     * @return next value
+     * @throws java.util.NoSuchElementException if no next element exists
+     */
+    int next();
+}
diff --git a/dexgen/src/com/android/dexgen/util/IntList.java b/dexgen/src/com/android/dexgen/util/IntList.java
new file mode 100644
index 0000000..ad29c0b
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/IntList.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+import java.util.Arrays;
+
+/**
+ * Simple list of {@code int}s.
+ */
+public final class IntList extends MutabilityControl {
+    /** {@code non-null;} immutable, no-element instance */
+    public static final IntList EMPTY = new IntList(0);
+
+    /** {@code non-null;} array of elements */
+    private int[] values;
+
+    /** {@code >= 0;} current size of the list */
+    private int size;
+
+    /** whether the values are currently sorted */
+    private boolean sorted;
+
+    static {
+        EMPTY.setImmutable();
+    }
+
+    /**
+     * Constructs a new immutable instance with the given element.
+     *
+     * @param value the sole value in the list
+     */
+    public static IntList makeImmutable(int value) {
+        IntList result = new IntList(1);
+
+        result.add(value);
+        result.setImmutable();
+
+        return result;
+    }
+
+    /**
+     * Constructs a new immutable instance with the given elements.
+     *
+     * @param value0 the first value in the list
+     * @param value1 the second value in the list
+     */
+    public static IntList makeImmutable(int value0, int value1) {
+        IntList result = new IntList(2);
+
+        result.add(value0);
+        result.add(value1);
+        result.setImmutable();
+
+        return result;
+    }
+
+    /**
+     * Constructs an empty instance with a default initial capacity.
+     */
+    public IntList() {
+        this(4);
+    }
+
+    /**
+     * Constructs an empty instance.
+     *
+     * @param initialCapacity {@code >= 0;} initial capacity of the list
+     */
+    public IntList(int initialCapacity) {
+        super(true);
+
+        try {
+            values = new int[initialCapacity];
+        } catch (NegativeArraySizeException ex) {
+            // Translate the exception.
+            throw new IllegalArgumentException("size < 0");
+        }
+
+        size = 0;
+        sorted = true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        int result = 0;
+
+        for (int i = 0; i < size; i++) {
+            result = (result * 31) + values[i];
+        }
+
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (other == this) {
+            return true;
+        }
+
+        if (! (other instanceof IntList)) {
+            return false;
+        }
+
+        IntList otherList = (IntList) other;
+
+        if (sorted != otherList.sorted) {
+            return false;
+        }
+
+        if (size != otherList.size) {
+            return false;
+        }
+
+        for (int i = 0; i < size; i++) {
+            if (values[i] != otherList.values[i]) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer(size * 5 + 10);
+
+        sb.append('{');
+
+        for (int i = 0; i < size; i++) {
+            if (i != 0) {
+                sb.append(", ");
+            }
+            sb.append(values[i]);
+        }
+
+        sb.append('}');
+
+        return sb.toString();
+    }
+
+    /**
+     * Gets the number of elements in this list.
+     */
+    public int size() {
+        return size;
+    }
+
+    /**
+     * Gets the indicated value.
+     *
+     * @param n {@code >= 0, < size();} which element
+     * @return the indicated element's value
+     */
+    public int get(int n) {
+        if (n >= size) {
+            throw new IndexOutOfBoundsException("n >= size()");
+        }
+
+        try {
+            return values[n];
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            // Translate exception.
+            throw new IndexOutOfBoundsException("n < 0");
+        }
+    }
+
+    /**
+     * Sets the value at the given index.
+     *
+     * @param n {@code >= 0, < size();} which element
+     * @param value value to store
+     */
+    public void set(int n, int value) {
+        throwIfImmutable();
+
+        if (n >= size) {
+            throw new IndexOutOfBoundsException("n >= size()");
+        }
+
+        try {
+            values[n] = value;
+            sorted = false;
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            // Translate the exception.
+            if (n < 0) {
+                throw new IllegalArgumentException("n < 0");
+            }
+        }
+    }
+
+    /**
+     * Adds an element to the end of the list. This will increase the
+     * list's capacity if necessary.
+     *
+     * @param value the value to add
+     */
+    public void add(int value) {
+        throwIfImmutable();
+
+        growIfNeeded();
+
+        values[size++] = value;
+
+        if (sorted && (size > 1)) {
+            sorted = (value >= values[size - 2]);
+        }
+    }
+
+    /**
+     * Inserts element into specified index, moving elements at and above
+     * that index up one. May not be used to insert at an index beyond the
+     * current size (that is, insertion as a last element is legal but
+     * no further).
+     *
+     * @param n {@code >= 0, <=size();} index of where to insert
+     * @param value value to insert
+     */
+    public void insert(int n, int value) {
+        if (n > size) {
+            throw new IndexOutOfBoundsException("n > size()");
+        }
+
+        growIfNeeded();
+
+        System.arraycopy (values, n, values, n+1, size - n);
+        values[n] = value;
+        size++;
+
+        sorted = sorted
+                && (n == 0 || value > values[n-1])
+                && (n == (size - 1) || value < values[n+1]);
+    }
+
+    /**
+     * Removes an element at a given index, shifting elements at greater
+     * indicies down one.
+     *
+     * @param n  {@code >=0, < size();} index of element to remove
+     */
+    public void removeIndex(int n) {
+        if (n >= size) {
+            throw new IndexOutOfBoundsException("n >= size()");
+        }
+
+        System.arraycopy (values, n + 1, values, n, size - n - 1);
+        size--;
+
+        // sort status is unchanged
+    }
+
+    /**
+     * Increases size of array if needed
+     */
+    private void growIfNeeded() {
+        if (size == values.length) {
+            // Resize.
+            int[] newv = new int[size * 3 / 2 + 10];
+            System.arraycopy(values, 0, newv, 0, size);
+            values = newv;
+        }
+    }
+
+    /**
+     * Returns the last element in the array without modifying the array
+     *
+     * @return last value in the array.
+     * @exception IndexOutOfBoundsException if stack is empty.
+     */
+    public int top() {
+        return get(size - 1);
+    }
+
+    /**
+     * Pops an element off the end of the list and decreasing the size by one.
+     *
+     * @return value from what was the last element.
+     * @exception IndexOutOfBoundsException if stack is empty.
+     */
+    public int pop() {
+        throwIfImmutable();
+
+        int result;
+
+        result = get(size-1);
+        size--;
+
+        return result;
+    }
+
+    /**
+     * Pops N elements off the end of the list and decreasing the size by N.
+     *
+     * @param n {@code >= 0;} number of elements to remove from end.
+     * @exception IndexOutOfBoundsException if stack is smaller than N
+     */
+    public void pop(int n) {
+        throwIfImmutable();
+
+        size -= n;
+    }
+
+    /**
+     * Shrinks the size of the list.
+     *
+     * @param newSize {@code >= 0;} the new size
+     */
+    public void shrink(int newSize) {
+        if (newSize < 0) {
+            throw new IllegalArgumentException("newSize < 0");
+        }
+
+        if (newSize > size) {
+            throw new IllegalArgumentException("newSize > size");
+        }
+
+        throwIfImmutable();
+
+        size = newSize;
+    }
+
+    /**
+     * Makes and returns a mutable copy of the list.
+     *
+     * @return {@code non-null;} an appropriately-constructed instance
+     */
+    public IntList mutableCopy() {
+        int sz = size;
+        IntList result = new IntList(sz);
+
+        for (int i = 0; i < sz; i++) {
+            result.add(values[i]);
+        }
+
+        return result;
+    }
+
+    /**
+     * Sorts the elements in the list in-place.
+     */
+    public void sort() {
+        throwIfImmutable();
+
+        if (!sorted) {
+            Arrays.sort(values, 0, size);
+            sorted = true;
+        }
+    }
+
+    /**
+     * Returns the index of the given value, or -1 if the value does not
+     * appear in the list.  This will do a binary search if the list is
+     * sorted or a linear search if not.
+     * @param value value to find
+     * @return index of value or -1
+     */
+    public int indexOf(int value) {
+        int ret = binarysearch(value);
+
+        return ret >= 0 ? ret : -1;
+
+    }
+
+    /**
+     * Performs a binary search on a sorted list, returning the index of
+     * the given value if it is present or
+     * {@code (-(insertion point) - 1)} if the value is not present.
+     * If the list is not sorted, then reverts to linear search and returns
+     * {@code -size()} if the element is not found.
+     *
+     * @param value value to find
+     * @return index of value or {@code (-(insertion point) - 1)} if the
+     * value is not present
+     */
+    public int binarysearch(int value) {
+        int sz = size;
+
+        if (!sorted) {
+            // Linear search.
+            for (int i = 0; i < sz; i++) {
+                if (values[i] == value) {
+                    return i;
+                }
+            }
+
+            return -sz;
+        }
+
+        /*
+         * Binary search. This variant does only one value comparison
+         * per iteration but does one more iteration on average than
+         * the variant that includes a value equality check per
+         * iteration.
+         */
+
+        int min = -1;
+        int max = sz;
+
+        while (max > (min + 1)) {
+            /*
+             * The guessIdx calculation is equivalent to ((min + max)
+             * / 2) but won't go wonky when min and max are close to
+             * Integer.MAX_VALUE.
+             */
+            int guessIdx = min + ((max - min) >> 1);
+            int guess = values[guessIdx];
+
+            if (value <= guess) {
+                max = guessIdx;
+            } else {
+                min = guessIdx;
+            }
+        }
+
+        if ((max != sz)) {
+            return (value == values[max]) ? max : (-max - 1);
+        } else {
+            return -sz - 1;
+        }
+    }
+
+
+    /**
+     * Returns whether or not the given value appears in the list.
+     * This will do a binary search if the list is sorted or a linear
+     * search if not.
+     *
+     * @see #sort
+     *
+     * @param value value to look for
+     * @return whether the list contains the given value
+     */
+    public boolean contains(int value) {
+        return indexOf(value) >= 0;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/IntSet.java b/dexgen/src/com/android/dexgen/util/IntSet.java
new file mode 100644
index 0000000..4de7525
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/IntSet.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+/**
+ * A set of integers
+ */
+public interface IntSet {
+
+    /**
+     * Adds an int to a set
+     *
+     * @param value int to add
+     */
+    void add(int value);
+
+    /**
+     * Removes an int from a set.
+     *
+     * @param value int to remove
+     */
+    void remove(int value);
+
+    /**
+     * Checks to see if a value is in the set
+     *
+     * @param value int to check
+     * @return true if in set
+     */
+    boolean has(int value);
+
+    /**
+     * Merges {@code other} into this set, so this set becomes the
+     * union of the two.
+     *
+     * @param other {@code non-null;} other set to merge with.
+     */
+    void merge(IntSet other);
+
+    /**
+     * Returns the count of unique elements in this set.
+     *
+     * @return {@code > = 0;} count of unique elements
+     */
+    int elements();
+
+    /**
+     * Iterates the set
+     *
+     * @return {@code non-null;} a set iterator
+     */
+    IntIterator iterator();
+}
diff --git a/dexgen/src/com/android/dexgen/util/LabeledItem.java b/dexgen/src/com/android/dexgen/util/LabeledItem.java
new file mode 100644
index 0000000..63cd067
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/LabeledItem.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+/**
+ * An item that has an integer label.
+ */
+public interface LabeledItem {
+
+    /*
+     * Gets the label of this block.
+     *
+     * @return {@code >= 0;} the label
+     */
+    public int getLabel();
+}
diff --git a/dexgen/src/com/android/dexgen/util/LabeledList.java b/dexgen/src/com/android/dexgen/util/LabeledList.java
new file mode 100644
index 0000000..a59e87d
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/LabeledList.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+import com.android.dexgen.rop.ByteBlock;
+
+/**
+ * A list of labeled items, allowing easy lookup by label.
+ */
+public class LabeledList extends FixedSizeList {
+
+    /**
+     * Sparse array indexed by label to FixedSizeList index.
+     * -1 = invalid label.
+     */
+    private final IntList labelToIndex;
+
+    /** @inheritDoc */
+    public LabeledList(int size) {
+        super(size);
+
+        labelToIndex = new IntList(size);
+    }
+
+    /**
+     * Constructs a new instance that is a copy of the old instance.
+     *
+     * @param old instance to copy
+     */
+    protected LabeledList(LabeledList old) {
+        super(old.size());
+        labelToIndex = old.labelToIndex.mutableCopy();
+
+        int sz = old.size();
+
+        for (int i = 0; i < sz; i++) {
+            Object one = old.get0(i);
+            if (one != null) {
+                set0(i, one);
+            }
+        }
+    }
+
+    /**
+     * Gets the maximum label (exclusive) of any block added to this instance.
+     *
+     * @return {@code >= 0;} the maximum label
+     */
+    public int getMaxLabel() {
+        int sz = labelToIndex.size();
+
+        // Gobble any deleted labels that may be at the end...
+        int i;
+        for (i = sz - 1; (i >= 0) && (labelToIndex.get(i) < 0); i--)
+            ;
+
+        int newSize = i+1;
+
+        labelToIndex.shrink(newSize);
+
+        return newSize;
+    }
+
+    /**
+     * Removes a label from the label-to-index mapping
+     * @param oldLabel label to remove
+     */
+    protected void removeLabel(int oldLabel) {
+        labelToIndex.set(oldLabel, -1);
+    }
+
+    /**
+     * Adds a label and index to the label-to-index mapping
+     * @param label new label
+     * @param index index of block.
+     */
+    protected void addLabelIndex(int label, int index) {
+        int origSz = labelToIndex.size();
+
+        for (int i = 0; i <= (label - origSz); i++) {
+            labelToIndex.add(-1);
+        }
+
+        labelToIndex.set(label, index);
+    }
+
+    /**
+     * Gets the index of the first item in the list with the given
+     * label, if any.
+     *
+     * @param label {@code >= 0;} the label to look for
+     * @return {@code >= -1;} the index of the so-labelled item, or {@code -1}
+     * if none is found
+     */
+    public int indexOfLabel(int label) {
+        if (label >= labelToIndex.size()) {
+            return -1;
+        } else {
+            return labelToIndex.get(label);
+        }
+    }
+
+    /** @inheritDoc */
+    @Override
+    public void shrinkToFit() {
+        super.shrinkToFit();
+
+        rebuildLabelToIndex();
+    }
+
+    /**
+     * Rebuilds the label-to-index mapping after a shrinkToFit().
+     * Note: assumes that the labels that are in the list are the same
+     * although the indicies may have changed.
+     */
+    protected void rebuildLabelToIndex() {
+        int szItems = size();
+
+        for (int i = 0; i < szItems; i++) {
+            LabeledItem li = (LabeledItem)get0(i);
+
+            if (li != null) {
+                labelToIndex.set(li.getLabel(), i);
+            }
+        }
+    }
+
+    /**
+     * Sets the element at the given index.
+     *
+     * @param n {@code >= 0, < size();} which element
+     * @param item {@code null-ok;} the value to store
+     */
+    protected void set(int n, LabeledItem item) {
+        LabeledItem old = (LabeledItem) getOrNull0(n);
+
+        set0(n, item);
+
+        if (old != null) {
+            removeLabel(old.getLabel());
+        }
+
+        if (item != null) {
+            addLabelIndex(item.getLabel(), n);
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/Leb128Utils.java b/dexgen/src/com/android/dexgen/util/Leb128Utils.java
new file mode 100644
index 0000000..05b38e2
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/Leb128Utils.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+/**
+ * LEB128 (little-endian base 128) utilities.
+ */
+public final class Leb128Utils {
+    /**
+     * This class is uninstantiable.
+     */
+    private Leb128Utils() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Gets the number of bytes in the unsigned LEB128 encoding of the
+     * given value.
+     *
+     * @param value the value in question
+     * @return its write size, in bytes
+     */
+    public static int unsignedLeb128Size(int value) {
+        // TODO: This could be much cleverer.
+
+        int remaining = value >> 7;
+        int count = 0;
+
+        while (remaining != 0) {
+            remaining >>= 7;
+            count++;
+        }
+
+        return count + 1;
+    }
+
+    /**
+     * Gets the number of bytes in the signed LEB128 encoding of the
+     * given value.
+     *
+     * @param value the value in question
+     * @return its write size, in bytes
+     */
+    public static int signedLeb128Size(int value) {
+        // TODO: This could be much cleverer.
+
+        int remaining = value >> 7;
+        int count = 0;
+        boolean hasMore = true;
+        int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1;
+
+        while (hasMore) {
+            hasMore = (remaining != end)
+                || ((remaining & 1) != ((value >> 6) & 1));
+
+            value = remaining;
+            remaining >>= 7;
+            count++;
+        }
+
+        return count;
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/ListIntSet.java b/dexgen/src/com/android/dexgen/util/ListIntSet.java
new file mode 100644
index 0000000..b262ebb
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/ListIntSet.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+import java.util.NoSuchElementException;
+
+/**
+ * A set of integers, represented by a list
+ */
+public class ListIntSet implements IntSet {
+
+    /** also accessed in BitIntSet */
+    final IntList ints;
+
+    /**
+     * Constructs an instance
+     */
+    public ListIntSet() {
+        ints = new IntList();
+        ints.sort();
+    }
+
+    /** @inheritDoc */
+    public void add(int value) {
+        int index = ints.binarysearch(value);
+
+        if (index < 0) {
+            ints.insert(-(index + 1), value);
+        }
+    }
+
+    /** @inheritDoc */
+    public void remove(int value) {
+        int index = ints.indexOf(value);
+
+        if (index >= 0) {
+            ints.removeIndex(index);
+        }
+    }
+
+    /** @inheritDoc */
+    public boolean has(int value) {
+        return ints.indexOf(value) >= 0;
+    }
+
+    /** @inheritDoc */
+    public void merge(IntSet other) {
+        if (other instanceof ListIntSet) {
+            ListIntSet o = (ListIntSet) other;
+            int szThis = ints.size();
+            int szOther = o.ints.size();
+
+            int i = 0;
+            int j = 0;
+
+            while (j < szOther && i < szThis) {
+                while (j < szOther && o.ints.get(j) < ints.get(i)) {
+                    add(o.ints.get(j++));
+                }
+                if (j == szOther) {
+                    break;
+                }
+                while (i < szThis && o.ints.get(j) >= ints.get(i)) {
+                    i++;
+                }
+            }
+
+            while (j < szOther) {
+                add(o.ints.get(j++));
+            }
+
+            ints.sort();
+        } else if (other instanceof BitIntSet) {
+            BitIntSet o = (BitIntSet) other;
+
+            for (int i = 0; i >= 0; i = Bits.findFirst(o.bits, i + 1)) {
+                ints.add(i);
+            }
+            ints.sort();
+        } else {
+            IntIterator iter = other.iterator();
+            while (iter.hasNext()) {
+                add(iter.next());
+            }
+        }
+    }
+
+    /** @inheritDoc */
+    public int elements() {
+        return ints.size();
+    }
+
+    /** @inheritDoc */
+    public IntIterator iterator() {
+        return new IntIterator() {
+            private int idx = 0;
+
+            /** @inheritDoc */
+            public boolean hasNext() {
+                return idx < ints.size();
+            }
+
+            /** @inheritDoc */
+            public int next() {
+                if (!hasNext()) {
+                    throw new NoSuchElementException();
+                }
+
+                return ints.get(idx++);
+            }
+        };
+    }
+
+    /** @inheritDoc */
+    public String toString() {
+        return ints.toString();
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/MutabilityControl.java b/dexgen/src/com/android/dexgen/util/MutabilityControl.java
new file mode 100644
index 0000000..b3ee691
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/MutabilityControl.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+/**
+ * Very simple base class that implements a flag to control the mutability
+ * of instances. This class just provides the flag and a utility to check
+ * and throw the right exception, but it is up to subclasses to place calls
+ * to the checker in all the right places.
+ */
+public class MutabilityControl {
+    /** whether this instance is mutable */
+    private boolean mutable;
+
+    /**
+     * Constructs an instance. It is initially mutable.
+     */
+    public MutabilityControl() {
+        mutable = true;
+    }
+
+    /**
+     * Constructs an instance, explicitly indicating the mutability.
+     *
+     * @param mutable {@code true} iff this instance is mutable
+     */
+    public MutabilityControl(boolean mutable) {
+        this.mutable = mutable;
+    }
+
+    /**
+     * Makes this instance immutable.
+     */
+    public void setImmutable() {
+        mutable = false;
+    }
+
+    /**
+     * Checks to see whether or not this instance is immutable. This is the
+     * same as calling {@code !isMutable()}.
+     *
+     * @return {@code true} iff this instance is immutable
+     */
+    public final boolean isImmutable() {
+        return !mutable;
+    }
+
+    /**
+     * Checks to see whether or not this instance is mutable.
+     *
+     * @return {@code true} iff this instance is mutable
+     */
+    public final boolean isMutable() {
+        return mutable;
+    }
+
+    /**
+     * Throws {@link MutabilityException} if this instance is
+     * immutable.
+     */
+    public final void throwIfImmutable() {
+        if (!mutable) {
+            throw new MutabilityException("immutable instance");
+        }
+    }
+
+    /**
+     * Throws {@link MutabilityException} if this instance is mutable.
+     */
+    public final void throwIfMutable() {
+        if (mutable) {
+            throw new MutabilityException("mutable instance");
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/MutabilityException.java b/dexgen/src/com/android/dexgen/util/MutabilityException.java
new file mode 100644
index 0000000..2188fe5
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/MutabilityException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+/**
+ * Exception due to a mutability problem.
+ */
+public class MutabilityException
+        extends ExceptionWithContext {
+    public MutabilityException(String message) {
+        super(message);
+    }
+
+    public MutabilityException(Throwable cause) {
+        super(cause);
+    }
+
+    public MutabilityException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/Output.java b/dexgen/src/com/android/dexgen/util/Output.java
new file mode 100644
index 0000000..469c66a
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/Output.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+/**
+ * Interface for a sink for binary output. This is similar to
+ * {@code java.util.DataOutput}, but no {@code IOExceptions}
+ * are declared, and multibyte output is defined to be little-endian.
+ */
+public interface Output {
+    /**
+     * Gets the current cursor position. This is the same as the number of
+     * bytes written to this instance.
+     *
+     * @return {@code >= 0;} the cursor position
+     */
+    public int getCursor();
+
+    /**
+     * Asserts that the cursor is the given value.
+     *
+     * @param expectedCursor the expected cursor value
+     * @throws RuntimeException thrown if {@code getCursor() !=
+     * expectedCursor}
+     */
+    public void assertCursor(int expectedCursor);
+
+    /**
+     * Writes a {@code byte} to this instance.
+     *
+     * @param value the value to write; all but the low 8 bits are ignored
+     */
+    public void writeByte(int value);
+
+    /**
+     * Writes a {@code short} to this instance.
+     *
+     * @param value the value to write; all but the low 16 bits are ignored
+     */
+    public void writeShort(int value);
+
+    /**
+     * Writes an {@code int} to this instance.
+     *
+     * @param value the value to write
+     */
+    public void writeInt(int value);
+
+    /**
+     * Writes a {@code long} to this instance.
+     *
+     * @param value the value to write
+     */
+    public void writeLong(long value);
+
+    /**
+     * Writes a DWARFv3-style unsigned LEB128 integer. For details,
+     * see the "Dalvik Executable Format" document or DWARF v3 section
+     * 7.6.
+     *
+     * @param value value to write, treated as an unsigned value
+     * @return {@code 1..5;} the number of bytes actually written
+     */
+    public int writeUnsignedLeb128(int value);
+
+    /**
+     * Writes a DWARFv3-style unsigned LEB128 integer. For details,
+     * see the "Dalvik Executable Format" document or DWARF v3 section
+     * 7.6.
+     *
+     * @param value value to write
+     * @return {@code 1..5;} the number of bytes actually written
+     */
+    public int writeSignedLeb128(int value);
+
+    /**
+     * Writes a {@link ByteArray} to this instance.
+     *
+     * @param bytes {@code non-null;} the array to write
+     */
+    public void write(ByteArray bytes);
+
+    /**
+     * Writes a portion of a {@code byte[]} to this instance.
+     *
+     * @param bytes {@code non-null;} the array to write
+     * @param offset {@code >= 0;} offset into {@code bytes} for the first
+     * byte to write
+     * @param length {@code >= 0;} number of bytes to write
+     */
+    public void write(byte[] bytes, int offset, int length);
+
+    /**
+     * Writes a {@code byte[]} to this instance. This is just
+     * a convenient shorthand for {@code write(bytes, 0, bytes.length)}.
+     *
+     * @param bytes {@code non-null;} the array to write
+     */
+    public void write(byte[] bytes);
+
+    /**
+     * Writes the given number of {@code 0} bytes.
+     *
+     * @param count {@code >= 0;} the number of zeroes to write
+     */
+    public void writeZeroes(int count);
+
+    /**
+     * Adds extra bytes if necessary (with value {@code 0}) to
+     * force alignment of the output cursor as given.
+     *
+     * @param alignment {@code > 0;} the alignment; must be a power of two
+     */
+    public void alignTo(int alignment);
+}
diff --git a/dexgen/src/com/android/dexgen/util/ToHuman.java b/dexgen/src/com/android/dexgen/util/ToHuman.java
new file mode 100644
index 0000000..bbf044c
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/ToHuman.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+/**
+ * Simple interface for objects that can return a "human" (as opposed to
+ * a complete but often hard to read) string form.
+ */
+public interface ToHuman {
+    /**
+     * Return the "human" string form of this instance.  This is
+     * generally less "debuggy" than {@code toString()}.
+     *
+     * @return {@code non-null;} the human string form
+     */
+    public String toHuman();
+}
diff --git a/dexgen/src/com/android/dexgen/util/TwoColumnOutput.java b/dexgen/src/com/android/dexgen/util/TwoColumnOutput.java
new file mode 100644
index 0000000..17a4c42
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/TwoColumnOutput.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ * Class that takes a combined output destination and provides two
+ * output writers, one of which ends up writing to the left column and
+ * one which goes on the right.
+ */
+public final class TwoColumnOutput {
+    /** {@code non-null;} underlying writer for final output */
+    private final Writer out;
+
+    /** {@code > 0;} the left column width */
+    private final int leftWidth;
+
+    /** {@code non-null;} pending left column output */
+    private final StringBuffer leftBuf;
+
+    /** {@code non-null;} pending right column output */
+    private final StringBuffer rightBuf;
+
+    /** {@code non-null;} left column writer */
+    private final IndentingWriter leftColumn;
+
+    /** {@code non-null;} right column writer */
+    private final IndentingWriter rightColumn;
+
+    /**
+     * Turns the given two strings (with widths) and spacer into a formatted
+     * two-column string.
+     *
+     * @param s1 {@code non-null;} first string
+     * @param width1 {@code > 0;} width of the first column
+     * @param spacer {@code non-null;} spacer string
+     * @param s2 {@code non-null;} second string
+     * @param width2 {@code > 0;} width of the second column
+     * @return {@code non-null;} an appropriately-formatted string
+     */
+    public static String toString(String s1, int width1, String spacer,
+                                  String s2, int width2) {
+        int len1 = s1.length();
+        int len2 = s2.length();
+
+        StringWriter sw = new StringWriter((len1 + len2) * 3);
+        TwoColumnOutput twoOut =
+            new TwoColumnOutput(sw, width1, width2, spacer);
+
+        try {
+            twoOut.getLeft().write(s1);
+            twoOut.getRight().write(s2);
+        } catch (IOException ex) {
+            throw new RuntimeException("shouldn't happen", ex);
+        }
+
+        twoOut.flush();
+        return sw.toString();
+    }
+
+    /**
+     * Constructs an instance.
+     *
+     * @param out {@code non-null;} writer to send final output to
+     * @param leftWidth {@code > 0;} width of the left column, in characters
+     * @param rightWidth {@code > 0;} width of the right column, in characters
+     * @param spacer {@code non-null;} spacer string to sit between the two columns
+     */
+    public TwoColumnOutput(Writer out, int leftWidth, int rightWidth,
+                           String spacer) {
+        if (out == null) {
+            throw new NullPointerException("out == null");
+        }
+
+        if (leftWidth < 1) {
+            throw new IllegalArgumentException("leftWidth < 1");
+        }
+
+        if (rightWidth < 1) {
+            throw new IllegalArgumentException("rightWidth < 1");
+        }
+
+        if (spacer == null) {
+            throw new NullPointerException("spacer == null");
+        }
+
+        StringWriter leftWriter = new StringWriter(1000);
+        StringWriter rightWriter = new StringWriter(1000);
+
+        this.out = out;
+        this.leftWidth = leftWidth;
+        this.leftBuf = leftWriter.getBuffer();
+        this.rightBuf = rightWriter.getBuffer();
+        this.leftColumn = new IndentingWriter(leftWriter, leftWidth);
+        this.rightColumn =
+            new IndentingWriter(rightWriter, rightWidth, spacer);
+    }
+
+    /**
+     * Constructs an instance.
+     *
+     * @param out {@code non-null;} stream to send final output to
+     * @param leftWidth {@code >= 1;} width of the left column, in characters
+     * @param rightWidth {@code >= 1;} width of the right column, in characters
+     * @param spacer {@code non-null;} spacer string to sit between the two columns
+     */
+    public TwoColumnOutput(OutputStream out, int leftWidth, int rightWidth,
+                           String spacer) {
+        this(new OutputStreamWriter(out), leftWidth, rightWidth, spacer);
+    }
+
+    /**
+     * Gets the writer to use to write to the left column.
+     *
+     * @return {@code non-null;} the left column writer
+     */
+    public Writer getLeft() {
+        return leftColumn;
+    }
+
+    /**
+     * Gets the writer to use to write to the right column.
+     *
+     * @return {@code non-null;} the right column writer
+     */
+    public Writer getRight() {
+        return rightColumn;
+    }
+
+    /**
+     * Flushes the output. If there are more lines of pending output in one
+     * column, then the other column will get filled with blank lines.
+     */
+    public void flush() {
+        try {
+            appendNewlineIfNecessary(leftBuf, leftColumn);
+            appendNewlineIfNecessary(rightBuf, rightColumn);
+            outputFullLines();
+            flushLeft();
+            flushRight();
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    /**
+     * Outputs to the final destination as many full line pairs as
+     * there are in the pending output, removing those lines from
+     * their respective buffers. This method terminates when at
+     * least one of the two column buffers is empty.
+     */
+    private void outputFullLines() throws IOException {
+        for (;;) {
+            int leftLen = leftBuf.indexOf("\n");
+            if (leftLen < 0) {
+                return;
+            }
+
+            int rightLen = rightBuf.indexOf("\n");
+            if (rightLen < 0) {
+                return;
+            }
+
+            if (leftLen != 0) {
+                out.write(leftBuf.substring(0, leftLen));
+            }
+
+            if (rightLen != 0) {
+                writeSpaces(out, leftWidth - leftLen);
+                out.write(rightBuf.substring(0, rightLen));
+            }
+
+            out.write('\n');
+
+            leftBuf.delete(0, leftLen + 1);
+            rightBuf.delete(0, rightLen + 1);
+        }
+    }
+
+    /**
+     * Flushes the left column buffer, printing it and clearing the buffer.
+     * If the buffer is already empty, this does nothing.
+     */
+    private void flushLeft() throws IOException {
+        appendNewlineIfNecessary(leftBuf, leftColumn);
+
+        while (leftBuf.length() != 0) {
+            rightColumn.write('\n');
+            outputFullLines();
+        }
+    }
+
+    /**
+     * Flushes the right column buffer, printing it and clearing the buffer.
+     * If the buffer is already empty, this does nothing.
+     */
+    private void flushRight() throws IOException {
+        appendNewlineIfNecessary(rightBuf, rightColumn);
+
+        while (rightBuf.length() != 0) {
+            leftColumn.write('\n');
+            outputFullLines();
+        }
+    }
+
+    /**
+     * Appends a newline to the given buffer via the given writer, but
+     * only if it isn't empty and doesn't already end with one.
+     *
+     * @param buf {@code non-null;} the buffer in question
+     * @param out {@code non-null;} the writer to use
+     */
+    private static void appendNewlineIfNecessary(StringBuffer buf,
+                                                 Writer out)
+            throws IOException {
+        int len = buf.length();
+
+        if ((len != 0) && (buf.charAt(len - 1) != '\n')) {
+            out.write('\n');
+        }
+    }
+
+    /**
+     * Writes the given number of spaces to the given writer.
+     *
+     * @param out {@code non-null;} where to write
+     * @param amt {@code >= 0;} the number of spaces to write
+     */
+    private static void writeSpaces(Writer out, int amt) throws IOException {
+        while (amt > 0) {
+            out.write(' ');
+            amt--;
+        }
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/Warning.java b/dexgen/src/com/android/dexgen/util/Warning.java
new file mode 100644
index 0000000..204d877
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/Warning.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+/**
+ * Exception which is meant to indicate a non-fatal warning.
+ */
+public class Warning extends RuntimeException {
+    /**
+     * Constructs an instance.
+     *
+     * @param message human-oriented message
+     */
+    public Warning(String message) {
+        super(message);
+    }
+}
diff --git a/dexgen/src/com/android/dexgen/util/Writers.java b/dexgen/src/com/android/dexgen/util/Writers.java
new file mode 100644
index 0000000..046967e
--- /dev/null
+++ b/dexgen/src/com/android/dexgen/util/Writers.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dexgen.util;
+
+import java.io.PrintWriter;
+import java.io.Writer;
+
+/**
+ * Utilities for dealing with {@code Writer}s.
+ */
+public final class Writers {
+    /**
+     * This class is uninstantiable.
+     */
+    private Writers() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Makes a {@code PrintWriter} for the given {@code Writer},
+     * returning the given writer if it already happens to be the right
+     * class.
+     *
+     * @param writer {@code non-null;} writer to (possibly) wrap
+     * @return {@code non-null;} an appropriate instance
+     */
+    public static PrintWriter printWriterFor(Writer writer) {
+        if (writer instanceof PrintWriter) {
+            return (PrintWriter) writer;
+        }
+
+        return new PrintWriter(writer);
+    }
+}
diff --git a/dexopt/OptMain.c b/dexopt/OptMain.c
index cde3d18..ca55565 100644
--- a/dexopt/OptMain.c
+++ b/dexopt/OptMain.c
@@ -346,7 +346,7 @@
         goto bail;
     }
 
-    outFd = open(outName, O_RDWR | O_EXCL | O_CREAT);
+    outFd = open(outName, O_RDWR | O_EXCL | O_CREAT, 0666);
     if (outFd < 0) {
         perror(argv[0]);
         goto bail;
diff --git a/tools/dex-preopt b/tools/dex-preopt
index 3936eec..21e4a32 100755
--- a/tools/dex-preopt
+++ b/tools/dex-preopt
@@ -15,7 +15,7 @@
 # limitations under the License.
 
 #
-# Usage: dex-preopt [options] path/to/input.jar path/to/output.dex
+# Usage: dex-preopt [options] path/to/input.jar path/to/output.odex
 #
 # This tool runs a host build of dalvikvm in order to preoptimize dex
 # files that will be run on a device.
@@ -40,12 +40,29 @@
 #     should be a directory with this name under build-dir/product. If
 #     not specified, then there must only be one such directory, and that
 #     one will be used.
+#   --boot-jars=list:of:jar:base:names -- Specify the list of base names
+#     of bootstrap classpath elements, colon-separated. This defaults to
+#     "core". However, as of this writing, a good choice to pass in here
+#     is "core:core-junit:ext:framework:android.policy:services".
+#   --verify={none,remote,all} -- Specify what level of verification to
+#     do. Defaults to "all".
+#   --optimize={none,verified,all} -- Specify which classes to optimize.
+#     Defaults to "verified".
+#   --no-register-maps -- Indicate that the output should not contain
+#     register maps. By default, register maps are created and included.
 #
 
+# Defaults.
 buildDir="."
 product=""
 bootstrap="no"
-bogus="no"
+doVerify="all"
+doOptimize="verified"
+doRegisterMaps="yes"
+bootJars="core"
+
+optimizeFlags="" # built up from the more human-friendly options
+bogus="no" # indicates if there was an error during processing arguments
 
 # Iterate over the arguments looking for options.
 while true; do
@@ -82,27 +99,90 @@
         buildDir="${value}"
     elif [ "${option}" = 'product' -a "${hasValue}" = 'yes' ]; then
         product="${value}"
+    elif [ "${option}" = 'boot-jars' -a "${hasValue}" = 'yes' ]; then
+        bootJars="${value}"
     elif [ "${option}" = 'bootstrap' -a "${hasValue}" = 'no' ]; then
         bootstrap="yes"
+    elif [ "${option}" = 'verify' -a "${hasValue}" = 'yes' ]; then
+        doVerify="${value}"
+    elif [ "${option}" = 'optimize' -a "${hasValue}" = 'yes' ]; then
+        doOptimize="${value}"
+    elif [ "${option}" = 'no-register-maps' -a "${hasValue}" = 'no' ]; then
+        doRegisterMaps="no"
     else
         echo "unknown option: ${origOption}" 1>&2
         bogus="yes"
     fi
 done
 
-if [ "${bogus}" = 'yes' ]; then
-    # There was an error during option processing.
-    echo "usage: $0 [--bootstrap] [--build-dir=path/to/out]" 1>&2
-    echo "  path/to/input.jar path/to/output.dex" 1>&2
-    exit 1
+# Check and set up the input and output files. In the case of bootstrap
+# processing, verify that no files are specified.
+inputFile=$1
+outputFile=$2
+if [ "${bootstrap}" = "yes" ]; then
+    if [ "$#" != "0" ]; then
+        echo "unexpected arguments in --bootstrap mode" 1>&2
+        bogus=yes
+    fi
+elif [ "$#" != "2" ]; then
+    echo "must specify input and output files (and no more arguments)" 1>&2
+    bogus=yes
 fi
 
 # Sanity-check the specified build directory.
 if [ "x${buildDir}" = 'x' ]; then
     echo "must specify build directory" 1>&2
-    exit 1
+    bogus=yes
 elif [ ! '(' -d "${buildDir}" -a -w "${buildDir}" ')' ]; then
     echo "build directory is not a writable directory" 1>&2
+    bogus=yes
+fi
+
+# Sanity-check the specified boot jar list.
+if [ "x${bootJars}" = 'x' ]; then
+    echo "must specify non-empty boot-jars list" 1>&2
+    bogus=yes
+fi
+
+# Sanity-check and expand the verify option.
+if [ "x${doVerify}" = "xnone" ]; then
+    optimizeFlags="${optimizeFlags},v=n"
+elif [ "x${doVerify}" = "xremote" ]; then
+    optimizeFlags="${optimizeFlags},v=r"
+elif [ "x${doVerify}" = "xall" ]; then
+    optimizeFlags="${optimizeFlags},v=a"
+else
+    echo "bad value for --verify: ${doVerify}" 1>&2
+    bogus=yes
+fi
+
+# Sanity-check and expand the optimize option.
+if [ "x${doOptimize}" = "xnone" ]; then
+    optimizeFlags="${optimizeFlags},o=n"
+elif [ "x${doOptimize}" = "xverified" ]; then
+    optimizeFlags="${optimizeFlags},o=v"
+elif [ "x${doOptimize}" = "xall" ]; then
+    optimizeFlags="${optimizeFlags},o=a"
+else
+    echo "bad value for --optimize: ${doOptimize}" 1>&2
+    bogus=yes
+fi
+
+# Expand the register maps selection, if necessary.
+if [ "${doRegisterMaps}" = "yes" ]; then
+    optimizeFlags="${optimizeFlags},m=y"
+fi
+
+# Kill off the spare comma in optimizeFlags.
+optimizeFlags=`echo ${optimizeFlags} | sed 's/^,//'`
+
+# Error out if there was trouble.
+if [ "${bogus}" = 'yes' ]; then
+    # There was an error during option processing.
+    echo "usage: $0 [--bootstrap] [--build-dir=path/to/out]" 1>&2
+    echo "  [--product=name] [--boot-jars=list:of:names]" 1>&2
+    echo "  [--verify=type] [--optimize=type] [--no-register-maps]" 1>&2
+    echo "  path/to/input.jar path/to/output.odex" 1>&2
     exit 1
 fi
 
@@ -142,39 +222,34 @@
     exit 1
 fi
 
+# Transform the bootJars into full paths, maintaining the colon separator.
+BOOTCLASSPATH=`echo ":${bootJars}" | \
+    sed "s!:\([^:]*\)!:${product}/system/framework/\1.jar!g" | \
+    sed 's/^://'`
+export BOOTCLASSPATH
 
-##
-## TODO: The rest of this is unmodified from fadden's original prototype.
-##
-exit 2
+if [ "${bootstrap}" = "yes" ]; then
+    # Split the boot classpath into separate elements and iterate over them,
+    # processing each, in order.
+    elements=`echo "${BOOTCLASSPATH}" | sed 's/:/ /g'`
 
-basedir=$rootdir/out/host/linux-x86/pr/sim
-javadir=$basedir/system/framework
-
-# this is where we find bin/dexopt
-export ANDROID_ROOT=$basedir/system
-# location of "dalvik-cache" directory with optimized bootstrap DEX files
-export ANDROID_DATA=$basedir/data
-
-# bootstrap class path, must match what's on the device in init.rc
-export BOOTCLASSPATH=$javadir/core.jar:$javadir/ext.jar:$javadir/framework.jar:$javadir/android.policy.jar:$javadir/services.jar
-
-# dalvik-cache naming convention
-pfx=$ANDROID_DATA/dalvik-cache/work@gitdalvik-dev@out@host@linux-x86@pr@sim@system@framework@
-sfx=@classes.dex
-
-# configure as appropriate for debuggging
-export ANDROID_LOG_TAGS='*:d'
-
-if [ "$1" = "--mkboot" ]; then
-       echo making bootstrap classes
-       dexopt-wrapper $javadir/core.jar ${pfx}core.jar${sfx}
-       dexopt-wrapper $javadir/ext.jar ${pfx}ext.jar${sfx}
-       dexopt-wrapper $javadir/framework.jar ${pfx}framework.jar${sfx}
-       dexopt-wrapper $javadir/android.policy.jar ${pfx}android.policy.jar${sfx}
-       dexopt-wrapper $javadir/services.jar ${pfx}services.jar${sfx}
-       exit 0
+    for inputFile in $elements; do
+        echo "Processing ${inputFile}" 1>&2
+        outputFile="`dirname ${inputFile}`/`basename ${inputFile} .jar`.odex"
+        "${dexopt}" --preopt "${inputFile}" "${outputFile}" "${optimizeFlags}"
+        status="$?"
+        if [ "${status}" != "0" ]; then
+            exit "${status}"
+        fi
+    done
 else
-       #echo Using bootstrap classes from $BOOTCLASSPATH
-       exec dexopt-wrapper $*
-fi
\ No newline at end of file
+    echo "Processing ${inputFile}" 1>&2
+    "${dexopt}" --preopt "${inputFile}" "${outputFile}" "${optimizeFlags}"
+
+    status="$?"
+    if [ "${status}" != "0" ]; then
+        exit "${status}"
+    fi
+fi
+
+echo "Done!" 1>&2