Merge "Keep static members matched by -keepclassmembers on kept classes"
diff --git a/build.gradle b/build.gradle
index 61360dc..c0a183b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -77,6 +77,12 @@
             ]
         }
     }
+    apiUsageSample {
+        java {
+            srcDirs = ['src/test/apiUsageSample']
+        }
+        output.resourcesDir = 'build/classes/apiUsageSample'
+    }
     debugTestResources {
         java {
             srcDirs = ['src/test/debugTestResources']
@@ -191,6 +197,7 @@
     supportLibs 'com.android.support:support-v4:25.4.0'
     supportLibs 'junit:junit:4.12'
     supportLibs 'com.android.support.test.espresso:espresso-core:3.0.0'
+    apiUsageSampleCompile sourceSets.main.output
     debugTestResourcesKotlinCompileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.1.4-3'
     apt 'com.google.auto.value:auto-value:1.5'
 }
@@ -630,6 +637,12 @@
     dependsOn createJctfTests
 }
 
+task buildApiUsageSample(type: Jar) {
+    from sourceSets.apiUsageSample.output
+    baseName 'api_usage_sample'
+    destinationDir file('tests')
+}
+
 task buildDebugInfoExamplesDex {
     def examplesDir = file("src/test/java")
     def hostJar = "debuginfo_examples.jar"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index a9f3329..18096f4 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -62,7 +62,6 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 
 public class R8 {
 
@@ -106,51 +105,6 @@
     }
   }
 
-  static DexApplication optimize(
-      DexApplication application,
-      AppInfoWithSubtyping appInfo,
-      InternalOptions options)
-      throws ApiLevelException, ExecutionException, IOException {
-    return new R8(options).optimize(application, appInfo);
-  }
-
-  private DexApplication optimize(DexApplication application, AppInfoWithSubtyping appInfo)
-      throws IOException, ApiLevelException, ExecutionException {
-    return optimize(application, appInfo, GraphLense.getIdentityLense(),
-        Executors.newSingleThreadExecutor());
-  }
-
-  private DexApplication optimize(
-      DexApplication application,
-      AppInfoWithSubtyping appInfo,
-      GraphLense graphLense,
-      ExecutorService executorService)
-      throws IOException, ApiLevelException, ExecutionException {
-    final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
-
-    timing.begin("Create IR");
-    try {
-      IRConverter converter = new IRConverter(
-          appInfo, options, timing, printer, graphLense);
-      application = converter.optimize(application, executorService);
-    } finally {
-      timing.end();
-    }
-
-    if (options.printCfg) {
-      if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
-        System.out.print(printer.toString());
-      } else {
-        try (OutputStreamWriter writer = new OutputStreamWriter(
-            new FileOutputStream(options.printCfgFile),
-            StandardCharsets.UTF_8)) {
-          writer.write(printer.toString());
-        }
-      }
-    }
-    return application;
-  }
-
   private Set<DexType> filterMissingClasses(Set<DexType> missingClasses,
       ProguardClassFilter dontWarnPatterns) {
     Set<DexType> result = new HashSet<>(missingClasses);
@@ -278,7 +232,26 @@
 
       graphLense = new BridgeMethodAnalysis(graphLense, appInfo.withSubtyping()).run();
 
-      application = optimize(application, appInfo, graphLense, executorService);
+      timing.begin("Create IR");
+      CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
+      try {
+        IRConverter converter = new IRConverter(appInfo, options, timing, printer, graphLense);
+        application = converter.optimize(application, executorService);
+      } finally {
+        timing.end();
+      }
+
+      if (options.printCfg) {
+        if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
+          System.out.print(printer.toString());
+        } else {
+          try (OutputStreamWriter writer = new OutputStreamWriter(
+              new FileOutputStream(options.printCfgFile),
+              StandardCharsets.UTF_8)) {
+            writer.write(printer.toString());
+          }
+        }
+      }
 
       // Overwrite SourceFile if specified. This step should be done after IR conversion.
       timing.begin("Rename SourceFile");
diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
new file mode 100644
index 0000000..6242e9b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
@@ -0,0 +1,162 @@
+// 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.errors.Unreachable;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstClass;
+import com.android.tools.r8.ir.code.ConstInstruction;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Load;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Pop;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.StackValue;
+import com.android.tools.r8.ir.code.Store;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfBuilder.FixedLocal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class LoadStoreHelper {
+
+  private final IRCode code;
+  private final Map<Value, DexType> types;
+
+  public LoadStoreHelper(IRCode code, Map<Value, DexType> types) {
+    this.code = code;
+    this.types = types;
+  }
+
+  public void insertLoadsAndStores() {
+    // Insert phi stores in all predecessors.
+    for (BasicBlock block : code.blocks) {
+      if (!block.getPhis().isEmpty()) {
+        for (int predIndex = 0; predIndex < block.getPredecessors().size(); predIndex++) {
+          BasicBlock pred = block.getPredecessors().get(predIndex);
+          List<Phi> phis = block.getPhis();
+          List<Value> values = new ArrayList<>(phis.size());
+          for (Phi phi : phis) {
+            values.add(phi.getOperand(predIndex));
+          }
+          InstructionListIterator it = pred.listIterator(pred.getInstructions().size());
+          it.previous();
+          movePhis(phis, values, it);
+        }
+      }
+    }
+    // Insert per-instruction loads and stores.
+    for (BasicBlock block : code.blocks) {
+      InstructionListIterator it = block.listIterator();
+      while (it.hasNext()) {
+        Instruction current = it.next();
+        current.insertLoadAndStores(it, this);
+      }
+    }
+  }
+
+  private StackValue createStackValue(Value value, int height) {
+    if (value.outType().isObject()) {
+      return StackValue.forObjectType(types.get(value), height);
+    }
+    return StackValue.forNonObjectType(value.outType(), height);
+  }
+
+  private StackValue createStackValue(DexType type, int height) {
+    if (type.isPrimitiveType()) {
+      return StackValue.forNonObjectType(ValueType.fromDexType(type), height);
+    }
+    return StackValue.forObjectType(type, height);
+  }
+
+  public void loadInValues(Instruction instruction, InstructionListIterator it) {
+    int topOfStack = 0;
+    it.previous();
+    for (Value value : instruction.inValues()) {
+      StackValue stackValue = createStackValue(value, topOfStack++);
+      add(load(stackValue, value), instruction, it);
+      value.removeUser(instruction);
+      instruction.replaceValue(value, stackValue);
+    }
+    it.next();
+  }
+
+  public void storeOutValue(Instruction instruction, InstructionListIterator it) {
+    if (instruction.outValue() instanceof StackValue) {
+      assert instruction.isConstInstruction();
+      return;
+    }
+    StackValue newOutValue = createStackValue(instruction.outValue(), 0);
+    Value oldOutValue = instruction.swapOutValue(newOutValue);
+    add(new Store(oldOutValue, newOutValue), instruction, it);
+  }
+
+  public void popOutValue(Value value, Instruction instruction, InstructionListIterator it) {
+    StackValue newOutValue = createStackValue(value, 0);
+    instruction.swapOutValue(newOutValue);
+    add(new Pop(newOutValue), instruction, it);
+  }
+
+  public void popOutType(DexType type, Instruction instruction, InstructionListIterator it) {
+    StackValue newOutValue = createStackValue(type, 0);
+    instruction.swapOutValue(newOutValue);
+    add(new Pop(newOutValue), instruction, it);
+  }
+
+  public void movePhis(List<Phi> phis, List<Value> values, InstructionListIterator it) {
+    // TODO(zerny): Accounting for non-interfering phis would lower the max stack size.
+    int topOfStack = 0;
+    List<StackValue> temps = new ArrayList<>(phis.size());
+    for (int i = 0; i < phis.size(); i++) {
+      Phi phi = phis.get(i);
+      Value value = values.get(i);
+      StackValue tmp = createStackValue(phi, topOfStack++);
+      add(load(tmp, value), phi.getBlock(), Position.none(), it);
+      temps.add(tmp);
+      value.removePhiUser(phi);
+    }
+    for (int i = phis.size() - 1; i >= 0; i--) {
+      Phi phi = phis.get(i);
+      StackValue tmp = temps.get(i);
+      FixedLocal out = new FixedLocal(phi);
+      add(new Store(out, tmp), phi.getBlock(), Position.none(), it);
+      phi.replaceUsers(out);
+    }
+  }
+
+  private Instruction load(StackValue stackValue, Value value) {
+    if (value.isConstant()) {
+      ConstInstruction constant = value.getConstInstruction();
+      if (constant.isConstNumber()) {
+        return new ConstNumber(stackValue, constant.asConstNumber().getRawValue());
+      } else if (constant.isConstString()) {
+        return new ConstString(stackValue, constant.asConstString().getValue());
+      } else if (constant.isConstClass()) {
+        return new ConstClass(stackValue, constant.asConstClass().getValue());
+      } else {
+        throw new Unreachable("Unexpected constant value: " + value);
+      }
+    }
+    return new Load(stackValue, value);
+  }
+
+  private static void add(
+      Instruction newInstruction, Instruction existingInstruction, InstructionListIterator it) {
+    add(newInstruction, existingInstruction.getBlock(), existingInstruction.getPosition(), it);
+  }
+
+  private static void add(
+      Instruction newInstruction, BasicBlock block, Position position, InstructionListIterator it) {
+    newInstruction.setBlock(block);
+    newInstruction.setPosition(position);
+    it.add(newInstruction);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
new file mode 100644
index 0000000..d22ff65
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -0,0 +1,119 @@
+// 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.errors.Unimplemented;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.Value;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+// Compute the types of all reference values.
+// The actual types are needed to emit stack-map frames for Java 1.6 and above.
+public class TypeVerificationHelper {
+
+  private final IRCode code;
+  private final DexItemFactory factory;
+
+  private Map<Value, DexType> types;
+
+  public TypeVerificationHelper(IRCode code, DexItemFactory factory) {
+    this.code = code;
+    this.factory = factory;
+  }
+
+  public DexItemFactory getFactory() {
+    return factory;
+  }
+
+  public DexType getType(Value value) {
+    assert value.outType().isObject();
+    return types.get(value);
+  }
+
+  // Helper to compute the join of a set of reference types.
+  public DexType join(Set<DexType> types) {
+    if (types.isEmpty()) {
+      return null; // Bottom element. Unknown type.
+    }
+    if (types.size() != 1) {
+      throw new Unimplemented("compute the join of the types");
+    }
+    return types.iterator().next();
+  }
+
+  public Map<Value, DexType> computeVerificationTypes() {
+    types = new HashMap<>();
+    Set<Value> worklist = new HashSet<>();
+    {
+      InstructionIterator it = code.instructionIterator();
+      Instruction instruction = null;
+      // Set the out-value types of each argument based on the method signature.
+      int argumentIndex = code.method.accessFlags.isStatic() ? 0 : -1;
+      while (it.hasNext()) {
+        instruction = it.next();
+        if (!instruction.isArgument()) {
+          break;
+        }
+        DexType argumentType =
+            (argumentIndex < 0)
+                ? code.method.method.getHolder()
+                : code.method.method.proto.parameters.values[argumentIndex];
+        Value outValue = instruction.outValue();
+        types.put(outValue, argumentType);
+        addUsers(outValue, worklist);
+        ++argumentIndex;
+      }
+      // Compute the out-value type of each normal instruction with an out-value but no in values.
+      while (instruction != null) {
+        assert !instruction.isArgument();
+        if (instruction.outValue() != null && instruction.outType().isObject()) {
+          Value outValue = instruction.outValue();
+          if (instruction.hasInvariantVerificationType()) {
+            DexType type = instruction.computeVerificationType(this);
+            assert type != null;
+            types.put(outValue, type);
+            addUsers(outValue, worklist);
+          }
+        }
+        instruction = it.hasNext() ? it.next() : null;
+      }
+    }
+    // Compute the fixed-point of all the out-value types.
+    while (!worklist.isEmpty()) {
+      Value item = worklist.iterator().next();
+      worklist.remove(item);
+      DexType previousType = types.get(item);
+      DexType refinedType = computeVerificationType(item);
+      if (previousType != refinedType) {
+        types.put(item, refinedType);
+        addUsers(item, worklist);
+      }
+    }
+    return types;
+  }
+
+  private DexType computeVerificationType(Value value) {
+    return value.isPhi()
+        ? value.asPhi().computeVerificationType(this)
+        : value.definition.computeVerificationType(this);
+  }
+
+  private static void addUsers(Value value, Set<Value> worklist) {
+    worklist.addAll(value.uniquePhiUsers());
+    for (Instruction instruction : value.uniqueUsers()) {
+      if (instruction.outValue() != null
+          && instruction.outType().isObject()
+          && !instruction.hasInvariantVerificationType()) {
+        worklist.add(instruction.outValue());
+      }
+    }
+  }
+}
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
new file mode 100644
index 0000000..78a350d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -0,0 +1,66 @@
+// 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.code;
+
+import static org.objectweb.asm.Opcodes.F_NEW;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
+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 {
+
+  private final Int2ReferenceSortedMap<DexType> locals;
+
+  public CfFrame(Int2ReferenceSortedMap<DexType> locals) {
+    this.locals = locals;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    int type = F_NEW;
+    int localsSize = locals.size();
+    Object[] localsCopy = new Object[localsSize];
+    int localIndex = 0;
+    for (Entry<DexType> entry : locals.int2ReferenceEntrySet()) {
+      Object typeOpcode = getType(entry.getValue());
+      if (typeOpcode == Opcodes.LONG || typeOpcode == Opcodes.DOUBLE) {
+        localsCopy[localIndex++] = Opcodes.TOP;
+      }
+      localsCopy[localIndex++] = typeOpcode;
+    }
+    // TODO(zerny): Compute the stack types too.
+    visitor.visitFrame(type, localsSize, localsCopy, 0, new Object[0]);
+  }
+
+  private Object getType(DexType type) {
+    if (type == DexItemFactory.nullValueType) {
+      return Opcodes.NULL;
+    }
+    switch (type.toShorty()) {
+      case 'L':
+        return Type.getType(type.toDescriptorString()).getInternalName();
+      case 'I':
+        return Opcodes.INTEGER;
+      case 'F':
+        return Opcodes.FLOAT;
+      case 'J':
+        return Opcodes.LONG;
+      case 'D':
+        return Opcodes.DOUBLE;
+      default:
+        throw new Unreachable("Unexpected value type: " + type);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return getClass().getSimpleName();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index a05299c..083c865 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.DexSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -161,16 +162,18 @@
   @Override
   public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
       throws ApiLevelException {
-    DexSourceCode source = new DexSourceCode(this, encodedMethod);
+    DexSourceCode source = new DexSourceCode(this, encodedMethod, null);
     IRBuilder builder = new IRBuilder(encodedMethod, source, options);
     return builder.build();
   }
 
   public IRCode buildIR(
       DexEncodedMethod encodedMethod,
-      InternalOptions options, ValueNumberGenerator valueNumberGenerator)
+      InternalOptions options,
+      ValueNumberGenerator valueNumberGenerator,
+      Position callerPosition)
       throws ApiLevelException {
-    DexSourceCode source = new DexSourceCode(this, encodedMethod);
+    DexSourceCode source = new DexSourceCode(this, encodedMethod, callerPosition);
     IRBuilder builder = new IRBuilder(encodedMethod, source, options, valueNumberGenerator);
     return builder.build();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
index f083794..908372a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableMap;
 import java.util.SortedSet;
@@ -16,19 +17,27 @@
   public final boolean prologueEnd;
   public final boolean epilogueBegin;
   public final ImmutableMap<Integer, DebugLocalInfo> locals;
+  public final DexMethod method;
+  public final Position callerPosition;
 
-  public DexDebugEntry(int address,
+  public DexDebugEntry(
+      int address,
       int line,
       DexString sourceFile,
       boolean prologueEnd,
       boolean epilogueBegin,
-      ImmutableMap<Integer, DebugLocalInfo> locals) {
+      ImmutableMap<Integer, DebugLocalInfo> locals,
+      DexMethod method,
+      Position callerPosition) {
     this.address = address;
     this.line = line;
     this.sourceFile = sourceFile;
     this.prologueEnd = prologueEnd;
     this.epilogueBegin = epilogueBegin;
     this.locals = locals;
+    this.method = method;
+    assert method != null;
+    this.callerPosition = callerPosition;
   }
 
   @Override
@@ -46,6 +55,10 @@
     if (sourceFile != null) {
       builder.append(", file ").append(sourceFile);
     }
+    if (callerPosition != null) {
+      builder.append(", method ").append(method);
+      builder.append(" <-(").append(callerPosition).append(")");
+    }
     if (prologueEnd) {
       builder.append(", prologue_end = true");
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
index c51e6e3..6ca2268 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
 import com.google.common.collect.ImmutableMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
@@ -43,10 +44,13 @@
   private int currentPc = 0;
   private int currentLine;
   private DexString currentFile = null;
+  private DexMethod currentMethod = null;
+  private Position currentCallerPosition = null;
   private boolean prologueEnd = false;
   private boolean epilogueBegin = false;
   private final Map<Integer, LocalEntry> locals = new HashMap<>();
   private final Int2ReferenceMap<DebugLocalInfo> arguments = new Int2ReferenceArrayMap<>();
+  private final DexMethod method;
 
   // Delayed construction of an entry. Is finalized once locals information has been collected.
   private DexDebugEntry pending = null;
@@ -57,11 +61,16 @@
   // Resulting debug entries.
   private List<DexDebugEntry> entries = new ArrayList<>();
 
-  public DexDebugEntryBuilder(int startLine) {
+  public DexDebugEntryBuilder(int startLine, DexMethod method) {
     currentLine = startLine;
+    this.method = method;
+    assert this.method != null;
+    currentMethod = method;
   }
 
   public DexDebugEntryBuilder(DexEncodedMethod method, DexItemFactory factory) {
+    this.method = method.method;
+    assert this.method != null;
     DexCode code = method.getCode().asDexCode();
     DexDebugInfo info = code.getDebugInfo();
     int argumentRegister = code.registerSize - code.incomingRegisterSize;
@@ -81,6 +90,7 @@
       argumentRegister += ValueType.fromDexType(types[i]).requiredRegisters();
     }
     currentLine = info.startLine;
+    currentMethod = this.method;
     for (DexDebugEvent event : info.events) {
       event.addToBuilder(this);
     }
@@ -94,6 +104,13 @@
     currentFile = file;
   }
 
+  public void setInlineFrame(DexMethod callee, Position caller) {
+    assert (caller == null && callee == method)
+        || (caller != null && caller.getOutermostCaller().method == method);
+    currentMethod = callee;
+    currentCallerPosition = caller;
+  }
+
   public void advancePC(int pcDelta) {
     assert pcDelta >= 0;
     currentPc += pcDelta;
@@ -133,15 +150,29 @@
     assert pcDelta >= 0;
     if (pending != null) {
       // Local changes contribute to the pending position entry.
-      entries.add(new DexDebugEntry(
-          pending.address, pending.line, pending.sourceFile,
-          pending.prologueEnd, pending.epilogueBegin,
-          getLocals()));
+      entries.add(
+          new DexDebugEntry(
+              pending.address,
+              pending.line,
+              pending.sourceFile,
+              pending.prologueEnd,
+              pending.epilogueBegin,
+              getLocals(),
+              pending.method,
+              pending.callerPosition));
     }
     currentPc += pcDelta;
     currentLine += lineDelta;
-    pending = new DexDebugEntry(
-        currentPc, currentLine, currentFile, prologueEnd, epilogueBegin, null);
+    pending =
+        new DexDebugEntry(
+            currentPc,
+            currentLine,
+            currentFile,
+            prologueEnd,
+            epilogueBegin,
+            null,
+            currentMethod,
+            currentCallerPosition);
     prologueEnd = false;
     epilogueBegin = false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 8d64cf4..724c10b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.dex.DebugBytecodeWriter;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.ir.code.Position;
+import java.util.Objects;
 
 abstract public class DexDebugEvent extends DexItem {
 
@@ -379,6 +381,47 @@
     }
   }
 
+  public static class SetInlineFrame extends DexDebugEvent {
+
+    final DexMethod callee;
+    final Position caller;
+
+    SetInlineFrame(DexMethod callee, Position caller) {
+      assert callee != null;
+      this.callee = callee;
+      this.caller = caller;
+    }
+
+    @Override
+    public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
+      // CallerPosition will not be written.
+    }
+
+    @Override
+    public void addToBuilder(DexDebugEntryBuilder builder) {
+      builder.setInlineFrame(callee, caller);
+    }
+
+    @Override
+    public String toString() {
+      return String.format("SET_INLINE_FRAME %s %s", callee, caller);
+    }
+
+    @Override
+    public int hashCode() {
+      return 31 * callee.hashCode() + Objects.hashCode(caller);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (!(other instanceof SetInlineFrame)) {
+        return false;
+      }
+      SetInlineFrame o = (SetInlineFrame) other;
+      return callee == o.callee && Objects.equals(caller, o.caller);
+    }
+  }
+
   public static class Default extends DexDebugEvent {
 
     final int value;
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index c52d12c..1c71b9b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -210,7 +210,7 @@
       }
       assert emittedPosition.isNone();
       startLine = position.line;
-      emittedPosition = position;
+      emittedPosition = new Position(position.line, null, method.method, null);
     }
     debugPositionListBuilder.add(position.line, position.line);
     emitAdvancementEvents(emittedPc, emittedPosition, pc, position, events, factory);
@@ -250,6 +250,10 @@
     if (nextPosition.file != previousPosition.file) {
       events.add(factory.createSetFile(nextPosition.file));
     }
+    if (nextPosition.method != previousPosition.method
+        || nextPosition.callerPosition != previousPosition.callerPosition) {
+      events.add(factory.createSetInlineFrame(nextPosition.method, nextPosition.callerPosition));
+    }
     if (lineDelta < Constants.DBG_LINE_BASE
         || lineDelta - Constants.DBG_LINE_BASE >= Constants.DBG_LINE_RANGE) {
       events.add(factory.createAdvanceLine(lineDelta));
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index ae669a2..99d1227 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -24,8 +24,8 @@
     hashCode();
   }
 
-  public List<DexDebugEntry> computeEntries() {
-    DexDebugEntryBuilder builder = new DexDebugEntryBuilder(startLine);
+  public List<DexDebugEntry> computeEntries(DexMethod method) {
+    DexDebugEntryBuilder builder = new DexDebugEntryBuilder(startLine, method);
     for (DexDebugEvent event : events) {
       event.addToBuilder(builder);
     }
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 c8932be..8fbcbce 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -273,7 +274,15 @@
       throws ApiLevelException {
     return code == null
         ? null
-        : code.asDexCode().buildIR(this, options, valueNumberGenerator);
+        : code.asDexCode().buildIR(this, options, valueNumberGenerator, null);
+  }
+
+  public IRCode buildIR(
+      InternalOptions options, ValueNumberGenerator valueNumberGenerator, Position callerPosition)
+      throws ApiLevelException {
+    return code == null
+        ? null
+        : code.asDexCode().buildIR(this, options, valueNumberGenerator, callerPosition);
   }
 
   public void setCode(Code code) {
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 a7d44ba..8f45a2e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -12,8 +12,10 @@
 import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
 import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
+import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.naming.NamingLens;
 import com.google.common.collect.ImmutableSet;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -47,11 +49,22 @@
   private final SetEpilogueBegin setEpilogueBegin = new SetEpilogueBegin();
   private final SetPrologueEnd setPrologueEnd = new SetPrologueEnd();
   private final Map<DexString, SetFile> setFiles = new HashMap<>();
+  private final Map<SetInlineFrame, SetInlineFrame> setInlineFrames = new HashMap<>();
 
   boolean sorted = false;
 
   public static final DexType catchAllType = new DexType(new DexString("CATCH_ALL"));
-  private static final Set<DexItem> internalSentinels = ImmutableSet.of(catchAllType);
+
+  // Internal type containing only the null value.
+  public static final DexType nullValueType = new DexType(new DexString("NULL"));
+
+  private static final Set<DexItem> internalSentinels = ImmutableSet.of(
+      catchAllType,
+      nullValueType);
+
+  public static boolean isInternalSentinel(DexItem item) {
+    return internalSentinels.contains(item);
+  }
 
   public final DexString booleanDescriptor = createString("Z");
   public final DexString byteDescriptor = createString("B");
@@ -291,7 +304,7 @@
 
   private static <T extends DexItem> T canonicalize(ConcurrentHashMap<T, T> map, T item) {
     assert item != null;
-    assert !internalSentinels.contains(item);
+    assert !DexItemFactory.isInternalSentinel(item);
     T previous = map.putIfAbsent(item, item);
     return previous == null ? item : previous;
   }
@@ -326,7 +339,7 @@
       result = new DexType(descriptor);
       assert result.isArrayType() || result.isClassType() || result.isPrimitiveType() ||
           result.isVoidType();
-      assert !internalSentinels.contains(result);
+      assert !isInternalSentinel(result);
       types.put(descriptor, result);
     }
     return result;
@@ -452,6 +465,13 @@
     }
   }
 
+  // TODO(tamaskenez) b/69024229 Measure if canonicalization is worth it.
+  public SetInlineFrame createSetInlineFrame(DexMethod callee, Position caller) {
+    synchronized (setInlineFrames) {
+      return setInlineFrames.computeIfAbsent(new SetInlineFrame(callee, caller), p -> p);
+    }
+  }
+
   public boolean isConstructor(DexMethod method) {
     return method.name == constructorMethodName;
   }
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 d4deb54..69a71f9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -228,8 +228,8 @@
   public String toSourceString() {
     if (toStringCache == null) {
       // TODO(ager): Pass in a ProguardMapReader to map names back to original names.
-      if (this == DexItemFactory.catchAllType) {
-        toStringCache = "CATCH_ALL";
+      if (DexItemFactory.isInternalSentinel(this)) {
+        toStringCache = descriptor.toString();
       } else {
         toStringCache = DescriptorUtils.descriptorToJavaType(toDescriptorString());
       }
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 44bc529..ce2de41 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.Resource.Origin;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.JarSourceCode;
@@ -94,39 +95,49 @@
       throws ApiLevelException {
     triggerDelayedParsingIfNeccessary();
     return options.debug
-        ? internalBuildWithLocals(encodedMethod, options, null)
-        : internalBuild(encodedMethod, options, null);
+        ? internalBuildWithLocals(encodedMethod, options, null, null)
+        : internalBuild(encodedMethod, options, null, null);
   }
 
   public IRCode buildIR(
-      DexEncodedMethod encodedMethod, InternalOptions options, ValueNumberGenerator generator)
+      DexEncodedMethod encodedMethod,
+      InternalOptions options,
+      ValueNumberGenerator generator,
+      Position callerPosition)
       throws ApiLevelException {
     assert generator != null;
     triggerDelayedParsingIfNeccessary();
     return options.debug
-        ? internalBuildWithLocals(encodedMethod, options, generator)
-        : internalBuild(encodedMethod, options, generator);
+        ? internalBuildWithLocals(encodedMethod, options, generator, callerPosition)
+        : internalBuild(encodedMethod, options, generator, callerPosition);
   }
 
   private IRCode internalBuildWithLocals(
-      DexEncodedMethod encodedMethod, InternalOptions options, ValueNumberGenerator generator)
+      DexEncodedMethod encodedMethod,
+      InternalOptions options,
+      ValueNumberGenerator generator,
+      Position callerPosition)
       throws ApiLevelException {
     try {
-      return internalBuild(encodedMethod, options, generator);
+      return internalBuild(encodedMethod, options, generator, callerPosition);
     } catch (InvalidDebugInfoException e) {
       options.warningInvalidDebugInfo(encodedMethod, origin, e);
       node.localVariables.clear();
-      return internalBuild(encodedMethod, options, generator);
+      return internalBuild(encodedMethod, options, generator, callerPosition);
     }
   }
 
   private IRCode internalBuild(
-      DexEncodedMethod encodedMethod, InternalOptions options, ValueNumberGenerator generator)
+      DexEncodedMethod encodedMethod,
+      InternalOptions options,
+      ValueNumberGenerator generator,
+      Position callerPosition)
       throws ApiLevelException {
     if (!options.debug) {
       node.localVariables.clear();
     }
-    JarSourceCode source = new JarSourceCode(clazz, node, application);
+    JarSourceCode source =
+        new JarSourceCode(clazz, node, application, encodedMethod.method, callerPosition);
     IRBuilder builder =
         (generator == null)
             ? new IRBuilder(encodedMethod, source, options)
diff --git a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
index 44ac9c8..7f7ec36 100644
--- a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
@@ -1,11 +1,15 @@
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ExecutionException;
 
 public class SmaliWriter extends DexByteCodeWriter {
 
@@ -15,12 +19,15 @@
   }
 
   /** Return smali source for the application code. */
-  public static String smali(DexApplication application, InternalOptions options) {
-    SmaliWriter writer = new SmaliWriter(application, options);
+  public static String smali(AndroidApp application, InternalOptions options) {
     ByteArrayOutputStream os = new ByteArrayOutputStream();
     try (PrintStream ps = new PrintStream(os)) {
+      DexApplication dexApplication = new ApplicationReader(application, options,
+          new Timing("SmaliWriter"))
+          .read();
+      SmaliWriter writer = new SmaliWriter(dexApplication, options);
       writer.write(ps);
-    } catch (IOException e) {
+    } catch (IOException | ExecutionException e) {
       throw new CompilationError("Failed to generate smali sting", e);
     }
     return new String(os.toByteArray(), StandardCharsets.UTF_8);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index 4da6eaf..a33c150 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -3,11 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.utils.InternalOptions;
@@ -80,11 +82,16 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
     // Arguments are defined by locals so nothing to load or store.
   }
 
   @Override
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    throw new Unreachable();
+  }
+
+  @Override
   public void buildCf(CfBuilder builder) {
     builder.addArgument(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 be0e6db..0fbac8f 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
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.code.CfArrayLoad;
 import com.android.tools.r8.code.Aget;
 import com.android.tools.r8.code.AgetBoolean;
@@ -16,7 +17,6 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -131,9 +131,9 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
-    stack.loadInValues(this, it);
-    stack.storeOutValue(this, it);
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    helper.loadInValues(this, it);
+    helper.storeOutValue(this, it);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index c78f9ad..d02863f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.code.Const;
@@ -14,8 +16,8 @@
 import com.android.tools.r8.code.ConstWide32;
 import com.android.tools.r8.code.ConstWideHigh16;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.NumberUtils;
 
@@ -134,8 +136,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
-    stack.storeOutValue(this, it);
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    helper.storeOutValue(this, it);
   }
 
   @Override
@@ -240,4 +242,10 @@
   public ConstNumber asConstNumber() {
     return this;
   }
+
+  @Override
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    assert outType().isObject();
+    return helper.getFactory().nullValueType;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 07c7c53..febbda3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -3,11 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.InternalOptions;
 
@@ -89,12 +91,17 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
-    stack.storeOutValue(this, it);
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    helper.storeOutValue(this, it);
   }
 
   @Override
   public void buildCf(CfBuilder builder) {
     builder.add(new CfConstString(value));
   }
+
+  @Override
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    return helper.getFactory().stringType;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
index 6833568..4355598 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.graph.DexType;
+
 /**
  * Instruction introducing an SSA value with attached local information.
  *
@@ -42,4 +45,14 @@
     assert other.isDebugLocalWrite();
     return true;
   }
+
+  @Override
+  public boolean hasInvariantVerificationType() {
+    return false;
+  }
+
+  @Override
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    return helper.getType(src());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index dbcf734..78ce128 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.utils.InternalOptions;
@@ -66,7 +66,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
     // Nothing to do for positions which are not actual instructions.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index 4b88706..95089a7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.ir.conversion.CfBuilder;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
 import java.util.List;
@@ -99,7 +99,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
     // Nothing to do.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index 8cdf209..c6e62af 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -6,10 +6,10 @@
 import static com.android.tools.r8.dex.Constants.U4BIT_MAX;
 import static com.android.tools.r8.dex.Constants.U8BIT_MAX;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.conversion.CfBuilder;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
 import java.util.List;
@@ -191,8 +191,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
-    stack.loadInValues(this, it);
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    helper.loadInValues(this, it);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index eeee6f8..ca68f64 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -3,13 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -965,7 +966,19 @@
   // Returns the inlining constraint for this instruction.
   public abstract Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder);
 
-  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
     throw new Unimplemented("Implement load/store insertion for: " + getInstructionName());
   }
+
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    throw new Unimplemented("Implement verification type computation for: " + getInstructionName());
+  }
+
+  public boolean hasInvariantVerificationType() {
+    if (inValues().isEmpty()) {
+      return true;
+    }
+    throw new Unimplemented(
+        "Implement has-invariant verification type for: " + getInstructionName());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index fef03c3..e7ca1b7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -3,13 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -84,15 +85,26 @@
   public abstract InlineAction computeInlining(InliningOracle decider);
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
-    stack.loadInValues(this, it);
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    helper.loadInValues(this, it);
     if (method.proto.returnType.isVoidType()) {
       return;
     }
     if (outValue == null) {
-      stack.popOutValue(ValueType.fromDexType(method.proto.returnType), this, it);
+      helper.popOutType(method.proto.returnType, this, it);
     } else {
-      stack.storeOutValue(this, it);
+      assert outValue.isUsed();
+      helper.storeOutValue(this, it);
     }
   }
+
+  @Override
+  public boolean hasInvariantVerificationType() {
+    return true;
+  }
+
+  @Override
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    return getInvokedMethod().proto.returnType;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Load.java b/src/main/java/com/android/tools/r8/ir/code/Load.java
index 401a880..74cf861 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Load.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -3,12 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 
 public class Load extends Instruction {
@@ -59,7 +60,12 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    return helper.getType(inValues.get(0));
+  }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
     // Nothing to do. This is only hit because loads and stores are insert for phis.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 2b419fd..23fdebf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -3,14 +3,18 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 public class MoveException extends Instruction {
 
@@ -73,11 +77,11 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
     if (outValue.isUsed()) {
-      stack.storeOutValue(this, it);
+      helper.storeOutValue(this, it);
     } else {
-      stack.popOutValue(outValue.type, this, it);
+      helper.popOutValue(outValue, this, it);
     }
   }
 
@@ -85,4 +89,24 @@
   public void buildCf(CfBuilder builder) {
     // Nothing to do. The exception is implicitly pushed on the stack.
   }
+
+  @Override
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    Set<DexType> exceptionTypes = new HashSet<>(getBlock().getPredecessors().size());
+    for (BasicBlock block : getBlock().getPredecessors()) {
+      int size = block.getCatchHandlers().size();
+      List<BasicBlock> targets = block.getCatchHandlers().getAllTargets();
+      List<DexType> guards = block.getCatchHandlers().getGuards();
+      for (int i = 0; i < size; i++) {
+        if (targets.get(i) == getBlock()) {
+          DexType guard = guards.get(i);
+          exceptionTypes.add(
+              guard == helper.getFactory().catchAllType
+                  ? helper.getFactory().throwableType
+                  : guard);
+        }
+      }
+    }
+    return helper.join(exceptionTypes);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index 1e77217..7c24034 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.code.NewArray;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -75,4 +76,14 @@
   public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
     return Constraint.classIsVisible(holder, type, info);
   }
+
+  @Override
+  public boolean hasInvariantVerificationType() {
+    return true;
+  }
+
+  @Override
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    return type;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 979b491..f3426a0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -3,12 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 
@@ -81,12 +82,17 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
-    stack.storeOutValue(this, it);
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    helper.storeOutValue(this, it);
   }
 
   @Override
   public void buildCf(CfBuilder builder) {
     builder.add(new CfNew(clazz));
   }
+
+  @Override
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    return clazz;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index faab702..2d9efb0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -3,8 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.BasicBlock.EdgeType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
@@ -371,4 +373,16 @@
   public boolean usesValueOneTime(Value usedValue) {
     return operands.indexOf(usedValue) == operands.lastIndexOf(usedValue);
   }
+
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    assert outType().isObject();
+    Set<DexType> operandTypes = new HashSet<>(operands.size());
+    for (Value operand : operands) {
+      DexType operandType = helper.getType(operand);
+      if (operandType != null) {
+        operandTypes.add(operandType);
+      }
+    }
+    return helper.join(operandTypes);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index 08298bb..fa6532f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -3,42 +3,75 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import java.util.Objects;
 
 public class Position {
 
-  private static final Position NO_POSITION = new Position(-1, null, false);
+  private static final Position NO_POSITION = new Position(-1, null, null, null, false);
 
   public final int line;
   public final DexString file;
   public final boolean synthetic;
 
-  public Position(int line, DexString file) {
-    this(line, file, false);
+  // If there's no inlining, callerPosition is null.
+  //
+  // For an inlined instruction its Position contains the inlinee's line and method and
+  // callerPosition is the position of the invoke instruction in the caller.
+
+  public final DexMethod method;
+  public final Position callerPosition;
+
+  public Position(int line, DexString file, DexMethod method, Position callerPosition) {
+    this(line, file, method, callerPosition, false);
     assert line >= 0;
   }
 
-  private Position(int line, DexString file, boolean synthetic) {
+  private Position(
+      int line, DexString file, DexMethod method, Position callerPosition, boolean synthetic) {
     this.line = line;
     this.file = file;
     this.synthetic = synthetic;
+    this.method = method;
+    this.callerPosition = callerPosition;
+    assert callerPosition == null || callerPosition.method != null;
+    assert line == -1 || method != null; // It's NO_POSITION or must have valid method.
   }
 
-  public static Position synthetic(int line) {
-    return new Position(line, null, true);
+  public static Position synthetic(int line, DexMethod method, Position callerPosition) {
+    assert line >= 0;
+    return new Position(line, null, method, callerPosition, true);
   }
 
   public static Position none() {
     return NO_POSITION;
   }
 
+  // This factory method is used by the Inliner to create Positions when the caller has no valid
+  // positions. Since the callee still may have valid positions we need a non-null Position to set
+  // it as the caller of the inlined Positions.
+  public static Position noneWithMethod(DexMethod method, Position callerPosition) {
+    assert method != null;
+    return new Position(-1, null, method, callerPosition, false);
+  }
+
   public boolean isNone() {
-    return this == NO_POSITION;
+    return line == -1;
   }
 
   public boolean isSome() {
-    return this != NO_POSITION;
+    return !isNone();
+  }
+
+  // Follow the linked list of callerPositions and return the last.
+  // Return this if no inliner.
+  public Position getOutermostCaller() {
+    Position lastPosition = this;
+    while (lastPosition.callerPosition != null) {
+      lastPosition = lastPosition.callerPosition;
+    }
+    return lastPosition;
   }
 
   @Override
@@ -48,7 +81,11 @@
     }
     if (other instanceof Position) {
       Position o = (Position) other;
-      return !isNone() && line == o.line && file == o.file;
+      return !isNone()
+          && line == o.line
+          && file == o.file
+          && method == o.method
+          && Objects.equals(callerPosition, o.callerPosition);
     }
     return false;
   }
@@ -58,11 +95,12 @@
     int result = line;
     result = 31 * result + Objects.hashCode(file);
     result = 31 * result + (synthetic ? 1 : 0);
+    result = 31 * result + Objects.hashCode(method);
+    result = 31 * result + Objects.hashCode(callerPosition);
     return result;
   }
 
-  @Override
-  public String toString() {
+  private String toString(boolean forceMethod) {
     if (isNone()) {
       return "--";
     }
@@ -70,7 +108,18 @@
     if (file != null) {
       builder.append(file).append(":");
     }
-    builder.append(line);
+    if (method != null && (forceMethod || callerPosition != null)) {
+      builder.append("[").append(method).append("]");
+    }
+    builder.append("#").append(line);
+    if (callerPosition != null) {
+      builder.append(" <- ").append(callerPosition.toString(true));
+    }
     return builder.toString();
   }
+
+  @Override
+  public String toString() {
+    return toString(false);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index a197c87..1177abb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.code.MoveType;
 import com.android.tools.r8.code.ReturnObject;
@@ -13,7 +14,6 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 
@@ -117,9 +117,9 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
     if (!isReturnVoid()) {
-      stack.loadInValues(this, it);
+      helper.loadInValues(this, it);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/StackValue.java b/src/main/java/com/android/tools/r8/ir/code/StackValue.java
index 3bf1417..65c4d3f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StackValue.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StackValue.java
@@ -3,16 +3,40 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+
 public class StackValue extends Value {
 
   private final int height;
+  private final DexType objectType;
 
-  public StackValue(ValueType type, int height) {
-    super(Value.UNDEFINED_NUMBER, type, null);
+  private StackValue(DexType objectType, ValueType valueType, int height) {
+    super(Value.UNDEFINED_NUMBER, valueType, null);
     this.height = height;
+    this.objectType = objectType;
     assert height >= 0;
   }
 
+  public static StackValue forObjectType(DexType type, int height) {
+    assert DexItemFactory.nullValueType == type || type.isClassType();
+    return new StackValue(type, ValueType.OBJECT, height);
+  }
+
+  public static StackValue forNonObjectType(ValueType valueType, int height) {
+    assert valueType.isPreciseType() && !valueType.isObject();
+    return new StackValue(null, valueType, height);
+  }
+
+  public int getHeight() {
+    return height;
+  }
+
+  public DexType getObjectType() {
+    assert outType().isObject();
+    return objectType;
+  }
+
   @Override
   public String toString() {
     return "s" + height;
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 23069f2..32871b3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfStaticGet;
 import com.android.tools.r8.code.Sget;
 import com.android.tools.r8.code.SgetBoolean;
@@ -18,7 +20,6 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 
 public class StaticGet extends FieldInstruction {
@@ -121,12 +122,17 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
-    stack.storeOutValue(this, it);
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    helper.storeOutValue(this, it);
   }
 
   @Override
   public void buildCf(CfBuilder builder) {
     builder.add(new CfStaticGet(field));
   }
+
+  @Override
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    return field.type;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index bc1e419..7bdf7ad 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -3,13 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.CfBuilder.FixedLocal;
-import com.android.tools.r8.ir.conversion.CfBuilder.StackHelper;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.utils.InternalOptions;
 
@@ -60,7 +61,12 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    return helper.getType(inValues.get(0));
+  }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
     // Nothing to do. This is only hit because loads and stores are insert for phis.
   }
 
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 f07b61e..d4cc49c 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
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfTryCatch;
@@ -11,31 +14,30 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
-import com.android.tools.r8.ir.code.ConstClass;
-import com.android.tools.r8.ir.code.ConstInstruction;
-import com.android.tools.r8.ir.code.ConstNumber;
-import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Load;
 import com.android.tools.r8.ir.code.Phi;
-import com.android.tools.r8.ir.code.Pop;
-import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.StackValue;
 import com.android.tools.r8.ir.code.Store;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
 import com.android.tools.r8.utils.InternalOptions;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -44,15 +46,16 @@
 
 public class CfBuilder {
 
+  private final DexItemFactory factory;
   private final DexEncodedMethod method;
   private final IRCode code;
 
   private int maxLocals = -1;
-  private int maxStack = 0;
-  private int currentStack = 0;
-  private List<CfInstruction> instructions;
+
+  private Map<Value, DexType> types;
   private Reference2IntMap<Value> registers;
   private Map<BasicBlock, CfLabel> labels;
+  private List<CfInstruction> instructions;
 
   /**
    * Value that represents a shared physical location defined by the phi value.
@@ -81,94 +84,44 @@
     }
   }
 
-  public static class StackHelper {
+  // Internal abstraction of the stack values and height.
+  private static class Stack {
+    int maxHeight = 0;
+    int height = 0;
 
-    private int currentStackHeight = 0;
-
-    public void loadInValues(Instruction instruction, InstructionListIterator it) {
-      int topOfStack = currentStackHeight;
-      it.previous();
-      for (Value value : instruction.inValues()) {
-        StackValue stackValue = new StackValue(value.outType(), topOfStack++);
-        add(load(stackValue, value), instruction, it);
-        value.removeUser(instruction);
-        instruction.replaceValue(value, stackValue);
-      }
-      it.next();
+    boolean isEmpty() {
+      return height == 0;
     }
 
-    public void storeOutValue(Instruction instruction, InstructionListIterator it) {
-      if (instruction.outValue() instanceof StackValue) {
-        assert instruction.isConstInstruction();
-        return;
-      }
-      StackValue newOutValue = new StackValue(instruction.outType(), currentStackHeight);
-      Value oldOutValue = instruction.swapOutValue(newOutValue);
-      add(new Store(oldOutValue, newOutValue), instruction, it);
+    void push(Value value) {
+      assert value instanceof StackValue;
+      height += value.requiredRegisters();
+      maxHeight = Math.max(maxHeight, height);
     }
 
-    public void popOutValue(ValueType type, Instruction instruction, InstructionListIterator it) {
-      StackValue newOutValue = new StackValue(type, currentStackHeight);
-      instruction.swapOutValue(newOutValue);
-      add(new Pop(newOutValue), instruction, it);
+    void pop(Value value) {
+      assert value instanceof StackValue;
+      height -= value.requiredRegisters();
     }
-
-    public void movePhi(Phi phi, Value value, InstructionListIterator it) {
-      StackValue tmp = new StackValue(phi.outType(), currentStackHeight);
-      FixedLocal out = new FixedLocal(phi);
-      add(load(tmp, value), phi.getBlock(), Position.none(), it);
-      add(new Store(out, tmp), phi.getBlock(), Position.none(), it);
-      value.removePhiUser(phi);
-      phi.replaceUsers(out);
-    }
-
-    private Instruction load(StackValue stackValue, Value value) {
-      if (value.isConstant()) {
-        ConstInstruction constant = value.getConstInstruction();
-        if (constant.isConstNumber()) {
-          return new ConstNumber(stackValue, constant.asConstNumber().getRawValue());
-        } else if (constant.isConstString()) {
-          return new ConstString(stackValue, constant.asConstString().getValue());
-        } else if (constant.isConstClass()) {
-          return new ConstClass(stackValue, constant.asConstClass().getValue());
-        } else {
-          throw new Unreachable("Unexpected constant value: " + value);
-        }
-      }
-      return new Load(stackValue, value);
-    }
-
-    private static void add(
-        Instruction newInstruction, Instruction existingInstruction, InstructionListIterator it) {
-      add(newInstruction, existingInstruction.getBlock(), existingInstruction.getPosition(), it);
-    }
-
-    private static void add(
-        Instruction newInstruction,
-        BasicBlock block,
-        Position position,
-        InstructionListIterator it) {
-      newInstruction.setBlock(block);
-      newInstruction.setPosition(position);
-      it.add(newInstruction);
-    }
-
   }
 
-  public CfBuilder(DexEncodedMethod method, IRCode code) {
+  public CfBuilder(DexEncodedMethod method, IRCode code, DexItemFactory factory) {
     this.method = method;
     this.code = code;
+    this.factory = factory;
   }
 
   public Code build(CodeRewriter rewriter, InternalOptions options) {
     try {
+      types = new TypeVerificationHelper(code, factory).computeVerificationTypes();
       splitExceptionalBlocks();
-      loadStoreInsertion();
+      new LoadStoreHelper(code, types).insertLoadsAndStores();
       DeadCodeRemover.removeDeadCode(code, rewriter, options);
       removeUnneededLoadsAndStores();
       allocateLocalRegisters();
       // TODO(zerny): Compute debug info.
-      return buildCfCode();
+      CfCode code = buildCfCode();
+      return code;
     } catch (Unimplemented e) {
       System.out.println("Incomplete CF construction: " + e.getMessage());
       return method.getCode().asJarCode();
@@ -207,32 +160,6 @@
     }
   }
 
-  private void loadStoreInsertion() {
-    StackHelper stack = new StackHelper();
-    // Insert phi stores in all predecessors.
-    for (BasicBlock block : code.blocks) {
-      if (!block.getPhis().isEmpty()) {
-        for (int predIndex = 0; predIndex < block.getPredecessors().size(); predIndex++) {
-          BasicBlock pred = block.getPredecessors().get(predIndex);
-          for (Phi phi : block.getPhis()) {
-            Value value = phi.getOperand(predIndex);
-            InstructionListIterator it = pred.listIterator(pred.getInstructions().size());
-            it.previous();
-            stack.movePhi(phi, value, it);
-          }
-        }
-      }
-    }
-    // Insert per-instruction loads and stores.
-    for (BasicBlock block : code.blocks) {
-      InstructionListIterator it = block.listIterator();
-      while (it.hasNext()) {
-        Instruction current = it.next();
-        current.insertLoadAndStores(it, stack);
-      }
-    }
-  }
-
   private void removeUnneededLoadsAndStores() {
     Iterator<BasicBlock> blockIterator = code.listIterator();
     while (blockIterator.hasNext()) {
@@ -258,6 +185,8 @@
             // Remove the load.
             it.next();
             it.remove();
+            // Rewind to the instruction before the store so we can identify new patterns.
+            it.previous();
           }
         }
       }
@@ -287,18 +216,8 @@
     maxLocals = nextFreeRegister;
   }
 
-  private void push(Value value) {
-    assert value instanceof StackValue;
-    currentStack += value.requiredRegisters();
-    maxStack = Math.max(maxStack, currentStack);
-  }
-
-  private void pop(Value value) {
-    assert value instanceof StackValue;
-    currentStack -= value.requiredRegisters();
-  }
-
   private CfCode buildCfCode() {
+    Stack stack = new Stack();
     List<CfTryCatch> tryCatchRanges = new ArrayList<>();
     labels = new HashMap<>(code.blocks.size());
     instructions = new ArrayList<>();
@@ -328,39 +247,73 @@
         tryCatchHandlers = handlers;
       }
       BasicBlock nextBlock = blockIterator.hasNext() ? blockIterator.next() : null;
-      buildCfInstructions(block, nextBlock);
+      boolean fallthrough = block.exit().isGoto() && block.exit().asGoto().getTarget() == nextBlock;
+      buildCfInstructions(block, fallthrough, stack);
+      if (nextBlock != null && (!fallthrough || nextBlock.getPredecessors().size() > 1)) {
+        assert stack.isEmpty();
+        instructions.add(getLabel(nextBlock));
+        if (nextBlock.getPredecessors().size() > 1) {
+          addFrame(Collections.emptyList(), types.keySet());
+        }
+      }
       block = nextBlock;
     } while (block != null);
-    assert currentStack == 0;
-    return new CfCode(maxStack, maxLocals, instructions, tryCatchRanges);
+    assert stack.isEmpty();
+    return new CfCode(stack.maxHeight, maxLocals, instructions, tryCatchRanges);
   }
 
-  private void buildCfInstructions(BasicBlock block, BasicBlock nextBlock) {
-    boolean fallthrough = false;
+  private void buildCfInstructions(BasicBlock block, boolean fallthrough, Stack stack) {
     InstructionIterator it = block.iterator();
     while (it.hasNext()) {
       Instruction instruction = it.next();
-      if (instruction.isGoto() && instruction.asGoto().getTarget() == nextBlock) {
-        fallthrough = true;
-        continue;
+      if (fallthrough && instruction.isGoto()) {
+        assert block.exit() == instruction;
+        return;
       }
-      for (Value inValue : instruction.inValues()) {
-        if (inValue instanceof StackValue) {
-          pop(inValue);
+      for (int i = instruction.inValues().size() - 1; i >= 0; i--) {
+        if (instruction.inValues().get(i) instanceof StackValue) {
+          stack.pop(instruction.inValues().get(i));
         }
       }
       if (instruction.outValue() != null) {
         Value outValue = instruction.outValue();
         if (outValue instanceof StackValue) {
-          push(outValue);
+          stack.push(outValue);
         }
       }
       instruction.buildCf(this);
     }
-    if (nextBlock == null || (fallthrough && nextBlock.getPredecessors().size() == 1)) {
-      return;
+  }
+
+  private void addFrame(Collection<StackValue> stack, Collection<Value> locals) {
+    // TODO(zerny): Support having values on the stack on control-edges.
+    assert stack.isEmpty();
+    Int2ReferenceSortedMap<DexType> mapping = new Int2ReferenceAVLTreeMap<>();
+    for (Value local : locals) {
+      DexType type;
+      switch (local.outType()) {
+        case INT:
+          type = factory.intType;
+          break;
+        case FLOAT:
+          type = factory.floatType;
+          break;
+        case LONG:
+          type = factory.longType;
+          break;
+        case DOUBLE:
+          type = factory.doubleType;
+          break;
+        case OBJECT:
+          type = types.get(local);
+          break;
+        default:
+          throw new Unreachable(
+              "Unexpected local type: " + local.outType() + " for local: " + local);
+      }
+      mapping.put(getLocalRegister(local), type);
     }
-    instructions.add(getLabel(nextBlock));
+    instructions.add(new CfFrame(mapping));
   }
 
   // Callbacks
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index e353a70..eb055f2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -37,6 +37,7 @@
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 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.graph.MethodAccessFlags;
@@ -73,17 +74,28 @@
   private final List<ValueType> argumentTypes;
 
   private List<DexDebugEntry> debugEntries = null;
+  private Position callerPosition; // In case of inlining the position of the invoke in the caller.
+  private final DexMethod method;
 
-  public DexSourceCode(DexCode code, DexEncodedMethod method) {
+  public DexSourceCode(DexCode code, DexEncodedMethod method, Position callerPosition) {
     this.code = code;
     this.proto = method.method.proto;
     this.accessFlags = method.accessFlags;
     argumentTypes = computeArgumentTypes();
     DexDebugInfo info = code.getDebugInfo();
     if (info != null) {
-      debugEntries = info.computeEntries();
+      debugEntries = info.computeEntries(method.method);
       canonicalPositions = new HashMap<>(debugEntries.size());
     }
+    if (info != null && callerPosition != null) {
+      // Canonicalize callerPosition
+      this.callerPosition = callerPosition;
+      canonicalPositions.put(callerPosition, callerPosition);
+    } else {
+      this.callerPosition = null;
+    }
+
+    this.method = method.method;
   }
 
   @Override
@@ -217,15 +229,44 @@
     if (current == null) {
       currentPosition = Position.none();
     } else {
-      currentPosition = getCanonicalPosition(current);
+      currentPosition = getCanonicalPositionAppendCaller(current);
       if (current.address == offset) {
         builder.addDebugPosition(currentPosition);
       }
     }
   }
 
-  private Position getCanonicalPosition(DexDebugEntry entry) {
-    return canonicalPositions.computeIfAbsent(new Position(entry.line, entry.sourceFile), p -> p);
+  private Position getCanonicalPosition(Position position) {
+    Position canonical = canonicalPositions.putIfAbsent(position, position);
+    return canonical != null ? canonical : position;
+  }
+
+  private Position canonicalizeCallerPosition(Position caller) {
+    if (caller == null) {
+      return callerPosition;
+    }
+    if (caller.callerPosition == null && callerPosition == null) {
+      return getCanonicalPosition(caller);
+    }
+    Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
+    return getCanonicalPosition(
+        caller.isNone()
+            ? Position.noneWithMethod(caller.method, callerOfCaller)
+            : new Position(caller.line, caller.file, caller.method, callerOfCaller));
+  }
+
+  private Position getCanonicalPositionAppendCaller(DexDebugEntry entry) {
+    // If this instruction has already been inlined then this.method must be the outermost caller.
+    assert (entry.callerPosition == null && entry.method == method)
+        || (entry.callerPosition != null
+            && entry.callerPosition.getOutermostCaller().method == method);
+
+    return getCanonicalPosition(
+        new Position(
+            entry.line,
+            entry.sourceFile,
+            entry.method,
+            canonicalizeCallerPosition(entry.callerPosition)));
   }
 
   @Override
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 00787ca..6728afe 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
@@ -39,6 +39,7 @@
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -417,7 +418,7 @@
     DexType result;
     int count = 0;
     do {
-      String name = options.outline.className + (count == 0 ? "" : Integer.toString(count));
+      String name = OutlineOptions.CLASS_NAME + (count == 0 ? "" : Integer.toString(count));
       count++;
       result = appInfo.dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(name));
     } while (appInfo.definitionFor(result) != null);
@@ -589,7 +590,7 @@
 
   private void finalizeToCf(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     assert method.getCode().isJarCode();
-    CfBuilder builder = new CfBuilder(method, code);
+    CfBuilder builder = new CfBuilder(method, code, options.itemFactory);
     // TODO(zerny): Change the return type of CfBuilder::build CfCode once complete.
     Code result = builder.build(codeRewriter, options);
     assert result.isCfCode() || result.isJarCode();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 04a2685..700d9da 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -181,12 +181,22 @@
   // Cooked position to indicate positions in synthesized code (ie, for synchronization).
   private Position syntheticPosition = null;
 
-  public JarSourceCode(DexType clazz, MethodNode node, JarApplicationReader application) {
+  private final DexMethod method;
+  private final Position callerPosition;
+
+  public JarSourceCode(
+      DexType clazz,
+      MethodNode node,
+      JarApplicationReader application,
+      DexMethod method,
+      Position callerPosition) {
     assert node != null;
     assert node.desc != null;
     this.node = node;
     this.application = application;
+    this.method = method;
     this.clazz = clazz;
+    this.callerPosition = callerPosition;
     parameterTypes = Arrays.asList(Type.getArgumentTypes(node.desc));
     state = new JarState(node.maxLocals, node.localVariables, this, application);
     AbstractInsnNode first = node.instructions.getFirst();
@@ -2840,7 +2850,8 @@
   }
 
   private Position getCanonicalPosition(int line) {
-    return canonicalPositions.computeIfAbsent(line, l -> new Position(l, null));
+    return canonicalPositions.computeIfAbsent(
+        line, l -> new Position(l, null, method, callerPosition));
   }
 
   // If we need to emit a synthetic position for exceptional monitor exits, we try to cook up a
@@ -2862,8 +2873,8 @@
       }
       syntheticPosition =
           (min == Integer.MAX_VALUE)
-              ? Position.none()
-              : Position.synthetic(min < max ? min - 1 : min);
+              ? Position.noneWithMethod(method, callerPosition)
+              : Position.synthetic(min < max ? min - 1 : min, method, callerPosition);
     }
     return syntheticPosition;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 3c7fe40..5386163 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1986,7 +1986,7 @@
     InstructionListIterator iterator = block.listIterator();
 
     // Attach some synthetic position to all inserted code.
-    Position position = Position.synthetic(1);
+    Position position = Position.synthetic(1, method.method, null);
     iterator.setInsertionPosition(position);
 
     // Split arguments into their own block.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index b15d254..cd51b25 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -21,6 +21,7 @@
 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.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.CallSiteInformation;
@@ -231,18 +232,23 @@
       return reason != Reason.SIMPLE;
     }
 
-    public IRCode buildIR(ValueNumberGenerator generator, AppInfoWithSubtyping appInfo,
-        GraphLense graphLense, InternalOptions options) throws ApiLevelException {
+    public IRCode buildIR(
+        ValueNumberGenerator generator,
+        AppInfoWithSubtyping appInfo,
+        GraphLense graphLense,
+        InternalOptions options,
+        Position callerPosition)
+        throws ApiLevelException {
       if (target.isProcessed()) {
         assert target.getCode().isDexCode();
-        return target.buildIR(options, generator);
+        return target.buildIR(options, generator, callerPosition);
       } else {
         // Build the IR for a yet not processed method, and perform minimal IR processing.
         IRCode code;
         if (target.getCode().isJarCode()) {
-          code = target.getCode().asJarCode().buildIR(target, options, generator);
+          code = target.getCode().asJarCode().buildIR(target, options, generator, callerPosition);
         } else {
-          code = target.getCode().asDexCode().buildIR(target, options, generator);
+          code = target.getCode().asDexCode().buildIR(target, options, generator, callerPosition);
         }
         new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
         return code;
@@ -359,8 +365,16 @@
           InlineAction result = invoke.computeInlining(oracle);
           if (result != null) {
             DexEncodedMethod target = result.target;
-            IRCode inlinee = result
-                .buildIR(code.valueNumberGenerator, appInfo, graphLense, options);
+            Position invokePosition = invoke.getPosition();
+            if (invokePosition.method == null) {
+              assert invokePosition.isNone();
+              invokePosition = Position.noneWithMethod(method.method, null);
+            }
+            assert invokePosition.getOutermostCaller().method == method.method;
+
+            IRCode inlinee =
+                result.buildIR(
+                    code.valueNumberGenerator, appInfo, graphLense, options, invokePosition);
             if (inlinee != null) {
               // TODO(64432527): Get rid of this additional check by improved inlining.
               if (block.hasCatchHandlers() && inlinee.computeNormalExitBlocks().isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 67e58cb..9709cf0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -48,6 +48,7 @@
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
 import com.google.common.collect.Sets;
@@ -1062,7 +1063,7 @@
       MethodAccessFlags methodAccess =
           MethodAccessFlags.fromSharedAccessFlags(
               Constants.ACC_PUBLIC | Constants.ACC_STATIC, false);
-      DexString methodName = dexItemFactory.createString(options.outline.methodPrefix + count);
+      DexString methodName = dexItemFactory.createString(OutlineOptions.METHOD_PREFIX + count);
       DexMethod method = outline.buildMethod(type, methodName);
       direct[count] = new DexEncodedMethod(method, methodAccess, DexAnnotationSet.empty(),
           DexAnnotationSetRefList.empty(), new OutlineCode(outline));
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index c494c08..36ea1b3 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -17,6 +17,7 @@
 import java.io.Writer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 import org.objectweb.asm.Type;
@@ -291,6 +292,12 @@
       this.parameters = parameters;
     }
 
+    public MethodSignature(String name, String type, Collection<String> parameters) {
+      super(name);
+      this.type = type;
+      this.parameters = parameters.toArray(new String[parameters.size()]);
+    }
+
     public static MethodSignature fromDexMethod(DexMethod method) {
       String[] paramNames = new String[method.getArity()];
       DexType[] values = method.proto.parameters.values;
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 67aa288..ddac51f 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -608,7 +608,7 @@
      * Set dead-code data.
      */
     public Builder setDeadCode(byte[] content) {
-      deadCode = content == null ? null : Resource.fromBytes(null, content);
+      deadCode = content == null ? null : Resource.fromBytes(Origin.unknown(), content);
       return this;
     }
 
@@ -631,7 +631,7 @@
      * Set proguard-map data.
      */
     public Builder setProguardMapData(byte[] content) {
-      proguardMap = content == null ? null : Resource.fromBytes(null, content);
+      proguardMap = content == null ? null : Resource.fromBytes(Origin.unknown(), content);
       return this;
     }
 
@@ -639,7 +639,7 @@
      * Set proguard-seeds data.
      */
     public Builder setProguardSeedsData(byte[] content) {
-      proguardSeeds = content == null ? null : Resource.fromBytes(null, content);
+      proguardSeeds = content == null ? null : Resource.fromBytes(Origin.unknown(), content);
       return this;
     }
 
@@ -685,7 +685,7 @@
      * Set the main-dex list output data.
      */
     public Builder setMainDexListOutputData(byte[] content) {
-      mainDexListOutput = content == null ? null : Resource.fromBytes(null, content);
+      mainDexListOutput = content == null ? null : Resource.fromBytes(Origin.unknown(), content);
       return this;
     }
 
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 d7006af..62dac5f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -267,9 +267,10 @@
 
   public static class OutlineOptions {
 
+    public static final String CLASS_NAME = "r8.GeneratedOutlineSupport";
+    public static final String METHOD_PREFIX = "outline";
+
     public boolean enabled = true;
-    public static final String className = "r8.GeneratedOutlineSupport";
-    public String methodPrefix = "outline";
     public int minSize = 3;
     public int maxSize = 99;
     public int threshold = 20;
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java
new file mode 100644
index 0000000..859876d
--- /dev/null
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java
@@ -0,0 +1,40 @@
+// 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.apiusagesample;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.Resource;
+import com.android.tools.r8.utils.DirectoryClassFileProvider;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class CachingArchiveClassFileProvider extends ArchiveClassFileProvider {
+
+  private ConcurrentHashMap<String, Resource> resources = new ConcurrentHashMap<>();
+
+  private CachingArchiveClassFileProvider(Path archive) throws IOException {
+    super(archive);
+  }
+
+  @Override
+  public Resource getResource(String descriptor) {
+    return resources.computeIfAbsent(descriptor, desc -> super.getResource(desc));
+  }
+
+  public static ClassFileResourceProvider getProvider(Path entry)
+      throws IOException {
+    if (Files.isRegularFile(entry)) {
+      return new CachingArchiveClassFileProvider(entry);
+    } else if (Files.isDirectory(entry)) {
+      return DirectoryClassFileProvider.fromDirectory(entry);
+    } else {
+      throw new FileNotFoundException(entry.toString());
+    }
+  }
+}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8Compiler.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8Compiler.java
new file mode 100644
index 0000000..ff61567
--- /dev/null
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8Compiler.java
@@ -0,0 +1,136 @@
+// 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.apiusagesample;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.utils.OutputMode;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class D8Compiler {
+  private int minSdkVersion;
+  private Path bootclasspath;
+  private List<Path> classpath;
+  private static ExecutorService pool = Executors.newFixedThreadPool(4);
+
+  private D8Compiler(int minSdkVersion, Path bootclasspath, List<Path> classpath) {
+    this.minSdkVersion = minSdkVersion;
+    this.bootclasspath = bootclasspath;
+    this.classpath = classpath;
+  }
+
+  /**
+   * java ...Compiler output input minSdkVersion mainDexClasses bootclasspath [classpathEntries]+
+   */
+  public static void main(String[] args) throws Throwable {
+    try {
+      int argIndex = 0;
+      Path outputDir = Paths.get(args[argIndex++]);
+      Path input = Paths.get(args[argIndex++]);
+      int minSdkVersion = Integer.parseInt(args[argIndex++]);
+      Path mainDexClasses = Paths.get(args[argIndex++]);
+      Path bootclasspath = Paths.get(args[argIndex++]);
+
+      List<Path> classpath = new ArrayList<>(args.length - argIndex);
+      while (argIndex < args.length) {
+        classpath.add(Paths.get(args[argIndex++]));
+      }
+
+      D8Compiler compiler = new D8Compiler(minSdkVersion, bootclasspath, classpath);
+
+      List<Path> toMerge = new ArrayList<>(3);
+
+      int intermediateIndex = 0;
+      for (Path entry : classpath) {
+        Path output = outputDir.resolve(entry.getFileName() + "." + (intermediateIndex++));
+        Files.createDirectory(output);
+        toMerge.add(output);
+        compiler.compile(output, entry);
+      }
+
+      Path output = outputDir.resolve("main." + (intermediateIndex++));
+      Files.createDirectory(output);
+      toMerge.add(output);
+      compiler.compile(output, input);
+
+      compiler.merge(outputDir, mainDexClasses, toMerge);
+    } finally {
+      // Terminate pool threads to prevent the VM to wait on then before exiting.
+      pool.shutdown();
+    }
+  }
+
+  private void compile(Path output, Path input) throws Throwable {
+    D8Command.Builder builder =
+        D8Command.builder()
+            // Compile in debug and merge in release to assert access to both modes
+            .setMode(CompilationMode.DEBUG)
+            .setMinApiLevel(minSdkVersion)
+            .setIntermediate(true)
+            .setEnableDesugaring(true)
+            .setOutputPath(output);
+
+    builder.addLibraryResourceProvider(CachingArchiveClassFileProvider.getProvider(bootclasspath));
+
+    for (Path entry : classpath) {
+      builder.addClasspathResourceProvider(CachingArchiveClassFileProvider.getProvider(entry));
+    }
+
+    if (Files.isRegularFile(input)) {
+      builder.setOutputMode(OutputMode.Indexed);
+      builder.addProgramFiles(input);
+    } else {
+      builder.setOutputMode(OutputMode.FilePerInputClass);
+      Files.walkFileTree(input, new SimpleFileVisitor<Path>() {
+        @Override
+        public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes)
+            throws IOException {
+          builder.addClassProgramData(Files.readAllBytes(path));
+          return FileVisitResult.CONTINUE;
+        }
+      });
+    }
+
+    D8.run(builder.build(), pool);
+  }
+
+  private void merge(Path outputDir, Path mainDexClasses,
+      List<Path> toMerge) throws IOException, CompilationException {
+    D8Command.Builder merger = D8Command.builder();
+    merger.setEnableDesugaring(false);
+
+    for (Path mergeInput : toMerge) {
+      Files.walkFileTree(mergeInput, new SimpleFileVisitor<Path>() {
+        @Override
+        public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes)
+            throws IOException {
+          merger.addDexProgramData(Files.readAllBytes(path));
+          return FileVisitResult.CONTINUE;
+        }
+      });
+    }
+    if (mainDexClasses != null) {
+      merger.addMainDexListFiles(mainDexClasses);
+    }
+    merger.setMinApiLevel(minSdkVersion)
+        .setMode(CompilationMode.RELEASE)
+        .setOutputPath(outputDir)
+        .setEnableDesugaring(false)
+        .setIntermediate(false);
+    D8.run(merger.build());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/D8APiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8APiBinaryCompatibilityTests.java
new file mode 100644
index 0000000..5a3b0f8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8APiBinaryCompatibilityTests.java
@@ -0,0 +1,64 @@
+// 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;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class D8APiBinaryCompatibilityTests {
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Test
+  public void testCompatibility() throws IOException {
+    Path compilerJar = Paths.get("tests", "api_usage_sample.jar");
+    String compiler = "com.android.tools.apiusagesample.D8Compiler";
+
+    String output = temp.newFolder().getAbsolutePath();
+    int minSdkVersion = AndroidApiLevel.K.getLevel();
+    String androidJar = ToolHelper.getAndroidJar(minSdkVersion);
+    Path lib1 = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
+        "desugaringwithmissingclasslib1" + JAR_EXTENSION);
+    Path lib2 = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
+        "desugaringwithmissingclasslib2" + JAR_EXTENSION);
+    Path input = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
+        "classes", "desugaringwithmissingclasstest1");
+    File mainDexClasses = temp.newFile();
+    Files.asCharSink(mainDexClasses, StandardCharsets.UTF_8)
+        .write("desugaringwithmissingclasstest1/Main.class");
+
+    List<String> command = ImmutableList.of(
+        ToolHelper.getJavaExecutable(),
+        "-cp",
+        compilerJar.toString() + File.pathSeparator + System.getProperty("java.class.path"),
+        compiler,
+        // Compiler arguments.
+        output,
+        input.toString(),
+        Integer.toString(minSdkVersion),
+        mainDexClasses.getAbsolutePath(),
+        androidJar,
+        lib1.toString(),
+        lib2.toString());
+    ProcessBuilder builder = new ProcessBuilder(command);
+    ProcessResult result = ToolHelper.runProcess(builder);
+
+    Assert.assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 2128663..60bcc5b 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -5,18 +5,20 @@
 package com.android.tools.r8;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.SmaliWriter;
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OutputMode;
-import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.io.ByteStreams;
@@ -368,8 +370,34 @@
    */
   protected void disassemble(AndroidApp app) throws Exception {
     InternalOptions options = new InternalOptions();
-    DexApplication dexApplication = new ApplicationReader(app, options, new Timing("XX")).read();
-    System.out.println(SmaliWriter.smali(dexApplication, options));
+    System.out.println(SmaliWriter.smali(app, options));
+  }
+
+  protected DexEncodedMethod getMethod(
+      DexInspector inspector,
+      String className,
+      String returnType,
+      String methodName,
+      List<String> parameters) {
+    ClassSubject clazz = inspector.clazz(className);
+    assertTrue(clazz.isPresent());
+    MethodSubject method = clazz.method(returnType, methodName, parameters);
+    assertTrue(method.isPresent());
+    return method.getMethod();
+  }
+
+  protected DexEncodedMethod getMethod(
+      AndroidApp application,
+      String className,
+      String returnType,
+      String methodName,
+      List<String> parameters) {
+    try {
+      DexInspector inspector = new DexInspector(application);
+      return getMethod(inspector, className, returnType, methodName, parameters);
+    } catch (Exception e) {
+      return null;
+    }
   }
 
   public enum MinifyMode {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 2c4bc98..34230de 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.DeviceRunner.DeviceRunnerConfigurationException;
 import com.android.tools.r8.ToolHelper.DexVm.Kind;
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.shaking.FilteredClassPath;
@@ -697,14 +696,6 @@
     return compatSink.build();
   }
 
-  public static DexApplication optimizeWithR8(
-      DexApplication application,
-      InternalOptions options)
-      throws CompilationException, ExecutionException, IOException {
-    application = application.toDirect();
-    return R8.optimize(application, new AppInfoWithSubtyping(application), options);
-  }
-
   public static AndroidApp runD8(AndroidApp app) throws CompilationException, IOException {
     return runD8(app, null);
   }
diff --git a/src/test/java/com/android/tools/r8/bisect/BisectTest.java b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
index ba49e64..fc6e4c1 100644
--- a/src/test/java/com/android/tools/r8/bisect/BisectTest.java
+++ b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
@@ -10,8 +10,8 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.smali.SmaliTestBase.MethodSignature;
-import com.android.tools.r8.smali.SmaliTestBase.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.InternalOptions;
diff --git a/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java b/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java
index 66652bd..7a911be 100644
--- a/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java
@@ -12,15 +12,13 @@
 import com.android.tools.r8.debuginfo.DebugInfoInspector;
 import com.android.tools.r8.graph.DexDebugEntry;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.smali.SmaliTestBase.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.utils.AndroidApp;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collections;
 import java.util.List;
 import org.apache.harmony.jpda.tests.framework.jdwp.Value;
-import org.junit.Assume;
-import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index b344c8c..8d93b31 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -10,9 +10,10 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliTestBase;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
@@ -62,10 +63,11 @@
         "    goto :return"
     );
 
-    DexApplication application = buildApplication(builder);
-    AppInfo appInfo = new AppInfo(application);
-    DexEncodedMethod method =
-        getMethod(application, DEFAULT_CLASS_NAME, "int", "x", ImmutableList.of());
+    AndroidApp application = buildApplication(builder);
+    AppInfo appInfo = getAppInfo(application);
+    DexInspector inspector = new DexInspector(appInfo.app);
+    DexEncodedMethod method = getMethod(inspector, DEFAULT_CLASS_NAME, "int", "x",
+        ImmutableList.of());
     assertNull(appInfo.lookupVirtualTarget(method.method.holder, method.method));
     assertNull(appInfo.lookupDirectTarget(method.method));
     assertNotNull(appInfo.lookupStaticTarget(method.method));
@@ -74,12 +76,12 @@
       // Dalvik rejects at verification time instead of producing the
       // expected IncompatibleClassChangeError.
       try {
-        runArt(application, new InternalOptions());
+        runArt(application);
       } catch (AssertionError e) {
         assert e.toString().contains("VerifyError");
       }
     } else {
-      assertEquals("OK", runArt(application, new InternalOptions()));
+      assertEquals("OK", runArt(application));
     }
   }
 
@@ -131,20 +133,21 @@
         "    goto :return"
     );
 
