Merge "Let dev versions tag dex with version sha-1"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d0739b9..76a6ed1 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -117,8 +117,7 @@
     return result;
   }
 
-  static void runForTesting(AndroidApp app, OutputSink outputSink,
-      InternalOptions options)
+  static void runForTesting(AndroidApp app, OutputSink outputSink, InternalOptions options)
       throws IOException, CompilationException {
     ExecutorService executor = ThreadUtils.getExecutorService(options);
     try {
@@ -130,14 +129,14 @@
 
   private static void runForTesting(
       AndroidApp app,
-      OutputSink outputSink, InternalOptions options,
+      OutputSink outputSink,
+      InternalOptions options,
       ExecutorService executor)
       throws IOException, CompilationException {
     new R8(options).run(app, outputSink, executor);
   }
 
-  private void run(AndroidApp inputApp, OutputSink outputSink,
-      ExecutorService executorService)
+  private void run(AndroidApp inputApp, OutputSink outputSink, ExecutorService executorService)
       throws IOException, CompilationException {
     if (options.quiet) {
       System.setOut(new PrintStream(ByteStreams.nullOutputStream()));
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
new file mode 100644
index 0000000..af51fdc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -0,0 +1,306 @@
+// Copyright (c) 2017, 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.cf;
+
+import com.android.tools.r8.cf.code.CfArrayLoad;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfPop;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStaticGet;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.HashMap;
+import java.util.Map;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Utility to print CF code and instructions.
+ *
+ * <p>This implementation prints the code formatted according to the Jasmin syntax.
+ */
+public class CfPrinter {
+
+  private final String indent;
+  private final Map<CfLabel, String> labels;
+
+  private final StringBuilder builder = new StringBuilder();
+
+  /** Entry for printing single instructions without global knowledge (eg, label numbers). */
+  public CfPrinter() {
+    indent = "";
+    labels = null;
+  }
+
+  /** Entry for printing a complete code object. */
+  public CfPrinter(CfCode code) {
+    indent = "  ";
+    labels = new HashMap<>();
+    int nextLabelNumber = 0;
+    for (CfInstruction instruction : code.getInstructions()) {
+      if (instruction instanceof CfLabel) {
+        labels.put((CfLabel) instruction, "L" + nextLabelNumber++);
+      }
+    }
+    builder.append(".method ");
+    appendMethod(code.getMethod());
+    newline();
+    builder.append(".limit stack ").append(code.getMaxStack());
+    newline();
+    builder.append(".limit locals ").append(code.getMaxLocals());
+    for (CfInstruction instruction : code.getInstructions()) {
+      instruction.print(this);
+    }
+    newline();
+    builder.append(".end method");
+    newline();
+  }
+
+  private void print(String name) {
+    indent();
+    builder.append(name);
+  }
+
+  public void print(CfConstNull constNull) {
+    print("aconst_null");
+  }
+
+  public void print(CfConstNumber constNumber) {
+    indent();
+    // TODO(zerny): Determine when the number matches the tconst_X instructions.
+    switch (constNumber.getType()) {
+      case INT:
+        builder.append("ldc ").append(constNumber.getIntValue());
+        break;
+      case FLOAT:
+        builder.append("ldc ").append(constNumber.getFloatValue());
+        break;
+      case LONG:
+        builder.append("ldc_w ").append(constNumber.getLongValue());
+        break;
+      case DOUBLE:
+        builder.append("ldc_w ").append(constNumber.getDoubleValue());
+        break;
+      default:
+        throw new Unreachable("Unexpected const-number type: " + constNumber.getType());
+    }
+  }
+
+  public void print(CfReturn ret) {
+    print("return");
+  }
+
+  public void print(CfPop pop) {
+    print("pop");
+  }
+
+  public void print(CfConstString constString) {
+    indent();
+    builder.append("ldc ").append(constString.getString());
+  }
+
+  public void print(CfArrayLoad arrayLoad) {
+    switch (arrayLoad.getType()) {
+      case OBJECT:
+        print("aaload");
+        break;
+      case BOOLEAN:
+      case BYTE:
+        print("baload");
+        break;
+      case CHAR:
+        print("caload");
+        break;
+      case SHORT:
+        print("saload");
+        break;
+      case INT:
+        print("iaload");
+        break;
+      case FLOAT:
+        print("faload");
+        break;
+      case LONG:
+        print("laload");
+        break;
+      case DOUBLE:
+        print("daload");
+        break;
+      default:
+        throw new Unreachable("Unexpected array-load type: " + arrayLoad.getType());
+    }
+  }
+
+  public void print(CfInvoke invoke) {
+    indent();
+    switch (invoke.getOpcode()) {
+      case Opcodes.INVOKEVIRTUAL:
+        builder.append("invokevirtual");
+        break;
+      case Opcodes.INVOKESPECIAL:
+        builder.append("invokespecial");
+        break;
+      case Opcodes.INVOKESTATIC:
+        builder.append("invokestatic");
+        break;
+      case Opcodes.INVOKEINTERFACE:
+        builder.append("invokeinterface");
+        break;
+      case Opcodes.INVOKEDYNAMIC:
+        builder.append("invokedynamic");
+        break;
+      default:
+        throw new Unreachable("Unexpected invoke opcode: " + invoke.getOpcode());
+    }
+    builder.append(' ');
+    appendMethod(invoke.getMethod());
+  }
+
+  public void print(CfFrame frame) {
+    comment("frame");
+  }
+
+  public void print(CfStaticGet staticGet) {
+    indent();
+    builder.append("getstatic ");
+    appendField(staticGet.getField());
+    appendDescriptor(staticGet.getField().type);
+  }
+
+  public void print(CfNew newInstance) {
+    indent();
+    builder.append("new ");
+    appendClass(newInstance.getType());
+  }
+
+  public void print(CfLabel label) {
+    newline();
+    builder.append(getLabel(label)).append(':');
+  }
+
+  public void print(CfGoto jump) {
+    indent();
+    builder.append("goto ").append(getLabel(jump.getTarget()));
+  }
+
+  public void print(CfIf conditional) {
+    indent();
+    switch (conditional.getOpcode()) {
+      case Opcodes.IFNULL:
+        builder.append("ifnull");
+        break;
+      case Opcodes.IFNONNULL:
+        builder.append("ifnonnull");
+        break;
+      case Opcodes.IFEQ:
+        builder.append("ifeq");
+        break;
+      case Opcodes.IFNE:
+        builder.append("ifne");
+        break;
+      case Opcodes.IFLT:
+        builder.append("iflt");
+        break;
+      case Opcodes.IFGE:
+        builder.append("ifge");
+        break;
+      case Opcodes.IFGT:
+        builder.append("ifgt");
+        break;
+      case Opcodes.IFLE:
+        builder.append("ifle");
+        break;
+      default:
+        throw new Unreachable();
+    }
+    builder.append(' ').append(getLabel(conditional.getTarget()));
+  }
+
+  public void print(CfLoad load) {
+    print(load.getType(), "load", load.getLocalIndex());
+  }
+
+  public void print(CfStore store) {
+    print(store.getType(), "store", store.getLocalIndex());
+  }
+
+  private void print(ValueType type, String instruction, int local) {
+    indent();
+    builder.append(typePrefix(type)).append(instruction).append(' ').append(local);
+  }
+
+  private char typePrefix(ValueType type) {
+    switch (type) {
+      case OBJECT:
+        return 'a';
+      case INT:
+        return 'i';
+      case FLOAT:
+        return 'f';
+      case LONG:
+        return 'l';
+      case DOUBLE:
+        return 'd';
+      default:
+        throw new Unreachable("Unexpected type for prefix: " + type);
+    }
+  }
+
+  private String getLabel(CfLabel label) {
+    return labels != null ? labels.get(label) : "L?";
+  }
+
+  private void newline() {
+    if (builder.length() > 0) {
+      builder.append('\n');
+    }
+  }
+
+  private void indent() {
+    newline();
+    builder.append(indent);
+  }
+
+  private void comment(String comment) {
+    indent();
+    builder.append("; ").append(comment);
+  }
+
+  private void appendDescriptor(DexType type) {
+    builder.append(type.toDescriptorString());
+  }
+
+  private void appendClass(DexType type) {
+    builder.append(type.getInternalName());
+  }
+
+  private void appendField(DexField field) {
+    appendClass(field.getHolder());
+    builder.append('/').append(field.name);
+  }
+
+  private void appendMethod(DexMethod method) {
+    builder.append(method.qualifiedName());
+    builder.append(method.proto.toDescriptorString());
+  }
+
+  @Override
+  public String toString() {
+    return builder.toString();
+  }
+}
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 9043ab6..3ff24b9 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
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 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.ir.code.MemberType;
 import org.objectweb.asm.MethodVisitor;
@@ -16,6 +17,10 @@
     this.type = type;
   }
 
+  public MemberType getType() {
+    return type;
+  }
+
   private int getLoadType() {
     switch (type) {
       case OBJECT:
@@ -44,4 +49,9 @@
   public void write(MethodVisitor visitor) {
     visitor.visitInsn(getLoadType());
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
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 f022f78..a788a3b 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
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -12,4 +13,9 @@
   public void write(MethodVisitor visitor) {
     visitor.visitInsn(Opcodes.ACONST_NULL);
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
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 1d51bab..c3b4490 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
@@ -3,9 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 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.ir.code.ValueType;
 import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 public class CfConstNumber extends CfInstruction {
 
@@ -17,23 +19,84 @@
     this.type = type;
   }
 
+  public ValueType getType() {
+    return type;
+  }
+
+  public long getRawValue() {
+    return value;
+  }
+
+  public int getIntValue() {
+    assert type == ValueType.INT;
+    return (int) value;
+  }
+
+  public long getLongValue() {
+    assert type == ValueType.LONG;
+    return value;
+  }
+
+  public float getFloatValue() {
+    assert type == ValueType.FLOAT;
+    return Float.intBitsToFloat((int) value);
+  }
+
+  public double getDoubleValue() {
+    assert type == ValueType.DOUBLE;
+    return Double.longBitsToDouble(value);
+  }
+
   @Override
   public void write(MethodVisitor visitor) {
     switch (type) {
       case INT:
-        visitor.visitLdcInsn((int)value);
-        break;
+        {
+          int value = getIntValue();
+          if (-1 <= value && value <= 5) {
+            visitor.visitInsn(Opcodes.ICONST_0 + value);
+          } else {
+            visitor.visitLdcInsn(value);
+          }
+          break;
+        }
       case LONG:
-        visitor.visitLdcInsn(value);
-        break;
+        {
+          long value = getLongValue();
+          if (value == 0 || value == 1) {
+            visitor.visitInsn(Opcodes.LCONST_0 + (int) value);
+          } else {
+            visitor.visitLdcInsn(value);
+          }
+          break;
+        }
       case FLOAT:
-        visitor.visitLdcInsn(Float.intBitsToFloat((int)value));
-        break;
+        {
+          float value = getFloatValue();
+          if (value == 0 || value == 1 || value == 2) {
+            visitor.visitInsn(Opcodes.FCONST_0 + (int) value);
+          } else {
+            visitor.visitLdcInsn(value);
+          }
+          break;
+        }
       case DOUBLE:
-        visitor.visitLdcInsn(Double.longBitsToDouble(value));
-        break;
+        {
+          double value = getDoubleValue();
+          if (value == 0 || value == 1) {
+            visitor.visitInsn(Opcodes.DCONST_0 + (int) value);
+          } else {
+            visitor.visitLdcInsn(value);
+          }
+          break;
+        }
       default:
         throw new Unreachable("Non supported type in cf backend: " + type);
     }
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
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 62f1537..0f7f9f7 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
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexString;
 import org.objectweb.asm.MethodVisitor;
 
@@ -14,8 +15,17 @@
     this.string = string;
   }
 
+  public DexString getString() {
+    return string;
+  }
+
   @Override
   public void write(MethodVisitor visitor) {
     visitor.visitLdcInsn(string.toString());
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
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 78a350d..1d946b2 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
@@ -5,6 +5,7 @@
 
 import static org.objectweb.asm.Opcodes.F_NEW;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -12,7 +13,6 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
 
 public class CfFrame extends CfInstruction {
 
@@ -45,7 +45,7 @@
     }
     switch (type.toShorty()) {
       case 'L':
-        return Type.getType(type.toDescriptorString()).getInternalName();
+        return type.getInternalName();
       case 'I':
         return Opcodes.INTEGER;
       case 'F':
@@ -63,4 +63,9 @@
   public String toString() {
     return getClass().getSimpleName();
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
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 2207e6f..c881e7b 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
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -14,8 +15,17 @@
     this.target = target;
   }
 
+  public CfLabel getTarget() {
+    return target;
+  }
+
   @Override
   public void write(MethodVisitor visitor) {
     visitor.visitJumpInsn(Opcodes.GOTO, target.getLabel());
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
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 b8397e5..5c7a2c7 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
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 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.ir.code.If;
 import com.android.tools.r8.ir.code.ValueType;
@@ -21,7 +22,11 @@
     this.target = target;
   }
 
-  private int getOpcode() {
+  public CfLabel getTarget() {
+    return target;
+  }
+
+  public int getOpcode() {
     switch (kind) {
       case EQ:
         return type.isObject() ? Opcodes.IFNULL : Opcodes.IFEQ;
@@ -41,6 +46,11 @@
   }
 
   @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  @Override
   public void write(MethodVisitor visitor) {
     visitor.visitJumpInsn(getOpcode(), target.getLabel());
   }
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 915a491..76806f6 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
@@ -3,14 +3,20 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import org.objectweb.asm.MethodVisitor;
 
 public abstract class CfInstruction {
 
   public abstract void write(MethodVisitor visitor);
 
+  public abstract void print(CfPrinter printer);
+
   @Override
   public String toString() {
-    return getClass().getSimpleName();
+    CfPrinter printer = new CfPrinter();
+    print(printer);
+    return printer.toString();
   }
+
 }
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 0e927ac..d90fb92 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
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexMethod;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
 
 public class CfInvoke extends CfInstruction {
 
@@ -19,12 +19,25 @@
     this.method = method;
   }
 
+  public DexMethod getMethod() {
+    return method;
+  }
+
+  public int getOpcode() {
+    return opcode;
+  }
+
   @Override
   public void write(MethodVisitor visitor) {
-    String owner = Type.getType(method.getHolder().toDescriptorString()).getInternalName();
+    String owner = method.getHolder().getInternalName();
     String name = method.name.toString();
     String desc = method.proto.toDescriptorString();
     boolean iface = method.holder.isInterface();
     visitor.visitMethodInsn(opcode, owner, name, desc, iface);
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
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 9c6560d..13d9078 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
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 
@@ -18,6 +19,11 @@
   }
 
   @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  @Override
   public void write(MethodVisitor visitor) {
     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 af053a8..7d6be37 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
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 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.ir.code.ValueType;
 import org.objectweb.asm.MethodVisitor;
@@ -39,4 +40,17 @@
   public void write(MethodVisitor visitor) {
     visitor.visitVarInsn(getLoadType(), var);
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  public ValueType getType() {
+    return type;
+  }
+
+  public int getLocalIndex() {
+    return var;
+  }
 }
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 b528ea5..48b2ca3 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
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
 
 public class CfNew extends CfInstruction {
 
@@ -16,8 +16,17 @@
     this.type = type;
   }
 
+  public DexType getType() {
+    return type;
+  }
+
   @Override
   public void write(MethodVisitor visitor) {
-    visitor.visitTypeInsn(Opcodes.NEW, Type.getType(type.toDescriptorString()).getInternalName());
+    visitor.visitTypeInsn(Opcodes.NEW, type.getInternalName());
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
   }
 }
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 e058df6..68cedb9 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
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.ir.code.ValueType;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -19,4 +20,9 @@
   public void write(MethodVisitor visitor) {
     visitor.visitInsn(type.isWide() ? Opcodes.POP2 : Opcodes.POP);
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
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 4cf2908..1a20d29 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
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -12,4 +13,9 @@
   public void write(MethodVisitor visitor) {
     visitor.visitInsn(Opcodes.RETURN);
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticGet.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticGet.java
index 6e71cb5..c5a68b6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStaticGet.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticGet.java
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexField;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
 
 public class CfStaticGet extends CfInstruction {
 
@@ -16,11 +16,20 @@
     this.field = field;
   }
 
+  public DexField getField() {
+    return field;
+  }
+
   @Override
   public void write(MethodVisitor visitor) {
-    String owner = Type.getType(field.getHolder().toDescriptorString()).getInternalName();
+    String owner = field.getHolder().getInternalName();
     String name = field.name.toString();
     String desc = field.type.toDescriptorString();
     visitor.visitFieldInsn(Opcodes.GETSTATIC, owner, name, desc);
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
 }
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 0074abc..2526a58 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
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 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.ir.code.ValueType;
 import org.objectweb.asm.MethodVisitor;
@@ -39,4 +40,17 @@
   public void write(MethodVisitor visitor) {
     visitor.visitVarInsn(getStoreType(), var);
   }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  public ValueType getType() {
+    return type;
+  }
+
+  public int getLocalIndex() {
+    return var;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java b/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java
index 73d5d86..4da7302 100644
--- a/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java
+++ b/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java
@@ -55,11 +55,11 @@
   boolean addMethod(DexMethod method);
 
   /**
-   * Adds the given class to the collection.
+   * Adds the given string to the collection.
    *
-   * <p>Does not add the classes components.
+   * <p>Does not add the string's components.
    *
-   * @return true if the class was not in the pool before.
+   * @return true if the string was not in the pool before.
    */
   boolean addString(DexString string);
 
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 7c76969..b0d0827 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -4,35 +4,55 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.Collections;
 import java.util.List;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Type;
 
 public class CfCode extends Code {
 
+  private final DexMethod method;
   private final int maxStack;
   private final int maxLocals;
   private final List<CfInstruction> instructions;
   private final List<CfTryCatch> tryCatchRanges;
 
   public CfCode(
+      DexMethod method,
       int maxStack,
       int maxLocals,
       List<CfInstruction> instructions,
       List<CfTryCatch> tryCatchRanges) {
+    this.method = method;
     this.maxStack = maxStack;
     this.maxLocals = maxLocals;
     this.instructions = instructions;
     this.tryCatchRanges = tryCatchRanges;
   }
 
+  public DexMethod getMethod() {
+    return method;
+  }
+
+  public int getMaxStack() {
+    return maxStack;
+  }
+
+  public int getMaxLocals() {
+    return maxLocals;
+  }
+
+  public List<CfInstruction> getInstructions() {
+    return Collections.unmodifiableList(instructions);
+  }
+
   @Override
   public boolean isCfCode() {
     return true;
@@ -59,9 +79,7 @@
             start,
             end,
             target,
-            guard == DexItemFactory.catchAllType
-                ? null
-                : Type.getType(guard.toDescriptorString()).getInternalName());
+            guard == DexItemFactory.catchAllType ? null : guard.getInternalName());
       }
     }
   }
@@ -89,11 +107,7 @@
 
   @Override
   public String toString() {
-    StringBuilder builder = new StringBuilder();
-    for (CfInstruction instruction : instructions) {
-      builder.append(instruction.toString()).append('\n');
-    }
-    return builder.toString();
+    return new CfPrinter(this).toString();
   }
 
   @Override
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 dcf2498..cb7ff79 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -122,11 +122,11 @@
   }
 
   public DexEncodedMethod[] allMethodsSorted() {
-    int vLen = virtualMethods().length;
-    int dLen = directMethods().length;
-    DexEncodedMethod[] result = new DexEncodedMethod[vLen+dLen];
-    System.arraycopy(virtualMethods(), 0, result, 0, vLen);
-    System.arraycopy(directMethods(), 0, result, vLen, dLen);
+    int vLen = virtualMethods.length;
+    int dLen = directMethods.length;
+    DexEncodedMethod[] result = new DexEncodedMethod[vLen + dLen];
+    System.arraycopy(virtualMethods, 0, result, 0, vLen);
+    System.arraycopy(directMethods, 0, result, vLen, dLen);
     Arrays.sort(result,
         (DexEncodedMethod a, DexEncodedMethod b) -> a.method.slowCompareTo(b.method));
     return result;
@@ -166,6 +166,17 @@
     instanceFields = MoreObjects.firstNonNull(values, NO_FIELDS);
   }
 
+  public DexEncodedField[] allFieldsSorted() {
+    int iLen = instanceFields.length;
+    int sLen = staticFields.length;
+    DexEncodedField[] result = new DexEncodedField[iLen + sLen];
+    System.arraycopy(instanceFields, 0, result, 0, iLen);
+    System.arraycopy(staticFields, 0, result, iLen, sLen);
+    Arrays.sort(result,
+        (DexEncodedField a, DexEncodedField b) -> a.field.slowCompareTo(b.field));
+    return result;
+  }
+
   /**
    * Find direct method in this class matching method
    */
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 8fbcbce..8445c54 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -573,6 +573,7 @@
     private boolean returnsConstant = false;
     private long returnedConstant = 0;
     private boolean forceInline = false;
+    private boolean useIdentifierNameString = false;
 
     private OptimizationInfo() {
       // Intentionally left empty.
@@ -584,6 +585,7 @@
       returnsConstant = template.returnsConstant;
       returnedConstant = template.returnedConstant;
       forceInline = template.forceInline;
+      useIdentifierNameString = template.useIdentifierNameString;
     }
 
     public boolean returnsArgument() {
@@ -612,6 +614,10 @@
       return forceInline;
     }
 
+    public boolean useIdentifierNameString() {
+      return useIdentifierNameString;
+    }
+
     private void markReturnsArgument(int argument) {
       assert argument >= 0;
       assert returnedArgument == -1 || returnedArgument == argument;
@@ -632,6 +638,10 @@
       forceInline = true;
     }
 
+    private void markUseIdentifierNameString() {
+      useIdentifierNameString = true;
+    }
+
     public OptimizationInfo copy() {
       return new OptimizationInfo(this);
     }
@@ -673,6 +683,10 @@
     ensureMutableOI().markForceInline();
   }
 
+  synchronized public void markUseIdentifierNameString() {
+    ensureMutableOI().markUseIdentifierNameString();
+  }
+
   public OptimizationInfo getOptimizationInfo() {
     return optimizationInfo;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemBasedString.java b/src/main/java/com/android/tools/r8/graph/DexItemBasedString.java
new file mode 100644
index 0000000..7777147
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexItemBasedString.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2017, 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.graph;
+
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.errors.Unreachable;
+import java.util.Arrays;
+
+public class DexItemBasedString extends DexString {
+  public final IndexedDexItem basedOn;
+
+  DexItemBasedString(DexType basedOn) {
+    super(basedOn.toString());
+    this.basedOn = basedOn;
+  }
+
+  DexItemBasedString(DexField basedOn) {
+    super(basedOn.name.toString());
+    this.basedOn = basedOn;
+  }
+
+  DexItemBasedString(DexMethod basedOn) {
+    super(basedOn.name.toString());
+    this.basedOn = basedOn;
+  }
+
+  @Override
+  public boolean computeEquals(Object other) {
+    if (other instanceof DexItemBasedString) {
+      DexItemBasedString o = (DexItemBasedString) other;
+      return basedOn == o.basedOn && size == o.size && Arrays.equals(content, o.content);
+    }
+    return false;
+  }
+
+  @Override
+  public int computeHashCode() {
+    return super.computeHashCode() + 7 * basedOn.hashCode();
+  }
+
+  @Override
+  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+    // This instance should not exist when collecting indexed items.
+    // {@link IdentifierMinifier} will replace this with an appropriate {@link DexString}.
+    throw new Unreachable("Remaining DexItemBasedString: " + this.toString());
+  }
+}
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 8f45a2e..0d4644a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -51,6 +51,10 @@
   private final Map<DexString, SetFile> setFiles = new HashMap<>();
   private final Map<SetInlineFrame, SetInlineFrame> setInlineFrames = new HashMap<>();
 
+  // -identifiernamestring canonicalization.
+  private final ConcurrentHashMap<DexItemBasedString, DexItemBasedString> identifiers =
+      new ConcurrentHashMap<>();
+
   boolean sorted = false;
 
   public static final DexType catchAllType = new DexType(new DexString("CATCH_ALL"));
@@ -109,10 +113,14 @@
   public final DexString objectDescriptor = createString("Ljava/lang/Object;");
   public final DexString objectArrayDescriptor = createString("[Ljava/lang/Object;");
   public final DexString classDescriptor = createString("Ljava/lang/Class;");
+  public final DexString fieldDescriptor = createString("Ljava/lang/reflect/Field;");
+  public final DexString methodDescriptor = createString("Ljava/lang/reflect/Method;");
   public final DexString enumDescriptor = createString("Ljava/lang/Enum;");
   public final DexString annotationDescriptor = createString("Ljava/lang/annotation/Annotation;");
   public final DexString throwableDescriptor = createString("Ljava/lang/Throwable;");
   public final DexString objectsDescriptor = createString("Ljava/util/Objects;");
+  public final DexString stringBuilderDescriptor = createString("Ljava/lang/StringBuilder;");
+  public final DexString stringBufferDescriptor = createString("Ljava/lang/StringBuffer;");
   public final DexString varHandleDescriptor = createString("Ljava/lang/invoke/VarHandle;");
 
   public final DexString constructorMethodName = createString(Constants.INSTANCE_INITIALIZER_NAME);
@@ -152,8 +160,8 @@
   public final DexType annotationType = createType(annotationDescriptor);
   public final DexType throwableType = createType(throwableDescriptor);
 
-  public final DexType stringBuilderType = createType("Ljava/lang/StringBuilder;");
-  public final DexType stringBufferType = createType("Ljava/lang/StringBuffer;");
+  public final DexType stringBuilderType = createType(stringBuilderDescriptor);
+  public final DexType stringBufferType = createType(stringBufferDescriptor);
 
   public final DexType varHandleType = createType(varHandleDescriptor);
 
@@ -244,7 +252,6 @@
     }
   }
 
-
   public class StringBuildingMethods {
 
     public final DexMethod appendBoolean;
@@ -319,6 +326,24 @@
     return canonicalize(strings, new DexString(source));
   }
 
+  // TODO(b/67934123) Unify into one method,
+  public DexItemBasedString createItemBasedString(DexType type) {
+    assert !sorted;
+    return canonicalize(identifiers, new DexItemBasedString(type));
+  }
+
+  // TODO(b/67934123) Unify into one method,
+  public DexItemBasedString createItemBasedString(DexField field) {
+    assert !sorted;
+    return canonicalize(identifiers, new DexItemBasedString(field));
+  }
+
+  // TODO(b/67934123) Unify into one method,
+  public DexItemBasedString createItemBasedString(DexMethod method) {
+    assert !sorted;
+    return canonicalize(identifiers, new DexItemBasedString(method));
+  }
+
   // Debugging support to extract marking string.
   synchronized public Marker extractMarker() {
     // This is slow but it is not needed for any production code yet.
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 69a71f9..bae6fba 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -457,6 +457,13 @@
     return getPackageOrName(false);
   }
 
+  /** Get the fully qualified name using '/' in place of '.', ala the "internal type name" in ASM */
+  public String getInternalName() {
+    assert isClassType();
+    String descriptor = toDescriptorString();
+    return descriptor.substring(1, descriptor.length() - 1);
+  }
+
   public boolean isImmediateSubtypeOf(DexType type) {
     assert hierarchyLevel != UNKNOWN_LEVEL;
     return type.directSubtypes.contains(this);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 0fbac8f..18634c2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -58,6 +58,8 @@
       case LONG:
       case DOUBLE:
       case LONG_OR_DOUBLE:
+        assert builder.getOptions().canUseSameArrayAndResultRegisterInArrayGetWide()
+            || dest != array;
         instruction = new AgetWide(dest, array, index);
         break;
       case OBJECT:
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index b925b8b..80917f0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -500,6 +500,10 @@
     return isConstant() && getConstInstruction().isConstString();
   }
 
+  public boolean isConstClass() {
+    return isConstant() && getConstInstruction().isConstClass();
+  }
+
   public boolean isConstant() {
     return definition.isOutConstant() && !hasLocalInfo();
   }
@@ -556,7 +560,7 @@
   /**
    * Returns whether this value is known to be the receiver (this argument) in a method body.
    * <p>
-   * For a receiver value {@link #isNeverNull()} is guarenteed to be <code>true</code> as well.
+   * For a receiver value {@link #isNeverNull()} is guaranteed to be <code>true</code> as well.
    */
   public boolean isThis() {
     return isThis;
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 0ad6c6c..f1c3e32 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
@@ -37,10 +37,12 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
+import java.util.Set;
 
 public class CfBuilder {
 
@@ -50,6 +52,7 @@
 
   private Map<Value, DexType> types;
   private Map<BasicBlock, CfLabel> labels;
+  private Set<CfLabel> emittedLabels;
   private List<CfInstruction> instructions;
   private CfRegisterAllocator registerAllocator;
 
@@ -170,6 +173,7 @@
     Stack stack = new Stack();
     List<CfTryCatch> tryCatchRanges = new ArrayList<>();
     labels = new HashMap<>(code.blocks.size());
+    emittedLabels = new HashSet<>(code.blocks.size());
     instructions = new ArrayList<>();
     Iterator<BasicBlock> blockIterator = code.listIterator();
     BasicBlock block = blockIterator.next();
@@ -182,17 +186,12 @@
           // Close try-catch and save the range.
           CfLabel tryCatchEnd = getLabel(block);
           tryCatchRanges.add(new CfTryCatch(tryCatchStart, tryCatchEnd, tryCatchHandlers, this));
-          if (instructions.get(instructions.size() - 1) != tryCatchEnd) {
-            instructions.add(tryCatchEnd);
-          }
+          emitLabel(tryCatchEnd);
         }
         if (!handlers.isEmpty()) {
           // Open a try-catch.
           tryCatchStart = getLabel(block);
-          if (instructions.isEmpty()
-              || instructions.get(instructions.size() - 1) != tryCatchStart) {
-            instructions.add(tryCatchStart);
-          }
+          emitLabel(tryCatchStart);
         }
         tryCatchHandlers = handlers;
       }
@@ -201,7 +200,7 @@
       buildCfInstructions(block, fallthrough, stack);
       if (nextBlock != null && (!fallthrough || nextBlock.getPredecessors().size() > 1)) {
         assert stack.isEmpty();
-        instructions.add(getLabel(nextBlock));
+        emitLabel(getLabel(nextBlock));
         if (nextBlock.getPredecessors().size() > 1) {
           addFrame(nextBlock, Collections.emptyList());
         }
@@ -210,7 +209,11 @@
     } while (block != null);
     assert stack.isEmpty();
     return new CfCode(
-        stack.maxHeight, registerAllocator.registersUsed(), instructions, tryCatchRanges);
+        method.method,
+        stack.maxHeight,
+        registerAllocator.registersUsed(),
+        instructions,
+        tryCatchRanges);
   }
 
   private void buildCfInstructions(BasicBlock block, boolean fallthrough, Stack stack) {
@@ -268,9 +271,16 @@
     instructions.add(new CfFrame(mapping));
   }
 
+  private void emitLabel(CfLabel label) {
+    if (!emittedLabels.contains(label)) {
+      emittedLabels.add(label);
+      instructions.add(label);
+    }
+  }
+
   // Callbacks
 
-  public CfLabel getLabel(BasicBlock target){
+  public CfLabel getLabel(BasicBlock target) {
     return labels.computeIfAbsent(target, (block) -> new CfLabel());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 928d3c1..e7677b3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -713,6 +713,10 @@
     return handlers;
   }
 
+  public InternalOptions getOptions() {
+    return options;
+  }
+
   // Dex instruction wrapper with information to compute instruction sizes and offsets for jumps.
   private static abstract class Info {
 
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 6728afe..8207e4c 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
@@ -35,6 +35,7 @@
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.naming.IdentifierNameStringMarker;
 import com.android.tools.r8.shaking.protolite.ProtoLitePruner;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -78,13 +79,17 @@
   private final LensCodeRewriter lensCodeRewriter;
   private final Inliner inliner;
   private final ProtoLitePruner protoLiteRewriter;
+  private final IdentifierNameStringMarker identifierNameStringMarker;
 
   private OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
   private DexString highestSortingString;
 
   private IRConverter(
-      AppInfo appInfo, InternalOptions options, Timing timing,
-      CfgPrinter printer, GraphLense graphLense,
+      AppInfo appInfo,
+      InternalOptions options,
+      Timing timing,
+      CfgPrinter printer,
+      GraphLense graphLense,
       boolean enableWholeProgramOptimizations) {
     assert appInfo != null;
     assert options != null;
@@ -108,8 +113,14 @@
       this.lensCodeRewriter = new LensCodeRewriter(graphLense, appInfo.withSubtyping());
       if (appInfo.hasLiveness()) {
         this.protoLiteRewriter = new ProtoLitePruner(appInfo.withLiveness());
+        if (!appInfo.withLiveness().identifierNameStrings.isEmpty()) {
+          this.identifierNameStringMarker = new IdentifierNameStringMarker(appInfo.withLiveness());
+        } else {
+          this.identifierNameStringMarker = null;
+        }
       } else {
         this.protoLiteRewriter = null;
+        this.identifierNameStringMarker = null;
       }
     } else {
       this.inliner = null;
@@ -117,6 +128,7 @@
       this.memberValuePropagation = null;
       this.lensCodeRewriter = null;
       this.protoLiteRewriter = null;
+      this.identifierNameStringMarker = null;
     }
   }
 
@@ -133,7 +145,9 @@
    * Create an IR converter for processing methods with full program optimization disabled.
    */
   public IRConverter(
-      AppInfo appInfo, InternalOptions options, Timing timing,
+      AppInfo appInfo,
+      InternalOptions options,
+      Timing timing,
       CfgPrinter printer) {
     this(appInfo, options, timing, printer, null, false);
   }
@@ -142,7 +156,9 @@
    * Create an IR converter for processing methods with full program optimization enabled.
    */
   public IRConverter(
-      AppInfoWithSubtyping appInfo, InternalOptions options, Timing timing,
+      AppInfoWithSubtyping appInfo,
+      InternalOptions options,
+      Timing timing,
       CfgPrinter printer,
       GraphLense graphLense) {
     this(appInfo, options, timing, printer, graphLense, true);
@@ -507,6 +523,12 @@
         assert graphLense.isIdentityLense();
       }
     }
+
+    if (identifierNameStringMarker != null) {
+      identifierNameStringMarker.decoupleIdentifierNameStrings(method, code);
+      assert code.isConsistentSSA();
+    }
+
     if (memberValuePropagation != null) {
       memberValuePropagation.rewriteWithConstantValues(code);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 2cae81a..92e7e05 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -293,13 +293,20 @@
       // Close ranges up-to but excluding the first instruction. Ends are exclusive but the values
       // might be live upon entering the first instruction (if they are used by it).
       int entryIndex = block.entry().getNumber();
+      if (block.entry().isMoveException()) {
+        // Close locals at a move exception since they close as part of the exceptional transfer.
+        entryIndex++;
+      }
       {
         ListIterator<LocalRange> it = openRanges.listIterator(0);
         while (it.hasNext()) {
           LocalRange openRange = it.next();
           if (openRange.end < entryIndex ||
-              // Don't close the local if it is used by the entry instruction.
-              (openRange.end == entryIndex && !usesValues(openRange.value, block.entry()))) {
+              // Don't close the local if it is used by the entry instruction. Exclude move
+              // exception instruction that are managed in other way that should be clean.
+              (openRange.end == entryIndex
+                  && !block.entry().isMoveException()
+                  && !usesValues(openRange.value, block.entry()))) {
             it.remove();
             assert currentLocals.get(openRange.register) == openRange.local;
             currentLocals.remove(openRange.register);
@@ -989,6 +996,49 @@
     return position % 2 == 1 ? position : position - 1;
   }
 
+  // Art had a bug (b/68761724) for Android N and O in the arm32 interpreter
+  // where an aget-wide instruction using the same register for the array
+  // and the first register of the result could lead to the wrong exception
+  // being thrown on out of bounds.
+  //
+  // For instructions of the form 'aget-wide regA, regA, regB' where
+  // regB is out of bounds of non-null array in regA, Art would throw a null
+  // pointer exception instead of an ArrayIndexOutOfBounds exception.
+  //
+  // We work around that bug by disallowing aget-wide with the same array
+  // and result register.
+  private boolean needsArrayGetWideWorkaround(LiveIntervals intervals) {
+    if (options.canUseSameArrayAndResultRegisterInArrayGetWide()) {
+      return false;
+    }
+    if (intervals.requiredRegisters() == 1) {
+      // Not the live range for a wide value and therefore not the output of aget-wide.
+      return false;
+    }
+    if (intervals.getValue().isPhi()) {
+      // If this writes a new register pair it will be via a move and not an aget-wide operation.
+      return false;
+    }
+    if (intervals.getSplitParent() != intervals) {
+      // This is a split of a parent interval and therefore if this leads to a write of a
+      // register pair it will be via a move and not an aget-wide operation.
+      return false;
+    }
+    Instruction definition = intervals.getValue().definition;
+    return definition.isArrayGet() && definition.asArrayGet().outType().isWide();
+  }
+
+  // Is the array-get array register the same as the first register we are
+  // allocating for the result?
+  private boolean isArrayGetArrayRegister(LiveIntervals intervals, int register) {
+    assert needsArrayGetWideWorkaround(intervals);
+    Value array = intervals.getValue().definition.asArrayGet().array();
+    int arrayReg =
+        array.getLiveIntervals().getSplitCovering(intervals.getStart()).getRegister();
+    assert arrayReg != NO_REGISTER;
+    return arrayReg == register;
+  }
+
   // The dalvik jit had a bug where the long operations add, sub, or, xor and and would write
   // the first part of the result long before reading the second part of the input longs.
   //
@@ -1271,6 +1321,11 @@
             hasOverlappingLongRegisters(unhandledInterval, register)) {
           return false;
         }
+        // Check for aget-wide bug in recent Art VMs.
+        if (needsArrayGetWideWorkaround(unhandledInterval) &&
+            isArrayGetArrayRegister(unhandledInterval, register)) {
+          return false;
+        }
         assignRegisterToUnhandledInterval(unhandledInterval, needsRegisterPair, register);
         return true;
       }
@@ -1374,6 +1429,21 @@
         lastCandidate = candidate;
       }
     }
+    if (needsArrayGetWideWorkaround(unhandledInterval)) {
+      int lastCandidate = candidate;
+      while (isArrayGetArrayRegister(unhandledInterval, candidate)) {
+        // Make the overlapping register unavailable for allocation and try again.
+        freePositions.set(candidate, 0);
+        candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair, type);
+        // If there are only invalid candidates of the give type we will end up with the same
+        // candidate returned again once we have tried them all. In that case we didn't find a
+        // valid register candidate and we need to broaden the search to other types.
+        if (lastCandidate == candidate) {
+          return REGISTER_CANDIDATE_NOT_FOUND;
+        }
+        lastCandidate = candidate;
+      }
+    }
     return candidate;
   }
 
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 b2760e4..d2b2f5e 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.InternalOptions;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -21,7 +20,6 @@
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Type;
 import org.objectweb.asm.tree.ClassNode;
 import org.objectweb.asm.tree.MethodNode;
 import org.objectweb.asm.util.CheckClassAdapter;
@@ -68,13 +66,13 @@
     int version = downgrade(clazz.getClassFileVersion());
     int access = clazz.accessFlags.getAsCfAccessFlags();
     String desc = clazz.type.toDescriptorString();
-    String name = internalName(clazz.type);
+    String name = clazz.type.getInternalName();
     String signature = null; // TODO(zerny): Support generic signatures.
     String superName =
-        clazz.type == options.itemFactory.objectType ? null : internalName(clazz.superType);
+        clazz.type == options.itemFactory.objectType ? null : clazz.superType.getInternalName();
     String[] interfaces = new String[clazz.interfaces.values.length];
     for (int i = 0; i < clazz.interfaces.values.length; i++) {
-      interfaces[i] = internalName(clazz.interfaces.values[i]);
+      interfaces[i] = clazz.interfaces.values[i].getInternalName();
     }
     writer.visit(version, access, name, signature, superName, interfaces);
     // TODO(zerny): Methods and fields.
@@ -107,10 +105,6 @@
     }
   }
 
