Return Code instances from Method.declare.

Add type cast, and instanceof.
Add a unit test for recursive method calls.
Add return type checking.
Improve documentation on BinaryOp.

Change-Id: Ib85ed81be90756793067913efe86e1e442483f76
http://code.google.com/p/android/issues/detail?id=6322
diff --git a/dx/junit-tests/HelloWorldMaker.java b/dx/junit-tests/HelloWorldMaker.java
index 6c6cbff..1f0008b 100644
--- a/dx/junit-tests/HelloWorldMaker.java
+++ b/dx/junit-tests/HelloWorldMaker.java
@@ -57,10 +57,11 @@
         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);
+        Method hello = helloWorld.getMethod(voidType, "hello");
 
         // create some registers
         //    (I'd like a better syntax for this)
-        Code code = generator.newCode();
+        Code code = hello.declare(AccessFlags.ACC_STATIC | AccessFlags.ACC_PUBLIC);
         Local<Integer> a = code.newLocal(intType);
         Local<Integer> b = code.newLocal(intType);
         Local<Integer> c = code.newLocal(intType);
@@ -76,10 +77,6 @@
         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);
diff --git a/dx/junit-tests/com/android/dx/gen/DexGeneratorTest.java b/dx/junit-tests/com/android/dx/gen/DexGeneratorTest.java
index c94e9b1..8a9a669 100644
--- a/dx/junit-tests/com/android/dx/gen/DexGeneratorTest.java
+++ b/dx/junit-tests/com/android/dx/gen/DexGeneratorTest.java
@@ -24,9 +24,7 @@
 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;
 
@@ -38,25 +36,12 @@
  * <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<String> stringType;
     private Type<DexGeneratorTest> dexGeneratorTestType;
     private Type<?> generatedType;
     private Type<Callable> callableType;
@@ -77,33 +62,33 @@
         longType = generator.getType(long.class);
         booleanType = generator.getType(boolean.class);
         objectType = generator.getType(Object.class);
+        stringType = generator.getType(String.class);
         dexGeneratorTestType = generator.getType(DexGeneratorTest.class);
         generatedType = generator.getType("LGenerated;");
         callableType = generator.getType(Callable.class);
         call = callableType.getMethod(objectType, "call");
+        generatedType.declare("Generated.java", ACC_PUBLIC, objectType);
     }
 
     public void testNewInstance() throws Exception {
         /*
-         * public static Constructable generatedMethod(long a, boolean b) {
+         * public static Constructable call(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);
+        Code code = generatedType.getMethod(constructable, "call", longType, booleanType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<Long> localA = code.getParameter(0, longType);
+        Local<Boolean> localB = code.getParameter(1, booleanType);
         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);
+        Constructable constructed = (Constructable) getMethod().invoke(null, 5L, false);
         assertEquals(5L, constructed.a);
         assertEquals(false, constructed.b);
     }
@@ -119,22 +104,21 @@
 
     public void testInvokeStatic() throws Exception {
         /*
-         * public static int generatedMethod(int a) {
+         * public static int call(int a) {
          *   int result = DexGeneratorTest.staticMethod(a);
          *   return result;
          * }
          */
-
-        Code code = generator.newCode();
-        Local<Integer> localA = code.newParameter(intType);
+        Code code = generatedType.getMethod(intType, "call", intType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<Integer> localA = code.getParameter(0, intType);
         Local<Integer> localResult = code.newLocal(intType);
-        Method<DexGeneratorTest, Integer> staticMethod
+        Method<?, 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));
+        assertEquals(10, getMethod().invoke(null, 4));
     }
 
     @SuppressWarnings("unused") // called by generated code