-    DexApplication application = buildApplication(builder);
-    AppInfo appInfo = new AppInfo(application);
+    AndroidApp application = buildApplication(builder);
+    AppInfo appInfo = getAppInfo(application);
+    DexInspector inspector = new DexInspector(appInfo.app);
 
     DexMethod methodXOnTestSuper =
-        getMethod(application, "TestSuper", "int", "x", ImmutableList.of()).method;
+        getMethod(inspector, "TestSuper", "int", "x", ImmutableList.of()).method;
     DexMethod methodYOnTest =
-        getMethod(application, "Test", "int", "y", ImmutableList.of()).method;
+        getMethod(inspector, "Test", "int", "y", ImmutableList.of()).method;
 
     DexType classTestSuper = methodXOnTestSuper.getHolder();
     DexType classTest = methodYOnTest.getHolder();
     DexProto methodXProto = methodXOnTestSuper.proto;
     DexString methodXName = methodXOnTestSuper.name;
     DexMethod methodXOnTest =
-        application.dexItemFactory.createMethod(classTest, methodXProto, methodXName);
+        appInfo.dexItemFactory.createMethod(classTest, methodXProto, methodXName);
 
     assertNull(appInfo.lookupVirtualTarget(classTestSuper, methodXOnTestSuper));
     assertNull(appInfo.lookupVirtualTarget(classTest, methodXOnTestSuper));