-  private static String internalName(DexType type) {
-    return Type.getType(type.toDescriptorString()).getInternalName();
-  }
-
   private String printCf(byte[] result) {
     ClassReader reader = new ClassReader(result);
     ClassNode node = new ClassNode(ASM6);
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index 552516d..92c2560 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -3,47 +3,60 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
+
 import com.android.tools.r8.code.ConstString;
 import com.android.tools.r8.code.ConstStringJumbo;
 import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexItemBasedString;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardClassFilter;
-import com.android.tools.r8.utils.DescriptorUtils;
 import java.util.Map;
+import java.util.Set;
 
 class IdentifierMinifier {
 
-  private final AppInfo appInfo;
+  private final AppInfoWithLiveness appInfo;
   private final ProguardClassFilter adaptClassStrings;
   private final NamingLens lens;
+  private final Set<DexItem> identifierNameStrings;
 
   IdentifierMinifier(
-      AppInfo appInfo,
+      AppInfoWithLiveness appInfo,
       ProguardClassFilter adaptClassStrings,
       NamingLens lens) {
     this.appInfo = appInfo;
     this.adaptClassStrings = adaptClassStrings;
     this.lens = lens;
+    this.identifierNameStrings = appInfo.identifierNameStrings;
   }
 
   void run() {
     if (!adaptClassStrings.isEmpty()) {
-      handleAdaptClassStrings();
+      adaptClassStrings();
     }
-    // TODO(b/36799092): Handle influx of string literals from call sites to annotated members.
+    if (!identifierNameStrings.isEmpty()) {
+      replaceIdentifierNameString();
+    }
   }
 
-  private void handleAdaptClassStrings() {
-    appInfo.classes().forEach(clazz -> {
+  private void adaptClassStrings() {
+    for (DexProgramClass clazz : appInfo.classes()) {
       if (!adaptClassStrings.matches(clazz.type)) {
-        return;
+        continue;
       }
-      clazz.forEachField(encodedField -> {
+      for (DexEncodedField encodedField : clazz.allFieldsSorted()) {
         if (encodedField.staticValue instanceof DexValueString) {
           DexString original = ((DexValueString) encodedField.staticValue).getValue();
           DexString renamed = getRenamedStringLiteral(original);
@@ -51,15 +64,15 @@
             encodedField.staticValue = new DexValueString(renamed);
           }
         }
-      });
-      clazz.forEachMethod(encodedMethod -> {
+      }
+      for (DexEncodedMethod encodedMethod : clazz.allMethodsSorted()) {
         // Abstract methods do not have code_item.
         if (encodedMethod.accessFlags.isAbstract()) {
-          return;
+          continue;
         }
         Code code = encodedMethod.getCode();
         if (code == null) {
-          return;
+          continue;
         }
         assert code.isDexCode();
         DexCode dexCode = code.asDexCode();
@@ -74,8 +87,8 @@
             cnst.BBBBBBBB = getRenamedStringLiteral(dexString);
           }
         }
-      });
-    });
+      }
+    }
   }
 
   private DexString getRenamedStringLiteral(DexString originalLiteral) {
@@ -90,11 +103,68 @@
       DexString renamed = lens.lookupDescriptor(type);
       // Create a new DexString only when the corresponding string literal will be replaced.
       if (renamed != originalLiteral) {
-        return appInfo.dexItemFactory.createString(
-            DescriptorUtils.descriptorToJavaType(renamed.toString()));
+        return appInfo.dexItemFactory.createString(descriptorToJavaType(renamed.toString()));
       }
     }
     return originalLiteral;
   }
 
