Bare-bones dex code generator.

This is functional but incomplete. In particular, the following
are still coming:
 - instructions for cast/instanceof/arrays/float comparison/try/catch
 - debug information
 - annotations

The API is also incomplete. When this is done I'm going to remove
the type parameters to see if that's a net win. I suspect it will
be because generics currently hurt the ability to do assignments
between unknown types - you can't currently assign from Label<?> to
Label<?>, for example.

I'm anticipating changes to the way Code instances are created.
In the current API these are created without an attached method and
later attached to a method with Method.declare(). This doesn't work
very well, particularly since the code blocks don't know how many
parameters they take, whether they're static, or what their return
type is. I'm tempted to make declare() return a writable Code instance,
or to simply combine the Code and Method classes.

This code can benefit from more error detection. It's currently quite
easy to do bad things with labels: use them on the wrong Code instance,
reuse them, don't use them, etc. Better error checking is due here.

Change-Id: I4fe20552f2c571e41bedba6ff9db6686688d97ee
http://code.google.com/p/android/issues/detail?id=6322
diff --git a/dx/junit-tests/HelloWorldMaker.java b/dx/junit-tests/HelloWorldMaker.java
new file mode 100644
index 0000000..6c6cbff
--- /dev/null
+++ b/dx/junit-tests/HelloWorldMaker.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.android.dx.gen.BinaryOp;
+import com.android.dx.gen.Code;
+import com.android.dx.gen.DexGenerator;
+import com.android.dx.gen.Field;
+import com.android.dx.gen.Local;
+import com.android.dx.gen.Method;
+import com.android.dx.gen.Type;
+import com.android.dx.rop.code.AccessFlags;
+import java.io.PrintStream;
+
+public class HelloWorldMaker {
+
+    public static void main(String[] args) throws Exception {
+
+        /*
+         * This code generates Dalvik bytecode equivalent to the following
+         * program.
+         *
+         *  public class HelloWorld {
+         *      public static void hello() {
+         *          int a = 0xabcd;
+         *          int b = 0xaaaa;
+         *          int c = a - b;
+         *          String s = Integer.toHexString(c);
+         *          System.out.println(s);
+         *      }
+         *  }
+         */
+
+        DexGenerator generator = new DexGenerator();
+
+        // lookup the symbols of interest
+        Type<Object> object = generator.getType(Object.class);
+        Type<Integer> integer = generator.getType(Integer.class);
+        Type<Integer> intType = generator.getType(int.class);
+        Type<String> string = generator.getType(String.class);
+        Type<Void> voidType = generator.getType(void.class);
+        Type<System> system = generator.getType(System.class);
+        Type<PrintStream> printStream = generator.getType(PrintStream.class);
+        Type<?> helloWorld = generator.getType("LHelloWorld;");
+        Field<System, PrintStream> systemOutField = system.getField(printStream, "out");
+        Method<Integer, String> toHexString = integer.getMethod(string, "toHexString", intType);
+        Method<PrintStream, Void> println = printStream.getMethod(voidType, "println", string);
+
+        // create some registers
+        //    (I'd like a better syntax for this)
+        Code code = generator.newCode();
+        Local<Integer> a = code.newLocal(intType);
+        Local<Integer> b = code.newLocal(intType);
+        Local<Integer> c = code.newLocal(intType);
+        Local<String> s = code.newLocal(string);
+        Local<PrintStream> localSystemOut = code.newLocal(printStream);
+
+        // specify the code instruction-by-instruction (approximately)
+        code.loadConstant(a, 0xabcd);
+        code.loadConstant(b, 0xaaaa);
+        code.op(BinaryOp.SUBTRACT, c, a, b);
+        code.invokeStatic(toHexString, s, c);
+        code.sget(systemOutField, localSystemOut);
+        code.invokeVirtual(println, null, localSystemOut, s);
+        code.returnVoid();
+
+        // wrap it up by building the HelloWorld class and hello() method
+        Method hello = helloWorld.getMethod(voidType, "hello");
+        hello.declare(AccessFlags.ACC_STATIC | AccessFlags.ACC_PUBLIC, code);
+
+        // TODO: create the constructor
+
+        helloWorld.declare("Generated.java", AccessFlags.ACC_PUBLIC, object);
+
+        // load the dex
+        ClassLoader loader = generator.load(HelloWorldMaker.class.getClassLoader());
+        Class<?> helloWorldClass = loader.loadClass("HelloWorld");
+        helloWorldClass.getMethod("hello").invoke(null);
+    }
+}
diff --git a/dx/junit-tests/com/android/dx/gen/DexGeneratorTest.java b/dx/junit-tests/com/android/dx/gen/DexGeneratorTest.java
new file mode 100644
index 0000000..c94e9b1
--- /dev/null
+++ b/dx/junit-tests/com/android/dx/gen/DexGeneratorTest.java
@@ -0,0 +1,1157 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dx.gen;
+
+import static com.android.dx.rop.code.AccessFlags.ACC_CONSTRUCTOR;
+import static com.android.dx.rop.code.AccessFlags.ACC_FINAL;
+import static com.android.dx.rop.code.AccessFlags.ACC_PRIVATE;
+import static com.android.dx.rop.code.AccessFlags.ACC_PROTECTED;
+import static com.android.dx.rop.code.AccessFlags.ACC_PUBLIC;
+import static com.android.dx.rop.code.AccessFlags.ACC_STATIC;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import junit.framework.TestCase;
+
+/**
+ * This generates a class named 'Generated' with one or more generated methods
+ * and fields. In loads the generated class into the current VM and uses
+ * reflection to invoke its methods.
+ *
+ * <p>This test must run on a Dalvik VM.
+ */
+public final class DexGeneratorTest extends TestCase {
+    private static final Map<Class<?>, Class<?>> BOXED_TO_PRIMITIVE
+            = new HashMap<Class<?>, Class<?>>();
+    static {
+        BOXED_TO_PRIMITIVE.put(Boolean.class, boolean.class);
+        BOXED_TO_PRIMITIVE.put(Byte.class, byte.class);
+        BOXED_TO_PRIMITIVE.put(Character.class, char.class);
+        BOXED_TO_PRIMITIVE.put(Double.class, double.class);
+        BOXED_TO_PRIMITIVE.put(Float.class, float.class);
+        BOXED_TO_PRIMITIVE.put(Integer.class, int.class);
+        BOXED_TO_PRIMITIVE.put(Long.class, long.class);
+        BOXED_TO_PRIMITIVE.put(Short.class, short.class);
+        BOXED_TO_PRIMITIVE.put(Void.class, void.class);
+    }
+
+    private DexGenerator generator;
+    private Type<Integer> intType;
+    private Type<Long> longType;
+    private Type<Boolean> booleanType;
+    private Type<Object> objectType;
+    private Type<DexGeneratorTest> dexGeneratorTestType;
+    private Type<?> generatedType;
+    private Type<Callable> callableType;
+    private Method<Callable, Object> call;
+
+    @Override protected void setUp() throws Exception {
+        super.setUp();
+        reset();
+    }
+
+    /**
+     * The generator is mutable. Calling reset creates a new empty generator.
+     * This is necessary to generate multiple classes in the same test method.
+     */
+    private void reset() {
+        generator = new DexGenerator();
+        intType = generator.getType(int.class);
+        longType = generator.getType(long.class);
+        booleanType = generator.getType(boolean.class);
+        objectType = generator.getType(Object.class);
+        dexGeneratorTestType = generator.getType(DexGeneratorTest.class);
+        generatedType = generator.getType("LGenerated;");
+        callableType = generator.getType(Callable.class);
+        call = callableType.getMethod(objectType, "call");
+    }
+
+    public void testNewInstance() throws Exception {
+        /*
+         * public static Constructable generatedMethod(long a, boolean b) {
+         *   Constructable result = new Constructable(a, b);
+         *   return result;
+         * }
+         */
+
+        Code code = generator.newCode();
+        Local<Long> localA = code.newParameter(longType);
+        Local<Boolean> localB = code.newParameter(booleanType);
+        Type<Constructable> constructable = generator.getType(Constructable.class);
+        Method<Constructable, Void> constructor
+                = constructable.getConstructor(longType, booleanType);
+        Local<Constructable> localResult = code.newLocal(constructable);
+        code.newInstance(localResult, constructor, localA, localB);
+        code.returnValue(localResult);
+
+        java.lang.reflect.Method method
+                = newMethod(Constructable.class, code, long.class, boolean.class);
+        Constructable constructed = (Constructable) method.invoke(null, 5L, false);
+        assertEquals(5L, constructed.a);
+        assertEquals(false, constructed.b);
+    }
+
+    public static class Constructable {
+        private final long a;
+        private final boolean b;
+        public Constructable(long a, boolean b) {
+            this.a = a;
+            this.b = b;
+        }
+    }
+
+    public void testInvokeStatic() throws Exception {
+        /*
+         * public static int generatedMethod(int a) {
+         *   int result = DexGeneratorTest.staticMethod(a);
+         *   return result;
+         * }
+         */
+
+        Code code = generator.newCode();
+        Local<Integer> localA = code.newParameter(intType);
+        Local<Integer> localResult = code.newLocal(intType);
+        Method<DexGeneratorTest, Integer> staticMethod
+                = dexGeneratorTestType.getMethod(intType, "staticMethod", intType);
+        code.invokeStatic(staticMethod, localResult, localA);
+        code.returnValue(localResult);
+
+        java.lang.reflect.Method method = newMethod(int.class, code, int.class);
+        assertEquals(10, method.invoke(null, 4));
+    }
+
+    @SuppressWarnings("unused") // called by generated code
+    public static int staticMethod(int a) {
+        return a + 6;
+    }
+
+    public void testInvokeVirtual() throws Exception {
+        /*
+         * public static int generatedMethod(DexGeneratorTest test, int a) {
+         *   int result = test.virtualMethod(a);
+         *   return result;
+         * }
+         */
+        Code code = generator.newCode();
+        Local<DexGeneratorTest> localInstance = code.newParameter(dexGeneratorTestType);
+        Local<Integer> localA = code.newParameter(intType);
+        Local<Integer> localResult = code.newLocal(intType);
+        Method<DexGeneratorTest, Integer> virtualMethod
+                = dexGeneratorTestType.getMethod(intType, "virtualMethod", intType);
+        code.invokeVirtual(virtualMethod, localResult, localInstance, localA);
+        code.returnValue(localResult);
+
+        java.lang.reflect.Method method = newMethod(
+                int.class, code, DexGeneratorTest.class, int.class);
+        assertEquals(9, method.invoke(null, this, 4));
+    }
+
+    @SuppressWarnings("unused") // called by generated code
+    public int virtualMethod(int a) {
+        return a + 5;
+    }
+
+    public void testInvokeDirect() throws Exception {
+        /*
+         * private int directMethod() {
+         *   int a = 5;
+         *   return a;
+         * }
+         *
+         * public static int generatedMethod(Generated g) {
+         *   int b = g.directMethod();
+         *   return b;
+         * }
+         */
+        Method<?, Integer> directMethod = generatedType.getMethod(intType, "directMethod");
+        Code directCode = generator.newCode();
+        directCode.newThisLocal(generatedType); // 'this' is unused
+        Local<Integer> localA = directCode.newLocal(intType);
+        directCode.loadConstant(localA, 5);
+        directCode.returnValue(localA);
+        directMethod.declare(ACC_PRIVATE, directCode);
+
+        Method<?, ?> method = generatedType.getMethod(intType, "generatedMethod", generatedType);
+        Code code = generator.newCode();
+        Local<Integer> localB = code.newLocal(intType);
+        Local<?> localG = code.newParameter(generatedType);
+        code.invokeDirect(directMethod, localB, localG);
+        code.returnValue(localB);
+        method.declare(ACC_PUBLIC | ACC_STATIC, code);
+
+        addDefaultConstructor();
+
+        generatedType.declare("Generated.java", ACC_PUBLIC, objectType);
+        Class<?> generatedClass = loadAndGenerate();
+        Object instance = generatedClass.newInstance();
+        java.lang.reflect.Method m = generatedClass.getMethod("generatedMethod", generatedClass);
+        assertEquals(5, m.invoke(null, instance));
+    }
+
+    public void testInvokeSuper() throws Exception {
+        /*
+         * public int superHashCode() {
+         *   int result = super.hashCode();
+         *   return result;
+         * }
+         * public int hashCode() {
+         *   return 0;
+         * }
+         */
+        Method<?, Integer> objectHashCode = objectType.getMethod(intType, "hashCode");
+        Code superHashCode = generator.newCode();
+        Local<Integer> localResult = superHashCode.newLocal(intType);
+        Local<?> localThis = superHashCode.newThisLocal(generatedType);
+        superHashCode.invokeSuper(objectHashCode, localResult, localThis);
+        superHashCode.returnValue(localResult);
+        generatedType.getMethod(intType, "superHashCode").declare(ACC_PUBLIC, superHashCode);
+
+        Code generatedHashCode = generator.newCode();
+        generatedHashCode.newThisLocal(generatedType);
+        Local<Integer> localZero = generatedHashCode.newLocal(intType);
+        generatedHashCode.loadConstant(localZero, 0);
+        generatedHashCode.returnValue(localZero);
+        generatedType.getMethod(intType, "hashCode").declare(ACC_PUBLIC, generatedHashCode);
+
+        addDefaultConstructor();
+
+        generatedType.declare("Generated.java", ACC_PUBLIC, objectType);
+        Class<?> generatedClass = loadAndGenerate();
+        Object instance = generatedClass.newInstance();
+        java.lang.reflect.Method m = generatedClass.getMethod("superHashCode");
+        assertEquals(System.identityHashCode(instance), m.invoke(instance));
+    }
+
+    @SuppressWarnings("unused") // called by generated code
+    public final int superMethod(int a) {
+        return a + 4;
+    }
+
+    public void testInvokeInterface() throws Exception {
+        /*
+         * public static Object generatedMethod(Callable c) {
+         *   Object result = c.call();
+         *   return result;
+         * }
+         */
+        Code code = generator.newCode();
+        Local<Callable> localC = code.newParameter(callableType);
+        Local<Object> localResult = code.newLocal(objectType);
+        code.invokeInterface(call, localResult, localC);
+        code.returnValue(localResult);
+
+        Callable<Object> callable = new Callable<Object>() {
+            public Object call() throws Exception {
+                return "abc";
+            }
+        };
+        java.lang.reflect.Method method = newMethod(Object.class, code, Callable.class);
+        assertEquals("abc", method.invoke(null, callable));
+    }
+
+    public void testParameterMismatch() throws Exception {
+        Code code = generator.newCode();
+        code.newParameter(intType);
+        code.newParameter(objectType);
+
+        Type<?>[] argTypes = {
+                generator.getType(Integer.class), // should fail because the code specifies int
+                objectType,
+        };
+
+        Method<?, Integer> method = generatedType.getMethod(intType, "generatedMethod", argTypes);
+        try {
+            method.declare(ACC_PUBLIC | ACC_STATIC, code);
+            fail();
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    public void testReturnTypeMismatch() {
+        fail("TODO");
+    }
+
+    public void testDeclareStaticFields() throws Exception {
+        /*
+         * class Generated {
+         *   public static int a;
+         *   protected static Object b;
+         * }
+         */
+        generatedType.getField(intType, "a").declare(ACC_PUBLIC | ACC_STATIC, 3);
+        generatedType.getField(objectType, "b").declare(ACC_PROTECTED | ACC_STATIC, null);
+        generatedType.declare("Generated.java", ACC_PUBLIC, objectType);
+
+        Class<?> generatedClass = loadAndGenerate();
+
+        java.lang.reflect.Field a = generatedClass.getField("a");
+        assertEquals(int.class, a.getType());
+        assertEquals(3, a.get(null));
+
+        java.lang.reflect.Field b = generatedClass.getDeclaredField("b");
+        assertEquals(Object.class, b.getType());
+        b.setAccessible(true);
+        assertEquals(null, b.get(null));
+    }
+
+    public void testDeclareInstanceFields() throws Exception {
+        /*
+         * class Generated {
+         *   public int a;
+         *   protected Object b;
+         * }
+         */
+        generatedType.getField(intType, "a").declare(ACC_PUBLIC, null);
+        generatedType.getField(objectType, "b").declare(ACC_PROTECTED, null);
+        generatedType.declare("Generated.java", ACC_PUBLIC, objectType);
+        addDefaultConstructor();
+
+        Class<?> generatedClass = loadAndGenerate();
+        Object instance = generatedClass.newInstance();
+
+        java.lang.reflect.Field a = generatedClass.getField("a");
+        assertEquals(int.class, a.getType());
+        assertEquals(0, a.get(instance));
+
+        java.lang.reflect.Field b = generatedClass.getDeclaredField("b");
+        assertEquals(Object.class, b.getType());
+        b.setAccessible(true);
+        assertEquals(null, b.get(instance));
+    }
+
+    /**
+     * Declare a constructor that takes an int parameter and assigns it to a
+     * field.
+     */
+    public void testDeclareConstructor() throws Exception {
+        /*
+         * class Generated {
+         *   public final int a;
+         *   public Generated(int a) {
+         *     this.a = a;
+         *   }
+         * }
+         */
+        Field<?, Integer> field = generatedType.getField(intType, "a");
+        field.declare(ACC_PUBLIC | ACC_FINAL, null);
+
+        Code constructor = generator.newCode();
+        Local<?> thisRef = constructor.newThisLocal(generatedType);
+        Local<Integer> parameter = constructor.newParameter(intType);
+        constructor.invokeDirect(objectType.getConstructor(), null, thisRef);
+        constructor.iput(field, (Local) thisRef, parameter); // TODO: type safety hurts us here
+        constructor.returnVoid();
+        generatedType.getConstructor(intType).declare(ACC_PUBLIC | ACC_CONSTRUCTOR, constructor);
+        generatedType.declare("Generated.java", ACC_PUBLIC, objectType);
+
+        Class<?> generatedClass = loadAndGenerate();
+        java.lang.reflect.Field a = generatedClass.getField("a");
+        Object instance = generatedClass.getConstructor(int.class).newInstance(0xabcd);
+        assertEquals(0xabcd, a.get(instance));
+    }
+
+    public void testReturnBoolean() throws Exception {
+        testReturnType(boolean.class, true);
+        testReturnType(byte.class, (byte) 5);
+        testReturnType(char.class, 'E');
+        testReturnType(double.class, 5.0);
+        testReturnType(float.class, 5.0f);
+        testReturnType(int.class, 5);
+        testReturnType(long.class, 5L);
+        testReturnType(short.class, (short) 5);
+        testReturnType(void.class, null);
+        testReturnType(String.class, "foo");
+        testReturnType(Class.class, List.class);
+    }
+
+    private <T> void testReturnType(Class<T> javaType, T value) throws Exception {
+        /*
+         * public int generatedMethod() {
+         *   int a = 5;
+         *   return a;
+         * }
+         */
+        reset();
+        Type<T> returnType = generator.getType(javaType);
+        Code code = generator.newCode();
+        if (value != null) {
+            Local<T> i = code.newLocal(returnType);
+            code.loadConstant(i, value);
+            code.returnValue(i);
+        } else {
+            code.returnVoid();
+        }
+        generatedType.getMethod(returnType, "generatedMethod")
+                .declare(ACC_PUBLIC | ACC_STATIC, code);
+        generatedType.declare("Generated.java", ACC_PUBLIC, objectType);
+
+        Class<?> generatedClass = loadAndGenerate();
+        java.lang.reflect.Method method = generatedClass.getMethod("generatedMethod");
+        assertEquals(javaType, method.getReturnType());
+        assertEquals(value, method.invoke(null));
+    }
+
+    public void testBranching() throws Exception {
+        java.lang.reflect.Method lt = newBranchingMethod(Comparison.LT);
+        assertEquals(Boolean.TRUE, lt.invoke(null, 1, 2));
+        assertEquals(Boolean.FALSE, lt.invoke(null, 1, 1));
+        assertEquals(Boolean.FALSE, lt.invoke(null, 2, 1));
+
+        java.lang.reflect.Method le = newBranchingMethod(Comparison.LE);
+        assertEquals(Boolean.TRUE, le.invoke(null, 1, 2));
+        assertEquals(Boolean.TRUE, le.invoke(null, 1, 1));
+        assertEquals(Boolean.FALSE, le.invoke(null, 2, 1));
+
+        java.lang.reflect.Method eq = newBranchingMethod(Comparison.EQ);
+        assertEquals(Boolean.FALSE, eq.invoke(null, 1, 2));
+        assertEquals(Boolean.TRUE, eq.invoke(null, 1, 1));
+        assertEquals(Boolean.FALSE, eq.invoke(null, 2, 1));
+
+        java.lang.reflect.Method ge = newBranchingMethod(Comparison.GE);
+        assertEquals(Boolean.FALSE, ge.invoke(null, 1, 2));
+        assertEquals(Boolean.TRUE, ge.invoke(null, 1, 1));
+        assertEquals(Boolean.TRUE, ge.invoke(null, 2, 1));
+
+        java.lang.reflect.Method gt = newBranchingMethod(Comparison.GT);
+        assertEquals(Boolean.FALSE, gt.invoke(null, 1, 2));
+        assertEquals(Boolean.FALSE, gt.invoke(null, 1, 1));
+        assertEquals(Boolean.TRUE, gt.invoke(null, 2, 1));
+
+        java.lang.reflect.Method ne = newBranchingMethod(Comparison.NE);
+        assertEquals(Boolean.TRUE, ne.invoke(null, 1, 2));
+        assertEquals(Boolean.FALSE, ne.invoke(null, 1, 1));
+        assertEquals(Boolean.TRUE, ne.invoke(null, 2, 1));
+    }
+
+    private java.lang.reflect.Method newBranchingMethod(Comparison comparison) throws Exception {
+        /*
+         * public static int generatedMethod(int localA, int localB) {
+         *   if (a comparison b) {
+         *     return true;
+         *   }
+         *   return false;
+         * }
+         */
+        reset();
+        Code code = generator.newCode();
+        Local<Integer> localA = code.newParameter(intType);
+        Local<Integer> localB = code.newParameter(intType);
+        Local<Boolean> result = code.newLocal(generator.getType(boolean.class));
+        Label afterIf = code.newLabel();
+        Label ifBody = code.newLabel();
+        code.compare(comparison, localA, localB, ifBody);
+        code.jump(afterIf);
+
+        code.mark(ifBody);
+        code.loadConstant(result, true);
+        code.returnValue(result);
+
+        code.mark(afterIf);
+        code.loadConstant(result, false);
+        code.returnValue(result);
+        return newMethod(boolean.class, code, int.class, int.class);
+    }
+
+    public void testCastIntegerToInteger() throws Exception {
+        java.lang.reflect.Method intToLong = newCastingMethod(int.class, long.class);
+        assertEquals(0x0000000000000000L, intToLong.invoke(null, 0x00000000));
+        assertEquals(0x000000007fffffffL, intToLong.invoke(null, 0x7fffffff));
+        assertEquals(0xffffffff80000000L, intToLong.invoke(null, 0x80000000));
+        assertEquals(0xffffffffffffffffL, intToLong.invoke(null, 0xffffffff));
+
+        java.lang.reflect.Method longToInt = newCastingMethod(long.class, int.class);
+        assertEquals(0x1234abcd, longToInt.invoke(null, 0x000000001234abcdL));
+        assertEquals(0x1234abcd, longToInt.invoke(null, 0x123456781234abcdL));
+        assertEquals(0x1234abcd, longToInt.invoke(null, 0xffffffff1234abcdL));
+
+        java.lang.reflect.Method intToShort = newCastingMethod(int.class, short.class);
+        assertEquals((short) 0x1234, intToShort.invoke(null, 0x00001234));
+        assertEquals((short) 0x1234, intToShort.invoke(null, 0xabcd1234));
+        assertEquals((short) 0x1234, intToShort.invoke(null, 0xffff1234));
+
+        java.lang.reflect.Method intToChar = newCastingMethod(int.class, char.class);
+        assertEquals((char) 0x1234, intToChar.invoke(null, 0x00001234));
+        assertEquals((char) 0x1234, intToChar.invoke(null, 0xabcd1234));
+        assertEquals((char) 0x1234, intToChar.invoke(null, 0xffff1234));
+
+        java.lang.reflect.Method intToByte = newCastingMethod(int.class, byte.class);
+        assertEquals((byte) 0x34, intToByte.invoke(null, 0x00000034));
+        assertEquals((byte) 0x34, intToByte.invoke(null, 0xabcd1234));
+        assertEquals((byte) 0x34, intToByte.invoke(null, 0xffffff34));
+    }
+
+    public void testCastIntegerToFloatingPoint() throws Exception {
+        java.lang.reflect.Method intToFloat = newCastingMethod(int.class, float.class);
+        assertEquals(0.0f, intToFloat.invoke(null, 0));
+        assertEquals(-1.0f, intToFloat.invoke(null, -1));
+        assertEquals(16777216f, intToFloat.invoke(null, 16777216));
+        assertEquals(16777216f, intToFloat.invoke(null, 16777217)); // precision
+
+        java.lang.reflect.Method intToDouble = newCastingMethod(int.class, double.class);
+        assertEquals(0.0, intToDouble.invoke(null, 0));
+        assertEquals(-1.0, intToDouble.invoke(null, -1));
+        assertEquals(16777216.0, intToDouble.invoke(null, 16777216));
+        assertEquals(16777217.0, intToDouble.invoke(null, 16777217));
+
+        java.lang.reflect.Method longToFloat = newCastingMethod(long.class, float.class);
+        assertEquals(0.0f, longToFloat.invoke(null, 0L));
+        assertEquals(-1.0f, longToFloat.invoke(null, -1L));
+        assertEquals(16777216f, longToFloat.invoke(null, 16777216L));
+        assertEquals(16777216f, longToFloat.invoke(null, 16777217L));
+
+        java.lang.reflect.Method longToDouble = newCastingMethod(long.class, double.class);
+        assertEquals(0.0, longToDouble.invoke(null, 0L));
+        assertEquals(-1.0, longToDouble.invoke(null, -1L));
+        assertEquals(9007199254740992.0, longToDouble.invoke(null, 9007199254740992L));
+        assertEquals(9007199254740992.0, longToDouble.invoke(null, 9007199254740993L)); // precision
+    }
+
+    public void testCastFloatingPointToInteger() throws Exception {
+        java.lang.reflect.Method floatToInt = newCastingMethod(float.class, int.class);
+        assertEquals(0, floatToInt.invoke(null, 0.0f));
+        assertEquals(-1, floatToInt.invoke(null, -1.0f));
+        assertEquals(Integer.MAX_VALUE, floatToInt.invoke(null, 10e15f));
+        assertEquals(0, floatToInt.invoke(null, 0.5f));
+        assertEquals(Integer.MIN_VALUE, floatToInt.invoke(null, Float.NEGATIVE_INFINITY));
+        assertEquals(0, floatToInt.invoke(null, Float.NaN));
+
+        java.lang.reflect.Method floatToLong = newCastingMethod(float.class, long.class);
+        assertEquals(0L, floatToLong.invoke(null, 0.0f));
+        assertEquals(-1L, floatToLong.invoke(null, -1.0f));
+        assertEquals(10000000272564224L, floatToLong.invoke(null, 10e15f));
+        assertEquals(0L, floatToLong.invoke(null, 0.5f));
+        assertEquals(Long.MIN_VALUE, floatToLong.invoke(null, Float.NEGATIVE_INFINITY));
+        assertEquals(0L, floatToLong.invoke(null, Float.NaN));
+
+        java.lang.reflect.Method doubleToInt = newCastingMethod(double.class, int.class);
+        assertEquals(0, doubleToInt.invoke(null, 0.0));
+        assertEquals(-1, doubleToInt.invoke(null, -1.0));
+        assertEquals(Integer.MAX_VALUE, doubleToInt.invoke(null, 10e15));
+        assertEquals(0, doubleToInt.invoke(null, 0.5));
+        assertEquals(Integer.MIN_VALUE, doubleToInt.invoke(null, Double.NEGATIVE_INFINITY));
+        assertEquals(0, doubleToInt.invoke(null, Double.NaN));
+
+        java.lang.reflect.Method doubleToLong = newCastingMethod(double.class, long.class);
+        assertEquals(0L, doubleToLong.invoke(null, 0.0));
+        assertEquals(-1L, doubleToLong.invoke(null, -1.0));
+        assertEquals(10000000000000000L, doubleToLong.invoke(null, 10e15));
+        assertEquals(0L, doubleToLong.invoke(null, 0.5));
+        assertEquals(Long.MIN_VALUE, doubleToLong.invoke(null, Double.NEGATIVE_INFINITY));
+        assertEquals(0L, doubleToLong.invoke(null, Double.NaN));
+    }
+
+    public void testCastFloatingPointToFloatingPoint() throws Exception {
+        java.lang.reflect.Method floatToDouble = newCastingMethod(float.class, double.class);
+        assertEquals(0.0, floatToDouble.invoke(null, 0.0f));
+        assertEquals(-1.0, floatToDouble.invoke(null, -1.0f));
+        assertEquals(0.5, floatToDouble.invoke(null, 0.5f));
+        assertEquals(Double.NEGATIVE_INFINITY, floatToDouble.invoke(null, Float.NEGATIVE_INFINITY));
+        assertEquals(Double.NaN, floatToDouble.invoke(null, Float.NaN));
+
+        java.lang.reflect.Method doubleToFloat = newCastingMethod(double.class, float.class);
+        assertEquals(0.0f, doubleToFloat.invoke(null, 0.0));
+        assertEquals(-1.0f, doubleToFloat.invoke(null, -1.0));
+        assertEquals(0.5f, doubleToFloat.invoke(null, 0.5));
+        assertEquals(Float.NEGATIVE_INFINITY, doubleToFloat.invoke(null, Double.NEGATIVE_INFINITY));
+        assertEquals(Float.NaN, doubleToFloat.invoke(null, Double.NaN));
+    }
+
+    private java.lang.reflect.Method newCastingMethod(Class<?> source, Class<?> target)
+            throws Exception {
+        /*
+         * public static short generatedMethod(int source) {
+         *   short casted = (short) source;
+         *   return casted;
+         * }
+         */
+        reset();
+        Type<?> sourceType = generator.getType(source);
+        Type<?> targetType = generator.getType(target);
+        Code code = generator.newCode();
+        Local<?> localSource = code.newParameter(sourceType);
+        Local<?> localCasted = code.newLocal(targetType);
+        code.cast(localSource, localCasted);
+        code.returnValue(localCasted);
+        return newMethod(target, code, source);
+    }
+
+    public void testNot() throws Exception {
+        java.lang.reflect.Method notInteger = newNotMethod(int.class);
+        assertEquals(0xffffffff, notInteger.invoke(null, 0x00000000));
+        assertEquals(0x00000000, notInteger.invoke(null, 0xffffffff));
+        assertEquals(0xedcba987, notInteger.invoke(null, 0x12345678));
+
+        java.lang.reflect.Method notLong = newNotMethod(long.class);
+        assertEquals(0xffffffffffffffffL, notLong.invoke(null, 0x0000000000000000L));
+        assertEquals(0x0000000000000000L, notLong.invoke(null, 0xffffffffffffffffL));
+        assertEquals(0x98765432edcba987L, notLong.invoke(null, 0x6789abcd12345678L));
+    }
+
+    private java.lang.reflect.Method newNotMethod(Class<?> source) throws Exception {
+        /*
+         * public static short generatedMethod(int source) {
+         *   source = ~source;
+         *   return not;
+         * }
+         */
+        reset();
+        Type<?> sourceType = generator.getType(source);
+        Code code = generator.newCode();
+        Local<?> localSource = code.newParameter(sourceType);
+        code.not((Local) localSource, localSource); // TODO: type safety
+        code.returnValue(localSource);
+        return newMethod(source, code, source);
+    }
+
+    public void testNegate() throws Exception {
+        java.lang.reflect.Method negateInteger = newNegateMethod(int.class);
+        assertEquals(0, negateInteger.invoke(null, 0));
+        assertEquals(-1, negateInteger.invoke(null, 1));
+        assertEquals(Integer.MIN_VALUE, negateInteger.invoke(null, Integer.MIN_VALUE));
+
+        java.lang.reflect.Method negateLong = newNegateMethod(long.class);
+        assertEquals(0L, negateLong.invoke(null, 0));
+        assertEquals(-1L, negateLong.invoke(null, 1));
+        assertEquals(Long.MIN_VALUE, negateLong.invoke(null, Long.MIN_VALUE));
+
+        java.lang.reflect.Method negateFloat = newNegateMethod(float.class);
+        assertEquals(-0.0f, negateFloat.invoke(null, 0.0f));
+        assertEquals(-1.0f, negateFloat.invoke(null, 1.0f));
+        assertEquals(Float.NaN, negateFloat.invoke(null, Float.NaN));
+        assertEquals(Float.POSITIVE_INFINITY, negateFloat.invoke(null, Float.NEGATIVE_INFINITY));
+
+        java.lang.reflect.Method negateDouble = newNegateMethod(double.class);
+        assertEquals(-0.0, negateDouble.invoke(null, 0.0));
+        assertEquals(-1.0, negateDouble.invoke(null, 1.0));
+        assertEquals(Double.NaN, negateDouble.invoke(null, Double.NaN));
+        assertEquals(Double.POSITIVE_INFINITY, negateDouble.invoke(null, Double.NEGATIVE_INFINITY));
+    }
+
+    private java.lang.reflect.Method newNegateMethod(Class<?> source) throws Exception {
+        /*
+         * public static short generatedMethod(int source) {
+         *   source = -source;
+         *   return not;
+         * }
+         */
+        reset();
+        Type<?> sourceType = generator.getType(source);
+        Code code = generator.newCode();
+        Local<?> localSource = code.newParameter(sourceType);
+        code.negate((Local) localSource, localSource); // TODO: type safety
+        code.returnValue(localSource);
+        return newMethod(source, code, source);
+    }
+
+    public void testIntBinaryOps() throws Exception {
+        java.lang.reflect.Method add = newBinaryOpMethod(int.class, BinaryOp.ADD);
+        assertEquals(79, add.invoke(null, 75, 4));
+
+        java.lang.reflect.Method subtract = newBinaryOpMethod(int.class, BinaryOp.SUBTRACT);
+        assertEquals(71, subtract.invoke(null, 75, 4));
+
+        java.lang.reflect.Method multiply = newBinaryOpMethod(int.class, BinaryOp.MULTIPLY);
+        assertEquals(300, multiply.invoke(null, 75, 4));
+
+        java.lang.reflect.Method divide = newBinaryOpMethod(int.class, BinaryOp.DIVIDE);
+        assertEquals(18, divide.invoke(null, 75, 4));
+        try {
+            divide.invoke(null, 75, 0);
+            fail();
+        } catch (InvocationTargetException expected) {
+            assertEquals(ArithmeticException.class, expected.getCause().getClass());
+        }
+
+        java.lang.reflect.Method remainder = newBinaryOpMethod(int.class, BinaryOp.REMAINDER);
+        assertEquals(3, remainder.invoke(null, 75, 4));
+        try {
+            remainder.invoke(null, 75, 0);
+            fail();
+        } catch (InvocationTargetException expected) {
+            assertEquals(ArithmeticException.class, expected.getCause().getClass());
+        }
+
+        java.lang.reflect.Method and = newBinaryOpMethod(int.class, BinaryOp.AND);
+        assertEquals(0xff000000, and.invoke(null, 0xff00ff00, 0xffff0000));
+
+        java.lang.reflect.Method or = newBinaryOpMethod(int.class, BinaryOp.OR);
+        assertEquals(0xffffff00, or.invoke(null, 0xff00ff00, 0xffff0000));
+
+        java.lang.reflect.Method xor = newBinaryOpMethod(int.class, BinaryOp.XOR);
+        assertEquals(0x00ffff00, xor.invoke(null, 0xff00ff00, 0xffff0000));
+
+        java.lang.reflect.Method shiftLeft = newBinaryOpMethod(int.class, BinaryOp.SHIFT_LEFT);
+        assertEquals(0xcd123400, shiftLeft.invoke(null, 0xabcd1234, 8));
+
+        java.lang.reflect.Method shiftRight = newBinaryOpMethod(int.class, BinaryOp.SHIFT_RIGHT);
+        assertEquals(0xffabcd12, shiftRight.invoke(null, 0xabcd1234, 8));
+
+        java.lang.reflect.Method unsignedShiftRight = newBinaryOpMethod(int.class,
+                BinaryOp.UNSIGNED_SHIFT_RIGHT);
+        assertEquals(0x00abcd12, unsignedShiftRight.invoke(null, 0xabcd1234, 8));
+    }
+
+    public void testLongBinaryOps() throws Exception {
+        java.lang.reflect.Method add = newBinaryOpMethod(long.class, BinaryOp.ADD);
+        assertEquals(79L, add.invoke(null, 75L, 4L));
+
+        java.lang.reflect.Method subtract = newBinaryOpMethod(long.class, BinaryOp.SUBTRACT);
+        assertEquals(71L, subtract.invoke(null, 75L, 4L));
+
+        java.lang.reflect.Method multiply = newBinaryOpMethod(long.class, BinaryOp.MULTIPLY);
+        assertEquals(300L, multiply.invoke(null, 75L, 4L));
+
+        java.lang.reflect.Method divide = newBinaryOpMethod(long.class, BinaryOp.DIVIDE);
+        assertEquals(18L, divide.invoke(null, 75L, 4L));
+        try {
+            divide.invoke(null, 75L, 0L);
+            fail();
+        } catch (InvocationTargetException expected) {
+            assertEquals(ArithmeticException.class, expected.getCause().getClass());
+        }
+
+        java.lang.reflect.Method remainder = newBinaryOpMethod(long.class, BinaryOp.REMAINDER);
+        assertEquals(3L, remainder.invoke(null, 75L, 4L));
+        try {
+            remainder.invoke(null, 75L, 0L);
+            fail();
+        } catch (InvocationTargetException expected) {
+            assertEquals(ArithmeticException.class, expected.getCause().getClass());
+        }
+
+        java.lang.reflect.Method and = newBinaryOpMethod(long.class, BinaryOp.AND);
+        assertEquals(0xff00ff0000000000L,
+                and.invoke(null, 0xff00ff00ff00ff00L, 0xffffffff00000000L));
+
+        java.lang.reflect.Method or = newBinaryOpMethod(long.class, BinaryOp.OR);
+        assertEquals(0xffffffffff00ff00L, or.invoke(null, 0xff00ff00ff00ff00L, 0xffffffff00000000L));
+
+        java.lang.reflect.Method xor = newBinaryOpMethod(long.class, BinaryOp.XOR);
+        assertEquals(0x00ff00ffff00ff00L,
+                xor.invoke(null, 0xff00ff00ff00ff00L, 0xffffffff00000000L));
+
+        java.lang.reflect.Method shiftLeft = newBinaryOpMethod(long.class, BinaryOp.SHIFT_LEFT);
+        assertEquals(0xcdef012345678900L, shiftLeft.invoke(null, 0xabcdef0123456789L, 8L));
+
+        java.lang.reflect.Method shiftRight = newBinaryOpMethod(long.class, BinaryOp.SHIFT_RIGHT);
+        assertEquals(0xffabcdef01234567L, shiftRight.invoke(null, 0xabcdef0123456789L, 8L));
+
+        java.lang.reflect.Method unsignedShiftRight = newBinaryOpMethod(long.class,
+                BinaryOp.UNSIGNED_SHIFT_RIGHT);
+        assertEquals(0x00abcdef01234567L, unsignedShiftRight.invoke(null, 0xabcdef0123456789L, 8L));
+    }
+
+    public void testFloatBinaryOps() throws Exception {
+        java.lang.reflect.Method add = newBinaryOpMethod(float.class, BinaryOp.ADD);
+        assertEquals(6.75f, add.invoke(null, 5.5f, 1.25f));
+
+        java.lang.reflect.Method subtract = newBinaryOpMethod(float.class, BinaryOp.SUBTRACT);
+        assertEquals(4.25f, subtract.invoke(null, 5.5f, 1.25f));
+
+        java.lang.reflect.Method multiply = newBinaryOpMethod(float.class, BinaryOp.MULTIPLY);
+        assertEquals(6.875f, multiply.invoke(null, 5.5f, 1.25f));
+
+        java.lang.reflect.Method divide = newBinaryOpMethod(float.class, BinaryOp.DIVIDE);
+        assertEquals(4.4f, divide.invoke(null, 5.5f, 1.25f));
+        assertEquals(Float.POSITIVE_INFINITY, divide.invoke(null, 5.5f, 0.0f));
+
+        java.lang.reflect.Method remainder = newBinaryOpMethod(float.class, BinaryOp.REMAINDER);
+        assertEquals(0.5f, remainder.invoke(null, 5.5f, 1.25f));
+        assertEquals(Float.NaN, remainder.invoke(null, 5.5f, 0.0f));
+    }
+
+    public void testDoubleBinaryOps() throws Exception {
+        java.lang.reflect.Method add = newBinaryOpMethod(double.class, BinaryOp.ADD);
+        assertEquals(6.75, add.invoke(null, 5.5, 1.25));
+
+        java.lang.reflect.Method subtract = newBinaryOpMethod(double.class, BinaryOp.SUBTRACT);
+        assertEquals(4.25, subtract.invoke(null, 5.5, 1.25));
+
+        java.lang.reflect.Method multiply = newBinaryOpMethod(double.class, BinaryOp.MULTIPLY);
+        assertEquals(6.875, multiply.invoke(null, 5.5, 1.25));
+
+        java.lang.reflect.Method divide = newBinaryOpMethod(double.class, BinaryOp.DIVIDE);
+        assertEquals(4.4, divide.invoke(null, 5.5, 1.25));
+        assertEquals(Double.POSITIVE_INFINITY, divide.invoke(null, 5.5, 0.0));
+
+        java.lang.reflect.Method remainder = newBinaryOpMethod(double.class, BinaryOp.REMAINDER);
+        assertEquals(0.5, remainder.invoke(null, 5.5, 1.25));
+        assertEquals(Double.NaN, remainder.invoke(null, 5.5, 0.0));
+    }
+
+    private java.lang.reflect.Method newBinaryOpMethod(Class<?> valueClass, BinaryOp op)
+            throws Exception {
+        /*
+         * public static int binaryOp(int a, int b) {
+         *   int result = a + b;
+         *   return result;
+         * }
+         */
+        reset();
+        Type<?> valueType = generator.getType(valueClass);
+        Code code = generator.newCode();
+        Local<?> localA = code.newParameter(valueType);
+        Local<?> localB = code.newParameter(valueType);
+        Local<?> localResult = code.newLocal(valueType);
+        code.op(op, (Local) localResult, (Local) localA, (Local) localB); // TODO: type safety
+        code.returnValue(localResult);
+        return newMethod(valueClass, code, valueClass, valueClass);
+    }
+
+    public void testReadAndWriteInstanceFields() throws Exception {
+        Instance instance = new Instance();
+
+        java.lang.reflect.Method intSwap = newInstanceSwapMethod(int.class, "intValue");
+        instance.intValue = 5;
+        assertEquals(5, intSwap.invoke(null, instance, 10));
+        assertEquals(10, instance.intValue);
+
+        java.lang.reflect.Method longSwap = newInstanceSwapMethod(long.class, "longValue");
+        instance.longValue = 500L;
+        assertEquals(500L, longSwap.invoke(null, instance, 1234L));
+        assertEquals(1234L, instance.longValue);
+
+        java.lang.reflect.Method booleanSwap = newInstanceSwapMethod(boolean.class, "booleanValue");
+        instance.booleanValue = false;
+        assertEquals(false, booleanSwap.invoke(null, instance, true));
+        assertEquals(true, instance.booleanValue);
+
+        java.lang.reflect.Method floatSwap = newInstanceSwapMethod(float.class, "floatValue");
+        instance.floatValue = 1.5f;
+        assertEquals(1.5f, floatSwap.invoke(null, instance, 0.5f));
+        assertEquals(0.5f, instance.floatValue);
+
+        java.lang.reflect.Method doubleSwap = newInstanceSwapMethod(double.class, "doubleValue");
+        instance.doubleValue = 155.5;
+        assertEquals(155.5, doubleSwap.invoke(null, instance, 266.6));
+        assertEquals(266.6, instance.doubleValue);
+
+        java.lang.reflect.Method objectSwap = newInstanceSwapMethod(Object.class, "objectValue");
+        instance.objectValue = "before";
+        assertEquals("before", objectSwap.invoke(null, instance, "after"));
+        assertEquals("after", instance.objectValue);
+
+        java.lang.reflect.Method byteSwap = newInstanceSwapMethod(byte.class, "byteValue");
+        instance.byteValue = 0x35;
+        assertEquals((byte) 0x35, byteSwap.invoke(null, instance, (byte) 0x64));
+        assertEquals((byte) 0x64, instance.byteValue);
+
+        java.lang.reflect.Method charSwap = newInstanceSwapMethod(char.class, "charValue");
+        instance.charValue = 'A';
+        assertEquals('A', charSwap.invoke(null, instance, 'B'));
+        assertEquals('B', instance.charValue);
+
+        java.lang.reflect.Method shortSwap = newInstanceSwapMethod(short.class, "shortValue");
+        instance.shortValue = (short) 0xabcd;
+        assertEquals((short) 0xabcd, shortSwap.invoke(null, instance, (short) 0x1234));
+        assertEquals((short) 0x1234, instance.shortValue);
+    }
+
+    public class Instance {
+        public int intValue;
+        public long longValue;
+        public float floatValue;
+        public double doubleValue;
+        public Object objectValue;
+        public boolean booleanValue;
+        public byte byteValue;
+        public char charValue;
+        public short shortValue;
+    }
+
+    private <V> java.lang.reflect.Method newInstanceSwapMethod(
+            Class<V> valueClass, String fieldName) throws Exception {
+        /*
+         * public static int generatedMethod(Instance instance, int newValue) {
+         *   int oldValue = instance.intValue;
+         *   instance.intValue = newValue;
+         *   return oldValue;
+         * }
+         */
+        reset();
+        Type<V> valueType = generator.getType(valueClass);
+        Type<Instance> objectType = generator.getType(Instance.class);
+        Field<Instance, V> field = objectType.getField(valueType, fieldName);
+        Code code = generator.newCode();
+        Local<Instance> localInstance = code.newParameter(objectType);
+        Local<V> localNewValue = code.newParameter(valueType);
+        Local<V> localOldValue = code.newLocal(valueType);
+        code.iget(field, localInstance, localOldValue);
+        code.iput(field, localInstance, localNewValue);
+        code.returnValue(localOldValue);
+        return newMethod(valueClass, code, Instance.class, valueClass);
+    }
+
+    public void testReadAndWriteStaticFields() throws Exception {
+        java.lang.reflect.Method intSwap = newStaticSwapMethod(int.class, "intValue");
+        Static.intValue = 5;
+        assertEquals(5, intSwap.invoke(null, 10));
+        assertEquals(10, Static.intValue);
+
+        java.lang.reflect.Method longSwap = newStaticSwapMethod(long.class, "longValue");
+        Static.longValue = 500L;
+        assertEquals(500L, longSwap.invoke(null, 1234L));
+        assertEquals(1234L, Static.longValue);
+
+        java.lang.reflect.Method booleanSwap = newStaticSwapMethod(boolean.class, "booleanValue");
+        Static.booleanValue = false;
+        assertEquals(false, booleanSwap.invoke(null, true));
+        assertEquals(true, Static.booleanValue);
+
+        java.lang.reflect.Method floatSwap = newStaticSwapMethod(float.class, "floatValue");
+        Static.floatValue = 1.5f;
+        assertEquals(1.5f, floatSwap.invoke(null, 0.5f));
+        assertEquals(0.5f, Static.floatValue);
+
+        java.lang.reflect.Method doubleSwap = newStaticSwapMethod(double.class, "doubleValue");
+        Static.doubleValue = 155.5;
+        assertEquals(155.5, doubleSwap.invoke(null, 266.6));
+        assertEquals(266.6, Static.doubleValue);
+
+        java.lang.reflect.Method objectSwap = newStaticSwapMethod(Object.class, "objectValue");
+        Static.objectValue = "before";
+        assertEquals("before", objectSwap.invoke(null, "after"));
+        assertEquals("after", Static.objectValue);
+
+        java.lang.reflect.Method byteSwap = newStaticSwapMethod(byte.class, "byteValue");
+        Static.byteValue = 0x35;
+        assertEquals((byte) 0x35, byteSwap.invoke(null, (byte) 0x64));
+        assertEquals((byte) 0x64, Static.byteValue);
+
+        java.lang.reflect.Method charSwap = newStaticSwapMethod(char.class, "charValue");
+        Static.charValue = 'A';
+        assertEquals('A', charSwap.invoke(null, 'B'));
+        assertEquals('B', Static.charValue);
+
+        java.lang.reflect.Method shortSwap = newStaticSwapMethod(short.class, "shortValue");
+        Static.shortValue = (short) 0xabcd;
+        assertEquals((short) 0xabcd, shortSwap.invoke(null, (short) 0x1234));
+        assertEquals((short) 0x1234, Static.shortValue);
+    }
+
+    public static class Static {
+        public static int intValue;
+        public static long longValue;
+        public static float floatValue;
+        public static double doubleValue;
+        public static Object objectValue;
+        public static boolean booleanValue;
+        public static byte byteValue;
+        public static char charValue;
+        public static short shortValue;
+    }
+
+    private <V> java.lang.reflect.Method newStaticSwapMethod(Class<V> valueClass, String fieldName)
+            throws Exception {
+        /*
+         * public static int generatedMethod(int newValue) {
+         *   int oldValue = Static.intValue;
+         *   Static.intValue = newValue;
+         *   return oldValue;
+         * }
+         */
+        reset();
+        Type<V> valueType = generator.getType(valueClass);
+        Type<Static> objectType = generator.getType(Static.class);
+        Field<Static, V> field = objectType.getField(valueType, fieldName);
+        Code code = generator.newCode();
+        Local<V> localNewValue = code.newParameter(valueType);
+        Local<V> localOldValue = code.newLocal(valueType);
+        code.sget(field, localOldValue);
+        code.sput(field, localNewValue);
+        code.returnValue(localOldValue);
+        return newMethod(valueClass, code, valueClass);
+    }
+
+    /**
+     * Tests that we can construct a for loop.
+     */
+    public void testForLoop() throws Exception {
+        /*
+         * public static int generatedMethod(int count) {
+         *   int result = 1;
+         *   for (int i = 0; i < count; i += 1) {
+         *     result = result * 2;
+         *   }
+         *   return result;
+         * }
+         */
+        Code code = generator.newCode();
+        Local<Integer> localCount = code.newParameter(intType);
+        Local<Integer> localResult = code.newLocal(intType);
+        Local<Integer> localI = code.newLocal(intType);
+        Local<Integer> local1 = code.newLocal(intType);
+        Local<Integer> local2 = code.newLocal(intType);
+        code.loadConstant(local1, 1);
+        code.loadConstant(local2, 2);
+        code.loadConstant(localResult, 1);
+        code.loadConstant(localI, 0);
+        Label loopCondition = code.newLabel();
+        Label loopBody = code.newLabel();
+        Label afterLoop = code.newLabel();
+        code.mark(loopCondition);
+        code.compare(Comparison.LT, localI, localCount, loopBody);
+        code.jump(afterLoop);
+        code.mark(loopBody);
+        code.op(BinaryOp.MULTIPLY, localResult, localResult, local2);
+        code.op(BinaryOp.ADD, localI, localI, local1);
+        code.jump(loopCondition);
+        code.mark(afterLoop);
+        code.returnValue(localResult);
+
+        java.lang.reflect.Method pow2 = newMethod(int.class, code, int.class);
+        assertEquals(1, pow2.invoke(null, 0));
+        assertEquals(2, pow2.invoke(null, 1));
+        assertEquals(4, pow2.invoke(null, 2));
+        assertEquals(8, pow2.invoke(null, 3));
+        assertEquals(16, pow2.invoke(null, 4));
+    }
+
+    /**
+     * Tests that we can construct a while loop.
+     */
+    public void testWhileLoop() throws Exception {
+        /*
+         * public static int generatedMethod(int max) {
+         *   int result = 1;
+         *   while (result < max) {
+         *     result = result * 2;
+         *   }
+         *   return result;
+         * }
+         */
+        Code code = generator.newCode();
+        Local<Integer> localMax = code.newParameter(intType);
+        Local<Integer> localResult = code.newLocal(intType);
+        Local<Integer> local2 = code.newLocal(intType);
+        code.loadConstant(localResult, 1);
+        code.loadConstant(local2, 2);
+        Label loopCondition = code.newLabel();
+        Label loopBody = code.newLabel();
+        Label afterLoop = code.newLabel();
+        code.mark(loopCondition);
+        code.compare(Comparison.LT, localResult, localMax, loopBody);
+        code.jump(afterLoop);
+        code.mark(loopBody);
+        code.op(BinaryOp.MULTIPLY, localResult, localResult, local2);
+        code.jump(loopCondition);
+        code.mark(afterLoop);
+        code.returnValue(localResult);
+
+        java.lang.reflect.Method ceilPow2 = newMethod(int.class, code, int.class);
+        assertEquals(1, ceilPow2.invoke(null, 1));
+        assertEquals(2, ceilPow2.invoke(null, 2));
+        assertEquals(4, ceilPow2.invoke(null, 3));
+        assertEquals(16, ceilPow2.invoke(null, 10));
+        assertEquals(128, ceilPow2.invoke(null, 100));
+        assertEquals(1024, ceilPow2.invoke(null, 1000));
+    }
+
+    public void testIfElseBlock() throws Exception {
+        /*
+         * public static int generatedMethod(int a, int b, int c) {
+         *   if (a < b) {
+         *     if (a < c) {
+         *       return a;
+         *     } else {
+         *       return c;
+         *     }
+         *   } else if (b < c) {
+         *     return b;
+         *   } else {
+         *     return c;
+         *   }
+         * }
+         */
+        Code code = generator.newCode();
+        Local<Integer> localA = code.newParameter(intType);
+        Local<Integer> localB = code.newParameter(intType);
+        Local<Integer> localC = code.newParameter(intType);
+        Label aLessThanB = code.newLabel();
+        Label aLessThanC = code.newLabel();
+        Label bLessThanC = code.newLabel();
+        code.compare(Comparison.LT, localA, localB, aLessThanB);
+        code.compare(Comparison.LT, localB, localC, bLessThanC);
+        code.returnValue(localC);
+        // (a < b)
+        code.mark(aLessThanB);
+        code.compare(Comparison.LT, localA, localC, aLessThanC);
+        code.returnValue(localC);
+        // (a < c)
+        code.mark(aLessThanC);
+        code.returnValue(localA);
+        // (b < c)
+        code.mark(bLessThanC);
+        code.returnValue(localB);
+
+        java.lang.reflect.Method min = newMethod(int.class, code, int.class, int.class, int.class);
+        assertEquals(1, min.invoke(null, 1, 2, 3));
+        assertEquals(1, min.invoke(null, 2, 3, 1));
+        assertEquals(1, min.invoke(null, 2, 1, 3));
+        assertEquals(1, min.invoke(null, 3, 2, 1));
+    }
+
+    // TODO: cast
+
+    // TODO: instanceof
+
+    // TODO: array ops including new array, aget, etc.
+
+    // TODO: throw + catch
+
+    // TODO: cmp float
+
+    // TODO: fail if a label is unreachable (never navigated to)
+
+    private void addDefaultConstructor() {
+        Code code = generator.newCode();
+        Local<?> thisRef = code.newThisLocal(generatedType);
+        code.invokeDirect(objectType.getConstructor(), null, thisRef);
+        code.returnVoid();
+        generatedType.getConstructor().declare(ACC_PUBLIC | ACC_CONSTRUCTOR, code);
+    }
+
+    /**
+     * Returns a method containing the body of {@code code}, accepting the
+     * {@code argClasses}, and returning {@code argClasses}.
+     */
+    private java.lang.reflect.Method newMethod(
+            Class<?> returnClass, Code code, Class<?>... argClasses) throws Exception {
+        Type<?> returnType = generator.getType(returnClass);
+        Type<?>[] argTypes = new Type<?>[argClasses.length];
+        for (int i = 0; i < argClasses.length; i++) {
+            argTypes[i] = generator.getType(argClasses[i]);
+        }
+
+        Method<?, ?> method = generatedType.getMethod(returnType, "generatedMethod", argTypes);
+        generatedType.declare("Generated.java", ACC_PUBLIC, objectType);
+        method.declare(ACC_PUBLIC | ACC_STATIC, code);
+        return loadAndGenerate().getMethod("generatedMethod", argClasses);
+    }
+
+    private Class<?> loadAndGenerate() throws IOException, ClassNotFoundException {
+        return generator.load(DexGeneratorTest.class.getClassLoader()).loadClass("Generated");
+    }
+
+    private static Class<?> unbox(Class<?> boxed) {
+        Class<?> unboxed = BOXED_TO_PRIMITIVE.get(boxed);
+        return unboxed != null ? unboxed : boxed;
+    }
+}
diff --git a/dx/junit-tests/com/android/dx/gen/TypeTest.java b/dx/junit-tests/com/android/dx/gen/TypeTest.java
new file mode 100644
index 0000000..d713879
--- /dev/null
+++ b/dx/junit-tests/com/android/dx/gen/TypeTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dx.gen;
+
+import junit.framework.TestCase;
+
+public final class TypeTest extends TestCase {
+
+    private final DexGenerator generator = new DexGenerator();
+
+    public void testGetType() {
+        assertEquals("Ljava/lang/String;", generator.getType(String.class).getName());
+        assertEquals("[Ljava/lang/String;", generator.getType(String[].class).getName());
+        assertEquals("[[Ljava/lang/String;", generator.getType(String[][].class).getName());
+        assertEquals("I", generator.getType(int.class).getName());
+        assertEquals("[I", generator.getType(int[].class).getName());
+        assertEquals("[[I", generator.getType(int[][].class).getName());
+    }
+}
diff --git a/dx/src/com/android/dx/cf/code/ConcreteMethod.java b/dx/src/com/android/dx/cf/code/ConcreteMethod.java
index da6cff7..1e1bc21 100644
--- a/dx/src/com/android/dx/cf/code/ConcreteMethod.java
+++ b/dx/src/com/android/dx/cf/code/ConcreteMethod.java
@@ -72,11 +72,15 @@
      * @param keepLocals whether to keep the local variable
      * information (if any)
      */
-    public ConcreteMethod(Method method, ClassFile cf, boolean keepLines,
-                          boolean keepLocals) {
+    public ConcreteMethod(Method method, ClassFile cf, boolean keepLines, boolean keepLocals) {
+        this(method, cf.getAccessFlags(), cf.getSourceFile(), keepLines, keepLocals);
+    }
+
+    public ConcreteMethod(Method method, int accessFlags, CstUtf8 sourceFile,
+            boolean keepLines, boolean keepLocals) {
         this.method = method;
-        this.accSuper = (cf.getAccessFlags() & AccessFlags.ACC_SUPER) != 0;
-        this.sourceFile = cf.getSourceFile();
+        this.accSuper = (accessFlags & AccessFlags.ACC_SUPER) != 0;
+        this.sourceFile = sourceFile;
 
         AttributeList attribs = method.getAttributes();
         this.attCode = (AttCode) attribs.findFirst(AttCode.ATTRIBUTE_NAME);
diff --git a/dx/src/com/android/dx/dex/code/OutputFinisher.java b/dx/src/com/android/dx/dex/code/OutputFinisher.java
index 1b13fab..51ef9a2 100644
--- a/dx/src/com/android/dx/dex/code/OutputFinisher.java
+++ b/dx/src/com/android/dx/dex/code/OutputFinisher.java
@@ -28,7 +28,6 @@
 import com.android.dx.rop.cst.CstType;
 import com.android.dx.rop.cst.CstUtf8;
 import com.android.dx.rop.type.Type;
-
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.HashSet;
@@ -520,7 +519,11 @@
      * @return {@code non-null;} the opcode that fits
      */
     private Dop findExpandedOpcodeForInsn(DalvInsn insn) {
-        return findOpcodeForInsn(insn.getLowRegVersion(), insn.getOpcode());
+        Dop result = findOpcodeForInsn(insn.getLowRegVersion(), insn.getOpcode());
+        if (result == null) {
+            throw new AssertionError();
+        }
+        return result;
     }
 
     /**
diff --git a/dx/src/com/android/dx/gen/BinaryOp.java b/dx/src/com/android/dx/gen/BinaryOp.java
new file mode 100644
index 0000000..9c8f25e
--- /dev/null
+++ b/dx/src/com/android/dx/gen/BinaryOp.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dx.gen;
+
+import com.android.dx.rop.code.Rop;
+import com.android.dx.rop.code.Rops;
+import com.android.dx.rop.type.TypeList;
+
+/**
+ * A binary operation on two values of the same type.
+ */
+public enum BinaryOp {
+    ADD() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opAdd(types);
+        }
+    },
+    SUBTRACT() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opSub(types);
+        }
+    },
+    MULTIPLY() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opMul(types);
+        }
+    },
+    DIVIDE() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opDiv(types);
+        }
+    },
+    REMAINDER() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opRem(types);
+        }
+    },
+    AND() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opAnd(types);
+        }
+    },
+    OR() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opOr(types);
+        }
+    },
+    XOR() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opXor(types);
+        }
+    },
+    SHIFT_LEFT() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opShl(types);
+        }
+    },
+    SHIFT_RIGHT() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opShr(types);
+        }
+    },
+    UNSIGNED_SHIFT_RIGHT() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opUshr(types);
+        }
+    };
+
+    abstract Rop rop(com.android.dx.rop.type.TypeList types);
+}
diff --git a/dx/src/com/android/dx/gen/Canonicalizer.java b/dx/src/com/android/dx/gen/Canonicalizer.java
new file mode 100644
index 0000000..8a76f1b
--- /dev/null
+++ b/dx/src/com/android/dx/gen/Canonicalizer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dx.gen;
+
+import java.util.AbstractSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+final class Canonicalizer<T> extends AbstractSet<T> implements Set<T> {
+    private final Map<T, T> map = new HashMap<T, T>();
+
+    public <S extends T> S get(S s) {
+        @SuppressWarnings("unchecked") // equals() guarantees that the types match
+        S result = (S) map.get(s);
+        if (result != null) {
+            return result;
+        }
+        map.put(s, s);
+        return s;
+    }
+
+    @Override public int size() {
+        return map.size();
+    }
+
+    public Iterator<T> iterator() {
+        return map.keySet().iterator();
+    }
+}
diff --git a/dx/src/com/android/dx/gen/Code.java b/dx/src/com/android/dx/gen/Code.java
new file mode 100644
index 0000000..c3c574e
--- /dev/null
+++ b/dx/src/com/android/dx/gen/Code.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dx.gen;
+
+import com.android.dx.rop.code.BasicBlockList;
+import com.android.dx.rop.code.Insn;
+import com.android.dx.rop.code.PlainCstInsn;
+import com.android.dx.rop.code.PlainInsn;
+import com.android.dx.rop.code.RegisterSpecList;
+import com.android.dx.rop.code.Rop;
+import static com.android.dx.rop.code.Rop.BRANCH_GOTO;
+import static com.android.dx.rop.code.Rop.BRANCH_NONE;
+import static com.android.dx.rop.code.Rop.BRANCH_RETURN;
+import com.android.dx.rop.code.Rops;
+import com.android.dx.rop.code.SourcePosition;
+import com.android.dx.rop.code.ThrowingCstInsn;
+import com.android.dx.rop.code.ThrowingInsn;
+import com.android.dx.rop.type.StdTypeList;
+import static com.android.dx.rop.type.Type.BT_BYTE;
+import static com.android.dx.rop.type.Type.BT_CHAR;
+import static com.android.dx.rop.type.Type.BT_DOUBLE;
+import static com.android.dx.rop.type.Type.BT_INT;
+import static com.android.dx.rop.type.Type.BT_LONG;
+import static com.android.dx.rop.type.Type.BT_SHORT;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Builds a sequence of instructions.
+ */
+public final class Code {
+    /**
+     * All allocated labels. Although the order of the labels in this list
+     * shouldn't impact behavior, it is used to determine basic block indices.
+     */
+    private final List<Label> labels = new ArrayList<Label>();
+
+    /**
+     * The label currently receiving instructions. This is null if the most
+     * recent instruction was a return or goto.
+     */
+    private Label currentLabel;
+
+    /** true once we've fixed the positions of the parameter registers */
+    private boolean localsInitialized;
+    private final List<Local<?>> locals = new ArrayList<Local<?>>();
+    private SourcePosition sourcePosition = SourcePosition.NO_INFO;
+
+    Code(DexGenerator generator) {
+        this.currentLabel = newLabel();
+        this.currentLabel.marked = true;
+    }
+
+    // locals
+
+    public <T> Local<T> newLocal(Type<T> type) {
+        return allocateLocal(type, Local.InitialValue.NONE);
+    }
+
+    public <T> Local<T> newParameter(Type<T> type) {
+        return allocateLocal(type, Local.InitialValue.PARAMETER);
+    }
+
+    public Local<?> newThisLocal(Type<?> type) {
+        return allocateLocal(type, Local.InitialValue.THIS);
+    }
+
+    private <T> Local<T> allocateLocal(Type<T> type, Local.InitialValue initialValue) {
+        if (localsInitialized) {
+            throw new IllegalStateException("Cannot allocate locals after adding instructions");
+        }
+        Local<T> result = new Local<T>(this, type, initialValue);
+        locals.add(result);
+        return result;
+    }
+
+    void initializeLocals() {
+        if (localsInitialized) {
+            throw new AssertionError();
+        }
+        localsInitialized = true;
+        Collections.sort(locals, Local.ORDER_BY_INITIAL_VALUE_TYPE);
+
+        int reg = 0;
+        for (Local<?> local : locals) {
+            local.initialize(reg);
+
+            switch (local.type.ropType.getBasicType()) {
+            case BT_LONG:
+            case BT_DOUBLE:
+                reg += 2;
+                break;
+            default:
+                reg += 1;
+                break;
+            }
+        }
+    }
+
+    // labels
+
+    /**
+     * Creates a new label for use as a branch target. The new label must have
+     * code attached to it later by calling {@link #mark(Label)}.
+     */
+    public Label newLabel() {
+        Label result = new Label();
+        labels.add(result);
+        return result;
+    }
+
+    /**
+     * Start defining instructions for the named label.
+     */
+    public void mark(Label label) {
+        if (label.marked) {
+            throw new IllegalStateException("already marked");
+        }
+        label.marked = true;
+        if (currentLabel != null) {
+            jump(label); // blocks must end with a branch, return or throw
+        }
+        currentLabel = label;
+    }
+
+    private void addInstruction(Insn insn) {
+        addInstruction(insn, null);
+    }
+
+    private void addInstruction(Insn insn, Label branch) {
+        if (currentLabel == null || !currentLabel.marked) {
+            throw new IllegalStateException("no current label");
+        }
+        currentLabel.instructions.add(insn);
+
+        switch (insn.getOpcode().getBranchingness()) {
+        case BRANCH_NONE:
+            if (branch != null) {
+                throw new IllegalArgumentException("branch != null");
+            }
+            return;
+
+        case BRANCH_RETURN:
+            if (branch != null) {
+                throw new IllegalArgumentException("branch != null");
+            }
+            currentLabel = null;
+            break;
+
+        case BRANCH_GOTO:
+            if (branch == null) {
+                throw new IllegalArgumentException("branch == null");
+            }
+            currentLabel.primarySuccessor = branch;
+            currentLabel = null;
+            break;
+
+        case Rop.BRANCH_IF:
+            if (branch == null) {
+                throw new IllegalArgumentException("branch == null");
+            }
+            splitCurrentLabel(branch);
+            break;
+
+        case Rop.BRANCH_THROW:
+            splitCurrentLabel(branch);
+            break;
+
+        default:
+            throw new IllegalArgumentException();
+        }
+    }
+
+    /**
+     * Closes the current label and starts a new one.
+     */
+    private void splitCurrentLabel(Label branch) {
+        Label newLabel = newLabel();
+        currentLabel.primarySuccessor = newLabel;
+        currentLabel.alternateSuccessor = branch;
+        currentLabel = newLabel;
+        currentLabel.marked = true;
+    }
+
+    // instructions: constants
+
+    public <T> void loadConstant(Local<T> target, T value) {
+        Rop rop = Rops.opConst(target.type.ropType);
+        if (rop.getBranchingness() == BRANCH_NONE) {
+            addInstruction(new PlainCstInsn(rop, sourcePosition, target.spec(),
+                    RegisterSpecList.EMPTY, Constants.getConstant(value)));
+        } else {
+            addInstruction(new ThrowingCstInsn(rop, sourcePosition,
+                    RegisterSpecList.EMPTY, StdTypeList.EMPTY, Constants.getConstant(value)));
+            moveResult(target, true);
+        }
+    }
+
+    // instructions: unary
+
+    public <T> void negate(Local<T> source, Local<T> target) {
+        unary(Rops.opNeg(source.type.ropType), source, target);
+    }
+
+    public <T> void not(Local<T> source, Local<T> target) {
+        unary(Rops.opNot(source.type.ropType), source, target);
+    }
+
+    public void cast(Local<?> source, Local<?> target) {
+        unary(getCastRop(source.type.ropType, target.type.ropType), source, target);
+    }
+
+    private Rop getCastRop(com.android.dx.rop.type.Type sourceType,
+            com.android.dx.rop.type.Type targetType) {
+        if (sourceType.getBasicType() == BT_INT) {
+            switch (targetType.getBasicType()) {
+            case BT_SHORT:
+                return Rops.TO_SHORT;
+            case BT_CHAR:
+                return Rops.TO_CHAR;
+            case BT_BYTE:
+                return Rops.TO_BYTE;
+            }
+        }
+        return Rops.opConv(targetType, sourceType);
+    }
+
+    private void unary(Rop rop, Local<?> source, Local<?> target) {
+        addInstruction(new PlainInsn(rop, sourcePosition, target.spec(), source.spec()));
+    }
+
+    // instructions: binary
+
+    public <T> void op(BinaryOp op, Local<T> target, Local<T> a, Local<T> b) {
+        Rop rop = op.rop(StdTypeList.make(a.type.ropType, b.type.ropType));
+        RegisterSpecList sources = RegisterSpecList.make(a.spec(), b.spec());
+
+        if (rop.getBranchingness() == BRANCH_NONE) {
+            addInstruction(new PlainInsn(rop, sourcePosition, target.spec(), sources));
+        } else {
+            addInstruction(new ThrowingInsn(rop, sourcePosition, sources, StdTypeList.EMPTY));
+            moveResult(target, true);
+        }
+    }
+
+    // instructions: branches
+
+    public <T> void compare(Comparison comparison, Local<T> a, Local<T> b, Label trueLabel) {
+        if (trueLabel == null) {
+            throw new IllegalArgumentException();
+        }
+        Rop rop = comparison.rop(StdTypeList.make(a.type.ropType, b.type.ropType));
+        addInstruction(new PlainInsn(rop, sourcePosition, null,
+                RegisterSpecList.make(a.spec(), b.spec())), trueLabel);
+    }
+
+    public void jump(Label target) {
+        addInstruction(new PlainInsn(Rops.GOTO, sourcePosition, null, RegisterSpecList.EMPTY),
+                target);
+    }
+
+    // instructions: fields
+
+    public <T, R> void iget(Field<T, R> field, Local<T> instance, Local<R> target) {
+        addInstruction(new ThrowingCstInsn(Rops.opGetField(target.type.ropType), sourcePosition,
+                RegisterSpecList.make(instance.spec()), StdTypeList.EMPTY, field.constant));
+        moveResult(target, true);
+    }
+
+    public <T, R> void iput(Field<T, R> field, Local<T> instance, Local<R> source) {
+        addInstruction(new ThrowingCstInsn(Rops.opPutField(source.type.ropType), sourcePosition,
+                RegisterSpecList.make(source.spec(), instance.spec()), StdTypeList.EMPTY,
+                field.constant));
+    }
+
+    public <T> void sget(Field<?, T> field, Local<T> target) {
+        addInstruction(new ThrowingCstInsn(Rops.opGetStatic(target.type.ropType), sourcePosition,
+                RegisterSpecList.EMPTY, StdTypeList.EMPTY, field.constant));
+        moveResult(target, true);
+    }
+
+    public <T> void sput(Field<?, T> field, Local<T> source) {
+        addInstruction(new ThrowingCstInsn(Rops.opPutStatic(source.type.ropType), sourcePosition,
+                RegisterSpecList.make(source.spec()), StdTypeList.EMPTY, field.constant));
+    }
+
+    // instructions: invoke
+
+    public <T> void newInstance(Local<T> target, Method<T, Void> constructor, Local<?>... args) {
+        if (target == null) {
+            throw new IllegalArgumentException();
+        }
+        addInstruction(new ThrowingCstInsn(Rops.NEW_INSTANCE, sourcePosition,
+                RegisterSpecList.EMPTY, StdTypeList.EMPTY, constructor.declaringType.constant));
+        moveResult(target, true);
+        invokeDirect(constructor, null, target, args);
+    }
+
+    public <R> void invokeStatic(Method<?, R> method, Local<R> target, Local<?>... args) {
+        invoke(Rops.opInvokeStatic(method.prototype(true)), method, target, null, args);
+    }
+
+    public <I, R> void invokeVirtual(Method<I, R> method, Local<R> target, Local<I> object,
+            Local<?>... args) {
+        invoke(Rops.opInvokeVirtual(method.prototype(true)), method, target, object, args);
+    }
+
+    public <I, R> void invokeDirect(Method<?, R> method, Local<R> target, Local<I> object,
+            Local<?>... args) {
+        invoke(Rops.opInvokeDirect(method.prototype(true)), method, target, object, args);
+    }
+
+    public <I, R> void invokeSuper(Method<I, R> method, Local<R> target, Local<?> object,
+            Local<?>... args) {
+        invoke(Rops.opInvokeSuper(method.prototype(true)), method, target, object, args);
+    }
+
+    public <I, R> void invokeInterface(Method<I, R> method, Local<R> target, Local<?> object,
+            Local<?>... args) {
+        invoke(Rops.opInvokeInterface(method.prototype(true)), method, target, object, args);
+    }
+
+    private <I, R> void invoke(Rop rop, Method method, Local<R> target, Local<I> object,
+            Local<?>... args) {
+        addInstruction(new ThrowingCstInsn(rop, sourcePosition, concatenate(object, args),
+                StdTypeList.EMPTY, method.constant));
+        if (target != null) {
+            moveResult(target, false);
+        }
+    }
+
+    // instructions: return
+
+    public void returnVoid() {
+        addInstruction(new PlainInsn(Rops.RETURN_VOID, sourcePosition, null,
+                RegisterSpecList.EMPTY));
+    }
+
+    public void returnValue(Local<?> result) {
+        addInstruction(new PlainInsn(Rops.opReturn(result.type.ropType), sourcePosition,
+                null, RegisterSpecList.make(result.spec())), null);
+    }
+
+    private void moveResult(Local<?> target, boolean afterNonInvokeThrowingInsn) {
+        Rop rop = afterNonInvokeThrowingInsn
+                ? Rops.opMoveResultPseudo(target.type.ropType)
+                : Rops.opMoveResult(target.type.ropType);
+        addInstruction(new PlainInsn(rop, sourcePosition, target.spec(), RegisterSpecList.EMPTY));
+    }
+
+    // produce BasicBlocks for dex
+
+    BasicBlockList toBasicBlocks() {
+        cleanUpLabels();
+
+        BasicBlockList result = new BasicBlockList(labels.size());
+        for (int i = 0; i < labels.size(); i++) {
+            result.set(i, labels.get(i).toBasicBlock());
+        }
+        return result;
+    }
+
+    /**
+     * Removes empty labels and assigns IDs to non-empty labels.
+     */
+    private void cleanUpLabels() {
+        int id = 0;
+        for (Iterator<Label> i = labels.iterator(); i.hasNext();) {
+            Label label = i.next();
+            if (label.isEmpty()) {
+                i.remove();
+            } else {
+                label.compact();
+                label.id = id++;
+            }
+        }
+    }
+
+    TypeList parameters() {
+        List<Type<?>> result = new ArrayList<Type<?>>();
+        for (Local<?> local : locals) {
+            if (local.initialValue == Local.InitialValue.PARAMETER) {
+                result.add(local.type);
+            }
+        }
+        return new TypeList(result);
+    }
+
+    Local<?> thisLocal() {
+        for (Local<?> local : locals) {
+            if (local.initialValue == Local.InitialValue.THIS) {
+                return local;
+            }
+        }
+        return null;
+    }
+
+    private static RegisterSpecList concatenate(Local<?> first, Local<?>[] rest) {
+        int offset = (first != null) ? 1 : 0;
+        RegisterSpecList result = new RegisterSpecList(offset + rest.length);
+        if (first != null) {
+            result.set(0, first.spec());
+        }
+        for (int i = 0; i < rest.length; i++) {
+            result.set(i + offset, rest[i].spec());
+        }
+        return result;
+    }
+}
diff --git a/dx/src/com/android/dx/gen/Comparison.java b/dx/src/com/android/dx/gen/Comparison.java
new file mode 100644
index 0000000..40a6e97
--- /dev/null
+++ b/dx/src/com/android/dx/gen/Comparison.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dx.gen;
+
+import com.android.dx.rop.code.Rop;
+import com.android.dx.rop.code.Rops;
+import com.android.dx.rop.type.TypeList;
+
+/**
+ * A comparison between two values of the same type.
+ */
+public enum Comparison {
+
+    /** {@code a < b} */
+    LT() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opIfLt(types);
+        }
+    },
+
+    /** {@code a <= b} */
+    LE() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opIfLe(types);
+        }
+    },
+
+    /** {@code a == b} */
+    EQ() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opIfEq(types);
+        }
+    },
+
+    /** {@code a >= b} */
+    GE() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opIfGe(types);
+        }
+    },
+
+    /** {@code a > b} */
+    GT() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opIfGt(types);
+        }
+    },
+
+    /** {@code a != b} */
+    NE() {
+        @Override Rop rop(TypeList types) {
+            return Rops.opIfNe(types);
+        }
+    };
+
+    abstract Rop rop(TypeList types);
+}
diff --git a/dx/src/com/android/dx/gen/Constants.java b/dx/src/com/android/dx/gen/Constants.java
new file mode 100644
index 0000000..71d7ac4
--- /dev/null
+++ b/dx/src/com/android/dx/gen/Constants.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dx.gen;
+
+import com.android.dx.rop.cst.CstBoolean;
+import com.android.dx.rop.cst.CstByte;
+import com.android.dx.rop.cst.CstChar;
+import com.android.dx.rop.cst.CstDouble;
+import com.android.dx.rop.cst.CstFloat;
+import com.android.dx.rop.cst.CstInteger;
+import com.android.dx.rop.cst.CstKnownNull;
+import com.android.dx.rop.cst.CstLong;
+import com.android.dx.rop.cst.CstShort;
+import com.android.dx.rop.cst.CstString;
+import com.android.dx.rop.cst.CstType;
+import com.android.dx.rop.cst.TypedConstant;
+
+/**
+ * Factory for rop constants.
+ */
+final class Constants {
+    private Constants() {}
+
+    /**
+     * Returns a rop constant for the specified value.
+     *
+     * @param value null, a boxed primitive, String, Class, or Type.
+     */
+    static TypedConstant getConstant(Object value) {
+        if (value == null) {
+            return CstKnownNull.THE_ONE;
+        } else if (value instanceof Boolean) {
+            return CstBoolean.make((Boolean) value);
+        } else if (value instanceof Byte) {
+            return CstByte.make((Byte) value);
+        } else if (value instanceof Character) {
+            return CstChar.make((Character) value);
+        } else if (value instanceof Double) {
+            return CstDouble.make(Double.doubleToLongBits((Double) value));
+        } else if (value instanceof Float) {
+            return CstFloat.make(Float.floatToIntBits((Float) value));
+        } else if (value instanceof Integer) {
+            return CstInteger.make((Integer) value);
+        } else if (value instanceof Long) {
+            return CstLong.make((Long) value);
+        } else if (value instanceof Short) {
+            return CstShort.make((Short) value);
+        } else if (value instanceof String) {
+            return new CstString((String) value);
+        } else if (value instanceof Class) {
+            String name = Type.getTypeName((Class<?>) value);
+            return new CstType(com.android.dx.rop.type.Type.internReturnType(name));
+        } else if (value instanceof Type) {
+            return new CstType(((Type) value).ropType);
+        } else {
+            throw new UnsupportedOperationException("Not a constant: " + value);
+        }
+    }
+}
diff --git a/dx/src/com/android/dx/gen/DexGenerator.java b/dx/src/com/android/dx/gen/DexGenerator.java
new file mode 100644
index 0000000..50f7718
--- /dev/null
+++ b/dx/src/com/android/dx/gen/DexGenerator.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dx.gen;
+
+import com.android.dx.dex.DexFormat;
+import com.android.dx.dex.file.DexFile;
+import com.android.dx.rop.cst.CstBoolean;
+import com.android.dx.rop.cst.CstByte;
+import com.android.dx.rop.cst.CstChar;
+import com.android.dx.rop.cst.CstDouble;
+import com.android.dx.rop.cst.CstFloat;
+import com.android.dx.rop.cst.CstInteger;
+import com.android.dx.rop.cst.CstKnownNull;
+import com.android.dx.rop.cst.CstLong;
+import com.android.dx.rop.cst.CstShort;
+import com.android.dx.rop.cst.CstString;
+import com.android.dx.rop.cst.CstType;
+import com.android.dx.rop.cst.TypedConstant;
+import dalvik.system.PathClassLoader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+/**
+ * Define types, fields and methods.
+ */
+public final class DexGenerator {
+    private final Canonicalizer<Type<?>> types = new Canonicalizer<Type<?>>();
+
+    /**
+     * @param name a descriptor like "(Ljava/lang/Class;[I)Ljava/lang/Object;".
+     */
+    public Type<?> getType(String name) {
+        return types.get(new Type<Object>(this, name));
+    }
+
+    public <T> Type<T> getType(Class<T> type) {
+        return types.get(new Type<T>(this, type));
+    }
+
+    public Code newCode() {
+        return new Code(this);
+    }
+
+    /**
+     * Returns a .dex formatted file.
+     */
+    public byte[] generate() {
+        DexFile outputDex = new DexFile();
+
+        for (Type<?> type : types) {
+            if (type.isDeclared()) {
+                outputDex.add(type.toClassDefItem());
+            } else {
+                for (Method<?, ?> m : type.getMethods()) {
+                    if (m.isDeclared()) {
+                        throw new IllegalStateException(
+                                "Undeclared type " + type + " declares " + m);
+                    }
+                }
+                for (Field<?, ?> f : type.getFields()) {
+                    if (f.isDeclared()) {
+                        throw new IllegalStateException(
+                                "Undeclared type " + type + " declares " + f);
+                    }
+                }
+            }
+        }
+
+        try {
+            return outputDex.toDex(null, false);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Loads the generated types into the current dalvikvm process.
+     */
+    public ClassLoader load(ClassLoader parent) throws IOException {
+        byte[] dex = generate();
+
+        /*
+         * This implementation currently dumps the dex to the filesystem. It
+         * jars the emitted .dex for the benefit of Gingerbread and earlier
+         * devices, which can't load .dex files directly.
+         *
+         * TODO: load the dex from memory where supported.
+         */
+        File result = File.createTempFile("Generated", ".jar");
+//        result.deleteOnExit(); // TODO
+        JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(result));
+        jarOut.putNextEntry(new JarEntry(DexFormat.DEX_IN_JAR_NAME));
+        jarOut.write(dex);
+        jarOut.closeEntry();
+        jarOut.close();
+        System.out.println(result);
+
+        return new PathClassLoader(result.getPath(), parent);
+    }
+}
diff --git a/dx/src/com/android/dx/gen/Field.java b/dx/src/com/android/dx/gen/Field.java
new file mode 100644
index 0000000..64f7b4c
--- /dev/null
+++ b/dx/src/com/android/dx/gen/Field.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dx.gen;
+
+import com.android.dx.dex.file.EncodedField;
+import com.android.dx.rop.code.AccessFlags;
+import com.android.dx.rop.cst.CstFieldRef;
+import com.android.dx.rop.cst.CstNat;
+import com.android.dx.rop.cst.CstUtf8;
+
+/**
+ * A field.
+ */
+public final class Field<T, R> {
+    final Type<T> declaringType;
+    final Type<R> type;
+    final String name;
+
+    /** cached converted state */
+    final CstNat nat;
+    final CstFieldRef constant;
+
+    /** declared state */
+    private boolean declared;
+    private int accessFlags;
+    private Object staticValue;
+
+    Field(Type<T> declaringType, Type<R> type, String name) {
+        if (declaringType == null || type == null || name == null) {
+            throw new NullPointerException();
+        }
+        this.declaringType = declaringType;
+        this.type = type;
+        this.name = name;
+        this.nat = new CstNat(new CstUtf8(name), new CstUtf8(type.name));
+        this.constant = new CstFieldRef(declaringType.constant, nat);
+    }
+
+    public Type<T> getDeclaringType() {
+        return declaringType;
+    }
+
+    public Type<R> getType() {
+        return type;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @param accessFlags any flags masked by {@link AccessFlags#FIELD_FLAGS}.
+     */
+    public void declare(int accessFlags, Object staticValue) {
+        if (declared) {
+            throw new IllegalStateException();
+        }
+        if ((accessFlags & (AccessFlags.ACC_STATIC)) == 0 && staticValue != null) {
+            throw new IllegalArgumentException("Instance fields may not have a value");
+        }
+        this.declared = true;
+        this.accessFlags = accessFlags;
+        this.staticValue = staticValue;
+    }
+
+    public Object getStaticValue() {
+        return staticValue;
+    }
+
+    boolean isDeclared() {
+        return declared;
+    }
+
+    public boolean isStatic() {
+        if (!declared) {
+            throw new IllegalStateException();
+        }
+        return (accessFlags & (AccessFlags.ACC_STATIC)) != 0;
+    }
+
+    EncodedField toEncodedField() {
+        if (!declared) {
+            throw new IllegalStateException();
+        }
+        return new EncodedField(constant, accessFlags);
+    }
+
+    @Override public boolean equals(Object o) {
+        return o instanceof Field
+                && ((Field) o).declaringType.equals(declaringType)
+                && ((Field) o).name.equals(name);
+    }
+
+    @Override public int hashCode() {
+        return declaringType.hashCode() + 37 * name.hashCode();
+    }
+
+    @Override public String toString() {
+        return declaringType + "." + name;
+    }
+}
diff --git a/dx/src/com/android/dx/gen/Label.java b/dx/src/com/android/dx/gen/Label.java
new file mode 100644
index 0000000..b68ca15
--- /dev/null
+++ b/dx/src/com/android/dx/gen/Label.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dx.gen;
+
+import com.android.dx.rop.code.BasicBlock;
+import com.android.dx.rop.code.Insn;
+import com.android.dx.rop.code.InsnList;
+import com.android.dx.util.IntList;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A branch target in a list of instructions.
+ */
+public final class Label {
+
+    final List<Insn> instructions = new ArrayList<Insn>();
+
+    boolean marked = false;
+
+    /** contains the next instruction if no branch occurs */
+    Label primarySuccessor;
+
+    /** contains the instruction to jump to if the if is true */
+    Label alternateSuccessor;
+
+    int id = -1;
+
+    Label() {}
+
+    boolean isEmpty() {
+        return instructions.isEmpty();
+    }
+
+    void compact() {
+        while (primarySuccessor != null && primarySuccessor.isEmpty()) {
+            primarySuccessor = primarySuccessor.primarySuccessor;
+        }
+        while (alternateSuccessor != null && alternateSuccessor.isEmpty()) {
+            alternateSuccessor = alternateSuccessor.primarySuccessor;
+        }
+    }
+
+    BasicBlock toBasicBlock() {
+        InsnList result = new InsnList(instructions.size());
+        for (int i = 0; i < instructions.size(); i++) {
+            result.set(i, instructions.get(i));
+        }
+        result.setImmutable();
+
+        int primarySuccessorIndex = -1;
+        IntList successors = new IntList();
+        if (primarySuccessor != null) {
+            primarySuccessorIndex = primarySuccessor.id;
+            successors.add(primarySuccessorIndex);
+        }
+        if (alternateSuccessor != null) {
+            successors.add(alternateSuccessor.id);
+        }
+        successors.setImmutable();
+
+        return new BasicBlock(id, result, successors, primarySuccessorIndex);
+    }
+}
diff --git a/dx/src/com/android/dx/gen/Local.java b/dx/src/com/android/dx/gen/Local.java
new file mode 100644
index 0000000..1c23011
--- /dev/null
+++ b/dx/src/com/android/dx/gen/Local.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dx.gen;
+
+import com.android.dx.rop.code.RegisterSpec;
+import java.util.Comparator;
+
+/**
+ * A temporary variable that holds a single value.
+ */
+public final class Local<T> {
+    /**
+     * Dalvik bytecode uses the last N registers for the method's N arguments.
+     * Instance methods are passed 'this' as the first argument. This ordering
+     * sorts locals into this sequence.
+     */
+    static final Comparator<Local<?>> ORDER_BY_INITIAL_VALUE_TYPE = new Comparator<Local<?>>() {
+        public int compare(Local<?> a, Local<?> b) {
+            return a.initialValue.ordinal() - b.initialValue.ordinal();
+        }
+    };
+
+    private final Code code;
+    final Type type;
+    final InitialValue initialValue;
+    private int reg = -1;
+    private RegisterSpec spec;
+
+    Local(Code code, Type type, InitialValue initialValue) {
+        this.code = code;
+        this.type = type;
+        this.initialValue = initialValue;
+    }
+
+    void initialize(int reg) {
+        this.reg = reg;
+        this.spec = RegisterSpec.make(reg, type.getRopType());
+    }
+
+    RegisterSpec spec() {
+        if (spec == null) {
+            code.initializeLocals();
+            if (spec == null) {
+                throw new AssertionError();
+            }
+        }
+        return spec;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    @Override public String toString() {
+        return "v" + reg + "(" + type + ")";
+    }
+
+    enum InitialValue {
+        NONE, THIS, PARAMETER
+    }
+}
diff --git a/dx/src/com/android/dx/gen/Method.java b/dx/src/com/android/dx/gen/Method.java
new file mode 100644
index 0000000..e88a031
--- /dev/null
+++ b/dx/src/com/android/dx/gen/Method.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dx.gen;
+
+import com.android.dx.dex.DexOptions;
+import com.android.dx.dex.code.DalvCode;
+import com.android.dx.dex.code.PositionList;
+import com.android.dx.dex.code.RopTranslator;
+import com.android.dx.dex.file.EncodedMethod;
+import com.android.dx.rop.code.AccessFlags;
+import static com.android.dx.rop.code.AccessFlags.ACC_CONSTRUCTOR;
+import static com.android.dx.rop.code.AccessFlags.ACC_PRIVATE;
+import static com.android.dx.rop.code.AccessFlags.ACC_STATIC;
+import com.android.dx.rop.code.LocalVariableInfo;
+import com.android.dx.rop.code.RopMethod;
+import com.android.dx.rop.cst.CstMethodRef;
+import com.android.dx.rop.cst.CstNat;
+import com.android.dx.rop.cst.CstUtf8;
+import com.android.dx.rop.type.Prototype;
+import com.android.dx.rop.type.StdTypeList;
+import java.util.List;
+
+/**
+ * A method or constructor.
+ */
+public final class Method<T, R> {
+    final Type<T> declaringType;
+    final Type<R> returnType;
+    final String name;
+    final TypeList parameters;
+
+    /** cached converted state */
+    final CstNat nat;
+    final CstMethodRef constant;
+
+    /** declared state */
+    private boolean declared;
+    private int accessFlags;
+    private Code code;
+
+    Method(Type<T> declaringType, Type<R> returnType, String name, TypeList parameters) {
+        if (declaringType == null || returnType == null || name == null || parameters == null) {
+            throw new NullPointerException();
+        }
+        this.declaringType = declaringType;
+        this.returnType = returnType;
+        this.name = name;
+        this.parameters = parameters;
+        this.nat = new CstNat(new CstUtf8(name), new CstUtf8(descriptor(false)));
+        this.constant = new CstMethodRef(declaringType.constant, nat);
+    }
+
+    public Type<T> getDeclaringType() {
+        return declaringType;
+    }
+
+    public Type<R> getReturnType() {
+        return returnType;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public List<Type<?>> getParameters() {
+        return parameters.asList();
+    }
+
+    /**
+     * Returns a descriptor like "(Ljava/lang/Class;[I)Ljava/lang/Object;".
+     */
+    String descriptor(boolean includeThis) {
+        StringBuilder result = new StringBuilder();
+        result.append("(");
+        if (includeThis) {
+            result.append(declaringType.name);
+        }
+        for (Type t : parameters.types) {
+            result.append(t.name);
+        }
+        result.append(")");
+        result.append(returnType.name);
+        return result.toString();
+    }
+
+    /**
+     * @param accessFlags any flags masked by {@link AccessFlags#METHOD_FLAGS}.
+     */
+    public void declare(int accessFlags, Code code) {
+        if (declared) {
+            throw new IllegalStateException();
+        }
+        if (code == null) {
+            throw new NullPointerException(); // TODO: permit methods without code
+        }
+        if (!parameters.equals(code.parameters())) {
+            throw new IllegalArgumentException("Parameters mismatch. Expected (" + parameters
+                    + ") but was (" + code.parameters() + ")");
+        }
+        boolean isStatic = (accessFlags & (ACC_STATIC)) != 0;
+        if (isStatic != (code.thisLocal() == null)) {
+            throw new IllegalArgumentException("Static mismatch. Declared static=" + isStatic
+                    + " this local=" + code.thisLocal());
+        }
+        this.declared = true;
+        this.accessFlags = accessFlags;
+        this.code = code;
+    }
+
+    boolean isDeclared() {
+        return declared;
+    }
+
+    boolean isDirect() {
+        if (!declared) {
+            throw new IllegalStateException();
+        }
+        return (accessFlags & (ACC_STATIC | ACC_PRIVATE | ACC_CONSTRUCTOR)) != 0;
+    }
+
+    Prototype prototype(boolean includeThis) {
+        return Prototype.intern(descriptor(includeThis));
+    }
+
+    EncodedMethod toEncodedMethod(DexOptions dexOptions) {
+        if (!declared) {
+            throw new IllegalStateException();
+        }
+        RopMethod ropMethod = new RopMethod(code.toBasicBlocks(), 0);
+        int paramSize = -1;
+        LocalVariableInfo locals = null;
+        int positionInfo = PositionList.NONE;
+        DalvCode code = RopTranslator.translate(
+                ropMethod, positionInfo, locals, paramSize, dexOptions);
+        return new EncodedMethod(constant, accessFlags, code, StdTypeList.EMPTY);
+    }
+
+    @Override public boolean equals(Object o) {
+        return o instanceof Method
+                && ((Method) o).declaringType.equals(declaringType)
+                && ((Method) o).name.equals(name)
+                && ((Method) o).parameters.equals(parameters)
+                && ((Method) o).returnType.equals(returnType);
+    }
+
+    @Override public int hashCode() {
+        int result = 17;
+        result = 31 * result + declaringType.hashCode();
+        result = 31 * result + name.hashCode();
+        result = 31 * result + parameters.hashCode();
+        result = 31 * result + returnType.hashCode();
+        return result;
+    }
+
+    @Override public String toString() {
+        return declaringType + "." + name + "(" + parameters + ")";
+    }
+}
diff --git a/dx/src/com/android/dx/gen/Type.java b/dx/src/com/android/dx/gen/Type.java
new file mode 100644
index 0000000..8cce3c6
--- /dev/null
+++ b/dx/src/com/android/dx/gen/Type.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dx.gen;
+
+import com.android.dx.dex.DexOptions;
+import com.android.dx.dex.file.ClassDefItem;
+import com.android.dx.dex.file.EncodedField;
+import com.android.dx.dex.file.EncodedMethod;
+import com.android.dx.rop.cst.CstType;
+import com.android.dx.rop.cst.CstUtf8;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An interface or class.
+ */
+public final class Type<T> {
+    private static final Map<Class<?>, String> PRIMITIVE_TO_TYPE_NAME
+            = new HashMap<Class<?>, String>();
+    static {
+        PRIMITIVE_TO_TYPE_NAME.put(boolean.class, "Z");
+        PRIMITIVE_TO_TYPE_NAME.put(byte.class, "B");
+        PRIMITIVE_TO_TYPE_NAME.put(char.class, "C");
+        PRIMITIVE_TO_TYPE_NAME.put(double.class, "D");
+        PRIMITIVE_TO_TYPE_NAME.put(float.class, "F");
+        PRIMITIVE_TO_TYPE_NAME.put(int.class, "I");
+        PRIMITIVE_TO_TYPE_NAME.put(long.class, "J");
+        PRIMITIVE_TO_TYPE_NAME.put(short.class, "S");
+        PRIMITIVE_TO_TYPE_NAME.put(void.class, "V");
+    }
+
+    final DexGenerator generator;
+    final String name;
+
+    /** cached converted values */
+    final com.android.dx.rop.type.Type ropType;
+    final CstType constant;
+
+    /** declared state */
+    private boolean declared;
+    private int flags;
+    private Type<?> supertype;
+    private String sourceFile;
+    private TypeList interfaces;
+
+    /** declared members */
+    private final Canonicalizer<Field<T, ?>> fields = new Canonicalizer<Field<T, ?>>();
+    private final Canonicalizer<Method<T, ?>> methods = new Canonicalizer<Method<T, ?>>();
+
+    Type(DexGenerator generator, String name) {
+        if (name == null) {
+            throw new NullPointerException();
+        }
+        this.generator = generator;
+        this.name = name;
+        this.ropType = com.android.dx.rop.type.Type.internReturnType(name);
+        this.constant = CstType.intern(ropType);
+    }
+
+    Type(DexGenerator generator, Class<T> type) {
+        this(generator, getTypeName(type));
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public <R> Field<T, R> getField(Type<R> type, String name) {
+        return fields.get(new Field<T, R>(this, type, name));
+    }
+
+    public <R> Method<T, R> getMethod(Type<R> returnType, String name, Type... parameters) {
+        return methods.get(new Method<T, R>(this, returnType, name, new TypeList(parameters)));
+    }
+
+    public Method<T, Void> getConstructor(Type... parameters) {
+        return getMethod(generator.getType(void.class), "<init>", parameters);
+    }
+
+    Set<Field<T, ?>> getFields() {
+        return fields;
+    }
+
+    Set<Method<T, ?>> getMethods() {
+        return methods;
+    }
+
+    /**
+     * @param flags any flags masked by {@link com.android.dx.rop.code.AccessFlags#METHOD_FLAGS}.
+     */
+    public void declare(String sourceFile, int flags, Type<?> supertype, Type<?>... interfaces) {
+        if (declared) {
+            throw new IllegalStateException();
+        }
+        this.declared = true;
+        this.flags = flags;
+        this.supertype = supertype;
+        this.sourceFile = sourceFile;
+        this.interfaces = new TypeList(interfaces);
+    }
+
+    boolean isDeclared() {
+        return declared;
+    }
+
+    ClassDefItem toClassDefItem() {
+        if (!declared) {
+            throw new IllegalStateException();
+        }
+
+        DexOptions dexOptions = new DexOptions();
+        dexOptions.enableExtendedOpcodes = false;
+
+        CstType thisType = constant;
+
+        ClassDefItem out = new ClassDefItem(thisType, flags, supertype.constant,
+                interfaces.ropTypes, new CstUtf8(sourceFile));
+
+        for (Method<T, ?> method : methods) {
+            EncodedMethod encoded = method.toEncodedMethod(dexOptions);
+            if (method.isDirect()) {
+                out.addDirectMethod(encoded);
+            } else {
+                out.addVirtualMethod(encoded);
+            }
+        }
+        for (Field<T, ?> field : fields) {
+            EncodedField encoded = field.toEncodedField();
+            if (field.isStatic()) {
+                out.addStaticField(encoded, Constants.getConstant(field.getStaticValue()));
+            } else {
+                out.addInstanceField(encoded);
+            }
+        }
+
+        return out;
+    }
+
+    com.android.dx.rop.type.Type getRopType() {
+        return com.android.dx.rop.type.Type.internReturnType(name);
+    }
+
+    @Override public boolean equals(Object o) {
+        return o instanceof Type
+                && ((Type) o).name.equals(name);
+    }
+
+    @Override public int hashCode() {
+        return name.hashCode();
+    }
+
+    @Override public String toString() {
+        return name;
+    }
+
+    /**
+     * Returns a type name like "Ljava/lang/Integer;".
+     */
+    static String getTypeName(Class<?> type) {
+        if (type.isPrimitive()) {
+            return PRIMITIVE_TO_TYPE_NAME.get(type);
+        }
+        String name = type.getName().replace('.', '/');
+        return type.isArray() ? name : 'L' + name + ';';
+    }
+}
diff --git a/dx/src/com/android/dx/gen/TypeList.java b/dx/src/com/android/dx/gen/TypeList.java
new file mode 100644
index 0000000..c6738fb
--- /dev/null
+++ b/dx/src/com/android/dx/gen/TypeList.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dx.gen;
+
+import com.android.dx.rop.type.StdTypeList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An immutable of types.
+ */
+final class TypeList {
+    final Type<?>[] types;
+    final StdTypeList ropTypes;
+
+    TypeList(Type<?>[] types) {
+        this.types = types.clone();
+        this.ropTypes = new StdTypeList(types.length);
+        for (int i = 0; i < types.length; i++) {
+            ropTypes.set(i, types[i].ropType);
+        }
+    }
+
+    TypeList(List<Type<?>> types) {
+        this(types.toArray(new Type[types.size()]));
+    }
+
+    /**
+     * Returns an immutable list.
+     */
+    public List<Type<?>> asList() {
+        return Collections.unmodifiableList(Arrays.asList(types));
+    }
+
+    @Override public boolean equals(Object o) {
+        return o instanceof TypeList && Arrays.equals(((TypeList) o).types, types);
+    }
+
+    @Override public int hashCode() {
+        return Arrays.hashCode(types);
+    }
+
+    @Override public String toString() {
+        StringBuilder result = new StringBuilder();
+        for (int i = 0; i < types.length; i++) {
+            if (i > 0) {
+                result.append(", ");
+            }
+            result.append(types[i]);
+        }
+        return result.toString();
+    }
+}
diff --git a/dx/src/com/android/dx/rop/code/BasicBlock.java b/dx/src/com/android/dx/rop/code/BasicBlock.java
index d6ee886..9d99833 100644
--- a/dx/src/com/android/dx/rop/code/BasicBlock.java
+++ b/dx/src/com/android/dx/rop/code/BasicBlock.java
@@ -103,7 +103,7 @@
 
         if (primarySuccessor >= 0 && !successors.contains(primarySuccessor)) {
             throw new IllegalArgumentException(
-                    "primarySuccessor not in successors");
+                    "primarySuccessor " + primarySuccessor + " not in successors " + successors);
         }
 
         this.label = label;
diff --git a/dx/src/com/android/dx/rop/code/Rops.java b/dx/src/com/android/dx/rop/code/Rops.java
index 9085ff4..c1f4f46 100644
--- a/dx/src/com/android/dx/rop/code/Rops.java
+++ b/dx/src/com/android/dx/rop/code/Rops.java
@@ -1720,6 +1720,7 @@
                     case Type.BT_LONG:   return CONV_I2L;
                     case Type.BT_FLOAT:  return CONV_I2F;
                     case Type.BT_DOUBLE: return CONV_I2D;
+                    default:             break;
                 }
             }
             case Type.BT_LONG: {
@@ -1727,6 +1728,7 @@
                     case Type.BT_INT:    return CONV_L2I;
                     case Type.BT_FLOAT:  return CONV_L2F;
                     case Type.BT_DOUBLE: return CONV_L2D;
+                    default:             break;
                 }
             }
             case Type.BT_FLOAT: {
@@ -1734,13 +1736,15 @@
                     case Type.BT_INT:    return CONV_F2I;
                     case Type.BT_LONG:   return CONV_F2L;
                     case Type.BT_DOUBLE: return CONV_F2D;
+                    default:             break;
                 }
             }
             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;
+                    case Type.BT_INT:    return CONV_D2I;
+                    case Type.BT_LONG:   return CONV_D2L;
+                    case Type.BT_FLOAT:  return CONV_D2F;
+                    default:             break;
                 }
             }
         }