@@ -156,7 +159,7 @@
     assertNotNull(appInfo.lookupStaticTarget(methodXOnTestSuper));
     assertNotNull(appInfo.lookupStaticTarget(methodXOnTest));
 
-    assertEquals("OK", runArt(application, new InternalOptions()));
+    assertEquals("OK", runArt(application));
   }
 }
 
diff --git a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
index ad9a26a..2d73d1f 100644
--- a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
@@ -4,13 +4,15 @@
 
 package com.android.tools.r8.ir;
 
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -45,8 +47,7 @@
         "    return              p0"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication application = buildApplication(builder, options);
+    AndroidApp application = buildApplication(builder);
 
     // Build the code, and split the code into three blocks.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index 27a3ce9..b190933 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -13,7 +13,8 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
@@ -22,7 +23,7 @@
 import java.util.ListIterator;
 import org.junit.Test;
 
-public class InlineTest extends SmaliTestBase {
+public class InlineTest extends IrInjectionTestBase {
 
   TestApplication codeForMethodReplaceTest(int a, int b) throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
@@ -84,7 +85,7 @@
     DexEncodedMethod methodB = getMethod(application, signatureB);
     IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator);
 
-    return new SmaliTestBase.TestApplication(application, method, code,
+    return new TestApplication(application, method, code,
         ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
index a688ffe..5d3b0fb 100644
--- a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
@@ -4,14 +4,16 @@
 
 package com.android.tools.r8.ir;
 
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -46,8 +48,7 @@
         "    return              p0"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication application = buildApplication(builder, options);
+    AndroidApp application = buildApplication(builder);
 
     // Build the code, and split the code into three blocks.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
new file mode 100644
index 0000000..9f3e0ac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -0,0 +1,146 @@
+// 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.ir;
+
+import com.android.tools.r8.R8;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.errors.DexOverflowException;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AndroidAppOutputSink;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import org.antlr.runtime.RecognitionException;
+
+public class IrInjectionTestBase extends SmaliTestBase {
+
+  protected DexApplication buildApplication(SmaliBuilder builder, InternalOptions options) {
+    try {
+      return buildApplication(AndroidApp.fromDexProgramData(builder.compile()), options);
+    } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  protected DexApplication buildApplication(AndroidApp input, InternalOptions options) {
+    try {
+      options.itemFactory.resetSortedIndices();
+      return new ApplicationReader(input, options, new Timing("IrInjectionTest")).read();
+    } catch (IOException | ExecutionException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  protected DexEncodedMethod getMethod(DexApplication application, MethodSignature signature) {
+    return getMethod(application,
+        signature.clazz, signature.returnType, signature.name, signature.parameterTypes);
+  }
+
+  protected DexEncodedMethod getMethod(
+      DexApplication application,
+      String className,
+      String returnType,
+      String methodName,
+      List<String> parameters) {
+    DexInspector inspector = new DexInspector(application);
+    return getMethod(inspector, className, returnType, methodName, parameters);
+  }
+
+  public class TestApplication {
+
+    public final DexApplication application;
+    public final DexEncodedMethod method;
+    public final IRCode code;
+    public final List<IRCode> additionalCode;
+    public final ValueNumberGenerator valueNumberGenerator;
+    public final InternalOptions options;
+
+    public TestApplication(
+        DexApplication application,
+        DexEncodedMethod method,
+        IRCode code,
+        ValueNumberGenerator valueNumberGenerator,
+        InternalOptions options) {
+      this(application, method, code, null, valueNumberGenerator, options);
+    }
+
+    public TestApplication(
+        DexApplication application,
+        DexEncodedMethod method,
+        IRCode code,
+        List<IRCode> additionalCode,
+        ValueNumberGenerator valueNumberGenerator,
+        InternalOptions options) {
+      this.application = application;
+      this.method = method;
+      this.code = code;
+      this.additionalCode = additionalCode;
+      this.valueNumberGenerator = valueNumberGenerator;
+      this.options = options;
+    }
+
+    public int countArgumentInstructions() {
+      int count = 0;
+      ListIterator<Instruction> iterator = code.blocks.get(0).listIterator();
+      while (iterator.next().isArgument()) {
+        count++;
+      }
+      return count;
+    }
+
+    public InstructionListIterator listIteratorAt(BasicBlock block, int index) {
+      InstructionListIterator iterator = block.listIterator();
+      for (int i = 0; i < index; i++) {
+        iterator.next();
+      }
+      return iterator;
+    }
+
+    private AndroidApp writeDex(DexApplication application, InternalOptions options)
+        throws DexOverflowException {
+      try {
+        AndroidAppOutputSink compatSink = new AndroidAppOutputSink();
+        R8.writeApplication(
+            Executors.newSingleThreadExecutor(),
+            application,
+            compatSink,
+            null,
+            NamingLens.getIdentityLens(),
+            null,
+            options);
+        compatSink.close();
+        return compatSink.build();
+      } catch (ExecutionException | IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    public String run() throws DexOverflowException, IOException {
+      AppInfo appInfo = new AppInfo(application);
+      IRConverter converter = new IRConverter(appInfo, options);
+      converter.replaceCodeForTesting(method, code);
+      AndroidApp app = writeDex(application, options);
+      return runOnArtRaw(app, DEFAULT_MAIN_CLASS_NAME).stdout;
+    }
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index 5165432..25a237d 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -21,14 +21,15 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 
-public class SplitBlockTest extends SmaliTestBase {
+public class SplitBlockTest extends IrInjectionTestBase {
 
   TestApplication codeWithoutCatchHandlers() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
index 1d17e31..74a91a8 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -3,13 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.regalloc;
 
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.util.PriorityQueue;
@@ -43,7 +45,7 @@
         ImmutableList.of(),
         1,
         "    return-void");
-    DexApplication application = buildApplication(builder, options);
+    AndroidApp application = buildApplication(builder);
     // Build the code, and split the code into three blocks.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
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 7421046..5579380 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -4,18 +4,15 @@
 package com.android.tools.r8.jasmin;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.Resource;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OutputMode;
 import com.android.tools.r8.utils.StringUtils;
@@ -33,13 +30,8 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.function.Consumer;
-import org.junit.Rule;
-import org.junit.rules.TemporaryFolder;
 
-public class JasminTestBase {
-
-  @Rule
-  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+public class JasminTestBase extends TestBase {
 
   public static String getPathFromDescriptor(String classDescriptor) {
     assert classDescriptor.startsWith("L");
@@ -95,6 +87,10 @@
     return runOnArt(compileWithD8(builder), main);
   }
 
+  protected AndroidApp compileWithR8(JasminBuilder builder) throws Exception {
+    return compileWithR8(builder, null);
+  }
+
   protected AndroidApp compileWithR8(JasminBuilder builder,
       Consumer<InternalOptions> optionsConsumer)
       throws Exception {
@@ -169,20 +165,6 @@
   protected DexEncodedMethod getMethod(AndroidApp application, String clazz,
       MethodSignature signature) {
     return getMethod(application,
-        clazz, signature.type, signature.name, signature.parameters);
-  }
-
-  protected DexEncodedMethod getMethod(AndroidApp application, String className,
-      String returnType, String methodName, String[] parameters) {
-    try {
-      DexInspector inspector = new DexInspector(application);
-      ClassSubject clazz = inspector.clazz(className);
-      assertTrue(clazz.isPresent());
-      MethodSubject method = clazz.method(returnType, methodName, Arrays.asList(parameters));
-      assertTrue(method.isPresent());
-      return method.getMethod();
-    } catch (Exception e) {
-      return null;
-    }
+        clazz, signature.type, signature.name, Arrays.asList(signature.parameters));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java
index acb0a51..de654b8 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java
@@ -17,6 +17,8 @@
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringUtils;
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
index 4d1d619..51772fe 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.Sput;
 import com.android.tools.r8.code.SputObject;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueBoolean;
@@ -23,11 +22,11 @@
 import com.android.tools.r8.graph.DexValue.DexValueLong;
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
 
@@ -92,9 +91,8 @@
         "return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     // Test is running without tree-shaking, so the empty <clinit> is not removed.
@@ -147,7 +145,7 @@
     assertTrue(value instanceof DexValueString);
     assertEquals(("8"), ((DexValueString) value).getValue().toString());
 
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
 
     assertEquals(StringUtils.lines("true", "1", "2", "3", "4", "5.0", "6.0", "7", "8"), result);
   }
@@ -177,16 +175,15 @@
         "return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     MethodSubject clinit = inspector.clazz("Test").clinit();
     // Nothing changed in the class initializer.
     assertEquals(5, clinit.getMethod().getCode().asDexCode().instructions.length);
 
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
 
     assertEquals(StringUtils.lines("0", "1"), result);
   }
@@ -219,16 +216,15 @@
         "return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     // Test is running without tree-shaking, so the empty <clinit> is not removed.
     assertTrue(
         inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
 
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
 
     assertEquals(StringUtils.lines("null", "null", "null"), result);
   }
@@ -262,16 +258,15 @@
         "return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     // Test is running without tree-shaking, so the empty <clinit> is not removed.
     assertTrue(
         inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
 
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
 
     assertEquals(StringUtils.lines("Value1", "Value2", "Value2"), result);
   }
@@ -313,9 +308,8 @@
         "return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     // Test is running without tree-shaking, so the empty <clinit> is not removed.
@@ -333,7 +327,7 @@
     assertTrue(value instanceof DexValueString);
     assertEquals(("7"), ((DexValueString) value).getValue().toString());
 
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
 
     assertEquals(StringUtils.lines("3", "7") , result);
   }
@@ -387,9 +381,8 @@
         "return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     assertTrue(inspector.clazz("Test").clinit().isPresent());
@@ -417,7 +410,7 @@
       }
     }
 
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
 
     assertEquals(StringUtils.lines("3", "7"), result);
   }
@@ -475,9 +468,8 @@
         "return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     assertTrue(inspector.clazz(className).isPresent());
@@ -485,7 +477,7 @@
     assertTrue(
         inspector.clazz(className).clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
 
-    String result = runArt(processedApplication, options, className);
+    String result = runArt(processedApplication, className);
 
     assertEquals(StringUtils.lines("Test", className, "Test", className, "Test", className),
         result);
@@ -525,15 +517,14 @@
 
     builder.addClass("org.example.Test2");
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     assertTrue(inspector.clazz(className).isPresent());
     assertTrue(inspector.clazz(className).clinit().isPresent());
 
-    String result = runArt(processedApplication, options, className);
+    String result = runArt(processedApplication, className);
 
     assertEquals(StringUtils.lines("Test2", "org.example.Test2"), result);
   }
@@ -559,16 +550,15 @@
     builder.addClass("Other");
     builder.addStaticField("field", "I", "1");
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     MethodSubject clinit = inspector.clazz("Test").clinit();
     // Nothing changed in the class initializer.
     assertEquals(3, clinit.getMethod().getCode().asDexCode().instructions.length);
 
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
 
     assertEquals(StringUtils.lines("2"), result);
   }
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
index d1a2514..76c24f9 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.shaking.FilteredClassPath;
-import com.android.tools.r8.smali.SmaliTestBase.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.PreloadedClassFileProvider;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java b/src/test/java/com/android/tools/r8/rewrite/switches/CheckSwitchInTestClass.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java
rename to src/test/java/com/android/tools/r8/rewrite/switches/CheckSwitchInTestClass.java
index f47025a..93b7c19 100644
--- a/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/CheckSwitchInTestClass.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.smali;
+package com.android.tools.r8.rewrite.switches;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
new file mode 100644
index 0000000..66cf986
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
@@ -0,0 +1,344 @@
+// 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.rewrite.switches;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.code.IfEq;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.PackedSwitch;
+import com.android.tools.r8.code.SparseSwitch;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.FilteredClassPath;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+
+public class SwitchRewritingJarTest extends JasminTestBase {
+
+  private void runSingleCaseJarTest(boolean packed, int key) throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    String switchCode;
+    if (packed) {
+      switchCode = StringUtils.join(
+          "\n",
+          "    tableswitch " + key,
+          "      case_0",
+          "      default : case_default");
+    } else {
+      switchCode = StringUtils.join(
+          "\n",
+          "    lookupswitch",
+          "      " + key + " : case_0",
+          "      default : case_default");
+    }
+
+    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+        "    .limit stack 1",
+        "    .limit locals 1",
+        "    iload 0",
+        switchCode,
+        "  case_0:",
+        "    iconst_3",
+        "    goto return_",
+        "  case_default:",
+        "    ldc 5",
+        "  return_:",
+        "    ireturn");
+
+    clazz.addMainMethod(
+        "    .limit stack 2",
+        "    .limit locals 1",
+        "    getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "    ldc 2",
+        "    invokestatic Test/test(I)I",
+        "    invokevirtual java/io/PrintStream/print(I)V",
+        "    return");
+
+    AndroidApp app = builder.build();
+    app = ToolHelper.runR8(app);
+
+    MethodSignature signature = new MethodSignature("test", "int", ImmutableList.of("int"));
+    DexEncodedMethod method = getMethod(app, "Test", signature);
+    DexCode code = method.getCode().asDexCode();
+    if (key == 0) {
+      assertEquals(5, code.instructions.length);
+      assertTrue(code.instructions[0] instanceof IfEqz);
+    } else {
+      assertEquals(6, code.instructions.length);
+      assertTrue(code.instructions[1] instanceof IfEq);
+    }
+  }
+
+  @Test
+  public void singleCaseJar() throws Exception {
+    for (boolean packed : new boolean[]{true, false}) {
+      runSingleCaseJarTest(packed, Integer.MIN_VALUE);
+      runSingleCaseJarTest(packed, -1);
+      runSingleCaseJarTest(packed, 0);
+      runSingleCaseJarTest(packed, 1);
+      runSingleCaseJarTest(packed, Integer.MAX_VALUE);
+    }
+  }
+
+  private void runTwoCaseSparseToPackedJarTest(int key1, int key2) throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+        "    .limit stack 1",
+        "    .limit locals 1",
+        "    iload 0",
+        "    lookupswitch",
+        "      " + key1 + " : case_1",
+        "      " + key2 + " : case_2",
+        "      default : case_default",
+        "  case_1:",
+        "    iconst_3",
+        "    goto return_",
+        "  case_2:",
+        "    iconst_4",
+        "    goto return_",
+        "  case_default:",
+        "    iconst_5",
+        "  return_:",
+        "    ireturn");
+
+    clazz.addMainMethod(
+        "    .limit stack 2",
+        "    .limit locals 1",
+        "    getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "    ldc 2",
+        "    invokestatic Test/test(I)I",
+        "    invokevirtual java/io/PrintStream/print(I)V",
+        "    return");
+
+    AndroidApp app = compileWithR8(builder);
+
+    MethodSignature signature = new MethodSignature("test", "int", ImmutableList.of("int"));
+    DexEncodedMethod method = getMethod(app, "Test", signature);
+    DexCode code = method.getCode().asDexCode();
+    if (SwitchRewritingTest.twoCaseWillUsePackedSwitch(key1, key2)) {
+      assertTrue(code.instructions[0] instanceof PackedSwitch);
+    } else {
+      if (key1 == 0) {
+        assertTrue(code.instructions[0] instanceof IfEqz);
+      } else {
+        // Const instruction before if.
+        assertTrue(code.instructions[1] instanceof IfEq);
+      }
+    }
+  }
+
+  @Test
+  public void twoCaseSparseToPackedJar() throws Exception {
+    for (int delta = 1; delta <= 3; delta++) {
+      runTwoCaseSparseToPackedJarTest(0, delta);
+      runTwoCaseSparseToPackedJarTest(-delta, 0);
+      runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
+      runTwoCaseSparseToPackedJarTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
+    }
+    runTwoCaseSparseToPackedJarTest(-1, 1);
+    runTwoCaseSparseToPackedJarTest(-2, 1);
+    runTwoCaseSparseToPackedJarTest(-1, 2);
+    runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
+    runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
+    runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
+  }
+
+  private void runLargerSwitchJarTest(int firstKey, int keyStep, int totalKeys,
+      Integer additionalLastKey) throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    StringBuilder switchSource = new StringBuilder();
+    StringBuilder targetCode = new StringBuilder();
+    for (int i = 0; i < totalKeys; i++) {
+      String caseLabel = "case_" + i;
+      switchSource.append("      " + (firstKey + i * keyStep) + " : " + caseLabel + "\n");
+      targetCode.append("  " + caseLabel + ":\n");
+      targetCode.append("    ldc " + i + "\n");
+      targetCode.append("    goto return_\n");
+    }
+    if (additionalLastKey != null) {
+      String caseLabel = "case_" + totalKeys;
+      switchSource.append("      " + additionalLastKey + " : " + caseLabel + "\n");
+      targetCode.append("  " + caseLabel + ":\n");
+      targetCode.append("    ldc " + totalKeys + "\n");
+      targetCode.append("    goto return_\n");
+    }
+
+    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+        "    .limit stack 1",
+        "    .limit locals 1",
+        "    iload 0",
+        "  lookupswitch",
+        switchSource.toString(),
+        "      default : case_default",
+        targetCode.toString(),
+        "  case_default:",
+        "    iconst_5",
+        "  return_:",
+        "    ireturn");
+
+    clazz.addMainMethod(
+        "    .limit stack 2",
+        "    .limit locals 1",
+        "    getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "    ldc 2",
+        "    invokestatic Test/test(I)I",
+        "    invokevirtual java/io/PrintStream/print(I)V",
+        "    return");
+
+    AndroidApp app = compileWithR8(builder);
+
+    MethodSignature signature = new MethodSignature("test", "int", ImmutableList.of("int"));
+    DexEncodedMethod method = getMethod(app, "Test", signature);
+    DexCode code = method.getCode().asDexCode();
+    int packedSwitchCount = 0;
+    int sparseSwitchCount = 0;
+    for (Instruction instruction : code.instructions) {
+      if (instruction instanceof PackedSwitch) {
+        packedSwitchCount++;
+      }
+      if (instruction instanceof SparseSwitch) {
+        sparseSwitchCount++;
+      }
+    }
+    if (keyStep <= 2) {
+      assertEquals(1, packedSwitchCount);
+      assertEquals(0, sparseSwitchCount);
+    } else {
+      assertEquals(0, packedSwitchCount);
+      assertEquals(1, sparseSwitchCount);
+    }
+  }
+
+  @Test
+  public void largerSwitchJar() throws Exception {
+    runLargerSwitchJarTest(0, 1, 100, null);
+    runLargerSwitchJarTest(0, 2, 100, null);
+    runLargerSwitchJarTest(0, 3, 100, null);
+    runLargerSwitchJarTest(100, 100, 100, null);
+    runLargerSwitchJarTest(-10000, 100, 100, null);
+    runLargerSwitchJarTest(-10000, 200, 100, 10000);
+    runLargerSwitchJarTest(
+        Integer.MIN_VALUE, (int) ((-(long) Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE);
+
+    // This is the maximal value possible with Jasmin with the generated code above. It depends on
+    // the source, so making smaller source can raise this limit. However we never get close to the
+    // class file max.
+    runLargerSwitchJarTest(0, 1, 5503, null);
+  }
+
+  private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs,
+      int expectedPackedSwitches, int expectedSparceSwitches) throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    StringBuilder x = new StringBuilder();
+    StringBuilder y = new StringBuilder();
+    for (Integer key : keys) {
+      x.append(key).append(" : case_").append(key).append("\n");
+      y.append("case_").append(key).append(":\n");
+      y.append("    ldc ").append(key).append("\n");
+      y.append("    goto return_\n");
+    }
+
+    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+        "    .limit stack 1",
+        "    .limit locals 1",
+        "    iload_0",
+        "    lookupswitch",
+        x.toString(),
+        "      default : case_default",
+        y.toString(),
+        "  case_default:",
+        "    ldc " + defaultValue,
+        "  return_:",
+        "    ireturn");
+
+    // Add the Jasmin class and a class from Java source with the main method.
+    AndroidApp.Builder appBuilder = AndroidApp.builder();
+    appBuilder.addClassProgramData(builder.buildClasses());
+    appBuilder.addProgramFiles(FilteredClassPath
+        .unfiltered(ToolHelper.getClassFileForTestClass(CheckSwitchInTestClass.class)));
+    AndroidApp app = compileWithR8(appBuilder.build());
+
+    DexInspector inspector = new DexInspector(app);
+    MethodSubject method = inspector.clazz("Test").method("int", "test", ImmutableList.of("int"));
+    DexCode code = method.getMethod().getCode().asDexCode();
+
+    int packedSwitches = 0;
+    int sparseSwitches = 0;
+    int ifs = 0;
+    for (Instruction instruction : code.instructions) {
+      if (instruction instanceof PackedSwitch) {
+        packedSwitches++;
+      }
+      if (instruction instanceof SparseSwitch) {
+        sparseSwitches++;
+      }
+      if (instruction instanceof IfEq || instruction instanceof IfEqz) {
+        ifs++;
+      }
+    }
+
+    assertEquals(expectedPackedSwitches, packedSwitches);
+    assertEquals(expectedSparceSwitches, sparseSwitches);
+    assertEquals(expectedIfs, ifs);
+
+    // Run the code
+    List<String> args = keys.stream().map(Object::toString).collect(Collectors.toList());
+    args.add(Integer.toString(defaultValue));
+    runOnArt(app, CheckSwitchInTestClass.class, args);
+  }
+
+  @Test
+  public void convertCasesToIf() throws Exception {
+    // Switches that are completely converted to ifs.
+    runConvertCasesToIf(ImmutableList.of(0, 1000), -100, 2, 0, 0);
+    runConvertCasesToIf(ImmutableList.of(0, 1000, 2000), -100, 3, 0, 0);
+    runConvertCasesToIf(ImmutableList.of(0, 1000, 2000, 3000), -100, 4, 0, 0);
+
+    // Switches that are completely converted to ifs and one switch.
+    runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
+    runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1, 1, 0);
+    runConvertCasesToIf(ImmutableList.of(
+        Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
+    runConvertCasesToIf(ImmutableList.of(
+        1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1, 1, 0);
+    runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 1, 0);
+    runConvertCasesToIf(ImmutableList.of(
+        Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2, 1, 0);
+
+    // Switches that are completely converted to ifs and two switches.
+    runConvertCasesToIf(ImmutableList.of(
+        0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 0, 2, 0);
+    runConvertCasesToIf(ImmutableList.of(
+        -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 1, 2, 0);
+    runConvertCasesToIf(ImmutableList.of(
+        -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 2, 0);
+
+    // Switches that are completely converted two switches (one sparse and one packed).
+    runConvertCasesToIf(ImmutableList.of(
+        -1000, -900, -800, -700, -600, -500, -400, -300,
+        1000, 1001, 1002, 1003, 1004,
+        2000, 2100, 2200, 2300, 2400, 2500), -100, 0, 1, 1);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
new file mode 100644
index 0000000..3d921b6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
@@ -0,0 +1,250 @@
+// 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.rewrite.switches;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.code.Const;
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ConstHigh16;
+import com.android.tools.r8.code.IfEq;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.PackedSwitch;
+import com.android.tools.r8.code.SparseSwitch;
+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.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.function.Consumer;
+import org.junit.Test;
+
+public class SwitchRewritingTest extends SmaliTestBase {
+
+  static boolean twoCaseWillUsePackedSwitch(int key1, int key2) {
+    assert key1 != key2;
+    return Math.abs((long) key1 - (long) key2) == 1;
+  }
+
+  private boolean some16BitConst(Instruction instruction) {
+    return instruction instanceof Const4
+        || instruction instanceof ConstHigh16
+        || instruction instanceof Const;
+  }
+  private void runSingleCaseDexTest(boolean packed, int key) {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+    String switchInstruction;
+    String switchData;
+    if (packed) {
+      switchInstruction = "packed-switch";
+      switchData = StringUtils.join(
+          "\n",
+          "  :switch_data",
+          "  .packed-switch " + key,
+          "    :case_0",
+          "  .end packed-switch");
+    } else {
+      switchInstruction = "sparse-switch";
+      switchData = StringUtils.join(
+          "\n",
+          "  :switch_data",
+          "  .sparse-switch",
+          "    " + key + " -> :case_0",
+          "  .end sparse-switch");
+    }
+    MethodSignature signature = builder.addStaticMethod(
+        "int",
+        DEFAULT_METHOD_NAME,
+        ImmutableList.of("int"),
+        0,
+        "    " + switchInstruction + " p0, :switch_data",
+        "    const/4 p0, 0x5",
+        "    goto :return",
+        "  :case_0",
+        "    const/4 p0, 0x3",
+        "  :return",
+        "    return p0",
+        switchData);
+
+    builder.addMainMethod(
+        2,
+        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "    const/4             v1, 0",
+        "    invoke-static       { v1 }, LTest;->method(I)I",
+        "    move-result         v1",
+        "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(I)V",
+        "    return-void"
+    );
+
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
+    DexEncodedMethod method = getMethod(processedApplication, signature);
+    DexCode code = method.getCode().asDexCode();
+
+    if (key == 0) {
+      assertEquals(5, code.instructions.length);
+      assertTrue(code.instructions[0] instanceof IfEqz);
+    } else {
+      assertEquals(6, code.instructions.length);
+      assertTrue(some16BitConst(code.instructions[0]));
+      assertTrue(code.instructions[1] instanceof IfEq);
+    }
+  }
+
+  @Test
+  public void singleCaseDex() {
+    for (boolean packed : new boolean[]{true, false}) {
+      runSingleCaseDexTest(packed, Integer.MIN_VALUE);
+      runSingleCaseDexTest(packed, -1);
+      runSingleCaseDexTest(packed, 0);
+      runSingleCaseDexTest(packed, 1);
+      runSingleCaseDexTest(packed, Integer.MAX_VALUE);
+    }
+  }
+
+  private void runTwoCaseSparseToPackedOrIfsDexTest(int key1, int key2) {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    MethodSignature signature = builder.addStaticMethod(
+        "int",
+        DEFAULT_METHOD_NAME,
+        ImmutableList.of("int"),
+        0,
+        "    sparse-switch p0, :sparse_switch_data",
+        "    const/4 v0, 0x5",
+        "    goto :return",
+        "  :case_1",
+        "    const/4 v0, 0x3",
+        "    goto :return",
+        "  :case_2",
+        "    const/4 v0, 0x4",
+        "  :return",
+        "    return v0",
+        "  :sparse_switch_data",
+        "  .sparse-switch",
+        "    " + key1 + " -> :case_1",
+        "    " + key2 + " -> :case_2",
+        "  .end sparse-switch");
+
+    builder.addMainMethod(
+        2,
+        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "    const/4             v1, 0",
+        "    invoke-static       { v1 }, LTest;->method(I)I",
+        "    move-result         v1",
+        "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(I)V",
+        "    return-void"
+    );
+
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
+    DexEncodedMethod method = getMethod(processedApplication, signature);
+    DexCode code = method.getCode().asDexCode();
+    if (twoCaseWillUsePackedSwitch(key1, key2)) {
+      assertTrue(code.instructions[0] instanceof PackedSwitch);
+    } else {
+      if (key1 == 0) {
+        assertTrue(code.instructions[0] instanceof IfEqz);
+      } else {
+        // Const instruction before if.
+        assertTrue(code.instructions[1] instanceof IfEq);
+      }
+    }
+  }
+
+  @Test
+  public void twoCaseSparseToPackedOrIfsDex() {
+    for (int delta = 1; delta <= 3; delta++) {
+      runTwoCaseSparseToPackedOrIfsDexTest(0, delta);
+      runTwoCaseSparseToPackedOrIfsDexTest(-delta, 0);
+      runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
+      runTwoCaseSparseToPackedOrIfsDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
+    }
+    runTwoCaseSparseToPackedOrIfsDexTest(-1, 1);
+    runTwoCaseSparseToPackedOrIfsDexTest(-2, 1);
+    runTwoCaseSparseToPackedOrIfsDexTest(-1, 2);
+    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
+    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
+    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
+  }
+
+  private void runLargerSwitchDexTest(int firstKey, int keyStep, int totalKeys,
+      Integer additionalLastKey) throws Exception {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    StringBuilder switchSource = new StringBuilder();
+    StringBuilder targetCode = new StringBuilder();
+    for (int i = 0; i < totalKeys; i++) {
+      String caseLabel = "case_" + i;
+      switchSource.append("    " + (firstKey + i * keyStep) + " -> :" + caseLabel + "\n");
+      targetCode.append("  :" + caseLabel + "\n");
+      targetCode.append("    goto :return\n");
+    }
+    if (additionalLastKey != null) {
+      String caseLabel = "case_" + totalKeys;
+      switchSource.append("    " + additionalLastKey + " -> :" + caseLabel + "\n");
+      targetCode.append("  :" + caseLabel + "\n");
+      targetCode.append("    goto :return\n");
+    }
+
+    MethodSignature signature = builder.addStaticMethod(
+        "void",
+        DEFAULT_METHOD_NAME,
+        ImmutableList.of("int"),
+        0,
+        "    sparse-switch p0, :sparse_switch_data",
+        "    goto :return",
+        targetCode.toString(),
+        "  :return",
+        "    return-void",
+        "  :sparse_switch_data",
+        "  .sparse-switch",
+        switchSource.toString(),
+        "  .end sparse-switch");
+
+    builder.addMainMethod(
+        2,
+        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "    const/4             v1, 0",
+        "    invoke-static       { v1 }, LTest;->method(I)V",
+        "    return-void"
+    );
+
+    Consumer<InternalOptions> optionsConsumer = options -> {
+      options.verbose = true;
+      options.printTimes = true;
+    };
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, optionsConsumer);
+    DexEncodedMethod method = getMethod(processedApplication, signature);
+    DexCode code = method.getCode().asDexCode();
+    if (keyStep <= 2) {
+      assertTrue(code.instructions[0] instanceof PackedSwitch);
+    } else {
+      assertTrue(code.instructions[0] instanceof SparseSwitch);
+    }
+  }
+
+  @Test
+  public void twoMonsterSparseToPackedDex() throws Exception {
+    runLargerSwitchDexTest(0, 1, 100, null);
+    runLargerSwitchDexTest(0, 2, 100, null);
+    runLargerSwitchDexTest(0, 3, 100, null);
+    runLargerSwitchDexTest(100, 100, 100, null);
+    runLargerSwitchDexTest(-10000, 100, 100, null);
+    runLargerSwitchDexTest(-10000, 200, 100, 10000);
+    runLargerSwitchDexTest(
+        Integer.MIN_VALUE, (int) ((-(long)Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE);
+
+    // TODO(63090177): Currently this is commented out as R8 gets really slow for large switches.
+    // runLargerSwitchDexTest(0, 1, Constants.U16BIT_MAX, null);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index 909f574..d2391ff 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -6,13 +6,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Arrays;
 import java.util.Collections;
@@ -62,13 +63,11 @@
         "  invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(I)V",
         "  return-void");
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-
+    AndroidApp originalApplication = buildApplication(builder);
 
     DexEncodedMethod method = getMethod(originalApplication, methodSig);
     // Get the IR pre-optimization.
-    IRCode code = method.buildIR(options);
+    IRCode code = method.buildIR(new InternalOptions());
 
     // Find the exit block and assert that the value is a phi merging the exceptional edge
     // with the normal edge.
diff --git a/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java b/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java
index 64aa946..de8b832 100644
--- a/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java
+++ b/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.smali;
 
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
 import java.util.Arrays;
 import java.util.Collections;
 import org.junit.Test;
@@ -48,9 +48,8 @@
         "  goto :in_try"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexEncodedMethod method = getMethod(processedApplication, methodSig);
     assert method.getCode().asDexCode().tries.length > 0;
diff --git a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
index 8d4b841..0395177 100644
--- a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
+++ b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
@@ -6,9 +6,8 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
@@ -56,10 +55,9 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(smaliBuilder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    String result = runArt(processedApplication, options);
+    AndroidApp originalApplication = buildApplication(smaliBuilder);
+    AndroidApp processedApplication = processApplication(originalApplication);
+    String result = runArt(processedApplication);
 
     assertEquals(expectedBuilder.toString(), result);
   }
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 1aa9be7..d7f16e8 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -21,26 +21,26 @@
 import com.android.tools.r8.code.ReturnObject;
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.code.ReturnWide;
-import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
-import org.antlr.runtime.RecognitionException;
 import org.junit.Test;
 
 public class OutlineTest extends SmaliTestBase {
@@ -52,6 +52,15 @@
     return result;
   }
 
+  private Consumer<InternalOptions> configureOptions(Consumer<OutlineOptions> optionsConsumer) {
+    return options -> {
+      // Disable inlining to make sure that code looks as expected.
+      options.inlineAccessors = false;
+      // Also apply outline options.
+      optionsConsumer.accept(options.outline);
+    };
+  }
+
   DexEncodedMethod getInvokedMethod(DexApplication application, InvokeStatic invoke) {
     DexInspector inspector = new DexInspector(application);
     ClassSubject clazz = inspector.clazz(invoke.getMethod().holder.toSourceString());
@@ -62,31 +71,19 @@
         invokedMethod.proto.returnType.toSourceString(),
         invokedMethod.name.toString(),
         Arrays.stream(invokedMethod.proto.parameters.values)
-            .map(p -> p.toSourceString())
+            .map(DexType::toSourceString)
             .collect(Collectors.toList()));
     assertTrue(method.isPresent());
     return method.getMethod();
   }
 
-  String firstOutlineMethodName(InternalOptions options) {
-    StringBuilder builder = new StringBuilder(options.outline.className);
-    builder.append('.');
-    builder.append(options.outline.methodPrefix);
-    builder.append("0");
-    return builder.toString();
+  private String firstOutlineMethodName() {
+    return OutlineOptions.CLASS_NAME + '.' + OutlineOptions.METHOD_PREFIX + "0";
   }
 
-  MethodSignature firstOutlineMethodSignature(
-      String returnType, List<String> parameterTypes, InternalOptions options) {
-    return new MethodSignature(
-        options.outline.className, options.outline.methodPrefix + "0", returnType, parameterTypes);
-  }
-
-  boolean isOutlineMethodName(InternalOptions options, String qualifiedName) {
-    StringBuilder builder = new StringBuilder(options.outline.className);
-    builder.append('.');
-    builder.append(options.outline.methodPrefix);
-    return qualifiedName.indexOf(builder.toString()) == 0;
+  private boolean isOutlineMethodName(String qualifiedName) {
+    String qualifiedPrefix = OutlineOptions.CLASS_NAME + '.' + OutlineOptions.METHOD_PREFIX;
+    return qualifiedName.indexOf(qualifiedPrefix) == 0;
   }
 
   @Test
@@ -127,14 +124,16 @@
     );
 
     for (int i = 2; i < 6; i++) {
-      InternalOptions options = createInternalOptions();
-      options.outline.threshold = 1;
-      options.outline.minSize = i;
-      options.outline.maxSize = i;
+      final int j = i;
+      Consumer<InternalOptions> options = configureOptions(outline -> {
+        outline.threshold = 1;
+        outline.minSize = j;
+        outline.maxSize = j;
+      });
 
-      DexApplication originalApplication = buildApplication(builder, options);
-      DexApplication processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, Iterables.size(processedApplication.classes()));
+      AndroidApp originalApplication = buildApplication(builder);
+      AndroidApp processedApplication = processApplication(originalApplication, options);
+      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -143,10 +142,10 @@
       assertTrue(code.instructions[0] instanceof ConstString);
       assertTrue(code.instructions[1] instanceof InvokeStatic);
       InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-      assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+      assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
 
       // Run code and check result.
-      String result = runArt(processedApplication, options);
+      String result = runArt(processedApplication);
       assertEquals("TestTestTestTest", result);
     }
   }
@@ -192,14 +191,16 @@
     );
 
     for (int i = 2; i < 6; i++) {
-      InternalOptions options = createInternalOptions();
-      options.outline.threshold = 1;
-      options.outline.minSize = i;
-      options.outline.maxSize = i;
+      final int finalI = i;
+      Consumer<InternalOptions> options = configureOptions(outline -> {
+        outline.threshold = 1;
+        outline.minSize = finalI;
+        outline.maxSize = finalI;
+      });
 
-      DexApplication originalApplication = buildApplication(builder, options);
-      DexApplication processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, Iterables.size(processedApplication.classes()));
+      AndroidApp originalApplication = buildApplication(builder);
+      AndroidApp processedApplication = processApplication(originalApplication, options);
+      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -213,10 +214,10 @@
       }
       assertTrue(code.instructions[firstOutlineInvoke] instanceof InvokeStatic);
       InvokeStatic invoke = (InvokeStatic) code.instructions[firstOutlineInvoke];
-      assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+      assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
 
       // Run code and check result.
-      String result = runArt(processedApplication, options);
+      String result = runArt(processedApplication);
       assertEquals("Test1Test2Test3Test4", result);
     }
   }
@@ -258,11 +259,12 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+    });
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -271,10 +273,10 @@
     assertTrue(code.instructions[0] instanceof ConstString);
     assertTrue(code.instructions[1] instanceof InvokeStatic);
     InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-    assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("1", result);
   }
 