+  private void replaceIdentifierNameString() {
+    for (DexProgramClass clazz : appInfo.classes()) {
+      for (DexEncodedMethod encodedMethod : clazz.allMethodsSorted()) {
+        if (!encodedMethod.getOptimizationInfo().useIdentifierNameString()) {
+          continue;
+        }
+        Code code = encodedMethod.getCode();
+        if (code == null) {
+          continue;
+        }
+        assert code.isDexCode();
+        DexCode dexCode = code.asDexCode();
+        for (Instruction instr : dexCode.instructions) {
+          if (instr instanceof ConstString
+              && ((ConstString) instr).getString() instanceof DexItemBasedString) {
+            ConstString cnst = (ConstString) instr;
+            DexItemBasedString itemBasedString = (DexItemBasedString) cnst.getString();
+            cnst.BBBB = materialize(itemBasedString);
+          } else if (instr instanceof ConstStringJumbo
+              && ((ConstStringJumbo) instr).getString() instanceof DexItemBasedString) {
+            ConstStringJumbo cnst = (ConstStringJumbo) instr;
+            DexItemBasedString itemBasedString = (DexItemBasedString) cnst.getString();
+            cnst.BBBBBBBB = materialize(itemBasedString);
+          }
+        }
+      }
+    }
+    // Some const strings could be moved to field's static value (from <clinit>).
+    for (DexItem dexItem : identifierNameStrings) {
+      if (!(dexItem instanceof DexField)) {
+        continue;
+      }
+      DexEncodedField encodedField = appInfo.definitionFor((DexField) dexItem);
+      if (!(encodedField.staticValue instanceof DexValueString)) {
+        continue;
+      }
+      DexString original = ((DexValueString) encodedField.staticValue).getValue();
+      if (original instanceof DexItemBasedString) {
+        encodedField.staticValue = new DexValueString(materialize((DexItemBasedString) original));
+      }
+    }
+  }
+
+  private DexString materialize(DexItemBasedString itemBasedString) {
+    if (itemBasedString.basedOn instanceof DexType) {
+      DexString renamed = lens.lookupDescriptor((DexType) itemBasedString.basedOn);
+      if (!renamed.toString().equals(itemBasedString.toString())) {
+        return appInfo.dexItemFactory.createString(descriptorToJavaType(renamed.toString()));
+      }
+      return renamed;
+    } else if (itemBasedString.basedOn instanceof DexMethod) {
+      return lens.lookupName((DexMethod) itemBasedString.basedOn);
+    } else {
+      assert itemBasedString.basedOn instanceof DexField;
+      return lens.lookupName((DexField) itemBasedString.basedOn);
+    }
+  }
+
 }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
