Add several classes from dx tool to the dexgen project

Dexgen depends on many classes from dx tool but for now there is no target
build of dx. That is why some of its classes need to be moved directly into
dexgen source code, as building target version of the whole dx project
is not an option. This CL produces a lot of duplicate classes in
dalvik/dx and dalvik/dexgen, but this will be resolved in future by removing
these classes from dalvik/dx.

Change-Id: I5411f92761d73c3ab555feaa345e5d150aa280ef
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);
+    }
+}