@@ -322,11 +324,12 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+    });
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -336,10 +339,10 @@
     assertTrue(code.instructions[1] instanceof ConstString);
     assertTrue(code.instructions[2] instanceof InvokeStatic);
     InvokeStatic invoke = (InvokeStatic) code.instructions[2];
-    assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("TestXTest1TestYTest2Test3", result);
   }
 
@@ -381,14 +384,16 @@
     );
 
     for (int i = 2; i < 4; i++) {
-      InternalOptions options = createInternalOptions();
-      options.outline.threshold = 1;
-      options.outline.minSize = i;
-      options.outline.maxSize = i;
+      final int finalI = i;
+      Consumer<InternalOptions> options = configureOptions(outline -> {
+        outline.threshold = 1;
+        outline.minSize = finalI;
+        outline.maxSize = finalI;
+      });
 
-      DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-      DexApplication processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, Iterables.size(processedApplication.classes()));
+      AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+      AndroidApp processedApplication = processApplication(originalApplication, options);
+      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -398,17 +403,17 @@
       if (i < 3) {
         assertTrue(code.instructions[1] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-        assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
       } else {
         assertTrue(code.instructions[1] instanceof InvokeVirtual);
         assertTrue(code.instructions[2] instanceof InvokeVirtual);
         assertTrue(code.instructions[3] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[3];
-        assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
       }
 
       // Run code and check result.
-      String result = runArt(processedApplication, options);
+      String result = runArt(processedApplication);
       StringBuilder resultBuilder = new StringBuilder();
       for (int j = 0; j < 4; j++) {
         resultBuilder.append(0x7fffffff00000000L);
@@ -455,14 +460,16 @@
     );
 
     for (int i = 2; i < 4; i++) {
-      InternalOptions options = createInternalOptions();
-      options.outline.threshold = 1;
-      options.outline.minSize = i;
-      options.outline.maxSize = i;
+      final int finalI = i;
+      Consumer<InternalOptions> options = configureOptions(outline -> {
+        outline.threshold = 1;
+        outline.minSize = finalI;
+        outline.maxSize = finalI;
+      });
 
-      DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-      DexApplication processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, Iterables.size(processedApplication.classes()));
+      AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+      AndroidApp processedApplication = processApplication(originalApplication, options);
+      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -472,17 +479,17 @@
       if (i < 3) {
         assertTrue(code.instructions[1] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-        assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
       } else {
         assertTrue(code.instructions[1] instanceof InvokeVirtual);
         assertTrue(code.instructions[2] instanceof InvokeVirtual);
         assertTrue(code.instructions[3] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[3];
-        assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
       }
 
       // Run code and check result.
-      String result = runArt(processedApplication, options);
+      String result = runArt(processedApplication);
       StringBuilder resultBuilder = new StringBuilder();
       for (int j = 0; j < 4; j++) {
         resultBuilder.append(1.0d);
@@ -525,14 +532,16 @@
     );
 
     for (int i = 2; i < 6; i++) {
-      InternalOptions options = createInternalOptions();
-      options.outline.threshold = 1;
-      options.outline.minSize = i;
-      options.outline.maxSize = i;
+      final int finalI = i;
+      Consumer<InternalOptions> options = configureOptions(outline -> {
+        outline.threshold = 1;
+        outline.minSize = finalI;
+        outline.maxSize = finalI;
+      });
 
-      DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-      DexApplication processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, Iterables.size(processedApplication.classes()));
+      AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+      AndroidApp processedApplication = processApplication(originalApplication, options);
+      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed main method for inspection.
       DexEncodedMethod mainMethod = getMethod(processedApplication, mainSignature);
@@ -548,18 +557,18 @@
       }
       if (i == 2) {
         InvokeStatic invoke = (InvokeStatic) mainCode.instructions[4];
-        assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
       } else if (i == 3) {
         InvokeStatic invoke = (InvokeStatic) mainCode.instructions[1];
-        assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
       } else {
         assert i == 4 || i == 5;
         InvokeStatic invoke = (InvokeStatic) mainCode.instructions[2];
-        assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
       }
 
       // Run code and check result.
-      String result = runArt(processedApplication, options);
+      String result = runArt(processedApplication);
       assertEquals("1122", result);
     }
   }
@@ -623,29 +632,30 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 7;
-    options.outline.maxSize = 7;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 7;
+      outline.maxSize = 7;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     DexCode code1 = getMethod(processedApplication, signature1).getCode().asDexCode();
     assertEquals(4, code1.instructions.length);
     assertTrue(code1.instructions[1] instanceof InvokeStatic);
     InvokeStatic invoke1 = (InvokeStatic) code1.instructions[1];
-    assertTrue(isOutlineMethodName(options, invoke1.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
 
     DexCode code2 = getMethod(processedApplication, signature2).getCode().asDexCode();
     assertEquals(5, code2.instructions.length);
     assertTrue(code2.instructions[2] instanceof InvokeStatic);
     InvokeStatic invoke2 = (InvokeStatic) code2.instructions[2];
-    assertTrue(isOutlineMethodName(options, invoke1.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("Test1Test1Test1Test1Test2Test2Test2Test2", result);
   }
 
@@ -687,14 +697,16 @@
     );
 
     for (int i = 2; i < 8; i++) {
-      InternalOptions options = createInternalOptions();
-      options.outline.threshold = 1;
-      options.outline.minSize = i;
-      options.outline.maxSize = i;
+      final int finalI = i;
+      Consumer<InternalOptions> options = configureOptions(outline -> {
+        outline.threshold = 1;
+        outline.minSize = finalI;
+        outline.maxSize = finalI;
+      });
 
-      DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-      DexApplication processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, Iterables.size(processedApplication.classes()));
+      AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+      AndroidApp processedApplication = processApplication(originalApplication, options);
+      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       DexCode code = getMethod(processedApplication, signature).getCode().asDexCode();
       InvokeStatic invoke;
@@ -711,10 +723,10 @@
           outlineInstructionIndex = 2;
       }
       invoke = (InvokeStatic) code.instructions[outlineInstructionIndex];
-      assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+      assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
 
       // Run code and check result.
-      String result = runArt(processedApplication, options);
+      String result = runArt(processedApplication);
       assertEquals("Test2Test2", result);
     }
   }