new file mode 100644
index 0000000..2f48083
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -0,0 +1,183 @@
+// Copyright (c) 2017, 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.naming;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexItemBasedString;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class IdentifierNameStringMarker {
+  private final AppInfoWithLiveness appInfo;
+  private final DexItemFactory dexItemFactory;
+  private final Set<DexItem> identifierNameStrings;
+
+  public IdentifierNameStringMarker(AppInfoWithLiveness appInfo) {
+    this.appInfo = appInfo;
+    this.dexItemFactory = appInfo.dexItemFactory;
+    this.identifierNameStrings = appInfo.identifierNameStrings;
+  }
+
+  public void decoupleIdentifierNameStrings(DexEncodedMethod encodedMethod, IRCode code) {
+    for (BasicBlock block : code.blocks) {
+      InstructionListIterator iterator = block.listIterator();
+      while (iterator.hasNext()) {
+        Instruction instruction = iterator.next();
+        if (instruction.isStaticPut()) {
+          StaticPut staticPut = instruction.asStaticPut();
+          DexField field = staticPut.getField();
+          if (identifierNameStrings.contains(field)) {
+            Value in = staticPut.inValue();
+            Value newIn = decoupleTypeIdentifierIfNecessary(code, iterator, staticPut, in);
+            if (newIn != in) {
+              iterator.replaceCurrentInstruction(
+                  new StaticPut(staticPut.getType(), newIn, field));
+              encodedMethod.markUseIdentifierNameString();
+            }
+          }
+        } else if (instruction.isInstancePut()) {
+          InstancePut instancePut = instruction.asInstancePut();
+          DexField field = instancePut.getField();
+          if (identifierNameStrings.contains(field)) {
+            Value in = instancePut.value();
+            Value newIn = decoupleTypeIdentifierIfNecessary(code, iterator, instancePut, in);
+            if (newIn != in) {
+              List<Value> values = new ArrayList<>(2);
+              values.add(newIn);
+              values.add(instancePut.object());
+              iterator.replaceCurrentInstruction(
+                  new InstancePut(instancePut.getType(), values, field));
+              encodedMethod.markUseIdentifierNameString();
+            }
+          }
+        } else if (instruction.isInvokeMethod()) {
+          InvokeMethod invoke = instruction.asInvokeMethod();
+          DexMethod invokedMethod = invoke.getInvokedMethod();
+          if (identifierNameStrings.contains(invokedMethod)) {
+            List<Value> ins = invoke.arguments();
+            List<Value> newIns;
+            if (isReflectiveCase(invokedMethod.proto)) {
+              Value in = ins.get(1);
+              Value newIn =
+                  decoupleReflectiveMemberIdentifier(code, iterator, invoke, in);
+              newIns =
+                  ins.stream()
+                      .map(i -> i == in ? newIn : i)
+                      .collect(Collectors.toList());
+            } else {
+              newIns =
+                  ins.stream()
+                      .map(in -> decoupleTypeIdentifierIfNecessary(code, iterator, invoke, in))
+                      .collect(Collectors.toList());
+            }
+            if (!ins.equals(newIns)) {
+              iterator.replaceCurrentInstruction(
+                  Invoke.create(
+                      invoke.getType(),
+                      invokedMethod,
+                      invokedMethod.proto,
+                      invoke.outValue(),
+                      newIns));
+              encodedMethod.markUseIdentifierNameString();
+            }
+          }
+        }
+      }
+    }
+  }
+
+  private Value decoupleTypeIdentifierIfNecessary(
+      IRCode code, InstructionListIterator iterator, Instruction base, Value in) {
+    if (!in.isConstString()) {
+      return in;
+    }
+    ConstString constString = in.getConstInstruction().asConstString();
+    String maybeDescriptor =
+        DescriptorUtils.javaTypeToDescriptorIfValidJavaType(constString.getValue().toString());
+    if (maybeDescriptor == null) {
+      return in;
+    }
+    DexType type = dexItemFactory.createType(maybeDescriptor);
+    DexItemBasedString typeString = dexItemFactory.createItemBasedString(type);
+    // v_n <- "x.y.z" // in.definition
+    // ...
+    // ... <- ... v_n ..
+    // ...
+    // this.fld <- v_n // base
+    //
+    //   ~>
+    //
+    // ...
+    // v_n' <- DexItemBasedString("Lx/y/z;") // decoupled
+    // this.fld <- v_n' // base
+    //
+    // 1) Move the cursor back to $base
+    iterator.previous();
+    // 2) Add $decoupled just before $base
+    Value newIn = code.createValue(in.outType(), in.getLocalInfo());
+    ConstString decoupled = new ConstString(newIn, typeString);
+    decoupled.setPosition(base.getPosition());
+    iterator.add(decoupled);
+    // 3) Restore the cursor
+    iterator.next();
+    return newIn;
+  }
+
+  private Value decoupleReflectiveMemberIdentifier(
+    IRCode code, InstructionListIterator iterator, InvokeMethod invoke, Value in) {
+    // TODO(b/36799092): special reflection cases.
+    return in;
+  }
+
+  private boolean isReflectiveCase(DexProto proto) {
+    // (Class, String) -> java.lang.reflect.Field
+    // (Class, String, Class[]) -> java.lang.reflect.Method
+    int numOfParams = proto.parameters.size();
+    if (numOfParams != 2 && numOfParams != 3) {
+      return false;
+    }
+    if (numOfParams == 2) {
+      if (proto.returnType.descriptor != dexItemFactory.fieldDescriptor) {
+        return false;
+      }
+    } else {
+      if (proto.returnType.descriptor != dexItemFactory.methodDescriptor) {
+        return false;
+      }
+    }
+    if (proto.parameters.values[0].descriptor != dexItemFactory.classDescriptor) {
+      return false;
+    }
+    if (proto.parameters.values[1].descriptor != dexItemFactory.stringDescriptor) {
+      return false;
+    }
+    if (numOfParams == 3) {
+      if (proto.parameters.values[2].toDescriptorString().equals("[Ljava/lang/Class;")) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+}
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 5b79fce..6f7926c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1158,6 +1158,10 @@
      */
     public final Set<DexItem> alwaysInline;
     /**
+     * All items with -identifiernamestring rule.
+     */
+    public final Set<DexItem> identifierNameStrings;
+    /**
      * Map from the class of an extension to the state it produced.
      */
     final Map<Class<?>, Object> extensions;
@@ -1189,6 +1193,7 @@
       this.noSideEffects = enqueuer.rootSet.noSideEffects;
       this.assumedValues = enqueuer.rootSet.assumedValues;
       this.alwaysInline = enqueuer.rootSet.alwaysInline;
+      this.identifierNameStrings = enqueuer.rootSet.identifierNameStrings;
       this.extensions = enqueuer.extensionsState;
       this.prunedTypes = Collections.emptySet();
       assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
@@ -1220,6 +1225,7 @@
       this.staticInvokes = previous.staticInvokes;
       this.extensions = previous.extensions;
       this.alwaysInline = previous.alwaysInline;
+      this.identifierNameStrings = previous.identifierNameStrings;
       this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses);
       assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
       assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