@@ -144,23 +128,22 @@
 
     public void testInvokeVirtual() throws Exception {
         /*
-         * public static int generatedMethod(DexGeneratorTest test, int a) {
+         * public static int call(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);
+        Code code = generatedType.getMethod(intType, "call", dexGeneratorTestType, intType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<DexGeneratorTest> localInstance = code.getParameter(0, dexGeneratorTestType);
+        Local<Integer> localA = code.getParameter(1, 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));
+        assertEquals(9, getMethod().invoke(null, this, 4));
     }
 
     @SuppressWarnings("unused") // called by generated code
@@ -168,44 +151,42 @@
         return a + 5;
     }
 
-    public void testInvokeDirect() throws Exception {
+    public <G> void testInvokeDirect() throws Exception {
         /*
          * private int directMethod() {
          *   int a = 5;
          *   return a;
          * }
          *
-         * public static int generatedMethod(Generated g) {
+         * public static int call(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
+        Type<G> generated = generator.getType("LGenerated;");
+        Method<G, Integer> directMethod = generated.getMethod(intType, "directMethod");
+        Code directCode = directMethod.declare(ACC_PRIVATE);
+        directCode.getThis(generated); // '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();
+        Method<G, Integer> method = generated.getMethod(intType, "call", generated);
+        Code code = method.declare(ACC_PUBLIC | ACC_STATIC);
         Local<Integer> localB = code.newLocal(intType);
-        Local<?> localG = code.newParameter(generatedType);
+        Local<G> localG = code.getParameter(0, generated);
         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);
+        java.lang.reflect.Method m = generatedClass.getMethod("call", generatedClass);
         assertEquals(5, m.invoke(null, instance));
     }
 
-    public void testInvokeSuper() throws Exception {
+    public <G> void testInvokeSuper() throws Exception {
         /*
          * public int superHashCode() {
          *   int result = super.hashCode();
@@ -215,24 +196,21 @@
          *   return 0;
          * }
          */
-        Method<?, Integer> objectHashCode = objectType.getMethod(intType, "hashCode");
-        Code superHashCode = generator.newCode();
+        Type<G> generated = generator.getType("LGenerated;");
+        Method<Object, Integer> objectHashCode = objectType.getMethod(intType, "hashCode");
+        Code superHashCode = generated.getMethod(intType, "superHashCode").declare(ACC_PUBLIC);
         Local<Integer> localResult = superHashCode.newLocal(intType);
-        Local<?> localThis = superHashCode.newThisLocal(generatedType);
+        Local<G> localThis = superHashCode.getThis(generated);
         superHashCode.invokeSuper(objectHashCode, localResult, localThis);
         superHashCode.returnValue(localResult);
-        generatedType.getMethod(intType, "superHashCode").declare(ACC_PUBLIC, superHashCode);
 
-        Code generatedHashCode = generator.newCode();
-        generatedHashCode.newThisLocal(generatedType);
+        Code generatedHashCode = generated.getMethod(intType, "hashCode").declare(ACC_PUBLIC);
         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");
@@ -246,13 +224,14 @@
 
     public void testInvokeInterface() throws Exception {
         /*
-         * public static Object generatedMethod(Callable c) {
+         * public static Object call(Callable c) {
          *   Object result = c.call();
          *   return result;
          * }
          */
-        Code code = generator.newCode();
-        Local<Callable> localC = code.newParameter(callableType);
+        Code code = generatedType.getMethod(objectType, "call", callableType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<Callable> localC = code.getParameter(0, callableType);
         Local<Object> localResult = code.newLocal(objectType);
         code.invokeInterface(call, localResult, localC);
         code.returnValue(localResult);
@@ -262,30 +241,63 @@
                 return "abc";
             }
         };
-        java.lang.reflect.Method method = newMethod(Object.class, code, Callable.class);
-        assertEquals("abc", method.invoke(null, callable));
+        assertEquals("abc", getMethod().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);
+        Method<?, Integer> method = generatedType.getMethod(intType, "call", argTypes);
+        Code code = method.declare(ACC_PUBLIC | ACC_STATIC);
         try {
-            method.declare(ACC_PUBLIC | ACC_STATIC, code);
-            fail();
+            code.getParameter(0, intType);
         } catch (IllegalArgumentException e) {
         }
+        try {
+            code.getParameter(2, intType);
+        } catch (IndexOutOfBoundsException e) {
+        }
     }
 