@@ -744,23 +756,24 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 3;
-    options.outline.maxSize = 3;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 3;
+      outline.maxSize = 3;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     DexCode code = getMethod(processedApplication, signature1).getCode().asDexCode();
     InvokeStatic invoke;
     assertTrue(code.instructions[0] instanceof InvokeStatic);
     invoke = (InvokeStatic) code.instructions[0];
-    assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("", result);
   }
 
@@ -817,18 +830,19 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 3;
-    options.outline.maxSize = 3;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 3;
+      outline.maxSize = 3;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Check that three outlining methods was created.
     DexInspector inspector = new DexInspector(processedApplication);
-    ClassSubject clazz = inspector.clazz(options.outline.className);
+    ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
     assertTrue(clazz.isPresent());
     assertEquals(3, clazz.getDexClass().directMethods().length);
     // Collect the return types of the putlines for the body of method1 and method2.
@@ -842,12 +856,12 @@
     assert r.size() == 2;
     DexType r1 = r.get(0);
     DexType r2 = r.get(1);
-    DexItemFactory factory = processedApplication.dexItemFactory;
+    DexItemFactory factory = inspector.getFactory();
     assertTrue(r1 == factory.voidType && r2 == factory.stringType ||
         r1 == factory.stringType && r2 == factory.voidType);
 
     // Run the code.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("TestTestTestTest", result);
   }
 
@@ -888,37 +902,40 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 3;
-    options.outline.maxSize = 3;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 3;
+      outline.maxSize = 3;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     final int count = 10;
     // Process the application several times. Each time will outline the previous outline.
     for (int i = 0; i < count; i++) {
       // Build a new application with the Outliner class.
-      DexApplication.Builder appBuilder = processedApplication.builder();
-      originalApplication = appBuilder.build();
+      originalApplication = processedApplication;
       processedApplication = processApplication(originalApplication, options);
-      assertEquals(i + 3, Iterables.size(processedApplication.classes()));
+      assertEquals(i + 3, getNumberOfProgramClasses(processedApplication));
     }
 
     // Process the application several times. No more outlining as threshold has been raised.
-    options.outline.threshold = 2;
+    options = configureOptions(outline -> {
+      outline.threshold = 2;
+      outline.minSize = 3;
+      outline.maxSize = 3;
+    });
     for (int i = 0; i < count; i++) {
       // Build a new application with the Outliner class.
-      DexApplication.Builder appBuilder = processedApplication.builder();
-      originalApplication = appBuilder.build();
+      originalApplication = processedApplication;
       processedApplication = processApplication(originalApplication, options);
-      assertEquals(count - 1 + 3, Iterables.size(processedApplication.classes()));
+      assertEquals(count - 1 + 3, getNumberOfProgramClasses(processedApplication));
     }
 
     // Run the application with several levels of outlining.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("TestTestTestTest", result);
   }
 
@@ -950,14 +967,15 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 5;
-    options.outline.maxSize = 5;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 5;
+      outline.maxSize = 5;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -968,10 +986,10 @@
     assertTrue(code.instructions[1] instanceof MoveResultWide);
     assertTrue(code.instructions[2] instanceof ReturnWide);
     InvokeStatic invoke = (InvokeStatic) code.instructions[0];
-    assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName());
+    assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
 
     // Run the code and expect a parsable long.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     Long.parseLong(result);
   }
 
@@ -1010,14 +1028,15 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 4;
-    options.outline.maxSize = 4;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 4;
+      outline.maxSize = 4;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -1026,10 +1045,10 @@
     assertTrue(code.instructions[0] instanceof InvokeStatic);
     assertTrue(code.instructions[1] instanceof ReturnObject);
     InvokeStatic invoke = (InvokeStatic) code.instructions[0];
-    assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName());
+    assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("null", result);
   }
 
@@ -1084,14 +1103,15 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 4;
-    options.outline.maxSize = 4;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 4;
+      outline.maxSize = 4;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method1 = getMethod(processedApplication, signature1);
@@ -1101,7 +1121,7 @@
     assertTrue(code1.instructions[1] instanceof MoveResult);
     assertTrue(code1.instructions[2] instanceof Return);
     InvokeStatic invoke1 = (InvokeStatic) code1.instructions[0];
-    assertTrue(isOutlineMethodName(options, invoke1.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
 
     DexEncodedMethod method2 = getMethod(processedApplication, signature2);
     DexCode code2 = method2.getCode().asDexCode();
@@ -1110,7 +1130,7 @@
     assertEquals(invoke1.getMethod().qualifiedName(), invoke2.getMethod().qualifiedName());
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("44", result);
   }
 
@@ -1159,14 +1179,15 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 3;  // Outline add, sub and mul.
-    options.outline.maxSize = 3;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 3;  // Outline add, sub and mul.
+      outline.maxSize = 3;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -1180,10 +1201,10 @@
     assertTrue(code.instructions[5] instanceof Const4);
     assertTrue(code.instructions[6] instanceof Return);
     InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-    assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("4", result);
   }
 
@@ -1211,14 +1232,15 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 3;
-    options.outline.maxSize = 3;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 3;
+      outline.maxSize = 3;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -1227,10 +1249,10 @@
     assertTrue(code.instructions[0] instanceof InvokeStatic);
     assertTrue(code.instructions[1] instanceof ReturnVoid);
     InvokeStatic invoke = (InvokeStatic) code.instructions[0];
-    assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName());
+    assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("", result);
   }
 
@@ -1256,14 +1278,15 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 3;
-    options.outline.maxSize = 3;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 3;
+      outline.maxSize = 3;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -1272,10 +1295,10 @@
     assertTrue(code.instructions[0] instanceof InvokeStatic);
     assertTrue(code.instructions[1] instanceof ReturnVoid);
     InvokeStatic invoke = (InvokeStatic) code.instructions[0];
-    assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName());
+    assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("", result);
   }
 
@@ -1462,14 +1485,15 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 2;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 2;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Verify the code.
-    runDex2Oat(processedApplication, options);
+    runDex2Oat(processedApplication);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/smali/Regress38014736.java b/src/test/java/com/android/tools/r8/smali/Regress38014736.java
index bf89f7f..1f0cd78 100644
--- a/src/test/java/com/android/tools/r8/smali/Regress38014736.java
+++ b/src/test/java/com/android/tools/r8/smali/Regress38014736.java
@@ -5,9 +5,7 @@
 
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
 