@@ -1253,6 +1259,7 @@
       this.assumedValues = previous.assumedValues;
       assert assertNotModifiedByLense(previous.alwaysInline, lense);
       this.alwaysInline = previous.alwaysInline;
+      this.identifierNameStrings = rewriteMixedItems(previous.identifierNameStrings, lense);
       this.extensions = previous.extensions;
       // Sanity check sets after rewriting.
       assert Sets.intersection(instanceFieldReads, staticFieldReads).isEmpty();
@@ -1316,7 +1323,7 @@
     private ImmutableSet<DexItem> rewritePinnedItemsToDescriptors(Collection<DexItem> source) {
       ImmutableSet.Builder<DexItem> builder = ImmutableSet.builder();
       for (DexItem item : source) {
-        // TODO(67934123) There should be a common interface to extract this information.
+        // TODO(b/67934123) There should be a common interface to extract this information.
         if (item instanceof DexClass) {
           builder.add(((DexClass) item).type);
         } else if (item instanceof DexEncodedMethod) {
@@ -1344,7 +1351,7 @@
         Set<DexItem> original, GraphLense lense) {
       ImmutableSet.Builder<DexItem> builder = ImmutableSet.builder();
       for (DexItem item : original) {
-        // TODO(67934123) There should be a common interface to perform the dispatch.
+        // TODO(b/67934123) There should be a common interface to perform the dispatch.
         if (item instanceof DexType) {
           builder.add(lense.lookupType((DexType) item, null));
         } else if (item instanceof DexMethod) {
@@ -1389,17 +1396,17 @@
       return this;
     }
 
-    // TODO(67934123) Unify into one method,
+    // TODO(b/67934123) Unify into one method,
     public boolean isPinned(DexType item) {
       return pinnedItems.contains(item);
     }
 
-    // TODO(67934123) Unify into one method,
+    // TODO(b/67934123) Unify into one method,
     public boolean isPinned(DexMethod item) {
       return pinnedItems.contains(item);
     }
 
-    // TODO(67934123) Unify into one method,
+    // TODO(b/67934123) Unify into one method,
     public boolean isPinned(DexField item) {
       return pinnedItems.contains(item);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 53f5fb7..25fc6e0 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -63,6 +63,7 @@
       new IdentityHashMap<>();
   private final Map<DexItem, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
   private final Map<DexItem, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
+  private final Set<DexItem> identifierNameStrings = Sets.newIdentityHashSet();
   private final InternalOptions options;
 
   public RootSetBuilder(DexApplication application, AppInfo appInfo,
@@ -207,7 +208,8 @@
         markMatchingFields(clazz, memberKeepRules, rule, null);
       } else {
         assert rule instanceof ProguardIdentifierNameStringRule;
-        // TODO(b/36799092): collect string literals while marking class and matching members.
+        markMatchingFields(clazz, memberKeepRules, rule, null);
+        markMatchingMethods(clazz, memberKeepRules, rule, null);
       }
     }
   }
@@ -258,7 +260,8 @@
         alwaysInline,
         noSideEffects,
         assumedValues,
-        dependentNoShrinking);
+        dependentNoShrinking,
+        identifierNameStrings);
   }
 
   private void markMatchingVisibleMethods(DexClass clazz,
@@ -514,6 +517,12 @@
       checkDiscarded.add(item);
     } else if (context instanceof ProguardAlwaysInlineRule) {
       alwaysInline.add(item);
+    } else if (context instanceof ProguardIdentifierNameStringRule) {
+      if (item instanceof DexEncodedField) {
+        identifierNameStrings.add(((DexEncodedField) item).field);
+      } else if (item instanceof DexEncodedMethod) {
+        identifierNameStrings.add(((DexEncodedMethod) item).method);
+      }
     }
   }
 
@@ -529,6 +538,7 @@
     public final Map<DexItem, ProguardMemberRule> noSideEffects;
     public final Map<DexItem, ProguardMemberRule> assumedValues;
     private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
+    public final Set<DexItem> identifierNameStrings;
 
     private boolean isTypeEncodedMethodOrEncodedField(DexItem item) {
       assert item instanceof DexType
@@ -561,7 +571,8 @@
         Set<DexItem> alwaysInline,
         Map<DexItem, ProguardMemberRule> noSideEffects,
         Map<DexItem, ProguardMemberRule> assumedValues,
-        Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking) {
+        Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking,
+        Set<DexItem> identifierNameStrings) {
       this.noShrinking = Collections.unmodifiableMap(noShrinking);
       this.noOptimization = Collections.unmodifiableSet(noOptimization);
       this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
@@ -572,6 +583,7 @@
       this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
       this.assumedValues = Collections.unmodifiableMap(assumedValues);
       this.dependentNoShrinking = dependentNoShrinking;
+      this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
       assert legalNoObfuscationItems(noObfuscation);
       assert legalDependentNoShrinkingItems(dependentNoShrinking);
     }
@@ -615,6 +627,7 @@
       builder.append("\nnoSideEffects: " + noSideEffects.size());
       builder.append("\nassumedValues: " + assumedValues.size());
       builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size());