+    public void testInvokeTypeSafety() throws Exception {
+        /*
+         * public static boolean call(DexGeneratorTest test) {
+         *   CharSequence cs = test.toString();
+         *   boolean result = cs.equals(test);
+         *   return result;
+         * }
+         */
+        Code code = generatedType.getMethod(booleanType, "call", dexGeneratorTestType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<DexGeneratorTest> localTest = code.getParameter(0, dexGeneratorTestType);
+        Type<CharSequence> charSequenceType = generator.getType(CharSequence.class);
+        Method<Object, String> objectToString = objectType.getMethod(stringType, "toString");
+        Method<Object, Boolean> objectEquals
+                = objectType.getMethod(booleanType, "equals", objectType);
+        Local<CharSequence> localCs = code.newLocal(charSequenceType);
+        Local<Boolean> localResult = code.newLocal(booleanType);
+        code.invokeVirtual(objectToString, localCs, localTest);
+        code.invokeVirtual(objectEquals, localResult, localCs, localTest);
+        code.returnValue(localResult);
+
+        assertEquals(false, getMethod().invoke(null, this));
+    }
+
     public void testReturnTypeMismatch() {
-        fail("TODO");
+        Code code = generatedType.getMethod(stringType, "call")
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        try {
+            code.returnValue(code.newLocal(booleanType));
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            code.returnVoid();
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
     }
 
     public void testDeclareStaticFields() throws Exception {
@@ -297,8 +309,6 @@
          */
         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");
@@ -320,7 +330,7 @@
          */
         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();
@@ -340,7 +350,7 @@
      * Declare a constructor that takes an int parameter and assigns it to a
      * field.
      */
-    public void testDeclareConstructor() throws Exception {
+    public <G> void testDeclareConstructor() throws Exception {
         /*
          * class Generated {
          *   public final int a;
@@ -349,17 +359,15 @@
          *   }
          * }
          */
-        Field<?, Integer> field = generatedType.getField(intType, "a");
+        Type<G> generated = generator.getType("LGenerated;");
+        Field<G, Integer> field = generated.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);
+        Code code = generatedType.getConstructor(intType).declare(ACC_PUBLIC | ACC_CONSTRUCTOR);
+        Local<G> thisRef = code.getThis(generated);
+        Local<Integer> parameter = code.getParameter(0, intType);
+        code.invokeDirect(objectType.getConstructor(), null, thisRef);
+        code.iput(field, thisRef, parameter);
+        code.returnVoid();
 
         Class<?> generatedClass = loadAndGenerate();
         java.lang.reflect.Field a = generatedClass.getField("a");
@@ -383,14 +391,15 @@
 
     private <T> void testReturnType(Class<T> javaType, T value) throws Exception {
         /*
-         * public int generatedMethod() {
+         * public int call() {
          *   int a = 5;
          *   return a;
          * }
          */
         reset();
         Type<T> returnType = generator.getType(javaType);
-        Code code = generator.newCode();
+        Code code = generatedType.getMethod(returnType, "call")
+                .declare(ACC_PUBLIC | ACC_STATIC);
         if (value != null) {
             Local<T> i = code.newLocal(returnType);
             code.loadConstant(i, value);
@@ -398,12 +407,9 @@
         } 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");
+        java.lang.reflect.Method method = generatedClass.getMethod("call");
         assertEquals(javaType, method.getReturnType());
         assertEquals(value, method.invoke(null));
     }
@@ -442,7 +448,7 @@
 
     private java.lang.reflect.Method newBranchingMethod(Comparison comparison) throws Exception {
         /*
-         * public static int generatedMethod(int localA, int localB) {
+         * public static boolean call(int localA, int localB) {
          *   if (a comparison b) {
          *     return true;
          *   }
@@ -450,9 +456,10 @@
          * }
          */
         reset();
-        Code code = generator.newCode();
-        Local<Integer> localA = code.newParameter(intType);
-        Local<Integer> localB = code.newParameter(intType);
+        Code code = generatedType.getMethod(booleanType, "call", intType, intType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<Integer> localA = code.getParameter(0, intType);
+        Local<Integer> localB = code.getParameter(1, intType);
         Local<Boolean> result = code.newLocal(generator.getType(boolean.class));
         Label afterIf = code.newLabel();
         Label ifBody = code.newLabel();
@@ -466,57 +473,57 @@
         code.mark(afterIf);
         code.loadConstant(result, false);
         code.returnValue(result);
-        return newMethod(boolean.class, code, int.class, int.class);
+        return getMethod();
     }
 
     public void testCastIntegerToInteger() throws Exception {
-        java.lang.reflect.Method intToLong = newCastingMethod(int.class, long.class);
+        java.lang.reflect.Method intToLong = newNumericCastingMethod(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);
+        java.lang.reflect.Method longToInt = newNumericCastingMethod(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);
+        java.lang.reflect.Method intToShort = newNumericCastingMethod(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);
+        java.lang.reflect.Method intToChar = newNumericCastingMethod(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);
+        java.lang.reflect.Method intToByte = newNumericCastingMethod(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);
+        java.lang.reflect.Method intToFloat = newNumericCastingMethod(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);
+        java.lang.reflect.Method intToDouble = newNumericCastingMethod(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);
+        java.lang.reflect.Method longToFloat = newNumericCastingMethod(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);
+        java.lang.reflect.Method longToDouble = newNumericCastingMethod(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));
@@ -524,7 +531,7 @@
     }
 
     public void testCastFloatingPointToInteger() throws Exception {
-        java.lang.reflect.Method floatToInt = newCastingMethod(float.class, int.class);
+        java.lang.reflect.Method floatToInt = newNumericCastingMethod(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));
@@ -532,7 +539,7 @@
         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);
+        java.lang.reflect.Method floatToLong = newNumericCastingMethod(float.class, long.class);
         assertEquals(0L, floatToLong.invoke(null, 0.0f));
         assertEquals(-1L, floatToLong.invoke(null, -1.0f));
         assertEquals(10000000272564224L, floatToLong.invoke(null, 10e15f));
@@ -540,7 +547,7 @@
         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);
+        java.lang.reflect.Method doubleToInt = newNumericCastingMethod(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));
@@ -548,7 +555,7 @@
         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);
+        java.lang.reflect.Method doubleToLong = newNumericCastingMethod(double.class, long.class);
         assertEquals(0L, doubleToLong.invoke(null, 0.0));
         assertEquals(-1L, doubleToLong.invoke(null, -1.0));
         assertEquals(10000000000000000L, doubleToLong.invoke(null, 10e15));
@@ -558,14 +565,14 @@
     }
 
     public void testCastFloatingPointToFloatingPoint() throws Exception {
-        java.lang.reflect.Method floatToDouble = newCastingMethod(float.class, double.class);
+        java.lang.reflect.Method floatToDouble = newNumericCastingMethod(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);
+        java.lang.reflect.Method doubleToFloat = newNumericCastingMethod(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));
@@ -573,10 +580,10 @@
         assertEquals(Float.NaN, doubleToFloat.invoke(null, Double.NaN));
     }
 