diff --git a/dx/src/com/android/dx/rop/type/Prototype.java b/dx/src/com/android/dx/rop/type/Prototype.java
index ec46ff9..3fc5d82 100644
--- a/dx/src/com/android/dx/rop/type/Prototype.java
+++ b/dx/src/com/android/dx/rop/type/Prototype.java
@@ -19,7 +19,7 @@
 import java.util.HashMap;
 
 /**
- * Representation of a method decriptor. Instances of this class are
+ * Representation of a method descriptor. Instances of this class are
  * generally interned and may be usefully compared with each other
  * using {@code ==}.
  */
diff --git a/dx/src/com/android/dx/rop/type/Type.java b/dx/src/com/android/dx/rop/type/Type.java
index eefd55b..c564fab 100644
--- a/dx/src/com/android/dx/rop/type/Type.java
+++ b/dx/src/com/android/dx/rop/type/Type.java
@@ -328,7 +328,7 @@
         int length = descriptor.length();
         if ((firstChar != 'L') ||
             (descriptor.charAt(length - 1) != ';')) {
-            throw new IllegalArgumentException("bad descriptor");
+            throw new IllegalArgumentException("bad descriptor: " + descriptor);
         }
 
         /*
@@ -349,13 +349,13 @@
                 case '.':
                 case '(':
                 case ')': {
-                    throw new IllegalArgumentException("bad descriptor");
+                    throw new IllegalArgumentException("bad descriptor: " + descriptor);
                 }
                 case '/': {
                     if ((i == 1) ||
                         (i == (length - 1)) ||
                         (descriptor.charAt(i - 1) == '/')) {
-                        throw new IllegalArgumentException("bad descriptor");
+                        throw new IllegalArgumentException("bad descriptor: " + descriptor);
                     }
                     break;
                 }
diff --git a/dx/src/com/android/dx/ssa/LiteralOpUpgrader.java b/dx/src/com/android/dx/ssa/LiteralOpUpgrader.java
index 12bfa0d..f3976f2 100644
--- a/dx/src/com/android/dx/ssa/LiteralOpUpgrader.java
+++ b/dx/src/com/android/dx/ssa/LiteralOpUpgrader.java
@@ -34,7 +34,7 @@
 import java.util.List;
 
 /**
- * Upgrades insn to their literal (constant-immediate) equivilent if possible.
+ * Upgrades insn to their literal (constant-immediate) equivalent if possible.
  * Also switches IF instructions that compare with a constant zero or null
  * to be their IF_*Z equivalents.
  */