+      builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
 
       builder.append("\n\nNo Shrinking:");
       noShrinking.keySet().stream()
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 ee3c845..43667e2 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -59,6 +59,32 @@
   }
 
   /**
+   * Convert a Java type name to a descriptor string only if the given {@param typeName} is valid.
+   *
+   * @param typeName the java type name
+   * @return the descriptor string if {@param typeName} is not valid or null otherwise
+   */
+  public static String javaTypeToDescriptorIfValidJavaType(String typeName) {
+    if (isValidJavaType(typeName)) {
+      return javaTypeToDescriptor(typeName);
+    }
+    return null;
+  }
+
+  /**
+   * Determine the given {@param typeName} is valid java type name or not.
+   *
+   * @param typeName the java type name
+   * @return true if and only if the given type name is valid java type
+   */
+  public static boolean isValidJavaType(String typeName) {
+    return typeName.length() > 0
+        && Character.isJavaIdentifierStart(typeName.charAt(0))
+        && typeName.substring(1).chars().allMatch(
+            ch -> Character.isJavaIdentifierPart(ch) || ch == JAVA_PACKAGE_SEPARATOR);
+  }
+
+  /**
    * Convert a Java type name to a shorty descriptor string.
    *
    * @param typeName the java type name
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 62dac5f..5105a46 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -342,4 +342,12 @@
   public boolean canUseFilledNewArrayOfObjects() {
     return minApiLevel >= AndroidApiLevel.K.getLevel();
   }
+
+  // Art had a bug (b/68761724) for Android N and O in the arm32 interpreter
+  // where an aget-wide instruction using the same register for the array
+  // and the first register of the result could lead to the wrong exception
+  // being thrown on out of bounds.
+  public boolean canUseSameArrayAndResultRegisterInArrayGetWide() {
+    return minApiLevel > AndroidApiLevel.O_MR1.getLevel();
+  }
 }
diff --git a/src/test/examples/identifiernamestring/A.java b/src/test/examples/identifiernamestring/A.java
new file mode 100644
index 0000000..8b0b705
--- /dev/null
+++ b/src/test/examples/identifiernamestring/A.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2017, 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 identifiernamestring;
+
+public class A {
+  @IdentifierNameString
+  static String foo = "identifiernamestring.B"; // should be renamed
+
+  @IdentifierNameString
+  String boo;
+
+  A() {
+    boo = "identifiernamestring.B"; // should be renamed
+  }
+
+  @IdentifierNameString
+  void bar(String s) {
+    System.out.println("identifiernamestring.B");
+    System.out.println(boo);
+    System.out.println(s); // renamed one will be passed
+  }
+
+  static String TYPE_A = "identifiernamestring.A"; // should be kept (restored)
+  static String TYPE_B = "identifiernamestring.B"; // should be renamed
+}
diff --git a/src/test/examples/identifiernamestring/B.java b/src/test/examples/identifiernamestring/B.java
new file mode 100644
index 0000000..279ba85
--- /dev/null
+++ b/src/test/examples/identifiernamestring/B.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2017, 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 identifiernamestring;
+
+public class B {
+  static String foo = "identifiernamestring.B";
+  static String TYPO_A = "identifiernamestring.A";
+  static String TYPO_B = "identifiernamestring.B";
+}
diff --git a/src/test/examples/identifiernamestring/IdentifierNameString.java b/src/test/examples/identifiernamestring/IdentifierNameString.java
new file mode 100644
index 0000000..94132bb
--- /dev/null
+++ b/src/test/examples/identifiernamestring/IdentifierNameString.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2017, 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 identifiernamestring;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+// It should not be discarded by javac.
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface IdentifierNameString {
+}
diff --git a/src/test/examples/identifiernamestring/Main.java b/src/test/examples/identifiernamestring/Main.java
new file mode 100644
index 0000000..5b69b18
--- /dev/null
+++ b/src/test/examples/identifiernamestring/Main.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2017, 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 identifiernamestring;
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    A ax = new A();
+    assert ax.boo.equals(A.TYPE_B);
+    // Should be renamed
+    ax.bar("identifiernamestring.B");
+
+    Class a = Class.forName(A.TYPE_A);
+    Class b_a = Class.forName(B.TYPO_A);
+    // A's name is kept.
+    assert a.equals(b_a);
+
+    Class b = Class.forName(ax.boo);
+    Class b_b = Class.forName(B.TYPO_B);
+    // As TYPO_B is not renamed, they will be different.
+    assert !b.equals(b_b);
+  }
+}
\ No newline at end of file
diff --git a/src/test/examples/identifiernamestring/keep-rules-1.txt b/src/test/examples/identifiernamestring/keep-rules-1.txt
new file mode 100644
index 0000000..a339a92
--- /dev/null
+++ b/src/test/examples/identifiernamestring/keep-rules-1.txt
@@ -0,0 +1,14 @@
+# Copyright (c) 2017, 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class identifiernamestring.Main {
+  public static void main(...);
+}
+
+-keepnames class identifiernamestring.A
+
+-dontshrink
+-dontoptimize
diff --git a/src/test/examples/identifiernamestring/keep-rules-2.txt b/src/test/examples/identifiernamestring/keep-rules-2.txt
new file mode 100644
index 0000000..096d834
--- /dev/null
+++ b/src/test/examples/identifiernamestring/keep-rules-2.txt
@@ -0,0 +1,19 @@
+# Copyright (c) 2017, 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class identifiernamestring.Main {
+  public static void main(...);
+}
+
+-keepnames class identifiernamestring.A
+
+-dontshrink
+-dontoptimize
+
+-identifiernamestring class * {
+  @identifiernamestring.IdentifierNameString *;
+  static java.lang.String TYPE_*;
+}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 139ee1f..ce46885 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -748,12 +748,25 @@
     return runJava(ImmutableList.of(path.toString()), main);
   }
 
+  public static ProcessResult runJavaNoVerify(Class clazz) throws Exception {
+    String main = clazz.getCanonicalName();
+    Path path = getClassPathForTests();
+    return runJavaNoVerify(ImmutableList.of(path.toString()), main);
+  }
+
   public static ProcessResult runJava(List<String> classpath, String mainClass) throws IOException {
     ProcessBuilder builder = new ProcessBuilder(
         getJavaExecutable(), "-cp", String.join(PATH_SEPARATOR, classpath), mainClass);
     return runProcess(builder);
   }
 
+  public static ProcessResult runJavaNoVerify(List<String> classpath, String mainClass)
+      throws IOException {
+    ProcessBuilder builder = new ProcessBuilder(
+        getJavaExecutable(), "-cp", String.join(PATH_SEPARATOR, classpath), "-noverify", mainClass);
+    return runProcess(builder);
+  }
+
   public static ProcessResult forkD8(Path dir, String... args)
       throws IOException, InterruptedException {
     return forkJava(dir, D8.class, args);
diff --git a/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java b/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java
index c475888..561fa9b 100644
--- a/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java
+++ b/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
@@ -20,7 +21,7 @@
 
   @Test
   public void booleanByteConfusion() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     // public static void foo(boolean condition) {
diff --git a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
index 2511d79..e572bfa 100644
--- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.debuginfo.DebugInfoInspector;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
@@ -170,7 +171,7 @@
   // Check that we properly handle switching a local slot from one variable to another.
   @Test
   public void checkLocalChange() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     MethodSignature foo = clazz.addStaticMethod("foo", ImmutableList.of("I"), "I",
@@ -341,7 +342,7 @@
 
   @Test
   public void argumentLiveAtReturn() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     /*
@@ -442,7 +443,7 @@
 
   @Test
   public void testLocalSwitchRewriteToIfs() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     /*
@@ -540,7 +541,7 @@
 
   @Test
   public void testLocalSwitchRewriteToSwitches() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     /*
diff --git a/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java b/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java
index a648381..a38ee9e 100644
--- a/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java
+++ b/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
@@ -44,7 +45,7 @@
 
   @Test
   public void filledArray() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     // Corresponds to something like the following (which doesn't compile with javac):
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java b/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
index 2551941..9394995 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.debuginfo.DebugInfoInspector;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.StringUtils;
@@ -26,7 +27,7 @@
   // will actually be a read of 'x'.
   @Test
   public void testInvalidInfoThrow() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of("I"), "V",
@@ -85,7 +86,7 @@
   // in this test the scope of "y" (local 2) spans the exceptional edge in which it is not live.
   @Test
   public void testInvalidInfoBug37722432() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of("I", "I"), "V",
@@ -218,7 +219,7 @@
 
   @Test
   public void invalidInfoBug63412730_onMove() throws Throwable {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
     MethodSignature method = clazz.addStaticMethod("bar", ImmutableList.of(), "V",
         ".limit stack 3",
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 94f1deb..d44b460 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -26,16 +26,62 @@
 
 public class JasminBuilder {
 
-  public static class ClassBuilder {
+  public enum ClassFileVersion {
+    JDK_1_1 {
+      @Override
+      public int getMajorVersion() {
+        return 45;
+      }
+
+      @Override
+      public int getMinorVersion() {
+        return 3;
+      }
+    },
+    JDK_1_2 {
+      @Override
+      public int getMajorVersion() {
+        return 46;
+      }
+    },
+    JDK_1_3 {
+      @Override
+      public int getMajorVersion() {
+        return 47;
+      }
+    },
+    JDK_1_4 {
+      @Override
+      public int getMajorVersion() {
+        return 48;
+      }
+    },
+    /** JSE 5 is not fully supported by Jasmin. Interfaces will not work. */
+    JSE_5 {
+      @Override
+      public int getMajorVersion() {
+        return 49;
+      }
+    };
+
+    public abstract int getMajorVersion();
+
+    public int getMinorVersion() {
+      return 0;
+    }
+  }
+
+  public class ClassBuilder {
     public final String name;
     public final String superName;
     public final ImmutableList<String> interfaces;
     private final List<String> methods = new ArrayList<>();
     private final List<String> fields = new ArrayList<>();
     private boolean makeInit = false;
+    private boolean hasInit = false;
     private boolean isInterface = false;
 
-    public ClassBuilder(String name) {
+    private ClassBuilder(String name) {
       this(name, "java/lang/Object");
     }
 
@@ -130,18 +176,19 @@
     @Override
     public String toString() {
       StringBuilder builder = new StringBuilder();
+      builder.append(".bytecode ").append(majorVersion).append('.').append(minorVersion)
+          .append('\n');
       builder.append(".source ").append(getSourceFile()).append('\n');
+      builder.append(".class");
       if (isInterface) {
-        builder.append(".interface");
-      } else {
-        builder.append(".class");
+        builder.append(" interface abstract");
       }
       builder.append(" public ").append(name).append('\n');
       builder.append(".super ").append(superName).append('\n');
       for (String iface : interfaces) {
         builder.append(".implements ").append(iface).append('\n');
       }
-      if (makeInit) {
+      if (makeInit && !hasInit) {
         builder
             .append(".method public <init>()V\n")
             .append(".limit locals 1\n")
@@ -165,6 +212,8 @@
     }
 
     public MethodSignature addDefaultConstructor() {
+      assert !hasInit;
+      hasInit = true;
       return addMethod("public", "<init>", Collections.emptyList(), "V",
           ".limit stack 1",
           ".limit locals 1",
@@ -175,8 +224,17 @@
   }
 
   private final List<ClassBuilder> classes = new ArrayList<>();
+  private final int minorVersion;
+  private final int majorVersion;
 
-  public JasminBuilder() {}
+  public JasminBuilder() {
+    this(ClassFileVersion.JDK_1_4);
+  }
+
+  public JasminBuilder(ClassFileVersion version) {
+    majorVersion = version.getMajorVersion();
+    minorVersion = version.getMinorVersion();
+  }
 
   public ClassBuilder addClass(String name) {
     ClassBuilder builder = new ClassBuilder(name);
@@ -197,6 +255,9 @@
   }
 
   public ClassBuilder addInterface(String name, String... interfaces) {
+    // Interfaces are broken in Jasmin (the ACC_SUPER access flag is set) and the JSE_5 and later
+    // will not load corresponding classes.
+    assert majorVersion <= ClassFileVersion.JDK_1_4.getMajorVersion();
     ClassBuilder builder = new ClassBuilder(name, "java/lang/Object", interfaces);
     builder.setIsInterface();
     classes.add(builder);
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 6d4a501..84b413a 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -55,7 +55,7 @@
         ByteStreams.copy(input, output);
       }
     }
-    return ToolHelper.runJava(ImmutableList.of(out.getPath()), main);
+    return ToolHelper.runJavaNoVerify(ImmutableList.of(out.getPath()), main);
   }
 
   protected String runOnJava(JasminBuilder builder, String main) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineDebugInfoTests.java b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineDebugInfoTests.java
index f5254fc..9711c88 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineDebugInfoTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineDebugInfoTests.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.debuginfo.DebugInfoInspector;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.google.common.collect.ImmutableList;
@@ -15,7 +16,7 @@
 
   @Test
   public void testJsrWithStraightlineAndDebugInfoCode() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     MethodSignature foo = clazz.addStaticMethod("foo", ImmutableList.of("I"), "I",
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
index c07f125..5e41a25 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.android.tools.r8.utils.AndroidApp;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
@@ -53,7 +54,7 @@
    *  }
    */
   public void testJsrJava130TryFinally() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_3);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addMainMethod(
@@ -106,7 +107,7 @@
    *  }
    */
   public void testJsrJava130TryFinallyNested() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_3);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addMainMethod(
@@ -160,7 +161,7 @@
 
   @Test
   public void testJsrWithStraightlineCode() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of(), "I",
@@ -192,7 +193,7 @@
 
   @Test
   public void testJsrWithStraightlineCodeMultiple() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of(), "I",
@@ -232,7 +233,7 @@
 
   @Test
   public void testJsrWithStraightlineCodeMultiple2() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of(), "I",
@@ -278,7 +279,7 @@
 
   @Test
   public void testJsrWithControlFlowCode() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -315,7 +316,7 @@
 
   @Test
   public void testJsrWithNestedJsr() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -360,7 +361,7 @@
 
   @Test
   public void testJsrWithNestedJsrPopReturnAddress() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -392,7 +393,7 @@
 
   @Test
   public void testJsrWithNestedPopReturnAddress2() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -418,7 +419,7 @@
 
   @Test
   public void testJsrJustThrows() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -450,7 +451,7 @@
 
   @Test
   public void testJsrJustThrows2() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -489,7 +490,7 @@
 
   @Test
   public void testJsrWithException() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -537,7 +538,7 @@
 
   @Test
   public void testJsrWithAddressManipulation() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of(), "V",
@@ -573,7 +574,7 @@
   @Test
   public void testJsrWithSharedExceptionHandler() throws Exception {
     // Regression test for b/37659886
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of("I"), "V",
@@ -615,7 +616,7 @@
 
   @Test
   public void regressJsrHitParentCatchHandler() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("test", ImmutableList.of(), "I",
@@ -707,7 +708,7 @@
 
   @Test
   public void regressJsrHitParentCatchHandler2() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     generateRegressJsrHitParentCatchHandler2(clazz, "test1", "java/lang/Exception", false);
@@ -734,7 +735,7 @@
   // https://github.com/cbeust/testng/blob/4a8459e36f2b0ed057ffa7e470f1057e8e5b0ff9/src/main/java/org/testng/internal/Invoker.java#L1066
   // compiled with some ancient version of javac generating code with jsr for try/finally.
   public void regress38156139() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of("Z"), "I",
@@ -787,7 +788,7 @@
 
   @Test
   public void regress37767254() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     // This is the code for the method org.apache.log4j.net.SocketAppender$Connector.run() from
@@ -1302,7 +1303,7 @@
   @Test
   // This test is based on the example on http://asm.ow2.org/doc/developer-guide.html.
   public void testJsrWithNestedJsrRetBasedOnControlFlow() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_3);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of("Z"), "I",
@@ -1350,7 +1351,7 @@
   @Test
   // This test is based on the example on http://asm.ow2.org/doc/developer-guide.html.
   public void testJsrWithNestedRetBasedOnControlFlow2() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of("Z"), "I",
@@ -1398,7 +1399,7 @@
   @Test
   // This test is based on the example on http://asm.ow2.org/doc/developer-guide.html.
   public void testJsrWithNestedRetBasedOnControlFlow3() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of("Z"), "I",
diff --git a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
index 8f32184..fb3c067 100644
--- a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
@@ -3,8 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
+import static java.util.Collections.emptyList;
+
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import org.junit.Assert;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -15,7 +18,7 @@
 
   @Test
   public void lookupStaticFieldFromDiamondInterface() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
 
     ClassBuilder interfaceA = builder.addInterface("InterfaceA");
     interfaceA.addStaticFinalField("aField", "I", "42");
@@ -39,7 +42,7 @@
 
   @Test
   public void lookupStaticFieldFromInterfaceNotSuper() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
 
     ClassBuilder superClass = builder.addClass("SuperClass");
     superClass.addStaticFinalField("aField", "I", "42");
@@ -63,7 +66,7 @@
 
   @Test
   public void lookupStaticFieldFromSupersInterfaceNotSupersSuper() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
 
     ClassBuilder superSuperClass = builder.addClass("SuperSuperClass");
     superSuperClass.addStaticFinalField("aField", "I", "123");
@@ -117,6 +120,189 @@
     ensureICCE(builder);
   }
 
+  @Test
+  @Ignore("b/69101406")
+  public void lookupVirtualMethodWithConflictingPrivate() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+
+    ClassBuilder superClass = builder.addClass("SuperClass");
+    superClass.addDefaultConstructor();
+    superClass.addVirtualMethod("aMethod", emptyList(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  bipush 42",
+        "  invokevirtual java/io/PrintStream/println(I)V",
+        "  return");
+
+    ClassBuilder subClass = builder.addClass("SubClass", "SuperClass");
+    subClass.addDefaultConstructor();
+    subClass.addPrivateVirtualMethod("aMethod", emptyList(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  bipush 123",
+        "  invokevirtual java/io/PrintStream/println(I)V",
+        "  return");
+
+    ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+    mainClass.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  new SubClass",
+        "  dup",
+        "  invokespecial SubClass/<init>()V",
+        "  invokevirtual SubClass/aMethod()V",
+        "  return");
+    ensureIAEExceptJava(builder);
+  }
+
+  @Test
+  @Ignore("b/69152228")
+  public void lookupDirectMethodFromWrongContext() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+
+    ClassBuilder superClass = builder.addClass("SuperClass");
+    superClass.addDefaultConstructor();
+    superClass.addVirtualMethod("aMethod", emptyList(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  bipush 42",
+        "  invokevirtual java/io/PrintStream/println(I)V",
+        "  return");
+
+    ClassBuilder subClass = builder.addClass("SubClass", "SuperClass");
+    subClass.addDefaultConstructor();
+    subClass.addPrivateVirtualMethod("aMethod", emptyList(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  bipush 123",
+        "  invokevirtual java/io/PrintStream/println(I)V",
+        "  return");
+
+    ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+    mainClass.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  new SubClass",
+        "  dup",
+        "  invokespecial SubClass/<init>()V",
+        "  invokespecial SubClass/aMethod()V",
+        "  return");
+    ensureIAEExceptJava(builder);
+  }
+
+  @Test
+  public void lookupPrivateSuperFromSubClass() throws Exception {
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JSE_5);
+
+    ClassBuilder superClass = builder.addClass("SuperClass");
+    superClass.addDefaultConstructor();
+    superClass.addPrivateVirtualMethod("aMethod", emptyList(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  bipush 42",
+        "  invokevirtual java/io/PrintStream/println(I)V",
+        "  return");
+
+    ClassBuilder subClass = builder.addClass("SubClass", "SuperClass");
+    subClass.addDefaultConstructor();
+    subClass.addVirtualMethod("callAMethod", emptyList(), "V",
+        ".limit stack 1",
+        ".limit locals 1",
+        "  aload 0",
+        "  invokespecial SuperClass/aMethod()V",
+        "  return");
+
+    ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+    mainClass.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  new SubClass",
+        "  dup",
+        "  invokespecial SubClass/<init>()V",
+        "  invokevirtual SubClass/callAMethod()V",
+        "  return");
+
+    ensureIAEExceptJava(builder);
+  }
+
+  @Test
+  @Ignore("b/69101406")
+  public void lookupStaticMethodWithConflictingVirtual() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+
+    ClassBuilder superClass = builder.addClass("SuperClass");
+    superClass.addDefaultConstructor();
+    superClass.addStaticMethod("aMethod", emptyList(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  bipush 42",
+        "  invokevirtual java/io/PrintStream/println(I)V",
+        "  return");
+
+    ClassBuilder subClass = builder.addClass("SubClass", "SuperClass");
+    subClass.addDefaultConstructor();
+    subClass.addVirtualMethod("aMethod", emptyList(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  bipush 123",
+        "  invokevirtual java/io/PrintStream/println(I)V",
+        "  return");
+
+    ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+    mainClass.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  new SubClass",
+        "  dup",
+        "  invokespecial SubClass/<init>()V",
+        "  invokestatic SubClass/aMethod()V",
+        "  return");
+    ensureICCE(builder);
+  }
+
+  @Test
+  @Ignore("b/69101406")
+  public void lookupVirtualMethodWithConflictingStatic() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+
+    ClassBuilder superClass = builder.addClass("SuperClass");
+    superClass.addDefaultConstructor();
+    superClass.addVirtualMethod("aMethod", emptyList(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  bipush 42",
+        "  invokevirtual java/io/PrintStream/println(I)V",
+        "  return");
+
+    ClassBuilder subClass = builder.addClass("SubClass", "SuperClass");
+    subClass.addDefaultConstructor();
+    subClass.addStaticMethod("aMethod", emptyList(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  bipush 123",
+        "  invokevirtual java/io/PrintStream/println(I)V",
+        "  return");
+
+    ClassBuilder mainClass = builder.addClass(MAIN_CLASS);
+    mainClass.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  new SubClass",
+        "  dup",
+        "  invokespecial SubClass/<init>()V",
+        "  invokevirtual SubClass/aMethod()V",
+        "  return");
+    ensureICCE(builder);
+  }
 
   private void ensureSameOutput(JasminBuilder app) throws Exception {
     String dxOutput = runOnArtDx(app, MAIN_CLASS);
@@ -129,13 +315,23 @@
   }
 
   private void ensureICCE(JasminBuilder app) throws Exception {
+    ensureRuntimeException(app, IncompatibleClassChangeError.class);
+  }
+
+  private void ensureIAEExceptJava(JasminBuilder app)
+      throws Exception {
+    ensureRuntimeException(app, IllegalAccessError.class);
+  }
+
+  private void ensureRuntimeException(JasminBuilder app, Class exception) throws Exception {
+    String name = exception.getSimpleName();
     ProcessResult dxOutput = runOnArtDxRaw(app, MAIN_CLASS);
-    Assert.assertTrue(dxOutput.stderr.contains("IncompatibleClassChangeError"));
+    Assert.assertTrue(dxOutput.stderr.contains(name));
     ProcessResult d8Output = runOnArtD8Raw(app, MAIN_CLASS);
-    Assert.assertTrue(d8Output.stderr.contains("IncompatibleClassChangeError"));
+    Assert.assertTrue(d8Output.stderr.contains(name));
     ProcessResult r8Output = runOnArtR8Raw(app, MAIN_CLASS, null);
-    Assert.assertTrue(r8Output.stderr.contains("IncompatibleClassChangeError"));
+    Assert.assertTrue(r8Output.stderr.contains(name));
     ProcessResult javaOutput = runOnJavaRaw(app, MAIN_CLASS);
-    Assert.assertTrue(javaOutput.stderr.contains("IncompatibleClassChangeError"));
+    Assert.assertTrue(javaOutput.stderr.contains(name));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java b/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java
index 091a43a..edfaff0 100644
--- a/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
@@ -12,7 +13,7 @@
 
   @Test
   public void testSimplifyIf() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("test1", ImmutableList.of("Z"), "Z",
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java b/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java
index 8c1df56..4390d30 100644
--- a/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
@@ -12,7 +13,7 @@
 
   @Test
   public void testInvalidTypeInfoFromLocals() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of("I"), "V",
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java b/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
index df0d6e2..6290e20 100644
--- a/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.code.IfNez;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.google.common.collect.ImmutableList;
@@ -19,7 +20,7 @@
 
   @Test
   public void testConstantNotIntoEntryBlock() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     MethodSignature signature = clazz.addStaticMethod("test1", ImmutableList.of("I"), "I",
diff --git a/src/test/java/com/android/tools/r8/jasmin/TryCatchStateTests.java b/src/test/java/com/android/tools/r8/jasmin/TryCatchStateTests.java
index 39c2f50..ff63ed9 100644
--- a/src/test/java/com/android/tools/r8/jasmin/TryCatchStateTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/TryCatchStateTests.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
@@ -13,7 +14,7 @@
 
   @Test
   public void testTryCatchStackHeight() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of("I"), "I",
@@ -59,7 +60,7 @@
 
   @Test
   public void testTryCatchLocals() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of("I"), "I",
@@ -107,7 +108,7 @@
 
   @Test
   public void testTryCatchOnUnreachableLabel() throws Exception {
-    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
     clazz.addStaticMethod("foo", ImmutableList.of("I"), "I",
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index 191f6cc..ebbacc6 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -90,11 +90,13 @@
 
   @Parameters(name = "test: {0} keep: {1}")
   public static Collection<Object[]> data() {
-    List<String> tests = Arrays.asList("adaptclassstrings");
+    List<String> tests = Arrays.asList("adaptclassstrings", "identifiernamestring");
 
     Map<String, Consumer<DexInspector>> inspections = new HashMap<>();
     inspections.put("adaptclassstrings:keep-rules-1.txt", IdentifierMinifierTest::test1_rule1);
     inspections.put("adaptclassstrings:keep-rules-2.txt", IdentifierMinifierTest::test1_rule2);
+    inspections.put("identifiernamestring:keep-rules-1.txt", IdentifierMinifierTest::test2_rule1);
+    inspections.put("identifiernamestring:keep-rules-2.txt", IdentifierMinifierTest::test2_rule2);
 
     return NamingTestBase.createTests(tests, inspections);
   }
@@ -145,6 +147,56 @@
     assertEquals(1, renamedYetFoundIdentifierCount);
   }
 
+  // Without -identifiernamestring
+  private static void test2_rule1(DexInspector inspector) {
+    ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
+    MethodSubject main = mainClass.method(DexInspector.MAIN);
+    Code mainCode = main.getMethod().getCode();
+    verifyPresenceOfConstString(mainCode.asDexCode().instructions);
+    int renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
+    assertEquals(0, renamedYetFoundIdentifierCount);
+
+    ClassSubject aClass = inspector.clazz("identifiernamestring.A");
+    MethodSubject aInit =
+        aClass.method("void", "<init>", ImmutableList.of());
+    Code initCode = aInit.getMethod().getCode();
+    verifyPresenceOfConstString(initCode.asDexCode().instructions);
+    renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, initCode.asDexCode().instructions);
+    assertEquals(0, renamedYetFoundIdentifierCount);
+
+    renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, aClass.getDexClass().staticFields());
+    assertEquals(0, renamedYetFoundIdentifierCount);
+  }
+
+  // With -identifiernamestring for annotations and name-based filters
+  private static void test2_rule2(DexInspector inspector) {
+    ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
+    MethodSubject main = mainClass.method(DexInspector.MAIN);
+    assertTrue(main.isPresent());
+    Code mainCode = main.getMethod().getCode();
+    assertTrue(mainCode.isDexCode());
+    verifyPresenceOfConstString(mainCode.asDexCode().instructions);
+    int renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
+    assertEquals(1, renamedYetFoundIdentifierCount);
+
+    ClassSubject aClass = inspector.clazz("identifiernamestring.A");
+    MethodSubject aInit =
+        aClass.method("void", "<init>", ImmutableList.of());
+    Code initCode = aInit.getMethod().getCode();
+    verifyPresenceOfConstString(initCode.asDexCode().instructions);
+    renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, initCode.asDexCode().instructions);
+    assertEquals(1, renamedYetFoundIdentifierCount);
+
+    renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, aClass.getDexClass().staticFields());
+    assertEquals(2, renamedYetFoundIdentifierCount);
+  }
+
   private static void verifyPresenceOfConstString(Instruction[] instructions) {
     boolean presence =
         Arrays.stream(instructions)
@@ -169,7 +221,7 @@
         .filter(instr -> instr instanceof ConstString || instr instanceof ConstStringJumbo)
         .reduce(0, (cnt, instr) -> {
           String cnstString = retrieveString(instr);
-          if (!DescriptorUtils.isClassDescriptor(cnstString)) {
+          if (DescriptorUtils.isValidJavaType(cnstString)) {
             ClassSubject classSubject = inspector.clazz(cnstString);
             if (classSubject.isRenamed()
                 && DescriptorUtils.descriptorToJavaType(classSubject.getFinalDescriptor())
@@ -187,7 +239,7 @@
         .filter(encodedField -> encodedField.staticValue instanceof DexValueString)
         .reduce(0, (cnt, encodedField) -> {
           String cnstString = ((DexValueString) encodedField.staticValue).getValue().toString();
-          if (!DescriptorUtils.isClassDescriptor(cnstString)) {
+          if (DescriptorUtils.isValidJavaType(cnstString)) {
             ClassSubject classSubject = inspector.clazz(cnstString);
             if (classSubject.isRenamed()
                 && DescriptorUtils.descriptorToJavaType(classSubject.getFinalDescriptor())
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
new file mode 100644
index 0000000..1431dd9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -0,0 +1,358 @@
+// Copyright (c) 2017, 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.naming;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.InvokeDirect;
+import com.android.tools.r8.code.InvokeStatic;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.code.IputObject;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.code.SgetObject;
+import com.android.tools.r8.code.SputObject;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.smali.SmaliTestBase;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+
+public class IdentifierNameStringMarkerTest extends SmaliTestBase {
+
+  private final static String BOO = "Boo";
+
+  @Test
+  public void instancePut_singleUseOperand() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder("Example");
+    builder.addInstanceField("aClassName", "Ljava/lang/String;");
+    MethodSignature init = builder.addInitializer(ImmutableList.of(), 1,
+        "invoke-direct {p0}, Ljava/lang/Object;-><init>()V",
+        "const-string v0, \"" + BOO + "\"",
+        "iput-object v0, p0, LExample;->aClassName:Ljava/lang/String;",
+        "return-void");
+
+    List<String> pgConfigs = ImmutableList.of(
+        "-identifiernamestring class Example { java.lang.String aClassName; }",
+        "-keep class Example",
+        "-dontoptimize");
+    Path processedApp = runR8(builder, pgConfigs);
+
+    DexEncodedMethod method = getMethod(processedApp, init);
+    assertNotNull(method);
+
+    DexCode code = method.getCode().asDexCode();
+    assertTrue(code.instructions[0] instanceof InvokeDirect);
+    assertTrue(code.instructions[1] instanceof ConstString);
+    // TODO(b/36799092): DeadCodeRemover should be able to remove this instruction.
+    ConstString constString = (ConstString) code.instructions[1];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[2] instanceof ConstString);
+    constString = (ConstString) code.instructions[2];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[3] instanceof IputObject);
+    assertTrue(code.instructions[4] instanceof ReturnVoid);
+  }
+
+  @Test
+  public void instancePut_sharedOperand() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder("Example");
+    builder.addInstanceField("aClassName", "Ljava/lang/String;");
+    MethodSignature init = builder.addInitializer(ImmutableList.of(), 2,
+        "invoke-direct {p0}, Ljava/lang/Object;-><init>()V",
+        "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "const-string v1, \"" + BOO + "\"",
+        "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "iput-object v1, p0, LExample;->aClassName:Ljava/lang/String;",
+        "return-void");
+
+    List<String> pgConfigs = ImmutableList.of(
+        "-identifiernamestring class Example { java.lang.String aClassName; }",
+        "-keep class Example",
+        "-dontoptimize");
+    Path processedApp = runR8(builder, pgConfigs);
+
+    DexEncodedMethod method = getMethod(processedApp, init);
+    assertNotNull(method);
+
+    DexCode code = method.getCode().asDexCode();
+    assertTrue(code.instructions[0] instanceof InvokeDirect);
+    assertTrue(code.instructions[1] instanceof SgetObject);
+    assertTrue(code.instructions[2] instanceof ConstString);
+    ConstString constString = (ConstString) code.instructions[2];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[3] instanceof InvokeVirtual);
+    assertTrue(code.instructions[4] instanceof ConstString);
+    constString = (ConstString) code.instructions[4];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[5] instanceof IputObject);
+    assertTrue(code.instructions[6] instanceof ReturnVoid);
+  }
+
+  @Test
+  public void instancePut_sharedOperand_renamed() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder("Example");
+    builder.addInstanceField("aClassName", "Ljava/lang/String;");
+    MethodSignature init = builder.addInitializer(ImmutableList.of(), 2,
+        "invoke-direct {p0}, Ljava/lang/Object;-><init>()V",
+        "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "const-string v1, \"" + BOO + "\"",
+        "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "iput-object v1, p0, LExample;->aClassName:Ljava/lang/String;",
+        "return-void");
+    builder.addClass(BOO);
+
+    List<String> pgConfigs = ImmutableList.of(
+        "-identifiernamestring class Example { java.lang.String aClassName; }",
+        "-keep class Example",
+        "-keep,allowobfuscation class " + BOO,
+        "-dontoptimize");
+    Path processedApp = runR8(builder, pgConfigs);
+
+    DexEncodedMethod method = getMethod(processedApp, init);
+    assertNotNull(method);
+
+    DexCode code = method.getCode().asDexCode();
+    assertTrue(code.instructions[0] instanceof InvokeDirect);
+    assertTrue(code.instructions[1] instanceof SgetObject);
+    assertTrue(code.instructions[2] instanceof ConstString);
+    ConstString constString = (ConstString) code.instructions[2];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[3] instanceof InvokeVirtual);
+    assertTrue(code.instructions[4] instanceof ConstString);
+    constString = (ConstString) code.instructions[4];
+    assertNotEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[5] instanceof IputObject);
+    assertTrue(code.instructions[6] instanceof ReturnVoid);
+  }
+
+  @Test
+  public void staticPut_singleUseOperand() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder("Example");
+    builder.addStaticField("sClassName", "Ljava/lang/String;");
+    MethodSignature clinit = builder.addStaticInitializer(1,
+        "const-string v0, \"" + BOO + "\"",
+        "sput-object v0, LExample;->sClassName:Ljava/lang/String;",
+        "return-void");
+
+    List<String> pgConfigs = ImmutableList.of(
+        "-identifiernamestring class Example { static java.lang.String sClassName; }",
+        "-keep class Example",
+        "-dontoptimize");
+    Path processedApp = runR8(builder, pgConfigs);
+
+    DexEncodedMethod method = getMethod(processedApp, clinit);
+    assertNotNull(method);
+
+    DexCode code = method.getCode().asDexCode();
+    // TODO(b/36799092): DeadCodeRemover should be able to remove this instruction.
+    assertTrue(code.instructions[0] instanceof ConstString);
+    ConstString constString = (ConstString) code.instructions[0];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[1] instanceof ConstString);
+    constString = (ConstString) code.instructions[1];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[2] instanceof SputObject);
+    assertTrue(code.instructions[3] instanceof ReturnVoid);
+  }
+
+  @Test
+  public void staticPut_sharedOperand() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder("Example");
+    builder.addStaticField("sClassName", "Ljava/lang/String;");
+    MethodSignature clinit = builder.addStaticInitializer(2,
+        "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "const-string v1, \"" + BOO + "\"",
+        "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "sput-object v1, LExample;->sClassName:Ljava/lang/String;",
+        "return-void");
+
+    List<String> pgConfigs = ImmutableList.of(
+        "-identifiernamestring class Example { static java.lang.String sClassName; }",
+        "-keep class Example",
+        "-dontoptimize");
+    Path processedApp = runR8(builder, pgConfigs);
+
+    DexEncodedMethod method = getMethod(processedApp, clinit);
+    assertNotNull(method);
+
+    DexCode code = method.getCode().asDexCode();
+    assertTrue(code.instructions[0] instanceof SgetObject);
+    assertTrue(code.instructions[1] instanceof ConstString);
+    ConstString constString = (ConstString) code.instructions[1];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[2] instanceof InvokeVirtual);
+    assertTrue(code.instructions[3] instanceof ConstString);
+    constString = (ConstString) code.instructions[3];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[4] instanceof SputObject);
+    assertTrue(code.instructions[5] instanceof ReturnVoid);
+  }
+
+  @Test
+  public void staticPut_sharedOperand_renamed() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder("Example");
+    builder.addStaticField("sClassName", "Ljava/lang/String;");
+    MethodSignature clinit = builder.addStaticInitializer(2,
+        "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "const-string v1, \"" + BOO + "\"",
+        "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "sput-object v1, LExample;->sClassName:Ljava/lang/String;",
+        "return-void");
+    builder.addClass(BOO);
+
+    List<String> pgConfigs = ImmutableList.of(
+        "-identifiernamestring class Example { static java.lang.String sClassName; }",
+        "-keep class Example",
+        "-keep,allowobfuscation class " + BOO,
+        "-dontoptimize");
+    Path processedApp = runR8(builder, pgConfigs);
+
+    DexEncodedMethod method = getMethod(processedApp, clinit);
+    assertNotNull(method);
+
+    DexCode code = method.getCode().asDexCode();
+    assertTrue(code.instructions[0] instanceof SgetObject);
+    assertTrue(code.instructions[1] instanceof ConstString);
+    ConstString constString = (ConstString) code.instructions[1];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[2] instanceof InvokeVirtual);
+    assertTrue(code.instructions[3] instanceof ConstString);
+    constString = (ConstString) code.instructions[3];
+    assertNotEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[4] instanceof SputObject);
+    assertTrue(code.instructions[5] instanceof ReturnVoid);
+  }
+
+  @Test
+  public void invoke_singleUseOperand() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder("Example");
+    builder.addStaticMethod(
+        "void",
+        "foo",
+        ImmutableList.of("java.lang.String", "java.lang.String"),
+        0,
+        "return-void");
+    MethodSignature foo = builder.addInitializer(ImmutableList.of(), 2,
+        "invoke-direct {p0}, Ljava/lang/Object;-><init>()V",
+        "const-string v0, \"" + BOO + "\"",
+        "const-string v1, \"Mixed/form.Boo\"",
+        "invoke-static {v0, v1}, LExample;->foo(Ljava/lang/String;Ljava/lang/String;)V",
+        "return-void");
+
+    List<String> pgConfigs = ImmutableList.of(
+        "-identifiernamestring class Example { static void foo(...); }",
+        "-keep class Example",
+        "-dontoptimize");
+    Path processedApp = runR8(builder, pgConfigs);
+
+    DexEncodedMethod method = getMethod(processedApp, foo);
+    assertNotNull(method);
+
+    DexCode code = method.getCode().asDexCode();
+    assertTrue(code.instructions[0] instanceof InvokeDirect);
+    // TODO(b/36799092): DeadCodeRemover should be able to remove this instruction.
+    assertTrue(code.instructions[1] instanceof ConstString);
+    ConstString constString = (ConstString) code.instructions[1];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[2] instanceof ConstString);
+    constString = (ConstString) code.instructions[2];
+    assertEquals("Mixed/form.Boo", constString.getString().toString());
+    assertTrue(code.instructions[3] instanceof ConstString);
+    constString = (ConstString) code.instructions[3];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[4] instanceof InvokeStatic);
+    assertTrue(code.instructions[5] instanceof ReturnVoid);
+  }
+
+  @Test
+  public void invoke_sharedOperand() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder("Example");
+    builder.addStaticMethod(
+        "void",
+        "foo",
+        ImmutableList.of("java.lang.String"),
+        0,
+        "return-void");
+    MethodSignature foo = builder.addInitializer(ImmutableList.of(), 3,
+        "invoke-direct {p0}, Ljava/lang/Object;-><init>()V",
+        "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "const-string v1, \"" + BOO + "\"",
+        "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "invoke-static {v1}, LExample;->foo(Ljava/lang/String;)V",
+        "return-void");
+
+    List<String> pgConfigs = ImmutableList.of(
+        "-identifiernamestring class Example { static void foo(...); }",
+        "-keep class Example",
+        "-dontoptimize");
+    Path processedApp = runR8(builder, pgConfigs);
+
+    DexEncodedMethod method = getMethod(processedApp, foo);
+    assertNotNull(method);
+
+    DexCode code = method.getCode().asDexCode();
+    assertTrue(code.instructions[0] instanceof InvokeDirect);
+    assertTrue(code.instructions[1] instanceof SgetObject);
+    assertTrue(code.instructions[2] instanceof ConstString);
+    ConstString constString = (ConstString) code.instructions[2];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[3] instanceof InvokeVirtual);
+    assertTrue(code.instructions[4] instanceof ConstString);
+    constString = (ConstString) code.instructions[4];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[5] instanceof InvokeStatic);
+    assertTrue(code.instructions[6] instanceof ReturnVoid);
+  }
+
+  @Test
+  public void invoke_sharedOperand_renamed() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder("Example");
+    builder.addStaticMethod(
+        "void",
+        "foo",
+        ImmutableList.of("java.lang.String"),
+        0,
+        "return-void");
+    MethodSignature foo = builder.addInitializer(ImmutableList.of(), 3,
+        "invoke-direct {p0}, Ljava/lang/Object;-><init>()V",
+        "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "const-string v1, \"" + BOO + "\"",
+        "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "invoke-static {v1}, LExample;->foo(Ljava/lang/String;)V",
+        "return-void");
+    builder.addClass(BOO);
+
+    List<String> pgConfigs = ImmutableList.of(
+        "-identifiernamestring class Example { static void foo(...); }",
+        "-keep class Example",
+        "-keep,allowobfuscation class " + BOO,
+        "-dontoptimize");
+    Path processedApp = runR8(builder, pgConfigs);
+
+    DexEncodedMethod method = getMethod(processedApp, foo);
+    assertNotNull(method);
+
+    DexCode code = method.getCode().asDexCode();
+    assertTrue(code.instructions[0] instanceof InvokeDirect);
+    assertTrue(code.instructions[1] instanceof SgetObject);
+    assertTrue(code.instructions[2] instanceof ConstString);
+    ConstString constString = (ConstString) code.instructions[2];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[3] instanceof InvokeVirtual);
+    assertTrue(code.instructions[4] instanceof ConstString);
+    constString = (ConstString) code.instructions[4];
+    assertNotEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[5] instanceof InvokeStatic);
+    assertTrue(code.instructions[6] instanceof ReturnVoid);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
index ad1e097..b8ea55a 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
@@ -252,37 +252,31 @@
     return new MethodSignature(currentClassName, name, returnType, parameters);
   }
 