-    private java.lang.reflect.Method newCastingMethod(Class<?> source, Class<?> target)
+    private java.lang.reflect.Method newNumericCastingMethod(Class<?> source, Class<?> target)
             throws Exception {
         /*
-         * public static short generatedMethod(int source) {
+         * public static short call(int source) {
          *   short casted = (short) source;
          *   return casted;
          * }
@@ -584,12 +591,13 @@
         reset();
         Type<?> sourceType = generator.getType(source);
         Type<?> targetType = generator.getType(target);
-        Code code = generator.newCode();
-        Local<?> localSource = code.newParameter(sourceType);
+        Code code = generatedType.getMethod(targetType, "call", sourceType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<?> localSource = code.getParameter(0, sourceType);
         Local<?> localCasted = code.newLocal(targetType);
-        code.cast(localSource, localCasted);
+        code.numericCast(localSource, localCasted);
         code.returnValue(localCasted);
-        return newMethod(target, code, source);
+        return getMethod();
     }
 
     public void testNot() throws Exception {
@@ -604,20 +612,21 @@
         assertEquals(0x98765432edcba987L, notLong.invoke(null, 0x6789abcd12345678L));
     }
 
-    private java.lang.reflect.Method newNotMethod(Class<?> source) throws Exception {
+    private <T> java.lang.reflect.Method newNotMethod(Class<T> source) throws Exception {
         /*
-         * public static short generatedMethod(int source) {
+         * public static short call(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
+        Type<T> valueType = generator.getType(source);
+        Code code = generatedType.getMethod(valueType, "call", valueType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<T> localSource = code.getParameter(0, valueType);
+        code.not(localSource, localSource);
         code.returnValue(localSource);
-        return newMethod(source, code, source);
+        return getMethod();
     }
 
     public void testNegate() throws Exception {
@@ -644,20 +653,21 @@
         assertEquals(Double.POSITIVE_INFINITY, negateDouble.invoke(null, Double.NEGATIVE_INFINITY));
     }
 
-    private java.lang.reflect.Method newNegateMethod(Class<?> source) throws Exception {
+    private <T> java.lang.reflect.Method newNegateMethod(Class<T> source) throws Exception {
         /*
-         * public static short generatedMethod(int source) {
+         * public static short call(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
+        Type<T> valueType = generator.getType(source);
+        Code code = generatedType.getMethod(valueType, "call", valueType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<T> localSource = code.getParameter(0, valueType);
+        code.negate(localSource, localSource);
         code.returnValue(localSource);
-        return newMethod(source, code, source);
+        return getMethod();
     }
 
     public void testIntBinaryOps() throws Exception {
@@ -796,7 +806,7 @@
         assertEquals(Double.NaN, remainder.invoke(null, 5.5, 0.0));
     }
 
-    private java.lang.reflect.Method newBinaryOpMethod(Class<?> valueClass, BinaryOp op)
+    private <T> java.lang.reflect.Method newBinaryOpMethod(Class<T> valueClass, BinaryOp op)
             throws Exception {
         /*
          * public static int binaryOp(int a, int b) {
@@ -805,14 +815,15 @@
          * }
          */
         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
+        Type<T> valueType = generator.getType(valueClass);
+        Code code = generatedType.getMethod(valueType, "call", valueType,valueType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<T> localA = code.getParameter(0, valueType);
+        Local<T> localB = code.getParameter(1, valueType);
+        Local<T> localResult = code.newLocal(valueType);
+        code.op(op, localResult, localA, localB);
         code.returnValue(localResult);
-        return newMethod(valueClass, code, valueClass, valueClass);
+        return getMethod();
     }
 
     public void testReadAndWriteInstanceFields() throws Exception {
@@ -879,7 +890,7 @@
     private <V> java.lang.reflect.Method newInstanceSwapMethod(
             Class<V> valueClass, String fieldName) throws Exception {
         /*
-         * public static int generatedMethod(Instance instance, int newValue) {
+         * public static int call(Instance instance, int newValue) {
          *   int oldValue = instance.intValue;
          *   instance.intValue = newValue;
          *   return oldValue;
@@ -889,14 +900,15 @@
         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);
+        Code code = generatedType.getMethod(valueType, "call", objectType, valueType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<Instance> localInstance = code.getParameter(0, objectType);
+        Local<V> localNewValue = code.getParameter(1, 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);
+        return getMethod();
     }
 
     public void testReadAndWriteStaticFields() throws Exception {
@@ -961,7 +973,7 @@
     private <V> java.lang.reflect.Method newStaticSwapMethod(Class<V> valueClass, String fieldName)
             throws Exception {
         /*
-         * public static int generatedMethod(int newValue) {
+         * public static int call(int newValue) {
          *   int oldValue = Static.intValue;
          *   Static.intValue = newValue;
          *   return oldValue;
@@ -971,13 +983,58 @@
         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);
+        Code code = generatedType.getMethod(valueType, "call", valueType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<V> localNewValue = code.getParameter(0, valueType);
         Local<V> localOldValue = code.newLocal(valueType);
         code.sget(field, localOldValue);
         code.sput(field, localNewValue);
         code.returnValue(localOldValue);
-        return newMethod(valueClass, code, valueClass);
+        return getMethod();
+    }
+
+    public void testTypeCast() throws Exception {
+        /*
+         * public static String call(Object o) {
+         *   String s = (String) o;
+         * }
+         */
+        Code code = generatedType.getMethod(stringType, "call", objectType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<Object> localObject = code.getParameter(0, objectType);
+        Local<String> localString = code.newLocal(stringType);
+        code.typeCast(localObject, localString);
+        code.returnValue(localString);
+
+        java.lang.reflect.Method method = getMethod();
+        assertEquals("s", method.invoke(null, "s"));
+        assertEquals(null, method.invoke(null, (String) null));
+        try {
+            method.invoke(null, 5);
+            fail();
+        } catch (InvocationTargetException expected) {
+            assertEquals(ClassCastException.class, expected.getCause().getClass());
+        }
+    }
+
+    public void testInstanceOf() throws Exception {
+        /*
+         * public static boolean call(Object o) {
+         *   boolean result = o instanceof String;
+         *   return result;
+         * }
+         */
+        Code code = generatedType.getMethod(booleanType, "call", objectType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<Object> localObject = code.getParameter(0, objectType);
+        Local<Boolean> localResult = code.newLocal(booleanType);
+        code.instanceOfType(localResult, localObject, stringType);
+        code.returnValue(localResult);
+
+        java.lang.reflect.Method method = getMethod();
+        assertEquals(true, method.invoke(null, "s"));
+        assertEquals(false, method.invoke(null, (String) null));
+        assertEquals(false, method.invoke(null, 5));
     }
 
     /**
@@ -985,7 +1042,7 @@
      */
     public void testForLoop() throws Exception {
         /*
-         * public static int generatedMethod(int count) {
+         * public static int call(int count) {
          *   int result = 1;
          *   for (int i = 0; i < count; i += 1) {
          *     result = result * 2;
@@ -993,8 +1050,9 @@
          *   return result;
          * }
          */
-        Code code = generator.newCode();
-        Local<Integer> localCount = code.newParameter(intType);
+        Code code = generatedType.getMethod(intType, "call", intType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<Integer> localCount = code.getParameter(0, intType);
         Local<Integer> localResult = code.newLocal(intType);
         Local<Integer> localI = code.newLocal(intType);
         Local<Integer> local1 = code.newLocal(intType);
@@ -1016,7 +1074,7 @@
         code.mark(afterLoop);
         code.returnValue(localResult);
 
-        java.lang.reflect.Method pow2 = newMethod(int.class, code, int.class);
+        java.lang.reflect.Method pow2 = getMethod();
         assertEquals(1, pow2.invoke(null, 0));
         assertEquals(2, pow2.invoke(null, 1));
         assertEquals(4, pow2.invoke(null, 2));
@@ -1029,7 +1087,7 @@
      */
     public void testWhileLoop() throws Exception {
         /*
-         * public static int generatedMethod(int max) {
+         * public static int call(int max) {
          *   int result = 1;
          *   while (result < max) {
          *     result = result * 2;
@@ -1037,8 +1095,9 @@
          *   return result;
          * }
          */
-        Code code = generator.newCode();
-        Local<Integer> localMax = code.newParameter(intType);
+        Code code = generatedType.getMethod(intType, "call", intType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<Integer> localMax = code.getParameter(0, intType);
         Local<Integer> localResult = code.newLocal(intType);
         Local<Integer> local2 = code.newLocal(intType);
         code.loadConstant(localResult, 1);
@@ -1055,7 +1114,7 @@
         code.mark(afterLoop);
         code.returnValue(localResult);
 
-        java.lang.reflect.Method ceilPow2 = newMethod(int.class, code, int.class);
+        java.lang.reflect.Method ceilPow2 = getMethod();
         assertEquals(1, ceilPow2.invoke(null, 1));
         assertEquals(2, ceilPow2.invoke(null, 2));
         assertEquals(4, ceilPow2.invoke(null, 3));
@@ -1066,7 +1125,7 @@
 
     public void testIfElseBlock() throws Exception {
         /*
-         * public static int generatedMethod(int a, int b, int c) {
+         * public static int call(int a, int b, int c) {
          *   if (a < b) {
          *     if (a < c) {
          *       return a;
@@ -1080,10 +1139,11 @@
          *   }
          * }
          */
-        Code code = generator.newCode();
-        Local<Integer> localA = code.newParameter(intType);
-        Local<Integer> localB = code.newParameter(intType);
-        Local<Integer> localC = code.newParameter(intType);
+        Code code = generatedType.getMethod(intType, "call", intType, intType, intType)
+                .declare(ACC_PUBLIC | ACC_STATIC);
+        Local<Integer> localA = code.getParameter(0, intType);
+        Local<Integer> localB = code.getParameter(1, intType);
+        Local<Integer> localC = code.getParameter(2, intType);
         Label aLessThanB = code.newLabel();
         Label aLessThanC = code.newLabel();
         Label bLessThanC = code.newLabel();
@@ -1101,16 +1161,58 @@
         code.mark(bLessThanC);
         code.returnValue(localB);
 
-        java.lang.reflect.Method min = newMethod(int.class, code, int.class, int.class, int.class);
+        java.lang.reflect.Method min = getMethod();
         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
+    public void testRecursion() throws Exception {
 
-    // TODO: instanceof
+        /*
+         * public static int call(int a) {
+         *   if (a < 2) {
+         *     return a;
+         *   }
+         *   a -= 1;
+         *   int x = call(a)
+         *   a -= 1;
+         *   int y = call(a);
+         *   int result = x + y;
+         *   return result;
+         * }
+         */
+        Method<?, Integer> c = generatedType.getMethod(intType, "call", intType);
+        Code code = c.declare(ACC_PUBLIC | ACC_STATIC);
+        Local<Integer> localA = code.getParameter(0, intType);
+        Local<Integer> local1 = code.newLocal(intType);
+        Local<Integer> local2 = code.newLocal(intType);
+        Local<Integer> localX = code.newLocal(intType);
+        Local<Integer> localY = code.newLocal(intType);
+        Local<Integer> localResult = code.newLocal(intType);
+        Label baseCase = code.newLabel();
+        code.loadConstant(local1, 1);
+        code.loadConstant(local2, 2);
+        code.compare(Comparison.LT, localA, local2, baseCase);
+        code.op(BinaryOp.SUBTRACT, localA, localA, local1);
+        code.invokeStatic(c, localX, localA);
+        code.op(BinaryOp.SUBTRACT, localA, localA, local1);
+        code.invokeStatic(c, localY, localA);
+        code.op(BinaryOp.ADD, localResult, localX, localY);
+        code.returnValue(localResult);
+        code.mark(baseCase);
+        code.returnValue(localA);
+
+        java.lang.reflect.Method fib = getMethod();
+        assertEquals(0, fib.invoke(null, 0));
+        assertEquals(1, fib.invoke(null, 1));
+        assertEquals(1, fib.invoke(null, 2));
+        assertEquals(2, fib.invoke(null, 3));
+        assertEquals(3, fib.invoke(null, 4));
+        assertEquals(5, fib.invoke(null, 5));
+        assertEquals(8, fib.invoke(null, 6));
+    }
 
     // TODO: array ops including new array, aget, etc.
 
@@ -1121,37 +1223,26 @@
     // TODO: fail if a label is unreachable (never navigated to)
 
     private void addDefaultConstructor() {
-        Code code = generator.newCode();
-        Local<?> thisRef = code.newThisLocal(generatedType);
+        Code code = generatedType.getConstructor().declare(ACC_PUBLIC | ACC_CONSTRUCTOR);
+        Local<?> thisRef = code.getThis(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}.
+     * Returns the generated method.
      */
-    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]);
+    private java.lang.reflect.Method getMethod() throws Exception {
+        Class<?> generated = loadAndGenerate();
+        for (java.lang.reflect.Method method : generated.getMethods()) {
+            if (method.getName().equals("call")) {
+                return method;
+            }
         }
-
-        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);
+        throw new IllegalStateException("no call() method");
     }
 
     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/src/com/android/dx/gen/BinaryOp.java b/dx/src/com/android/dx/gen/BinaryOp.java
index 9c8f25e..65a2998 100644
--- a/dx/src/com/android/dx/gen/BinaryOp.java
+++ b/dx/src/com/android/dx/gen/BinaryOp.java
@@ -21,59 +21,93 @@
 import com.android.dx.rop.type.TypeList;
 
 /**
- * A binary operation on two values of the same type.
+ * An operation on two values of the same type.
+ *
+ * <p>Math operations ({@link #ADD}, {@link #SUBTRACT}, {@link #MULTIPLY},
+ * {@link #DIVIDE}, and {@link #REMAINDER}) support ints, longs, floats and
+ * doubles.
+ *
+ * <p>Bit operations ({@link #AND}, {@link #OR}, {@link #XOR}, {@link
+ * #SHIFT_LEFT}, {@link #SHIFT_RIGHT}, {@link #UNSIGNED_SHIFT_RIGHT}) support
+ * ints and longs.
+ *
+ * <p>Division by zero behaves differently depending on the operand type.
+ * For int and long operands, {@link #DIVIDE} and {@link #REMAINDER} throw
+ * {@link ArithmeticException} if {@code b == 0}. For float and double operands,
+ * the operations return {@code NaN}.
  */
 public enum BinaryOp {
+    /** {@code a + b} */
     ADD() {
         @Override Rop rop(TypeList types) {
             return Rops.opAdd(types);
         }
     },
+
+    /** {@code a - b} */
     SUBTRACT() {
         @Override Rop rop(TypeList types) {
             return Rops.opSub(types);
         }
     },
+
+    /** {@code a * b} */
     MULTIPLY() {
         @Override Rop rop(TypeList types) {
             return Rops.opMul(types);
         }
     },
+
+    /** {@code a / b} */
     DIVIDE() {
         @Override Rop rop(TypeList types) {
             return Rops.opDiv(types);
         }
     },
+
+    /** {@code a % b} */
     REMAINDER() {
         @Override Rop rop(TypeList types) {
             return Rops.opRem(types);
         }
     },
+
+    /** {@code a & b} */
     AND() {
         @Override Rop rop(TypeList types) {
             return Rops.opAnd(types);
         }
     },
+
+    /** {@code a | b} */
     OR() {
         @Override Rop rop(TypeList types) {
             return Rops.opOr(types);
         }
     },
+
+    /** {@code a ^ b} */
     XOR() {
         @Override Rop rop(TypeList types) {
             return Rops.opXor(types);
         }
     },
+
+    /** {@code a << b} */
     SHIFT_LEFT() {
         @Override Rop rop(TypeList types) {
             return Rops.opShl(types);
         }
     },
+
+    /** {@code a >> b} */
     SHIFT_RIGHT() {
         @Override Rop rop(TypeList types) {
             return Rops.opShr(types);
         }
     },
+
+    /** {@code a >>> b} */
     UNSIGNED_SHIFT_RIGHT() {
         @Override Rop rop(TypeList types) {
             return Rops.opUshr(types);
diff --git a/dx/src/com/android/dx/gen/Code.java b/dx/src/com/android/dx/gen/Code.java
index c3c574e..6ce9e2a 100644
--- a/dx/src/com/android/dx/gen/Code.java
+++ b/dx/src/com/android/dx/gen/Code.java
@@ -32,12 +32,9 @@
 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;
 
@@ -45,6 +42,7 @@
  * Builds a sequence of instructions.
  */
 public final class Code {
+    private final Method<?, ?> method;
     /**
      * All allocated labels. Although the order of the labels in this list
      * shouldn't impact behavior, it is used to determine basic block indices.
@@ -59,26 +57,46 @@
 
     /** true once we've fixed the positions of the parameter registers */
     private boolean localsInitialized;
+
+    private final Local<?> thisLocal;
+    private final List<Local<?>> parameters = new ArrayList<Local<?>>();
     private final List<Local<?>> locals = new ArrayList<Local<?>>();
     private SourcePosition sourcePosition = SourcePosition.NO_INFO;
 
-    Code(DexGenerator generator) {
+    public Code(Method<?, ?> method) {
+        this.method = method;
+        thisLocal = method.isStatic()
+                ? null
+                : new Local<Object>(this, method.declaringType, Local.InitialValue.THIS);
+        for (Type<?> parameter : method.parameters.types) {
+            parameters.add(new Local<Object>(this, parameter, Local.InitialValue.PARAMETER));
+        }
         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 <T> Local<T> getParameter(int index, Type<T> type) {
+        return coerce(parameters.get(index), type);
     }
 
-    public Local<?> newThisLocal(Type<?> type) {
-        return allocateLocal(type, Local.InitialValue.THIS);
+    public <T> Local<T> getThis(Type<T> type) {
+        if (thisLocal == null) {
+            throw new IllegalStateException("static methods cannot access 'this'");
+        }
+        return coerce(thisLocal, type);
+    }
+
+    @SuppressWarnings("unchecked") // guarded by an equals check
+    private <T> Local<T> coerce(Local<?> local, Type<T> expectedType) {
+        if (!local.type.equals(expectedType)) {
+            throw new IllegalArgumentException(
+                    "requested " + expectedType + " but was " + local.type);
+        }
+        return (Local<T>) local;
     }
 
     private <T> Local<T> allocateLocal(Type<T> type, Local.InitialValue initialValue) {
@@ -90,26 +108,28 @@
         return result;
     }
 
+    /**
+     * Assigns registers to locals. From the spec:
+     *  "the N arguments to a method land in the last N registers of the
+     *   method's invocation frame, in order. Wide arguments consume two
+     *   registers. Instance methods are passed a this reference as their
+     *   first argument."
+     */
     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;
-            }
+            reg += local.initialize(reg);
+        }
+        if (thisLocal != null) {
+            reg += thisLocal.initialize(reg);
+        }
+        for (Local<?> local : parameters) {
+            reg += local.initialize(reg);
         }
     }
 
@@ -222,7 +242,7 @@
         unary(Rops.opNot(source.type.ropType), source, target);
     }
 
-    public void cast(Local<?> source, Local<?> target) {
+    public void numericCast(Local<?> source, Local<?> target) {
         unary(getCastRop(source.type.ropType, target.type.ropType), source, target);
     }
 
@@ -261,6 +281,10 @@
 
     // instructions: branches
 
+    /**
+     * Compare integers. If the comparison is true, execution jumps to {@code
+     * trueLabel}. If it is false, execution continues to the next instruction.
+     */
     public <T> void compare(Comparison comparison, Local<T> a, Local<T> b, Label trueLabel) {
         if (trueLabel == null) {
             throw new IllegalArgumentException();
@@ -312,32 +336,32 @@
         invokeDirect(constructor, null, target, args);
     }
 
-    public <R> void invokeStatic(Method<?, R> method, Local<R> target, Local<?>... args) {
+    public <R> void invokeStatic(Method<?, R> method, Local<? super 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) {
+    public <I, R> void invokeVirtual(Method<I, R> method, Local<? super R> target,
+            Local<? extends 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) {
+    public <I, R> void invokeDirect(Method<I, R> method, Local<? super R> target,
+            Local<? extends 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) {
+    public <I, R> void invokeSuper(Method<I, R> method, Local<? super R> target,
+            Local<? extends I> 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) {
+    public <I, R> void invokeInterface(Method<I, R> method, Local<? super R> target,
+            Local<? extends I> 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) {
+    private <I, R> void invoke(Rop rop, Method<I, R> method, Local<? super R> target,
+            Local<? extends I> object, Local<?>... args) {
         addInstruction(new ThrowingCstInsn(rop, sourcePosition, concatenate(object, args),
                 StdTypeList.EMPTY, method.constant));
         if (target != null) {
@@ -345,14 +369,37 @@
         }
     }
 
+    // instructions: types
+
+    public void instanceOfType(Local<?> target, Local<?> source, Type<?> type) {
+        addInstruction(new ThrowingCstInsn(Rops.INSTANCE_OF, sourcePosition,
+                RegisterSpecList.make(source.spec()), StdTypeList.EMPTY, type.constant));
+        moveResult(target, true);
+    }
+
+    public void typeCast(Local<?> source, Local<?> target) {
+        addInstruction(new ThrowingCstInsn(Rops.CHECK_CAST, sourcePosition,
+                RegisterSpecList.make(source.spec()), StdTypeList.EMPTY, target.type.constant));
+        moveResult(target, true);
+    }
+
     // instructions: return
 
     public void returnVoid() {
+        if (!method.returnType.isVoid()) {
+            throw new IllegalArgumentException("declared " + method.returnType
+                    + " but returned void");
+        }
         addInstruction(new PlainInsn(Rops.RETURN_VOID, sourcePosition, null,
                 RegisterSpecList.EMPTY));
     }
 
     public void returnValue(Local<?> result) {
+        if (!result.type.equals(method.returnType)) {
+            // TODO: this is probably too strict.
+            throw new IllegalArgumentException("declared " + method.returnType
+                    + " but returned " + result.type);
+        }
         addInstruction(new PlainInsn(Rops.opReturn(result.type.ropType), sourcePosition,
                 null, RegisterSpecList.make(result.spec())), null);
     }
@@ -392,25 +439,6 @@
         }
     }
 
-    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);
diff --git a/dx/src/com/android/dx/gen/DexGenerator.java b/dx/src/com/android/dx/gen/DexGenerator.java
index 8a9f5a3..5355cec 100644
--- a/dx/src/com/android/dx/gen/DexGenerator.java
+++ b/dx/src/com/android/dx/gen/DexGenerator.java
@@ -18,18 +18,6 @@
 
 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 java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -46,18 +34,14 @@
     /**
      * @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(String name) {
+        return types.get(new Type<T>(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.
      */
diff --git a/dx/src/com/android/dx/gen/Field.java b/dx/src/com/android/dx/gen/Field.java
index 64f7b4c..f6a42cf 100644
--- a/dx/src/com/android/dx/gen/Field.java
+++ b/dx/src/com/android/dx/gen/Field.java
@@ -67,10 +67,10 @@
      */
     public void declare(int accessFlags, Object staticValue) {
         if (declared) {
-            throw new IllegalStateException();
+            throw new IllegalStateException("already declared: " + this);
         }
         if ((accessFlags & (AccessFlags.ACC_STATIC)) == 0 && staticValue != null) {
-            throw new IllegalArgumentException("Instance fields may not have a value");
+            throw new IllegalArgumentException("instance fields may not have a value");
         }
         this.declared = true;
         this.accessFlags = accessFlags;
diff --git a/dx/src/com/android/dx/gen/Local.java b/dx/src/com/android/dx/gen/Local.java
index 1c23011..db2d909 100644
--- a/dx/src/com/android/dx/gen/Local.java
+++ b/dx/src/com/android/dx/gen/Local.java
@@ -17,23 +17,13 @@
 package com.android.dx.gen;
 
 import com.android.dx.rop.code.RegisterSpec;
-import java.util.Comparator;
+import static com.android.dx.rop.type.Type.BT_DOUBLE;
+import static com.android.dx.rop.type.Type.BT_LONG;
 
 /**
  * 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;
@@ -46,9 +36,22 @@
         this.initialValue = initialValue;
     }
 
-    void initialize(int reg) {
+    /**
+     * Assigns registers to this local.
+     *
+     * @return the number of registers required.
+     */
+    int initialize(int reg) {
         this.reg = reg;
         this.spec = RegisterSpec.make(reg, type.getRopType());
+
+        switch (type.ropType.getBasicType()) {
+        case BT_LONG:
+        case BT_DOUBLE:
+            return 2;
+        default:
+            return 1;
+        }
     }
 
     RegisterSpec spec() {
diff --git a/dx/src/com/android/dx/gen/Method.java b/dx/src/com/android/dx/gen/Method.java
index e88a031..343510a 100644
--- a/dx/src/com/android/dx/gen/Method.java
+++ b/dx/src/com/android/dx/gen/Method.java
@@ -100,31 +100,27 @@
     /**
      * @param accessFlags any flags masked by {@link AccessFlags#METHOD_FLAGS}.
      */
-    public void declare(int accessFlags, Code code) {
+    public Code declare(int accessFlags) {
         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());
+            throw new IllegalStateException("already declared: " + this);
         }
         this.declared = true;
         this.accessFlags = accessFlags;
-        this.code = code;
+        this.code = new Code(this);
+        return code;
     }
 
     boolean isDeclared() {
         return declared;
     }
 
+    boolean isStatic() {
+        if (!declared) {
+            throw new IllegalStateException();
+        }
+        return (accessFlags & ACC_STATIC) != 0;
+    }
+
     boolean isDirect() {
         if (!declared) {
             throw new IllegalStateException();
diff --git a/dx/src/com/android/dx/gen/Type.java b/dx/src/com/android/dx/gen/Type.java
index 8cce3c6..72e804f 100644
--- a/dx/src/com/android/dx/gen/Type.java
+++ b/dx/src/com/android/dx/gen/Type.java
@@ -105,7 +105,7 @@
      */
     public void declare(String sourceFile, int flags, Type<?> supertype, Type<?>... interfaces) {
         if (declared) {
-            throw new IllegalStateException();
+            throw new IllegalStateException("already declared: " + this);
         }
         this.declared = true;
         this.flags = flags;
@@ -114,6 +114,10 @@
         this.interfaces = new TypeList(interfaces);
     }
 
+    boolean isVoid() {
+        return constant.getClassType() == com.android.dx.rop.type.Type.VOID;
+    }
+
     boolean isDeclared() {
         return declared;
     }