Merge "Improve the error messages for invalid locals information"
diff --git a/.gitignore b/.gitignore
index 86e3098..cdc19a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,8 @@
 third_party/android_jar/lib-v[0-9][0-9].tar.gz
 third_party/benchmarks/santa-tracker
 third_party/benchmarks/santa-tracker.tar.gz
+third_party/desugar/desugar_*/
+third_party/desugar/desugar_*.tar.gz
 third_party/gmail/*
 !third_party/gmail/*.sha1
 third_party/gmscore/*
@@ -66,6 +68,8 @@
 third_party/ddmlib.tar.gz
 third_party/core-lambda-stubs
 third_party/core-lambda-stubs.tar.gz
+third_party/openjdk/openjdk-rt-1.8
+third_party/openjdk/openjdk-rt-1.8.tar.gz
 src/test/jack/ub-jack
 gradle-app.setting
 gradlew
diff --git a/build.gradle b/build.gradle
index e0c50ee..2d9e9b4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -310,6 +310,7 @@
                 "shadow",
                 "ddmlib",
                 "core-lambda-stubs",
+                "openjdk/openjdk-rt-1.8",
         ],
         // All dex-vms have a fixed OS of Linux, as they are only supported on Linux, and will be run in a Docker
         // container on other platforms where supported.
@@ -366,6 +367,7 @@
         "proguard/proguard_internal_159423826",
         "framework",
         "goyt",
+        "desugar/desugar_20180308"
     ],
 ]
 
@@ -1337,6 +1339,10 @@
 }
 
 task buildPreNJdwpTestsDex(type: Exec, dependsOn: "buildPreNJdwpTestsJar") {
+    onlyIf {
+        // TODO(b/76135355): Update dx.bat on Windows to something that can build this.
+        !OperatingSystem.current().isWindows()
+    }
     def inFile = buildPreNJdwpTestsJar.archivePath
     def outFile = new File(buildPreNJdwpTestsJar.destinationDir, buildPreNJdwpTestsJar.baseName + '-dex.jar')
     inputs.file inFile
@@ -1451,6 +1457,7 @@
         dependsOn jctfTestsClasses
         dependsOn buildDebugInfoExamplesDex
         dependsOn buildPreNJdwpTestsJar
+        dependsOn buildPreNJdwpTestsDex
     } else {
         logger.lifecycle("WARNING: Testing in not supported on your platform. Testing is only fully supported on " +
             "Linux and partially supported on Mac OS and Windows. Art does not run on other platforms.")
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java
index 1df78be..d2969d6 100644
--- a/src/main/java/com/android/tools/r8/ResourceShrinker.java
+++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -91,7 +91,7 @@
 @Deprecated
 final public class ResourceShrinker {
 
-  final static class Command extends BaseCommand {
+  public final static class Command extends BaseCommand {
 
     Command(AndroidApp app) {
       super(app);
@@ -103,7 +103,7 @@
     }
   }
 
-  final public static class Builder extends BaseCommand.Builder<Command, Builder> {
+  public final static class Builder extends BaseCommand.Builder<Command, Builder> {
 
     @Override
     Builder self() {
@@ -120,7 +120,7 @@
    * Classes that would like to process data relevant to resource shrinking should implement this
    * interface.
    */
