Merge "Decrease pressure on register allocator for /lit instructions"
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
new file mode 100644
index 0000000..2207e6f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -0,0 +1,21 @@
+// 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 org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfGoto extends CfInstruction {
+
+  private final CfLabel target;
+
+  public CfGoto(CfLabel target) {
+    this.target = target;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    visitor.visitJumpInsn(Opcodes.GOTO, target.getLabel());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
new file mode 100644
index 0000000..b8397e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -0,0 +1,47 @@
+// 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 com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.ValueType;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfIf extends CfInstruction {
+
+  private final If.Type kind;
+  private final ValueType type;
+  private final CfLabel target;
+
+  public CfIf(If.Type kind, ValueType type, CfLabel target) {
+    this.kind = kind;
+    this.type = type;
+    this.target = target;
+  }
+
+  private int getOpcode() {
+    switch (kind) {
+      case EQ:
+        return type.isObject() ? Opcodes.IFNULL : Opcodes.IFEQ;
+      case GE:
+        return Opcodes.IFGE;
+      case GT:
+        return Opcodes.IFGT;
+      case LE:
+        return Opcodes.IFLE;
+      case LT:
+        return Opcodes.IFLT;
+      case NE:
+        return type.isObject() ? Opcodes.IFNONNULL : Opcodes.IFNE;
+      default:
+        throw new Unreachable("Unexpected type " + type);
+    }
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    visitor.visitJumpInsn(getOpcode(), target.getLabel());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
new file mode 100644
index 0000000..9c6560d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -0,0 +1,24 @@
+// 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 org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfLabel extends CfInstruction {
+
+  private Label label = null;
+
+  public Label getLabel() {
+    if (label == null) {
+      label = new Label();
+    }
+    return label;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    visitor.visitLabel(getLabel());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
new file mode 100644
index 0000000..a0d1f6a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
@@ -0,0 +1,32 @@
+// 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 com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CfTryCatch {
+  public final CfLabel start;
+  public final CfLabel end;
+  public final List<DexType> guards;
+  public final List<CfLabel> targets;
+
+  public CfTryCatch(
+      CfLabel start,
+      CfLabel end,
+      CatchHandlers<BasicBlock> handlers,
+      CfBuilder builder) {
+    this.start = start;
+    this.end = end;
+    guards = handlers.getGuards();
+    targets = new ArrayList<>(handlers.getAllTargets().size());
+    for (BasicBlock block : handlers.getAllTargets()) {
+      targets.add(builder.getLabel(block));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/code/ConstClass.java b/src/main/java/com/android/tools/r8/code/ConstClass.java
index ef8b257..f140022 100644
--- a/src/main/java/com/android/tools/r8/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/code/ConstClass.java
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry registry) {
-    registry.registerConstClass(getType());
+    registry.registerTypeReference(getType());
   }
 
   public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index a2caf1d..ad0f549 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -84,6 +84,9 @@
         populateSuperType(map, inter, baseClass, definitions);
         inter.addInterfaceSubtype(holder);
       }
+      if (holderClass.isInterface()) {
+        holder.tagAsInteface();
+      }
     } else {
       if (!baseClass.isLibraryClass()) {
         missingClasses.add(holder);
@@ -193,18 +196,28 @@
     return holder.accessFlags.isAbstract();
   }
 
+  private boolean holderIsInterface(Descriptor<?, ?> desc) {
+    DexClass holder = definitionFor(desc.getHolder());
+    return holder.accessFlags.isInterface();
+  }
+
   // For mapping invoke interface instruction to target methods.
   public Set<DexEncodedMethod> lookupInterfaceTargets(DexMethod method) {
-    Set<DexEncodedMethod> result = new HashSet<>();
     Set<DexType> set = subtypes(method.holder);
-    if (set != null) {
-      for (DexType type : set) {
-        DexClass clazz = definitionFor(type);
-        if (!clazz.isInterface()) {
-          DexEncodedMethod targetMethod = lookupVirtualTarget(type, method);
-          if (targetMethod != null) {
-            result.add(targetMethod);
-          }
+    if (set == null) {
+      return Collections.emptySet();
+    }
+    assert holderIsInterface(method);
+    Set<DexEncodedMethod> result = new HashSet<>();
+    for (DexType type : set) {
+      DexClass clazz = definitionFor(type);
+      // Default methods are looked up when looking at a specific subtype that does not
+      // override them, so we ignore interfaces here. Otherwise, we would look up default methods
+      // that are factually never used.
+      if (!clazz.isInterface()) {
+        DexEncodedMethod targetMethod = lookupVirtualTarget(type, method);
+        if (targetMethod != null) {
+          result.add(targetMethod);
         }
       }
     }
@@ -212,9 +225,8 @@
   }
 
   public DexEncodedMethod lookupSingleInterfaceTarget(DexMethod method) {
-    assert method != null;
     DexClass holder = definitionFor(method.holder);
-    if ((holder == null) || holder.isLibraryClass()) {
+    if ((holder == null) || holder.isLibraryClass() || !holder.accessFlags.isInterface()) {
       return null;
     }
     DexEncodedMethod result = null;
@@ -222,6 +234,9 @@
     if (set != null) {
       for (DexType type : set) {
         DexClass clazz = definitionFor(type);
+        // Default methods are looked up when looking at a specific subtype that does not
+        // override them, so we ignore interfaces here. Otherwise, we would look up default methods
+        // that are factually never used.
         if (!clazz.isInterface()) {
           DexEncodedMethod t = lookupVirtualTarget(type, method);
           if (t != null) {
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index e1f9c63..7c76969 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -5,23 +5,32 @@
 
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.List;
+import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
 
 public class CfCode extends Code {
 
   private final int maxStack;
   private final int maxLocals;
   private final List<CfInstruction> instructions;
+  private final List<CfTryCatch> tryCatchRanges;
 
-  public CfCode(int maxStack, int maxLocals, List<CfInstruction> instructions) {
+  public CfCode(
+      int maxStack,
+      int maxLocals,
+      List<CfInstruction> instructions,
+      List<CfTryCatch> tryCatchRanges) {
     this.maxStack = maxStack;
     this.maxLocals = maxLocals;
     this.instructions = instructions;
+    this.tryCatchRanges = tryCatchRanges;
   }
 
   @Override
@@ -40,6 +49,21 @@
     }
     visitor.visitEnd();
     visitor.visitMaxs(maxStack, maxLocals);
+    for (CfTryCatch tryCatch : tryCatchRanges) {
+      Label start = tryCatch.start.getLabel();
+      Label end = tryCatch.end.getLabel();
+      for (int i = 0; i < tryCatch.guards.size(); i++) {
+        DexType guard = tryCatch.guards.get(i);
+        Label target = tryCatch.targets.get(i).getLabel();
+        visitor.visitTryCatchBlock(
+            start,
+            end,
+            target,
+            guard == DexItemFactory.catchAllType
+                ? null
+                : Type.getType(guard.toDescriptorString()).getInternalName());
+      }
+    }
   }
 
   @Override
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 5aaf308..d4deb54 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -71,23 +71,27 @@
     }
   }
 
-  public void addDirectSubtype(DexType type) {
+  void addDirectSubtype(DexType type) {
     assert hierarchyLevel != UNKNOWN_LEVEL;
     ensureDirectSubTypeSet();
     directSubtypes.add(type);
     type.setLevel(hierarchyLevel + 1);
   }
 
-  public void tagAsSubtypeRoot() {
+  void tagAsSubtypeRoot() {
     setLevel(ROOT_LEVEL);
   }
 
+  void tagAsInteface() {
+    setLevel(INTERFACE_LEVEL);
+  }
+
   public boolean isInterface() {
     assert isClassType() && hierarchyLevel != UNKNOWN_LEVEL;
     return hierarchyLevel == INTERFACE_LEVEL;
   }
 
-  public void addInterfaceSubtype(DexType type) {
+  void addInterfaceSubtype(DexType type) {
     // Interfaces all inherit from java.lang.Object. However, we assign a special level to
     // identify them later on.
     setLevel(INTERFACE_LEVEL);
@@ -156,7 +160,7 @@
 
   /**
    * Apply the given function to all classes that directly extend this class.
-   *
+   * <p>
    * If this class is an interface, then this method will visit all sub-interfaces. This deviates
    * from the dex-file encoding, where subinterfaces "implement" their super interfaces. However,
    * it is consistent with the source language.
@@ -185,7 +189,7 @@
 
   /**
    * Apply the given function to all classes that directly implement this interface.
-   *
+   * <p>
    * The implementation does not consider how the hierarchy is encoded in the dex file, where
    * interfaces "implement" their super interfaces. Instead it takes the view of the source
    * language, where interfaces "extend" their superinterface.
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index f6736fa..78b2f56 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -65,8 +65,4 @@
         throw new AssertionError();
     }
   }
-
-  public boolean registerConstClass(DexType type) {
-    return registerTypeReference(type);
-  }
 }
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 eef666b..4b88706 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,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.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;
@@ -94,4 +97,14 @@
   public Goto asGoto() {
     return this;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    // Nothing to do.
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfGoto(builder.getLabel(getTarget())));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 95dd4ba..934d3db 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -402,7 +402,8 @@
             assert instruction.isDebugInstruction()
                 || instruction.isJumpInstruction()
                 || instruction.isConstInstruction()
-                || instruction.isNewArrayFilledData();
+                || instruction.isNewArrayFilledData()
+                || instruction.isStore();
           }
         }
       }
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 d2e16f6..8cdf209 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,7 +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.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;
@@ -186,4 +189,15 @@
   public If asIf() {
     return this;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    stack.loadInValues(this, it);
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    assert inValues.size() == 1;
+    builder.add(new CfIf(type, inValues.get(0).type, builder.getLabel(getTrueTarget())));
+  }
 }
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 cdcf070..eeee6f8 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
@@ -938,6 +938,22 @@
     return null;
   }
 
+  public boolean isStore() {
+    return false;
+  }
+
+  public Store asStore() {
+    return null;
+  }
+
+  public boolean isLoad() {
+    return false;
+  }
+
+  public Load asLoad() {
+    return null;
+  }
+
   public boolean canBeFolded() {
     return false;
   }
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 734b2dd..401a880 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
@@ -8,6 +8,7 @@
 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 {
@@ -17,6 +18,16 @@
   }
 
   @Override
+  public boolean isLoad() {
+    return true;
+  }
+
+  @Override
+  public Load asLoad() {
+    return this;
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return true;
   }
@@ -46,4 +57,9 @@
     Value value = inValues.get(0);
     builder.add(new CfLoad(value.outType(), builder.getLocalRegister(value)));
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    // 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 0f72182..2b419fd 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
@@ -6,6 +6,8 @@
 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;
@@ -61,7 +63,7 @@
 
   @Override
   public boolean canBeDeadCode(IRCode code, InternalOptions options) {
-    return !options.debug;
+    return !options.debug && !options.outputClassFiles;
   }
 
   @Override
@@ -69,4 +71,18 @@
     // TODO(64432527): Revisit this constraint.
     return Constraint.NEVER;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    if (outValue.isUsed()) {
+      stack.storeOutValue(this, it);
+    } else {
+      stack.popOutValue(outValue.type, this, it);
+    }
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    // Nothing to do. The exception is implicitly pushed on the stack.
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 33d5557..7dd62ae 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.utils.InternalOptions;
 
 public class Pop extends Instruction {
 
@@ -45,4 +46,10 @@
   public void buildCf(CfBuilder builder) {
     builder.add(new CfPop(inValues.get(0).type));
   }
+
+  @Override
+  public boolean canBeDeadCode(IRCode code, InternalOptions options) {
+    // Pop cannot be dead code as it modifies the stack height.
+    return false;
+  }
 }
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 f670621..3bf1417 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
@@ -5,12 +5,16 @@
 
 public class StackValue extends Value {
 
-  public StackValue(ValueType type) {
+  private final int height;
+
+  public StackValue(ValueType type, int height) {
     super(Value.UNDEFINED_NUMBER, type, null);
+    this.height = height;
+    assert height >= 0;
   }
 
   @Override
   public String toString() {
-    return "s";
+    return "s" + height;
   }
 }
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 ddc4f50..bc1e419 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
@@ -8,7 +8,10 @@
 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;
 
 public class Store extends Instruction {
 
@@ -17,6 +20,16 @@
   }
 
   @Override
+  public boolean isStore() {
+    return true;
+  }
+
+  @Override
+  public Store asStore() {
+    return this;
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return true;
   }
@@ -45,4 +58,14 @@
   public void buildCf(CfBuilder builder) {
     builder.add(new CfStore(outType(), builder.getLocalRegister(outValue)));
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, StackHelper stack) {
+    // Nothing to do. This is only hit because loads and stores are insert for phis.
+  }
+
+  @Override
+  public boolean canBeDeadCode(IRCode code, InternalOptions options) {
+    return !(outValue instanceof FixedLocal);
+  }
 }
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 aed20acb..f07b61e 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
@@ -4,6 +4,8 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.CfCode;
@@ -11,6 +13,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 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;
@@ -20,7 +23,9 @@
 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;
@@ -28,43 +33,64 @@
 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.objects.Reference2IntArrayMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
 
 public class CfBuilder {
 
   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 Reference2IntMap<Value> argumentRegisters;
-  private int maxLocals;
+  private Reference2IntMap<Value> registers;
+  private Map<BasicBlock, CfLabel> labels;
+
+  /**
+   * Value that represents a shared physical location defined by the phi value.
+   *
+   * This value is introduced to represent the store instructions used to unify the location of
+   * in-flowing values to phi's. After introducing this fixed location the graph is no longer in
+   * SSA since the fixed location signifies a place that can be written to from multiple places.
+   */
+  public static class FixedLocal extends Value {
+
+    private final Phi phi;
+
+    public FixedLocal(Phi phi) {
+      super(phi.getNumber(), phi.outType(), phi.getLocalInfo());
+      this.phi = phi;
+    }
+
+    @Override
+    public boolean isConstant() {
+      return false;
+    }
+
+    @Override
+    public String toString() {
+      return "fixed:v" + phi.getNumber();
+    }
+  }
 
   public static class StackHelper {
 
+    private int currentStackHeight = 0;
+
     public void loadInValues(Instruction instruction, InstructionListIterator it) {
+      int topOfStack = currentStackHeight;
       it.previous();
-      for (int i = 0; i < instruction.inValues().size(); i++) {
-        Value value = instruction.inValues().get(i);
-        StackValue stackValue = new StackValue(value.outType());
-        Instruction load;
-        if (value.isConstant()) {
-          ConstInstruction constant = value.getConstInstruction();
-          if (constant.isConstNumber()) {
-            load = new ConstNumber(stackValue, constant.asConstNumber().getRawValue());
-          } else if (constant.isConstString()) {
-            load = new ConstString(stackValue, constant.asConstString().getValue());
-          } else if (constant.isConstClass()) {
-            load = new ConstClass(stackValue, constant.asConstClass().getValue());
-          } else {
-            throw new Unreachable("Unexpected constant value: " + value);
-          }
-        } else {
-          load = new Load(stackValue, value);
-        }
-        add(load, instruction, it);
+      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);
       }
@@ -72,26 +98,61 @@
     }
 
     public void storeOutValue(Instruction instruction, InstructionListIterator it) {
-      if (instruction.isOutConstant()) {
+      if (instruction.outValue() instanceof StackValue) {
+        assert instruction.isConstInstruction();
         return;
       }
-      StackValue newOutValue = new StackValue(instruction.outType());
+      StackValue newOutValue = new StackValue(instruction.outType(), currentStackHeight);
       Value oldOutValue = instruction.swapOutValue(newOutValue);
       add(new Store(oldOutValue, newOutValue), instruction, it);
     }
 
     public void popOutValue(ValueType type, Instruction instruction, InstructionListIterator it) {
-      StackValue newOutValue = new StackValue(type);
+      StackValue newOutValue = new StackValue(type, currentStackHeight);
       instruction.swapOutValue(newOutValue);
       add(new Pop(newOutValue), instruction, it);
     }
 
+    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) {
-      newInstruction.setBlock(existingInstruction.getBlock());
-      newInstruction.setPosition(existingInstruction.getPosition());
+      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) {
@@ -101,6 +162,7 @@
 
   public Code build(CodeRewriter rewriter, InternalOptions options) {
     try {
+      splitExceptionalBlocks();
       loadStoreInsertion();
       DeadCodeRemover.removeDeadCode(code, rewriter, options);
       removeUnneededLoadsAndStores();
@@ -113,8 +175,55 @@
     }
   }
 
+  // Split all blocks with throwing instructions and exceptional edges such that any non-throwing
+  // instructions that might define values prior to the throwing exception are excluded from the
+  // try-catch range. Failure to do so will result in code that does not verify on the JVM.
+  private void splitExceptionalBlocks() {
+    ListIterator<BasicBlock> it = code.listIterator();
+    while (it.hasNext()) {
+      BasicBlock block = it.next();
+      if (!block.hasCatchHandlers()) {
+        continue;
+      }
+      int size = block.getInstructions().size();
+      boolean isThrow = block.exit().isThrow();
+      if ((isThrow && size == 1) || (!isThrow && size == 2)) {
+        // Fast-path to avoid processing blocks with just a single throwing instruction.
+        continue;
+      }
+      InstructionListIterator instructions = block.listIterator();
+      boolean hasOutValues = false;
+      while (instructions.hasNext()) {
+        Instruction instruction = instructions.next();
+        if (instruction.instructionTypeCanThrow()) {
+          break;
+        }
+        hasOutValues |= instruction.outValue() != null;
+      }
+      if (hasOutValues) {
+        instructions.previous();
+        instructions.split(code, it);
+      }
+    }
+  }
+
   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()) {
@@ -131,6 +240,10 @@
       InstructionListIterator it = block.listIterator();
       while (it.hasNext()) {
         Instruction store = it.next();
+        // Eliminate unneeded loads of stores:
+        //  v <- store si
+        //  si <- load v
+        // where |users(v)| == 1 (ie, the load is the only user)
         if (store instanceof Store && store.outValue().numberOfAllUsers() == 1) {
           Instruction load = it.peekNext();
           if (load instanceof Load && load.inValues().get(0) == store.outValue()) {
@@ -154,60 +267,115 @@
   private void allocateLocalRegisters() {
     // TODO(zerny): Allocate locals based on live ranges.
     InstructionIterator it = code.instructionIterator();
-    argumentRegisters = new Reference2IntArrayMap<>(
-        method.method.proto.parameters.values.length + (method.accessFlags.isStrict() ? 0 : 1));
-    int argumentRegister = 0;
-    int maxRegister = -1;
+    registers = new Reference2IntOpenHashMap<>();
+    int nextFreeRegister = 0;
     while (it.hasNext()) {
       Instruction instruction = it.next();
-      if (instruction.isArgument()) {
-        argumentRegisters.put(instruction.outValue(), argumentRegister);
-        argumentRegister += instruction.outValue().requiredRegisters();
-        maxRegister = argumentRegister - 1;
-      } else if (instruction.outValue() != null
-          && !(instruction.outValue() instanceof StackValue)) {
-        maxRegister = Math.max(maxRegister, 2 * instruction.outValue().getNumber());
+      Value outValue = instruction.outValue();
+      if (outValue instanceof FixedLocal) {
+        // Phi stores are marked by a "fixed-local" value which share the same local index.
+        FixedLocal fixed = (FixedLocal) outValue;
+        if (!registers.containsKey(fixed.phi)) {
+          registers.put(fixed.phi, nextFreeRegister);
+          nextFreeRegister += fixed.requiredRegisters();
+        }
+      } else if (outValue != null && !(outValue instanceof StackValue)) {
+        registers.put(instruction.outValue(), nextFreeRegister);
+        nextFreeRegister += instruction.outValue().requiredRegisters();
       }
     }
-    maxLocals = maxRegister + 1;
+    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() {
-    int maxStack = 0;
-    int currentStack = 0;
+    List<CfTryCatch> tryCatchRanges = new ArrayList<>();
+    labels = new HashMap<>(code.blocks.size());
     instructions = new ArrayList<>();
     Iterator<BasicBlock> blockIterator = code.listIterator();
-    while (blockIterator.hasNext()) {
-      BasicBlock block = blockIterator.next();
-      InstructionIterator it = block.iterator();
-      while (it.hasNext()) {
-        Instruction instruction = it.next();
-        if (instruction.outValue() != null) {
-          Value outValue = instruction.outValue();
-          if (outValue instanceof StackValue) {
-            currentStack += outValue.requiredRegisters();
-            maxStack = Math.max(maxStack, currentStack);
+    BasicBlock block = blockIterator.next();
+    CfLabel tryCatchStart = null;
+    CatchHandlers<BasicBlock> tryCatchHandlers = CatchHandlers.EMPTY_BASIC_BLOCK;
+    do {
+      CatchHandlers<BasicBlock> handlers = block.getCatchHandlers();
+      if (!tryCatchHandlers.equals(handlers)) {
+        if (!tryCatchHandlers.isEmpty()) {
+          // Close try-catch and save the range.
+          CfLabel tryCatchEnd = getLabel(block);
+          tryCatchRanges.add(new CfTryCatch(tryCatchStart, tryCatchEnd, tryCatchHandlers, this));
+          if (instructions.get(instructions.size() - 1) != tryCatchEnd) {
+            instructions.add(tryCatchEnd);
           }
         }
-        for (Value inValue : instruction.inValues()) {
-          if (inValue instanceof StackValue) {
-            currentStack -= inValue.requiredRegisters();
+        if (!handlers.isEmpty()) {
+          // Open a try-catch.
+          tryCatchStart = getLabel(block);
+          if (instructions.isEmpty()
+              || instructions.get(instructions.size() - 1) != tryCatchStart) {
+            instructions.add(tryCatchStart);
           }
         }
-        instruction.buildCf(this);
+        tryCatchHandlers = handlers;
       }
-    }
+      BasicBlock nextBlock = blockIterator.hasNext() ? blockIterator.next() : null;
+      buildCfInstructions(block, nextBlock);
+      block = nextBlock;
+    } while (block != null);
     assert currentStack == 0;
-    return new CfCode(maxStack, maxLocals, instructions);
+    return new CfCode(maxStack, maxLocals, instructions, tryCatchRanges);
+  }
+
+  private void buildCfInstructions(BasicBlock block, BasicBlock nextBlock) {
+    boolean fallthrough = false;
+    InstructionIterator it = block.iterator();
+    while (it.hasNext()) {
+      Instruction instruction = it.next();
+      if (instruction.isGoto() && instruction.asGoto().getTarget() == nextBlock) {
+        fallthrough = true;
+        continue;
+      }
+      for (Value inValue : instruction.inValues()) {
+        if (inValue instanceof StackValue) {
+          pop(inValue);
+        }
+      }
+      if (instruction.outValue() != null) {
+        Value outValue = instruction.outValue();
+        if (outValue instanceof StackValue) {
+          push(outValue);
+        }
+      }
+      instruction.buildCf(this);
+    }
+    if (nextBlock == null || (fallthrough && nextBlock.getPredecessors().size() == 1)) {
+      return;
+    }
+    instructions.add(getLabel(nextBlock));
   }
 
   // Callbacks
 
+  public CfLabel getLabel(BasicBlock target){
+    return labels.computeIfAbsent(target, (block) -> new CfLabel());
+  }
+
   public int getLocalRegister(Value value) {
-    if (value.isArgument()) {
-      return argumentRegisters.getInt(value);
+    if (value instanceof FixedLocal) {
+      // Phi stores are marked by a "fixed-local" value which share the same local index.
+      FixedLocal fixed = (FixedLocal) value;
+      return registers.getInt(fixed.phi);
     }
-    return 2 * value.getNumber();
+    return registers.getInt(value);
   }
 
   public void add(CfInstruction instruction) {
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 e8c6bac..3c7fe40 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
@@ -710,6 +710,9 @@
 
   // Replace result uses for methods where something is known about what is returned.
   public void rewriteMoveResult(IRCode code) {
+    if (options.outputClassFiles) {
+      return;
+    }
     AppInfoWithSubtyping appInfoWithSubtyping = appInfo.withSubtyping();
     InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 3509357..b2760e4 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -57,27 +57,15 @@
     }
   }
 
+  // TODO(zerny): Implement stack-map computation to support Java 1.6 and above.
+  private int downgrade(int version) {
+    return version > 49 ? 49 : version;
+  }
+
   private void writeClass(DexProgramClass clazz, OutputSink outputSink) throws IOException {
-    // If there are CF representations for all methods manually compute stack height and frames.
-    // TODO(zerny): This is a temporary hack since we will need to manually compute stack maps for
-    // any methods that are IR processed.
-    int flags = 0;
-    for (DexEncodedMethod method : clazz.directMethods()) {
-      if (!method.getCode().isCfCode()) {
-        flags = ClassWriter.COMPUTE_FRAMES;
-        break;
-      }
-    }
-    if (flags == 0) {
-      for (DexEncodedMethod method : clazz.virtualMethods()) {
-        if (!method.getCode().isCfCode()) {
-          flags = ClassWriter.COMPUTE_FRAMES;
-        }
-      }
-    }
-    ClassWriter writer = new ClassWriter(flags);
+    ClassWriter writer = new ClassWriter(0);
     writer.visitSource(clazz.sourceFile.toString(), null);
-    int version = clazz.getClassFileVersion();
+    int version = downgrade(clazz.getClassFileVersion());
     int access = clazz.accessFlags.getAsCfAccessFlags();
     String desc = clazz.type.toDescriptorString();
     String name = internalName(clazz.type);
diff --git a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
index 46bcbd5..3321cb7 100644
--- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
@@ -51,7 +51,7 @@
       // Nothing to register for method type, it represents only a prototype not associated with a
       // method name.
       if (((Type) cst).getSort() != Type.METHOD) {
-        registry.registerConstClass(application.getType((Type) cst));
+        registry.registerTypeReference(application.getType((Type) cst));
       }
     } else if (cst instanceof Handle) {
       registerMethodHandleType((Handle) cst);
diff --git a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
index fed236a..e4e2ed7 100644
--- a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
@@ -6,16 +6,10 @@
 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.logging.Log;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.google.common.base.Equivalence;
-import com.google.common.base.Equivalence.Wrapper;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 /**
  * Removes abstract methods if they only shadow methods of the same signature in a superclass.
@@ -29,20 +23,20 @@
 public class AbstractMethodRemover {
 
   private final AppInfoWithSubtyping appInfo;
-  private ScopedDexItemSet scope;
+  private ScopedDexMethodSet scope = new ScopedDexMethodSet();
 
   public AbstractMethodRemover(AppInfoWithSubtyping appInfo) {
     this.appInfo = appInfo;
   }
 
   public void run() {
-    assert scope == null;
+    assert scope.getParent() == null;
     processClass(appInfo.dexItemFactory.objectType);
   }
 
   private void processClass(DexType type) {
     DexClass holder = appInfo.definitionFor(type);
-    scope = new ScopedDexItemSet(scope);
+    scope = scope.newNestedScope();
     if (holder != null && !holder.isLibraryClass()) {
       holder.setVirtualMethods(processMethods(holder.virtualMethods()));
     }
@@ -77,29 +71,4 @@
     return methods == null ? virtualMethods : methods.toArray(new DexEncodedMethod[methods.size()]);
   }
 
-  private static class ScopedDexItemSet {
-
-    private static Equivalence<DexMethod> METHOD_EQUIVALENCE = MethodSignatureEquivalence.get();
-
-    private final ScopedDexItemSet parent;
-    private final Set<Wrapper<DexMethod>> items = new HashSet<>();
-
-    private ScopedDexItemSet(ScopedDexItemSet parent) {
-      this.parent = parent;
-    }
-
-    private boolean contains(Wrapper<DexMethod> item) {
-      return items.contains(item)
-          || ((parent != null) && parent.contains(item));
-    }
-
-    boolean addMethod(DexMethod method) {
-      Wrapper<DexMethod> wrapped = METHOD_EQUIVALENCE.wrap(method);
-      return !contains(wrapped) && items.add(wrapped);
-    }
-
-    ScopedDexItemSet getParent() {
-      return parent;
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 4c30d36..215ea88 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -31,9 +31,7 @@
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
@@ -160,12 +158,6 @@
    */
   private final Map<DexType, Set<DexAnnotation>> deferredAnnotations = new IdentityHashMap<>();
 
-  /**
-   * Methods that have been seen as not reachable due to related classes not being instantiated.
-   * As more instantiated types are collected this set needs to be re-visited.
-   */
-  private List<DexEncodedMethod> pendingAdditionalInstantiatedTypes = new ArrayList<>();
-
   public Enqueuer(AppInfoWithSubtyping appInfo, InternalOptions options) {
     this.appInfo = appInfo;
     this.options = options;
@@ -294,18 +286,7 @@
 
     @Override
     public boolean registerNewInstance(DexType type) {
-      if (instantiatedTypes.contains(type)) {
-        return false;
-      }
-      DexClass clazz = appInfo.definitionFor(type);
-      if (clazz == null) {
-        reportMissingClass(type);
-        return false;
-      }
-      if (Log.ENABLED) {
-        Log.verbose(getClass(), "Register new instatiation of `%s`.", clazz);
-      }
-      workList.add(Action.markInstantiated(clazz, KeepReason.instantiatedIn(currentMethod)));
+      markInstantiated(type, currentMethod);
       return true;
     }
 
@@ -343,30 +324,6 @@
       }
       return false;
     }
-
-    @Override
-    public boolean registerConstClass(DexType type) {
-      boolean result = registerTypeReference(type);
-      // For Proguard compatibility mark default initializer for live type as live.
-      if (options.forceProguardCompatibility) {
-        if (type.isArrayType()) {
-          return result;
-        }
-        assert type.isClassType();
-        DexClass holder = appInfo.definitionFor(type);
-        if (holder == null) {
-          // Don't call reportMissingClass(type) here. That already happened in the call to
-          // registerTypeReference(type).
-          return result;
-        }
-        if (holder.hasDefaultInitializer()) {
-          registerNewInstance(type);
-          DexEncodedMethod init = holder.getDefaultInitializer();
-          markDirectStaticOrConstructorMethodAsLive(init, KeepReason.reachableFromLiveType(type));
-        }
-      }
-      return result;
-    }
   }
 
   //
@@ -410,6 +367,9 @@
       if (options.forceProguardCompatibility) {
         if (holder.hasDefaultInitializer()) {
           DexEncodedMethod init = holder.getDefaultInitializer();
+          // TODO(68246915): For now just register the default constructor as the source of
+          // instantiation.
+          markInstantiated(type, init);
           markDirectStaticOrConstructorMethodAsLive(init, KeepReason.reachableFromLiveType(type));
         }
       }
@@ -510,11 +470,13 @@
    *
    * <p>Only methods that are visible in this type are considered. That is, only those methods that
    * are either defined directly on this type or that are defined on a supertype but are not
-   * shadowed by another inherited method.
+   * shadowed by another inherited method. Furthermore, default methods from implemented interfaces
+   * that are not otherwise shadowed are considered, too.
    */
-  private void transitionMethodsForInstantiatedClass(DexType type) {
-    Set<Wrapper<DexMethod>> seen = new HashSet<>();
-    MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
+  private void transitionMethodsForInstantiatedClass(DexType instantiatedType) {
+    ScopedDexMethodSet seen = new ScopedDexMethodSet();
+    Set<DexType> interfaces = Sets.newIdentityHashSet();
+    DexType type = instantiatedType;
     do {
       DexClass clazz = appInfo.definitionFor(type);
       if (clazz == null) {
@@ -525,15 +487,52 @@
       SetWithReason<DexEncodedMethod> reachableMethods = reachableVirtualMethods.get(type);
       if (reachableMethods != null) {
         for (DexEncodedMethod encodedMethod : reachableMethods.getItems()) {
-          Wrapper<DexMethod> ignoringClass = equivalence.wrap(encodedMethod.method);
-          if (!seen.contains(ignoringClass)) {
-            seen.add(ignoringClass);
-            markVirtualMethodAsLive(encodedMethod, KeepReason.reachableFromLiveType(type));
+          if (seen.addMethod(encodedMethod.method)) {
+            markVirtualMethodAsLive(encodedMethod,
+                KeepReason.reachableFromLiveType(instantiatedType));
           }
         }
       }
+      Collections.addAll(interfaces, clazz.interfaces.values);
       type = clazz.superType;
     } while (type != null && !instantiatedTypes.contains(type));
+    // The set now contains all virtual methods on the type and its supertype that are reachable.
+    // In a second step, we now look at interfaces. We have to do this in this order due to JVM
+    // semantics for default methods. A default method is only reachable if it is not overridden in
+    // any superclass. Also, it is not defined which default method is chosen if multiple
+    // interfaces define the same default method. Hence, for every interface (direct or indirect),
+    // we have to look at the interface chain and mark default methods as reachable, not taking
+    // the shadowing of other interface chains into account.
+    // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3
+    for (DexType iface : interfaces) {
+      DexClass clazz = appInfo.definitionFor(iface);
+      if (clazz == null) {
+        reportMissingClass(iface);
+        // TODO(herhut): In essence, our subtyping chain is broken here. Handle that case better.
+        break;
+      }
+      transitionDefaultMethodsForInstantiatedClass(iface, instantiatedType, seen);
+    }
+  }
+
+  private void transitionDefaultMethodsForInstantiatedClass(DexType iface, DexType instantiatedType,
+      ScopedDexMethodSet seen) {
+    DexClass clazz = appInfo.definitionFor(iface);
+    assert clazz.accessFlags.isInterface();
+    SetWithReason<DexEncodedMethod> reachableMethods = reachableVirtualMethods.get(iface);
+    if (reachableMethods != null) {
+      seen = seen.newNestedScope();
+      for (DexEncodedMethod encodedMethod : reachableMethods.getItems()) {
+        assert !encodedMethod.accessFlags.isAbstract();
+        if (seen.addMethod(encodedMethod.method)) {
+          markVirtualMethodAsLive(encodedMethod,
+              KeepReason.reachableFromLiveType(instantiatedType));
+        }
+      }
+      for (DexType subInterface : clazz.interfaces.values) {
+        transitionDefaultMethodsForInstantiatedClass(subInterface, instantiatedType, seen);
+      }
+    }
   }
 
   /**
@@ -587,6 +586,21 @@
     enqueueRootItems(rootSet.getDependentItems(field));
   }
 
+  private void markInstantiated(DexType type, DexEncodedMethod method) {
+    if (instantiatedTypes.contains(type)) {
+      return;
+    }
+    DexClass clazz = appInfo.definitionFor(type);
+    if (clazz == null) {
+      reportMissingClass(type);
+      return;
+    }
+    if (Log.ENABLED) {
+      Log.verbose(getClass(), "Register new instatiation of `%s`.", clazz);
+    }
+    workList.add(Action.markInstantiated(clazz, KeepReason.instantiatedIn(method)));
+  }
+
   private void markDirectStaticOrConstructorMethodAsLive(
       DexEncodedMethod encodedMethod, KeepReason reason) {
     assert encodedMethod != null;
@@ -685,39 +699,33 @@
       SetWithReason<DexEncodedMethod> reachable = reachableVirtualMethods
           .computeIfAbsent(encodedMethod.method.holder, (ignore) -> new SetWithReason<>());
       if (reachable.add(encodedMethod, reason)) {
-        handleIsInstantiatedOrHasInstantiatedSubtype(encodedMethod);
-      }
-    }
-  }
-
-  private void handleIsInstantiatedOrHasInstantiatedSubtype(DexEncodedMethod encodedMethod) {
-    // If the holder type is instantiated, the method is live. Otherwise check whether we find
-    // a subtype that does not shadow this methods but is instantiated.
-    // Note that library classes are always considered instantiated, as we do not know where
-    // they are instantiated.
-    if (isInstantiatedOrHasInstantiatedSubtype(encodedMethod.method.holder)) {
-      if (instantiatedTypes.contains(encodedMethod.method.holder)) {
-        markVirtualMethodAsLive(encodedMethod,
-            KeepReason.reachableFromLiveType(encodedMethod.method.holder));
-      } else {
-        Deque<DexType> worklist = new ArrayDeque<>();
-        fillWorkList(worklist, encodedMethod.method.holder);
-        while (!worklist.isEmpty()) {
-          DexType current = worklist.pollFirst();
-          DexClass currentHolder = appInfo.definitionFor(current);
-          if (currentHolder == null
-              || currentHolder.findVirtualTarget(encodedMethod.method) != null) {
-            continue;
+        // If the holder type is instantiated, the method is live. Otherwise check whether we find
+        // a subtype that does not shadow this methods but is instantiated.
+        // Note that library classes are always considered instantiated, as we do not know where
+        // they are instantiated.
+        if (isInstantiatedOrHasInstantiatedSubtype(encodedMethod.method.holder)) {
+          if (instantiatedTypes.contains(encodedMethod.method.holder)) {
+            markVirtualMethodAsLive(encodedMethod,
+                KeepReason.reachableFromLiveType(encodedMethod.method.holder));
+          } else {
+            Deque<DexType> worklist = new ArrayDeque<>();
+            fillWorkList(worklist, encodedMethod.method.holder);
+            while (!worklist.isEmpty()) {
+              DexType current = worklist.pollFirst();
+              DexClass currentHolder = appInfo.definitionFor(current);
+              if (currentHolder == null
+                  || currentHolder.findVirtualTarget(encodedMethod.method) != null) {
+                continue;
+              }
+              if (instantiatedTypes.contains(current)) {
+                markVirtualMethodAsLive(encodedMethod, KeepReason.reachableFromLiveType(current));
+                break;
+              }
+              fillWorkList(worklist, current);
+            }
           }
-          if (instantiatedTypes.contains(current)) {
-            markVirtualMethodAsLive(encodedMethod, KeepReason.reachableFromLiveType(current));
-            break;
-          }
-          fillWorkList(worklist, current);
         }
       }
-    } else {
-      pendingAdditionalInstantiatedTypes.add(encodedMethod);
     }
   }
 
@@ -790,22 +798,38 @@
   private AppInfoWithLiveness trace(Timing timing) {
     timing.begin("Grow the tree.");
     try {
-      int instantiatedTypesCount = 0;
-      while (true) {
-        doTrace();
-        // If methods where not considered due to relevant types not being instantiated reconsider
-        // if more instantiated types where collected.
-        if (pendingAdditionalInstantiatedTypes.size() > 0
-            && instantiatedTypes.items.size() > instantiatedTypesCount) {
-          instantiatedTypesCount = instantiatedTypes.items.size();
-          List<DexEncodedMethod> reconsider = pendingAdditionalInstantiatedTypes;
-          pendingAdditionalInstantiatedTypes = new ArrayList<>();
-          reconsider.forEach(this::handleIsInstantiatedOrHasInstantiatedSubtype);
-        } else {
-          break;
+      while (!workList.isEmpty()) {
+        Action action = workList.poll();
+        switch (action.kind) {
+          case MARK_INSTANTIATED:
+            processNewlyInstantiatedClass((DexClass) action.target, action.reason);
+            break;
+          case MARK_REACHABLE_FIELD:
+            markFieldAsReachable((DexField) action.target, action.reason);
+            break;
+          case MARK_REACHABLE_VIRTUAL:
+            markVirtualMethodAsReachable((DexMethod) action.target, false, action.reason);
+            break;
+          case MARK_REACHABLE_INTERFACE:
+            markVirtualMethodAsReachable((DexMethod) action.target, true, action.reason);
+            break;
+          case MARK_REACHABLE_SUPER:
+            markSuperMethodAsReachable((DexMethod) action.target,
+                (DexEncodedMethod) action.context);
+            break;
+          case MARK_METHOD_KEPT:
+            markMethodAsKept((DexEncodedMethod) action.target, action.reason);
+            break;
+          case MARK_FIELD_KEPT:
+            markFieldAsKept((DexEncodedField) action.target, action.reason);
+            break;
+          case MARK_METHOD_LIVE:
+            processNewlyLiveMethod(((DexEncodedMethod) action.target), action.reason);
+            break;
+          default:
+            throw new IllegalArgumentException(action.kind.toString());
         }
       }
-
       if (Log.ENABLED) {
         Set<DexEncodedMethod> allLive = Sets.newIdentityHashSet();
         for (Entry<DexType, SetWithReason<DexEncodedMethod>> entry : reachableVirtualMethods
@@ -833,41 +857,6 @@
     return new AppInfoWithLiveness(appInfo, this);
   }
 
-  private void doTrace() {
-    while (!workList.isEmpty()) {
-      Action action = workList.poll();
-      switch (action.kind) {
-        case MARK_INSTANTIATED:
-          processNewlyInstantiatedClass((DexClass) action.target, action.reason);
-          break;
-        case MARK_REACHABLE_FIELD:
-          markFieldAsReachable((DexField) action.target, action.reason);
-          break;
-        case MARK_REACHABLE_VIRTUAL:
-          markVirtualMethodAsReachable((DexMethod) action.target, false, action.reason);
-          break;
-        case MARK_REACHABLE_INTERFACE:
-          markVirtualMethodAsReachable((DexMethod) action.target, true, action.reason);
-          break;
-        case MARK_REACHABLE_SUPER:
-          markSuperMethodAsReachable((DexMethod) action.target,
-              (DexEncodedMethod) action.context);
-          break;
-        case MARK_METHOD_KEPT:
-          markMethodAsKept((DexEncodedMethod) action.target, action.reason);
-          break;
-        case MARK_FIELD_KEPT:
-          markFieldAsKept((DexEncodedField) action.target, action.reason);
-          break;
-        case MARK_METHOD_LIVE:
-          processNewlyLiveMethod(((DexEncodedMethod) action.target), action.reason);
-          break;
-        default:
-          throw new IllegalArgumentException(action.kind.toString());
-      }
-    }
-  }
-
   private void markMethodAsKept(DexEncodedMethod target, KeepReason reason) {
     DexClass holder = appInfo.definitionFor(target.method.holder);
     // If this method no longer has a corresponding class then we have shaken it away before.
diff --git a/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java b/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java
new file mode 100644
index 0000000..612b88a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java
@@ -0,0 +1,45 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import java.util.HashSet;
+import java.util.Set;
+
+class ScopedDexMethodSet {
+
+  private static Equivalence<DexMethod> METHOD_EQUIVALENCE = MethodSignatureEquivalence.get();
+
+  private final ScopedDexMethodSet parent;
+  private final Set<Wrapper<DexMethod>> items = new HashSet<>();
+
+  public ScopedDexMethodSet() {
+    this(null);
+  }
+
+  private ScopedDexMethodSet(ScopedDexMethodSet parent) {
+    this.parent = parent;
+  }
+
+  public ScopedDexMethodSet newNestedScope() {
+    return new ScopedDexMethodSet(this);
+  }
+
+  private boolean contains(Wrapper<DexMethod> item) {
+    return items.contains(item)
+        || ((parent != null) && parent.contains(item));
+  }
+
+  public boolean addMethod(DexMethod method) {
+    Wrapper<DexMethod> wrapped = METHOD_EQUIVALENCE.wrap(method);
+    return !contains(wrapped) && items.add(wrapped);
+  }
+
+  public ScopedDexMethodSet getParent() {
+    return parent;
+  }
+}
diff --git a/src/test/examplesAndroidN/shaking/InterfaceWithDefault.java b/src/test/examplesAndroidN/shaking/InterfaceWithDefault.java
new file mode 100644
index 0000000..f32a754
--- /dev/null
+++ b/src/test/examplesAndroidN/shaking/InterfaceWithDefault.java
@@ -0,0 +1,11 @@
+// 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 shaking;
+
+public interface InterfaceWithDefault {
+
+  default void foo() {
+    System.out.println("Default method foo");
+  }
+}
diff --git a/src/test/examplesAndroidN/shaking/OtherInterface.java b/src/test/examplesAndroidN/shaking/OtherInterface.java
new file mode 100644
index 0000000..8831a1d
--- /dev/null
+++ b/src/test/examplesAndroidN/shaking/OtherInterface.java
@@ -0,0 +1,9 @@
+// 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 shaking;
+
+public interface OtherInterface {
+
+  void bar();
+}
diff --git a/src/test/examplesAndroidN/shaking/OtherInterfaceWithDefault.java b/src/test/examplesAndroidN/shaking/OtherInterfaceWithDefault.java
new file mode 100644
index 0000000..8d4dec6
--- /dev/null
+++ b/src/test/examplesAndroidN/shaking/OtherInterfaceWithDefault.java
@@ -0,0 +1,12 @@
+// 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 shaking;
+
+public interface OtherInterfaceWithDefault extends OtherInterface {
+
+  @Override
+  default void bar() {
+    System.out.println("bar from OtherInterfaceWithDefault");
+  }
+}
diff --git a/src/test/examplesAndroidN/shaking/Shaking.java b/src/test/examplesAndroidN/shaking/Shaking.java
new file mode 100644
index 0000000..353e9b4
--- /dev/null
+++ b/src/test/examplesAndroidN/shaking/Shaking.java
@@ -0,0 +1,16 @@
+// 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 shaking;
+
+public class Shaking {
+
+  public static void main(String... args) {
+    SubClassOne anInstance = new SubClassOne();
+    invokeFooOnInterface(anInstance);
+  }
+
+  private static void invokeFooOnInterface(InterfaceWithDefault anInstance) {
+    anInstance.foo();
+  }
+}
diff --git a/src/test/examplesAndroidN/shaking/SubClassOne.java b/src/test/examplesAndroidN/shaking/SubClassOne.java
new file mode 100644
index 0000000..7dfcf8d
--- /dev/null
+++ b/src/test/examplesAndroidN/shaking/SubClassOne.java
@@ -0,0 +1,29 @@
+// 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 shaking;
+
+public class SubClassOne implements InterfaceWithDefault, OtherInterfaceWithDefault {
+
+  @Override
+  public void foo() {
+    System.out.println("Method foo from SubClassOne");
+    makeSubClassTwoLive().foo();
+    asOtherInterface().bar();
+  }
+
+  private OtherInterface asOtherInterface() {
+    return new SubClassTwo();
+  }
+
+  @Override
+  public void bar() {
+    System.out.println("Method bar from SubClassOne");
+  }
+
+  private InterfaceWithDefault makeSubClassTwoLive() {
+    // Once we see this method, SubClassTwo will be live. This should also make the default method
+    // in the interface live, as SubClassTwo does not override it.
+    return new SubClassTwo();
+  }
+}
diff --git a/src/test/examplesAndroidN/shaking/SubClassTwo.java b/src/test/examplesAndroidN/shaking/SubClassTwo.java
new file mode 100644
index 0000000..95b2492
--- /dev/null
+++ b/src/test/examplesAndroidN/shaking/SubClassTwo.java
@@ -0,0 +1,8 @@
+// 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 shaking;
+
+public class SubClassTwo implements InterfaceWithDefault, OtherInterfaceWithDefault {
+  // Intentionally left empty.
+}
diff --git a/src/test/examplesAndroidN/shaking/keep-rules.txt b/src/test/examplesAndroidN/shaking/keep-rules.txt
new file mode 100644
index 0000000..791cebf
--- /dev/null
+++ b/src/test/examplesAndroidN/shaking/keep-rules.txt
@@ -0,0 +1,9 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class shaking.Shaking {
+  public static void main(...);
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 0fc2489..c79719a 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -39,6 +39,8 @@
 @RunWith(Parameterized.class)
 public class R8RunExamplesTest {
 
+  private static final boolean ONLY_RUN_CF_TESTS = false;
+
   enum Input {
     DX, JAVAC, JAVAC_ALL, JAVAC_NONE
   }
@@ -125,22 +127,25 @@
         "constants.Constants",
         "hello.Hello",
         "arithmetic.Arithmetic",
+        "barray.BArray",
     };
 
     List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
-    for (String test : tests) {
-      fullTestList.add(makeTest(Input.JAVAC, CompilerUnderTest.D8, CompilationMode.DEBUG, test));
-      fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.D8, CompilationMode.DEBUG,
-          test));
-      fullTestList.add(makeTest(Input.JAVAC_NONE, CompilerUnderTest.D8, CompilationMode.DEBUG,
-          test));
-      fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.D8, CompilationMode.RELEASE,
-          test));
-      fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.RELEASE,
-          test));
-      fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.DEBUG,
-          test));
-      fullTestList.add(makeTest(Input.DX, CompilerUnderTest.R8, CompilationMode.RELEASE, test));
+    if (!ONLY_RUN_CF_TESTS) {
+      for (String test : tests) {
+        fullTestList.add(makeTest(Input.JAVAC, CompilerUnderTest.D8, CompilationMode.DEBUG, test));
+        fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.D8, CompilationMode.DEBUG,
+            test));
+        fullTestList.add(makeTest(Input.JAVAC_NONE, CompilerUnderTest.D8, CompilationMode.DEBUG,
+            test));
+        fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.D8, CompilationMode.RELEASE,
+            test));
+        fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.RELEASE,
+            test));
+        fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.DEBUG,
+            test));
+        fullTestList.add(makeTest(Input.DX, CompilerUnderTest.R8, CompilationMode.RELEASE, test));
+      }
     }
     // TODO(zerny): Once all tests pass create the java tests in the main test loop.
     for (String test : javaBytecodeTests) {
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index ea51c65..2128663 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -26,11 +26,15 @@
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.jar.JarOutputStream;
 import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 import org.junit.Rule;
 import org.junit.rules.TemporaryFolder;
 
@@ -93,6 +97,25 @@
   }
 
   /**
+   * Read the names of classes in a jar.
+   */
+  protected Set<String> readClassesInJar(Path jar) throws IOException {
+    Set<String> result = new HashSet<>();
+    try (ZipFile zipFile = new ZipFile(jar.toFile())) {
+      final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+      while (entries.hasMoreElements()) {
+        ZipEntry entry = entries.nextElement();
+        String name = entry.getName();
+        if (name.endsWith(".class")) {
+          result.add(name.substring(0, name.length() - ".class".length()).replace('/', '.'));
+        }
+      }
+    }
+    return result;
+  }
+
+
+  /**
    * Get the class name generated by javac.
    */
   protected String getJavacGeneratedClassName(Class clazz) {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 5753d84..2c4bc98 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -58,15 +58,17 @@
 public class ToolHelper {
 
   public static final String BUILD_DIR = "build/";
-  public static final String EXAMPLES_DIR = "src/test/examples/";
-  public static final String EXAMPLES_ANDROID_O_DIR = "src/test/examplesAndroidO/";
-  public static final String EXAMPLES_ANDROID_P_DIR = "src/test/examplesAndroidP/";
-  public static final String EXAMPLES_BUILD_DIR = BUILD_DIR + "test/examples/";
-  public static final String EXAMPLES_ANDROID_N_BUILD_DIR = BUILD_DIR + "test/examplesAndroidN/";
-  public static final String EXAMPLES_ANDROID_O_BUILD_DIR = BUILD_DIR + "test/examplesAndroidO/";
-  public static final String EXAMPLES_ANDROID_P_BUILD_DIR = BUILD_DIR + "test/examplesAndroidP/";
-  public static final String EXAMPLES_JAVA9_BUILD_DIR = BUILD_DIR + "test/examplesJava9/";
-  public static final String SMALI_BUILD_DIR = BUILD_DIR + "test/smali/";
+  public static final String TESTS_DIR = "src/test/";
+  public static final String EXAMPLES_DIR = TESTS_DIR + "examples/";
+  public static final String EXAMPLES_ANDROID_O_DIR = TESTS_DIR + "examplesAndroidO/";
+  public static final String EXAMPLES_ANDROID_P_DIR = TESTS_DIR + "examplesAndroidP/";
+  public static final String TESTS_BUILD_DIR = BUILD_DIR + "test/";
+  public static final String EXAMPLES_BUILD_DIR = TESTS_BUILD_DIR + "examples/";
+  public static final String EXAMPLES_ANDROID_N_BUILD_DIR = TESTS_BUILD_DIR + "examplesAndroidN/";
+  public static final String EXAMPLES_ANDROID_O_BUILD_DIR = TESTS_BUILD_DIR + "examplesAndroidO/";
+  public static final String EXAMPLES_ANDROID_P_BUILD_DIR = TESTS_BUILD_DIR + "examplesAndroidP/";
+  public static final String EXAMPLES_JAVA9_BUILD_DIR = TESTS_BUILD_DIR + "examplesJava9/";
+  public static final String SMALI_BUILD_DIR = TESTS_BUILD_DIR + "smali/";
 
   public static final String LINE_SEPARATOR = StringUtils.LINE_SEPARATOR;
   public final static String PATH_SEPARATOR = File.pathSeparator;
@@ -970,7 +972,7 @@
     }
   }
 
-  public static void runProguard(Path inJar, Path outJar, Path config) throws IOException {
+  public static String runProguard(Path inJar, Path outJar, Path config) throws IOException {
     List<String> command = new ArrayList<>();
     command.add(PROGUARD);
     command.add("-forceprocessing");  // Proguard just checks the creation time on the in/out jars.
@@ -987,6 +989,7 @@
     if (result.exitCode != 0) {
       fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
     }
+    return result.stdout;
   }
 
 
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 756c93c..081b083 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -29,6 +29,7 @@
 import com.google.common.collect.Lists;
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -61,27 +62,31 @@
       .of(ANDROID_JAR, ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"), Paths::get);
   private static final String EMPTY_FLAGS = "src/test/proguard/valid/empty.flags";
   private static final Set<String> IGNORED_FLAGS = ImmutableSet.of(
-      "minification:conflict-mapping.txt",
-      "minification:keep-rules-apply-conflict-mapping.txt"
+      "examples/minification:conflict-mapping.txt",
+      "examples/minification:keep-rules-apply-conflict-mapping.txt"
   );
   private static final Set<String> IGNORED = ImmutableSet.of(
       // there's no point in running those without obfuscation
-      "shaking1:keep-rules-repackaging.txt:DEX:NONE",
-      "shaking1:keep-rules-repackaging.txt:JAR:NONE",
-      "shaking16:keep-rules-1.txt:DEX:NONE",
-      "shaking16:keep-rules-1.txt:JAR:NONE",
-      "shaking16:keep-rules-2.txt:DEX:NONE",
-      "shaking16:keep-rules-2.txt:JAR:NONE",
-      "shaking15:keep-rules.txt:DEX:NONE",
-      "shaking15:keep-rules.txt:JAR:NONE",
-      "minifygeneric:keep-rules.txt:DEX:NONE",
-      "minifygeneric:keep-rules.txt:JAR:NONE",
-      "minifygenericwithinner:keep-rules.txt:DEX:NONE",
-      "minifygenericwithinner:keep-rules.txt:JAR:NONE"
+      "examples/shaking1:keep-rules-repackaging.txt:DEX:NONE",
+      "examples/shaking1:keep-rules-repackaging.txt:JAR:NONE",
+      "examples/shaking16:keep-rules-1.txt:DEX:NONE",
+      "examples/shaking16:keep-rules-1.txt:JAR:NONE",
+      "examples/shaking16:keep-rules-2.txt:DEX:NONE",
+      "examples/shaking16:keep-rules-2.txt:JAR:NONE",
+      "examples/shaking15:keep-rules.txt:DEX:NONE",
+      "examples/shaking15:keep-rules.txt:JAR:NONE",
+      "examples/minifygeneric:keep-rules.txt:DEX:NONE",
+      "examples/minifygeneric:keep-rules.txt:JAR:NONE",
+      "examples/minifygenericwithinner:keep-rules.txt:DEX:NONE",
+      "examples/minifygenericwithinner:keep-rules.txt:JAR:NONE",
+      // No prebuild DEX files for AndroidN
+      "examplesAndroidN/shaking:keep-rules.txt:DEX:NONE",
+      "examplesAndroidN/shaking:keep-rules.txt:DEX:JAVA",
+      "examplesAndroidN/shaking:keep-rules.txt:DEX:AGGRESSIVE"
   );
 
   // TODO(65355452): Reenable or remove inlining tests.
-  private static Set<String> SKIPPED = ImmutableSet.of("inlining");
+  private static Set<String> SKIPPED = ImmutableSet.of("examples/inlining");
 
   private final MinifyMode minify;
 
@@ -107,11 +112,11 @@
       BiConsumer<String, String> outputComparator,
       BiConsumer<DexInspector, DexInspector> dexComparator) {
     this.kind = kind;
-    originalDex = ToolHelper.EXAMPLES_BUILD_DIR + test + "/classes.dex";
+    originalDex = ToolHelper.TESTS_BUILD_DIR + test + "/classes.dex";
     if (kind == Frontend.DEX) {
       this.programFile = originalDex;
     } else {
-      this.programFile = ToolHelper.EXAMPLES_BUILD_DIR + test + ".jar";
+      this.programFile = ToolHelper.TESTS_BUILD_DIR + test + ".jar";
     }
     this.mainClass = mainClass;
     this.keepRulesFiles = keepRulesFiles;
@@ -622,160 +627,166 @@
   public static Collection<Object[]> data() {
     List<String> tests = Arrays
         .asList(
-            "shaking1",
-            "shaking2",
-            "shaking3",
-            "shaking4",
-            "shaking5",
-            "shaking6",
-            "shaking7",
-            "shaking8",
-            "shaking9",
-            "shaking10",
-            "shaking11",
-            "shaking12",
-            "shaking13",
-            "shaking14",
-            "shaking15",
-            "shaking16",
-            "shaking17",
-            "minification",
-            "minifygeneric",
-            "minifygenericwithinner",
-            "assumenosideeffects1",
-            "assumenosideeffects2",
-            "assumenosideeffects3",
-            "assumenosideeffects4",
-            "assumenosideeffects5",
-            "assumevalues1",
-            "assumevalues2",
-            "assumevalues3",
-            "assumevalues4",
-            "assumevalues5",
-            "annotationremoval",
-            "memberrebinding2",
-            "memberrebinding3",
-            "simpleproto1",
-            "simpleproto2",
-            "simpleproto3",
-            "nestedproto1",
-            "nestedproto2",
-            "enumproto",
-            "repeatedproto",
-            "oneofproto");
+            "examples/shaking1",
+            "examples/shaking2",
+            "examples/shaking3",
+            "examples/shaking4",
+            "examples/shaking5",
+            "examples/shaking6",
+            "examples/shaking7",
+            "examples/shaking8",
+            "examples/shaking9",
+            "examples/shaking10",
+            "examples/shaking11",
+            "examples/shaking12",
+            "examples/shaking13",
+            "examples/shaking14",
+            "examples/shaking15",
+            "examples/shaking16",
+            "examples/shaking17",
+            "examples/minification",
+            "examples/minifygeneric",
+            "examples/minifygenericwithinner",
+            "examples/assumenosideeffects1",
+            "examples/assumenosideeffects2",
+            "examples/assumenosideeffects3",
+            "examples/assumenosideeffects4",
+            "examples/assumenosideeffects5",
+            "examples/assumevalues1",
+            "examples/assumevalues2",
+            "examples/assumevalues3",
+            "examples/assumevalues4",
+            "examples/assumevalues5",
+            "examples/annotationremoval",
+            "examples/memberrebinding2",
+            "examples/memberrebinding3",
+            "examples/simpleproto1",
+            "examples/simpleproto2",
+            "examples/simpleproto3",
+            "examples/nestedproto1",
+            "examples/nestedproto2",
+            "examples/enumproto",
+            "examples/repeatedproto",
+            "examples/oneofproto",
+            "examplesAndroidN/shaking");
 
     // Keys can be the name of the test or the name of the test followed by a colon and the name
     // of the keep file.
     Map<String, Consumer<DexInspector>> inspections = new HashMap<>();
-    inspections.put("shaking1:keep-rules.txt", TreeShakingTest::shaking1HasNoClassUnused);
+    inspections.put("examples/shaking1:keep-rules.txt", TreeShakingTest::shaking1HasNoClassUnused);
+    inspections.put("examples/shaking1:keep-rules-repackaging.txt",
+        TreeShakingTest::shaking1IsCorrectlyRepackaged);
     inspections
-        .put("shaking1:keep-rules-repackaging.txt", TreeShakingTest::shaking1IsCorrectlyRepackaged);
-    inspections.put("shaking2:keep-rules.txt", TreeShakingTest::shaking2SuperClassIsAbstract);
-    inspections.put("shaking3:keep-by-tag.txt", TreeShakingTest::shaking3HasNoClassB);
-    inspections.put("shaking3:keep-by-tag-default.txt", TreeShakingTest::shaking3HasNoClassB);
-    inspections.put("shaking3:keep-by-tag-with-pattern.txt", TreeShakingTest::shaking3HasNoClassB);
-    inspections.put("shaking3:keep-by-tag-via-interface.txt", TreeShakingTest::shaking3HasNoClassB);
-    inspections.put("shaking3:keep-by-tag-on-method.txt", TreeShakingTest::shaking3HasNoClassB);
+        .put("examples/shaking2:keep-rules.txt", TreeShakingTest::shaking2SuperClassIsAbstract);
+    inspections.put("examples/shaking3:keep-by-tag.txt", TreeShakingTest::shaking3HasNoClassB);
     inspections
-        .put("shaking3:keep-no-abstract-classes.txt", TreeShakingTest::shaking3HasNoPrivateClass);
-    inspections.put("shaking5", TreeShakingTest::shaking5Inspection);
-    inspections.put("shaking6:keep-public.txt", TreeShakingTest::hasNoPrivateMethods);
-    inspections.put("shaking6:keep-non-public.txt", TreeShakingTest::hasNoPublicMethodsButPrivate);
+        .put("examples/shaking3:keep-by-tag-default.txt", TreeShakingTest::shaking3HasNoClassB);
+    inspections.put("examples/shaking3:keep-by-tag-with-pattern.txt",
+        TreeShakingTest::shaking3HasNoClassB);
+    inspections.put("examples/shaking3:keep-by-tag-via-interface.txt",
+        TreeShakingTest::shaking3HasNoClassB);
     inspections
-        .put("shaking6:keep-justAMethod-public.txt", TreeShakingTest::hasNoPrivateJustAMethod);
-    inspections.put("shaking6:keep-justAMethod-OnInt.txt", TreeShakingTest::hasOnlyIntJustAMethod);
+        .put("examples/shaking3:keep-by-tag-on-method.txt", TreeShakingTest::shaking3HasNoClassB);
+    inspections.put("examples/shaking3:keep-no-abstract-classes.txt",
+        TreeShakingTest::shaking3HasNoPrivateClass);
+    inspections.put("examples/shaking5", TreeShakingTest::shaking5Inspection);
+    inspections.put("examples/shaking6:keep-public.txt", TreeShakingTest::hasNoPrivateMethods);
+    inspections.put("examples/shaking6:keep-non-public.txt",
+        TreeShakingTest::hasNoPublicMethodsButPrivate);
+    inspections.put("examples/shaking6:keep-justAMethod-public.txt",
+        TreeShakingTest::hasNoPrivateJustAMethod);
+    inspections.put("examples/shaking6:keep-justAMethod-OnInt.txt",
+        TreeShakingTest::hasOnlyIntJustAMethod);
+    inspections.put("examples/shaking7:keep-public-fields.txt",
+        TreeShakingTest::shaking7HasOnlyPublicFields);
+    inspections.put("examples/shaking7:keep-double-fields.txt",
+        TreeShakingTest::shaking7HasOnlyDoubleFields);
+    inspections.put("examples/shaking7:keep-public-theDoubleField-fields.txt",
+        TreeShakingTest::shaking7HasOnlyPublicFieldsNamedTheDoubleField);
+    inspections.put("examples/shaking7:keep-public-theIntField-fields.txt",
+        TreeShakingTest::shaking7HasOnlyPublicFieldsNamedTheIntField);
+    inspections.put("examples/shaking8:keep-rules.txt",
+        TreeShakingTest::shaking8ThingClassIsAbstractAndEmpty);
     inspections
-        .put("shaking7:keep-public-fields.txt", TreeShakingTest::shaking7HasOnlyPublicFields);
+        .put("examples/shaking9:keep-rules.txt", TreeShakingTest::shaking9OnlySuperMethodsKept);
     inspections
-        .put("shaking7:keep-double-fields.txt", TreeShakingTest::shaking7HasOnlyDoubleFields);
-    inspections
-        .put("shaking7:keep-public-theDoubleField-fields.txt",
-            TreeShakingTest::shaking7HasOnlyPublicFieldsNamedTheDoubleField);
-    inspections
-        .put("shaking7:keep-public-theIntField-fields.txt",
-            TreeShakingTest::shaking7HasOnlyPublicFieldsNamedTheIntField);
-    inspections
-        .put("shaking8:keep-rules.txt", TreeShakingTest::shaking8ThingClassIsAbstractAndEmpty);
-    inspections
-        .put("shaking9:keep-rules.txt", TreeShakingTest::shaking9OnlySuperMethodsKept);
-    inspections
-        .put("shaking11:keep-rules.txt", TreeShakingTest::shaking11OnlyOneClassKept);
-    inspections
-        .put("shaking11:keep-rules-keep-method.txt", TreeShakingTest::shaking11BothMethodsKept);
-    inspections.put("shaking12:keep-rules.txt",
+        .put("examples/shaking11:keep-rules.txt", TreeShakingTest::shaking11OnlyOneClassKept);
+    inspections.put("examples/shaking11:keep-rules-keep-method.txt",
+        TreeShakingTest::shaking11BothMethodsKept);
+    inspections.put("examples/shaking12:keep-rules.txt",
             TreeShakingTest::shaking12OnlyInstantiatedClassesHaveConstructors);
-    inspections.put("shaking13:keep-rules.txt",
+    inspections.put("examples/shaking13:keep-rules.txt",
             TreeShakingTest::shaking13EnsureFieldWritesCorrect);
-    inspections.put("shaking14:keep-rules.txt",
+    inspections.put("examples/shaking14:keep-rules.txt",
             TreeShakingTest::shaking14EnsureRightStaticMethodsLive);
-    inspections.put("shaking15:keep-rules.txt", TreeShakingTest::shaking15testDictionary);
-    inspections.put("shaking17:keep-rules.txt", TreeShakingTest::abstractMethodRemains);
-    inspections.put("annotationremoval:keep-rules.txt",
+    inspections.put("examples/shaking15:keep-rules.txt", TreeShakingTest::shaking15testDictionary);
+    inspections.put("examples/shaking17:keep-rules.txt", TreeShakingTest::abstractMethodRemains);
+    inspections.put("examples/annotationremoval:keep-rules.txt",
             TreeShakingTest::annotationRemovalHasNoInnerClassAnnotations);
-    inspections.put("annotationremoval:keep-rules-keep-innerannotation.txt",
+    inspections.put("examples/annotationremoval:keep-rules-keep-innerannotation.txt",
             TreeShakingTest::annotationRemovalHasAllInnerClassAnnotations);
+    inspections.put("examples/simpleproto1:keep-rules.txt",
+        TreeShakingTest::simpleproto1UnusedFieldIsGone);
+    inspections.put("examples/simpleproto2:keep-rules.txt",
+        TreeShakingTest::simpleproto2UnusedFieldsAreGone);
+    inspections.put("examples/nestedproto1:keep-rules.txt",
+        TreeShakingTest::nestedproto1UnusedFieldsAreGone);
+    inspections.put("examples/nestedproto2:keep-rules.txt",
+        TreeShakingTest::nestedproto2UnusedFieldsAreGone);
     inspections
-        .put("simpleproto1:keep-rules.txt", TreeShakingTest::simpleproto1UnusedFieldIsGone);
+        .put("examples/enumproto:keep-rules.txt", TreeShakingTest::enumprotoUnusedFieldsAreGone);
     inspections
-        .put("simpleproto2:keep-rules.txt", TreeShakingTest::simpleproto2UnusedFieldsAreGone);
+        .put("examples/repeatedproto:keep-rules.txt", TreeShakingTest::repeatedUnusedFieldsAreGone);
     inspections
-        .put("nestedproto1:keep-rules.txt", TreeShakingTest::nestedproto1UnusedFieldsAreGone);
-    inspections
-        .put("nestedproto2:keep-rules.txt", TreeShakingTest::nestedproto2UnusedFieldsAreGone);
-    inspections
-        .put("enumproto:keep-rules.txt", TreeShakingTest::enumprotoUnusedFieldsAreGone);
-    inspections
-        .put("repeatedproto:keep-rules.txt", TreeShakingTest::repeatedUnusedFieldsAreGone);
-    inspections
-        .put("oneofproto:keep-rules.txt", TreeShakingTest::oneofprotoUnusedFieldsAreGone);
+        .put("examples/oneofproto:keep-rules.txt", TreeShakingTest::oneofprotoUnusedFieldsAreGone);
 
     // Keys can be the name of the test or the name of the test followed by a colon and the name
     // of the keep file.
     Map<String, Collection<List<String>>> optionalRules = new HashMap<>();
-    optionalRules.put("shaking1", ImmutableList.of(
+    optionalRules.put("examples/shaking1", ImmutableList.of(
         Collections.singletonList(EMPTY_FLAGS),
         Lists.newArrayList(EMPTY_FLAGS, EMPTY_FLAGS)));
     List<Object[]> testCases = new ArrayList<>();
 
     Map<String, BiConsumer<String, String>> outputComparators = new HashMap<>();
     outputComparators
-        .put("assumenosideeffects1",
+        .put("examples/assumenosideeffects1",
             TreeShakingTest::assumenosideeffects1CheckOutput);
     outputComparators
-        .put("assumenosideeffects2",
+        .put("examples/assumenosideeffects2",
             TreeShakingTest::assumenosideeffects2CheckOutput);
     outputComparators
-        .put("assumenosideeffects3",
+        .put("examples/assumenosideeffects3",
             TreeShakingTest::assumenosideeffects3CheckOutput);
     outputComparators
-        .put("assumenosideeffects4",
+        .put("examples/assumenosideeffects4",
             TreeShakingTest::assumenosideeffects4CheckOutput);
     outputComparators
-        .put("assumenosideeffects5",
+        .put("examples/assumenosideeffects5",
             TreeShakingTest::assumenosideeffects5CheckOutput);
     outputComparators
-        .put("assumevalues1",
+        .put("examples/assumevalues1",
             TreeShakingTest::assumevalues1CheckOutput);
     outputComparators
-        .put("assumevalues2",
+        .put("examples/assumevalues2",
             TreeShakingTest::assumevalues2CheckOutput);
     outputComparators
-        .put("assumevalues3",
+        .put("examples/assumevalues3",
             TreeShakingTest::assumevalues3CheckOutput);
     outputComparators
-        .put("assumevalues4",
+        .put("examples/assumevalues4",
             TreeShakingTest::assumevalues4CheckOutput);
     outputComparators
-        .put("assumevalues5",
+        .put("examples/assumevalues5",
             TreeShakingTest::assumevalues5CheckOutput);
 
     Map<String, BiConsumer<DexInspector, DexInspector>> dexComparators = new HashMap<>();
     dexComparators
-        .put("shaking1:keep-rules-dont-shrink.txt", TreeShakingTest::checkSameStructure);
+        .put("examples/shaking1:keep-rules-dont-shrink.txt", TreeShakingTest::checkSameStructure);
     dexComparators
-        .put("shaking2:keep-rules-dont-shrink.txt", TreeShakingTest::checkSameStructure);
+        .put("examples/shaking2:keep-rules-dont-shrink.txt", TreeShakingTest::checkSameStructure);
     dexComparators
-        .put("shaking4:keep-rules-dont-shrink.txt", TreeShakingTest::checkSameStructure);
+        .put("examples/shaking4:keep-rules-dont-shrink.txt", TreeShakingTest::checkSameStructure);
 
     Set<String> usedInspections = new HashSet<>();
     Set<String> usedOptionalRules = new HashSet<>();
@@ -784,7 +795,7 @@
 
     for (String test : tests) {
       String mainClass = deriveMainClass(test);
-      File[] keepFiles = new File(ToolHelper.EXAMPLES_DIR + "/" + test)
+      File[] keepFiles = new File(ToolHelper.TESTS_DIR + test)
           .listFiles(file -> file.isFile() && file.getName().endsWith(".txt"));
       for (File keepFile : keepFiles) {
         String keepName = keepFile.getName();
@@ -863,12 +874,13 @@
   }
 
   private static String deriveMainClass(String testName) {
+    String testBaseName = testName.substring(testName.lastIndexOf('/') + 1);
     StringBuilder mainClass = new StringBuilder(testName.length() * 2 + 1);
-    mainClass.append(testName);
+    mainClass.append(testBaseName);
     mainClass.append('.');
-    mainClass.append(Character.toUpperCase(testName.charAt(0)));
-    for (int i = 1; i < testName.length(); i++) {
-      char next = testName.charAt(i);
+    mainClass.append(Character.toUpperCase(testBaseName.charAt(0)));
+    for (int i = 1; i < testBaseName.length(); i++) {
+      char next = testBaseName.charAt(i);
       if (!Character.isAlphabetic(next)) {
         break;
       }
@@ -888,24 +900,32 @@
       builder.appendClasspath(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib/classes.dex");
     };
 
-    if (outputComparator != null) {
-      String output1 = ToolHelper.runArtNoVerificationErrors(
-          Collections.singletonList(originalDex), mainClass, extraArtArgs, null);
-      String output2 = ToolHelper.runArtNoVerificationErrors(
-          Collections.singletonList(generated.toString()), mainClass, extraArtArgs, null);
-      outputComparator.accept(output1, output2);
-    } else {
-      String output = ToolHelper.checkArtOutputIdentical(Collections.singletonList(originalDex),
-          Collections.singletonList(generated.toString()), mainClass,
-          extraArtArgs, null);
-    }
+    if (Files.exists(Paths.get(originalDex))) {
+      if (outputComparator != null) {
+        String output1 = ToolHelper.runArtNoVerificationErrors(
+            Collections.singletonList(originalDex), mainClass, extraArtArgs, null);
+        String output2 = ToolHelper.runArtNoVerificationErrors(
+            Collections.singletonList(generated.toString()), mainClass, extraArtArgs, null);
+        outputComparator.accept(output1, output2);
+      } else {
+        ToolHelper.checkArtOutputIdentical(Collections.singletonList(originalDex),
+            Collections.singletonList(generated.toString()), mainClass,
+            extraArtArgs, null);
+      }
 
-    if (dexComparator != null) {
-      DexInspector ref = new DexInspector(Paths.get(originalDex));
-      DexInspector inspector = new DexInspector(generated,
-          minify.isMinify() ? temp.getRoot().toPath().resolve(DEFAULT_PROGUARD_MAP_FILE).toString()
-              : null);
-      dexComparator.accept(ref, inspector);
+      if (dexComparator != null) {
+        DexInspector ref = new DexInspector(Paths.get(originalDex));
+        DexInspector inspector = new DexInspector(generated,
+            minify.isMinify() ? temp.getRoot().toPath().resolve(DEFAULT_PROGUARD_MAP_FILE)
+                .toString()
+                : null);
+        dexComparator.accept(ref, inspector);
+      }
+    } else {
+      Assert.assertNull(outputComparator);
+      Assert.assertNull(dexComparator);
+      ToolHelper.runArtNoVerificationErrors(
+          Collections.singletonList(generated.toString()), mainClass, extraArtArgs, null);
     }
 
     if (inspection != null) {
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index dae1163..30d9111 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -20,6 +20,7 @@
 import java.io.File;
 import java.nio.file.Path;
 import java.util.List;
+import java.util.Set;
 import org.junit.Test;
 
 public class ForceProguardCompatibilityTest extends TestBase {
@@ -125,4 +126,50 @@
     runDefaultConstructorTest(false, TestClassWithDefaultConstructor.class, true);
     runDefaultConstructorTest(false, TestClassWithoutDefaultConstructor.class, false);
   }
+
+  public void testCheckCast(boolean forceProguardCompatibility, Class mainClass,
+      Class instantiatedClass, boolean containsCheckCast)
+      throws Exception {
+    R8Command.Builder builder = new CompatProguardCommandBuilder(forceProguardCompatibility, false);
+    builder.addProgramFiles(ToolHelper.getClassFileForTestClass(mainClass));
+    builder.addProgramFiles(ToolHelper.getClassFileForTestClass(instantiatedClass));
+    List<String> proguardConfig = ImmutableList.of(
+        "-keep class " + mainClass.getCanonicalName() + " {",
+        "  public static void main(java.lang.String[]);",
+        "}",
+        "-dontobfuscate");
+    builder.addProguardConfiguration(proguardConfig);
+
+    DexInspector inspector = new DexInspector(ToolHelper.runR8(builder.build()));
+    assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent());
+    ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(instantiatedClass));
+    assertEquals(containsCheckCast, clazz.isPresent());
+    assertEquals(containsCheckCast, clazz.isPresent());
+    if (clazz.isPresent()) {
+      assertEquals(forceProguardCompatibility && containsCheckCast, !clazz.isAbstract());
+    }
+
+    if (RUN_PROGUARD) {
+      Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
+      Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+      FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+      ToolHelper.runProguard(jarTestClasses(ImmutableList.of(mainClass, instantiatedClass)),
+          proguardedJar, proguardConfigFile);
+      Set<String> classesAfterProguard = readClassesInJar(proguardedJar);
+      assertTrue(classesAfterProguard.contains(mainClass.getCanonicalName()));
+      assertEquals(
+          containsCheckCast, classesAfterProguard.contains(instantiatedClass.getCanonicalName()));
+    }
+  }
+
+  @Test
+  public void checkCastTest() throws Exception {
+    testCheckCast(true, TestMainWithCheckCast.class, TestClassWithDefaultConstructor.class, true);
+    testCheckCast(
+        true, TestMainWithoutCheckCast.class, TestClassWithDefaultConstructor.class, false);
+    testCheckCast(
+        false, TestMainWithCheckCast.class, TestClassWithDefaultConstructor.class, true);
+    testCheckCast(
+        false, TestMainWithoutCheckCast.class, TestClassWithDefaultConstructor.class, false);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMainWithCheckCast.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMainWithCheckCast.java
new file mode 100644
index 0000000..d1eba28
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMainWithCheckCast.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.forceproguardcompatibility;
+
+public class TestMainWithCheckCast {
+
+  public static void main(String[] args) throws Exception {
+    String thisPackage = TestMainWithCheckCast.class.getPackage().getName();
+    Object o = (TestClassWithDefaultConstructor)
+        Class.forName(thisPackage + ".TestClassWithDefaultConstructor").newInstance();
+    System.out.println("Instance of: " + o.getClass().getCanonicalName());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMainWithoutCheckCast.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMainWithoutCheckCast.java
new file mode 100644
index 0000000..1ba2549
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMainWithoutCheckCast.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.forceproguardcompatibility;
+
+public class TestMainWithoutCheckCast {
+
+  public static void main(String[] args) throws Exception {
+    String thisPackage = TestMainWithoutCheckCast.class.getPackage().getName();
+    Object o = Class.forName(thisPackage + ".TestClassWithDefaultConstructor").newInstance();
+    System.out.println("Instantiated " + o.getClass().getCanonicalName());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
index 8bff0b3..308ce69 100644
--- a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
@@ -24,21 +24,6 @@
 
 public class IncludeDescriptorClassesTest extends TestBase {
 
-  private Set<String> readJarClasses(Path jar) throws IOException {
-    Set<String> result = new HashSet<>();
-    try (ZipFile zipFile = new ZipFile(jar.toFile())) {
-      final Enumeration<? extends ZipEntry> entries = zipFile.entries();
-      while (entries.hasMoreElements()) {
-        ZipEntry entry = entries.nextElement();
-        String name = entry.getName();
-        if (name.endsWith(".class")) {
-          result.add(name.substring(0, name.length() - ".class".length()).replace('/', '.'));
-        }
-      }
-    }
-    return result;
-  }
-
   private class Result {
     final DexInspector inspector;
     final Set<String> classesAfterProguard;
@@ -92,7 +77,7 @@
     if (false) {
       Path proguardedJar = temp.newFile("proguarded.jar").toPath();
       ToolHelper.runProguard(jarTestClasses(classes), proguardedJar, proguardConfig);
-      classesAfterProguard = readJarClasses(proguardedJar);
+      classesAfterProguard = readClassesInJar(proguardedJar);
     }
 
     return new Result(inspector, classesAfterProguard);