-  public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
-      int locals, String... instructions) {
-    StringBuilder builder = new StringBuilder();
-    for (String instruction : instructions) {
-      builder.append(instruction);
-      builder.append("\n");
-    }
-    return addStaticMethod(returnType, name, parameters, locals, builder.toString());
+  public MethodSignature addStaticMethod(
+      String returnType, String name, List<String> parameters, int locals, String... instructions) {
+    return addStaticMethod(returnType, name, parameters, locals, buildCode(instructions));
   }
 
-  public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
-      int locals, String code) {
+  public MethodSignature addStaticMethod(
+      String returnType, String name, List<String> parameters, int locals, String code) {
     return addStaticMethod("", returnType, name, parameters, locals, code);
   }
 
   public MethodSignature addStaticInitializer(int locals, String... instructions) {
-    StringBuilder builder = new StringBuilder();
-    for (String instruction : instructions) {
-      builder.append(instruction);
-      builder.append("\n");
-    }
-    return addStaticInitializer(locals, builder.toString());
+    return addStaticInitializer(locals, buildCode(instructions));
   }
 
   public MethodSignature addStaticInitializer(int locals, String code) {
     return addStaticMethod("constructor", "void", "<clinit>", ImmutableList.of(), locals, code);
   }
 
-  private MethodSignature addStaticMethod(String flags, String returnType, String name,
-      List<String> parameters, int locals, String code) {
-    StringBuilder builder = new StringBuilder();
+  private MethodSignature addStaticMethod(
+      String flags,
+      String returnType,
+      String name,
+      List<String> parameters,
+      int locals,
+      String code) {
     return addMethod("public static " + flags, returnType, name, parameters, locals, code);
   }
 
@@ -291,20 +285,19 @@
     return addMethod("public abstract", returnType, name, parameters, -1, null);
   }
 
-  public MethodSignature addInstanceMethod(String returnType, String name,
-      List<String> parameters,
-      int locals, String... instructions) {
-    StringBuilder builder = new StringBuilder();
-    for (String instruction : instructions) {
-      builder.append(instruction);
-      builder.append("\n");
-    }
-    return addInstanceMethod(returnType, name, parameters, locals, builder.toString());
+  public MethodSignature addInitializer(
+      List<String> parameters, int locals, String... instructions) {
+    return addMethod(
+        "public constructor", "void", "<init>", parameters, locals, buildCode(instructions));
   }
 
-  public MethodSignature addInstanceMethod(String returnType, String name,
-      List<String> parameters,
-      int locals, String code) {
+  public MethodSignature addInstanceMethod(
+      String returnType, String name, List<String> parameters, int locals, String... instructions) {
+    return addInstanceMethod(returnType, name, parameters, locals, buildCode(instructions));
+  }
+
+  public MethodSignature addInstanceMethod(
+      String returnType, String name, List<String> parameters, int locals, String code) {
     return addMethod("public", returnType, name, parameters, locals, code);
   }
 
@@ -317,11 +310,20 @@
     StringBuilder builder = new StringBuilder();
     for (String line : source) {
       builder.append(line);
-      builder.append("\n");
+      builder.append(System.lineSeparator());
     }
     getSource(currentClassName).add(builder.toString());
   }
 
+  private static String buildCode(String... instructions) {
+    StringBuilder builder = new StringBuilder();
+    for (String instruction : instructions) {
+      builder.append(instruction);
+      builder.append(System.lineSeparator());
+    }
+    return builder.toString();
+  }
+
   public List<String> buildSource() {
     List<String> result = new ArrayList<>(classes.size());
     for (String clazz : classes.keySet()) {
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 4949153..1a864fe 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -83,6 +83,10 @@
     }
   }
 
+  protected Path runR8(SmaliBuilder builder, List<String> proguardConfigurations) {
+    return runR8(builder, proguardConfigurations, pg -> {}, o -> {});
+  }
+
   protected Path runR8(
       SmaliBuilder builder,
       List<String> proguardConfigurations,