@@ -48,10 +46,9 @@
         "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V",
         "return-void");
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    String result = runArt(processedApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
+    String result = runArt(processedApplication);
     // The art runtime changed the way exceptions are printed. Therefore, we only check
     // for the type of the exception and that the message mentions null.
     assertTrue(result.startsWith(StringUtils.joinLines("0", "java.lang.NumberFormatException:")));
diff --git a/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java b/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
index 214a3b4..4d13ae0 100644
--- a/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
+++ b/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
@@ -12,12 +12,11 @@
 import com.android.tools.r8.code.InvokeVirtual;
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.code.SgetObject;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.Iterables;
 import org.junit.Test;
 
 public class RunArtSmokeTest extends SmaliTestBase {
@@ -34,10 +33,9 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(1, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
+    assertEquals(1, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod main = getMethod(processedApplication, mainSignature);
@@ -50,7 +48,7 @@
     assertTrue(code.instructions[3] instanceof ReturnVoid);
 
     // Run the generated code in Art.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals(StringUtils.lines("Hello, world!"), result);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
index b3a4060..8e946b6 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
@@ -7,20 +7,24 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 
 public class SmaliBuildTest extends SmaliTestBase {
 
-  private void checkJavaLangString(DexApplication application, boolean present) {
-    DexInspector inspector = new DexInspector(application);
-    ClassSubject clazz = inspector.clazz("java.lang.String");
-    assertEquals(present, clazz.isPresent());
+  private void checkJavaLangString(AndroidApp application, boolean present) {
+    try {
+      DexInspector inspector = new DexInspector(application);
+      ClassSubject clazz = inspector.clazz("java.lang.String");
+      assertEquals(present, clazz.isPresent());
+    } catch (IOException | ExecutionException e) {
+      throw new RuntimeException(e);
+    }
   }
 
   @Test
@@ -35,12 +39,11 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
     // No libraries added - java.lang.String is not present.
-    DexApplication originalApplication = buildApplication(builder, options);
+    AndroidApp originalApplication = buildApplication(builder);
     checkJavaLangString(originalApplication, false);
 
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp processedApplication = processApplication(originalApplication);
     checkJavaLangString(processedApplication, false);
   }
 
@@ -56,17 +59,17 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    AndroidApp app = AndroidApp.builder()
+    AndroidApp originalApp = AndroidApp.builder()
         .addDexProgramData(builder.compile())
         .addLibraryFiles(FilteredClassPath.unfiltered(ToolHelper.getDefaultAndroidJar()))
         .build();
 
     // Java standard library added - java.lang.String is present.
-    DexApplication originalApplication = buildApplication(app, options);
-    checkJavaLangString(originalApplication, true);
+    checkJavaLangString(originalApp, true);
 
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    checkJavaLangString(processedApplication, true);
+    AndroidApp processedApplication = processApplication(originalApp);
+
+    // The library method is not part of the output.
+    checkJavaLangString(processedApplication, false);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
new file mode 100644
index 0000000..ad1e097
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
@@ -0,0 +1,349 @@
+// 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.smali;
+
+import com.android.tools.r8.errors.DexOverflowException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Smali;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import org.antlr.runtime.RecognitionException;
+
+public class SmaliBuilder {
+
+  public static class MethodSignature {
+
+    public final String clazz;
+    public final String name;
+    public final String returnType;
+    public final List<String> parameterTypes;
+
+    public MethodSignature(String clazz, String name, String returnType,
+        List<String> parameterTypes) {
+      this.clazz = clazz;
+      this.name = name;
+      this.returnType = returnType;
+      this.parameterTypes = parameterTypes;
+    }
+
+    public static MethodSignature staticInitializer(String clazz) {
+      return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of());
+    }
+
+    @Override
+    public String toString() {
+      return returnType + " " + clazz + "." + name
+          + "(" + StringUtils.join(parameterTypes, ",") + ")";
+    }
+  }
+
+  abstract class Builder {
+
+    String name;
+    String superName;
+    List<String> implementedInterfaces;
+    String sourceFile = null;
+    List<String> source = new ArrayList<>();
+
+    Builder(String name, String superName, List<String> implementedInterfaces) {
+      this.name = name;
+      this.superName = superName;
+      this.implementedInterfaces = implementedInterfaces;
+    }
+
+    protected void appendSuper(StringBuilder builder) {
+      builder.append(".super ");
+      builder.append(DescriptorUtils.javaTypeToDescriptor(superName));
+      builder.append("\n");
+    }
+
+    protected void appendImplementedInterfaces(StringBuilder builder) {
+      for (String implementedInterface : implementedInterfaces) {
+        builder.append(".implements ");
+        builder.append(DescriptorUtils.javaTypeToDescriptor(implementedInterface));
+        builder.append("\n");
+      }
+    }
+
+    protected void writeSource(StringBuilder builder) {
+      for (String sourceLine : source) {
+        builder.append(sourceLine);
+        builder.append("\n");
+      }
+    }
+  }
+
+  public class ClassBuilder extends Builder {
+
+    ClassBuilder(String name, String superName, List<String> implementedInterfaces) {
+      super(name, superName, implementedInterfaces);
+    }
+
+    public String toString() {
+      StringBuilder builder = new StringBuilder();
+      builder.append(".class public ");
+      builder.append(DescriptorUtils.javaTypeToDescriptor(name));
+      builder.append("\n");
+      appendSuper(builder);
+      appendImplementedInterfaces(builder);
+      builder.append("\n");
+      if (sourceFile != null) {
+        builder.append(".source \"").append(sourceFile).append("\"\n");
+      }
+      writeSource(builder);
+      return builder.toString();
+    }
+  }
+
+  public class InterfaceBuilder extends Builder {
+
+    InterfaceBuilder(String name, String superName) {
+      super(name, superName, ImmutableList.of());
+    }
+
+    public String toString() {
+      StringBuilder builder = new StringBuilder();
+      builder.append(".class public interface abstract ");
+      builder.append(DescriptorUtils.javaTypeToDescriptor(name));
+      builder.append("\n");
+      appendSuper(builder);
+      appendImplementedInterfaces(builder);
+      builder.append("\n");
+      writeSource(builder);
+      return builder.toString();
+    }
+  }
+
+  private String currentClassName;
+  private final Map<String, Builder> classes = new HashMap<>();
+
+  public SmaliBuilder() {
+    // No default class.
+  }
+
+  public SmaliBuilder(String name) {
+    addClass(name);
+  }
+
+  public SmaliBuilder(String name, String superName) {
+    addClass(name, superName);
+  }
+
+  private List<String> getSource(String clazz) {
+    return classes.get(clazz).source;
+  }
+
+  public String getCurrentClassName() {
+    return currentClassName;
+  }
+
+  public String getCurrentClassDescriptor() {
+    return DescriptorUtils.javaTypeToDescriptor(currentClassName);
+  }
+
+  public void addClass(String name) {
+    addClass(name, "java.lang.Object");
+  }
+
+  public void addClass(String name, String superName) {
+    addClass(name, superName, ImmutableList.of());
+  }
+
+  public void addClass(String name, String superName, List<String> implementedInterfaces) {
+    assert !classes.containsKey(name);
+    currentClassName = name;
+    classes.put(name, new ClassBuilder(name, superName, implementedInterfaces));
+  }
+
+  public void addInterface(String name) {
+    addInterface(name, "java.lang.Object");
+  }
+
+  public void addInterface(String name, String superName) {
+    assert !classes.containsKey(name);
+    currentClassName = name;
+    classes.put(name, new InterfaceBuilder(name, superName));
+  }
+
+  public void setSourceFile(String file) {
+    classes.get(currentClassName).sourceFile = file;
+  }
+
+  public void addDefaultConstructor() {
+    String superDescriptor =
+        DescriptorUtils.javaTypeToDescriptor(classes.get(currentClassName).superName);
+    addMethodRaw(
+        "  .method public constructor <init>()V",
+        "    .locals 0",
+        "    invoke-direct {p0}, " + superDescriptor + "-><init>()V",
+        "    return-void",
+        "  .end method"
+    );
+  }
+
+  public void addStaticField(String name, String type, String defaultValue) {
+    StringBuilder builder = new StringBuilder();
+    builder.append(".field static ");
+    builder.append(name);
+    builder.append(":");
+    builder.append(type);
+    if (defaultValue != null) {
+      builder.append(" = ");
+      if (type.equals("Ljava/lang/String;")) {
+        builder.append('"');
+        builder.append(defaultValue);
+        builder.append('"');
+      } else {
+        builder.append(defaultValue);
+      }
+    }
+    getSource(currentClassName).add(builder.toString());
+  }
+
+  public void addStaticField(String name, String type) {
+    addStaticField(name, type, null);
+  }
+
+  public void addInstanceField(String name, String type) {
+    StringBuilder builder = new StringBuilder();
+    builder.append(".field ");
+    builder.append(name);
+    builder.append(":");
+    builder.append(type);
+    getSource(currentClassName).add(builder.toString());
+  }
+
+  private MethodSignature addMethod(String flags, String returnType, String name,
+      List<String> parameters, int locals, String code) {
+    StringBuilder builder = new StringBuilder();
+    builder.append(".method ");
+    if (flags != null && flags.length() > 0) {
+      builder.append(flags);
+      builder.append(" ");
+    }
+    builder.append(name);
+    builder.append("(");
+    for (String parameter : parameters) {
+      builder.append(DescriptorUtils.javaTypeToDescriptor(parameter));
+    }
+    builder.append(")");
+    builder.append(DescriptorUtils.javaTypeToDescriptor(returnType));
+    builder.append("\n");
+    if (locals >= 0) {
+      builder.append(".locals ");
+      builder.append(locals);
+      builder.append("\n\n");
+      assert code != null;
+      builder.append(code);
+    } else {
+      assert code == null;
+    }
+    builder.append(".end method");
+    getSource(currentClassName).add(builder.toString());
+    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 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());
+  }
+
+  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();
+    return addMethod("public static " + flags, returnType, name, parameters, locals, code);
+  }
+
+  public MethodSignature addAbstractMethod(
+      String returnType, String name, List<String> parameters) {
+    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 addInstanceMethod(String returnType, String name,
+      List<String> parameters,
+      int locals, String code) {
+    return addMethod("public", returnType, name, parameters, locals, code);
+  }
+
+  public MethodSignature addMainMethod(int locals, String... instructions) {
+    return addStaticMethod(
+        "void", "main", Collections.singletonList("java.lang.String[]"), locals, instructions);
+  }
+
+  public void addMethodRaw(String... source) {
+    StringBuilder builder = new StringBuilder();
+    for (String line : source) {
+      builder.append(line);
+      builder.append("\n");
+    }
+    getSource(currentClassName).add(builder.toString());
+  }
+
+  public List<String> buildSource() {
+    List<String> result = new ArrayList<>(classes.size());
+    for (String clazz : classes.keySet()) {
+      Builder classBuilder = classes.get(clazz);
+      result.add(classBuilder.toString());
+    }
+    return result;
+  }
+
+  public byte[] compile()
+      throws IOException, RecognitionException, DexOverflowException, ExecutionException {
+    return Smali.compile(buildSource());
+  }
+
+  public AndroidApp build()
+      throws IOException, RecognitionException, DexOverflowException, ExecutionException {
+    return AndroidApp.fromDexProgramData(compile());
+  }
+
+
+  @Override
+  public String toString() {
+    return String.join("\n\n", buildSource());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
index b35e768..3fe8da9 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
@@ -6,16 +6,12 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.DexOverflowException;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.SmaliWriter;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Smali;
-import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.concurrent.ExecutionException;
@@ -27,12 +23,7 @@
   // Run the provided smali through R8 smali disassembler and expect the exact same output.
   void roundTripRawSmali(String smali) {
     try {
-      DexApplication application =
-          new ApplicationReader(
-                  AndroidApp.fromDexProgramData(Smali.compile(smali)),
-                  new InternalOptions(),
-                  new Timing("SmaliTest"))
-              .read();
+      AndroidApp application = AndroidApp.fromDexProgramData(Smali.compile(smali));
       assertEquals(smali, SmaliWriter.smali(application, new InternalOptions()));
     } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
       throw new RuntimeException(e);
@@ -41,7 +32,7 @@
 
   @Test
   public void simpleSmokeTest() {
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         "int", Collections.singletonList("int"),
         4,
         "    const/4 v0, 1           ",
@@ -81,7 +72,7 @@
 
   @Test
   public void sparseSwitchTest() {
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         "int", Collections.singletonList("int"),
         0,
         "    sparse-switch v0, :sparse_switch_data",
@@ -137,7 +128,7 @@
 
   @Test
   public void packedSwitchTest() {
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         "int", Collections.singletonList("int"),
         0,
         "    packed-switch v0, :packed_switch_data",
@@ -193,7 +184,7 @@
 
   @Test
   public void fillArrayDataTest8Bit() {
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         "int[]", ImmutableList.of(),
         2,
         "    const/4 v1, 3",
@@ -236,7 +227,7 @@
 
   @Test
   public void fillArrayDataTest16Bit() {
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         "int[]", ImmutableList.of(),
         2,
         "    const/4 v1, 3",
@@ -279,7 +270,7 @@
 
   @Test
   public void fillArrayDataTest32Bit() {
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         "int[]", ImmutableList.of(),
         2,
         "    const/4 v1, 3",
@@ -322,7 +313,7 @@
 
   @Test
   public void fillArrayDataTest64Bit() {
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         "int[]", ImmutableList.of(),
         2,
         "    const/4 v1, 3",
@@ -368,8 +359,7 @@
     SmaliBuilder builder = new SmaliBuilder();
     builder.addInterface("Test");
     builder.addAbstractMethod("int", "test", ImmutableList.of());
-    DexApplication application = buildApplication(builder);
-    assertEquals(1, Iterables.size(application.classes()));
+    AndroidApp application = buildApplication(builder);
 
     String expected =
         ".class public interface abstract LTest;\n" +
@@ -391,8 +381,7 @@
     SmaliBuilder builder = new SmaliBuilder();
     builder.addClass("Test", "java.lang.Object", ImmutableList.of("java.util.List"));
     builder.addAbstractMethod("int", "test", ImmutableList.of());
-    DexApplication application = buildApplication(builder);
-    assertEquals(1, Iterables.size(application.classes()));
+    AndroidApp application = buildApplication(builder);
 
     String expected =
         ".class public LTest;\n" +
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 2d2f887..4949153 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -8,8 +8,8 @@
 
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
+import com.android.tools.r8.Resource;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
@@ -18,39 +18,21 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AndroidAppOutputSink;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OutputMode;
-import com.android.tools.r8.utils.Smali;
-import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
+import java.util.Collection;
 import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executors;
 import java.util.function.Consumer;
 import org.antlr.runtime.RecognitionException;
 
@@ -60,414 +42,20 @@
   public static final String DEFAULT_MAIN_CLASS_NAME = DEFAULT_CLASS_NAME;
   public static final String DEFAULT_METHOD_NAME = "method";
 
-  public static class MethodSignature {
-
-    public final String clazz;
-    public final String name;
-    public final String returnType;
-    public final List<String> parameterTypes;
-
-    public MethodSignature(String clazz, String name, String returnType,
-        List<String> parameterTypes) {
-      this.clazz = clazz;
-      this.name = name;
-      this.returnType = returnType;
-      this.parameterTypes = parameterTypes;
-    }
-
-    public static MethodSignature staticInitializer(String clazz) {
-      return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of());
-    }
-
-    @Override
-    public String toString() {
-      return returnType + " " + clazz + "." + name
-          + "(" + StringUtils.join(parameterTypes, ",") + ")";
-    }
-  }
-
-  public static class SmaliBuilder {
-
-    abstract class Builder {
-
-      String name;
-      String superName;
-      List<String> implementedInterfaces;
-      String sourceFile = null;
-      List<String> source = new ArrayList<>();
-
-      Builder(String name, String superName, List<String> implementedInterfaces) {
-        this.name = name;
-        this.superName = superName;
-        this.implementedInterfaces = implementedInterfaces;
-      }
-
-      protected void appendSuper(StringBuilder builder) {
-        builder.append(".super ");
-        builder.append(DescriptorUtils.javaTypeToDescriptor(superName));
-        builder.append("\n");
-      }
-
-      protected void appendImplementedInterfaces(StringBuilder builder) {
-        for (String implementedInterface : implementedInterfaces) {
-          builder.append(".implements ");
-          builder.append(DescriptorUtils.javaTypeToDescriptor(implementedInterface));
-          builder.append("\n");
-        }
-      }
-
-      protected void writeSource(StringBuilder builder) {
-        for (String sourceLine : source) {
-          builder.append(sourceLine);
-          builder.append("\n");
-        }
-      }
-    }
-
-    public class ClassBuilder extends Builder {
-
-      ClassBuilder(String name, String superName, List<String> implementedInterfaces) {
-        super(name, superName, implementedInterfaces);
-      }
-
-      public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append(".class public ");
-        builder.append(DescriptorUtils.javaTypeToDescriptor(name));
-        builder.append("\n");
-        appendSuper(builder);
-        appendImplementedInterfaces(builder);
-        builder.append("\n");
-        if (sourceFile != null) {
-          builder.append(".source \"").append(sourceFile).append("\"\n");
-        }
-        writeSource(builder);
-        return builder.toString();
-      }
-    }
-
-    public class InterfaceBuilder extends Builder {
-
-      InterfaceBuilder(String name, String superName) {
-        super(name, superName, ImmutableList.of());
-      }
-
-      public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append(".class public interface abstract ");
-        builder.append(DescriptorUtils.javaTypeToDescriptor(name));
-        builder.append("\n");
-        appendSuper(builder);
-        appendImplementedInterfaces(builder);
-        builder.append("\n");
-        writeSource(builder);
-        return builder.toString();
-      }
-    }
-
-    private String currentClassName;
-    private final Map<String, Builder> classes = new HashMap<>();
-
-    public SmaliBuilder() {
-      // No default class.
-    }
-
-    public SmaliBuilder(String name) {
-      addClass(name);
-    }
-
-    public SmaliBuilder(String name, String superName) {
-      addClass(name, superName);
-    }
-
-    private List<String> getSource(String clazz) {
-      return classes.get(clazz).source;
-    }
-
-    public String getCurrentClassName() {
-      return currentClassName;
-    }
-
-    public String getCurrentClassDescriptor() {
-      return DescriptorUtils.javaTypeToDescriptor(currentClassName);
-    }
-
-    public void addClass(String name) {
-      addClass(name, "java.lang.Object");
-    }
-
-    public void addClass(String name, String superName) {
-      addClass(name, superName, ImmutableList.of());
-    }
-
-    public void addClass(String name, String superName, List<String> implementedInterfaces) {
-      assert !classes.containsKey(name);
-      currentClassName = name;
-      classes.put(name, new ClassBuilder(name, superName, implementedInterfaces));
-    }
-
-    public void addInterface(String name) {
-      addInterface(name, "java.lang.Object");
-    }
-
-    public void addInterface(String name, String superName) {
-      assert !classes.containsKey(name);
-      currentClassName = name;
-      classes.put(name, new InterfaceBuilder(name, superName));
-    }
-
-    public void setSourceFile(String file) {
-      classes.get(currentClassName).sourceFile = file;
-    }
-
-    public void addDefaultConstructor() {
-      String superDescriptor =
-          DescriptorUtils.javaTypeToDescriptor(classes.get(currentClassName).superName);
-      addMethodRaw(
-          "  .method public constructor <init>()V",
-          "    .locals 0",
-          "    invoke-direct {p0}, " + superDescriptor + "-><init>()V",
-          "    return-void",
-          "  .end method"
-      );
-    }
-
-    public void addStaticField(String name, String type, String defaultValue) {
-      StringBuilder builder = new StringBuilder();
-      builder.append(".field static ");
-      builder.append(name);
-      builder.append(":");
-      builder.append(type);
-      if (defaultValue != null) {
-        builder.append(" = ");
-        if (type.equals("Ljava/lang/String;")) {
-          builder.append('"');
-          builder.append(defaultValue);
-          builder.append('"');
-        } else {
-          builder.append(defaultValue);
-        }
-      }
-      getSource(currentClassName).add(builder.toString());
-    }
-
-    public void addStaticField(String name, String type) {
-      addStaticField(name, type, null);
-    }
-
-    public void addInstanceField(String name, String type) {
-      StringBuilder builder = new StringBuilder();
-      builder.append(".field ");
-      builder.append(name);
-      builder.append(":");
-      builder.append(type);
-      getSource(currentClassName).add(builder.toString());
-    }
-
-    private MethodSignature addMethod(String flags, String returnType, String name,
-        List<String> parameters, int locals, String code) {
-      StringBuilder builder = new StringBuilder();
-      builder.append(".method ");
-      if (flags != null && flags.length() > 0) {
-        builder.append(flags);
-        builder.append(" ");
-      }
-      builder.append(name);
-      builder.append("(");
-      for (String parameter : parameters) {
-        builder.append(DescriptorUtils.javaTypeToDescriptor(parameter));
-      }
-      builder.append(")");
-      builder.append(DescriptorUtils.javaTypeToDescriptor(returnType));
-      builder.append("\n");
-      if (locals >= 0) {
-        builder.append(".locals ");
-        builder.append(locals);
-        builder.append("\n\n");
-        assert code != null;
-        builder.append(code);
-      } else {
-        assert code == null;
-      }
-      builder.append(".end method");
-      getSource(currentClassName).add(builder.toString());
-      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 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());
-    }
-
-    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();
-      return addMethod("public static " + flags, returnType, name, parameters, locals, code);
-    }
-
-    public MethodSignature addAbstractMethod(
-        String returnType, String name, List<String> parameters) {
-      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 addInstanceMethod(String returnType, String name,
-        List<String> parameters,
-        int locals, String code) {
-      return addMethod("public", returnType, name, parameters, locals, code);
-    }
-
-    public MethodSignature addMainMethod(int locals, String... instructions) {
-      return addStaticMethod(
-          "void", "main", Collections.singletonList("java.lang.String[]"), locals, instructions);
-    }
-
-    public void addMethodRaw(String... source) {
-      StringBuilder builder = new StringBuilder();
-      for (String line : source) {
-        builder.append(line);
-        builder.append("\n");
-      }
-      getSource(currentClassName).add(builder.toString());
-    }
-
-    public List<String> buildSource() {
-      List<String> result = new ArrayList<>(classes.size());
-      for (String clazz : classes.keySet()) {
-        Builder classBuilder = classes.get(clazz);
-        result.add(classBuilder.toString());
-      }
-      return result;
-    }
-
-    public byte[] compile()
-        throws IOException, RecognitionException, DexOverflowException, ExecutionException {
-      return Smali.compile(buildSource());
-    }
-
-    public AndroidApp build()
-        throws IOException, RecognitionException, DexOverflowException, ExecutionException {
-      return AndroidApp.fromDexProgramData(compile());
-    }
-
-
-    @Override
-    public String toString() {
-      return String.join("\n\n", buildSource());
-    }
-  }
-
-  public class TestApplication {
-
-    public final DexApplication application;
-    public final DexEncodedMethod method;
-    public final IRCode code;
-    public final List<IRCode> additionalCode;
-    public final ValueNumberGenerator valueNumberGenerator;
-    public final InternalOptions options;
-
-    public TestApplication(
-        DexApplication application,
-        DexEncodedMethod method,
-        IRCode code,
-        ValueNumberGenerator valueNumberGenerator,
-        InternalOptions options) {
-      this(application, method, code, null, valueNumberGenerator, options);
-    }
-
-    public TestApplication(
-        DexApplication application,
-        DexEncodedMethod method,
-        IRCode code,
-        List<IRCode> additionalCode,
-        ValueNumberGenerator valueNumberGenerator,
-        InternalOptions options) {
-      this.application = application;
-      this.method = method;
-      this.code = code;
-      this.additionalCode = additionalCode;
-      this.valueNumberGenerator = valueNumberGenerator;
-      this.options = options;
-    }
-
-    public int countArgumentInstructions() {
-      int count = 0;
-      ListIterator<Instruction> iterator = code.blocks.get(0).listIterator();
-      while (iterator.next().isArgument()) {
-        count++;
-      }
-      return count;
-    }
-
-    public InstructionListIterator listIteratorAt(BasicBlock block, int index) {
-      InstructionListIterator iterator = block.listIterator();
-      for (int i = 0; i < index; i++) {
-        iterator.next();
-      }
-      return iterator;
-    }
-
-    public String run() throws DexOverflowException {
-      AppInfo appInfo = new AppInfo(application);
-      IRConverter converter = new IRConverter(appInfo, options);
-      converter.replaceCodeForTesting(method, code);
-      return runArt(application, options);
-    }
-  }
-
-  protected DexApplication buildApplication(SmaliBuilder builder) {
-    return buildApplication(builder, new InternalOptions());
-  }
-
-  protected DexApplication buildApplication(SmaliBuilder builder, InternalOptions options) {
+  protected AndroidApp buildApplication(SmaliBuilder builder) {
     try {
-      return buildApplication(AndroidApp.fromDexProgramData(builder.compile()), options);
-    } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
+      return AndroidApp.fromDexProgramData(builder.compile());
+    } catch (IOException | RecognitionException | DexOverflowException | ExecutionException e) {
       throw new RuntimeException(e);
     }
   }
 
-  protected DexApplication buildApplicationWithAndroidJar(
-      SmaliBuilder builder, InternalOptions options) {
+  protected AndroidApp buildApplicationWithAndroidJar(SmaliBuilder builder) {
     try {
-      AndroidApp input = AndroidApp.builder()
+      return AndroidApp.builder()
           .addDexProgramData(builder.compile())
           .addLibraryFiles(FilteredClassPath.unfiltered(ToolHelper.getDefaultAndroidJar()))
           .build();
-      return buildApplication(input, options);
     } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
       throw new RuntimeException(e);
     }
@@ -482,10 +70,15 @@
     }
   }
 
-  protected DexApplication processApplication(DexApplication application, InternalOptions options) {
+  protected AndroidApp processApplication(AndroidApp application) {
+    return processApplication(application, null);
+  }
+
+  protected AndroidApp processApplication(AndroidApp application,
+      Consumer<InternalOptions> optionsConsumer) {
     try {
-      return ToolHelper.optimizeWithR8(application, options);
-    } catch (IOException | CompilationException | ExecutionException e) {
+      return ToolHelper.runR8(application, optionsConsumer);
+    } catch (IOException | CompilationException e) {
       throw new RuntimeException(e);
     }
   }
@@ -535,29 +128,6 @@
     }
   }
 
-  protected DexEncodedMethod getMethod(
-      DexInspector inspector,
-      String className,
-      String returnType,
-      String methodName,
-      List<String> parameters) {
-    ClassSubject clazz = inspector.clazz(className);
-    assertTrue(clazz.isPresent());
-    MethodSubject method = clazz.method(returnType, methodName, parameters);
-    assertTrue(method.isPresent());
-    return method.getMethod();
-  }
-
-  protected DexEncodedMethod getMethod(
-      DexApplication application,
-      String className,
-      String returnType,
-      String methodName,
-      List<String> parameters) {
-    DexInspector inspector = new DexInspector(application);
-    return getMethod(inspector, className, returnType, methodName, parameters);
-  }
-
   protected DexEncodedMethod getMethod(Path appPath, MethodSignature signature) {
     try {
       DexInspector inspector = new DexInspector(appPath);
@@ -572,7 +142,7 @@
     }
   }
 
-  protected DexEncodedMethod getMethod(DexApplication application, MethodSignature signature) {
+  protected DexEncodedMethod getMethod(AndroidApp application, MethodSignature signature) {
     return getMethod(application,
         signature.clazz, signature.returnType, signature.name, signature.parameterTypes);
   }
@@ -588,16 +158,45 @@
    * @param instructions instructions for the method
    * @return the processed method for inspection
    */
-  public DexApplication singleMethodApplication(String returnType, List<String> parameters,
+  public AndroidApp singleMethodApplication(String returnType, List<String> parameters,
       int locals, String... instructions) {
     // Build a one class method.
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
     builder.addStaticMethod(returnType, DEFAULT_METHOD_NAME, parameters, locals, instructions);
 
     // Read the one class method as an application.
-    DexApplication application = buildApplication(builder);
-    assertEquals(1, Iterables.size(application.classes()));
-    return application;
+    return buildApplication(builder);
+  }
+
+  private int getNumberOfClassesForResources(Iterable<Resource> resources) {
+    int count = 0;
+    for (Resource resource : resources) {
+      Collection<String> descriptors = resource.getClassDescriptors();
+      if (descriptors == null) {
+        throw new IllegalStateException("Cannot count classes in application without descriptors.");
+      }
+      count += descriptors.size();
+    }
+    return count;
+  }
+
+  protected int getNumberOfProgramClasses(AndroidApp application) {
+    try {
+      return getNumberOfClassesForResources(application.getClassProgramResources())
+          + getNumberOfClassesForResources(application.getDexProgramResources());
+    } catch (IOException e) {
+      return -1;
+    }
+  }
+
+  protected AppInfo getAppInfo(AndroidApp application) {
+    try {
+      DexApplication dexApplication = new ApplicationReader(application, new InternalOptions(),
+          new Timing("SmaliTest.getAppInfo")).read();
+      return new AppInfo(dexApplication);
+    } catch (IOException | ExecutionException e) {
+      throw new RuntimeException(e);
+    }
   }
 
   /**
@@ -616,30 +215,29 @@
     InternalOptions options = new InternalOptions();
 
     // Build a one class application.
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         returnType, parameters, locals, instructions);
 
     // Process the application with R8.
-    DexApplication processdApplication = processApplication(application, options);
-    assertEquals(1, Iterables.size(processdApplication.classes()));
+    AndroidApp processdApplication = processApplication(application);
+    assertEquals(1, getNumberOfProgramClasses(processdApplication));
 
     // Return the processed method for inspection.
     return getMethod(
         processdApplication, DEFAULT_CLASS_NAME, returnType, DEFAULT_METHOD_NAME, parameters);
   }
 
-  public String runArt(DexApplication application, InternalOptions options)
-      throws DexOverflowException {
-    return runArt(application, options, DEFAULT_MAIN_CLASS_NAME);
+  public String runArt(AndroidApp application) throws DexOverflowException {
+    return runArt(application, DEFAULT_MAIN_CLASS_NAME);
   }
 
-  public String runArt(DexApplication application, InternalOptions options, String mainClass)
+
+  public String runArt(AndroidApp application, String mainClass)
       throws DexOverflowException {
     try {
-      AndroidApp app = writeDex(application, options);
       Path out = temp.getRoot().toPath().resolve("run-art-input.zip");
       // TODO(sgjesse): Pass in a unique temp directory for each run.
-      app.writeToZip(out, OutputMode.Indexed);
+      application.writeToZip(out, OutputMode.Indexed);
       return ToolHelper.runArtNoVerificationErrors(out.toString(), mainClass);
     } catch (IOException e) {
       throw new RuntimeException(e);
@@ -654,36 +252,15 @@
     }
   }
 
-  public void runDex2Oat(DexApplication application, InternalOptions options)
+  public void runDex2Oat(AndroidApp application)
       throws DexOverflowException {
     try {
-      AndroidApp app = writeDex(application, options);
       Path dexOut = temp.getRoot().toPath().resolve("run-dex2oat-input.zip");
       Path oatFile = temp.getRoot().toPath().resolve("oat-file");
-      app.writeToZip(dexOut, OutputMode.Indexed);
+      application.writeToZip(dexOut, OutputMode.Indexed);
       ToolHelper.runDex2Oat(dexOut, oatFile);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
   }
-
-  public AndroidApp writeDex(DexApplication application, InternalOptions options)
-      throws DexOverflowException {
-    try {
-      AndroidAppOutputSink compatSink = new AndroidAppOutputSink();
-      R8.writeApplication(
-          Executors.newSingleThreadExecutor(),
-          application,
-          compatSink,
-          null,
-          NamingLens.getIdentityLens(),
-          null,
-          options);
-      compatSink.close();
-      return compatSink.build();
-    } catch (ExecutionException | IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
 }
diff --git a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
deleted file mode 100644
index 3fc329c..0000000
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ /dev/null
@@ -1,570 +0,0 @@
-// Copyright (c) 2016, 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.smali;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.code.Const;
-import com.android.tools.r8.code.Const4;
-import com.android.tools.r8.code.ConstHigh16;
-import com.android.tools.r8.code.IfEq;
-import com.android.tools.r8.code.IfEqz;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.PackedSwitch;
-import com.android.tools.r8.code.SparseSwitch;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexCode;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.jasmin.JasminBuilder;
-import com.android.tools.r8.shaking.FilteredClassPath;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Test;
-
-public class SwitchRewritingTest extends SmaliTestBase {
-
-  private boolean twoCaseWillUsePackedSwitch(int key1, int key2) {
-    assert key1 != key2;
-    return Math.abs((long) key1 - (long) key2) == 1;
-  }
-
-  private boolean some16BitConst(Instruction instruction) {
-    return instruction instanceof Const4
-        || instruction instanceof ConstHigh16
-        || instruction instanceof Const;
-  }
-  private void runSingleCaseDexTest(boolean packed, int key) {
-    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-    String switchInstruction;
-    String switchData;
-    if (packed) {
-      switchInstruction = "packed-switch";
-      switchData = StringUtils.join(
-          "\n",
-          "  :switch_data",
-          "  .packed-switch " + key,
-          "    :case_0",
-          "  .end packed-switch");
-    } else {
-      switchInstruction = "sparse-switch";
-      switchData = StringUtils.join(
-          "\n",
-          "  :switch_data",
-          "  .sparse-switch",
-          "    " + key + " -> :case_0",
-          "  .end sparse-switch");
-    }
-    MethodSignature signature = builder.addStaticMethod(
-        "int",
-        DEFAULT_METHOD_NAME,
-        ImmutableList.of("int"),
-        0,
-        "    " + switchInstruction + " p0, :switch_data",
-        "    const/4 p0, 0x5",
-        "    goto :return",
-        "  :case_0",
-        "    const/4 p0, 0x3",
-        "  :return",
-        "    return p0",
-        switchData);
-
-    builder.addMainMethod(
-        2,
-        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
-        "    const/4             v1, 0",
-        "    invoke-static       { v1 }, LTest;->method(I)I",
-        "    move-result         v1",
-        "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(I)V",
-        "    return-void"
-    );
-
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    DexEncodedMethod method = getMethod(processedApplication, signature);
-    DexCode code = method.getCode().asDexCode();
-
-    if (key == 0) {
-      assertEquals(5, code.instructions.length);
-      assertTrue(code.instructions[0] instanceof IfEqz);
-    } else {
-      assertEquals(6, code.instructions.length);
-      assertTrue(some16BitConst(code.instructions[0]));
-      assertTrue(code.instructions[1] instanceof IfEq);
-    }
-  }
-
-  @Test
-  public void singleCaseDex() {
-    for (boolean packed : new boolean[]{true, false}) {
-      runSingleCaseDexTest(packed, Integer.MIN_VALUE);
-      runSingleCaseDexTest(packed, -1);
-      runSingleCaseDexTest(packed, 0);
-      runSingleCaseDexTest(packed, 1);
-      runSingleCaseDexTest(packed, Integer.MAX_VALUE);
-    }
-  }
-
-  private void runTwoCaseSparseToPackedOrIfsDexTest(int key1, int key2) {
-    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-
-    MethodSignature signature = builder.addStaticMethod(
-        "int",
-        DEFAULT_METHOD_NAME,
-        ImmutableList.of("int"),
-        0,
-        "    sparse-switch p0, :sparse_switch_data",
-        "    const/4 v0, 0x5",
-        "    goto :return",
-        "  :case_1",
-        "    const/4 v0, 0x3",
-        "    goto :return",
-        "  :case_2",
-        "    const/4 v0, 0x4",
-        "  :return",
-        "    return v0",
-        "  :sparse_switch_data",
-        "  .sparse-switch",
-        "    " + key1 + " -> :case_1",
-        "    " + key2 + " -> :case_2",
-        "  .end sparse-switch");
-
-    builder.addMainMethod(
-        2,
-        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
-        "    const/4             v1, 0",
-        "    invoke-static       { v1 }, LTest;->method(I)I",
-        "    move-result         v1",
-        "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(I)V",
-        "    return-void"
-    );
-
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    DexEncodedMethod method = getMethod(processedApplication, signature);
-    DexCode code = method.getCode().asDexCode();
-    if (twoCaseWillUsePackedSwitch(key1, key2)) {
-      assertTrue(code.instructions[0] instanceof PackedSwitch);
-    } else {
-      if (key1 == 0) {
-        assertTrue(code.instructions[0] instanceof IfEqz);
-      } else {
-        // Const instruction before if.
-        assertTrue(code.instructions[1] instanceof IfEq);
-      }
-    }
-  }
-
-  @Test
-  public void twoCaseSparseToPackedOrIfsDex() {
-    for (int delta = 1; delta <= 3; delta++) {
-      runTwoCaseSparseToPackedOrIfsDexTest(0, delta);
-      runTwoCaseSparseToPackedOrIfsDexTest(-delta, 0);
-      runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
-      runTwoCaseSparseToPackedOrIfsDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
-    }
-    runTwoCaseSparseToPackedOrIfsDexTest(-1, 1);
-    runTwoCaseSparseToPackedOrIfsDexTest(-2, 1);
-    runTwoCaseSparseToPackedOrIfsDexTest(-1, 2);
-    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
-    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
-    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
-  }
-
-  private void runLargerSwitchDexTest(int firstKey, int keyStep, int totalKeys,
-      Integer additionalLastKey) throws Exception {
-    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-
-    StringBuilder switchSource = new StringBuilder();
-    StringBuilder targetCode = new StringBuilder();
-    for (int i = 0; i < totalKeys; i++) {
-      String caseLabel = "case_" + i;
-      switchSource.append("    " + (firstKey + i * keyStep) + " -> :" + caseLabel + "\n");
-      targetCode.append("  :" + caseLabel + "\n");
-      targetCode.append("    goto :return\n");
-    }
-    if (additionalLastKey != null) {
-      String caseLabel = "case_" + totalKeys;
-      switchSource.append("    " + additionalLastKey + " -> :" + caseLabel + "\n");
-      targetCode.append("  :" + caseLabel + "\n");
-      targetCode.append("    goto :return\n");
-    }
-
-    MethodSignature signature = builder.addStaticMethod(
-        "void",
-        DEFAULT_METHOD_NAME,
-        ImmutableList.of("int"),
-        0,
-        "    sparse-switch p0, :sparse_switch_data",
-        "    goto :return",
-        targetCode.toString(),
-        "  :return",
-        "    return-void",
-        "  :sparse_switch_data",
-        "  .sparse-switch",
-        switchSource.toString(),
-        "  .end sparse-switch");
-
-    builder.addMainMethod(
-        2,
-        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
-        "    const/4             v1, 0",
-        "    invoke-static       { v1 }, LTest;->method(I)V",
-        "    return-void"
-    );
-
-    InternalOptions options = new InternalOptions();
-    options.verbose = true;
-    options.printTimes = true;
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    DexEncodedMethod method = getMethod(processedApplication, signature);
-    DexCode code = method.getCode().asDexCode();
-    if (keyStep <= 2) {
-      assertTrue(code.instructions[0] instanceof PackedSwitch);
-    } else {
-      assertTrue(code.instructions[0] instanceof SparseSwitch);
-    }
-  }
-
-  @Test
-  public void twoMonsterSparseToPackedDex() throws Exception {
-    runLargerSwitchDexTest(0, 1, 100, null);
-    runLargerSwitchDexTest(0, 2, 100, null);
-    runLargerSwitchDexTest(0, 3, 100, null);
-    runLargerSwitchDexTest(100, 100, 100, null);
-    runLargerSwitchDexTest(-10000, 100, 100, null);
-    runLargerSwitchDexTest(-10000, 200, 100, 10000);
-    runLargerSwitchDexTest(
-        Integer.MIN_VALUE, (int) ((-(long)Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE);
-
-    // TODO(63090177): Currently this is commented out as R8 gets really slow for large switches.
-    // runLargerSwitchDexTest(0, 1, Constants.U16BIT_MAX, null);
-  }
-
-  private void runSingleCaseJarTest(boolean packed, int key) throws Exception {
-    JasminBuilder builder = new JasminBuilder();
-    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-
-    String switchCode;
-    if (packed) {
-      switchCode = StringUtils.join(
-          "\n",
-          "    tableswitch " + key,
-          "      case_0",
-          "      default : case_default");
-    } else {
-      switchCode = StringUtils.join(
-          "\n",
-          "    lookupswitch",
-          "      " + key + " : case_0",
-          "      default : case_default");
-    }
-
-    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
-        "    .limit stack 1",
-        "    .limit locals 1",
-        "    iload 0",
-        switchCode,
-        "  case_0:",
-        "    iconst_3",
-        "    goto return_",
-        "  case_default:",
-        "    ldc 5",
-        "  return_:",
-        "    ireturn");
-
-    clazz.addMainMethod(
-        "    .limit stack 2",
-        "    .limit locals 1",
-        "    getstatic java/lang/System/out Ljava/io/PrintStream;",
-        "    ldc 2",
-        "    invokestatic Test/test(I)I",
-        "    invokevirtual java/io/PrintStream/print(I)V",
-        "    return");
-
-    DexApplication app = builder.read();
-    app = ToolHelper.optimizeWithR8(app, new InternalOptions());
-
-    MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
-    DexEncodedMethod method = getMethod(app, signature);
-    DexCode code = method.getCode().asDexCode();
-    if (key == 0) {
-      assertEquals(5, code.instructions.length);
-      assertTrue(code.instructions[0] instanceof IfEqz);
-    } else {
-      assertEquals(6, code.instructions.length);
-      assertTrue(code.instructions[1] instanceof IfEq);
-    }
-  }
-
-  @Test
-  public void singleCaseJar() throws Exception {
-    for (boolean packed : new boolean[]{true, false}) {
-      runSingleCaseJarTest(packed, Integer.MIN_VALUE);
-      runSingleCaseJarTest(packed, -1);
-      runSingleCaseJarTest(packed, 0);
-      runSingleCaseJarTest(packed, 1);
-      runSingleCaseJarTest(packed, Integer.MAX_VALUE);
-    }
-  }
-
-  private void runTwoCaseSparseToPackedJarTest(int key1, int key2) throws Exception {
-    JasminBuilder builder = new JasminBuilder();
-    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-
-    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
-        "    .limit stack 1",
-        "    .limit locals 1",
-        "    iload 0",
-        "    lookupswitch",
-        "      " + key1 + " : case_1",
-        "      " + key2 + " : case_2",
-        "      default : case_default",
-        "  case_1:",
-        "    iconst_3",
-        "    goto return_",
-        "  case_2:",
-        "    iconst_4",
-        "    goto return_",
-        "  case_default:",
-        "    iconst_5",
-        "  return_:",
-        "    ireturn");
-
-    clazz.addMainMethod(
-        "    .limit stack 2",
-        "    .limit locals 1",
-        "    getstatic java/lang/System/out Ljava/io/PrintStream;",
-        "    ldc 2",
-        "    invokestatic Test/test(I)I",
-        "    invokevirtual java/io/PrintStream/print(I)V",
-        "    return");
-
-    DexApplication app = builder.read();
-    app = ToolHelper.optimizeWithR8(app, new InternalOptions());
-
-    MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
-    DexEncodedMethod method = getMethod(app, signature);
-    DexCode code = method.getCode().asDexCode();
-    if (twoCaseWillUsePackedSwitch(key1, key2)) {
-      assertTrue(code.instructions[0] instanceof PackedSwitch);
-    } else {
-      if (key1 == 0) {
-        assertTrue(code.instructions[0] instanceof IfEqz);
-      } else {
-        // Const instruction before if.
-        assertTrue(code.instructions[1] instanceof IfEq);
-      }
-    }
-  }
-
-  @Test
-  public void twoCaseSparseToPackedJar() throws Exception {
-    for (int delta = 1; delta <= 3; delta++) {
-      runTwoCaseSparseToPackedJarTest(0, delta);
-      runTwoCaseSparseToPackedJarTest(-delta, 0);
-      runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
-      runTwoCaseSparseToPackedJarTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
-    }
-    runTwoCaseSparseToPackedJarTest(-1, 1);
-    runTwoCaseSparseToPackedJarTest(-2, 1);
-    runTwoCaseSparseToPackedJarTest(-1, 2);
-    runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
-    runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
-    runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
-  }
-
-  private void runLargerSwitchJarTest(int firstKey, int keyStep, int totalKeys,
-      Integer additionalLastKey) throws Exception {
-    JasminBuilder builder = new JasminBuilder();
-    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-
-    StringBuilder switchSource = new StringBuilder();
-    StringBuilder targetCode = new StringBuilder();
-    for (int i = 0; i < totalKeys; i++) {
-      String caseLabel = "case_" + i;
-      switchSource.append("      " + (firstKey + i * keyStep) + " : " + caseLabel + "\n");
-      targetCode.append("  " + caseLabel + ":\n");
-      targetCode.append("    ldc " + i + "\n");
-      targetCode.append("    goto return_\n");
-    }
-    if (additionalLastKey != null) {
-      String caseLabel = "case_" + totalKeys;
-      switchSource.append("      " + additionalLastKey + " : " + caseLabel + "\n");
-      targetCode.append("  " + caseLabel + ":\n");
-      targetCode.append("    ldc " + totalKeys + "\n");
-      targetCode.append("    goto return_\n");
-    }
-
-    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
-        "    .limit stack 1",
-        "    .limit locals 1",
-        "    iload 0",
-        "  lookupswitch",
-        switchSource.toString(),
-        "      default : case_default",
-        targetCode.toString(),
-        "  case_default:",
-        "    iconst_5",
-        "  return_:",
-        "    ireturn");
-
-    clazz.addMainMethod(
-        "    .limit stack 2",
-        "    .limit locals 1",
-        "    getstatic java/lang/System/out Ljava/io/PrintStream;",
-        "    ldc 2",
-        "    invokestatic Test/test(I)I",
-        "    invokevirtual java/io/PrintStream/print(I)V",
-        "    return");
-
-    DexApplication app = builder.read();
-    app = ToolHelper.optimizeWithR8(app, new InternalOptions());
-
-    MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
-    DexEncodedMethod method = getMethod(app, signature);
-    DexCode code = method.getCode().asDexCode();
-    int packedSwitchCount = 0;
-    int sparseSwitchCount = 0;
-    for (Instruction instruction : code.instructions) {
-      if (instruction instanceof PackedSwitch) {
-        packedSwitchCount++;
-      }
-      if (instruction instanceof SparseSwitch) {
-        sparseSwitchCount++;
-      }
-    }
-    if (keyStep <= 2) {
-      assertEquals(1, packedSwitchCount);
-      assertEquals(0, sparseSwitchCount);
-    } else {
-      assertEquals(0, packedSwitchCount);
-      assertEquals(1, sparseSwitchCount);
-    }
-  }
-
-  @Test
-  public void largerSwitchJar() throws Exception {
-    runLargerSwitchJarTest(0, 1, 100, null);
-    runLargerSwitchJarTest(0, 2, 100, null);
-    runLargerSwitchJarTest(0, 3, 100, null);
-    runLargerSwitchJarTest(100, 100, 100, null);
-    runLargerSwitchJarTest(-10000, 100, 100, null);
-    runLargerSwitchJarTest(-10000, 200, 100, 10000);
-    runLargerSwitchJarTest(
-        Integer.MIN_VALUE, (int) ((-(long)Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE);
-
-    // This is the maximal value possible with Jasmin with the generated code above. It depends on
-    // the source, so making smaller source can raise this limit. However we never get close to the
-    // class file max.
-    runLargerSwitchJarTest(0, 1, 5503, null);
-  }
-
-  private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs,
-      int expectedPackedSwitches, int expectedSparceSwitches) throws Exception {
-    JasminBuilder builder = new JasminBuilder();
-    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-
-    StringBuilder x = new StringBuilder();
-    StringBuilder y = new StringBuilder();
-    for (Integer key : keys) {
-      x.append(key).append(" : case_").append(key).append("\n");
-      y.append("case_").append(key).append(":\n");
-      y.append("    ldc ").append(key).append("\n");
-      y.append("    goto return_\n");
-    }
-
-    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
-        "    .limit stack 1",
-        "    .limit locals 1",
-        "    iload_0",
-        "    lookupswitch",
-        x.toString(),
-        "      default : case_default",
-        y.toString(),
-        "  case_default:",
-        "    ldc " + defaultValue,
-        "  return_:",
-        "    ireturn");
-
-    // Add the Jasmin class and a class from Java source with the main method.
-    AndroidApp.Builder appBuilder = AndroidApp.builder();
-    appBuilder.addClassProgramData(builder.buildClasses());
-    appBuilder.addProgramFiles(FilteredClassPath
-        .unfiltered(ToolHelper.getClassFileForTestClass(CheckSwitchInTestClass.class)));
-    AndroidApp app = compileWithR8(appBuilder.build());
-
-    DexInspector inspector = new DexInspector(app);
-    MethodSubject method = inspector.clazz("Test").method("int", "test", ImmutableList.of("int"));
-    DexCode code = method.getMethod().getCode().asDexCode();
-
-    int packedSwitches = 0;
-    int sparseSwitches = 0;
-    int ifs = 0;
-    for (Instruction instruction : code.instructions) {
-      if (instruction instanceof PackedSwitch) {
-        packedSwitches++;
-      }
-      if (instruction instanceof SparseSwitch) {
-        sparseSwitches++;
-      }
-      if (instruction instanceof IfEq || instruction instanceof IfEqz) {
-        ifs++;
-      }
-    }
-
-    assertEquals(expectedPackedSwitches, packedSwitches);
-    assertEquals(expectedSparceSwitches, sparseSwitches);
-    assertEquals(expectedIfs, ifs);
-
-    // Run the code
-    List<String> args = keys.stream().map(Object::toString).collect(Collectors.toList());
-    args.add(Integer.toString(defaultValue));
-    runOnArt(app, CheckSwitchInTestClass.class, args);
-  }
-
-  @Test
-  public void convertCasesToIf() throws Exception {
-    // Switches that are completely converted to ifs.
-    runConvertCasesToIf(ImmutableList.of(0, 1000), -100, 2, 0, 0);
-    runConvertCasesToIf(ImmutableList.of(0, 1000, 2000), -100, 3, 0, 0);
-    runConvertCasesToIf(ImmutableList.of(0, 1000, 2000, 3000), -100, 4, 0, 0);
-
-    // Switches that are completely converted to ifs and one switch.
-    runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
-    runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1, 1, 0);
-    runConvertCasesToIf(ImmutableList.of(
-        Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
-    runConvertCasesToIf(ImmutableList.of(
-        1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1, 1, 0);
-    runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 1, 0);
-    runConvertCasesToIf(ImmutableList.of(
-        Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2, 1, 0);
-
-    // Switches that are completely converted to ifs and two switches.
-    runConvertCasesToIf(ImmutableList.of(
-        0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 0, 2, 0);
-    runConvertCasesToIf(ImmutableList.of(
-        -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 1, 2, 0);
-    runConvertCasesToIf(ImmutableList.of(
-        -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 2, 0);
-
-    // Switches that are completely converted two switches (one sparse and one packed).
-    runConvertCasesToIf(ImmutableList.of(
-        -1000, -900, -800, -700, -600, -500, -400, -300,
-        1000, 1001, 1002, 1003, 1004,
-        2000, 2100, 2200, 2300, 2400, 2500), -100, 0, 1, 1);
-  }
-}
\ No newline at end of file
diff --git a/tests/api_usage_sample.jar b/tests/api_usage_sample.jar
new file mode 100644
index 0000000..b8b9d66
--- /dev/null
+++ b/tests/api_usage_sample.jar
Binary files differ
diff --git a/tools/archive.py b/tools/archive.py
index b88eb8d..20d057c 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -4,6 +4,7 @@
 # BSD-style license that can be found in the LICENSE file.
 
 import gradle
+import create_maven_release
 import d8
 import os
 import r8
@@ -42,11 +43,11 @@
     if 'origin/master' in branches:
       raise Exception('We are seeing origin/master in a commit that '
                       'don\'t have -dev in version')
-    return False;
+    return False
   if not 'origin/master' in branches:
       raise Exception('We are not seeing origin/master '
                       'in a commit that have -dev in version')
-  return True;
+  return True
 
 def GetStorageDestination(storage_prefix, version, file_name, is_master):
   # We archive master commits under raw/master instead of directly under raw
@@ -64,27 +65,33 @@
 def Main():
   if not 'BUILDBOT_BUILDERNAME' in os.environ:
     raise Exception('You are not a bot, don\'t archive builds')
+  # Ensure all archived artifacts has been built before archiving.
+  gradle.RunGradle([utils.D8, utils.R8, utils.COMPATDX, utils.COMPATPROGUARD])
+  create_maven_release.main(['--jar', utils.R8_JAR, "--out", utils.LIBS])
+
   version = GetVersion()
-  is_master = True #IsMaster(version)
+  is_master = IsMaster(version)
   if is_master:
     # On master we use the git hash to archive with
     print 'On master, using git hash for archiving'
     version = GetGitHash()
 
-  # Ensure all archived artifacts has been built before archiving.
-  gradle.RunGradle([utils.D8, utils.R8, utils.COMPATDX, utils.COMPATPROGUARD])
-
   with utils.TempDir() as temp:
     version_file = os.path.join(temp, 'r8-version.properties')
     with open(version_file,'w') as version_writer:
       version_writer.write('version.sha=' + GetGitHash() + '\n')
-      version_writer.write('releaser=go/r8bot (' + os.environ.get('BUILDBOT_SLAVENAME') + ')\n')
+      version_writer.write(
+          'releaser=go/r8bot (' + os.environ.get('BUILDBOT_SLAVENAME') + ')\n')
       version_writer.write('version-file.version.code=1\n')
 
-    for jar in [utils.D8_JAR, utils.R8_JAR, utils.COMPATDX_JAR, utils.COMPATPROGUARD_JAR]:
-      file_name = os.path.basename(jar)
+    for file in [utils.D8_JAR,
+                 utils.R8_JAR,
+                 utils.COMPATDX_JAR,
+                 utils.COMPATPROGUARD_JAR,
+                 utils.MAVEN_ZIP]:
+      file_name = os.path.basename(file)
       tagged_jar = os.path.join(temp, file_name)
-      shutil.copyfile(jar, tagged_jar)
+      shutil.copyfile(file, tagged_jar)
       with zipfile.ZipFile(tagged_jar, 'a') as zip:
         zip.write(version_file, os.path.basename(version_file))
       destination = GetUploadDestination(version, file_name, is_master)
diff --git a/tools/update_prebuilds_in_android.py b/tools/update_prebuilds_in_android.py
new file mode 100755
index 0000000..6789f18
--- /dev/null
+++ b/tools/update_prebuilds_in_android.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+# 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.
+
+import argparse
+import gradle
+import os
+import sys
+import utils
+
+from shutil import copyfile
+
+def parse_arguments():
+  parser = argparse.ArgumentParser(
+      description = 'Build and copy jars to an Android tree.')
+  parser.add_argument('android_root', nargs=1,
+      help='Android checkout root.')
+  return parser.parse_args()
+
+def Main():
+  args = parse_arguments()
+  targets = ['r8', 'd8', 'compatdx', 'compatproguard']
+  gradle.RunGradle(targets)
+  for target in targets:
+    src = os.path.join(utils.REPO_ROOT, 'build', 'libs', target + '.jar')
+    dest = os.path.join(
+        args.android_root[0], 'prebuilts', 'r8', target + '-master.jar')
+    print 'Copying: ' + src + ' -> ' + dest
+    copyfile(src, dest)
+
+if __name__ == '__main__':
+  sys.exit(Main())
diff --git a/tools/utils.py b/tools/utils.py
index 7d198fa..40a62b8 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -19,6 +19,7 @@
     'dexsegments.jar')
 DEX_SEGMENTS_RESULT_PATTERN = re.compile('- ([^:]+): ([0-9]+)')
 LIBS = os.path.join(REPO_ROOT, 'build', 'libs')
+MAVEN_ZIP = os.path.join(LIBS, 'r8.zip')
 
 D8 = 'd8'
 R8 = 'r8'