-  interface ReferenceChecker {
+  public interface ReferenceChecker {
 
     /**
      * Returns if the class with specified internal name should be processed. Typically,
diff --git a/src/main/java/com/android/tools/r8/StringConsumer.java b/src/main/java/com/android/tools/r8/StringConsumer.java
index 9232e08..23883e9 100644
--- a/src/main/java/com/android/tools/r8/StringConsumer.java
+++ b/src/main/java/com/android/tools/r8/StringConsumer.java
@@ -147,6 +147,7 @@
           new BufferedWriter(new OutputStreamWriter(outputStream, encoding.newEncoder()));
       try {
         writer.write(string);
+        writer.flush();
       } catch (IOException e) {
         handler.error(new IOExceptionDiagnostic(e, origin));
       }
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index bf46660..6a78145 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "v1.2.2-dev";
+  public static final String LABEL = "v1.2.3-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index 0b5e678..dfe7c1b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -4,13 +4,14 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
 public class CfArrayLength extends CfInstruction {
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitInsn(Opcodes.ARRAYLENGTH);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index 3ff24b9..85405e4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -46,7 +47,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitInsn(getLoadType());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index 6d7ca1a..4b13af4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -46,7 +47,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitInsn(getStoreType());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfBinop.java
index af78290..6f8e868 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfBinop.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 
 public class CfBinop extends CfInstruction {
@@ -19,7 +20,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitInsn(opcode);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index 2ff7625..e46d475 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -5,6 +5,8 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -21,12 +23,17 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
-    visitor.visitTypeInsn(Opcodes.CHECKCAST, type.getInternalName());
+  public void write(MethodVisitor visitor, NamingLens lens) {
+    visitor.visitTypeInsn(Opcodes.CHECKCAST, lens.lookupInternalName(type));
   }
 
   @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public void registerUse(UseRegistry registry, DexType clazz) {
+    registry.registerCheckCast(type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index 5218a02..e4037e4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -6,6 +6,8 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Type;
 
@@ -22,8 +24,8 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
-    visitor.visitLdcInsn(Type.getObjectType(getInternalName()));
+  public void write(MethodVisitor visitor, NamingLens lens) {
+    visitor.visitLdcInsn(Type.getObjectType(getInternalName(lens)));
   }
 
   @Override
@@ -31,11 +33,11 @@
     printer.print(this);
   }
 
-  private String getInternalName() {
+  private String getInternalName(NamingLens lens) {
     switch (type.toShorty()) {
       case '[':
       case 'L':
-        return type.getInternalName();
+        return lens.lookupInternalName(type);
       case 'Z':
         return "java/lang/Boolean/TYPE";
       case 'B':
@@ -56,4 +58,9 @@
         throw new Unreachable("Unexpected type in const-class: " + type);
     }
   }
+
+  @Override
+  public void registerUse(UseRegistry registry, DexType clazz) {
+    registry.registerConstClass(type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index b63b4d5..ca34be7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -5,6 +5,9 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 
 public class CfConstMethodHandle extends CfInstruction {
@@ -20,12 +23,17 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
-    visitor.visitLdcInsn(handle.toAsmHandle());
+  public void write(MethodVisitor visitor, NamingLens lens) {
+    visitor.visitLdcInsn(handle.toAsmHandle(lens));
   }
 
   @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public void registerUse(UseRegistry registry, DexType clazz) {
+    registry.registerMethodHandle(handle);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index 8ec1545..7e06a88 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -5,6 +5,9 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Type;
 
@@ -21,12 +24,17 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
-    visitor.visitLdcInsn(Type.getType(type.toDescriptorString()));
+  public void write(MethodVisitor visitor, NamingLens lens) {
+    visitor.visitLdcInsn(Type.getType(type.toDescriptorString(lens)));
   }
 
   @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public void registerUse(UseRegistry registry, DexType clazz) {
+    registry.registerProto(type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
index a788a3b..385669d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -4,13 +4,14 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
 public class CfConstNull extends CfInstruction {
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitInsn(Opcodes.ACONST_NULL);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index 246cf0b..07fd664 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -48,7 +49,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     switch (type) {
       case INT:
         {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index 0f7f9f7..a22f279 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 
 public class CfConstString extends CfInstruction {
@@ -20,7 +21,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitLdcInsn(string.toString());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index dc8f827..e30d8ce 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -4,8 +4,13 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 public class CfFieldInstruction extends CfInstruction {
 
@@ -26,10 +31,10 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
-    String owner = field.getHolder().getInternalName();
-    String name = field.name.toString();
-    String desc = field.type.toDescriptorString();
+  public void write(MethodVisitor visitor, NamingLens lens) {
+    String owner = lens.lookupInternalName(field.getHolder());
+    String name = lens.lookupName(field).toString();
+    String desc = lens.lookupDescriptor(field.type).toString();
     visitor.visitFieldInsn(opcode, owner, name, desc);
   }
 
@@ -37,4 +42,24 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public void registerUse(UseRegistry registry, DexType clazz) {
+    switch (opcode) {
+      case Opcodes.GETFIELD:
+        registry.registerInstanceFieldRead(field);
+        break;
+      case Opcodes.PUTFIELD:
+        registry.registerInstanceFieldWrite(field);
+        break;
+      case Opcodes.GETSTATIC:
+        registry.registerStaticFieldRead(field);
+        break;
+      case Opcodes.PUTSTATIC:
+        registry.registerStaticFieldWrite(field);
+        break;
+      default:
+        throw new Unreachable("Unexpected opcode " + opcode);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index b589a78..2818e44 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.util.List;
 import org.objectweb.asm.MethodVisitor;
@@ -33,11 +34,11 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     int stackCount = computeStackCount();
-    Object[] stackTypes = computeStackTypes(stackCount);
+    Object[] stackTypes = computeStackTypes(stackCount, lens);
     int localsCount = computeLocalsCount();
-    Object[] localsTypes = computeLocalsTypes(localsCount);
+    Object[] localsTypes = computeLocalsTypes(localsCount, lens);
     visitor.visitFrame(F_NEW, localsCount, localsTypes, stackCount, stackTypes);
   }
 
@@ -49,14 +50,14 @@
     return stack.size();
   }
 
-  private Object[] computeStackTypes(int stackCount) {
+  private Object[] computeStackTypes(int stackCount, NamingLens lens) {
     assert stackCount == stack.size();
     if (stackCount == 0) {
       return null;
     }
     Object[] stackTypes = new Object[stackCount];
     for (int i = 0; i < stackCount; i++) {
-      stackTypes[i] = getType(stack.get(i));
+      stackTypes[i] = getType(stack.get(i), lens);
     }
     return stackTypes;
   }
@@ -78,7 +79,7 @@
     return localsCount;
   }
 
-  private Object[] computeLocalsTypes(int localsCount) {
+  private Object[] computeLocalsTypes(int localsCount, NamingLens lens) {
     if (localsCount == 0) {
       return null;
     }
@@ -87,7 +88,7 @@
     int localIndex = 0;
     for (int i = 0; i <= maxRegister; i++) {
       DexType type = locals.get(i);
-      Object typeOpcode = getType(type);
+      Object typeOpcode = getType(type, lens);
       localsTypes[localIndex++] = typeOpcode;
       if (type != null && isWide(type)) {
         i++;
@@ -96,7 +97,7 @@
     return localsTypes;
   }
 
-  private Object getType(DexType type) {
+  private Object getType(DexType type, NamingLens lens) {
     if (type == null) {
       return Opcodes.TOP;
     }
@@ -105,7 +106,7 @@
     }
     switch (type.toShorty()) {
       case 'L':
-        return type.getInternalName();
+        return lens.lookupInternalName(type);
       case 'I':
         return Opcodes.INTEGER;
       case 'F':
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index c881e7b..47cfed6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -20,7 +21,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitJumpInsn(Opcodes.GOTO, target.getLabel());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
index d928ff7..d39c358 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -60,7 +61,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitJumpInsn(getOpcode(), target.getLabel());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
index dc8b63f..2a076e4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -60,7 +61,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitJumpInsn(getOpcode(), target.getLabel());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index 43b6966..deba4ad 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -5,6 +5,8 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -21,12 +23,17 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
-    visitor.visitTypeInsn(Opcodes.INSTANCEOF, type.getInternalName());
+  public void write(MethodVisitor visitor, NamingLens lens) {
+    visitor.visitTypeInsn(Opcodes.INSTANCEOF, lens.lookupInternalName(type));
   }
 
   @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public void registerUse(UseRegistry registry, DexType clazz) {
+    registry.registerTypeReference(type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 76806f6..04ab380 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -4,11 +4,14 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 
 public abstract class CfInstruction {
 
-  public abstract void write(MethodVisitor visitor);
+  public abstract void write(MethodVisitor visitor, NamingLens lens);
 
   public abstract void print(CfPrinter printer);
 
@@ -19,4 +22,7 @@
     return printer.toString();
   }
 
+  public void registerUse(UseRegistry registry, DexType clazz) {
+    // Intentionally empty.
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 86903fe..82f2438 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -4,7 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -12,11 +17,15 @@
 
   private final DexMethod method;
   private final int opcode;
+  private final boolean itf;
 
-  public CfInvoke(int opcode, DexMethod method) {
+  public CfInvoke(int opcode, DexMethod method, boolean itf) {
     assert Opcodes.INVOKEVIRTUAL <= opcode && opcode <= Opcodes.INVOKEINTERFACE;
+    assert !(opcode == Opcodes.INVOKEVIRTUAL && itf) : "InvokeVirtual on interface type";
+    assert !(opcode == Opcodes.INVOKEINTERFACE && !itf) : "InvokeInterface on class type";
     this.opcode = opcode;
     this.method = method;
+    this.itf = itf;
   }
 
   public DexMethod getMethod() {
@@ -28,16 +37,41 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
-    String owner = method.getHolder().getInternalName();
-    String name = method.name.toString();
-    String desc = method.proto.toDescriptorString();
-    boolean isInterface = opcode == Opcodes.INVOKEINTERFACE;
-    visitor.visitMethodInsn(opcode, owner, name, desc, isInterface);
+  public void write(MethodVisitor visitor, NamingLens lens) {
+    String owner = lens.lookupInternalName(method.getHolder());
+    String name = lens.lookupName(method).toString();
+    String desc = method.proto.toDescriptorString(lens);
+    visitor.visitMethodInsn(opcode, owner, name, desc, itf);
   }
 
   @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public void registerUse(UseRegistry registry, DexType clazz) {
+    switch (opcode) {
+      case Opcodes.INVOKEINTERFACE:
+        registry.registerInvokeInterface(method);
+        break;
+      case Opcodes.INVOKEVIRTUAL:
+        registry.registerInvokeVirtual(method);
+        break;
+      case Opcodes.INVOKESPECIAL:
+        if (method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME)) {
+          registry.registerInvokeDirect(method);
+        } else if (method.holder == clazz) {
+          registry.registerInvokeDirect(method);
+        } else {
+          registry.registerInvokeSuper(method);
+        }
+        break;
+      case Opcodes.INVOKESTATIC:
+        registry.registerInvokeStatic(method);
+        break;
+      default:
+        throw new Unreachable("unknown CfInvoke opcode " + opcode);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index 43e6133..ca8f7cb 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueDouble;
 import com.android.tools.r8.graph.DexValue.DexValueFloat;
@@ -16,6 +17,8 @@
 import com.android.tools.r8.graph.DexValue.DexValueMethodType;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.naming.NamingLens;
 import java.util.List;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.MethodVisitor;
@@ -30,14 +33,14 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     DexMethodHandle bootstrapMethod = callSite.bootstrapMethod;
     List<DexValue> bootstrapArgs = callSite.bootstrapArgs;
     Object[] bsmArgs = new Object[bootstrapArgs.size()];
     for (int i = 0; i < bootstrapArgs.size(); i++) {
-      bsmArgs[i] = decodeBootstrapArgument(bootstrapArgs.get(i));
+      bsmArgs[i] = decodeBootstrapArgument(bootstrapArgs.get(i), lens);
     }
-    Handle bsmHandle = bootstrapMethod.toAsmHandle();
+    Handle bsmHandle = bootstrapMethod.toAsmHandle(lens);
     visitor.visitInvokeDynamicInsn(
         callSite.methodName.toString(),
         callSite.methodProto.toDescriptorString(),
@@ -45,7 +48,7 @@
         bsmArgs);
   }
 
-  private Object decodeBootstrapArgument(DexValue dexValue) {
+  private Object decodeBootstrapArgument(DexValue dexValue, NamingLens lens) {
     if (dexValue instanceof DexValueInt) {
       return ((DexValueInt) dexValue).getValue();
     } else if (dexValue instanceof DexValueLong) {
@@ -61,7 +64,7 @@
     } else if (dexValue instanceof DexValueMethodType) {
       return Type.getMethodType(((DexValueMethodType) dexValue).value.toDescriptorString());
     } else if (dexValue instanceof DexValueMethodHandle) {
-      return ((DexValueMethodHandle) dexValue).value.toAsmHandle();
+      return ((DexValueMethodHandle) dexValue).value.toAsmHandle(lens);
     } else {
       throw new Unreachable(
           "Unsupported bootstrap argument of type " + dexValue.getClass().getSimpleName());
@@ -76,4 +79,9 @@
   public DexCallSite getCallSite() {
     return callSite;
   }
+
+  @Override
+  public void registerUse(UseRegistry registry, DexType clazz) {
+    registry.registerCallSite(callSite);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
index 13d9078..401e4e8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 
@@ -24,7 +25,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitLabel(getLabel());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
index 7d6be37..58c59a5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -37,7 +38,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitVarInsn(getLoadType(), var);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
index 76052c5..b38ef12 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.ir.code.Monitor.Type;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -21,7 +22,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitInsn(type == Type.ENTER ? Opcodes.MONITORENTER : Opcodes.MONITOREXIT);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 6fecd01..a2f3da2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -5,6 +5,8 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 
 public class CfMultiANewArray extends CfInstruction {
@@ -26,12 +28,17 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
-    visitor.visitMultiANewArrayInsn(type.getInternalName(), dimensions);
+  public void write(MethodVisitor visitor, NamingLens lens) {
+    visitor.visitMultiANewArrayInsn(lens.lookupInternalName(type), dimensions);
   }
 
   @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public void registerUse(UseRegistry registry, DexType clazz) {
+    registry.registerTypeReference(type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index 48b2ca3..b7261b6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -5,6 +5,8 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -21,12 +23,17 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
-    visitor.visitTypeInsn(Opcodes.NEW, type.getInternalName());
+  public void write(MethodVisitor visitor, NamingLens lens) {
+    visitor.visitTypeInsn(Opcodes.NEW, lens.lookupInternalName(type));
   }
 
   @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public void registerUse(UseRegistry registry, DexType clazz) {
+    registry.registerNewInstance(type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index 2271cfa..708a414 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -6,9 +6,11 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.DescriptorUtils;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
 
 public class CfNewArray extends CfInstruction {
 
@@ -46,17 +48,20 @@
     }
   }
 
-  private String getElementInternalName() {
+  private String getElementInternalName(NamingLens lens) {
     assert !type.isPrimitiveArrayType();
-    return Type.getType(type.toDescriptorString().substring(1)).getInternalName();
+    String renamedArrayType = lens.lookupDescriptor(type).toString();
+    assert renamedArrayType.charAt(0) == '[';
+    String elementType = renamedArrayType.substring(1);
+    return DescriptorUtils.descriptorToInternalName(elementType);
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     if (type.isPrimitiveArrayType()) {
       visitor.visitIntInsn(Opcodes.NEWARRAY, getPrimitiveTypeCode());
     } else {
-      visitor.visitTypeInsn(Opcodes.ANEWARRAY, getElementInternalName());
+      visitor.visitTypeInsn(Opcodes.ANEWARRAY, getElementInternalName(lens));
     }
   }
 
@@ -64,4 +69,11 @@
   public void print(CfPrinter printer) {
     printer.print(this);
   }
+
+  @Override
+  public void registerUse(UseRegistry registry, DexType clazz) {
+    if (!type.isPrimitiveArrayType()) {
+      registry.registerTypeReference(type);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index e8eb664..2cdd37c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -4,13 +4,14 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
 public class CfNop extends CfInstruction {
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitInsn(Opcodes.NOP);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPop.java b/src/main/java/com/android/tools/r8/cf/code/CfPop.java
index 68cedb9..9f485d2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPop.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -17,7 +18,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitInsn(type.isWide() ? Opcodes.POP2 : Opcodes.POP);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 8004ad8..169d252 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 
 public class CfPosition extends CfInstruction {
@@ -18,7 +19,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitLineNumber(position.line, label.getLabel());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index c426dbd..7993919 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -39,7 +40,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitInsn(getOpcode());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
index b8bfc7d..7d98073 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
@@ -4,13 +4,14 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
 public class CfReturnVoid extends CfInstruction {
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitInsn(Opcodes.RETURN);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index 2526a58..817045c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -37,7 +38,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitVarInsn(getStoreType(), var);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index 6d84c85..f27064c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.naming.NamingLens;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.List;
@@ -44,7 +45,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     Label[] labels = new Label[targets.size()];
     for (int i = 0; i < targets.size(); i++) {
       labels[i] = targets.get(i).getLabel();
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
index cff3a8c..ef73d10 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -4,13 +4,14 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
 public class CfThrow extends CfInstruction {
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitInsn(Opcodes.ATHROW);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfUnop.java b/src/main/java/com/android/tools/r8/cf/code/CfUnop.java
index 6e50bb7..6b4bf76 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfUnop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfUnop.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 
 public class CfUnop extends CfInstruction {
@@ -15,7 +16,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitInsn(this.opcode);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/code/InvokeCustom.java
index 42acdbe..171aef2 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeCustom.java
@@ -4,9 +4,6 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
-import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.UseRegistry;
@@ -43,7 +40,7 @@
 
   @Override
   public void registerUse(UseRegistry registry) {
-    registerCallSite(registry, getCallSite());
+    registry.registerCallSite(getCallSite());
   }
 
   @Override
@@ -60,17 +57,4 @@
   public boolean canThrow() {
     return true;
   }
-
-  static void registerCallSite(UseRegistry registry, DexCallSite callSite) {
-    registry.registerMethodHandle(callSite.bootstrapMethod);
-
-    // Register bootstrap method arguments, only Type and MethodHandle need to be register.
-    for (DexValue arg : callSite.bootstrapArgs) {
-      if (arg instanceof DexValueType) {
-        registry.registerTypeReference(((DexValueType) arg).value);
-      } else if (arg instanceof DexValueMethodHandle) {
-        registry.registerMethodHandle(((DexValueMethodHandle) arg).value);
-      }
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java b/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java
index c98a930..3aa60fc 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java
@@ -44,7 +44,7 @@
 
   @Override
   public void registerUse(UseRegistry registry) {
-    InvokeCustom.registerCallSite(registry, getCallSite());
+    registry.registerCallSite(getCallSite());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 0ccd724..958deb4 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -689,21 +689,23 @@
       AttributesAndAnnotations attrs =
           new AttributesAndAnnotations(type, annotationsDirectory.clazz, dexItemFactory);
 
-      DexClass clazz = classKind.create(
-          type,
-          Kind.DEX,
-          origin,
-          flags,
-          superclass,
-          typeListAt(interfacesOffsets[i]),
-          source,
-          attrs.getEnclosingMethodAttribute(),
-          attrs.getInnerClasses(),
-          attrs.getAnnotations(),
-          staticFields,
-          instanceFields,
-          directMethods,
-          virtualMethods);
+      DexClass clazz =
+          classKind.create(
+              type,
+              Kind.DEX,
+              origin,
+              flags,
+              superclass,
+              typeListAt(interfacesOffsets[i]),
+              source,
+              attrs.getEnclosingMethodAttribute(),
+              attrs.getInnerClasses(),
+              attrs.getAnnotations(),
+              staticFields,
+              instanceFields,
+              directMethods,
+              virtualMethods,
+              dexItemFactory.getSkipNameValidationForTesting());
       classCollection.accept(clazz);  // Update the application object.
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 947f77e..61cd5a7 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -5,18 +5,29 @@
 
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Throw;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
-import java.util.function.Consumer;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 
@@ -113,9 +124,9 @@
     return this;
   }
 
-  public void write(MethodVisitor visitor) {
+  public void write(MethodVisitor visitor, NamingLens namingLens) {
     for (CfInstruction instruction : instructions) {
-      instruction.write(visitor);
+      instruction.write(visitor, namingLens);
     }
     visitor.visitEnd();
     visitor.visitMaxs(maxStack, maxLocals);
@@ -129,14 +140,14 @@
             start,
             end,
             target,
-            guard == DexItemFactory.catchAllType ? null : guard.getInternalName());
+            guard == DexItemFactory.catchAllType ? null : namingLens.lookupInternalName(guard));
       }
     }
     for (LocalVariableInfo localVariable : localVariables) {
       DebugLocalInfo info = localVariable.local;
       visitor.visitLocalVariable(
           info.name.toString(),
-          info.type.toDescriptorString(),
+          namingLens.lookupDescriptor(info.type).toString(),
           info.signature == null ? null : info.signature.toString(),
           localVariable.start.getLabel(),
           localVariable.end.getLabel(),
@@ -155,8 +166,35 @@
   }
 
   @Override
+  public boolean isEmptyVoidMethod() {
+    for (CfInstruction insn : instructions) {
+      if (!(insn instanceof CfReturnVoid)
+          && !(insn instanceof CfLabel)
+          && !(insn instanceof CfPosition)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
   public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
       throws ApiLevelException {
+    if (instructions.size() == 2
+        && instructions.get(0) instanceof CfConstNull
+        && instructions.get(1) instanceof CfThrow) {
+      BasicBlock block = new BasicBlock();
+      block.setNumber(1);
+      Value nullValue = new Value(0, ValueType.OBJECT, null);
+      block.add(new ConstNumber(nullValue, 0L));
+      block.add(new Throw(nullValue));
+      block.close(null);
+      for (Instruction insn : block.getInstructions()) {
+        insn.setPosition(Position.none());
+      }
+      LinkedList<BasicBlock> blocks = new LinkedList<>(Collections.singleton(block));
+      return new IRCode(options, encodedMethod, blocks, null, false);
+    }
     throw new Unimplemented("Converting Java class-file bytecode to IR not yet supported");
   }
 
@@ -171,13 +209,17 @@
   }
 
   @Override
-  public void registerInstructionsReferences(UseRegistry registry) {
-    throw new Unimplemented("Inspecting Java class-file bytecode not yet supported");
-  }
-
-  @Override
-  public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
-    throw new Unimplemented("Inspecting Java class-file bytecode not yet supported");
+  public void registerCodeReferences(UseRegistry registry) {
+    for (CfInstruction instruction : instructions) {
+      instruction.registerUse(registry, method.holder);
+    }
+    for (CfTryCatch tryCatch : tryCatchRanges) {
+      for (DexType guard : tryCatch.guards) {
+        if (guard != DexItemFactory.catchAllType) {
+          registry.registerTypeReference(guard);
+        }
+      }
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/ClassKind.java b/src/main/java/com/android/tools/r8/graph/ClassKind.java
index 755cb7b..94b2076 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassKind.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassKind.java
@@ -27,7 +27,8 @@
         DexEncodedField[] staticFields,
         DexEncodedField[] instanceFields,
         DexEncodedMethod[] directMethods,
-        DexEncodedMethod[] virtualMethods);
+        DexEncodedMethod[] virtualMethods,
+        boolean skipNameValidationForTesting);
   }
 
   private final Factory factory;
@@ -52,7 +53,8 @@
       DexEncodedField[] staticFields,
       DexEncodedField[] instanceFields,
       DexEncodedMethod[] directMethods,
-      DexEncodedMethod[] virtualMethods) {
+      DexEncodedMethod[] virtualMethods,
+      boolean skipNameValidationForTesting) {
     return factory.create(
         type,
         kind,
@@ -67,7 +69,8 @@
         staticFields,
         instanceFields,
         directMethods,
-        virtualMethods);
+        virtualMethods,
+        skipNameValidationForTesting);
   }
 
   public boolean isOfKind(DexClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index b6c1583..52217a3 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.ir.optimize.Outliner.OutlineCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.InternalOptions;
-import java.util.function.Consumer;
 
 public abstract class Code extends CachedHashValueDexItem {
 
@@ -30,9 +29,7 @@
         + getClass().getCanonicalName());
   }
 
-  public abstract void registerInstructionsReferences(UseRegistry registry);
-
-  public abstract void registerCaughtTypes(Consumer<DexType> dexTypeConsumer);
+  public abstract void registerCodeReferences(UseRegistry registry);
 
   @Override
   public abstract String toString();
@@ -84,4 +81,6 @@
   void collectMixedSectionItems(MixedSectionCollection collection) {
     throw new Unreachable();
   }
+
+  public abstract boolean isEmptyVoidMethod();
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCallSite.java b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
index b228098..0c32750 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCallSite.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
@@ -15,7 +15,11 @@
 import java.io.ObjectOutputStream;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
 import java.util.List;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.InvokeDynamicInsnNode;
 
 public final class DexCallSite extends IndexedDexItem {
 
@@ -40,6 +44,37 @@
     this.bootstrapArgs = bootstrapArgs;
   }
 
+  public static DexCallSite fromAsmInvokeDynamic(
+      InvokeDynamicInsnNode insn, JarApplicationReader application, DexType clazz) {
+    return fromAsmInvokeDynamic(application, clazz, insn.name, insn.desc, insn.bsm, insn.bsmArgs);
+  }
+
+  public static DexCallSite fromAsmInvokeDynamic(
+      JarApplicationReader application,
+      DexType clazz,
+      String name,
+      String desc,
+      Handle bsmHandle,
+      Object[] bsmArgs) {
+    // Bootstrap method
+    if (bsmHandle.getTag() != Opcodes.H_INVOKESTATIC
+        && bsmHandle.getTag() != Opcodes.H_NEWINVOKESPECIAL) {
+      // JVM9 §4.7.23 note: Tag must be InvokeStatic or NewInvokeSpecial.
+      throw new Unreachable("Bootstrap handle invalid: tag == " + bsmHandle.getTag());
+    }
+    // Resolve the bootstrap method.
+    DexMethodHandle bootstrapMethod = DexMethodHandle.fromAsmHandle(bsmHandle, application, clazz);
+
+    // Decode static bootstrap arguments
+    List<DexValue> bootstrapArgs = new ArrayList<>();
+    for (Object arg : bsmArgs) {
+      bootstrapArgs.add(DexValue.fromAsmBootstrapArgument(arg, application, clazz));
+    }
+
+    // Construct call site
+    return application.getCallSite(name, desc, bootstrapMethod, bootstrapArgs);
+  }
+
   @Override
   public int computeHashCode() {
     return methodName.hashCode()
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index d6b15eb..e9b9255 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -64,7 +64,8 @@
       EnclosingMethodAttribute enclosingMethod,
       List<InnerClassAttribute> innerClasses,
       DexAnnotationSet annotations,
-      Origin origin) {
+      Origin origin,
+      boolean skipNameValidationForTesting) {
     assert origin != null;
     this.origin = origin;
     this.sourceFile = sourceFile;
@@ -87,7 +88,7 @@
         throw new CompilationError("Interface " + type.toString() + " cannot implement itself");
       }
     }
-    if (!type.descriptor.isValidClassDescriptor()) {
+    if (!skipNameValidationForTesting && !type.descriptor.isValidClassDescriptor()) {
       throw new CompilationError(
           "Class descriptor '"
               + type.descriptor.toString()
@@ -338,9 +339,7 @@
       return superType == null;
     }
     DexEncodedMethod clinit = getClassInitializer();
-    return clinit != null
-        && clinit.getCode() != null
-        && clinit.getCode().asDexCode().isEmptyVoidMethod();
+    return clinit != null && clinit.getCode() != null && clinit.getCode().isEmptyVoidMethod();
   }
 
   public boolean hasNonTrivialClassInitializer() {
@@ -352,11 +351,7 @@
     if (clinit == null || clinit.getCode() == null) {
       return false;
     }
-    if (clinit.getCode().isDexCode()) {
-      return !clinit.getCode().asDexCode().isEmptyVoidMethod();
-    }
-    // For non-dex code we don't try to check the code.
-    return true;
+    return !clinit.getCode().isEmptyVoidMethod();
   }
 
   public boolean hasDefaultInitializer() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
index 6148b00..4308dd4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
@@ -29,7 +29,8 @@
       DexEncodedField[] staticFields,
       DexEncodedField[] instanceFields,
       DexEncodedMethod[] directMethods,
-      DexEncodedMethod[] virtualMethods) {
+      DexEncodedMethod[] virtualMethods,
+      boolean skipNameValidationForTesting) {
     super(
         sourceFile,
         interfaces,
@@ -43,7 +44,8 @@
         enclosingMember,
         innerClasses,
         annotations,
-        origin);
+        origin,
+        skipNameValidationForTesting);
     assert kind == Kind.CF : "Invalid kind " + kind + " for class-path class " + type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 1517dfb..50117c7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -28,7 +28,6 @@
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Consumer;
 
 // DexCode corresponds to code item in dalvik/dex-format.html
 public class DexCode extends Code {
@@ -158,6 +157,7 @@
     return false;
   }
 
+  @Override
   public boolean isEmptyVoidMethod() {
     return instructions.length == 1 && instructions[0] instanceof ReturnVoid;
   }
@@ -190,17 +190,15 @@
   }
 
   @Override
-  public void registerInstructionsReferences(UseRegistry registry) {
+  public void registerCodeReferences(UseRegistry registry) {
     for (Instruction insn : instructions) {
       insn.registerUse(registry);
     }
-  }
-
-  @Override
-  public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
-    for (TryHandler handler : handlers) {
-      for (TypeAddrPair pair : handler.pairs) {
-        dexTypeConsumer.accept(pair.type);
+    if (handlers != null) {
+      for (TryHandler handler : handlers) {
+        for (TypeAddrPair pair : handler.pairs) {
+          registry.registerTypeReference(pair.type);
+        }
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index b7f1802..e18bf8a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -10,6 +10,9 @@
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
 
 import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.code.Const;
 import com.android.tools.r8.code.ConstString;
 import com.android.tools.r8.code.ConstStringJumbo;
@@ -38,7 +41,9 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
@@ -296,13 +301,17 @@
   }
 
   public String descriptor() {
+    return descriptor(NamingLens.getIdentityLens());
+  }
+
+  public String descriptor(NamingLens namingLens) {
     StringBuilder builder = new StringBuilder();
     builder.append("(");
     for (DexType type : method.proto.parameters.values) {
-      builder.append(type.descriptor.toString());
+      builder.append(namingLens.lookupDescriptor(type).toString());
     }
     builder.append(")");
-    builder.append(method.proto.returnType.descriptor.toString());
+    builder.append(namingLens.lookupDescriptor(method.proto.returnType).toString());
     return builder.toString();
   }
 
@@ -360,12 +369,28 @@
         outRegisters, instructions, new DexCode.Try[0], new DexCode.TryHandler[0], null, null);
   }
 
-  public DexEncodedMethod toEmptyThrowingMethod() {
-    Instruction insn[] = {new Const(0, 0), new Throw(0)};
-    DexCode code = generateCodeFromTemplate(1, 0, insn);
-    assert !accessFlags.isAbstract();
+  public DexEncodedMethod toEmptyThrowingMethodDex() {
+    assert !accessFlags.isAbstract() && !accessFlags.isNative();
     Builder builder = builder(this);
-    builder.setCode(code);
+    Instruction insn[] = {new Const(0, 0), new Throw(0)};
+    DexCode emptyThrowingCode = generateCodeFromTemplate(1, 0, insn);
+    builder.setCode(emptyThrowingCode);
+    return builder.build();
+  }
+
+  public DexEncodedMethod toEmptyThrowingMethodCf() {
+    assert !accessFlags.isAbstract() && !accessFlags.isNative();
+    Builder builder = builder(this);
+    CfInstruction insn[] = {new CfConstNull(), new CfThrow()};
+    CfCode emptyThrowingCode =
+        new CfCode(
+            method,
+            1,
+            method.proto.parameters.size() + 1,
+            Arrays.asList(insn),
+            Collections.emptyList(),
+            Collections.emptyList());
+    builder.setCode(emptyThrowingCode);
     return builder.build();
   }
 
@@ -506,21 +531,12 @@
     return !annotations.isEmpty() || !parameterAnnotations.isEmpty();
   }
 
-  public void registerInstructionsReferences(UseRegistry registry) {
+  public void registerCodeReferences(UseRegistry registry) {
     if (code != null) {
       if (Log.ENABLED) {
         Log.verbose(getClass(), "Registering definitions reachable from `%s`.", method);
       }
-      code.registerInstructionsReferences(registry);
-    }
-  }
-
-  public void registerCatchedTypes(Consumer<DexType> dexTypeConsumer) {
-    if (code != null) {
-      if (Log.ENABLED) {
-        Log.verbose(getClass(), "Visiting catched types `%s`.", method);
-      }
-      code.registerCaughtTypes(dexTypeConsumer);
+      code.registerCodeReferences(registry);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index fa3d6ad..ab0d654 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -14,11 +14,11 @@
   public final DexType type;
   public final DexString name;
 
-  DexField(DexType clazz, DexType type, DexString name) {
+  DexField(DexType clazz, DexType type, DexString name, boolean skipNameValidationForTesting) {
     this.clazz = clazz;
     this.type = type;
     this.name = name;
-    if (!name.isValidFieldName()) {
+    if (!skipNameValidationForTesting && !name.isValidFieldName()) {
       throw new CompilationError(
           "Field name '" + name.toString() + "' cannot be represented in dex format.");
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index ff3e30d..30d0dbe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -227,6 +227,16 @@
   public final DexType annotationSynthesizedClassMap =
       createType("Lcom/android/tools/r8/annotations/SynthesizedClassMap;");
 
+  private boolean skipNameValidationForTesting = false;
+
+  public void setSkipNameValidationForTesting(boolean skipNameValidationForTesting) {
+    this.skipNameValidationForTesting = skipNameValidationForTesting;
+  }
+
+  public boolean getSkipNameValidationForTesting() {
+    return skipNameValidationForTesting;
+  }
+
   public synchronized void clearSubtypeInformation() {
     types.values().forEach(DexType::clearSubtypeInformation);
   }
@@ -479,7 +489,7 @@
 
   public DexField createField(DexType clazz, DexType type, DexString name) {
     assert !sorted;
-    DexField field = new DexField(clazz, type, name);
+    DexField field = new DexField(clazz, type, name, skipNameValidationForTesting);
     return canonicalize(fields, field);
   }
 
@@ -510,7 +520,7 @@
 
   public DexMethod createMethod(DexType holder, DexProto proto, DexString name) {
     assert !sorted;
-    DexMethod method = new DexMethod(holder, proto, name);
+    DexMethod method = new DexMethod(holder, proto, name, skipNameValidationForTesting);
     return canonicalize(methods, method);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index 4700553..9644558 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -29,7 +29,8 @@
       DexEncodedField[] staticFields,
       DexEncodedField[] instanceFields,
       DexEncodedMethod[] directMethods,
-      DexEncodedMethod[] virtualMethods) {
+      DexEncodedMethod[] virtualMethods,
+      boolean skipNameValidationForTesting) {
     super(
         sourceFile,
         interfaces,
@@ -43,7 +44,8 @@
         enclosingMember,
         innerClasses,
         annotations,
-        origin);
+        origin,
+        skipNameValidationForTesting);
     // Set all static field values to unknown. We don't want to use the value from the library
     // at compile time, as it can be different at runtime.
     for (DexEncodedField staticField : staticFields) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 621878f..c0c2e27 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -19,11 +19,11 @@
   // Caches used during processing.
   private Map<DexType, DexEncodedMethod> singleTargetCache;
 
-  DexMethod(DexType holder, DexProto proto, DexString name) {
+  DexMethod(DexType holder, DexProto proto, DexString name, boolean skipNameValidationForTesting) {
     this.holder = holder;
     this.proto = proto;
     this.name = name;
-    if (!name.isValidMethodName()) {
+    if (!skipNameValidationForTesting && !name.isValidMethodName()) {
       throw new CompilationError(
           "Method name '" + name.toASCIIString() + "' in class '" + holder.toSourceString() +
               "' cannot be represented in dex format.");
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
index ae8251d..a9e3b8e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.NamingLens;
@@ -77,6 +78,39 @@
       return kind;
     }
 
+    public static MethodHandleType fromAsmHandle(
+        Handle handle, JarApplicationReader application, DexType clazz) {
+      switch (handle.getTag()) {
+        case Opcodes.H_GETFIELD:
+          return MethodHandleType.INSTANCE_GET;
+        case Opcodes.H_GETSTATIC:
+          return MethodHandleType.STATIC_GET;
+        case Opcodes.H_PUTFIELD:
+          return MethodHandleType.INSTANCE_PUT;
+        case Opcodes.H_PUTSTATIC:
+          return MethodHandleType.STATIC_PUT;
+        case Opcodes.H_INVOKESPECIAL:
+          assert !handle.getName().equals(Constants.INSTANCE_INITIALIZER_NAME);
+          assert !handle.getName().equals(Constants.CLASS_INITIALIZER_NAME);
+          DexType owner = application.getTypeFromName(handle.getOwner());
+          if (owner == clazz) {
+            return MethodHandleType.INVOKE_DIRECT;
+          } else {
+            return MethodHandleType.INVOKE_SUPER;
+          }
+        case Opcodes.H_INVOKEVIRTUAL:
+          return MethodHandleType.INVOKE_INSTANCE;
+        case Opcodes.H_INVOKEINTERFACE:
+          return MethodHandleType.INVOKE_INTERFACE;
+        case Opcodes.H_INVOKESTATIC:
+          return MethodHandleType.INVOKE_STATIC;
+        case Opcodes.H_NEWINVOKESPECIAL:
+          return MethodHandleType.INVOKE_CONSTRUCTOR;
+        default:
+          throw new Unreachable("MethodHandle tag is not supported: " + handle.getTag());
+      }
+    }
+
     public boolean isFieldType() {
       return isStaticPut() || isStaticGet() || isInstancePut() || isInstanceGet();
     }
@@ -137,6 +171,16 @@
     this.fieldOrMethod = fieldOrMethod;
   }
 
+  public static DexMethodHandle fromAsmHandle(
+      Handle handle, JarApplicationReader application, DexType clazz) {
+    MethodHandleType methodHandleType = MethodHandleType.fromAsmHandle(handle, application, clazz);
+    Descriptor<? extends DexItem, ? extends Descriptor<?, ?>> descriptor =
+        methodHandleType.isFieldType()
+            ? application.getField(handle.getOwner(), handle.getName(), handle.getDesc())
+            : application.getMethod(handle.getOwner(), handle.getName(), handle.getDesc());
+    return application.getMethodHandle(methodHandleType, descriptor);
+  }
+
   @Override
   public int computeHashCode() {
     return type.hashCode() + fieldOrMethod.computeHashCode() * 7;
@@ -248,16 +292,16 @@
     return sortedCompareTo(other.getSortedIndex());
   }
 
-  public Handle toAsmHandle() {
+  public Handle toAsmHandle(NamingLens lens) {
     String owner;
     String name;
     String desc;
     boolean itf;
     if (isMethodHandle()) {
       DexMethod method = asMethod();
-      owner = method.holder.getInternalName();
-      name = method.name.toString();
-      desc = method.proto.toDescriptorString();
+      owner = lens.lookupInternalName(method.holder);
+      name = lens.lookupName(method).toString();
+      desc = method.proto.toDescriptorString(lens);
       if (method.holder.toDescriptorString().equals("Ljava/lang/invoke/LambdaMetafactory;")) {
         itf = false;
       } else {
@@ -266,9 +310,9 @@
     } else {
       assert isFieldHandle();
       DexField field = asField();
-      owner = field.clazz.getInternalName();
-      name = field.name.toString();
-      desc = field.type.toDescriptorString();
+      owner = lens.lookupInternalName(field.clazz);
+      name = lens.lookupName(field).toString();
+      desc = lens.lookupDescriptor(field.type).toString();
       itf = field.clazz.isInterface();
     }
     return new Handle(getAsmTag(), owner, name, desc, itf);
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index ffff10f..90db1d2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -44,7 +44,8 @@
       DexEncodedField[] staticFields,
       DexEncodedField[] instanceFields,
       DexEncodedMethod[] directMethods,
-      DexEncodedMethod[] virtualMethods) {
+      DexEncodedMethod[] virtualMethods,
+      boolean skipNameValidationForTesting) {
     this(
         type,
         originKind,
@@ -60,6 +61,7 @@
         instanceFields,
         directMethods,
         virtualMethods,
+        skipNameValidationForTesting,
         Collections.emptyList());
   }
 
@@ -78,6 +80,7 @@
       DexEncodedField[] instanceFields,
       DexEncodedMethod[] directMethods,
       DexEncodedMethod[] virtualMethods,
+      boolean skipNameValidationForTesting,
       Collection<DexProgramClass> synthesizedDirectlyFrom) {
     super(
         sourceFile,
@@ -92,7 +95,8 @@
         enclosingMember,
         innerClasses,
         classAnnotations,
-        origin);
+        origin,
+        skipNameValidationForTesting);
     assert classAnnotations != null;
     this.originKind = originKind;
     this.synthesizedFrom = accumulateSynthesizedFrom(new HashSet<>(), synthesizedDirectlyFrom);
@@ -108,8 +112,6 @@
 
   @Override
   public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    assert getEnclosingMethod() == null;
-    assert getInnerClasses().isEmpty();
     if (indexedItems.addClass(this)) {
       type.collectIndexedItems(indexedItems);
       if (superType != null) {
@@ -126,6 +128,12 @@
       if (interfaces != null) {
         interfaces.collectIndexedItems(indexedItems);
       }
+      if (getEnclosingMethod() != null) {
+        getEnclosingMethod().collectIndexedItems(indexedItems);
+      }
+      for (InnerClassAttribute attribute : getInnerClasses()) {
+        attribute.collectIndexedItems(indexedItems);
+      }
       synchronizedCollectAll(indexedItems, staticFields);
       synchronizedCollectAll(indexedItems, instanceFields);
       synchronizedCollectAll(indexedItems, directMethods);
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java
index 69eaeb5..7acab73 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProto.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -93,13 +93,17 @@
   }
 
   public String toDescriptorString() {
+    return toDescriptorString(NamingLens.getIdentityLens());
+  }
+
+  public String toDescriptorString(NamingLens lens) {
     StringBuilder builder = new StringBuilder();
     builder.append("(");
     for (int i = 0; i < parameters.values.length; i++) {
-      builder.append(parameters.values[i].toDescriptorString());
+      builder.append(lens.lookupDescriptor(parameters.values[i]));
     }
     builder.append(")");
-    builder.append(returnType.toDescriptorString());
+    builder.append(lens.lookupDescriptor(returnType));
     return builder.toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index cc1f8e7..820e20b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -463,8 +463,7 @@
   /** Get the fully qualified name using '/' in place of '.', ala the "internal type name" in ASM */
   public String getInternalName() {
     assert isClassType() || isArrayType();
-    String descriptor = toDescriptorString();
-    return isArrayType() ? descriptor : descriptor.substring(1, descriptor.length() - 1);
+    return DescriptorUtils.descriptorToInternalName(toDescriptorString());
   }
 
   public boolean isImmediateSubtypeOf(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index dd08190..f1a0f4d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.utils.EncodedValueUtils;
 import java.util.Arrays;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Type;
 
 public abstract class DexValue extends DexItem {
 
@@ -37,6 +39,40 @@
   public static final byte VALUE_NULL = 0x1e;
   public static final byte VALUE_BOOLEAN = 0x1f;
 
+  public static DexValue fromAsmBootstrapArgument(
+      Object value, JarApplicationReader application, DexType clazz) {
+    if (value instanceof Integer) {
+      return DexValue.DexValueInt.create((Integer) value);
+    } else if (value instanceof Long) {
+      return DexValue.DexValueLong.create((Long) value);
+    } else if (value instanceof Float) {
+      return DexValue.DexValueFloat.create((Float) value);
+    } else if (value instanceof Double) {
+      return DexValue.DexValueDouble.create((Double) value);
+    } else if (value instanceof String) {
+      return new DexValue.DexValueString(application.getString((String) value));
+
+    } else if (value instanceof Type) {
+      Type type = (Type) value;
+      switch (type.getSort()) {
+        case Type.OBJECT:
+          return new DexValue.DexValueType(
+              application.getTypeFromDescriptor(((Type) value).getDescriptor()));
+        case Type.METHOD:
+          return new DexValue.DexValueMethodType(
+              application.getProto(((Type) value).getDescriptor()));
+        default:
+          throw new Unreachable("Type sort is not supported: " + type.getSort());
+      }
+    } else if (value instanceof Handle) {
+      return new DexValue.DexValueMethodHandle(
+          DexMethodHandle.fromAsmHandle((Handle) value, application, clazz));
+    } else {
+      throw new Unreachable(
+          "Unsupported bootstrap static argument of type " + value.getClass().getSimpleName());
+    }
+  }
+
   private static void writeHeader(byte type, int arg, DexOutputBuffer dest) {
     dest.putByte((byte) ((arg << 5) | type));
   }
diff --git a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
index ce741a3..57bc3c4 100644
--- a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.ClassWriter;
 
 /**
@@ -30,14 +32,14 @@
     this.enclosingMethod = enclosingMethod;
   }
 
-  public void write(ClassWriter writer) {
+  public void write(ClassWriter writer, NamingLens lens) {
     if (enclosingMethod != null) {
       writer.visitOuterClass(
-          enclosingMethod.getHolder().getInternalName(),
-          enclosingMethod.name.toString(),
-          enclosingMethod.proto.toDescriptorString());
+          lens.lookupInternalName(enclosingMethod.getHolder()),
+          lens.lookupName(enclosingMethod).toString(),
+          enclosingMethod.proto.toDescriptorString(lens));
     } else {
-      writer.visitOuterClass(enclosingClass.getInternalName(), null, null);
+      writer.visitOuterClass(lens.lookupInternalName(enclosingClass), null, null);
     }
   }
 
@@ -61,4 +63,13 @@
         enclosingClass == ((EnclosingMethodAttribute) obj).enclosingClass &&
         enclosingMethod == ((EnclosingMethodAttribute) obj).enclosingMethod;
   }
+
+  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+    if (enclosingClass != null) {
+      enclosingClass.collectIndexedItems(indexedItems);
+    }
+    if (enclosingMethod != null) {
+      enclosingMethod.collectIndexedItems(indexedItems);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
index 8a158de..cd62ef3 100644
--- a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.ClassWriter;
 
 /** Representation of an entry in the Java InnerClasses attribute table. */
@@ -58,11 +60,23 @@
     return innerName;
   }
 
-  public void write(ClassWriter writer) {
+  public void write(ClassWriter writer, NamingLens lens) {
+    String internalName = lens.lookupInternalName(inner);
+    String simpleName = lens.lookupSimpleName(inner, innerName);
     writer.visitInnerClass(
-        inner.getInternalName(),
-        outer == null ? null : outer.getInternalName(),
-        innerName == null ? null : innerName.toString(),
+        internalName,
+        outer == null ? null : lens.lookupInternalName(outer),
+        innerName == null ? null : simpleName,
         access);
   }
+
+  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+    inner.collectIndexedItems(indexedItems);
+    if (outer != null) {
+      outer.collectIndexedItems(indexedItems);
+    }
+    if (innerName != null) {
+      innerName.collectIndexedItems(indexedItems);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 945da10..32cc10b 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -251,7 +251,8 @@
               staticFields.toArray(new DexEncodedField[staticFields.size()]),
               instanceFields.toArray(new DexEncodedField[instanceFields.size()]),
               directMethods.toArray(new DexEncodedMethod[directMethods.size()]),
-              virtualMethods.toArray(new DexEncodedMethod[virtualMethods.size()]));
+              virtualMethods.toArray(new DexEncodedMethod[virtualMethods.size()]),
+              application.getFactory().getSkipNameValidationForTesting());
       if (clazz.isProgramClass()) {
         context.owner = clazz.asProgramClass();
         clazz.asProgramClass().setClassFileVersion(version);
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 8933353..8c1a6c5 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -18,13 +18,16 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
-import java.util.function.Consumer;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.commons.JSRInlinerAdapter;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.LabelNode;
+import org.objectweb.asm.tree.LineNumberNode;
 import org.objectweb.asm.tree.MethodNode;
 import org.objectweb.asm.util.Textifier;
 import org.objectweb.asm.util.TraceMethodVisitor;
@@ -99,6 +102,19 @@
   }
 
   @Override
+  public boolean isEmptyVoidMethod() {
+    for (Iterator<AbstractInsnNode> it = getNode().instructions.iterator(); it.hasNext(); ) {
+      AbstractInsnNode insn = it.next();
+      if (insn.getType() != Opcodes.RETURN
+          && !(insn instanceof LabelNode)
+          && !(insn instanceof LineNumberNode)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
   public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
       throws ApiLevelException {
     triggerDelayedParsingIfNeccessary();
@@ -155,16 +171,12 @@
   }
 
   @Override
-  public void registerInstructionsReferences(UseRegistry registry) {
+  public void registerCodeReferences(UseRegistry registry) {
     triggerDelayedParsingIfNeccessary();
     node.instructions.accept(
         new JarRegisterEffectsVisitor(method.getHolder(), registry, application));
-  }
-
-  @Override
-  public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
     node.tryCatchBlocks.forEach(tryCatchBlockNode ->
-        dexTypeConsumer.accept(application.getTypeFromDescriptor(
+        registry.registerTypeReference(application.getTypeFromDescriptor(
             DescriptorUtils.getDescriptorFromClassBinaryName(tryCatchBlockNode.type))));
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 48ef91d..49145fa 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -3,6 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.graph.DexValue.DexValueDouble;
+import com.android.tools.r8.graph.DexValue.DexValueFloat;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueLong;
+import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueMethodType;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+
 public abstract class UseRegistry {
 
   public abstract boolean registerInvokeVirtual(DexMethod method);
@@ -73,4 +82,33 @@
         throw new AssertionError();
     }
   }
+
+  public void registerCallSite(DexCallSite callSite) {
+    registerMethodHandle(callSite.bootstrapMethod);
+
+    // Register bootstrap method arguments.
+    // Only Type, MethodHandle, and MethodType need to be registered.
+    for (DexValue arg : callSite.bootstrapArgs) {
+      if (arg instanceof DexValueType) {
+        registerTypeReference(((DexValueType) arg).value);
+      } else if (arg instanceof DexValueMethodHandle) {
+        registerMethodHandle(((DexValueMethodHandle) arg).value);
+      } else if (arg instanceof DexValueMethodType) {
+        registerProto(((DexValueMethodType) arg).value);
+      } else {
+        assert (arg instanceof DexValueInt)
+            || (arg instanceof DexValueLong)
+            || (arg instanceof DexValueFloat)
+            || (arg instanceof DexValueDouble)
+            || (arg instanceof DexValueString);
+      }
+    }
+  }
+
+  public void registerProto(DexProto proto) {
+    registerTypeReference(proto.returnType);
+    for (DexType type : proto.parameters.values) {
+      registerTypeReference(type);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 58bc675..ee2c6ab 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -242,7 +242,20 @@
         if (indexOfNewBlock >= successors.size() - 2 && indexOfOldBlock >= successors.size() - 2) {
           // New and old are true target and fallthrough, replace last instruction with a goto.
           Instruction instruction = getInstructions().removeLast();
-          for (Value value : instruction.inValues()) {
+          // Iterate in reverse order to ensure that POP instructions are inserted in correct order.
+          for (int i = instruction.inValues().size() - 1; i >= 0; i--) {
+            Value value = instruction.inValues().get(i);
+            if (value instanceof StackValue) {
+              if (value.definition.isLoad()) {
+                assert value.definition.getBlock() == this;
+                removeInstruction(value.definition);
+              } else {
+                Pop pop = new Pop((StackValue) value);
+                pop.setBlock(this);
+                pop.setPosition(instruction.getPosition());
+                getInstructions().addLast(pop);
+              }
+            }
             if (value.hasUsersInfo()) {
               value.removeUser(instruction);
             }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index ff8bd02..bf62fed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -45,7 +45,7 @@
 
   public final boolean hasDebugPositions;
 
-  private final InternalOptions options;
+  public final InternalOptions options;
 
   public IRCode(
       InternalOptions options,
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index cdca04e..04e7bb6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -78,7 +78,13 @@
   protected void addInValue(Value value) {
     if (value != null) {
       inValues.add(value);
-      value.addUser(this);
+      // TODO(mathiasr): We insert Pop instructions in BasicBlock.replaceSuccessor() after clearing
+      // user info. If we change the CF-specific IR to avoid having StackValues as in/out-values,
+      // we can remove this 'if'.
+      assert value.hasUsersInfo() || isPop();
+      if (value.hasUsersInfo()) {
+        value.addUser(this);
+      }
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 7f24908..af6d5a3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -39,15 +39,20 @@
 
   public static Invoke create(
       Type type, DexItem target, DexProto proto, Value result, List<Value> arguments) {
+    return create(type, target, proto, result, arguments, false);
+  }
+
+  public static Invoke create(
+      Type type, DexItem target, DexProto proto, Value result, List<Value> arguments, boolean itf) {
     switch (type) {
       case DIRECT:
-        return new InvokeDirect((DexMethod) target, result, arguments);
+        return new InvokeDirect((DexMethod) target, result, arguments, itf);
       case INTERFACE:
         return new InvokeInterface((DexMethod) target, result, arguments);
       case STATIC:
-        return new InvokeStatic((DexMethod) target, result, arguments);
+        return new InvokeStatic((DexMethod) target, result, arguments, itf);
       case SUPER:
-        return new InvokeSuper((DexMethod) target, result, arguments);
+        return new InvokeSuper((DexMethod) target, result, arguments, itf);
       case VIRTUAL:
         return new InvokeVirtual((DexMethod) target, result, arguments);
       case NEW_ARRAY:
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index 7338a2b..fa85845 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -21,8 +21,15 @@
 
 public class InvokeDirect extends InvokeMethodWithReceiver {
 
+  private final boolean itf;
+
   public InvokeDirect(DexMethod target, Value result, List<Value> arguments) {
+    this(target, result, arguments, false);
+  }
+
+  public InvokeDirect(DexMethod target, Value result, List<Value> arguments, boolean itf) {
     super(target, result, arguments);
+    this.itf = itf;
   }
 
   @Override
@@ -114,6 +121,6 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(new CfInvoke(Opcodes.INVOKESPECIAL, getInvokedMethod()));
+    builder.add(new CfInvoke(Opcodes.INVOKESPECIAL, getInvokedMethod(), itf));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index 60aee95..d29e3eb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -103,6 +103,6 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(new CfInvoke(Opcodes.INVOKEINTERFACE, getInvokedMethod()));
+    builder.add(new CfInvoke(Opcodes.INVOKEINTERFACE, getInvokedMethod(), true));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 01cd5f5..0e63cc7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -90,7 +90,7 @@
     // To translate InvokePolymorphic back into InvokeVirtual, use the original prototype
     // that is stored in getProto().
     DexMethod method = factory.createMethod(dexMethod.holder, getProto(), dexMethod.name);
-    builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method));
+    builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method, false));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index e628f39..896fca9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -22,8 +22,15 @@
 
 public class InvokeStatic extends InvokeMethod {
 
+  private final boolean itf;
+
   public InvokeStatic(DexMethod target, Value result, List<Value> arguments) {
+    this(target, result, arguments, false);
+  }
+
+  public InvokeStatic(DexMethod target, Value result, List<Value> arguments, boolean itf) {
     super(target, result, arguments);
+    this.itf = itf;
   }
 
   @Override
@@ -109,7 +116,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(new CfInvoke(Opcodes.INVOKESTATIC, getInvokedMethod()));
+    builder.add(new CfInvoke(Opcodes.INVOKESTATIC, getInvokedMethod(), itf));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index 9e9a096..c761184 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -22,8 +22,11 @@
 
 public class InvokeSuper extends InvokeMethodWithReceiver {
 
-  public InvokeSuper(DexMethod target, Value result, List<Value> arguments) {
+  public final boolean itf;
+
+  public InvokeSuper(DexMethod target, Value result, List<Value> arguments, boolean itf) {
     super(target, result, arguments);
+    this.itf = itf;
   }
 
   @Override
@@ -76,7 +79,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(new CfInvoke(Opcodes.INVOKESPECIAL, getInvokedMethod()));
+    builder.add(new CfInvoke(Opcodes.INVOKESPECIAL, getInvokedMethod(), itf));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 8a41c4e..5ef74b8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -103,6 +103,6 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, getInvokedMethod()));
+    builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, getInvokedMethod(), false));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index f863778..8330e06 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -144,7 +144,7 @@
       for (DexEncodedMethod method : clazz.allMethodsSorted()) {
         Node node = graph.ensureMethodNode(method);
         InvokeExtractor extractor = new InvokeExtractor(appInfo, graphLense, node, graph);
-        method.registerInstructionsReferences(extractor);
+        method.registerCodeReferences(extractor);
       }
     }
     assert allMethodsExists(application, graph);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 60f9c79..933f66d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -11,12 +11,10 @@
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfTryCatch;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
-import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -106,27 +104,23 @@
     return factory;
   }
 
-  public Code build(CodeRewriter rewriter, InternalOptions options, AppInfoWithSubtyping appInfo) {
-    try {
-      types = new TypeVerificationHelper(code, factory, appInfo).computeVerificationTypes();
-      splitExceptionalBlocks();
-      LoadStoreHelper loadStoreHelper = new LoadStoreHelper(code, types);
-      loadStoreHelper.insertLoadsAndStores();
-      DeadCodeRemover.removeDeadCode(code, rewriter, options);
-      removeUnneededLoadsAndStores();
-      registerAllocator = new CfRegisterAllocator(code, options);
-      registerAllocator.allocateRegisters();
-      loadStoreHelper.insertPhiMoves(registerAllocator);
-      CodeRewriter.collapsTrivialGotos(method, code);
-      int instructionTableCount =
-          DexBuilder.instructionNumberToIndex(code.numberRemainingInstructions());
-      DexBuilder.removeRedundantDebugPositions(code, instructionTableCount);
-      CfCode code = buildCfCode();
-      return code;
-    } catch (Unimplemented e) {
-      System.out.println("Incomplete CF construction: " + e.getMessage());
-      return method.getCode().asJarCode();
-    }
+  public CfCode build(
+      CodeRewriter rewriter, InternalOptions options, AppInfoWithSubtyping appInfo) {
+    types = new TypeVerificationHelper(code, factory, appInfo).computeVerificationTypes();
+    splitExceptionalBlocks();
+    LoadStoreHelper loadStoreHelper = new LoadStoreHelper(code, types);
+    loadStoreHelper.insertLoadsAndStores();
+    DeadCodeRemover.removeDeadCode(code, rewriter, options);
+    removeUnneededLoadsAndStores();
+    registerAllocator = new CfRegisterAllocator(code, options);
+    registerAllocator.allocateRegisters();
+    loadStoreHelper.insertPhiMoves(registerAllocator);
+    CodeRewriter.collapsTrivialGotos(method, code);
+    int instructionTableCount =
+        DexBuilder.instructionNumberToIndex(code.numberRemainingInstructions());
+    DexBuilder.removeRedundantDebugPositions(code, instructionTableCount);
+    CfCode code = buildCfCode();
+    return code;
   }
 
   // Split all blocks with throwing instructions and exceptional edges such that any non-throwing
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 20051f9..5bea64e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -327,7 +327,8 @@
     targets.put(INITIAL_BLOCK_OFFSET, new BlockInfo());
 
     // Process reachable code paths starting from instruction 0.
-    processedInstructions = new boolean[source.instructionCount()];
+    int instCount = source.instructionCount();
+    processedInstructions = new boolean[instCount];
     traceBlocksWorklist.add(0);
     while (!traceBlocksWorklist.isEmpty()) {
       int startOfBlockOffset = traceBlocksWorklist.remove();
@@ -337,17 +338,17 @@
         continue;
       }
       // Process each instruction until the block is closed.
-      for (int index = startOfBlockIndex; index < source.instructionCount(); ++index) {
+      for (int index = startOfBlockIndex; index < instCount; ++index) {
         markIndexProcessed(index);
         int closedAt = source.traceInstruction(index, this);
         if (closedAt != -1) {
-          if (closedAt + 1 < source.instructionCount()) {
+          if (closedAt + 1 < instCount) {
             ensureBlockWithoutEnqueuing(source.instructionOffset(closedAt + 1));
           }
           break;
         }
         // If the next instruction starts a block, fall through to it.
-        if (index + 1 < source.instructionCount()) {
+        if (index + 1 < instCount) {
           int nextOffset = source.instructionOffset(index + 1);
           if (targets.get(nextOffset) != null) {
             ensureNormalSuccessorBlock(startOfBlockOffset, nextOffset);
@@ -491,7 +492,8 @@
         continue;
       }
       // Build IR for each dex instruction in the block.
-      for (int i = item.firstInstructionIndex; i < source.instructionCount(); ++i) {
+      int instCount = source.instructionCount();
+      for (int i = item.firstInstructionIndex; i < instCount; ++i) {
         if (currentBlock == null) {
           break;
         }
@@ -997,7 +999,8 @@
     add(instruction);
   }
 
-  public void addInvoke(Type type, DexItem item, DexProto callSiteProto, List<Value> arguments)
+  public void addInvoke(
+      Type type, DexItem item, DexProto callSiteProto, List<Value> arguments, boolean itf)
       throws ApiLevelException {
     if (type == Invoke.Type.POLYMORPHIC) {
       assert item instanceof DexMethod;
@@ -1014,7 +1017,12 @@
             null /* sourceString */);
       }
     }
-    add(Invoke.create(type, item, callSiteProto, null, arguments));
+    add(Invoke.create(type, item, callSiteProto, null, arguments, itf));
+  }
+
+  public void addInvoke(Type type, DexItem item, DexProto callSiteProto, List<Value> arguments)
+      throws ApiLevelException {
+    addInvoke(type, item, callSiteProto, arguments, false);
   }
 
   public void addInvoke(
@@ -1024,12 +1032,23 @@
       List<ValueType> types,
       List<Integer> registers)
       throws ApiLevelException {
+    addInvoke(type, item, callSiteProto, types, registers, false);
+  }
+
+  public void addInvoke(
+      Invoke.Type type,
+      DexItem item,
+      DexProto callSiteProto,
+      List<ValueType> types,
+      List<Integer> registers,
+      boolean itf)
+      throws ApiLevelException {
     assert types.size() == registers.size();
     List<Value> arguments = new ArrayList<>(types.size());
     for (int i = 0; i < types.size(); i++) {
       arguments.add(readRegister(registers.get(i), types.get(i)));
     }
-    addInvoke(type, item, callSiteProto, arguments);
+    addInvoke(type, item, callSiteProto, arguments, itf);
   }
 
   public void addInvokeCustomRegisters(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 0f0b44a..2b6ad47 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexApplication;
@@ -711,9 +712,7 @@
   private void finalizeToCf(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     assert !method.getCode().isDexCode();
     CfBuilder builder = new CfBuilder(method, code, options.itemFactory);
-    // TODO(zerny): Change the return type of CfBuilder::build CfCode once complete.
-    Code result = builder.build(codeRewriter, options, appInfo.withSubtyping());
-    assert result.isCfCode() || result.isJarCode();
+    CfCode result = builder.build(codeRewriter, options, appInfo.withSubtyping());
     method.setCode(result);
     markProcessed(method, code, feedback);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index b584448..2298bc6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -8,17 +8,13 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DebugLocalInfo;
-import com.android.tools.r8.graph.Descriptor;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
-import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.JarApplicationReader;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Cmp.Bias;
@@ -428,11 +424,12 @@
     for (JarStateWorklistItem item = worklist.poll(); item != null; item = worklist.poll()) {
       state.restoreState(item.instructionIndex);
       // Iterate each of the instructions in the block to compute the outgoing JarState.
-      for (int i = item.instructionIndex; i <= instructionCount(); ++i) {
+      int instCount = instructionCount();
+      for (int i = item.instructionIndex; i <= instCount; ++i) {
         // If we are at the end of the instruction stream or if we have reached the start
         // of a new block, propagate the state to all successors and add the ones
         // that changed to the worklist.
-        if (i == instructionCount() || (i != item.instructionIndex && CFG.containsKey(i))) {
+        if (i == instCount || (i != item.instructionIndex && CFG.containsKey(i))) {
           item.blockInfo.normalSuccessors.iterator().forEachRemaining(offset -> {
             if (state.recordStateForTarget(offset)) {
               if (offset >= 0) {
@@ -2606,7 +2603,7 @@
             }
             callSiteProto = application.getProto(insn.desc);
           }
-          builder.addInvoke(invokeType, targetMethod, callSiteProto, types, registers);
+          builder.addInvoke(invokeType, targetMethod, callSiteProto, types, registers, insn.itf);
         });
   }
 
@@ -2654,104 +2651,13 @@
   }
 
   private void build(InvokeDynamicInsnNode insn, IRBuilder builder) throws ApiLevelException {
-    // Bootstrap method
-    Handle bsmHandle = insn.bsm;
-    if (bsmHandle.getTag() != Opcodes.H_INVOKESTATIC &&
-        bsmHandle.getTag() != Opcodes.H_NEWINVOKESPECIAL) {
-      // JVM9 §4.7.23 note: Tag must be InvokeStatic or NewInvokeSpecial.
-      throw new Unreachable("Bootstrap handle invalid: tag == " + bsmHandle.getTag());
-    }
-    // Resolve the bootstrap method.
-    DexMethodHandle bootstrapMethod = getMethodHandle(application, bsmHandle);
-
-    // Decode static bootstrap arguments
-    List<DexValue> bootstrapArgs = new ArrayList<>();
-    for (Object arg : insn.bsmArgs) {
-      bootstrapArgs.add(decodeBootstrapArgument(arg));
-    }
-
-    // Construct call site
-    DexCallSite callSite = application
-        .getCallSite(insn.name, insn.desc, bootstrapMethod, bootstrapArgs);
+    DexCallSite callSite = DexCallSite.fromAsmInvokeDynamic(insn, application, clazz);
 
     buildInvoke(insn.desc, null /* Not needed */,
         false /* Receiver is passed explicitly */, builder,
         (types, registers) -> builder.addInvokeCustom(callSite, types, registers));
   }
 
-  private DexValue decodeBootstrapArgument(Object value) {
-    if (value instanceof Integer) {
-      return DexValue.DexValueInt.create((Integer) value);
-    } else if (value instanceof Long) {
-      return DexValue.DexValueLong.create((Long) value);
-    } else if (value instanceof Float) {
-      return DexValue.DexValueFloat.create((Float) value);
-    } else if (value instanceof Double) {
-      return DexValue.DexValueDouble.create((Double) value);
-    } else if (value instanceof String) {
-      return new DexValue.DexValueString(application.getString((String) value));
-
-    } else if (value instanceof Type) {
-      Type type = (Type) value;
-      switch (type.getSort()) {
-        case Type.OBJECT:
-          return new DexValue.DexValueType(
-              application.getTypeFromDescriptor(((Type) value).getDescriptor()));
-        case Type.METHOD:
-          return new DexValue.DexValueMethodType(
-              application.getProto(((Type) value).getDescriptor()));
-        default:
-          throw new Unreachable("Type sort is not supported: " + type.getSort());
-      }
-    } else if (value instanceof Handle) {
-      return new DexValue.DexValueMethodHandle(getMethodHandle(application, (Handle) value));
-    } else {
-      throw new Unreachable(
-          "Unsupported bootstrap static argument of type " + value.getClass().getSimpleName());
-    }
-  }
-
-  private DexMethodHandle getMethodHandle(JarApplicationReader application, Handle handle) {
-    MethodHandleType methodHandleType = getMethodHandleType(handle);
-    Descriptor<? extends DexItem, ? extends Descriptor<?,?>> descriptor =
-        methodHandleType.isFieldType()
-            ? application.getField(handle.getOwner(), handle.getName(), handle.getDesc())
-            : application.getMethod(handle.getOwner(), handle.getName(), handle.getDesc());
-    return application.getMethodHandle(methodHandleType, descriptor);
-  }
-
-  private MethodHandleType getMethodHandleType(Handle handle) {
-    switch (handle.getTag()) {
-      case Opcodes.H_GETFIELD:
-        return MethodHandleType.INSTANCE_GET;
-      case Opcodes.H_GETSTATIC:
-        return MethodHandleType.STATIC_GET;
-      case Opcodes.H_PUTFIELD:
-        return MethodHandleType.INSTANCE_PUT;
-      case Opcodes.H_PUTSTATIC:
-        return MethodHandleType.STATIC_PUT;
-      case Opcodes.H_INVOKESPECIAL:
-        assert !handle.getName().equals(Constants.INSTANCE_INITIALIZER_NAME);
-        assert !handle.getName().equals(Constants.CLASS_INITIALIZER_NAME);
-        DexType owner = application.getTypeFromName(handle.getOwner());
-        if (owner == clazz) {
-          return MethodHandleType.INVOKE_DIRECT;
-        } else {
-          return MethodHandleType.INVOKE_SUPER;
-        }
-      case Opcodes.H_INVOKEVIRTUAL:
-        return MethodHandleType.INVOKE_INSTANCE;
-      case Opcodes.H_INVOKEINTERFACE:
-        return MethodHandleType.INVOKE_INTERFACE;
-      case Opcodes.H_INVOKESTATIC:
-        return MethodHandleType.INVOKE_STATIC;
-      case Opcodes.H_NEWINVOKESPECIAL:
-        return MethodHandleType.INVOKE_CONSTRUCTOR;
-      default:
-        throw new Unreachable("MethodHandle tag is not supported: " + handle.getTag());
-    }
-  }
-
   private void build(JumpInsnNode insn, IRBuilder builder) {
     processLocalVariablesAtControlEdge(insn, builder);
     int[] targets = getTargets(insn);
@@ -2844,7 +2750,7 @@
     } else if (insn.cst instanceof Handle) {
       Handle handle = (Handle) insn.cst;
       int dest = state.push(METHOD_HANDLE_TYPE);
-      builder.addConstMethodHandle(dest, getMethodHandle(application, handle));
+      builder.addConstMethodHandle(dest, DexMethodHandle.fromAsmHandle(handle, application, clazz));
     } else {
       throw new CompilationError("Unsupported constant: " + insn.cst.toString());
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 8c17d91..15a90e9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -62,8 +62,10 @@
         newFlags.unsetBridge();
         newFlags.setStatic();
         DexCode dexCode = code.asDexCode();
-        // TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
-        dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(null));
+        // We cannot name the parameter "this" because the debugger may omit it due to the method
+        // actually being static. Instead we prepend it with a special character.
+        dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(
+            rewriter.factory.createString("-this")));
         assert (dexCode.getDebugInfo() == null)
             || (companionMethod.getArity() == dexCode.getDebugInfo().parameters.length);
 
@@ -172,6 +174,7 @@
             DexEncodedField.EMPTY_ARRAY,
             companionMethods.toArray(new DexEncodedMethod[companionMethods.size()]),
             DexEncodedMethod.EMPTY_ARRAY,
+            rewriter.factory.getSkipNameValidationForTesting(),
             Collections.singletonList(iface));
     companionClasses.put(iface, companionClass);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 05a4d56..82958da 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -139,6 +139,7 @@
         synthesizeInstanceFields(),
         synthesizeDirectMethods(),
         synthesizeVirtualMethods(),
+        rewriter.factory.getSkipNameValidationForTesting(),
         synthesizedFrom);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 30e9bae4..3feb59a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1838,8 +1838,20 @@
 
   private static class CSEExpressionEquivalence extends Equivalence<Instruction> {
 
+    private final IRCode code;
+
+    private CSEExpressionEquivalence(IRCode code) {
+      this.code = code;
+    }
+
     @Override
     protected boolean doEquivalent(Instruction a, Instruction b) {
+      // Some Dalvik VMs incorrectly handle Cmp instructions which leads to a requirement
+      // that we do not perform common subexpression elimination for them. See comment on
+      // canHaveCmpLongBug for details.
+      if (a.isCmp() && code.options.canHaveCmpLongBug()) {
+        return false;
+      }
       // Note that we don't consider positions because CSE can at most remove an instruction.
       if (a.getClass() != b.getClass() || !a.identicalNonValueNonPositionParts(b)) {
         return false;
@@ -1923,7 +1935,7 @@
   public void commonSubexpressionElimination(IRCode code) {
     final ListMultimap<Wrapper<Instruction>, Value> instructionToValue = ArrayListMultimap.create();
     final DominatorTree dominatorTree = new DominatorTree(code);
-    final CSEExpressionEquivalence equivalence = new CSEExpressionEquivalence();
+    final CSEExpressionEquivalence equivalence = new CSEExpressionEquivalence(code);
 
     for (int i = 0; i < dominatorTree.getSortedBlocks().length; i++) {
       BasicBlock block = dominatorTree.getSortedBlocks()[i];
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 536e4b7..dba6183 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -62,7 +62,6 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.function.Consumer;
 
 public class Outliner {
 
@@ -997,6 +996,11 @@
     }
 
     @Override
+    public boolean isEmptyVoidMethod() {
+      return false;
+    }
+
+    @Override
     public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
         throws ApiLevelException {
       OutlineSourceCode source = new OutlineSourceCode(outline);
@@ -1010,12 +1014,7 @@
     }
 
     @Override
-    public void registerInstructionsReferences(UseRegistry registry) {
-      throw new Unreachable();
-    }
-
-    @Override
-    public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
+    public void registerCodeReferences(UseRegistry registry) {
       throw new Unreachable();
     }
 
@@ -1083,8 +1082,8 @@
             DexEncodedField.EMPTY_ARRAY, // Static fields.
             DexEncodedField.EMPTY_ARRAY, // Instance fields.
             direct,
-            DexEncodedMethod.EMPTY_ARRAY // Virtual methods.
-            );
+            DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
+            options.itemFactory.getSkipNameValidationForTesting());
 
     return clazz;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
index 5127632..10dc013 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
@@ -48,7 +48,8 @@
         buildStaticFields(),
         buildInstanceFields(),
         buildDirectMethods(),
-        buildVirtualMethods());
+        buildVirtualMethods(),
+        factory.getSkipNameValidationForTesting());
   }
 
   protected abstract DexType getSuperClassType();
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index 62238c3..53bc57e 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -33,6 +32,11 @@
   }
 
   @Override
+  public boolean isEmptyVoidMethod() {
+    return false;
+  }
+
+  @Override
   public final IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
       throws ApiLevelException {
     return new IRBuilder(encodedMethod, sourceCode, options).build();
@@ -48,18 +52,11 @@
   }
 
   @Override
-  public void registerInstructionsReferences(UseRegistry registry) {
+  public void registerCodeReferences(UseRegistry registry) {
     registryCallback.accept(registry);
   }
 
   @Override
-  public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
-    // Support for synthesized code with catch handler is not implemented.
-    // Let's check that we're in a well known where no catch handler is possible.
-    assert sourceCode.instructionCount() == 1 || sourceCode instanceof SyntheticSourceCode;
-  }
-
-  @Override
   protected final int computeHashCode() {
     return sourceCode.hashCode();
   }
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 5eb2bb7..c8be00e 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -108,25 +108,27 @@
     writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, null);
     int version = clazz.getClassFileVersion();
     int access = clazz.accessFlags.getAsCfAccessFlags();
-    String desc = clazz.type.toDescriptorString();
-    String name = clazz.type.getInternalName();
+    String desc = namingLens.lookupDescriptor(clazz.type).toString();
+    String name = namingLens.lookupInternalName(clazz.type);
     String signature = getSignature(clazz.annotations);
     String superName =
-        clazz.type == options.itemFactory.objectType ? null : clazz.superType.getInternalName();
+        clazz.type == options.itemFactory.objectType
+            ? null
+            : namingLens.lookupInternalName(clazz.superType);
     String[] interfaces = new String[clazz.interfaces.values.length];
     for (int i = 0; i < clazz.interfaces.values.length; i++) {
-      interfaces[i] = clazz.interfaces.values[i].getInternalName();
+      interfaces[i] = namingLens.lookupInternalName(clazz.interfaces.values[i]);
     }
     writer.visit(version, access, name, signature, superName, interfaces);
     writeAnnotations(writer::visitAnnotation, clazz.annotations.annotations);
     ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations);
 
     if (clazz.getEnclosingMethod() != null) {
-      clazz.getEnclosingMethod().write(writer);
+      clazz.getEnclosingMethod().write(writer, namingLens);
     }
 
     for (InnerClassAttribute entry : clazz.getInnerClasses()) {
-      entry.write(writer);
+      entry.write(writer, namingLens);
     }
 
     for (DexEncodedField field : clazz.staticFields()) {
@@ -175,6 +177,7 @@
     if (value == null) {
       return null;
     }
+    // Signature has already been minified by ClassNameMinifier.renameTypesInGenericSignatures().
     DexValue[] parts = value.getValues();
     StringBuilder res = new StringBuilder();
     for (DexValue part : parts) {
@@ -208,7 +211,7 @@
     DexValue[] values = value.getValues();
     String[] res = new String[values.length];
     for (int i = 0; i < values.length; i++) {
-      res[i] = ((DexValueType) values[i]).value.getInternalName();
+      res[i] = namingLens.lookupInternalName(((DexValueType) values[i]).value);
     }
     return res;
   }
@@ -222,8 +225,8 @@
 
   private void writeField(DexEncodedField field, ClassWriter writer) {
     int access = field.accessFlags.getAsCfAccessFlags();
-    String name = field.field.name.toString();
-    String desc = field.field.type.toDescriptorString();
+    String name = namingLens.lookupName(field.field).toString();
+    String desc = namingLens.lookupDescriptor(field.field.type).toString();
     String signature = getSignature(field.annotations);
     Object value = getStaticValue(field);
     FieldVisitor visitor = writer.visitField(access, name, desc, signature, value);
@@ -234,8 +237,8 @@
   private void writeMethod(
       DexEncodedMethod method, ClassWriter writer, ImmutableMap<DexString, DexValue> defaults) {
     int access = method.accessFlags.getAsCfAccessFlags();
-    String name = method.method.name.toString();
-    String desc = method.descriptor();
+    String name = namingLens.lookupName(method.method).toString();
+    String desc = method.descriptor(namingLens);
     String signature = getSignature(method.annotations);
     String[] exceptions = getExceptions(method.annotations);
     MethodVisitor visitor = writer.visitMethod(access, name, desc, signature, exceptions);
@@ -332,10 +335,11 @@
 
   private void writeCode(Code code, MethodVisitor visitor) {
     if (code.isJarCode()) {
+      assert namingLens.isIdentityLens();
       code.asJarCode().writeTo(visitor);
     } else {
       assert code.isCfCode();
-      code.asCfCode().write(visitor);
+      code.asCfCode().write(visitor, namingLens);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
index 37c9aea..0b67945 100644
--- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
@@ -7,8 +7,10 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.JarApplicationReader;
 import com.android.tools.r8.graph.UseRegistry;
@@ -50,13 +52,16 @@
   @Override
   public void visitLdcInsn(Object cst) {
     if (cst instanceof Type) {
-      // Nothing to register for method type, it represents only a prototype not associated with a
-      // method name.
-      if (((Type) cst).getSort() != Type.METHOD) {
+      if (((Type) cst).getSort() == Type.METHOD) {
+        String descriptor = ((Type) cst).getDescriptor();
+        assert descriptor.charAt(0) == '(';
+        registry.registerProto(application.getProto(descriptor));
+      } else {
         registry.registerConstClass(application.getType((Type) cst));
       }
     } else if (cst instanceof Handle) {
-      registerMethodHandleType((Handle) cst);
+      registry.registerMethodHandle(
+          DexMethodHandle.fromAsmHandle((Handle) cst, application, clazz));
     }
   }
 
@@ -109,55 +114,8 @@
 
   @Override
   public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
-    registerMethodHandleType(bsm);
-
-    // Register bootstrap method arguments, only Type and MethodHandle need to be register.
-    for (Object arg : bsmArgs) {
-      if (arg instanceof Type && ((Type) arg).getSort() == Type.OBJECT) {
-        registry.registerTypeReference(application.getType((Type) arg));
-      } else if (arg instanceof Handle) {
-        registerMethodHandleType((Handle) arg);
-      }
-    }
+    registry.registerCallSite(
+        DexCallSite.fromAsmInvokeDynamic(application, clazz, name, desc, bsm, bsmArgs));
   }
 
-  private void registerMethodHandleType(Handle handle) {
-    switch (handle.getTag()) {
-      case Opcodes.H_GETFIELD:
-        visitFieldInsn(Opcodes.GETFIELD, handle.getOwner(), handle.getName(), handle.getDesc());
-        break;
-      case Opcodes.H_GETSTATIC:
-        visitFieldInsn(Opcodes.GETSTATIC, handle.getOwner(), handle.getName(), handle.getDesc());
-        break;
-      case Opcodes.H_PUTFIELD:
-        visitFieldInsn(Opcodes.PUTFIELD, handle.getOwner(), handle.getName(), handle.getDesc());
-        break;
-      case Opcodes.H_PUTSTATIC:
-        visitFieldInsn(Opcodes.PUTSTATIC, handle.getOwner(), handle.getName(), handle.getDesc());
-        break;
-      case Opcodes.H_INVOKEVIRTUAL:
-        visitMethodInsn(
-            Opcodes.INVOKEVIRTUAL, handle.getOwner(), handle.getName(), handle.getDesc(), false);
-        break;
-      case Opcodes.H_INVOKEINTERFACE:
-        visitMethodInsn(
-            Opcodes.INVOKEINTERFACE, handle.getOwner(), handle.getName(), handle.getDesc(), true);
-        break;
-      case Opcodes.H_INVOKESPECIAL:
-        visitMethodInsn(
-            Opcodes.INVOKESPECIAL, handle.getOwner(), handle.getName(), handle.getDesc(), false);
-        break;
-      case Opcodes.H_INVOKESTATIC:
-        visitMethodInsn(
-            Opcodes.INVOKESTATIC, handle.getOwner(), handle.getName(), handle.getDesc(), false);
-        break;
-      case Opcodes.H_NEWINVOKESPECIAL:
-        registry.registerNewInstance(application.getTypeFromName(handle.getOwner()));
-        visitMethodInsn(
-            Opcodes.INVOKESPECIAL, handle.getOwner(), handle.getName(), handle.getDesc(), false);
-        break;
-      default:
-        throw new Unreachable("MethodHandle tag is not supported: " + handle.getTag());
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index dd00254..4f49fbe 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -438,14 +438,15 @@
           appInfo.dexItemFactory.createType(
               getDescriptorFromClassBinaryName(
                   getClassBinaryNameFromDescriptor(enclosingDescriptor)
-                  + '$' + name));
+                      + Minifier.INNER_CLASS_SEPARATOR
+                      + name));
       String enclosingRenamedBinaryName =
           getClassBinaryNameFromDescriptor(
               renaming.getOrDefault(enclosingType, enclosingType.descriptor).toString());
       String renamed =
           getClassBinaryNameFromDescriptor(
               renaming.getOrDefault(type, type.descriptor).toString());
-      assert renamed.startsWith(enclosingRenamedBinaryName + '$');
+      assert renamed.startsWith(enclosingRenamedBinaryName + Minifier.INNER_CLASS_SEPARATOR);
       String outName = renamed.substring(enclosingRenamedBinaryName.length() + 1);
       renamedSignature.append(outName);
       return type;
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 82eb242..6bd0c6d 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -25,6 +25,8 @@
 
 public class Minifier {
 
+  static final char INNER_CLASS_SEPARATOR = '$';
+
   private final AppInfoWithLiveness appInfo;
   private final RootSet rootSet;
   private final InternalOptions options;
@@ -77,6 +79,12 @@
     }
 
     @Override
+    public String lookupSimpleName(DexType inner, DexString innerName) {
+      String internalName = lookupInternalName(inner);
+      return internalName.substring(internalName.lastIndexOf(INNER_CLASS_SEPARATOR) + 1);
+    }
+
+    @Override
     public DexString lookupName(DexMethod method) {
       return renaming.getOrDefault(method, method.name);
     }
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index ca6c1c8..fe73dc5 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -30,6 +31,8 @@
 
   public abstract DexString lookupDescriptor(DexType type);
 
+  public abstract String lookupSimpleName(DexType inner, DexString innerName);
+
   public abstract DexString lookupName(DexMethod method);
 
   public abstract DexString lookupName(DexField field);
@@ -42,6 +45,11 @@
     return this instanceof IdentityLens;
   }
 
+  public String lookupInternalName(DexType type) {
+    assert type.isClassType() || type.isArrayType();
+    return DescriptorUtils.descriptorToInternalName(lookupDescriptor(type).toString());
+  }
+
   abstract void forAllRenamedTypes(Consumer<DexType> consumer);
 
   abstract <T extends DexItem> Map<String, T> getRenamedItems(
@@ -68,6 +76,11 @@
     }
 
     @Override
+    public String lookupSimpleName(DexType inner, DexString innerName) {
+      return innerName == null ? null : innerName.toString();
+    }
+
+    @Override
     public DexString lookupName(DexMethod method) {
       return method.name;
     }
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
index 3736008..1dd6e31 100644
--- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -50,16 +50,20 @@
         if (code == null) {
           return;
         }
-        assert code.isDexCode();
-        DexDebugInfo dexDebugInfo = code.asDexCode().getDebugInfo();
-        if (dexDebugInfo == null) {
-          return;
+        if (code.isDexCode()) {
+          DexDebugInfo dexDebugInfo = code.asDexCode().getDebugInfo();
+          if (dexDebugInfo == null) {
+            return;
+          }
+          // Thanks to a single global source file, we can safely remove DBG_SET_FILE entirely.
+          dexDebugInfo.events =
+              Arrays.stream(dexDebugInfo.events)
+                  .filter(dexDebugEvent -> !(dexDebugEvent instanceof SetFile))
+                  .toArray(DexDebugEvent[]::new);
+        } else {
+          assert code.isCfCode();
+          // CF has nothing equivalent to SetFile, so there is nothing to remove.
         }
-        // Thanks to a single global source file, we can safely remove DBG_SET_FILE entirely.
-        dexDebugInfo.events =
-            Arrays.stream(dexDebugInfo.events)
-                .filter(dexDebugEvent -> !(dexDebugEvent instanceof SetFile))
-                .toArray(DexDebugEvent[]::new);
       });
     }
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
index 321001e..a6a9a11 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
@@ -38,7 +38,7 @@
     // be removed.
     if (method.accessFlags.isBridge() && !method.accessFlags.isAbstract()) {
       InvokeSingleTargetExtractor targetExtractor = new InvokeSingleTargetExtractor();
-      method.getCode().registerInstructionsReferences(targetExtractor);
+      method.getCode().registerCodeReferences(targetExtractor);
       DexMethod target = targetExtractor.getTarget();
       InvokeKind kind = targetExtractor.getKind();
       if (target != null && target.getArity() == method.method.getArity()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 685e733..c815cf8 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -30,7 +30,7 @@
   private void identifyBridgeMethod(DexEncodedMethod method) {
     if (method.accessFlags.isBridge()) {
       InvokeSingleTargetExtractor targetExtractor = new InvokeSingleTargetExtractor();
-      method.getCode().registerInstructionsReferences(targetExtractor);
+      method.getCode().registerCodeReferences(targetExtractor);
       DexMethod target = targetExtractor.getTarget();
       InvokeKind kind = targetExtractor.getKind();
       if (target != null &&
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 9e00a64..b11b999 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1179,7 +1179,7 @@
       if (protoLiteExtension != null && protoLiteExtension.appliesTo(method)) {
         protoLiteExtension.processMethod(method, new UseRegistry(method), protoLiteFields);
       } else {
-        method.registerInstructionsReferences(new UseRegistry(method));
+        method.registerCodeReferences(new UseRegistry(method));
       }
       // Add all dependent members to the workqueue.
       enqueueRootItems(rootSet.getDependentItems(method));
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index 5c51786..8e5e12a 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -151,8 +151,7 @@
       clazz.forEachField(field -> addMainDexType(field.field.type));
       clazz.forEachMethod(method -> {
         traceMethodDirectDependencies(method.method);
-        method.registerInstructionsReferences(codeDirectReferenceCollector);
-        method.registerCatchedTypes(this::addMainDexType);
+        method.registerCodeReferences(codeDirectReferenceCollector);
       });
     }
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index 63475b7..a80e758 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -371,7 +371,7 @@
         return existing;
       } else if (existing.accessFlags.isBridge()) {
         InvokeSingleTargetExtractor extractor = new InvokeSingleTargetExtractor();
-        existing.getCode().registerInstructionsReferences(extractor);
+        existing.getCode().registerCodeReferences(extractor);
         if (extractor.getTarget() != method.method) {
           abortMerge = true;
         }
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 27a6405..962247e 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -151,9 +151,12 @@
         // this can only happen as the result of an invalid invoke. They will not actually be
         // called at runtime but we have to keep them as non-abstract (see above) to produce the
         // same failure mode.
-        reachableMethods.add(allowAbstract
-            ? method.toAbstractMethod()
-            : method.toEmptyThrowingMethod());
+        reachableMethods.add(
+            allowAbstract
+                ? method.toAbstractMethod()
+                : (options.isGeneratingClassFiles()
+                    ? method.toEmptyThrowingMethodCf()
+                    : method.toEmptyThrowingMethodDex()));
       } else {
         if (Log.ENABLED) {
           Log.debug(getClass(), "Removing method %s.", method.method);
diff --git a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLiteExtension.java b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLiteExtension.java
index c2ba7e5..c4dee58 100644
--- a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLiteExtension.java
+++ b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLiteExtension.java
@@ -116,13 +116,13 @@
       // serialized stream for this proto. As we mask all reads in the writing code and normally
       // remove fields that are only written but never read, we have to mark fields used in setters
       // as read and written.
-      method.registerInstructionsReferences(
+      method.registerCodeReferences(
           new FieldWriteImpliesReadUseRegistry(registry, method.method.holder));
     } else {
       // Filter all getters and field accesses in these methods. We do not want fields to become
       // live just due to being referenced in a special method. The pruning phase will remove
       // all references to dead fields in the code later.
-      method.registerInstructionsReferences(new FilteringUseRegistry(registry, method.method.holder,
+      method.registerCodeReferences(new FilteringUseRegistry(registry, method.method.holder,
           protoLiteFields));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index b79e8e6..fa045e6 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -138,6 +138,23 @@
   }
 
   /**
+   * Convert a class type descriptor to an ASM internal name.
+   *
+   * @param descriptor type descriptor
+   * @return Java type name
+   */
+  public static String descriptorToInternalName(String descriptor) {
+    switch (descriptor.charAt(0)) {
+      case '[':
+        return descriptor;
+      case 'L':
+        return descriptor.substring(1, descriptor.length() - 1);
+      default:
+        throw new Unreachable("Not array or class type");
+    }
+  }
+
+  /**
    * Convert a type descriptor to a Java type name. Will also deobfuscate class names if a
    * class mapper is provided.
    *
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 8fdf68d..dc0cd0f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -518,6 +518,32 @@
   // Some dalvik versions found in the wild perform invalid JIT compilation of cmp-long
   // instructions where the result register overlaps with the input registers.
   // See b/74084493.
+  //
+  // The same dalvik versions also have a bug where the JIT compilation of code such as:
+  //
+  // void method(long l) {
+  //  if (l < 0) throw new RuntimeException("less than");
+  //  if (l == 0) throw new RuntimeException("equal");
+  // }
+  //
+  // Will enter the case for l==0 even when l is non-zero. The code generated for this is of
+  // the form:
+  //
+  // 0:   0x00: ConstWide16         v0, 0x0000000000000000 (0)
+  // 1:   0x02: CmpLong             v2, v4, v0
+  // 2:   0x04: IfLtz               v2, 0x0c (+8)
+  // 3:   0x06: IfNez               v2, 0x0a (+4)
+  //
+  // However, the jit apparently clobbers the input register in the IfLtz instruction. Therefore,
+  // for dalvik VMs we have to instead generate the following code:
+  //
+  // 0:   0x00: ConstWide16         v0, 0x0000000000000000 (0)
+  // 1:   0x02: CmpLong             v2, v4, v0
+  // 2:   0x04: IfLtz               v2, 0x0e (+10)
+  // 3:   0x06: CmpLong             v2, v4, v0
+  // 4:   0x08: IfNez               v2, 0x0c (+4)
+  //
+  // See b/75408029.
   public boolean canHaveCmpLongBug() {
     return minApiLevel < AndroidApiLevel.L.getLevel();
   }
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index c208cea..258aa43 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -1907,11 +1907,6 @@
           // 1) t03
           // java.lang.AssertionError: expected null, but was:<[I@e2603b4>
 
-          .put("lang.ref.SoftReference.enqueue.SoftReference_enqueue_A01",
-                  match(runtimesUpTo(Version.V4_4_4)))
-          // 1) t03(com.google.jctf.test.lib.java.lang.ref.SoftReference.enqueue.SoftReference_enqueue_A01)
-          // java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0
-
           .put("lang.ref.ReferenceQueue.poll.ReferenceQueue_poll_A01",
               match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t03
@@ -4963,12 +4958,24 @@
 
           .put("lang.ref.PhantomReference.isEnqueued.PhantomReference_isEnqueued_A01",
               match(runtimesUpTo(Version.V4_4_4)))
+
           .put("lang.ref.WeakReference.isEnqueued.WeakReference_isEnqueued_A01",
               match(runtimesUpTo(Version.V4_4_4)))
+
+          .put("lang.ref.WeakReference.enqueue.WeakReference_enqueue_A01",
+              match(runtimesUpTo(Version.V4_4_4)))
+          // 1) t03
+          // java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0
+
           .put("lang.ref.SoftReference.isEnqueued.SoftReference_isEnqueued_A01",
               match(runtimesUpTo(Version.V4_4_4)))
           // Passes or fails randomly. Check that something is enqueued after 2 seconds.
 
+          .put("lang.ref.SoftReference.enqueue.SoftReference_enqueue_A01",
+              match(runtimesUpTo(Version.V4_4_4)))
+          // 1) t03(com.google.jctf.test.lib.java.lang.ref.SoftReference.enqueue.SoftReference_enqueue_A01)
+          // java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0
+
           .put("lang.ref.ReferenceQueue.poll.ReferenceQueue_poll_A01",
               match(runtimesUpTo(Version.V4_4_4)))
           // Passes or fails randomly.
diff --git a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
index 28fe5dc..79017bc 100644
--- a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
+++ b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
@@ -13,7 +16,6 @@
 import java.nio.file.Paths;
 import java.util.List;
 import java.util.stream.Collectors;
-import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -31,13 +33,15 @@
     List<Path> inputs =
         ImmutableList.of(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "arithmetic.jar"));
 
+    String keepMain = "-keep public class arithmetic.Arithmetic {\n"
+        + "  public static void main(java.lang.String[]);\n"
+        + "}";
+
     Path pgConf = temp.getRoot().toPath().resolve("pg.conf");
-    FileUtils.writeTextFile(
-        pgConf, TestBase.keepMainProguardConfiguration("arithmetic.Arithmetic"));
+    FileUtils.writeTextFile(pgConf, keepMain);
 
     Path mainDexRules = temp.getRoot().toPath().resolve("maindex.rules");
-    FileUtils.writeTextFile(
-        mainDexRules, TestBase.keepMainProguardConfiguration("arithmetic.Arithmetic"));
+    FileUtils.writeTextFile(mainDexRules, keepMain);
 
     Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
     FileUtils.writeTextFile(mainDexList, "arithmetic/Arithmetic.class");
@@ -68,8 +72,8 @@
 
     ProcessBuilder builder = new ProcessBuilder(command);
     ProcessResult result = ToolHelper.runProcess(builder);
-    Assert.assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
-    Assert.assertTrue(result.stdout, result.stdout.isEmpty());
-    Assert.assertTrue(result.stderr, result.stderr.isEmpty());
+    assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
+    assertTrue(result.stdout, result.stdout.isEmpty());
+    assertTrue(result.stderr, result.stderr.isEmpty());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
index f4d3da5..b3950b4 100644
--- a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
@@ -107,8 +107,6 @@
 
   private static List<String> expectedFailures =
       ImmutableList.of(
-          "native-private-interface-methods",
-          "desugared-private-interface-methods"
       );
 
   private boolean expectedToFailCf(String testName) {
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index c6ab4e2..5d7bec0 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -361,6 +361,47 @@
         .build());
   }
 
+  private ProcessResult runR8OnShaking1(Path additionalProguardConfiguration) throws Throwable {
+    Path input = Paths.get(EXAMPLES_BUILD_DIR, "shaking1.jar").toAbsolutePath();
+    Path proguardConfiguration =
+        Paths.get(ToolHelper.EXAMPLES_DIR, "shaking1", "keep-rules.txt").toAbsolutePath();
+    return ToolHelper.forkR8(temp.getRoot().toPath(),
+        "--pg-conf", proguardConfiguration.toString(),
+        "--pg-conf", additionalProguardConfiguration.toString(),
+        "--lib", ToolHelper.getDefaultAndroidJar().toAbsolutePath().toString(),
+        input.toString());
+  }
+
+  @Test
+  public void printsPrintSeedsOnStdout() throws Throwable {
+    Path proguardPrintSeedsConfiguration = temp.newFile("printseeds.txt").toPath().toAbsolutePath();
+    FileUtils.writeTextFile(proguardPrintSeedsConfiguration, ImmutableList.of("-printseeds"));
+    ProcessResult result = runR8OnShaking1(proguardPrintSeedsConfiguration);
+    assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
+    assertTrue(result.stdout.contains("void main(java.lang.String[])"));
+  }
+
+  @Test
+  public void printsPrintUsageOnStdout() throws Throwable {
+    Path proguardPrintUsageConfiguration = temp.newFile("printusage.txt").toPath().toAbsolutePath();
+    FileUtils.writeTextFile(proguardPrintUsageConfiguration, ImmutableList.of("-printusage"));
+    ProcessResult result = runR8OnShaking1(proguardPrintUsageConfiguration);
+    assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
+    assertTrue(result.stdout.contains("shaking1.Unused"));
+  }
+
+  @Test
+  public void printsPrintSeedsAndPrintUsageOnStdout() throws Throwable {
+    Path proguardPrintSeedsConfiguration =
+        temp.newFile("printseedsandprintusage.txt").toPath().toAbsolutePath();
+    FileUtils.writeTextFile(
+        proguardPrintSeedsConfiguration, ImmutableList.of("-printseeds", "-printusage"));
+    ProcessResult result = runR8OnShaking1(proguardPrintSeedsConfiguration);
+    assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
+    assertTrue(result.stdout.contains("void main(java.lang.String[])"));
+    assertTrue(result.stdout.contains("shaking1.Unused"));
+  }
+
   private R8Command parse(String... args) throws CompilationFailedException {
     return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 024be59..d7d8b99 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -54,7 +54,7 @@
 public class TestBase {
 
   // Actually running Proguard should only be during development.
-  private boolean runProguard = System.getProperty("run_proguard") != null;
+  private static final boolean RUN_PROGUARD = System.getProperty("run_proguard") != null;
 
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
@@ -63,7 +63,7 @@
    * Check if tests should also run Proguard when applicable.
    */
   protected boolean isRunProguard() {
-    return runProguard;
+    return RUN_PROGUARD;
   }
 
   /**
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 6ffe54d..bd66301 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -86,6 +86,7 @@
   public static final String DEFAULT_DEX_FILENAME = "classes.dex";
   public static final String DEFAULT_PROGUARD_MAP_FILE = "proguard.map";
 
+  public static final String JAVA_8_RUNTIME = "third_party/openjdk/openjdk-rt-1.8/rt.jar";
   private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
   private static final AndroidApiLevel DEFAULT_MIN_SDK = AndroidApiLevel.I;
 
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java b/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
index c80047a..a213a91 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
@@ -22,14 +22,18 @@
 // so this LDC instruction must be in a subclass of C, and not directly on MethodHandleTest.
 public class MethodHandleDump implements Opcodes {
 
+  private static final String cDesc = "com/android/tools/r8/cf/MethodHandleTest$C";
+  private static final String eDesc = "com/android/tools/r8/cf/MethodHandleTest$E";
+  private static final String fDesc = "com/android/tools/r8/cf/MethodHandleTest$F";
+  private static final String iDesc = "com/android/tools/r8/cf/MethodHandleTest$I";
   private static final Type viType = Type.getMethodType(Type.VOID_TYPE, Type.INT_TYPE);
   private static final Type jiType = Type.getMethodType(Type.LONG_TYPE, Type.INT_TYPE);
   private static final Type vicType =
       Type.getMethodType(Type.VOID_TYPE, Type.INT_TYPE, Type.CHAR_TYPE);
   private static final Type jicType =
       Type.getMethodType(Type.LONG_TYPE, Type.INT_TYPE, Type.CHAR_TYPE);
-  private static final String cDesc = "com/android/tools/r8/cf/MethodHandleTest$C";
-  private static final String iDesc = "com/android/tools/r8/cf/MethodHandleTest$I";
+  private static final Type veType = Type.getMethodType(Type.VOID_TYPE, Type.getObjectType(eDesc));
+  private static final Type fType = Type.getMethodType(Type.getObjectType(fDesc));
   private static final String viDesc = viType.getDescriptor();
   private static final String jiDesc = jiType.getDescriptor();
   private static final String vicDesc = vicType.getDescriptor();
@@ -44,6 +48,8 @@
             .put("jiType", jiType)
             .put("vicType", vicType)
             .put("jicType", jicType)
+            .put("veType", veType)
+            .put("fType", fType)
             .build();
 
     Builder<String, Handle> methodsBuilder = ImmutableMap.builder();
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java
index d42d443..844ae99 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java
@@ -73,6 +73,14 @@
     }
   }
 
+  public static class E {
+    // Class that is only mentioned in parameter list of LDC(MethodType)-instruction.
+  }
+
+  public static class F {
+    // Class that is only mentioned in return value of LDC(MethodType)-instruction.
+  }
+
   public interface I {
     int ii = 42;
 
@@ -166,6 +174,8 @@
       MethodHandle methodHandle = D.vcviSpecialMethod();
       methodHandle.invoke(new D(), 20);
       constructorMethod().invoke(21);
+      System.out.println(veType().parameterType(0).getName().lastIndexOf('.'));
+      System.out.println(fType().returnType().getName().lastIndexOf('.'));
     } catch (Throwable e) {
       throw new RuntimeException(e);
     }
@@ -199,6 +209,14 @@
     return MethodType.methodType(long.class, int.class, char.class);
   }
 
+  public static MethodType veType() {
+    return MethodType.methodType(void.class, E.class);
+  }
+
+  public static MethodType fType() {
+    return MethodType.methodType(F.class);
+  }
+
   public static MethodHandle scviMethod() {
     try {
       return MethodHandles.lookup().findStatic(C.class, "svi", viType());
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index d92ff90..ae3b95e 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.nio.file.Path;
+import java.util.Arrays;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -27,8 +28,10 @@
   static final Class<?> CLASS = MethodHandleTest.class;
 
   private boolean ldc = false;
+  private boolean minify = false;
 
-  @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
   @Test
   public void testMethodHandlesLookup() throws Exception {
@@ -44,20 +47,35 @@
     test();
   }
 
+  @Test
+  public void testMinify() throws Exception {
+    // Run test with LDC methods, i.e. without java.lang.invoke.MethodHandles
+    ldc = true;
+    ProcessResult runInput = runInput();
+    assertEquals(0, runInput.exitCode);
+    Path outCf = temp.getRoot().toPath().resolve("cf.jar");
+    build(new ClassFileConsumer.ArchiveConsumer(outCf), true);
+    ProcessResult runCf =
+        ToolHelper.runJava(outCf, CLASS.getCanonicalName(), ldc ? "error" : "exception");
+    assertEquals(runInput.toString(), runCf.toString());
+  }
+
   private final Class[] inputClasses = {
     MethodHandleTest.class,
     MethodHandleTest.C.class,
     MethodHandleTest.I.class,
     MethodHandleTest.Impl.class,
     MethodHandleTest.D.class,
+    MethodHandleTest.E.class,
+    MethodHandleTest.F.class,
   };
 
   private void test() throws Exception {
     ProcessResult runInput = runInput();
     Path outCf = temp.getRoot().toPath().resolve("cf.jar");
-    build(new ClassFileConsumer.ArchiveConsumer(outCf));
+    build(new ClassFileConsumer.ArchiveConsumer(outCf), false);
     Path outDex = temp.getRoot().toPath().resolve("dex.zip");
-    build(new DexIndexedConsumer.ArchiveConsumer(outDex));
+    build(new DexIndexedConsumer.ArchiveConsumer(outDex), false);
 
     ProcessResult runCf =
         ToolHelper.runJava(outCf, CLASS.getCanonicalName(), ldc ? "error" : "exception");
@@ -79,7 +97,7 @@
     assertEquals(runInput.exitCode, runDex.exitCode);
   }
 
-  private void build(ProgramConsumer programConsumer) throws Exception {
+  private void build(ProgramConsumer programConsumer, boolean minify) throws Exception {
     // MethodHandle.invoke() only supported from Android O
     // ConstMethodHandle only supported from Android P
     AndroidApiLevel apiLevel = AndroidApiLevel.P;
@@ -95,6 +113,14 @@
       byte[] classAsBytes = getClassAsBytes(c);
       cfBuilder.addClassProgramData(classAsBytes, Origin.unknown());
     }
+    if (minify) {
+      cfBuilder.addProguardConfiguration(
+          Arrays.asList(
+              "-keep public class com.android.tools.r8.cf.MethodHandleTest {",
+              "  public static void main(...);",
+              "}"),
+          Origin.unknown());
+    }
     R8.run(cfBuilder.build());
   }
 
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index cbfcca6..685e8f7 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -139,6 +139,9 @@
     Assume.assumeTrue("Skipping test " + testName.getMethodName()
             + " because debug tests are not yet supported on Windows",
         !ToolHelper.isWindows());
+    Assume.assumeTrue("Skipping test " + testName.getMethodName()
+            + " because debug tests are not yet supported on device",
+        ToolHelper.getDexVm().getKind() == ToolHelper.DexVm.Kind.HOST);
 
     ClassNameMapper classNameMapper =
         config.getProguardMap() == null
diff --git a/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java b/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
index c5c21a7..26d3d5c 100644
--- a/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
@@ -58,6 +58,8 @@
     // TODO(shertz) we should see the local variable this even when desugaring.
     if (supportsDefaultMethod(config)) {
       commands.add(checkLocal("this"));
+    } else {
+      commands.add(checkLocal("-this"));
     }
     commands.add(checkLocal(parameterName));
     commands.add(stepOver(INTELLIJ_FILTER));
diff --git a/src/test/java/com/android/tools/r8/debug/MinificationTest.java b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
index 5f63038..414fbc3 100644
--- a/src/test/java/com/android/tools/r8/debug/MinificationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
@@ -3,13 +3,20 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
@@ -28,13 +35,15 @@
 
   private static final String SOURCE_FILE = "Minified.java";
 
-  @Parameterized.Parameters(name = "minification: {0}, proguardMap: {1}")
+  @Parameterized.Parameters(name = "backend:{0} minification:{1} proguardMap:{2}")
   public static Collection minificationControl() {
     ImmutableList.Builder<Object> builder = ImmutableList.builder();
-    for (MinifyMode mode : MinifyMode.values()) {
-      builder.add((Object) new Object[]{mode, false});
-      if (mode.isMinify()) {
-        builder.add((Object) new Object[]{mode, true});
+    for (RuntimeKind kind : RuntimeKind.values()) {
+      for (MinifyMode mode : MinifyMode.values()) {
+        builder.add((Object) new Object[] {kind, mode, false});
+        if (mode.isMinify()) {
+          builder.add((Object) new Object[] {kind, mode, true});
+        }
       }
     }
     return builder.build();
@@ -43,10 +52,13 @@
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
+  private final RuntimeKind runtimeKind;
   private final MinifyMode minificationMode;
   private final boolean writeProguardMap;
 
-  public MinificationTest(MinifyMode minificationMode, boolean writeProguardMap) throws Exception {
+  public MinificationTest(
+      RuntimeKind runtimeKind, MinifyMode minificationMode, boolean writeProguardMap) {
+    this.runtimeKind = runtimeKind;
     this.minificationMode = minificationMode;
     this.writeProguardMap = writeProguardMap;
   }
@@ -69,15 +81,19 @@
     }
 
     AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
-    Path dexOutputDir = temp.newFolder().toPath();
-    Path proguardMap = writeProguardMap ? dexOutputDir.resolve("proguard.map") : null;
+    Path outputPath = temp.getRoot().toPath().resolve("classes.zip");
+    Path proguardMap = writeProguardMap ? temp.getRoot().toPath().resolve("proguard.map") : null;
+    OutputMode outputMode =
+        runtimeKind == RuntimeKind.CF ? OutputMode.ClassFile : OutputMode.DexIndexed;
     R8Command.Builder builder =
         R8Command.builder()
             .addProgramFiles(DEBUGGEE_JAR)
-            .setOutput(dexOutputDir, OutputMode.DexIndexed)
-            .setMinApiLevel(minSdk.getLevel())
+            .setOutput(outputPath, outputMode)
             .setMode(CompilationMode.DEBUG)
             .addLibraryFiles(ToolHelper.getAndroidJar(minSdk));
+    if (runtimeKind != RuntimeKind.CF) {
+      builder.setMinApiLevel(minSdk.getLevel());
+    }
     if (proguardMap != null) {
       builder.setProguardMapOutputPath(proguardMap);
     }
@@ -91,9 +107,22 @@
             ? (oc -> oc.lineNumberOptimization = LineNumberOptimization.OFF)
             : null);
 
-    DexDebugTestConfig config = new DexDebugTestConfig(dexOutputDir.resolve("classes.dex"));
-    config.setProguardMap(proguardMap);
-    return config;
+    switch (runtimeKind) {
+      case CF:
+        {
+          CfDebugTestConfig config = new CfDebugTestConfig(outputPath);
+          config.setProguardMap(proguardMap);
+          return config;
+        }
+      case DEX:
+        {
+          DexDebugTestConfig config = new DexDebugTestConfig(outputPath);
+          config.setProguardMap(proguardMap);
+          return config;
+        }
+      default:
+        throw new Unreachable();
+    }
   }
 
   @Test
@@ -104,8 +133,15 @@
     final String innerClassName = minifiedNames() ? "a" : "Minified$Inner";
     final String innerMethodName = minifiedNames() ? "a" : "innerTest";
     final String innerSignature = "()I";
+    DebugTestConfig config = getTestConfig();
+    checkStructure(
+        config,
+        className,
+        MethodSignature.fromSignature(methodName, signature),
+        innerClassName,
+        MethodSignature.fromSignature(innerMethodName, innerSignature));
     runDebugTest(
-        getTestConfig(),
+        config,
         className,
         breakpoint(className, methodName, signature),
         run(),
@@ -126,8 +162,14 @@
     final String innerClassName = minifiedNames() ? "a" : "Minified$Inner";
     final String innerMethodName = minifiedNames() ? "a" : "innerTest";
     final String innerSignature = "()I";
+    DebugTestConfig config = getTestConfig();
+    checkStructure(
+        config,
+        className,
+        innerClassName,
+        MethodSignature.fromSignature(innerMethodName, innerSignature));
     runDebugTest(
-        getTestConfig(),
+        config,
         className,
         breakpoint(innerClassName, innerMethodName, innerSignature),
         run(),
@@ -135,4 +177,35 @@
         checkLine(SOURCE_FILE, 8),
         run());
   }
+
+  private void checkStructure(
+      DebugTestConfig config,
+      String className,
+      MethodSignature method,
+      String innerClassName,
+      MethodSignature innerMethod)
+      throws Throwable {
+    if (ToolHelper.isWindows()) {
+      // TODO(b/76135355): Update dx.bat on Windows to something that can build
+      // jdwp-tests-preN-dex.jar.
+      return;
+    }
+    Path proguardMap = config.getProguardMap();
+    String mappingFile = proguardMap == null ? null : proguardMap.toString();
+    DexInspector inspector = new DexInspector(config.getPaths(), mappingFile);
+    ClassSubject clazz = inspector.clazz(className);
+    assertTrue(clazz.isPresent());
+    if (method != null) {
+      assertTrue(clazz.method(method).isPresent());
+    }
+    ClassSubject innerClass = inspector.clazz(innerClassName);
+    assertTrue(innerClass.isPresent());
+    assertTrue(innerClass.method(innerMethod).isPresent());
+  }
+
+  private void checkStructure(
+      DebugTestConfig config, String className, String innerClassName, MethodSignature innerMethod)
+      throws Throwable {
+    checkStructure(config, className, null, innerClassName, innerMethod);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
index 40d8ad0..e07f319 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
@@ -4,11 +4,14 @@
 
 package com.android.tools.r8.desugaring.interfacemethods;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.AsmTestBase;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.VmTestRunner;
-import com.android.tools.r8.desugaring.interfacemethods.test0.InterfaceWithDefaults;
-import com.android.tools.r8.desugaring.interfacemethods.test0.TestMain;
+import com.android.tools.r8.VmTestRunner.IgnoreForRangeOfVmVersions;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -25,13 +28,35 @@
 
   @Test
   public void testInvokeSpecialToDefaultMethod() throws Exception {
-    ensureSameOutput(TestMain.class.getCanonicalName(),
+    ensureSameOutput(
+        com.android.tools.r8.desugaring.interfacemethods.test0.TestMain.class.getCanonicalName(),
         ToolHelper.getMinApiLevelForDexVm(),
-        ToolHelper.getClassAsBytes(TestMain.class),
-        introduceInvokeSpecial(ToolHelper.getClassAsBytes(InterfaceWithDefaults.class)));
+        ToolHelper.getClassAsBytes(
+            com.android.tools.r8.desugaring.interfacemethods.test0.TestMain.class),
+        introduceInvokeSpecial(ToolHelper.getClassAsBytes(
+            com.android.tools.r8.desugaring.interfacemethods.test0.InterfaceWithDefaults.class)));
+  }
+
+  // NOTE: this particular test is working on pre-N devices since
+  //       it's fixed by interface default method desugaring.
+  @IgnoreForRangeOfVmVersions(from = Version.V7_0_0, to = Version.DEFAULT)
+  @Test
+  public void testInvokeSpecialToDefaultMethodFromStatic() throws Exception {
+    ensureSameOutput(
+        com.android.tools.r8.desugaring.interfacemethods.test1.TestMain.class.getCanonicalName(),
+        ToolHelper.getMinApiLevelForDexVm(),
+        ToolHelper.getClassAsBytes(
+            com.android.tools.r8.desugaring.interfacemethods.test1.TestMain.class),
+        introduceInvokeSpecial(ToolHelper.getClassAsBytes(
+            com.android.tools.r8.desugaring.interfacemethods.test1.InterfaceWithDefaults.class)));
+  }
+
+  private class MutableBoolean {
+    boolean value;
   }
 
   private byte[] introduceInvokeSpecial(byte[] classBytes) throws IOException {
+    MutableBoolean patched = new MutableBoolean();
     try (InputStream input = new ByteArrayInputStream(classBytes)) {
       ClassReader cr = new ClassReader(input);
       ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
@@ -46,9 +71,11 @@
                 public void visitMethodInsn(
                     int opcode, String owner, String name, String desc, boolean itf) {
                   if (opcode == Opcodes.INVOKEINTERFACE &&
-                      owner.endsWith("test0/InterfaceWithDefaults") &&
+                      owner.endsWith("InterfaceWithDefaults") &&
                       name.equals("foo")) {
+                    assertFalse(patched.value);
                     super.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, itf);
+                    patched.value = true;
 
                   } else {
                     super.visitMethodInsn(opcode, owner, name, desc, itf);
@@ -57,6 +84,7 @@
               };
             }
           }, 0);
+      assertTrue(patched.value);
       return cw.toByteArray();
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/InterfaceWithDefaults.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/InterfaceWithDefaults.java
index fc5ab9e..dc671dc 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/InterfaceWithDefaults.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/InterfaceWithDefaults.java
@@ -1,3 +1,7 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 package com.android.tools.r8.desugaring.interfacemethods.test0;
 
 public interface InterfaceWithDefaults {
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/TestMain.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/TestMain.java
index b57cbf3..79ac7b0 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/TestMain.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/TestMain.java
@@ -1,3 +1,7 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 package com.android.tools.r8.desugaring.interfacemethods.test0;
 
 public class TestMain implements InterfaceWithDefaults {
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test1/InterfaceWithDefaults.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test1/InterfaceWithDefaults.java
new file mode 100644
index 0000000..6f79c96
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test1/InterfaceWithDefaults.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugaring.interfacemethods.test1;
+
+public interface InterfaceWithDefaults {
+  default void foo() {
+    System.out.println("InterfaceWithDefaults::foo()");
+  }
+
+  static void bar(InterfaceWithDefaults iface) {
+    System.out.println("InterfaceWithDefaults::bar()");
+    iface.foo();
+  }
+
+  void test();
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test1/TestMain.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test1/TestMain.java
new file mode 100644
index 0000000..e38a17d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test1/TestMain.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugaring.interfacemethods.test1;
+
+public class TestMain implements InterfaceWithDefaults {
+  @Override
+  public void test() {
+    System.out.println("TestMain::test()");
+    this.foo();
+    InterfaceWithDefaults.bar(this);
+  }
+
+  @Override
+  public void foo() {
+    System.out.println("TestMain::foo()");
+  }
+
+  public static void main(String[] args) {
+    new TestMain().test();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index b99bf6d..9edc735 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -103,6 +103,7 @@
         DexEncodedField.EMPTY_ARRAY,
         DexEncodedMethod.EMPTY_ARRAY,
         new DexEncodedMethod[] {makeMethod(type, stringCount, startOffset)},
+        false,
         synthesizedFrom);
   }
 
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
index 6e9efa6..bd462b7 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
@@ -3,80 +3,88 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
 
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.CompilationError;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class InvalidClassNames extends JasminTestBase {
+public class InvalidClassNames extends NameTestBase {
 
-  public boolean runsOnJVM;
-  public String name;
+  private static final String RESULT = "MAIN";
+  private static final String MAIN_CLASS = "Main";
 
-  public InvalidClassNames(String name, boolean runsOnJVM) {
-    this.name = name;
-    this.runsOnJVM = runsOnJVM;
-  }
-
-  private void runTest(JasminBuilder builder, String main, String expected) throws Exception {
-    if (runsOnJVM) {
-      String javaResult = runOnJava(builder, main);
-      assertEquals(expected, javaResult);
-    }
-    String artResult = null;
-    try {
-      artResult = runOnArtD8(builder, main);
-      fail();
-    } catch (CompilationError t) {
-      // Intentionally left empty.
-    } catch (Throwable t) {
-      t.printStackTrace(System.out);
-      fail("Invalid dex class names should be compilation errors.");
-    }
-    assertNull("Invalid dex class names should be rejected.", artResult);
-  }
-
-  @Parameters
+  @Parameters(name = "\"{0}\", jvm: {1}, art: {2}")
   public static Collection<Object[]> data() {
-    return Arrays.asList(new Object[][]{
-        {"\u00a0", !ToolHelper.isJava9Runtime()},
-        {"\u2000", !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime()},
-        {"\u200f", !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime()},
-        {"\u2028", !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime()},
-        {"\u202f", !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime()},
-        {"\ud800", false},
-        {"\udfff", false},
-        {"\ufff0", !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime()},
-        {"\uffff", !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime()},
-        {"a/b/c/a/D/", true},
-        {"a<b", !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime()},
-        {"a>b", !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime()},
-        {"<a>b", !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime()},
-        {"<a>", !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime()}
-    });
+    Collection<Object[]> data = new ArrayList<>();
+    data.addAll(NameTestBase.getCommonNameTestData(true));
+    data.addAll(
+        Arrays.asList(
+            new Object[][] {
+              {new TestString("a/b/c/a/D/"), true, false},
+              {
+                new TestString("a<b"),
+                !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime(),
+                false
+              },
+              {
+                new TestString("a>b"),
+                !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime(),
+                false
+              },
+              {
+                new TestString("<a>b"),
+                !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime(),
+                false
+              },
+              {
+                new TestString("<a>"),
+                !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime(),
+                false
+              }
+            }));
+    return data;
+  }
+
+  private String name;
+  private boolean validForJVM;
+  private boolean validForArt;
+
+  public InvalidClassNames(TestString name, boolean validForJVM, boolean validForArt) {
+    this.name = name.getValue();
+    this.validForJVM = validForJVM;
+    this.validForArt = validForArt;
+  }
+
+  private JasminBuilder createJasminBuilder() {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass(name);
+    clazz.addStaticMethod(
+        "run",
+        Collections.emptyList(),
+        "V",
+        ".limit stack 2",
+        ".limit locals 0",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  ldc \"" + RESULT + "\"",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  return");
+
+    clazz = builder.addClass(MAIN_CLASS);
+    clazz.addMainMethod(
+        ".limit stack 0", ".limit locals 1", "invokestatic " + name + "/run()V", "  return");
+
+    return builder;
   }
 
   @Test
   public void invalidClassName() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
-    JasminBuilder.ClassBuilder clazz = builder.addClass(name);
-    clazz.addMainMethod(
-        ".limit stack 2",
-        ".limit locals 1",
-        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
-        "  ldc \"MAIN\"",
-        "  invokevirtual java/io/PrintStream.print(Ljava/lang/String;)V",
-        "  return");
-
-    runTest(builder, clazz.name, "MAIN");
+    runNameTesting(validForJVM, createJasminBuilder(), MAIN_CLASS, RESULT, validForArt, name);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
index bad64ac..8f680ee 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
@@ -3,117 +3,48 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.smali.SmaliBuilder;
-import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.base.Strings;
-import com.google.common.primitives.Bytes;
-import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.zip.Adler32;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class InvalidFieldNames extends JasminTestBase {
+public class InvalidFieldNames extends NameTestBase {
 
   private static final String CLASS_NAME = "Test";
   private static String FIELD_VALUE = "42";
 
   @Parameters(name = "\"{0}\", jvm: {1}, art: {2}")
   public static Collection<Object[]> data() {
-    return Arrays.asList(
-        new Object[][] {
-          {new TestStringParameter("azAZ09$_"), true, true},
-          {new TestStringParameter("_"), !ToolHelper.isJava9Runtime(), true},
-          {new TestStringParameter("a-b"), !ToolHelper.isJava9Runtime(), true},
-          {new TestStringParameter("\u00a0"), !ToolHelper.isJava9Runtime(), false},
-          {new TestStringParameter("\u00a1"), !ToolHelper.isJava9Runtime(), true},
-          {new TestStringParameter("\u1fff"), !ToolHelper.isJava9Runtime(), true},
-          {new TestStringParameter("\u2000"), !ToolHelper.isJava9Runtime(), false},
-          {new TestStringParameter("\u200f"), !ToolHelper.isJava9Runtime(), false},
-          {new TestStringParameter("\u2010"), !ToolHelper.isJava9Runtime(), true},
-          {new TestStringParameter("\u2027"), !ToolHelper.isJava9Runtime(), true},
-          {new TestStringParameter("\u2028"), !ToolHelper.isJava9Runtime(), false},
-          {new TestStringParameter("\u202f"), !ToolHelper.isJava9Runtime(), false},
-          {new TestStringParameter("\u2030"), !ToolHelper.isJava9Runtime(), true},
-          {new TestStringParameter("\ud7ff"), !ToolHelper.isJava9Runtime(), true},
-
-          // Standalone high and low surrogates.
-          {new TestStringParameter("\ud800"), !ToolHelper.isJava9Runtime(), false},
-          {new TestStringParameter("\udbff"), !ToolHelper.isJava9Runtime(), false},
-          {new TestStringParameter("\udc00"), !ToolHelper.isJava9Runtime(), false},
-          {new TestStringParameter("\udfff"), !ToolHelper.isJava9Runtime(), false},
-          {new TestStringParameter("\ue000"), !ToolHelper.isJava9Runtime(), true},
-          {new TestStringParameter("\uffef"), !ToolHelper.isJava9Runtime(), true},
-          {new TestStringParameter("\ufff0"), !ToolHelper.isJava9Runtime(), false},
-          {new TestStringParameter("\uffff"), !ToolHelper.isJava9Runtime(), false},
-
-          // Single and double code points above 0x10000.
-          {new TestStringParameter("\ud800\udc00"), true, true},
-          {new TestStringParameter("\ud800\udcfa"), true, true},
-          {new TestStringParameter("\ud800\udcfb"), !ToolHelper.isJava9Runtime(), true},
-          {new TestStringParameter("\udbff\udfff"), !ToolHelper.isJava9Runtime(), true},
-          {new TestStringParameter("\ud800\udc00\ud800\udcfa"), true, true},
-          {new TestStringParameter("\ud800\udc00\udbff\udfff"), !ToolHelper.isJava9Runtime(), true},
-          {new TestStringParameter("a/b"), false, false},
-          {new TestStringParameter("<a"), !ToolHelper.isJava9Runtime(), false},
-          {new TestStringParameter("a>"), !ToolHelper.isJava9Runtime(), false},
-          {new TestStringParameter("a<b>"), !ToolHelper.isJava9Runtime(), false},
-          {new TestStringParameter("<a>b"), !ToolHelper.isJava9Runtime(), false}
-        });
-  }
-
-  // TestStringParameter is a String with modified toString() which prints \\uXXXX for
-  // characters outside 0x20..0x7e.
-  static class TestStringParameter {
-    private final String value;
-
-    TestStringParameter(String value) {
-      this.value = value;
-    }
-
-    String getValue() {
-      return value;
-    }
-
-    @Override
-    public String toString() {
-      return StringUtils.toASCIIString(value);
-    }
+    Collection<Object[]> data = new ArrayList<>();
+    data.addAll(NameTestBase.getCommonNameTestData(false));
+    data.addAll(
+        Arrays.asList(
+            new Object[][] {
+              {new TestString("a/b"), false, false},
+              {new TestString("<a"), !ToolHelper.isJava9Runtime(), false},
+              {new TestString("a>"), !ToolHelper.isJava9Runtime(), false},
+              {new TestString("a<b>"), !ToolHelper.isJava9Runtime(), false},
+              {new TestString("<a>b"), !ToolHelper.isJava9Runtime(), false},
+              {new TestString("<a>"), false, true}
+            }));
+    return data;
   }
 
   private String name;
   private boolean validForJVM;
   private boolean validForArt;
 
-  public InvalidFieldNames(TestStringParameter name, boolean validForJVM, boolean validForArt) {
+  public InvalidFieldNames(TestString name, boolean validForJVM, boolean validForArt) {
     this.name = name.getValue();
     this.validForJVM = validForJVM;
     this.validForArt = validForArt;
   }
 
-  private byte[] trimLastZeroByte(byte[] bytes) {
-    assert bytes.length > 0 && bytes[bytes.length - 1] == 0;
-    byte[] result = new byte[bytes.length - 1];
-    System.arraycopy(bytes, 0, result, 0, result.length);
-    return result;
-  }
-
   private JasminBuilder createJasminBuilder() {
     JasminBuilder builder = new JasminBuilder();
     JasminBuilder.ClassBuilder clazz = builder.addClass(CLASS_NAME);
@@ -130,84 +61,8 @@
     return builder;
   }
 
-  private AndroidApp createAppWithSmali() throws Exception {
-
-    SmaliBuilder smaliBuilder = new SmaliBuilder(CLASS_NAME);
-    String originalSourceFile = CLASS_NAME + FileUtils.JAVA_EXTENSION;
-    smaliBuilder.setSourceFile(originalSourceFile);
-
-    // We're using a valid placeholder string which will be replaced by the actual name.
-    byte[] nameMutf8 = trimLastZeroByte(DexString.encodeToMutf8(name));
-
-    String placeholderString = Strings.repeat("A", nameMutf8.length);
-
-    smaliBuilder.addStaticField(placeholderString, "I", FIELD_VALUE);
-    MethodSignature mainSignature =
-        smaliBuilder.addMainMethod(
-            1,
-            "sget-object p0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
-            "sget v0, LTest;->" + placeholderString + ":I",
-            "invoke-virtual {p0, v0}, Ljava/io/PrintStream;->print(I)V",
-            "return-void");
-    byte[] dexCode = smaliBuilder.compile();
-
-    // Replace placeholder by mutf8-encoded name
-    byte[] placeholderBytes = trimLastZeroByte(DexString.encodeToMutf8(placeholderString));
-    assert placeholderBytes.length == nameMutf8.length;
-    int index = Bytes.indexOf(dexCode, placeholderBytes);
-    if (index >= 0) {
-      System.arraycopy(nameMutf8, 0, dexCode, index, nameMutf8.length);
-    }
-    assert Bytes.indexOf(dexCode, placeholderBytes) < 0;
-
-    // Update checksum
-    Adler32 adler = new Adler32();
-    adler.update(dexCode, Constants.SIGNATURE_OFFSET, dexCode.length - Constants.SIGNATURE_OFFSET);
-    int checksum = (int) adler.getValue();
-    for (int i = 0; i < 4; ++i) {
-      dexCode[Constants.CHECKSUM_OFFSET + i] = (byte) (checksum >> (8 * i) & 0xff);
-    }
-
-    return AndroidApp.builder()
-        .addDexProgramData(dexCode, new PathOrigin(Paths.get(originalSourceFile)))
-        .build();
-  }
-
   @Test
   public void invalidFieldNames() throws Exception {
-    JasminBuilder jasminBuilder = createJasminBuilder();
-
-    if (validForJVM) {
-      String javaResult = runOnJava(jasminBuilder, CLASS_NAME);
-      assertEquals(FIELD_VALUE, javaResult);
-    } else {
-      try {
-        runOnJava(jasminBuilder, CLASS_NAME);
-        fail("Should have failed on JVM.");
-      } catch (AssertionError e) {
-        // Silent on expected failure.
-      }
-    }
-
-    if (validForArt) {
-      String artResult = runOnArtD8(jasminBuilder, CLASS_NAME);
-      assertEquals(FIELD_VALUE, artResult);
-    } else {
-      // Make sure the compiler fails.
-      try {
-        runOnArtD8(jasminBuilder, CLASS_NAME);
-        fail("D8 should have rejected this case.");
-      } catch (CompilationError t) {
-        assertTrue(t.getMessage().contains(name));
-      }
-
-      // Make sure ART also fail, if D8 rejects it.
-      try {
-        runOnArt(createAppWithSmali(), CLASS_NAME);
-        fail("Art should have failed.");
-      } catch (AssertionError e) {
-        // Silent on expected failure.
-      }
-    }
+    runNameTesting(validForJVM, createJasminBuilder(), CLASS_NAME, FIELD_VALUE, validForArt, name);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
index 5f83016..fe95845 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
@@ -3,15 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import org.junit.Test;
@@ -20,73 +16,79 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class InvalidMethodNames extends JasminTestBase {
+public class InvalidMethodNames extends NameTestBase {
 
-  public boolean runsOnJVM;
-  public String name;
+  private final String CLASS_NAME = "Test";
+  private final String RESULT = "CALLED";
 
-  public InvalidMethodNames(String name, boolean runsOnJVM) {
-    this.name = name;
-    this.runsOnJVM = runsOnJVM;
-  }
+  private String name;
+  private boolean validForJVM;
+  private boolean validForArt;
 
-  private void runTest(JasminBuilder builder, String main, String expected) throws Exception {
-    if (runsOnJVM) {
-      String javaResult = runOnJava(builder, main);
-      assertEquals(expected, javaResult);
-    }
-    String artResult = null;
-    try {
-      artResult = runOnArtD8(builder, main);
-      fail();
-    } catch (CompilationError t) {
-      String asciiString = new DexString(name).toASCIIString();
-      assertTrue(t.getMessage().contains(asciiString));
-    } catch (Throwable t) {
-      t.printStackTrace(System.out);
-      fail("Invalid dex method names should be compilation errors.");
-    }
-    assertNull("Invalid dex method names should be rejected.", artResult);
-  }
-
-  @Parameters
+  @Parameters(name = "\"{0}\", jvm: {1}, art: {2}")
   public static Collection<Object[]> data() {
-    return Arrays.asList(new Object[][]{
-        {"\u00a0", !ToolHelper.isJava9Runtime()},
-        {"\u2000", !ToolHelper.isJava9Runtime()},
-        {"\u200f", !ToolHelper.isJava9Runtime()},
-        {"\u2028", !ToolHelper.isJava9Runtime()},
-        {"\u202f", !ToolHelper.isJava9Runtime()},
-        {"\ud800", !ToolHelper.isJava9Runtime()},
-        {"\udfff", !ToolHelper.isJava9Runtime()},
-        {"\ufff0", !ToolHelper.isJava9Runtime()},
-        {"\uffff", !ToolHelper.isJava9Runtime()},
-        {"a/b", false},
-        {"<a", false},
-        {"a>", !ToolHelper.isJava9Runtime()},
-        {"<a>", false}
-    });
+    Collection<Object[]> data = new ArrayList<>();
+    data.addAll(NameTestBase.getCommonNameTestData(false));
+    data.addAll(
+        Arrays.asList(
+            new Object[][] {
+              {new TestString("a/b"), false, false},
+              {new TestString("<a"), false, false},
+              {new TestString("a>"), !ToolHelper.isJava9Runtime(), false},
+              {new TestString("<a>"), false, false}
+            }));
+    return data;
   }
 
-  @Test
-  public void invalidMethodName() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
-    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+  public InvalidMethodNames(TestString name, boolean validForJVM, boolean validForArt) {
+    this.name = name.getValue();
+    this.validForJVM = validForJVM;
+    this.validForArt = validForArt;
+  }
 
-    clazz.addStaticMethod(name, ImmutableList.of(), "V",
+  JasminBuilder createJasminBuilder() {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass(CLASS_NAME);
+
+    clazz.addStaticMethod(
+        name,
+        ImmutableList.of(),
+        "V",
         ".limit stack 2",
         ".limit locals 0",
         "  getstatic java/lang/System/out Ljava/io/PrintStream;",
-        "  ldc \"CALLED\"",
+        "  ldc \"" + RESULT + "\"",
         "  invokevirtual java/io/PrintStream.print(Ljava/lang/String;)V",
         "  return");
 
     clazz.addMainMethod(
         ".limit stack 0",
         ".limit locals 1",
-        "  invokestatic Test/" + name + "()V",
+        "  invokestatic " + CLASS_NAME + "/" + name + "()V",
         "  return");
+    return builder;
+  }
 
-    runTest(builder, clazz.name, "CALLED");
+  static class StringReplaceData {
+    final byte[] inputMutf8;
+    final String placeholderString;
+    final byte[] placeholderBytes;
+
+    StringReplaceData(byte[] inputMutf8, String placeholderString, byte[] placeholderBytes) {
+      this.inputMutf8 = inputMutf8;
+      this.placeholderString = placeholderString;
+      this.placeholderBytes = placeholderBytes;
+    }
+  }
+
+  @Test
+  public void invalidMethodName() throws Exception {
+    runNameTesting(
+        validForJVM,
+        createJasminBuilder(),
+        CLASS_NAME,
+        RESULT,
+        validForArt,
+        StringUtils.toASCIIString(name));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index ff0a177..c75ea7d 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -75,7 +75,13 @@
   }
 
   protected String runOnArtD8(JasminBuilder builder, String main) throws Exception {
-    return runOnArt(compileWithD8(builder), main);
+    return runOnArtD8(builder, main, null);
+  }
+
+  protected String runOnArtD8(
+      JasminBuilder builder, String main, Consumer<InternalOptions> optionsConsumer)
+      throws Exception {
+    return runOnArt(compileWithD8(builder, optionsConsumer), main);
   }
 
   protected ProcessResult runOnArtD8Raw(JasminBuilder builder, String main) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/jasmin/NameTestBase.java b/src/test/java/com/android/tools/r8/jasmin/NameTestBase.java
new file mode 100644
index 0000000..1104e54
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/NameTestBase.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.jasmin;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.InvalidPathException;
+import java.util.Arrays;
+import java.util.Collection;
+
+class NameTestBase extends JasminTestBase {
+  // TestString is a String with modified toString() which prints \\uXXXX for
+  // characters outside 0x20..0x7e.
+  static class TestString {
+    private final String value;
+
+    TestString(String value) {
+      this.value = value;
+    }
+
+    String getValue() {
+      return value;
+    }
+
+    @Override
+    public String toString() {
+      return StringUtils.toASCIIString(value);
+    }
+  }
+
+  // The return value is a collection of rows with the following fields:
+  // - Name (String) to test (can be class name, field name, method name).
+  // - boolean, whether it runs on the JVM.
+  // - boolean, whether it runs on the ART.
+  static Collection<Object[]> getCommonNameTestData(boolean classNames) {
+
+    boolean windowsSensitive = classNames && ToolHelper.isWindows();
+
+    return Arrays.asList(
+        new Object[][] {
+          {new TestString("azAZ09$_"), true, true},
+          {new TestString("_"), !ToolHelper.isJava9Runtime(), true},
+          {new TestString("a-b"), !ToolHelper.isJava9Runtime(), true},
+          {new TestString("\u00a0"), !ToolHelper.isJava9Runtime(), false},
+          {new TestString("\u00a1"), !ToolHelper.isJava9Runtime(), true},
+          {new TestString("\u1fff"), !ToolHelper.isJava9Runtime(), true},
+          {new TestString("\u2000"), !windowsSensitive && !ToolHelper.isJava9Runtime(), false},
+          {new TestString("\u200f"), !windowsSensitive && !ToolHelper.isJava9Runtime(), false},
+          {new TestString("\u2010"), !windowsSensitive && !ToolHelper.isJava9Runtime(), true},
+          {new TestString("\u2027"), !windowsSensitive && !ToolHelper.isJava9Runtime(), true},
+          {new TestString("\u2028"), !windowsSensitive && !ToolHelper.isJava9Runtime(), false},
+          {new TestString("\u202f"), !windowsSensitive && !ToolHelper.isJava9Runtime(), false},
+          {new TestString("\u2030"), !windowsSensitive && !ToolHelper.isJava9Runtime(), true},
+          {new TestString("\ud7ff"), !windowsSensitive && !ToolHelper.isJava9Runtime(), true},
+          {new TestString("\ue000"), !windowsSensitive && !ToolHelper.isJava9Runtime(), true},
+          {new TestString("\uffef"), !windowsSensitive && !ToolHelper.isJava9Runtime(), true},
+          {new TestString("\ufff0"), !windowsSensitive && !ToolHelper.isJava9Runtime(), false},
+          {new TestString("\uffff"), !windowsSensitive && !ToolHelper.isJava9Runtime(), false},
+
+          // Standalone high and low surrogates.
+          {new TestString("\ud800"), !classNames && !ToolHelper.isJava9Runtime(), false},
+          {new TestString("\udbff"), !classNames && !ToolHelper.isJava9Runtime(), false},
+          {new TestString("\udc00"), !classNames && !ToolHelper.isJava9Runtime(), false},
+          {new TestString("\udfff"), !classNames && !ToolHelper.isJava9Runtime(), false},
+
+          // Single and double code points above 0x10000.
+          {new TestString("\ud800\udc00"), true, true},
+          {new TestString("\ud800\udcfa"), true, true},
+          {new TestString("\ud800\udcfb"), !windowsSensitive && !ToolHelper.isJava9Runtime(), true},
+          {new TestString("\udbff\udfff"), !windowsSensitive && !ToolHelper.isJava9Runtime(), true},
+          {new TestString("\ud800\udc00\ud800\udcfa"), true, true},
+          {
+            new TestString("\ud800\udc00\udbff\udfff"),
+            !windowsSensitive && !ToolHelper.isJava9Runtime(),
+            true
+          }
+        });
+  }
+
+  void runNameTesting(
+      boolean validForJVM,
+      JasminBuilder jasminBuilder,
+      String mainClassName,
+      String expectedResult,
+      boolean validForArt,
+      String expectedNameInFailingD8Message)
+      throws Exception {
+
+    if (validForJVM) {
+      String javaResult = runOnJava(jasminBuilder, mainClassName);
+      assertEquals(expectedResult, javaResult);
+    } else {
+      try {
+        runOnJava(jasminBuilder, mainClassName);
+        fail("Should have failed on JVM.");
+      } catch (AssertionError | InvalidPathException e) {
+        // Silent on expected failure.
+      }
+    }
+
+    if (validForArt) {
+      String artResult = runOnArtD8(jasminBuilder, mainClassName);
+      assertEquals(expectedResult, artResult);
+    } else {
+      // Make sure the compiler fails.
+      try {
+        runOnArtD8(jasminBuilder, mainClassName);
+        fail("D8 should have rejected this case.");
+      } catch (CompilationError t) {
+        assertTrue(t.getMessage().contains(expectedNameInFailingD8Message));
+      }
+
+      // Make sure ART also fail, if D8 rejects it.
+      try {
+        runOnArtD8(
+            jasminBuilder,
+            mainClassName,
+            options -> {
+              options.itemFactory.setSkipNameValidationForTesting(true);
+            });
+        fail("Art should have failed.");
+      } catch (AssertionError e) {
+        // Silent on expected failure.
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
index 65dc98f..315e980 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -251,10 +251,10 @@
 
   @Test
   public void testTrivialKs() throws Exception {
-    final String mainClassName = "lambdas.kstyle.trivial.MainKt";
+    final String mainClassName = "lambdas_kstyle_trivial.MainKt";
     runTest("lambdas_kstyle_trivial", mainClassName, null, (app) -> {
       Verifier verifier = new Verifier(app);
-      String pkg = "lambdas/kstyle/trivial";
+      String pkg = "lambdas_kstyle_trivial";
 
       verifier.assertLambdaGroups(
           allowAccessModification ?
@@ -292,10 +292,10 @@
 
   @Test
   public void testCapturesKs() throws Exception {
-    final String mainClassName = "lambdas.kstyle.captures.MainKt";
+    final String mainClassName = "lambdas_kstyle_captures.MainKt";
     runTest("lambdas_kstyle_captures", mainClassName, null, (app) -> {
       Verifier verifier = new Verifier(app);
-      String pkg = "lambdas/kstyle/captures";
+      String pkg = "lambdas_kstyle_captures";
       String grpPkg = allowAccessModification ? "" : pkg;
 
       verifier.assertLambdaGroups(
@@ -317,10 +317,10 @@
 
   @Test
   public void testGenericsNoSignatureKs() throws Exception {
-    final String mainClassName = "lambdas.kstyle.generics.MainKt";
+    final String mainClassName = "lambdas_kstyle_generics.MainKt";
     runTest("lambdas_kstyle_generics", mainClassName, null, (app) -> {
       Verifier verifier = new Verifier(app);
-      String pkg = "lambdas/kstyle/generics";
+      String pkg = "lambdas_kstyle_generics";
       String grpPkg = allowAccessModification ? "" : pkg;
 
       verifier.assertLambdaGroups(
@@ -338,10 +338,10 @@
 
   @Test
   public void testInnerClassesAndEnclosingMethodsKs() throws Exception {
-    final String mainClassName = "lambdas.kstyle.generics.MainKt";
+    final String mainClassName = "lambdas_kstyle_generics.MainKt";
     runTest("lambdas_kstyle_generics", mainClassName, KEEP_INNER_AND_ENCLOSING, (app) -> {
       Verifier verifier = new Verifier(app);
-      String pkg = "lambdas/kstyle/generics";
+      String pkg = "lambdas_kstyle_generics";
       String grpPkg = allowAccessModification ? "" : pkg;
 
       verifier.assertLambdaGroups(
@@ -361,10 +361,10 @@
 
   @Test
   public void testGenericsSignatureInnerEnclosingKs() throws Exception {
-    final String mainClassName = "lambdas.kstyle.generics.MainKt";
+    final String mainClassName = "lambdas_kstyle_generics.MainKt";
     runTest("lambdas_kstyle_generics", mainClassName, KEEP_SIGNATURE_INNER_ENCLOSING, (app) -> {
       Verifier verifier = new Verifier(app);
-      String pkg = "lambdas/kstyle/generics";
+      String pkg = "lambdas_kstyle_generics";
       String grpPkg = allowAccessModification ? "" : pkg;
 
       verifier.assertLambdaGroups(
@@ -386,17 +386,17 @@
 
   @Test
   public void testTrivialJs() throws Exception {
-    final String mainClassName = "lambdas.jstyle.trivial.MainKt";
+    final String mainClassName = "lambdas_jstyle_trivial.MainKt";
     runTest("lambdas_jstyle_trivial", mainClassName, null, (app) -> {
       Verifier verifier = new Verifier(app);
-      String pkg = "lambdas/jstyle/trivial";
+      String pkg = "lambdas_jstyle_trivial";
       String grp = allowAccessModification ? "" : pkg;
 
-      String supplier = "Lambdas$Supplier";
-      String intSupplier = "Lambdas$IntSupplier";
-      String consumer = "Lambdas$Consumer";
-      String intConsumer = "Lambdas$IntConsumer";
-      String multiFunction = "Lambdas$MultiFunction";
+      String supplier = "lambdas_jstyle_trivial.Lambdas$Supplier";
+      String intSupplier = "lambdas_jstyle_trivial.Lambdas$IntSupplier";
+      String consumer = "lambdas_jstyle_trivial.Lambdas$Consumer";
+      String intConsumer = "lambdas_jstyle_trivial.Lambdas$IntConsumer";
+      String multiFunction = "lambdas_jstyle_trivial.Lambdas$MultiFunction";
 
       verifier.assertLambdaGroups(
           jstyle(grp, 0, intSupplier, 2),
@@ -434,10 +434,10 @@
 
   @Test
   public void testSingleton() throws Exception {
-    final String mainClassName = "lambdas.singleton.MainKt";
+    final String mainClassName = "lambdas_singleton.MainKt";
     runTest("lambdas_singleton", mainClassName, null, (app) -> {
       Verifier verifier = new Verifier(app);
-      String pkg = "lambdas/singleton";
+      String pkg = "lambdas_singleton";
       String grp = allowAccessModification ? "" : pkg;
 
       verifier.assertLambdaGroups(
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 5df6bd5..35df33e 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -639,7 +639,8 @@
               DexEncodedField.EMPTY_ARRAY,
               DexEncodedField.EMPTY_ARRAY,
               directMethods,
-              DexEncodedMethod.EMPTY_ARRAY));
+              DexEncodedMethod.EMPTY_ARRAY,
+              false));
     }
     DirectMappedDexApplication application = builder.build().toDirect();
     ApplicationWriter writer =
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index fe1f1eb..ae2a432 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -57,8 +59,7 @@
 public class TreeShakingTest {
 
   private static final Path ANDROID_JAR = ToolHelper.getDefaultAndroidJar();
-  private static final List<Path> JAR_LIBRARIES = ImmutableList
-      .of(ANDROID_JAR, Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"));
+  private final List<Path> JAR_LIBRARIES;
   private static final String EMPTY_FLAGS = "src/test/proguard/valid/empty.flags";
   private static final Set<String> IGNORED_FLAGS = ImmutableSet.of(
       "examples/minification:conflict-mapping.txt",
@@ -66,33 +67,35 @@
   );
   private static final Set<String> IGNORED = ImmutableSet.of(
       // there's no point in running those without obfuscation
-      "examples/shaking1:keep-rules-repackaging.txt:DEX:NONE",
-      "examples/shaking1:keep-rules-repackaging.txt:JAR:NONE",
-      "examples/shaking16:keep-rules-1.txt:DEX:NONE",
-      "examples/shaking16:keep-rules-1.txt:JAR:NONE",
-      "examples/shaking16:keep-rules-2.txt:DEX:NONE",
-      "examples/shaking16:keep-rules-2.txt:JAR:NONE",
-      "examples/shaking15:keep-rules.txt:DEX:NONE",
-      "examples/shaking15:keep-rules.txt:JAR:NONE",
-      "examples/minifygeneric:keep-rules.txt:DEX:NONE",
-      "examples/minifygeneric:keep-rules.txt:JAR:NONE",
-      "examples/minifygenericwithinner:keep-rules.txt:DEX:NONE",
-      "examples/minifygenericwithinner:keep-rules.txt:JAR:NONE",
+      "examples/shaking1:keep-rules-repackaging.txt:*:NONE",
+      "examples/shaking16:keep-rules-1.txt:*:NONE",
+      "examples/shaking16:keep-rules-2.txt:*:NONE",
+      "examples/shaking15:keep-rules.txt:*:NONE",
+      "examples/minifygeneric:keep-rules.txt:*:NONE",
+      "examples/minifygenericwithinner:keep-rules.txt:*:NONE",
       // No prebuild DEX files for AndroidN
-      "examplesAndroidN/shaking:keep-rules.txt:DEX:NONE",
-      "examplesAndroidN/shaking:keep-rules.txt:DEX:JAVA",
-      "examplesAndroidN/shaking:keep-rules.txt:DEX:AGGRESSIVE"
+      "examplesAndroidN/shaking:keep-rules.txt:DEX:DEX:*",
+      // TODO(b/75997473): No inlining in R8/CF yet
+      "examples/inlining:keep-rules-discard.txt:JAR:CF:*",
+      "examples/inlining:keep-rules.txt:JAR:CF:*"
   );
 
   private static Set<String> SKIPPED = Collections.emptySet();
 
   private final MinifyMode minify;
+  private Path proguardMap;
+  private Path out;
 
   private enum Frontend {
     DEX, JAR
-
   }
-  private final Frontend kind;
+
+  private enum Backend {
+    DEX, CF
+  }
+
+  private final Frontend frontend;
+  private final Backend backend;
   private final String originalDex;
   private final String programFile;
   private final String mainClass;
@@ -105,13 +108,14 @@
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
-  public TreeShakingTest(String test, Frontend kind, String mainClass, List<String> keepRulesFiles,
-      MinifyMode minify, Consumer<DexInspector> inspection,
+  public TreeShakingTest(String test, Frontend frontend, Backend backend, String mainClass,
+      List<String> keepRulesFiles, MinifyMode minify, Consumer<DexInspector> inspection,
       BiConsumer<String, String> outputComparator,
       BiConsumer<DexInspector, DexInspector> dexComparator) {
-    this.kind = kind;
+    this.frontend = frontend;
+    this.backend = backend;
     originalDex = ToolHelper.TESTS_BUILD_DIR + test + "/classes.dex";
-    if (kind == Frontend.DEX) {
+    if (frontend == Frontend.DEX) {
       this.programFile = originalDex;
     } else {
       this.programFile = ToolHelper.TESTS_BUILD_DIR + test + ".jar";
@@ -122,12 +126,23 @@
     this.minify = minify;
     this.outputComparator = outputComparator;
     this.dexComparator = dexComparator;
+    if (backend == Backend.CF) {
+      JAR_LIBRARIES =
+          ImmutableList.of(
+              Paths.get(ToolHelper.JAVA_8_RUNTIME),
+              Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"));
+    } else {
+      JAR_LIBRARIES =
+          ImmutableList.of(
+              ANDROID_JAR, Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"));
+    }
   }
 
   @Before
   public void generateTreeShakedVersion() throws Exception {
+    out = temp.getRoot().toPath().resolve("out.zip");
+    proguardMap = temp.getRoot().toPath().resolve(DEFAULT_PROGUARD_MAP_FILE);
     // Generate R8 processed version without library option.
-    Path out = temp.getRoot().toPath();
     boolean inline = programFile.contains("inlining");
 
     R8Command.Builder builder =
@@ -135,15 +150,24 @@
                 R8Command.builder(),
                 pgConfig -> {
                   pgConfig.setPrintMapping(true);
-                  pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
+                  pgConfig.setPrintMappingFile(proguardMap);
                   pgConfig.setOverloadAggressively(minify == MinifyMode.AGGRESSIVE);
                   if (!minify.isMinify()) {
                     pgConfig.disableObfuscation();
                   }
                 })
-            .setOutput(out, OutputMode.DexIndexed)
             .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
             .addLibraryFiles(JAR_LIBRARIES);
+    switch (backend) {
+      case CF:
+        builder.setOutput(out, OutputMode.ClassFile);
+        break;
+      case DEX:
+        builder.setOutput(out, OutputMode.DexIndexed);
+        break;
+      default:
+        throw new Unreachable();
+    }
     ToolHelper.getAppBuilder(builder).addProgramFiles(Paths.get(programFile));
     ToolHelper.runR8(builder.build(), options -> options.enableInlining = inline);
   }
@@ -364,6 +388,10 @@
     ClassSubject mainClass = inspector.clazz("shaking13.Shaking");
     MethodSubject testMethod = mainClass.method("void", "fieldTest", Collections.emptyList());
     Assert.assertTrue(testMethod.isPresent());
+    if (testMethod.getMethod().getCode().isJarCode()) {
+      // TODO(mathiasr): Implement iterateInstructions() for JarCode/CfCode
+      return;
+    }
     Iterator<FieldAccessInstructionSubject> iterator =
         testMethod.iterateInstructions(InstructionSubject::isFieldAccess);
     Assert.assertTrue(iterator.hasNext() && iterator.next().holder().is("shakinglib.LibraryClass"));
@@ -617,7 +645,7 @@
         clazz.field(signature.type, signature.name).isPresent());
   }
 
-  @Parameters(name = "dex: {0} frontend: {1} keep: {3} minify: {4}")
+  @Parameters(name = "dex:{0} mode:{1}-{2} keep:{4} minify:{5}")
   public static Collection<Object[]> data() {
     List<String> tests = Arrays
         .asList(
@@ -837,21 +865,38 @@
       return;
     }
     for (MinifyMode mode : MinifyMode.values()) {
-      addTestCase(testCases, test, Frontend.JAR, mainClass, keepName, keepList, mode, inspection,
-          outputComparator, dexComparator);
-      addTestCase(testCases, test, Frontend.DEX, mainClass, keepName, keepList, mode, inspection,
-          outputComparator, dexComparator);
+      addTestCase(testCases, test, Frontend.JAR, Backend.CF, mainClass, keepName, keepList, mode,
+          inspection, outputComparator, dexComparator);
+      addTestCase(testCases, test, Frontend.JAR, Backend.DEX, mainClass, keepName, keepList, mode,
+          inspection, outputComparator, dexComparator);
+      addTestCase(testCases, test, Frontend.DEX, Backend.DEX, mainClass, keepName, keepList, mode,
+          inspection, outputComparator, dexComparator);
     }
   }
 
-  private static void addTestCase(List<Object[]> testCases, String test, Frontend kind,
-      String mainClass, String keepName, List<String> keepList, MinifyMode minify,
+  private static void addTestCase(List<Object[]> testCases, String test, Frontend frontend,
+      Backend backend, String mainClass, String keepName, List<String> keepList, MinifyMode minify,
       Consumer<DexInspector> inspection, BiConsumer<String, String> outputComparator,
       BiConsumer<DexInspector, DexInspector> dexComparator) {
+    String full = test + ":" + keepName + ":" + frontend + ":" + backend + ":" + minify;
+    String anyKind = test + ":" + keepName + ":*:" + minify;
+    String anyMinify = test + ":" + keepName + ":" + frontend + ":" + backend + ":*";
     if (!IGNORED_FLAGS.contains(test + ":" + keepName)
-        && !IGNORED.contains(test + ":" + keepName + ":" + kind + ":" + minify)) {
-      testCases.add(new Object[]{
-          test, kind, mainClass, keepList, minify, inspection, outputComparator, dexComparator});
+        && !IGNORED.contains(full)
+        && !IGNORED.contains(anyKind)
+        && !IGNORED.contains(anyMinify)) {
+      testCases.add(
+          new Object[] {
+            test,
+            frontend,
+            backend,
+            mainClass,
+            keepList,
+            minify,
+            inspection,
+            outputComparator,
+            dexComparator
+          });
     }
   }
 
@@ -888,11 +933,32 @@
 
   @Test
   public void treeShakingTest() throws IOException, InterruptedException, ExecutionException {
+    if (backend == Backend.CF) {
+      Path shakinglib = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "shakinglib.jar");
+      ProcessResult resultInput =
+          ToolHelper.runJava(Arrays.asList(Paths.get(programFile), shakinglib), mainClass);
+      Assert.assertEquals(0, resultInput.exitCode);
+      ProcessResult resultOutput =
+          ToolHelper.runJava(Arrays.asList(out, shakinglib), mainClass);
+      if (outputComparator != null) {
+        outputComparator.accept(resultInput.stdout, resultOutput.stdout);
+      } else {
+        Assert.assertEquals(resultInput.toString(), resultOutput.toString());
+      }
+      if (inspection != null) {
+        DexInspector inspector =
+            new DexInspector(
+                out,
+                minify.isMinify()
+                    ? proguardMap.toString()
+                    : null);
+        inspection.accept(inspector);
+      }
+      return;
+    }
     if (!ToolHelper.artSupported()) {
       return;
     }
-    String out = temp.getRoot().getCanonicalPath();
-    Path generated = Paths.get(out, "classes.dex");
     Consumer<ArtCommandBuilder> extraArtArgs = builder -> {
       builder.appendClasspath(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib/classes.dex");
     };
@@ -902,19 +968,18 @@
         String output1 = ToolHelper.runArtNoVerificationErrors(
             Collections.singletonList(originalDex), mainClass, extraArtArgs, null);
         String output2 = ToolHelper.runArtNoVerificationErrors(
-            Collections.singletonList(generated.toString()), mainClass, extraArtArgs, null);
+            Collections.singletonList(out.toString()), mainClass, extraArtArgs, null);
         outputComparator.accept(output1, output2);
       } else {
         ToolHelper.checkArtOutputIdentical(Collections.singletonList(originalDex),
-            Collections.singletonList(generated.toString()), mainClass,
+            Collections.singletonList(out.toString()), mainClass,
             extraArtArgs, null);
       }
 
       if (dexComparator != null) {
         DexInspector ref = new DexInspector(Paths.get(originalDex));
-        DexInspector inspector = new DexInspector(generated,
-            minify.isMinify() ? temp.getRoot().toPath().resolve(DEFAULT_PROGUARD_MAP_FILE)
-                .toString()
+        DexInspector inspector = new DexInspector(out,
+            minify.isMinify() ? proguardMap.toString()
                 : null);
         dexComparator.accept(ref, inspector);
       }
@@ -922,12 +987,12 @@
       Assert.assertNull(outputComparator);
       Assert.assertNull(dexComparator);
       ToolHelper.runArtNoVerificationErrors(
-          Collections.singletonList(generated.toString()), mainClass, extraArtArgs, null);
+          Collections.singletonList(out.toString()), mainClass, extraArtArgs, null);
     }
 
     if (inspection != null) {
-      DexInspector inspector = new DexInspector(generated,
-          minify.isMinify() ? temp.getRoot().toPath().resolve(DEFAULT_PROGUARD_MAP_FILE).toString()
+      DexInspector inspector = new DexInspector(out,
+          minify.isMinify() ? proguardMap.toString()
               : null);
       inspection.accept(inspector);
     }
diff --git a/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/Lambdas.java b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/Lambdas.java
index 69e9a78..2352efd 100644
--- a/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/Lambdas.java
+++ b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/Lambdas.java
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+package lambdas_jstyle_trivial;
+
 public class Lambdas {
   public interface IntConsumer {
     void put(int value);
diff --git a/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/inner.kt b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/inner/inner.kt
similarity index 85%
rename from src/test/kotlinR8TestResources/lambdas_jstyle_trivial/inner.kt
rename to src/test/kotlinR8TestResources/lambdas_jstyle_trivial/inner/inner.kt
index 5da8884..b0afcc3 100644
--- a/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/inner.kt
+++ b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/inner/inner.kt
@@ -2,11 +2,11 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package lambdas.jstyle.trivial.inner
+package lambdas_jstyle_trivial.inner
 
-import Lambdas
-import lambdas.jstyle.trivial.next
-import lambdas.jstyle.trivial.nextInt
+import lambdas_jstyle_trivial.Lambdas
+import lambdas_jstyle_trivial.next
+import lambdas_jstyle_trivial.nextInt
 
 fun testInner() {
     testInner1(nextInt(), nextInt(), nextInt(), nextInt())
diff --git a/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/main.kt b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/main.kt
index 39673af..6cdae60 100644
--- a/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/main.kt
+++ b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/main.kt
@@ -2,10 +2,10 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package lambdas.jstyle.trivial
+package lambdas_jstyle_trivial
 
-import Lambdas
-import lambdas.jstyle.trivial.inner.testInner
+import lambdas_jstyle_trivial.Lambdas
+import lambdas_jstyle_trivial.inner.testInner
 
 private var COUNT = 0
 
diff --git a/src/test/kotlinR8TestResources/lambdas_kstyle_captures/main.kt b/src/test/kotlinR8TestResources/lambdas_kstyle_captures/main.kt
index 3ee0bf8..62727bd 100644
--- a/src/test/kotlinR8TestResources/lambdas_kstyle_captures/main.kt
+++ b/src/test/kotlinR8TestResources/lambdas_kstyle_captures/main.kt
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package lambdas.kstyle.captures
+package lambdas_kstyle_captures
 
 fun consume(l: () -> String) = l()
 
diff --git a/src/test/kotlinR8TestResources/lambdas_kstyle_generics/main.kt b/src/test/kotlinR8TestResources/lambdas_kstyle_generics/main.kt
index 7fb22eb..e2d044d 100644
--- a/src/test/kotlinR8TestResources/lambdas_kstyle_generics/main.kt
+++ b/src/test/kotlinR8TestResources/lambdas_kstyle_generics/main.kt
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package lambdas.kstyle.generics
+package lambdas_kstyle_generics
 
 private var COUNT = 11
 
diff --git a/src/test/kotlinR8TestResources/lambdas_kstyle_trivial/inner.kt b/src/test/kotlinR8TestResources/lambdas_kstyle_trivial/inner/inner.kt
similarity index 79%
rename from src/test/kotlinR8TestResources/lambdas_kstyle_trivial/inner.kt
rename to src/test/kotlinR8TestResources/lambdas_kstyle_trivial/inner/inner.kt
index 00587a9..c22a3b8 100644
--- a/src/test/kotlinR8TestResources/lambdas_kstyle_trivial/inner.kt
+++ b/src/test/kotlinR8TestResources/lambdas_kstyle_trivial/inner/inner.kt
@@ -2,11 +2,11 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package lambdas.kstyle.trivial.inner
+package lambdas_kstyle_trivial.inner
 
-import lambdas.kstyle.trivial.consumeEmpty
-import lambdas.kstyle.trivial.consumeOne
-import lambdas.kstyle.trivial.consumeTwo
+import lambdas_kstyle_trivial.consumeEmpty
+import lambdas_kstyle_trivial.consumeOne
+import lambdas_kstyle_trivial.consumeTwo
 
 fun testInner() {
     testInnerStateless()
diff --git a/src/test/kotlinR8TestResources/lambdas_kstyle_trivial/main.kt b/src/test/kotlinR8TestResources/lambdas_kstyle_trivial/main.kt
index 34ba194..42cce37 100644
--- a/src/test/kotlinR8TestResources/lambdas_kstyle_trivial/main.kt
+++ b/src/test/kotlinR8TestResources/lambdas_kstyle_trivial/main.kt
@@ -2,9 +2,9 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package lambdas.kstyle.trivial
+package lambdas_kstyle_trivial
 
-import lambdas.kstyle.trivial.inner.testInner
+import lambdas_kstyle_trivial.inner.testInner
 
 private var COUNT = 11
 
diff --git a/src/test/kotlinR8TestResources/lambdas_singleton/Lambdas.java b/src/test/kotlinR8TestResources/lambdas_singleton/Lambdas.java
deleted file mode 100644
index 4140293..0000000
--- a/src/test/kotlinR8TestResources/lambdas_singleton/Lambdas.java
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-public class Lambdas {
-  public interface Function<R, P1, P2> {
-    R get(P1 a, P2 b);
-  }
-
-  public synchronized static <R, P1, P2> void accept(Function<R, P1, P2> s, P1 a, P2 b) {
-    System.out.println(s.get(a, b));
-  }
-}
diff --git a/src/test/kotlinR8TestResources/lambdas_singleton/main.kt b/src/test/kotlinR8TestResources/lambdas_singleton/main.kt
index 8e34a0c..f06904a 100644
--- a/src/test/kotlinR8TestResources/lambdas_singleton/main.kt
+++ b/src/test/kotlinR8TestResources/lambdas_singleton/main.kt
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package lambdas.singleton
+package lambdas_singleton
 
 private var COUNT = 0
 
diff --git a/src/test/sampleApks/simple/AndroidManifest.xml b/src/test/sampleApks/simple/AndroidManifest.xml
new file mode 100644
index 0000000..f959dbd
--- /dev/null
+++ b/src/test/sampleApks/simple/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+for details. All rights reserved. Use of this source code is governed by a
+BSD-style license that can be found in the LICENSE file.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.tools.r8.sample.simple"
+    android:versionCode="1"
+    android:versionName="0.1" >
+
+  <uses-sdk android:minSdkVersion="21" />
+
+  <application
+      android:icon="@drawable/icon"
+      android:label="@string/app_name" >
+    <activity
+        android:name=".R8Activity"
+        android:label="@string/app_name" >
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+  </application>
+</manifest>
diff --git a/src/test/sampleApks/simple/assets/README.txt b/src/test/sampleApks/simple/assets/README.txt
new file mode 100644
index 0000000..fdf73af
--- /dev/null
+++ b/src/test/sampleApks/simple/assets/README.txt
@@ -0,0 +1 @@
+Sample app from R8 project
diff --git a/src/test/sampleApks/simple/res/drawable-mdpi/icon.png b/src/test/sampleApks/simple/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..0799d58
--- /dev/null
+++ b/src/test/sampleApks/simple/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/src/test/sampleApks/simple/res/layout/main.xml b/src/test/sampleApks/simple/res/layout/main.xml
new file mode 100644
index 0000000..7859435
--- /dev/null
+++ b/src/test/sampleApks/simple/res/layout/main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+for details. All rights reserved. Use of this source code is governed by a
+BSD-style license that can be found in the LICENSE file.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="fill_parent"
+    android:layout_height="fill_parent" android:id="@+id/MainLayout"
+    android:background="@android:color/background_light">
+
+  <Button
+      android:layout_height="wrap_content"
+      android:layout_width="match_parent" android:id="@+id/PressButton"
+      android:layout_margin="2dip"
+      android:text="Do something"/>
+</LinearLayout>
diff --git a/src/test/sampleApks/simple/res/values/strings.xml b/src/test/sampleApks/simple/res/values/strings.xml
new file mode 100644
index 0000000..8bae26c
--- /dev/null
+++ b/src/test/sampleApks/simple/res/values/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+for details. All rights reserved. Use of this source code is governed by a
+BSD-style license that can be found in the LICENSE file.
+-->
+<resources>
+  <string name="app_name">R8 simple app</string>
+</resources>
diff --git a/src/test/sampleApks/simple/src/com/android/tools/r8/sample/simple/R8Activity.java b/src/test/sampleApks/simple/src/com/android/tools/r8/sample/simple/R8Activity.java
new file mode 100644
index 0000000..4a09fd4
--- /dev/null
+++ b/src/test/sampleApks/simple/src/com/android/tools/r8/sample/simple/R8Activity.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.sample.simple;
+
+import android.app.Activity;
+import android.os.Bundle;
+import com.android.tools.r8.sample.simple.R;
+
+public class R8Activity extends Activity {
+  public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setTheme(android.R.style.Theme_Light);
+    setContentView(R.layout.main);
+  }
+}
diff --git a/third_party/desugar/desugar_20180308.tar.gz.sha1 b/third_party/desugar/desugar_20180308.tar.gz.sha1
new file mode 100644
index 0000000..bc15c22
--- /dev/null
+++ b/third_party/desugar/desugar_20180308.tar.gz.sha1
@@ -0,0 +1 @@
+e31fb39c34a94592056e878462f086ffb922d1d0
\ No newline at end of file
diff --git a/third_party/openjdk/openjdk-rt-1.8.tar.gz.sha1 b/third_party/openjdk/openjdk-rt-1.8.tar.gz.sha1
new file mode 100644
index 0000000..47ebc7c
--- /dev/null
+++ b/third_party/openjdk/openjdk-rt-1.8.tar.gz.sha1
@@ -0,0 +1 @@
+f2a21bbd0efa0d78617b8f4f4ae42ac0bcc4ec4c
\ No newline at end of file
diff --git a/tools/apk-masseur.py b/tools/apk-masseur.py
index 8e98e47..4f24f0f 100755
--- a/tools/apk-masseur.py
+++ b/tools/apk-masseur.py
@@ -3,6 +3,7 @@
 # for details. All rights reserved. Use of this source code is governed by a
 # BSD-style license that can be found in the LICENSE file.
 
+import apk_utils
 import glob
 import optparse
 import os
@@ -68,23 +69,8 @@
   return processed_apk
 
 def sign(unsigned_apk, keystore, temp):
-  print 'Signing (ignore the warnings)'
-  cmd = ['zip', '-d', unsigned_apk, 'META-INF/*']
-  utils.PrintCmd(cmd)
-  subprocess.call(cmd)
   signed_apk = os.path.join(temp, 'unaligned.apk')
-  cmd = [
-    'jarsigner',
-    '-sigalg', 'SHA1withRSA',
-    '-digestalg', 'SHA1',
-    '-keystore', keystore,
-    '-storepass', 'android',
-    '-signedjar', signed_apk,
-    unsigned_apk,
-    'androiddebugkey'
-  ]
-  utils.PrintCmd(cmd)
-  subprocess.check_call(cmd)
+  apk_utils.sign(unsigned_apk, signed_apk, keystore)
   return signed_apk
 
 def align(signed_apk, temp):
diff --git a/tools/apk_utils.py b/tools/apk_utils.py
new file mode 100644
index 0000000..a4c0471
--- /dev/null
+++ b/tools/apk_utils.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+import subprocess
+import utils
+
+def sign(unsigned_apk, signed_apk, keystore):
+  print 'Signing (ignore the warnings)'
+  cmd = ['zip', '-d', unsigned_apk, 'META-INF/*']
+  utils.PrintCmd(cmd)
+  subprocess.call(cmd)
+  cmd = [
+    'jarsigner',
+    '-sigalg', 'SHA1withRSA',
+    '-digestalg', 'SHA1',
+    '-keystore', keystore,
+    '-storepass', 'android',
+    '-signedjar', signed_apk,
+    unsigned_apk,
+    'androiddebugkey'
+  ]
+  utils.PrintCmd(cmd)
+  subprocess.check_call(cmd)
diff --git a/tools/build_sample_apk.py b/tools/build_sample_apk.py
new file mode 100755
index 0000000..5d691d9
--- /dev/null
+++ b/tools/build_sample_apk.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python
+# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Script for building sample apks using the sdk tools directly.
+
+import apk_utils
+import fnmatch
+import glob
+import optparse
+import os
+import shutil
+import subprocess
+import sys
+import utils
+
+ANDROID_JAR = 'third_party/android_jar/lib-v{api}/android.jar'
+DEFAULT_AAPT = 'aapt' # Assume in path.
+DEFAULT_D8 = os.path.join(utils.REPO_ROOT, 'tools', 'd8.py')
+DEFAULT_JAVAC = 'javac'
+SRC_LOCATION = 'src/com/android/tools/r8/sample/{app}/*.java'
+DEFAULT_KEYSTORE = os.path.join(os.getenv('HOME'), '.android', 'debug.keystore')
+
+SAMPLE_APKS = [
+    'simple'
+]
+
+def parse_options():
+  result = optparse.OptionParser()
+  result.add_option('--aapt',
+                    help='aapt executable to use',
+                    default=DEFAULT_AAPT)
+  result.add_option('--api',
+                    help='Android api level',
+                    default=21,
+                    choices=[14, 15, 19, 21, 22, 23, 24, 25, 26])
+  result.add_option('--keystore',
+                    help='Keystore used for signing',
+                    default=DEFAULT_KEYSTORE)
+  result.add_option('--app',
+                    help='Which app to build',
+                    default='simple',
+                    choices=SAMPLE_APKS)
+  return result.parse_args()
+
+def run_aapt(aapt, args):
+  command = [aapt]
+  command.extend(args)
+  utils.PrintCmd(command)
+  subprocess.check_call(command)
+
+def get_build_dir(app):
+  return os.path.join(utils.BUILD, 'sampleApks', app)
+
+def get_gen_path(app):
+  gen_path = os.path.join(get_build_dir(app), 'gen')
+  utils.makedirs_if_needed(gen_path)
+  return gen_path
+
+def get_bin_path(app):
+  bin_path = os.path.join(get_build_dir(app), 'bin')
+  utils.makedirs_if_needed(bin_path)
+  return bin_path
+
+def get_android_jar(api):
+  return os.path.join(utils.REPO_ROOT, ANDROID_JAR.format(api=api))
+
+def get_sample_dir(app):
+  return os.path.join(utils.REPO_ROOT, 'src', 'test', 'sampleApks', app)
+
+def get_src_path(app):
+  return os.path.join(get_sample_dir(app), 'src')
+
+def run_aapt_pack(aapt, api, app):
+  with utils.ChangedWorkingDirectory(get_sample_dir(app)):
+    args = ['package',
+            '-v', '-f',
+            '-I', get_android_jar(api),
+            '-M', 'AndroidManifest.xml',
+            '-A', 'assets',
+            '-S', 'res',
+            '-m',
+            '-J', get_gen_path(app),
+            '-F', os.path.join(get_bin_path(app), 'resources.ap_')]
+    run_aapt(aapt, args)
+
+def compile_with_javac(api, app):
+  with utils.ChangedWorkingDirectory(get_sample_dir(app)):
+    files = glob.glob(SRC_LOCATION.format(app=app))
+    command = [DEFAULT_JAVAC,
+               '-classpath', get_android_jar(api),
+               '-sourcepath', '%s:%s' % (get_src_path(app), get_gen_path(app)),
+               '-d', get_bin_path(app)]
+    command.extend(files)
+    utils.PrintCmd(command)
+    subprocess.check_call(command)
+
+def dex(app, api):
+  files = []
+  for root, dirnames, filenames in os.walk(get_bin_path(app)):
+    for filename in fnmatch.filter(filenames, '*.class'):
+        files.append(os.path.join(root, filename))
+  command = [DEFAULT_D8,
+             '--output', get_bin_path(app),
+             '--classpath', get_android_jar(api),
+             '--min-api', str(api)]
+  command.extend(files)
+  utils.PrintCmd(command)
+  subprocess.check_call(command)
+
+def create_temp_apk(app):
+  temp_apk_path = os.path.join(get_bin_path(app), '%s.ap_' % app)
+  shutil.move(os.path.join(get_bin_path(app), 'resources.ap_'),
+              temp_apk_path)
+  return temp_apk_path
+
+def aapt_add_dex(aapt, app, temp_apk_path):
+  args = ['add',
+          '-k', temp_apk_path,
+          os.path.join(get_bin_path(app), 'classes.dex')]
+  run_aapt(aapt, args)
+
+def Main():
+  (options, args) = parse_options()
+  run_aapt_pack(options.aapt, options.api, options.app)
+  compile_with_javac(options.api, options.app)
+  dex(options.app, options.api)
+  temp_apk_path = create_temp_apk(options.app)
+  aapt_add_dex(options.aapt, options.app, temp_apk_path)
+  apk_path = os.path.join(get_bin_path(options.app), '%s.apk' % options.app)
+  apk_utils.sign(temp_apk_path, apk_path,  options.keystore)
+  print('Apk available at: %s' % apk_path)
+
+if __name__ == '__main__':
+  sys.exit(Main())