Merge "Publicize private constructors."
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 805bdf7..fd547cf 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -235,7 +235,7 @@
       System.setOut(new PrintStream(ByteStreams.nullOutputStream()));
     }
     // TODO(b/65390962): Remove this warning once the CF backend is complete.
-    if (options.isGeneratingClassFiles()) {
+    if (options.isGeneratingClassFiles() && !options.testing.suppressExperimentalCfBackendWarning) {
       options.reporter.warning(new StringDiagnostic(
           "R8 support for generating Java classfiles is incomplete and experimental. "
               + "Even if R8 appears to succeed, the generated output is likely incorrect."));
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 584b06d..f0ae9e0 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -618,6 +618,13 @@
     internal.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
     internal.dataResourceConsumer = internal.programConsumer.getDataResourceConsumer();
 
+    // Default is to remove Java assertion code as Dalvik and Art does not reliable support
+    // Java assertions. When generation class file output always keep the Java assertions code.
+    assert internal.disableAssertions;
+    if (internal.isGeneratingClassFiles()) {
+      internal.disableAssertions = false;
+    }
+
     // EXPERIMENTAL flags.
     assert !internal.forceProguardCompatibility;
     internal.forceProguardCompatibility = forceProguardCompatibility;
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 55c4134..0ef8a34 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -489,17 +489,22 @@
   }
 
   public boolean canTriggerStaticInitializer(DexType type, boolean ignoreTypeItself) {
+    DexClass clazz = definitionFor(type);
+    assert clazz != null;
+    return canTriggerStaticInitializer(clazz, ignoreTypeItself);
+  }
+
+  public boolean canTriggerStaticInitializer(DexClass clazz, boolean ignoreTypeItself) {
     Set<DexType> knownInterfaces = Sets.newIdentityHashSet();
 
     // Process superclass chain.
-    DexType clazz = type;
-    while (clazz != null && clazz != dexItemFactory.objectType) {
-      DexClass definition = definitionFor(clazz);
-      if (canTriggerStaticInitializer(definition) && (!ignoreTypeItself || clazz != type)) {
+    DexClass current = clazz;
+    while (current != null && current.type != dexItemFactory.objectType) {
+      if (canTriggerStaticInitializer(current) && (!ignoreTypeItself || current != clazz)) {
         return true;
       }
-      knownInterfaces.addAll(Arrays.asList(definition.interfaces.values));
-      clazz = definition.superType;
+      knownInterfaces.addAll(Arrays.asList(current.interfaces.values));
+      current = current.superType != null ? definitionFor(current.superType) : null;
     }
 
     // Process interfaces.
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 b8e0bd8..776cc42 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import java.util.Collections;
 import java.util.List;
 import org.objectweb.asm.Label;
@@ -230,7 +231,13 @@
       throw new Unimplemented(
           "Converting CfCode to IR not supported for DEX output of synchronized methods.");
     }
-    CfSourceCode source = new CfSourceCode(this, encodedMethod, callerPosition, origin);
+    CfSourceCode source =
+        new CfSourceCode(
+            this,
+            encodedMethod,
+            callerPosition,
+            origin,
+            options.lineNumberOptimization == LineNumberOptimization.ON);
     IRBuilder builder =
         (generator == null)
             ? new IRBuilder(encodedMethod, appInfo, source, options)
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 64c85f3..fc1ef7b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.Iterators;
 import java.util.Arrays;
@@ -138,16 +137,6 @@
     }
   }
 
-  public <E extends Throwable> void forEachMethodThrowing(
-      ThrowingConsumer<DexEncodedMethod, E> consumer) throws E {
-    for (DexEncodedMethod method : directMethods()) {
-      consumer.accept(method);
-    }
-    for (DexEncodedMethod method : virtualMethods()) {
-      consumer.accept(method);
-    }
-  }
-
   public DexEncodedMethod[] allMethodsSorted() {
     int vLen = virtualMethods.length;
     int dLen = directMethods.length;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 3586d6d..9a44dde 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -634,6 +634,7 @@
     // class inliner, null value indicates that the method is not eligible.
     private ClassInlinerEligibility classInlinerEligibility = null;
     private TrivialInitializer trivialInitializerInfo = null;
+    private boolean initializerEnablingJavaAssertions = false;
     private ParameterUsagesInfo parametersUsages = null;
     private BitSet kotlinNotNullParamHints = null;
 
@@ -705,6 +706,14 @@
       return this.trivialInitializerInfo;
     }
 
+    private void setInitializerEnablingJavaAssertions() {
+      this.initializerEnablingJavaAssertions = true;
+    }
+
+    public boolean isInitializerEnablingJavaAssertions() {
+      return initializerEnablingJavaAssertions;
+    }
+
     public long getReturnedConstant() {
       assert returnsConstant();
       return returnedConstant;
@@ -754,6 +763,10 @@
       forceInline = true;
     }
 
+    private void unsetForceInline() {
+      forceInline = false;
+    }
+
     private void markPublicized() {
       publicized = true;
     }
@@ -831,10 +844,18 @@
     ensureMutableOI().setTrivialInitializer(info);
   }
 
+  synchronized public void setInitializerEnablingJavaAssertions() {
+    ensureMutableOI().setInitializerEnablingJavaAssertions();
+  }
+
   synchronized public void markForceInline() {
     ensureMutableOI().markForceInline();
   }
 
+  public synchronized void unsetForceInline() {
+    ensureMutableOI().unsetForceInline();
+  }
+
   synchronized public void markPublicized() {
     ensureMutableOI().markPublicized();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 8ab2c1a..5bed061 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.ir.code.IRCode.INSTRUCTION_NUMBER_DELTA;
 
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DebugLocalInfo.PrintLevel;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -468,7 +467,7 @@
         return instruction;
       }
     }
-    throw new Unreachable();
+    return null;
   }
 
   public void clearUserInfo() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
new file mode 100644
index 0000000..66456cd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.code;
+
+import com.android.tools.r8.graph.DexMethod;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Maintains a set of canonical positions. Also supports appending a new caller at the end of the
+ * caller chain of a Position.
+ */
+public class CanonicalPositions {
+  private final Position callerPosition;
+  private final boolean preserveCaller;
+  private final Map<Position, Position> canonicalPositions;
+  private final Position preamblePosition;
+
+  /** For callerPosition and preserveCaller see canonicalizeCallerPosition. */
+  public CanonicalPositions(
+      Position callerPosition,
+      boolean preserveCaller,
+      int expectedPositionsCount,
+      DexMethod method) {
+    canonicalPositions =
+        new HashMap<>(1 + (callerPosition == null ? 0 : 1) + expectedPositionsCount);
+    this.preserveCaller = preserveCaller;
+    this.callerPosition = callerPosition;
+    if (callerPosition != null) {
+      canonicalPositions.put(callerPosition, callerPosition);
+    }
+    preamblePosition =
+        callerPosition == null
+            ? Position.synthetic(0, method, null)
+            : new Position(0, null, method, callerPosition);
+    canonicalPositions.put(preamblePosition, preamblePosition);
+  }
+
+  public Position getPreamblePosition() {
+    return preamblePosition;
+  }
+
+  /**
+   * Update the internal set if this is the first occurence of the position's value and return
+   * canonical instance of position.
+   */
+  public Position getCanonical(Position position) {
+    Position canonical = canonicalPositions.putIfAbsent(position, position);
+    return canonical != null ? canonical : position;
+  }
+
+  /**
+   * Append callerPosition (supplied in constructor) to the end of caller's caller chain and return
+   * the canonical instance. Always returns null if preserveCaller (also supplied in constructor) is
+   * false.
+   */
+  public Position canonicalizeCallerPosition(Position caller) {
+    if (!preserveCaller) {
+      return null;
+    }
+
+    if (caller == null) {
+      return callerPosition;
+    }
+    if (caller.callerPosition == null && callerPosition == null) {
+      return getCanonical(caller);
+    }
+    Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
+    return getCanonical(
+        caller.isNone()
+            ? Position.noneWithMethod(caller.method, callerOfCaller)
+            : new Position(caller.line, caller.file, caller.method, callerOfCaller));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index 6b3c89c..3b1b2d1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -34,6 +34,14 @@
     return inValues.get(0);
   }
 
+  public boolean isEnter() {
+    return type == Type.ENTER;
+  }
+
+  public boolean isExit() {
+    return type == Type.EXIT;
+  }
+
   @Override
   public void buildDex(DexBuilder builder) {
     // If the monitor object is an argument, we use the argument register for all the monitor
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueType.java b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
index c414342..69618b3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
@@ -48,6 +48,10 @@
     if (other == INT_OR_FLOAT_OR_NULL) {
       return other.meet(this);
     }
+    if (isPreciseType() && other.isPreciseType()) {
+      // Precise types must be identical, hitting the first check above.
+      throw new CompilationError("Cannot compute meet of types: " + this + " and " + other);
+    }
     switch (this) {
       case OBJECT:
         {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 650757e..382dd64 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -15,16 +16,23 @@
 import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.ThrowingBiConsumer;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.Deque;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -56,7 +64,7 @@
     this.shuffle = options.testing.irOrdering;
   }
 
-  private static class Node {
+  public static class Node {
 
     public final DexEncodedMethod method;
     private int invokeCount = 0;
@@ -68,7 +76,7 @@
     // Incoming calls to this method.
     private final Set<Node> callers = new LinkedHashSet<>();
 
-    private Node(DexEncodedMethod method) {
+    public Node(DexEncodedMethod method) {
       this.method = method;
     }
 
@@ -76,19 +84,20 @@
       return method.accessFlags.isBridge();
     }
 
-    private void addCallee(Node method) {
+    public void addCallee(Node method) {
       callees.add(method);
+      method.callers.add(this);
     }
 
-    private void addCaller(Node method) {
-      callers.add(method);
+    public boolean hasCallee(Node method) {
+      return callees.contains(method);
     }
 
     boolean isSelfRecursive() {
       return isSelfRecursive;
     }
 
-    boolean isLeaf() {
+    public boolean isLeaf() {
       return callees.isEmpty();
     }
 
@@ -96,7 +105,7 @@
     public String toString() {
       StringBuilder builder = new StringBuilder();
       builder.append("MethodNode for: ");
-      builder.append(method.qualifiedName());
+      builder.append(method.toSourceString());
       builder.append(" (");
       builder.append(callees.size());
       builder.append(" callees, ");
@@ -114,7 +123,7 @@
         builder.append("Callees:\n");
         for (Node call : callees) {
           builder.append("  ");
-          builder.append(call.method.qualifiedName());
+          builder.append(call.method.toSourceString());
           builder.append("\n");
         }
       }
@@ -122,7 +131,7 @@
         builder.append("Callers:\n");
         for (Node caller : callers) {
           builder.append("  ");
-          builder.append(caller.method.qualifiedName());
+          builder.append(caller.method.toSourceString());
           builder.append("\n");
         }
       }
@@ -136,8 +145,12 @@
   private final Set<DexEncodedMethod> singleCallSite = Sets.newIdentityHashSet();
   private final Set<DexEncodedMethod> doubleCallSite = Sets.newIdentityHashSet();
 
-  public static CallGraph build(DexApplication application, AppInfoWithLiveness appInfo,
-      GraphLense graphLense, InternalOptions options) {
+  public static CallGraph build(
+      DexApplication application,
+      AppInfoWithLiveness appInfo,
+      GraphLense graphLense,
+      InternalOptions options,
+      Timing timing) {
     CallGraph graph = new CallGraph(options);
     DexClass[] classes = application.classes().toArray(new DexClass[application.classes().size()]);
     Arrays.sort(classes, (DexClass a, DexClass b) -> a.type.slowCompareTo(b.type));
@@ -149,8 +162,13 @@
       }
     }
     assert allMethodsExists(application, graph);
-    graph.breakCycles();
-    assert graph.breakCycles() == 0;  // This time the cycles should be gone.
+
+    timing.begin("Cycle elimination");
+    CycleEliminator cycleEliminator = new CycleEliminator(graph.nodes.values(), options);
+    cycleEliminator.breakCycles();
+    timing.end();
+    assert cycleEliminator.breakCycles() == 0; // This time the cycles should be gone.
+
     graph.fillCallSiteSets(appInfo);
     return graph;
   }
@@ -196,9 +214,10 @@
 
   /**
    * Extract the next set of leaves (nodes with an call (outgoing) degree of 0) if any.
-   * <p>
-   * All nodes in the graph are extracted if called repeatedly until null is returned.
-   * Please note that there are no cycles in this graph (see {@link #breakCycles}).
+   *
+   * <p>All nodes in the graph are extracted if called repeatedly until null is returned. Please
+   * note that there are no cycles in this graph (see {@link CycleEliminator#breakCycles}).
+   *
    * <p>
    */
   private Set<DexEncodedMethod> extractLeaves() {
@@ -215,48 +234,197 @@
         .collect(Collectors.toCollection(LinkedHashSet::new)));
   }
 
-  private int traverse(Node node, Set<Node> stack, Set<Node> marked) {
-    int numberOfCycles = 0;
-    if (!marked.contains(node)) {
-      assert !stack.contains(node);
-      stack.add(node);
-      ArrayList<Node> toBeRemoved = null;
-      // Sort the callees before calling traverse recursively.
-      // This will ensure cycles are broken the same way across
-      // multiple invocations of the R8 compiler.
+  public static class CycleEliminator {
+
+    public static final String CYCLIC_FORCE_INLINING_MESSAGE =
+        "Unable to satisfy force inlining constraints due to cyclic force inlining";
+
+    private static class CallEdge {
+
+      private final Node caller;
+      private final Node callee;
+
+      public CallEdge(Node caller, Node callee) {
+        this.caller = caller;
+        this.callee = callee;
+      }
+    }
+
+    private final Collection<Node> nodes;
+    private final InternalOptions options;
+
+    // DFS stack.
+    private Deque<Node> stack = new ArrayDeque<>();
+
+    // Set of nodes on the DFS stack.
+    private Set<Node> stackSet = Sets.newIdentityHashSet();
+
+    // Set of nodes that have been visited entirely.
+    private Set<Node> marked = Sets.newIdentityHashSet();
+
+    private int numberOfCycles = 0;
+
+    public CycleEliminator(Collection<Node> nodes, InternalOptions options) {
+      this.options = options;
+
+      // Call to reorderNodes must happen after assigning options.
+      this.nodes =
+          options.testing.nondeterministicCycleElimination
+              ? reorderNodes(new ArrayList<>(nodes))
+              : nodes;
+    }
+
+    public int breakCycles() {
+      // Break cycles in this call graph by removing edges causing cycles.
+      for (Node node : nodes) {
+        traverse(node);
+      }
+      int result = numberOfCycles;
+      reset();
+      return result;
+    }
+
+    private void reset() {
+      assert stack.isEmpty();
+      assert stackSet.isEmpty();
+      marked.clear();
+      numberOfCycles = 0;
+    }
+
+    private void traverse(Node node) {
+      if (marked.contains(node)) {
+        // Already visited all nodes that can be reached from this node.
+        return;
+      }
+
+      push(node);
+
+      // Sort the callees before calling traverse recursively. This will ensure cycles are broken
+      // the same way across multiple invocations of the R8 compiler.
       Node[] callees = node.callees.toArray(new Node[node.callees.size()]);
       Arrays.sort(callees, (Node a, Node b) -> a.method.method.slowCompareTo(b.method.method));
+      if (options.testing.nondeterministicCycleElimination) {
+        reorderNodes(Arrays.asList(callees));
+      }
+
       for (Node callee : callees) {
-        if (stack.contains(callee)) {
-          if (toBeRemoved == null) {
-            toBeRemoved = new ArrayList<>();
+        if (stackSet.contains(callee)) {
+          // Found a cycle that needs to be eliminated.
+          numberOfCycles++;
+
+          if (edgeRemovalIsSafe(node, callee)) {
+            // Break the cycle by removing the edge node->callee.
+            callee.callers.remove(node);
+            node.callees.remove(callee);
+
+            if (Log.ENABLED) {
+              Log.info(
+                  CallGraph.class,
+                  "Removed call edge from method '%s' to '%s'",
+                  node.method.toSourceString(),
+                  callee.method.toSourceString());
+            }
+          } else {
+            // The cycle has a method that is marked as force inline.
+            LinkedList<Node> cycle = extractCycle(callee);
+
+            if (Log.ENABLED) {
+              Log.info(
+                  CallGraph.class, "Extracted cycle to find an edge that can safely be removed");
+            }
+
+            // Break the cycle by finding an edge that can be removed without breaking force
+            // inlining. If that is not possible, this call fails with a compilation error.
+            CallEdge edge = findCallEdgeForRemoval(cycle);
+
+            // The edge will be null if this cycle has already been eliminated as a result of
+            // another cycle elimination.
+            if (edge != null) {
+              assert edgeRemovalIsSafe(edge.caller, edge.callee);
+
+              // Break the cycle by removing the edge caller->callee.
+              edge.caller.callees.remove(edge.callee);
+              edge.callee.callers.remove(edge.caller);
+
+              if (Log.ENABLED) {
+                Log.info(
+                    CallGraph.class,
+                    "Removed call edge from force inlined method '%s' to '%s' to ensure that "
+                        + "force inlining will succeed",
+                    node.method.toSourceString(),
+                    callee.method.toSourceString());
+              }
+            }
+
+            // Recover the stack.
+            recoverStack(cycle);
           }
-          // We have a cycle; break it by removing node->callee.
-          toBeRemoved.add(callee);
-          callee.callers.remove(node);
         } else {
-          numberOfCycles += traverse(callee, stack, marked);
+          traverse(callee);
         }
       }
-      if (toBeRemoved != null) {
-        numberOfCycles += toBeRemoved.size();
-        node.callees.removeAll(toBeRemoved);
-      }
-      stack.remove(node);
+      pop(node);
       marked.add(node);
     }
-    return numberOfCycles;
-  }
 
-  private int breakCycles() {
-    // Break cycles in this call graph by removing edges causing cycles.
-    int numberOfCycles = 0;
-    Set<Node> stack = Sets.newIdentityHashSet();
-    Set<Node> marked = Sets.newIdentityHashSet();
-    for (Node node : nodes.values()) {
-      numberOfCycles += traverse(node, stack, marked);
+    private void push(Node node) {
+      stack.push(node);
+      boolean changed = stackSet.add(node);
+      assert changed;
     }
-    return numberOfCycles;
+
+    private void pop(Node node) {
+      Node popped = stack.pop();
+      assert popped == node;
+      boolean changed = stackSet.remove(node);
+      assert changed;
+    }
+
+    private LinkedList<Node> extractCycle(Node entry) {
+      LinkedList<Node> cycle = new LinkedList<>();
+      do {
+        assert !stack.isEmpty();
+        cycle.add(stack.pop());
+      } while (cycle.getLast() != entry);
+      return cycle;
+    }
+
+    private CallEdge findCallEdgeForRemoval(LinkedList<Node> extractedCycle) {
+      Node callee = extractedCycle.getLast();
+      for (Node caller : extractedCycle) {
+        if (!caller.callees.contains(callee)) {
+          // No need to break any edges since this cycle has already been broken previously.
+          assert !callee.callers.contains(caller);
+          return null;
+        }
+        if (edgeRemovalIsSafe(caller, callee)) {
+          return new CallEdge(caller, callee);
+        }
+        callee = caller;
+      }
+      throw new CompilationError(CYCLIC_FORCE_INLINING_MESSAGE);
+    }
+
+    private static boolean edgeRemovalIsSafe(Node caller, Node callee) {
+      // All call edges where the callee is a method that should be force inlined must be kept,
+      // to guarantee that the IR converter will process the callee before the caller.
+      return !callee.method.getOptimizationInfo().forceInline();
+    }
+
+    private void recoverStack(LinkedList<Node> extractedCycle) {
+      Iterator<Node> descendingIt = extractedCycle.descendingIterator();
+      while (descendingIt.hasNext()) {
+        stack.push(descendingIt.next());
+      }
+    }
+
+    private Collection<Node> reorderNodes(List<Node> nodes) {
+      assert options.testing.nondeterministicCycleElimination;
+      if (!InternalOptions.DETERMINISTIC_DEBUGGING) {
+        Collections.shuffle(nodes);
+      }
+      return nodes;
+    }
   }
 
   synchronized private Node ensureMethodNode(DexEncodedMethod method) {
@@ -268,7 +436,6 @@
     assert callee != null;
     if (caller != callee) {
       caller.addCallee(callee);
-      callee.addCaller(caller);
     } else {
       caller.isSelfRecursive = true;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 4cbebcf..92645c4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.CanonicalPositions;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
@@ -184,8 +185,6 @@
   private final Position callerPosition;
   private final Origin origin;
 
-  // Synthetic position with line = 0.
-  private final Position preamblePosition;
   private final Reference2IntMap<CfLabel> labelOffsets = new Reference2IntOpenHashMap<>();
   private TryHandlerList cachedTryHandlerList;
   private LocalVariableList cachedLocalVariableList;
@@ -196,21 +195,31 @@
   private Int2ReferenceMap<Int2ObjectMap<DebugLocalInfo>> definitelyLiveIncomingLocals =
       new Int2ReferenceOpenHashMap<>();
   private Int2ReferenceMap<CfState.Snapshot> incomingState = new Int2ReferenceOpenHashMap<>();
+  private final CanonicalPositions canonicalPositions;
 
   public CfSourceCode(
-      CfCode code, DexEncodedMethod method, Position callerPosition, Origin origin) {
+      CfCode code,
+      DexEncodedMethod method,
+      Position callerPosition,
+      Origin origin,
+      boolean preserveCaller) {
     this.code = code;
     this.method = method;
     this.callerPosition = callerPosition;
     this.origin = origin;
-    preamblePosition = Position.synthetic(0, method.method, null);
+    int cfPositionCount = 0;
     for (int i = 0; i < code.getInstructions().size(); i++) {
       CfInstruction instruction = code.getInstructions().get(i);
       if (instruction instanceof CfLabel) {
         labelOffsets.put((CfLabel) instruction, instructionOffset(i));
       }
+      if (instruction instanceof CfPosition) {
+        ++cfPositionCount;
+      }
     }
     this.state = new CfState(origin);
+    canonicalPositions =
+        new CanonicalPositions(callerPosition, preserveCaller, cfPositionCount, this.method.method);
   }
 
   @Override
@@ -383,7 +392,21 @@
       }
       state.clear();
     } else {
-      instruction.buildIR(builder, state, this);
+      if (instruction instanceof CfPosition) {
+        CfPosition cfPosition = (CfPosition) instruction;
+        Position position = cfPosition.getPosition();
+        Position newPosition =
+            canonicalPositions.getCanonical(
+                new Position(
+                    position.line,
+                    position.file,
+                    position.method,
+                    canonicalPositions.canonicalizeCallerPosition(position.callerPosition)));
+        CfPosition newCfPosition = new CfPosition(cfPosition.getLabel(), newPosition);
+        newCfPosition.buildIR(builder, state, this);
+      } else {
+        instruction.buildIR(builder, state, this);
+      }
       ensureDebugValueLiveness(builder);
       if (builder.getCFG().containsKey(currentInstructionIndex + 1)) {
         recordStateForTarget(currentInstructionIndex + 1, state.getSnapshot());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index b59794b..9a9b836 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -40,6 +40,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.code.CanonicalPositions;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
@@ -68,25 +69,20 @@
   private Instruction currentDexInstruction = null;
 
   private Position currentPosition = null;
-  private Map<Position, Position> canonicalPositions = null;
+  private final CanonicalPositions canonicalPositions;
 
   private final List<ValueType> argumentTypes;
 
   private List<DexDebugEntry> debugEntries = null;
   // In case of inlining the position of the invoke in the caller.
-  private final Position callerPosition;
   private final DexMethod method;
-  private final boolean preserveCaller;
-  private final Position preamblePosition;
 
   public DexSourceCode(
       DexCode code, DexEncodedMethod method, Position callerPosition, boolean preserveCaller) {
     this.code = code;
     this.proto = method.method.proto;
     this.accessFlags = method.accessFlags;
-    this.callerPosition = callerPosition;
     this.method = method.method;
-    this.preserveCaller = preserveCaller;
 
     argumentTypes = computeArgumentTypes();
     DexDebugInfo info = code.getDebugInfo();
@@ -94,18 +90,11 @@
       debugEntries = info.computeEntries(method.method);
     }
     canonicalPositions =
-        new HashMap<>(
-            1
-                + (callerPosition == null ? 0 : 1)
-                + (debugEntries == null ? 0 : debugEntries.size()));
-    if (callerPosition != null) {
-      canonicalPositions.put(callerPosition, callerPosition);
-    }
-    preamblePosition =
-        callerPosition == null
-            ? Position.synthetic(0, this.method, null)
-            : new Position(0, null, this.method, callerPosition);
-    canonicalPositions.put(preamblePosition, preamblePosition);
+        new CanonicalPositions(
+            callerPosition,
+            preserveCaller,
+            debugEntries == null ? 0 : debugEntries.size(),
+            this.method);
   }
 
   @Override
@@ -148,7 +137,7 @@
 
   @Override
   public void buildPrelude(IRBuilder builder) {
-    currentPosition = preamblePosition;
+    currentPosition = canonicalPositions.getPreamblePosition();
     if (code.incomingRegisterSize == 0) {
       return;
     }
@@ -197,7 +186,9 @@
   @Override
   public Position getDebugPositionAtOffset(int offset) {
     DexDebugEntry entry = getDebugEntryAtOffset(offset);
-    return entry == null ? preamblePosition : getCanonicalPositionAppendCaller(entry);
+    return entry == null
+        ? canonicalPositions.getPreamblePosition()
+        : getCanonicalPositionAppendCaller(entry);
   }
 
   @Override
@@ -250,7 +241,7 @@
     int offset = instructionOffset(instructionIndex);
     DexDebugEntry entry = getDebugEntryAtOffset(offset);
     if (entry == null) {
-      currentPosition = preamblePosition;
+      currentPosition = canonicalPositions.getPreamblePosition();
     } else {
       currentPosition = getCanonicalPositionAppendCaller(entry);
       if (entry.lineEntry && entry.address == offset) {
@@ -259,39 +250,17 @@
     }
   }
 
-  private Position getCanonicalPosition(Position position) {
-    Position canonical = canonicalPositions.putIfAbsent(position, position);
-    return canonical != null ? canonical : position;
-  }
-
-  private Position canonicalizeCallerPosition(Position caller) {
-    // We are not supposed to get here from getCanonicalPositionAppendCaller if !preserveCaller.
-    assert preserveCaller;
-
-    if (caller == null) {
-      return callerPosition;
-    }
-    if (caller.callerPosition == null && callerPosition == null) {
-      return getCanonicalPosition(caller);
-    }
-    Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
-    return getCanonicalPosition(
-        caller.isNone()
-            ? Position.noneWithMethod(caller.method, callerOfCaller)
-            : new Position(caller.line, caller.file, caller.method, callerOfCaller));
-  }
-
   private Position getCanonicalPositionAppendCaller(DexDebugEntry entry) {
     // If this instruction has already been inlined then this.method must be the outermost caller.
     assert entry.callerPosition == null
         || entry.callerPosition.getOutermostCaller().method == method;
 
-    return getCanonicalPosition(
+    return canonicalPositions.getCanonical(
         new Position(
             entry.line,
             entry.sourceFile,
             entry.method,
-            preserveCaller ? canonicalizeCallerPosition(entry.callerPosition) : null));
+            canonicalPositions.canonicalizeCallerPosition(entry.callerPosition)));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 63843ee..33531da 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -46,6 +46,7 @@
 import com.android.tools.r8.ir.optimize.NonNullTracker;
 import com.android.tools.r8.ir.optimize.Outliner;
 import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
+import com.android.tools.r8.ir.optimize.RedundantFieldLoadElimination;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
 import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -104,7 +105,10 @@
   private final Devirtualizer devirtualizer;
   private final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer;
 
+  private final boolean enableWholeProgramOptimizations;
+
   private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
+  private final OptimizationFeedback simpleOptimizationFeedback = new OptimizationFeedbackSimple();
   private DexString highestSortingString;
 
   private IRConverter(
@@ -134,6 +138,7 @@
         options.processCovariantReturnTypeAnnotations
             ? new CovariantReturnTypeAnnotationTransformer(this, appInfo.dexItemFactory)
             : null;
+    this.enableWholeProgramOptimizations = enableWholeProgramOptimizations;
     if (enableWholeProgramOptimizations) {
       assert appInfo.hasLiveness();
       this.nonNullTracker = new NonNullTracker();
@@ -172,7 +177,8 @@
     }
     this.classInliner =
         (options.enableClassInlining && options.enableInlining && inliner != null)
-            ? new ClassInliner(appInfo.dexItemFactory, options.classInliningInstructionLimit)
+            ? new ClassInliner(
+            appInfo.dexItemFactory, lambdaRewriter, options.classInliningInstructionLimit)
             : null;
   }
 
@@ -352,24 +358,34 @@
       ExecutorService executor) throws ExecutionException {
     List<Future<?>> futures = new ArrayList<>();
     for (DexProgramClass clazz : classes) {
-      futures.add(
-          executor.submit(
-              () -> {
-                clazz.forEachMethodThrowing(this::convertMethodToDex);
-                return null; // we want a Callable not a Runnable to be able to throw
-              }));
+      futures.add(executor.submit(() -> convertMethodsToDex(clazz)));
     }
     ThreadUtils.awaitFutures(futures);
   }
 
-  void convertMethodToDex(DexEncodedMethod method) {
+  private void convertMethodsToDex(DexProgramClass clazz) {
+    // When converting all methods on a class always convert <clinit> first.
+    for (DexEncodedMethod method : clazz.directMethods()) {
+      if (method.isClassInitializer()) {
+        convertMethodToDex(method);
+        break;
+      }
+    }
+    clazz.forEachMethod(method -> {
+      if (!method.isClassInitializer()) {
+        convertMethodToDex(method);
+      }
+    });
+  }
+
+  private void convertMethodToDex(DexEncodedMethod method) {
     assert options.isGeneratingDex();
     if (method.getCode() != null) {
       boolean matchesMethodFilter = options.methodMatchesFilter(method);
       if (matchesMethodFilter) {
         if (!(options.passthroughDexCode && method.getCode().isDexCode())) {
           // We do not process in call graph order, so anything could be a leaf.
-          rewriteCode(method, ignoreOptimizationFeedback, x -> true, CallSiteInformation.empty(),
+          rewriteCode(method, simpleOptimizationFeedback, x -> true, CallSiteInformation.empty(),
               Outliner::noProcessing);
         }
         updateHighestSortingStrings(method);
@@ -401,8 +417,8 @@
     OptimizationFeedback directFeedback = new OptimizationFeedbackDirect();
     {
       timing.begin("Build call graph");
-      CallGraph callGraph = CallGraph
-          .build(application, appInfo.withLiveness(), graphLense, options);
+      CallGraph callGraph =
+          CallGraph.build(application, appInfo.withLiveness(), graphLense, options, timing);
       timing.end();
       timing.begin("IR conversion phase 1");
       BiConsumer<IRCode, DexEncodedMethod> outlineHandler =
@@ -561,7 +577,7 @@
     try {
       codeRewriter.enterCachedClass(clazz);
       // Process the generated class, but don't apply any outlining.
-      clazz.forEachMethodThrowing(this::optimizeSynthesizedMethod);
+      clazz.forEachMethod(this::optimizeSynthesizedMethod);
     } finally {
       codeRewriter.leaveCachedClass(clazz);
     }
@@ -668,7 +684,7 @@
       codeRewriter.removeSwitchMaps(code);
     }
     if (options.disableAssertions) {
-      codeRewriter.disableAssertions(code);
+      codeRewriter.disableAssertions(appInfo, method, code, feedback);
     }
     if (options.enableNonNullTracking && nonNullTracker != null) {
       nonNullTracker.addNonNull(code);
@@ -695,6 +711,7 @@
     codeRewriter.rewriteSwitch(code);
     codeRewriter.processMethodsNeverReturningNormally(code);
     codeRewriter.simplifyIf(code, typeEnvironment);
+    new RedundantFieldLoadElimination(appInfo, code, enableWholeProgramOptimizations).run();
 
     if (options.testing.invertConditionals) {
       invertConditionalsForTesting(code);
@@ -728,11 +745,6 @@
       assert code.isConsistentSSA();
     }
 
-    if (interfaceMethodRewriter != null) {
-      interfaceMethodRewriter.rewriteMethodReferences(method, code);
-      assert code.isConsistentSSA();
-    }
-
     if (classInliner != null) {
       // Class inliner should work before lambda merger, so if it inlines the
       // lambda, it is not get collected by merger.
@@ -750,6 +762,11 @@
       assert code.isConsistentSSA();
     }
 
+    if (interfaceMethodRewriter != null) {
+      interfaceMethodRewriter.rewriteMethodReferences(method, code);
+      assert code.isConsistentSSA();
+    }
+
     if (lambdaMerger != null) {
       lambdaMerger.processMethodCode(method, code);
       assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 9a88674..1877053 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -15,6 +15,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueMethodType;
+import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -36,11 +38,11 @@
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
-import java.util.stream.Collectors;
 
 public class LensCodeRewriter {
 
@@ -80,16 +82,7 @@
                   callSite.methodProto, graphLense::lookupType, protoFixupCache);
           DexMethodHandle newBootstrapMethod =
               rewriteDexMethodHandle(callSite.bootstrapMethod, method);
-          List<DexValue> newArgs = callSite.bootstrapArgs.stream().map(
-              (arg) -> {
-                if (arg instanceof DexValueMethodHandle) {
-                  return new DexValueMethodHandle(
-                      rewriteDexMethodHandle(((DexValueMethodHandle) arg).value, method));
-                }
-                return arg;
-              })
-              .collect(Collectors.toList());
-
+          List<DexValue> newArgs = rewriteBootstrapArgs(callSite.bootstrapArgs, method);
           if (!newMethodProto.equals(callSite.methodProto)
               || newBootstrapMethod != callSite.bootstrapMethod
               || !newArgs.equals(callSite.bootstrapArgs)) {
@@ -225,6 +218,47 @@
     assert code.isConsistentSSA();
   }
 
+  private List<DexValue> rewriteBootstrapArgs(
+      List<DexValue> bootstrapArgs, DexEncodedMethod method) {
+    List<DexValue> newBoostrapArgs = null;
+    boolean changed = false;
+    for (int i = 0; i < bootstrapArgs.size(); i++) {
+      DexValue argument = bootstrapArgs.get(i);
+      DexValue newArgument = null;
+      if (argument instanceof DexValueMethodHandle) {
+        DexMethodHandle oldHandle = ((DexValueMethodHandle) argument).value;
+        DexMethodHandle newHandle = rewriteDexMethodHandle(oldHandle, method);
+        if (newHandle != oldHandle) {
+          newArgument = new DexValueMethodHandle(newHandle);
+        }
+      } else if (argument instanceof DexValueMethodType) {
+        DexProto oldProto = ((DexValueMethodType) argument).value;
+        DexProto newProto =
+            appInfo.dexItemFactory.applyClassMappingToProto(
+                oldProto, graphLense::lookupType, protoFixupCache);
+        if (newProto != oldProto) {
+          newArgument = new DexValueMethodType(newProto);
+        }
+      } else if (argument instanceof DexValueType) {
+        DexType oldType = ((DexValueType) argument).value;
+        DexType newType = graphLense.lookupType(oldType);
+        if (newType != oldType) {
+          newArgument = new DexValueType(newType);
+        }
+      }
+      if (newArgument != null) {
+        if (newBoostrapArgs == null) {
+          newBoostrapArgs = new ArrayList<>(bootstrapArgs.subList(0, i));
+        }
+        newBoostrapArgs.add(newArgument);
+        changed = true;
+      } else if (newBoostrapArgs != null) {
+        newBoostrapArgs.add(argument);
+      }
+    }
+    return changed ? newBoostrapArgs : bootstrapArgs;
+  }
+
   private DexMethodHandle rewriteDexMethodHandle(
       DexMethodHandle methodHandle, DexEncodedMethod context) {
     if (methodHandle.isMethodHandle()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
index c26aecf..49bfac8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
@@ -21,6 +21,7 @@
   void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
   void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibility eligibility);
   void setTrivialInitializer(DexEncodedMethod method, TrivialInitializer info);
+  void setInitializerEnablingJavaAssertions(DexEncodedMethod method);
   void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo);
   void setKotlinNotNullParamHints(DexEncodedMethod method, BitSet hints);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
index 2b1a6db..1d7a2d6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
@@ -60,6 +60,11 @@
   }
 
   @Override
+  public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+    method.setInitializerEnablingJavaAssertions();
+  }
+
+  @Override
   public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
     method.setParameterUsages(parameterUsagesInfo);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
index a547b36..2fb2969 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
@@ -44,6 +44,10 @@
   }
 
   @Override
+  public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+  }
+
+  @Override
   public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
new file mode 100644
index 0000000..b31f28a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
+import com.android.tools.r8.graph.ParameterUsagesInfo;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import java.util.BitSet;
+
+public class OptimizationFeedbackSimple implements OptimizationFeedback {
+
+  @Override
+  public void methodReturnsArgument(DexEncodedMethod method, int argument) {
+    // Ignored.
+  }
+
+  @Override
+  public void methodReturnsConstant(DexEncodedMethod method, long value) {
+    // Ignored.
+  }
+
+  @Override
+  public void methodNeverReturnsNull(DexEncodedMethod method) {
+    // Ignored.
+  }
+
+  @Override
+  public void methodNeverReturnsNormally(DexEncodedMethod method) {
+    // Ignored.
+  }
+
+  @Override
+  public void markProcessed(DexEncodedMethod method, Constraint state) {
+    // Just as processed, don't provide any inlining constraints.
+    method.markProcessed(Constraint.NEVER);
+  }
+
+  @Override
+  public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
+    // Ignored.
+  }
+
+  @Override
+  public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
+    // Ignored.
+  }
+
+  @Override
+  public void setClassInlinerEligibility(
+      DexEncodedMethod method, ClassInlinerEligibility eligibility) {
+    // Ignored.
+  }
+
+  @Override
+  public void setTrivialInitializer(DexEncodedMethod method, TrivialInitializer info) {
+    // Ignored.
+  }
+
+  @Override
+  public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+    method.setInitializerEnablingJavaAssertions();
+  }
+
+  @Override
+  public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
+    // Ignored.
+  }
+
+  @Override
+  public void setKotlinNotNullParamHints(DexEncodedMethod method, BitSet hints) {
+    // Ignored.
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 81407bd..86f3f17 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -29,11 +29,13 @@
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.google.common.base.Suppliers;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
 
 /**
  * Represents lambda class generated for a lambda descriptor in context
@@ -64,6 +66,8 @@
   final Target target;
   final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
   private final Collection<DexProgramClass> synthesizedFrom = new ArrayList<DexProgramClass>(1);
+  private final Supplier<DexProgramClass> lazyDexClass =
+      Suppliers.memoize(this::synthesizeLambdaClass); // NOTE: thread-safe.
 
   LambdaClass(LambdaRewriter rewriter, DexType accessedFrom,
       DexType lambdaClassType, LambdaDescriptor descriptor) {
@@ -119,7 +123,11 @@
     return rewriter.factory.createType(lambdaClassDescriptor.toString());
   }
 
-  final DexProgramClass synthesizeLambdaClass() {
+  final DexProgramClass getLambdaClass() {
+    return lazyDexClass.get();
+  }
+
+  private DexProgramClass synthesizeLambdaClass() {
     return new DexProgramClass(
         type,
         null,
@@ -171,7 +179,7 @@
                 Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
-            new SynthesizedCode(new LambdaMainMethodSourceCode(this, mainMethod)));
+            new SynthesizedCode(() -> new LambdaMainMethodSourceCode(this, mainMethod)));
 
     // Synthesize bridge methods.
     for (DexProto bridgeProto : descriptor.bridges) {
@@ -188,7 +196,7 @@
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               new SynthesizedCode(
-                  new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
+                  () -> new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
     }
     return methods;
   }
@@ -208,7 +216,7 @@
                 true),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
-            new SynthesizedCode(new LambdaConstructorSourceCode(this)));
+            new SynthesizedCode(() -> new LambdaConstructorSourceCode(this)));
 
     // Class constructor for stateless lambda classes.
     if (stateless) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index e7686db..51b8b8a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -141,7 +141,6 @@
           if (method.name == deserializeLambdaMethodName &&
               method.proto == deserializeLambdaMethodProto) {
             assert encoded.accessFlags.isStatic();
-            assert encoded.accessFlags.isPrivate();
             assert encoded.accessFlags.isSynthetic();
 
             DexEncodedMethod[] newMethods = new DexEncodedMethod[methodCount - 1];
@@ -170,10 +169,19 @@
     }
   }
 
+  /**
+   * Returns a synthetic class for desugared lambda or `null` if the `type`
+   * does not represent one. Method can be called concurrently.
+   */
+  public DexProgramClass getLambdaClass(DexType type) {
+    LambdaClass lambdaClass = getKnown(knownLambdaClasses, type);
+    return lambdaClass == null ? null : lambdaClass.getLambdaClass();
+  }
+
   /** Generates lambda classes and adds them to the builder. */
   public void synthesizeLambdaClasses(Builder<?> builder) {
     for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
-      DexProgramClass synthesizedClass = lambdaClass.synthesizeLambdaClass();
+      DexProgramClass synthesizedClass = lambdaClass.getLambdaClass();
       converter.optimizeSynthesizedClass(synthesizedClass);
       builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
     }
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 94e8147..d87a508 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
@@ -1265,7 +1265,29 @@
    * }
    * </pre>
    */
-  public void disableAssertions(IRCode code) {
+  public void disableAssertions(
+      AppInfo appInfo, DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+    if (method.isClassInitializer()) {
+      if (!hasJavacClinitAssertionCode(code)) {
+        return;
+      }
+      // Mark the clinit as having code to turn on assertions.
+      feedback.setInitializerEnablingJavaAssertions(method);
+    } else {
+      // If the clinit of this class did not have have code to turn on assertions don't try to
+      // remove assertion code from the method.
+      DexClass clazz = appInfo.definitionFor(method.method.holder);
+      if (clazz == null) {
+        return;
+      }
+      DexEncodedMethod clinit = clazz.getClassInitializer();
+      if (clinit == null
+          || !clinit.isProcessed()
+          || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
+        return;
+      }
+    }
+
     InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
       Instruction current = iterator.next();
@@ -1288,6 +1310,78 @@
     }
   }
 
+  private boolean isClassDesiredAssertionStatusInvoke(Instruction instruction) {
+    return instruction.isInvokeMethod()
+    && instruction.asInvokeMethod().getInvokedMethod()
+        == dexItemFactory.classMethods.desiredAssertionStatus;
+  }
+
+  private boolean isAssertionsDisabledFieldPut(Instruction instruction) {
+    return instruction.isStaticPut()
+        && instruction.asStaticPut().getField().name == dexItemFactory.assertionsDisabled;
+  }
+
+  private boolean isNotDebugInstruction(Instruction instruction) {
+    return !instruction.isDebugInstruction();
+  }
+
+  private Value blockWithSingleConstNumberAndGoto(BasicBlock block) {
+    InstructionIterator iterator  = block.iterator();
+    Instruction constNumber = iterator.nextUntil(this::isNotDebugInstruction);
+    if (constNumber == null || !constNumber.isConstNumber()) {
+      return null;
+    }
+    Instruction exit = iterator.nextUntil(this::isNotDebugInstruction);
+    return exit != null && exit.isGoto() ? constNumber.outValue() : null;
+  }
+
+  private Value blockWithAssertionsDisabledFieldPut(BasicBlock block) {
+    InstructionIterator iterator  = block.iterator();
+    Instruction fieldPut = iterator.nextUntil(this::isNotDebugInstruction);
+    return fieldPut != null
+        && isAssertionsDisabledFieldPut(fieldPut) ? fieldPut.inValues().get(0) : null;
+  }
+
+  private boolean hasJavacClinitAssertionCode(IRCode code) {
+    InstructionIterator iterator = code.instructionIterator();
+    Instruction current = iterator.nextUntil(this::isClassDesiredAssertionStatusInvoke);
+    if (current == null) {
+      return false;
+    }
+
+    Value DesiredAssertionStatus = current.outValue();
+    assert iterator.hasNext();
+    current = iterator.next();
+    if (!current.isIf()
+        || !current.asIf().isZeroTest()
+        || current.asIf().inValues().get(0) != DesiredAssertionStatus) {
+      return false;
+    }
+
+    If theIf = current.asIf();
+    BasicBlock trueTarget = theIf.getTrueTarget();
+    BasicBlock falseTarget = theIf.fallthroughBlock();
+    if (trueTarget == falseTarget) {
+      return false;
+    }
+
+    Value trueValue = blockWithSingleConstNumberAndGoto(trueTarget);
+    Value falseValue = blockWithSingleConstNumberAndGoto(falseTarget);
+    if (trueValue == null
+        || falseValue == null
+        || (trueTarget.exit().asGoto().getTarget() != falseTarget.exit().asGoto().getTarget())) {
+      return false;
+    }
+
+    BasicBlock target = trueTarget.exit().asGoto().getTarget();
+    Value storeValue = blockWithAssertionsDisabledFieldPut(target);
+    return storeValue != null
+        && storeValue.isPhi()
+        && storeValue.asPhi().getOperands().size() == 2
+        && storeValue.asPhi().getOperands().contains(trueValue)
+        && storeValue.asPhi().getOperands().contains(falseValue);
+  }
+
   // Check if the static put is a constant derived from the class holding the method.
   // This checks for java.lang.Class.getName and java.lang.Class.getSimpleName.
   private boolean isClassNameConstant(DexEncodedMethod method, StaticPut put) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index d3d1e2f..85f7a39 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -60,7 +60,8 @@
 
   @Override
   public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
-    assert target.isProcessed();
+    // Do nothing. If the method is not yet processed, we still should
+    // be able to build IR for inlining, though.
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
new file mode 100644
index 0000000..905d4a8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -0,0 +1,223 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Value;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Eliminate redundant field loads.
+ *
+ * <p>Simple algorithm that goes through all blocks in one pass in dominator order and propagates
+ * active field sets across control-flow edges where the target has only one predecessor.
+ */
+// TODO(ager): Evaluate speed/size for computing active field sets in a fixed-point computation.
+public class RedundantFieldLoadElimination {
+
+  private final AppInfo appInfo;
+  private final DexEncodedMethod method;
+  private final IRCode code;
+  private final boolean enableWholeProgramOptimizations;
+  private final DominatorTree dominatorTree;
+
+  // Maps keeping track of fields that have an already loaded value at basic block entry.
+  private final HashMap<BasicBlock, HashMap<FieldAndObject, Instruction>>
+      activeInstanceFieldsAtEntry = new HashMap<>();
+  private final HashMap<BasicBlock, HashMap<DexField, Instruction>> activeStaticFieldsAtEntry =
+      new HashMap<>();
+
+  // Maps keeping track of fields with already loaded values for the current block during
+  // elimination.
+  private HashMap<FieldAndObject, Instruction> activeInstanceFields;
+  private HashMap<DexField, Instruction> activeStaticFields;
+
+  public RedundantFieldLoadElimination(
+      AppInfo appInfo, IRCode code, boolean enableWholeProgramOptimizations) {
+    this.appInfo = appInfo;
+    this.method = code.method;
+    this.code = code;
+    this.enableWholeProgramOptimizations = enableWholeProgramOptimizations;
+    dominatorTree = new DominatorTree(code);
+  }
+
+  private static class FieldAndObject {
+    private final DexField field;
+    private final Value object;
+
+    private FieldAndObject(DexField field, Value receiver) {
+      this.field = field;
+      this.object = receiver;
+    }
+
+    @Override
+    public int hashCode() {
+      return field.hashCode() * 7 + object.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (!(other instanceof FieldAndObject)) {
+        return false;
+      }
+      FieldAndObject o = (FieldAndObject) other;
+      return o.object == object && o.field == field;
+    }
+  }
+
+  private boolean couldBeVolatile(DexField field) {
+    if (!enableWholeProgramOptimizations && field.getHolder() != method.method.getHolder()) {
+      return true;
+    }
+    DexEncodedField definition = appInfo.definitionFor(field);
+    return definition == null || definition.accessFlags.isVolatile();
+  }
+
+  public void run() {
+    for (BasicBlock block : dominatorTree.getSortedBlocks()) {
+      activeInstanceFields =
+          activeInstanceFieldsAtEntry.containsKey(block)
+              ? activeInstanceFieldsAtEntry.get(block)
+              : new HashMap<>();
+      activeStaticFields =
+          activeStaticFieldsAtEntry.containsKey(block)
+              ? activeStaticFieldsAtEntry.get(block)
+              : new HashMap<>();
+      InstructionListIterator it = block.listIterator();
+      while (it.hasNext()) {
+        Instruction instruction = it.next();
+        if (instruction.isFieldInstruction()) {
+          DexField field = instruction.asFieldInstruction().getField();
+          if (instruction.isInstancePut() || instruction.isStaticPut()) {
+            killActiveFields(instruction.asFieldInstruction());
+          } else if (couldBeVolatile(field)) {
+            assert instruction.isInstanceGet() || instruction.isStaticGet();
+            killAllActiveFields();
+          } else {
+            assert instruction.isInstanceGet() || instruction.isStaticGet();
+            assert !couldBeVolatile(field);
+            if (instruction.isInstanceGet() && !instruction.outValue().hasLocalInfo()) {
+              Value object = instruction.asInstanceGet().object();
+              FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+              if (activeInstanceFields.containsKey(fieldAndObject)) {
+                Instruction active = activeInstanceFields.get(fieldAndObject);
+                eliminateRedundantRead(it, instruction, active);
+              } else {
+                activeInstanceFields.put(fieldAndObject, instruction);
+              }
+            } else if (instruction.isStaticGet() && !instruction.outValue().hasLocalInfo()) {
+              if (activeStaticFields.containsKey(field)) {
+                Instruction active = activeStaticFields.get(field);
+                eliminateRedundantRead(it, instruction, active);
+              } else {
+                // A field get on a different class can cause <clinit> to run and change static
+                // field values.
+                killActiveFields(instruction.asFieldInstruction());
+                activeStaticFields.put(field, instruction);
+              }
+            }
+          }
+        }
+        if ((instruction.isMonitor() && instruction.asMonitor().isEnter())
+            || instruction.isInvokeMethod()) {
+          killAllActiveFields();
+        }
+      }
+      propagateActiveFieldsFrom(block);
+    }
+    assert code.isConsistentSSA();
+  }
+
+  private void propagateActiveFieldsFrom(BasicBlock block) {
+    for (BasicBlock successor : block.getSuccessors()) {
+      // Allow propagation across exceptional edges, just be careful not to propagate if the
+      // throwing instruction is a field instruction.
+      if (successor.getPredecessors().size() == 1) {
+        if (block.hasCatchSuccessor(successor)) {
+          Instruction exceptionalExit = block.exceptionalExit();
+          if (exceptionalExit != null && exceptionalExit.isFieldInstruction()) {
+            killActiveFieldsForExceptionalExit(exceptionalExit.asFieldInstruction());
+          }
+        }
+        assert !activeInstanceFieldsAtEntry.containsKey(successor);
+        activeInstanceFieldsAtEntry.put(successor, new HashMap<>(activeInstanceFields));
+        assert !activeStaticFieldsAtEntry.containsKey(successor);
+        activeStaticFieldsAtEntry.put(successor, new HashMap<>(activeStaticFields));
+      }
+    }
+  }
+
+  private void killAllActiveFields() {
+    activeInstanceFields.clear();
+    activeStaticFields.clear();
+  }
+
+  private void killActiveFields(FieldInstruction instruction) {
+    DexField field = instruction.getField();
+    if (instruction.isInstancePut()) {
+      // Remove all the field/object pairs that refer to this field to make sure
+      // that we are conservative.
+      List<FieldAndObject> keysToRemove = new ArrayList<>();
+      for (FieldAndObject key : activeInstanceFields.keySet()) {
+        if (key.field == field) {
+          keysToRemove.add(key);
+        }
+      }
+      keysToRemove.forEach((k) -> activeInstanceFields.remove(k));
+    } else if (instruction.isInstanceGet()) {
+      Value object = instruction.asInstanceGet().object();
+      FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+      activeInstanceFields.remove(fieldAndObject);
+    } else if (instruction.isStaticPut()) {
+      if (field.clazz != code.method.method.holder) {
+        // Accessing a static field on a different object could cause <clinit> to run which
+        // could modify any static field on any other object.
+        activeStaticFields.clear();
+      } else {
+        activeStaticFields.remove(field);
+      }
+    } else if (instruction.isStaticGet()) {
+      if (field.clazz != code.method.method.holder) {
+        // Accessing a static field on a different object could cause <clinit> to run which
+        // could modify any static field on any other object.
+        activeStaticFields.clear();
+      }
+    }
+  }
+
+  // If a field get instruction throws an exception it did not have an effect on the
+  // value of the field. Therefore, when propagating across exceptional edges for a
+  // field get instruction we have to exclude that field from the set of known
+  // field values.
+  private void killActiveFieldsForExceptionalExit(FieldInstruction instruction) {
+    DexField field = instruction.getField();
+    if (instruction.isInstanceGet()) {
+      Value object = instruction.asInstanceGet().object();
+      FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+      activeInstanceFields.remove(fieldAndObject);
+    } else if (instruction.isStaticGet()) {
+      activeStaticFields.remove(field);
+    }
+  }
+
+  private void eliminateRedundantRead(
+      InstructionListIterator it, Instruction redundant, Instruction active) {
+    redundant.outValue().replaceUsers(active.outValue());
+    it.removeOrReplaceByDebugLocalRead();
+    active.outValue().uniquePhiUsers().forEach(Phi::removeTrivialPhi);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 5bf2c74..3a2371d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -8,10 +8,10 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
 import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -27,15 +27,18 @@
 
 public final class ClassInliner {
   private final DexItemFactory factory;
+  private final LambdaRewriter lambdaRewriter;
   private final int totalMethodInstructionLimit;
-  private final ConcurrentHashMap<DexType, Boolean> knownClasses = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexClass, Boolean> knownClasses = new ConcurrentHashMap<>();
 
   public interface InlinerAction {
     void inline(Map<InvokeMethod, InliningInfo> methods);
   }
 
-  public ClassInliner(DexItemFactory factory, int totalMethodInstructionLimit) {
+  public ClassInliner(DexItemFactory factory,
+      LambdaRewriter lambdaRewriter, int totalMethodInstructionLimit) {
     this.factory = factory;
+    this.lambdaRewriter = lambdaRewriter;
     this.totalMethodInstructionLimit = totalMethodInstructionLimit;
   }
 
@@ -142,8 +145,8 @@
       while (rootsIterator.hasNext()) {
         Instruction root = rootsIterator.next();
         InlineCandidateProcessor processor =
-            new InlineCandidateProcessor(factory, appInfo,
-                type -> isClassEligible(appInfo, type),
+            new InlineCandidateProcessor(factory, appInfo, lambdaRewriter,
+                clazz -> isClassEligible(appInfo, clazz),
                 isProcessedConcurrently, method, root);
 
         // Assess eligibility of instance and class.
@@ -180,7 +183,7 @@
     } while (repeat);
   }
 
-  private boolean isClassEligible(AppInfo appInfo, DexType clazz) {
+  private boolean isClassEligible(AppInfo appInfo, DexClass clazz) {
     Boolean eligible = knownClasses.get(clazz);
     if (eligible == null) {
       Boolean computed = computeClassEligible(appInfo, clazz);
@@ -195,15 +198,14 @@
   //   - is not an abstract class or interface
   //   - does not declare finalizer
   //   - does not trigger any static initializers except for its own
-  private boolean computeClassEligible(AppInfo appInfo, DexType clazz) {
-    DexClass definition = appInfo.definitionFor(clazz);
-    if (definition == null || definition.isLibraryClass() ||
-        definition.accessFlags.isAbstract() || definition.accessFlags.isInterface()) {
+  private boolean computeClassEligible(AppInfo appInfo, DexClass clazz) {
+    if (clazz == null || clazz.isLibraryClass() ||
+        clazz.accessFlags.isAbstract() || clazz.accessFlags.isInterface()) {
       return false;
     }
 
     // Class must not define finalizer.
-    for (DexEncodedMethod method : definition.virtualMethods()) {
+    for (DexEncodedMethod method : clazz.virtualMethods()) {
       if (method.method.name == factory.finalizeMethodName &&
           method.method.proto == factory.objectMethods.finalize.proto) {
         return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 57ce42f..5fedf37 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -53,7 +54,8 @@
 
   private final DexItemFactory factory;
   private final AppInfoWithLiveness appInfo;
-  private final Predicate<DexType> isClassEligible;
+  private final LambdaRewriter lambdaRewriter;
+  private final Predicate<DexClass> isClassEligible;
   private final Predicate<DexEncodedMethod> isProcessedConcurrently;
   private final DexEncodedMethod method;
   private final Instruction root;
@@ -61,6 +63,7 @@
   private Value eligibleInstance;
   private DexType eligibleClass;
   private DexClass eligibleClassDefinition;
+  private boolean isDesugaredLambda;
 
   private final Map<InvokeMethod, InliningInfo> methodCallsOnInstance
       = new IdentityHashMap<>();
@@ -73,10 +76,11 @@
 
   InlineCandidateProcessor(
       DexItemFactory factory, AppInfoWithLiveness appInfo,
-      Predicate<DexType> isClassEligible,
+      LambdaRewriter lambdaRewriter, Predicate<DexClass> isClassEligible,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
       DexEncodedMethod method, Instruction root) {
     this.factory = factory;
+    this.lambdaRewriter = lambdaRewriter;
     this.isClassEligible = isClassEligible;
     this.method = method;
     this.root = root;
@@ -99,6 +103,11 @@
     eligibleClass = isNewInstance() ?
         root.asNewInstance().clazz : root.asStaticGet().getField().type;
     eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
+    if (eligibleClassDefinition == null && lambdaRewriter != null) {
+      // Check if the class is synthesized for a desugared lambda
+      eligibleClassDefinition = lambdaRewriter.getLambdaClass(eligibleClass);
+      isDesugaredLambda = eligibleClassDefinition != null;
+    }
     return eligibleClassDefinition != null;
   }
 
@@ -114,7 +123,7 @@
   //      * class has class initializer marked as TrivialClassInitializer, and
   //        class initializer initializes the field we are reading here.
   boolean isClassAndUsageEligible() {
-    if (!isClassEligible.test(eligibleClass)) {
+    if (!isClassEligible.test(eligibleClassDefinition)) {
       return false;
     }
 
@@ -129,6 +138,11 @@
 
     assert root.isStaticGet();
 
+    // We know that desugared lambda classes satisfy eligibility requirements.
+    if (isDesugaredLambda) {
+      return true;
+    }
+
     // Checking if we can safely inline class implemented following singleton-like
     // pattern, by which we assume a static final field holding on to the reference
     // initialized in class constructor.
@@ -444,7 +458,7 @@
         : "Inlined constructor? [invoke: " + initInvoke +
         ", expected class: " + eligibleClass + "]";
 
-    DexEncodedMethod definition = appInfo.definitionFor(init);
+    DexEncodedMethod definition = findSingleTarget(init, true);
     if (definition == null || isProcessedConcurrently.test(definition)) {
       return null;
     }
@@ -455,6 +469,12 @@
       return null;
     }
 
+    if (isDesugaredLambda) {
+      // Lambda desugaring synthesizes eligible constructors.
+      markSizeForInlining(definition);
+      return new InliningInfo(definition, eligibleClass);
+    }
+
     // If the superclass of the initializer is NOT java.lang.Object, the super class
     // initializer being called must be classified as TrivialInstanceInitializer.
     //
@@ -499,7 +519,7 @@
   private InliningInfo isEligibleMethodCall(boolean allowMethodsWithoutNormalReturns,
       DexMethod callee, Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
 
-    DexEncodedMethod singleTarget = findSingleTarget(callee);
+    DexEncodedMethod singleTarget = findSingleTarget(callee, false);
     if (singleTarget == null || isProcessedConcurrently.test(singleTarget)) {
       return null;
     }
@@ -507,6 +527,16 @@
       return null; // Don't inline itself.
     }
 
+    if (isDesugaredLambda) {
+      // If this is the call to method of the desugared lambda, we consider only calls
+      // to main lambda method eligible (for both direct and indirect calls).
+      if (singleTarget.accessFlags.isBridge()) {
+        return null;
+      }
+      markSizeForInlining(singleTarget);
+      return new InliningInfo(singleTarget, eligibleClass);
+    }
+
     OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
 
     ClassInlinerEligibility eligibility = optimizationInfo.getClassInlinerEligibility();
@@ -652,6 +682,9 @@
 
   private boolean exemptFromInstructionLimit(DexEncodedMethod inlinee) {
     DexType inlineeHolder = inlinee.method.holder;
+    if (isDesugaredLambda && inlineeHolder == eligibleClass) {
+      return true;
+    }
     if (appInfo.isPinned(inlineeHolder)) {
       return false;
     }
@@ -674,14 +707,16 @@
     return root.isNewInstance();
   }
 
-  private DexEncodedMethod findSingleTarget(DexMethod callee) {
+  private DexEncodedMethod findSingleTarget(DexMethod callee, boolean isDirect) {
     // We don't use computeSingleTarget(...) on invoke since it sometimes fails to
     // find the single target, while this code may be more successful since we exactly
     // know what is the actual type of the receiver.
 
     // Note that we also intentionally limit ourselves to methods directly defined in
     // the instance's class. This may be improved later.
-    return eligibleClassDefinition.lookupVirtualMethod(callee);
+    return isDirect
+        ? eligibleClassDefinition.lookupDirectMethod(callee)
+        : eligibleClassDefinition.lookupVirtualMethod(callee);
   }
 
   private void removeInstruction(Instruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index a5807c3..7835da2 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -10,25 +10,33 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 public final class SynthesizedCode extends Code {
 
-  private final SourceCode sourceCode;
+  private final Supplier<SourceCode> sourceCodeProvider;
   private final Consumer<UseRegistry> registryCallback;
 
-  public SynthesizedCode(SourceCode sourceCode) {
-    this.sourceCode = sourceCode;
+  public SynthesizedCode(SourceCode sourceCodeProvider) {
+    this(() -> sourceCodeProvider);
+  }
+
+  public SynthesizedCode(Supplier<SourceCode> sourceCodeProvider) {
+    this.sourceCodeProvider = sourceCodeProvider;
     this.registryCallback = SynthesizedCode::registerReachableDefinitionsDefault;
   }
 
-  public SynthesizedCode(SourceCode sourceCode, Consumer<UseRegistry> callback) {
-    this.sourceCode = sourceCode;
+  public SynthesizedCode(
+      SourceCode sourceCode, Consumer<UseRegistry> callback) {
+    this.sourceCodeProvider = () -> sourceCode;
     this.registryCallback = callback;
   }
 
@@ -40,7 +48,15 @@
   @Override
   public final IRCode buildIR(
       DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
-    return new IRBuilder(encodedMethod, appInfo, sourceCode, options).build();
+    return new IRBuilder(encodedMethod, appInfo, sourceCodeProvider.get(), options).build();
+  }
+
+  @Override
+  public IRCode buildInliningIR(
+      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options,
+      ValueNumberGenerator valueNumberGenerator, Position callerPosition, Origin origin) {
+    return new IRBuilder(encodedMethod, appInfo,
+        sourceCodeProvider.get(), options, valueNumberGenerator).build();
   }
 
   @Override
@@ -59,17 +75,16 @@
 
   @Override
   protected final int computeHashCode() {
-    return sourceCode.hashCode();
+    throw new Unreachable();
   }
 
   @Override
   protected final boolean computeEquals(Object other) {
-    return other instanceof SynthesizedCode &&
-        this.sourceCode.equals(((SynthesizedCode) other).sourceCode);
+    throw new Unreachable();
   }
 
   @Override
   public final String toString(DexEncodedMethod method, ClassNameMapper naming) {
-    return "SynthesizedCode: " + sourceCode.toString();
+    return "SynthesizedCode";
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 05c7b92..fb6ef33 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -10,11 +10,20 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
 /** Class provides basic information about symbols related to Kotlin support. */
 public final class Kotlin {
+  // Simply "Lkotlin/", but to avoid being renamed by Shadow.relocate
+  private static final String KOTLIN =
+      String.join("", ImmutableList.of("L", "k", "o", "t", "l", "i", "n", "/"));
+
+  static String addKotlinPrefix(String str) {
+    return KOTLIN + str;
+  }
+
   public final DexItemFactory factory;
 
   public final Functional functional;
@@ -44,14 +53,15 @@
       //
       // This implementation just ignores lambdas with arity > 22.
       for (int i = 0; i <= 22; i++) {
-        functions.add(factory.createType("Lkotlin/jvm/functions/Function" + i + ";"));
+        functions.add(factory.createType(addKotlinPrefix("jvm/functions/Function") + i + ";"));
       }
     }
 
     public final DexString kotlinStyleLambdaInstanceName = factory.createString("INSTANCE");
 
-    public final DexType functionBase = factory.createType("Lkotlin/jvm/internal/FunctionBase;");
-    public final DexType lambdaType = factory.createType("Lkotlin/jvm/internal/Lambda;");
+    public final DexType functionBase =
+        factory.createType(addKotlinPrefix("jvm/internal/FunctionBase;"));
+    public final DexType lambdaType = factory.createType(addKotlinPrefix("jvm/internal/Lambda;"));
 
     public final DexMethod lambdaInitializerMethod = factory.createMethod(
         lambdaType,
@@ -64,7 +74,7 @@
   }
 
   public final class Metadata {
-    public final DexType kotlinMetadataType = factory.createType("Lkotlin/Metadata;");
+    public final DexType kotlinMetadataType = factory.createType(addKotlinPrefix("Metadata;"));
     public final DexString kind = factory.createString("k");
     public final DexString metadataVersion = factory.createString("mv");
     public final DexString bytecodeVersion = factory.createString("bv");
@@ -77,7 +87,7 @@
 
   // kotlin.jvm.internal.Intrinsics class
   public final class Intrinsics {
-    public final DexType type = factory.createType("Lkotlin/jvm/internal/Intrinsics;");
+    public final DexType type = factory.createType(addKotlinPrefix("jvm/internal/Intrinsics;"));
     public final DexMethod throwParameterIsNullException = factory.createMethod(type,
         factory.createProto(factory.voidType, factory.stringType), "throwParameterIsNullException");
     public final DexMethod checkParameterIsNotNull = factory.createMethod(type,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 32aebff..389a8f8 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -36,7 +36,7 @@
             new StringDiagnostic("Class " + clazz.type.toSourceString()
                 + " has malformed kotlin.Metadata: " + e.getMessage()));
       } catch (Throwable e) {
-         reporter.warning(
+        reporter.warning(
             new StringDiagnostic("Unexpected error while reading " + clazz.type.toSourceString()
                 + "'s kotlin.Metadata: " + e.getMessage()));
       }
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 38473f3..4d072c4 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -895,50 +895,61 @@
     }
 
     private void redirectSuperCallsInTarget(DexMethod oldTarget, DexMethod newTarget) {
-      // If we merge class B into class C, and class C contains an invocation super.m(), then it
-      // is insufficient to rewrite "invoke-super B.m()" to "invoke-direct C.m$B()" (the method
-      // C.m$B denotes the direct method that has been created in C for B.m). In particular, there
-      // might be an instruction "invoke-super A.m()" in C that resolves to B.m at runtime (A is
-      // a superclass of B), which also needs to be rewritten to "invoke-direct C.m$B()".
-      //
-      // We handle this by adding a mapping for [target] and all of its supertypes.
-      DexClass holder = target;
-      while (holder != null && holder.isProgramClass()) {
-        DexMethod signatureInHolder =
-            application.dexItemFactory.createMethod(holder.type, oldTarget.proto, oldTarget.name);
-        // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError.
-        boolean resolutionSucceeds =
-            holder.lookupVirtualMethod(signatureInHolder) != null
-                || appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
-        if (resolutionSucceeds) {
-          deferredRenamings.mapVirtualMethodToDirectInType(
-              signatureInHolder, newTarget, target.type);
-        } else {
-          break;
-        }
+      if (source.accessFlags.isInterface()) {
+        // If we merge a default interface method from interface I to its subtype C, then we need
+        // to rewrite invocations on the form "invoke-super I.m()" to "invoke-direct C.m$I()".
+        //
+        // Unlike when we merge a class into its subclass (the else-branch below), we should *not*
+        // rewrite any invocations on the form "invoke-super J.m()" to "invoke-direct C.m$I()",
+        // if I has a supertype J. This is due to the fact that invoke-super instructions that
+        // resolve to a method on an interface never hit an implementation below that interface.
+        deferredRenamings.mapVirtualMethodToDirectInType(oldTarget, newTarget, target.type);
+      } else {
+        // If we merge class B into class C, and class C contains an invocation super.m(), then it
+        // is insufficient to rewrite "invoke-super B.m()" to "invoke-direct C.m$B()" (the method
+        // C.m$B denotes the direct method that has been created in C for B.m). In particular, there
+        // might be an instruction "invoke-super A.m()" in C that resolves to B.m at runtime (A is
+        // a superclass of B), which also needs to be rewritten to "invoke-direct C.m$B()".
+        //
+        // We handle this by adding a mapping for [target] and all of its supertypes.
+        DexClass holder = target;
+        while (holder != null && holder.isProgramClass()) {
+          DexMethod signatureInHolder =
+              application.dexItemFactory.createMethod(holder.type, oldTarget.proto, oldTarget.name);
+          // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError.
+          boolean resolutionSucceeds =
+              holder.lookupVirtualMethod(signatureInHolder) != null
+                  || appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
+          if (resolutionSucceeds) {
+            deferredRenamings.mapVirtualMethodToDirectInType(
+                signatureInHolder, newTarget, target.type);
+          } else {
+            break;
+          }
 
-        // Consider that A gets merged into B and B's subclass C gets merged into D. Instructions
-        // on the form "invoke-super {B,C,D}.m()" in D are changed into "invoke-direct D.m$C()" by
-        // the code above. However, instructions on the form "invoke-super A.m()" should also be
-        // changed into "invoke-direct D.m$C()". This is achieved by also considering the classes
-        // that have been merged into [holder].
-        Set<DexType> mergedTypes = mergedClassesInverse.get(holder.type);
-        if (mergedTypes != null) {
-          for (DexType type : mergedTypes) {
-            DexMethod signatureInType =
-                application.dexItemFactory.createMethod(type, oldTarget.proto, oldTarget.name);
-            // Resolution would have succeeded if the method used to be in [type], or if one of
-            // its super classes declared the method.
-            boolean resolutionSucceededBeforeMerge =
-                renamedMembersLense.hasMappingForSignatureInContext(holder.type, signatureInType)
-                    || appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
-            if (resolutionSucceededBeforeMerge) {
-              deferredRenamings.mapVirtualMethodToDirectInType(
-                  signatureInType, newTarget, target.type);
+          // Consider that A gets merged into B and B's subclass C gets merged into D. Instructions
+          // on the form "invoke-super {B,C,D}.m()" in D are changed into "invoke-direct D.m$C()" by
+          // the code above. However, instructions on the form "invoke-super A.m()" should also be
+          // changed into "invoke-direct D.m$C()". This is achieved by also considering the classes
+          // that have been merged into [holder].
+          Set<DexType> mergedTypes = mergedClassesInverse.get(holder.type);
+          if (mergedTypes != null) {
+            for (DexType type : mergedTypes) {
+              DexMethod signatureInType =
+                  application.dexItemFactory.createMethod(type, oldTarget.proto, oldTarget.name);
+              // Resolution would have succeeded if the method used to be in [type], or if one of
+              // its super classes declared the method.
+              boolean resolutionSucceededBeforeMerge =
+                  renamedMembersLense.hasMappingForSignatureInContext(holder.type, signatureInType)
+                      || appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
+              if (resolutionSucceededBeforeMerge) {
+                deferredRenamings.mapVirtualMethodToDirectInType(
+                    signatureInType, newTarget, target.type);
+              }
             }
           }
+          holder = holder.superType != null ? appInfo.definitionFor(holder.superType) : null;
         }
-        holder = holder.superType != null ? appInfo.definitionFor(holder.superType) : null;
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index eb8ee0e..93a80b6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -447,7 +447,9 @@
     public boolean placeExceptionalBlocksLast = false;
     public boolean dontCreateMarkerInD8 = false;
     public boolean forceJumboStringProcessing = false;
+    public boolean nondeterministicCycleElimination = false;
     public Set<Inliner.Reason> validInliningReasons = null;
+    public boolean suppressExperimentalCfBackendWarning = false;
   }
 
   public boolean canUseInvokePolymorphicOnVarHandle() {
diff --git a/src/test/examples/classmerging/CallGraphCycleTest.java b/src/test/examples/classmerging/CallGraphCycleTest.java
new file mode 100644
index 0000000..40347d6
--- /dev/null
+++ b/src/test/examples/classmerging/CallGraphCycleTest.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package classmerging;
+
+public class CallGraphCycleTest {
+
+  public static void main(String[] args) {
+    new B(true);
+  }
+
+  public static class A {
+
+    public A(boolean instantiateB) {
+      if (instantiateB) {
+        new B(false);
+      }
+      System.out.println("A(" + instantiateB + ")");
+    }
+  }
+
+  public static class B extends A {
+
+    public B(boolean instantiateBinA) {
+      super(instantiateBinA);
+      System.out.println("B(" + instantiateBinA + ")");
+    }
+  }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 4ba014e..37a3010 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -7,6 +7,9 @@
 -keep public class classmerging.Test {
   public static void main(...);
 }
+-keep public class classmerging.CallGraphCycleTest {
+  public static void main(...);
+}
 -keep public class classmerging.ClassWithNativeMethodTest {
   public static void main(...);
 }
diff --git a/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java b/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java
new file mode 100644
index 0000000..781d2b4
--- /dev/null
+++ b/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2018 the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package uninitializedfinal;
+
+// Test that leaks an instance before its final field has been initialized to a thread that
+// reads that field. This tests that redundant field load elimination does not eliminate
+// field reads (even of final fields) that cross a monitor operation.
+public class UninitializedFinalFieldLeak {
+
+  public static class PollingThread extends Thread {
+    public int result = 0;
+    UninitializedFinalFieldLeak f;
+
+    PollingThread(UninitializedFinalFieldLeak f) {
+      this.f = f;
+    }
+
+    // Read the field a number of times. Then lock on the object to await field initialization.
+    public void run() {
+      result += f.i;
+      result += f.i;
+      result += f.i;
+      f.threadReadsDone = true;
+      synchronized (f) {
+        result += f.i;
+      }
+      // The right result is 42. Reading the uninitialized 0 three times and then
+      // reading the initialized value. It is safe to remove the two redundant loads
+      // before the monitor operation.
+      System.out.println(result);
+    }
+  }
+
+  public final int i;
+  public volatile boolean threadReadsDone = false;
+
+  public UninitializedFinalFieldLeak() throws InterruptedException {
+    // Leak the object to a thread and start the thread with the lock on the object taken.
+    // Then allow the other thread to run and read the uninitialized field.
+    // Finally, initialize the field and release the lock.
+    PollingThread t = new PollingThread(this);
+    synchronized (this) {
+      t.start();
+      while (!threadReadsDone) {
+        Thread.yield();
+      }
+      i = 42;
+    }
+    t.join();
+  }
+
+  public static void main(String[] args) throws InterruptedException {
+    new UninitializedFinalFieldLeak();
+  }
+}
diff --git a/src/test/examplesAndroidO/classmerging/NestedDefaultInterfaceMethodsTest.java b/src/test/examplesAndroidO/classmerging/NestedDefaultInterfaceMethodsTest.java
new file mode 100644
index 0000000..7a0e325
--- /dev/null
+++ b/src/test/examplesAndroidO/classmerging/NestedDefaultInterfaceMethodsTest.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package classmerging;
+
+public class NestedDefaultInterfaceMethodsTest {
+
+  public static void main(String[] args) {
+    new C().m();
+  }
+
+  public interface A {
+
+    default void m() {
+      System.out.println("In A.m()");
+    }
+  }
+
+  public interface B extends A {
+
+    @Override
+    default void m() {
+      System.out.println("In B.m()");
+      A.super.m();
+    }
+  }
+
+  public static class C implements B {
+
+    @Override
+    public void m() {
+      System.out.println("In C.m()");
+      B.super.m();
+    }
+  }
+}
diff --git a/src/test/examplesAndroidO/classmerging/keep-rules.txt b/src/test/examplesAndroidO/classmerging/keep-rules.txt
index fc91808..4df182e 100644
--- a/src/test/examplesAndroidO/classmerging/keep-rules.txt
+++ b/src/test/examplesAndroidO/classmerging/keep-rules.txt
@@ -10,6 +10,9 @@
 -keep public class classmerging.MergeDefaultMethodIntoClassTest {
   public static void main(...);
 }
+-keep public class classmerging.NestedDefaultInterfaceMethodsTest {
+  public static void main(...);
+}
 
 # TODO(herhut): Consider supporting merging of inner-class attributes.
 # -keepattributes *
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index f20335b..be9261f 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -4,11 +4,15 @@
 
 package com.android.tools.r8;
 
+import static org.junit.Assert.assertEquals;
+
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
 import com.android.tools.r8.utils.OffOrAuto;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -94,6 +98,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -101,6 +106,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 23, "lambdadesugaring"))
         .run();
   }
 
@@ -112,6 +118,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -119,6 +126,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 23, "lambdadesugaring"))
         .run();
   }
 
@@ -131,6 +139,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -139,6 +148,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
         .run();
   }
 
@@ -151,6 +161,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -159,9 +170,21 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
         .run();
   }
 
+  private void checkLambdaCount(DexInspector inspector, int expectedCount, String prefix) {
+    int count = 0;
+    for (FoundClassSubject clazz : inspector.allClasses()) {
+      if (clazz.isSynthesizedJavaLambdaClass() &&
+          clazz.getOriginalName().startsWith(prefix)) {
+        count++;
+      }
+    }
+    assertEquals(expectedCount, count);
+  }
+
   class R8TestRunner extends TestRunner<R8TestRunner> {
 
     R8TestRunner(String testName, String packageName, String mainClass) {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 36f7594..221d427 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -45,9 +45,11 @@
         "instancevariable.InstanceVariable",
         "instanceofstring.InstanceofString",
         "invoke.Invoke",
+        "invokeempty.InvokeEmpty",
         "jumbostring.JumboString",
         "loadconst.LoadConst",
         "loop.UdpServer",
+        "nestedtrycatches.NestedTryCatches",
         "newarray.NewArray",
         "regalloc.RegAlloc",
         "returns.Returns",
@@ -58,9 +60,7 @@
         "throwing.Throwing",
         "trivial.Trivial",
         "trycatch.TryCatch",
-        "nestedtrycatches.NestedTryCatches",
         "trycatchmany.TryCatchMany",
-        "invokeempty.InvokeEmpty",
         "regress.Regress",
         "regress2.Regress2",
         "regress_37726195.Regress",
@@ -82,6 +82,7 @@
         "enclosingmethod_proguarded.Main",
         "interfaceinlining.Main",
         "switchmaps.Switches",
+        "uninitializedfinal.UninitializedFinalFieldLeak",
     };
 
     List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
diff --git a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
index 0f3cadc..11ad12b 100644
--- a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
@@ -11,12 +11,14 @@
 import com.android.tools.r8.utils.TestDescriptionWatcher;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -89,6 +91,11 @@
       // This list is currently empty!
   );
 
+  private Set<String> failingOnX8 = ImmutableSet.of(
+      // Contains use of register as both an int and a float.
+      "regression/33336471"
+  );
+
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
@@ -161,6 +168,10 @@
         .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
             .setOutput(Paths.get(outputPath), OutputMode.DexIndexed);
     ToolHelper.getAppBuilder(builder).addProgramFiles(originalDexFile);
+
+    if (failingOnX8.contains(directoryName)) {
+      thrown.expect(CompilationFailedException.class);
+    }
     R8.run(builder.build());
 
     if (!ToolHelper.artSupported()) {
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 7b4f199..855265d 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -54,6 +54,11 @@
 
 public class TestBase {
 
+  protected enum Backend {
+    CF,
+    DEX
+  };
+
   // Actually running Proguard should only be during development.
   private static final boolean RUN_PROGUARD = System.getProperty("run_proguard") != null;
   // Actually running r8.jar in a forked process.
@@ -166,6 +171,17 @@
   }
 
   /**
+   * Copy test classes to the specified directory.
+   */
+  protected void copyTestClasses(Path dest, Class... classes) throws IOException {
+    for (Class clazz : classes) {
+      Path path = dest.resolve(clazz.getCanonicalName().replace('.', '/') + ".class");
+      Files.createDirectories(path.getParent());
+      Files.copy(ToolHelper.getClassFileForTestClass(clazz), path);
+    }
+  }
+
+  /**
    * Create a temporary JAR file containing the specified test classes.
    */
   protected Path jarTestClasses(Class... classes) throws IOException {
@@ -518,7 +534,11 @@
   }
 
   protected ProcessResult runOnJavaRaw(String main, byte[]... classes) throws IOException {
-    Path file = writeToZip(Arrays.asList(classes));
+    return runOnJavaRaw(main, Arrays.asList(classes));
+  }
+
+  protected ProcessResult runOnJavaRaw(String main, List<byte[]> classes) throws IOException {
+    Path file = writeToZip(classes);
     return ToolHelper.runJavaNoVerify(file, main);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 200a1e7..2767df7 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -474,8 +474,9 @@
   public static byte[] getClassAsBytes(Class clazz) throws IOException {
     String s = clazz.getSimpleName() + ".class";
     Class outer = clazz.getEnclosingClass();
-    if (outer != null) {
+    while (outer != null) {
       s = outer.getSimpleName() + '$' + s;
+      outer = outer.getEnclosingClass();
     }
     return ByteStreams.toByteArray(clazz.getResourceAsStream(s));
   }
@@ -745,10 +746,6 @@
     return parts;
   }
 
-  public static Path getPackageDirectoryForTestClass(Class clazz) {
-    return getPackageDirectoryForTestPackage(clazz.getPackage());
-  }
-
   public static List<Path> getClassFilesForTestPackage(Package pkg) throws IOException {
     Path dir = ToolHelper.getPackageDirectoryForTestPackage(pkg);
     return Files.walk(dir)
@@ -762,6 +759,11 @@
         Paths.get("", parts.toArray(new String[parts.size() - 1])));
   }
 
+  public static Path getFileNameForTestClass(Class clazz) {
+    List<String> parts = getNamePartsForTestClass(clazz);
+    return Paths.get("", parts.toArray(new String[parts.size() - 1]));
+  }
+
   public static String getJarEntryForTestClass(Class clazz) {
     List<String> parts = getNamePartsForTestClass(clazz);
     return String.join("/", parts);
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index f28c86f..7635f0c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -70,6 +70,7 @@
     options.enableClassMerging = true;
     options.enableClassInlining = false;
     options.enableMinification = false;
+    options.testing.nondeterministicCycleElimination = true;
   }
 
   private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
@@ -115,6 +116,29 @@
     }
   }
 
+  // This test has a cycle in the call graph consisting of the methods A.<init> and B.<init>.
+  // When nondeterministicCycleElimination is enabled, we shuffle the nodes in the call graph
+  // before the cycle elimination. Therefore, it is nondeterministic if the cycle detector will
+  // detect the cycle upon the call edge A.<init> to B.<init>, or B.<init> to A.<init>. In order
+  // increase the likelihood that this test encounters both orderings, the test is repeated 5 times.
+  // Assuming that the chance of observing one of the two orderings is 50%, this test therefore has
+  // approximately 3% chance of succeeding although there is an issue.
+  @Test
+  public void testCallGraphCycle() throws Exception {
+    String main = "classmerging.CallGraphCycleTest";
+    Path[] programFiles =
+        new Path[] {
+          CF_DIR.resolve("CallGraphCycleTest.class"),
+          CF_DIR.resolve("CallGraphCycleTest$A.class"),
+          CF_DIR.resolve("CallGraphCycleTest$B.class")
+        };
+    Set<String> preservedClassNames =
+        ImmutableSet.of("classmerging.CallGraphCycleTest", "classmerging.CallGraphCycleTest$B");
+    for (int i = 0; i < 5; i++) {
+      runTest(main, programFiles, preservedClassNames::contains);
+    }
+  }
+
   @Test
   public void testConflictInGeneratedName() throws Exception {
     String main = "classmerging.ConflictInGeneratedNameTest";
@@ -251,6 +275,50 @@
   }
 
   @Test
+  public void testNestedDefaultInterfaceMethodsTest() throws Exception {
+    String main = "classmerging.NestedDefaultInterfaceMethodsTest";
+    Path[] programFiles =
+        new Path[] {
+          JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest.class"),
+          JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$A.class"),
+          JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$B.class"),
+          JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$C.class")
+        };
+    Set<String> preservedClassNames =
+        ImmutableSet.of(
+            "classmerging.NestedDefaultInterfaceMethodsTest",
+            "classmerging.NestedDefaultInterfaceMethodsTest$B",
+            "classmerging.NestedDefaultInterfaceMethodsTest$C");
+    runTest(
+        main, programFiles, preservedClassNames::contains, getProguardConfig(JAVA8_EXAMPLE_KEEP));
+  }
+
+  @Test
+  public void testNestedDefaultInterfaceMethodsWithCDumpTest() throws Exception {
+    String main = "classmerging.NestedDefaultInterfaceMethodsTest";
+    Path[] programFiles =
+        new Path[] {
+          JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest.class"),
+          JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$A.class"),
+          JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$B.class")
+        };
+    Set<String> preservedClassNames =
+        ImmutableSet.of(
+            "classmerging.NestedDefaultInterfaceMethodsTest",
+            "classmerging.NestedDefaultInterfaceMethodsTest$B",
+            "classmerging.NestedDefaultInterfaceMethodsTest$C");
+    runTestOnInput(
+        main,
+        AndroidApp.builder()
+            .addProgramFiles(programFiles)
+            .addClassProgramData(
+                NestedDefaultInterfaceMethodsTestDump.CDump.dump(), Origin.unknown())
+            .build(),
+        preservedClassNames::contains,
+        getProguardConfig(JAVA8_EXAMPLE_KEEP));
+  }
+
+  @Test
   public void testPinnedParameterTypes() throws Exception {
     String main = "classmerging.PinnedParameterTypesTest";
     Path[] programFiles =
diff --git a/src/test/java/com/android/tools/r8/classmerging/NestedDefaultInterfaceMethodsTestDump.java b/src/test/java/com/android/tools/r8/classmerging/NestedDefaultInterfaceMethodsTestDump.java
new file mode 100644
index 0000000..e55802d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/NestedDefaultInterfaceMethodsTestDump.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class NestedDefaultInterfaceMethodsTestDump {
+
+  // Generated by running ./tools/asmifier.py build/test/examplesAndroidO/classes/classmerging/-
+  // NestedDefaultInterfaceMethodsTest\$C.class, and changing "invoke-special B.m()" to "invoke-
+  // special A.m()".
+  public static class CDump implements Opcodes {
+
+    public static byte[] dump() {
+      ClassWriter cw = new ClassWriter(0);
+      FieldVisitor fv;
+      MethodVisitor mv;
+      AnnotationVisitor av0;
+
+      cw.visit(
+          V1_8,
+          ACC_PUBLIC + ACC_SUPER,
+          "classmerging/NestedDefaultInterfaceMethodsTest$C",
+          null,
+          "java/lang/Object",
+          new String[] {"classmerging/NestedDefaultInterfaceMethodsTest$B"});
+
+      cw.visitInnerClass(
+          "classmerging/NestedDefaultInterfaceMethodsTest$C",
+          "classmerging/NestedDefaultInterfaceMethodsTest",
+          "C",
+          ACC_PUBLIC + ACC_STATIC);
+
+      cw.visitInnerClass(
+          "classmerging/NestedDefaultInterfaceMethodsTest$B",
+          "classmerging/NestedDefaultInterfaceMethodsTest",
+          "B",
+          ACC_PUBLIC + ACC_STATIC + ACC_ABSTRACT + ACC_INTERFACE);
+
+      {
+        mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+        mv.visitCode();
+        mv.visitVarInsn(ALOAD, 0);
+        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        mv.visitInsn(RETURN);
+        mv.visitMaxs(1, 1);
+        mv.visitEnd();
+      }
+      {
+        mv = cw.visitMethod(ACC_PUBLIC, "m", "()V", null, null);
+        mv.visitCode();
+        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        mv.visitLdcInsn("In C.m()");
+        mv.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        mv.visitVarInsn(ALOAD, 0);
+        // The signature "classmerging/NestedDefaultInterfaceMethodsTest$B" has been changed to
+        // "classmerging/NestedDefaultInterfaceMethodsTest$A".
+        mv.visitMethodInsn(
+            INVOKESPECIAL, "classmerging/NestedDefaultInterfaceMethodsTest$A", "m", "()V", true);
+        mv.visitInsn(RETURN);
+        mv.visitMaxs(2, 1);
+        mv.visitEnd();
+      }
+      cw.visitEnd();
+
+      return cw.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest.java
new file mode 100644
index 0000000..68277dd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+public class ArrayDimensionGreaterThanSevenTest {
+
+  public static float foo(int x) {
+    try {
+      float[] fs1 = new float[] {42f};
+      float[][] fs2 = new float[][] {fs1};
+      float[][][] fs3 = new float[][][] {fs2};
+      float[][][][] fs4 = new float[][][][] {fs3};
+      float[][][][][] fs5 = new float[][][][][] {fs4};
+      float[][][][][][] fs6 = new float[][][][][][] {fs5};
+      float[][][][][][][] fs7 = new float[][][][][][][] {fs6};
+      float[][][][][][][][] fs8 = new float[][][][][][][][] {fs7};
+      while (x-- > 0) {
+        try {
+          fs8 = x == 0 ? fs8 : null;
+          fs7 = x == 1 ? fs8[1] : fs8[0];
+          fs6 = x == 2 ? fs7[1] : fs7[0];
+          fs5 = x == 3 ? fs6[1] : fs6[0];
+          fs4 = x == 4 ? fs5[1] : fs5[0];
+          fs3 = x == 5 ? fs4[1] : fs4[0];
+          fs2 = x == 6 ? fs3[1] : fs3[0];
+          fs1 = x == 7 ? fs2[1] : fs2[0];
+        } catch (NullPointerException e) {
+          System.out.println("null pointer");
+        }
+      }
+    } catch (RuntimeException e) {
+      return -1f;
+    }
+    return 42;
+  }
+
+  public static void main(String[] args) {
+    System.out.println(foo(args.length + 1));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java
new file mode 100644
index 0000000..5cd652d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java
@@ -0,0 +1,590 @@
+package com.android.tools.r8.debug;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class ArrayDimensionGreaterThanSevenTestDump implements Opcodes {
+
+  public static byte[] dump() throws Exception {
+
+    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    AnnotationVisitor av0;
+
+    cw.visit(
+        V1_8,
+        ACC_PUBLIC + ACC_SUPER,
+        "com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest",
+        null,
+        "java/lang/Object",
+        null);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "foo", "(I)F", null, null);
+      mv.visitCode();
+      Label l0 = new Label();
+      Label l1 = new Label();
+      Label l2 = new Label();
+      mv.visitTryCatchBlock(l0, l1, l2, "java/lang/NullPointerException");
+      Label l3 = new Label();
+      Label l4 = new Label();
+      Label l5 = new Label();
+      mv.visitTryCatchBlock(l3, l4, l5, "java/lang/RuntimeException");
+      mv.visitLabel(l3);
+      mv.visitInsn(ICONST_1);
+      mv.visitIntInsn(NEWARRAY, T_FLOAT);
+      mv.visitInsn(DUP);
+      mv.visitInsn(ICONST_0);
+      mv.visitLdcInsn(new Float("42.0"));
+      mv.visitInsn(FASTORE);
+      mv.visitVarInsn(ASTORE, 1);
+      mv.visitInsn(ICONST_1);
+      mv.visitTypeInsn(ANEWARRAY, "[F");
+      mv.visitInsn(DUP);
+      mv.visitInsn(ICONST_0);
+      mv.visitVarInsn(ALOAD, 1);
+      mv.visitInsn(AASTORE);
+      mv.visitVarInsn(ASTORE, 2);
+      mv.visitInsn(ICONST_1);
+      mv.visitTypeInsn(ANEWARRAY, "[[F");
+      mv.visitInsn(DUP);
+      mv.visitInsn(ICONST_0);
+      mv.visitVarInsn(ALOAD, 2);
+      mv.visitInsn(AASTORE);
+      mv.visitVarInsn(ASTORE, 3);
+      mv.visitInsn(ICONST_1);
+      mv.visitTypeInsn(ANEWARRAY, "[[[F");
+      mv.visitInsn(DUP);
+      mv.visitInsn(ICONST_0);
+      mv.visitVarInsn(ALOAD, 3);
+      mv.visitInsn(AASTORE);
+      mv.visitVarInsn(ASTORE, 4);
+      mv.visitInsn(ICONST_1);
+      mv.visitTypeInsn(ANEWARRAY, "[[[[F");
+      mv.visitInsn(DUP);
+      mv.visitInsn(ICONST_0);
+      mv.visitVarInsn(ALOAD, 4);
+      mv.visitInsn(AASTORE);
+      mv.visitVarInsn(ASTORE, 5);
+      mv.visitInsn(ICONST_1);
+      mv.visitTypeInsn(ANEWARRAY, "[[[[[F");
+      mv.visitInsn(DUP);
+      mv.visitInsn(ICONST_0);
+      mv.visitVarInsn(ALOAD, 5);
+      mv.visitInsn(AASTORE);
+      mv.visitVarInsn(ASTORE, 6);
+      mv.visitInsn(ICONST_1);
+      mv.visitTypeInsn(ANEWARRAY, "[[[[[[F");
+      mv.visitInsn(DUP);
+      mv.visitInsn(ICONST_0);
+      mv.visitVarInsn(ALOAD, 6);
+      mv.visitInsn(AASTORE);
+      mv.visitVarInsn(ASTORE, 7);
+      mv.visitInsn(ICONST_1);
+      mv.visitTypeInsn(ANEWARRAY, "[[[[[[[F");
+      mv.visitInsn(DUP);
+      mv.visitInsn(ICONST_0);
+      mv.visitVarInsn(ALOAD, 7);
+      mv.visitInsn(AASTORE);
+      mv.visitVarInsn(ASTORE, 8);
+      Label l6 = new Label();
+      mv.visitLabel(l6);
+      // mv.visitFrame(Opcodes.F_FULL, 9, new Object[] {Opcodes.INTEGER, "[F", "[[F", "[[[F",
+      // "[[[[F", "[[[[[F", "[[[[[[F", "[[[[[[[F", "[[[[[[[[F"}, 0, new Object[] {});
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          0,
+          new Object[] {});
+      mv.visitVarInsn(ILOAD, 0);
+      mv.visitIincInsn(0, -1);
+      mv.visitJumpInsn(IFLE, l4);
+      mv.visitLabel(l0);
+      mv.visitVarInsn(ILOAD, 0);
+      Label l7 = new Label();
+      mv.visitJumpInsn(IFNE, l7);
+      mv.visitVarInsn(ALOAD, 8);
+      Label l8 = new Label();
+      mv.visitJumpInsn(GOTO, l8);
+      mv.visitLabel(l7);
+      // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          0,
+          new Object[] {});
+      mv.visitInsn(ACONST_NULL);
+      mv.visitTypeInsn(CHECKCAST, "[[[[[[[[F");
+      mv.visitLabel(l8);
+      // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[[[[F"});
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          1,
+          new Object[] {"[[[[[[[[F"});
+      mv.visitVarInsn(ASTORE, 8);
+      mv.visitVarInsn(ILOAD, 0);
+      mv.visitInsn(ICONST_1);
+      Label l9 = new Label();
+      mv.visitJumpInsn(IF_ICMPNE, l9);
+      mv.visitVarInsn(ALOAD, 8);
+      mv.visitInsn(ICONST_1);
+      mv.visitInsn(AALOAD);
+      Label l10 = new Label();
+      mv.visitJumpInsn(GOTO, l10);
+      mv.visitLabel(l9);
+      // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          0,
+          new Object[] {});
+      mv.visitVarInsn(ALOAD, 8);
+      mv.visitInsn(ICONST_0);
+      mv.visitInsn(AALOAD);
+      mv.visitLabel(l10);
+      // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[[[F"});
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          1,
+          new Object[] {"[[[[[[[F"});
+      mv.visitVarInsn(ASTORE, 7);
+      mv.visitVarInsn(ILOAD, 0);
+      mv.visitInsn(ICONST_2);
+      Label l11 = new Label();
+      mv.visitJumpInsn(IF_ICMPNE, l11);
+      mv.visitVarInsn(ALOAD, 7);
+      mv.visitInsn(ICONST_1);
+      mv.visitInsn(AALOAD);
+      Label l12 = new Label();
+      mv.visitJumpInsn(GOTO, l12);
+      mv.visitLabel(l11);
+      // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          0,
+          new Object[] {});
+      mv.visitVarInsn(ALOAD, 7);
+      mv.visitInsn(ICONST_0);
+      mv.visitInsn(AALOAD);
+      mv.visitLabel(l12);
+      // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[[F"});
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          1,
+          new Object[] {"[[[[[[F"});
+      mv.visitVarInsn(ASTORE, 6);
+      mv.visitVarInsn(ILOAD, 0);
+      mv.visitInsn(ICONST_3);
+      Label l13 = new Label();
+      mv.visitJumpInsn(IF_ICMPNE, l13);
+      mv.visitVarInsn(ALOAD, 6);
+      mv.visitInsn(ICONST_1);
+      mv.visitInsn(AALOAD);
+      Label l14 = new Label();
+      mv.visitJumpInsn(GOTO, l14);
+      mv.visitLabel(l13);
+      // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          0,
+          new Object[] {});
+      mv.visitVarInsn(ALOAD, 6);
+      mv.visitInsn(ICONST_0);
+      mv.visitInsn(AALOAD);
+      mv.visitLabel(l14);
+      // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[F"});
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          1,
+          new Object[] {"[[[[[F"});
+      mv.visitVarInsn(ASTORE, 5);
+      mv.visitVarInsn(ILOAD, 0);
+      mv.visitInsn(ICONST_4);
+      Label l15 = new Label();
+      mv.visitJumpInsn(IF_ICMPNE, l15);
+      mv.visitVarInsn(ALOAD, 5);
+      mv.visitInsn(ICONST_1);
+      mv.visitInsn(AALOAD);
+      Label l16 = new Label();
+      mv.visitJumpInsn(GOTO, l16);
+      mv.visitLabel(l15);
+      // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          0,
+          new Object[] {});
+      mv.visitVarInsn(ALOAD, 5);
+      mv.visitInsn(ICONST_0);
+      mv.visitInsn(AALOAD);
+      mv.visitLabel(l16);
+      // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[F"});
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          1,
+          new Object[] {"[[[[F"});
+      mv.visitVarInsn(ASTORE, 4);
+      mv.visitVarInsn(ILOAD, 0);
+      mv.visitInsn(ICONST_5);
+      Label l17 = new Label();
+      mv.visitJumpInsn(IF_ICMPNE, l17);
+      mv.visitVarInsn(ALOAD, 4);
+      mv.visitInsn(ICONST_1);
+      mv.visitInsn(AALOAD);
+      Label l18 = new Label();
+      mv.visitJumpInsn(GOTO, l18);
+      mv.visitLabel(l17);
+      // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          0,
+          new Object[] {});
+      mv.visitVarInsn(ALOAD, 4);
+      mv.visitInsn(ICONST_0);
+      mv.visitInsn(AALOAD);
+      mv.visitLabel(l18);
+      // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[F"});
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          1,
+          new Object[] {"[[[F"});
+      mv.visitVarInsn(ASTORE, 3);
+      mv.visitVarInsn(ILOAD, 0);
+      mv.visitIntInsn(BIPUSH, 6);
+      Label l19 = new Label();
+      mv.visitJumpInsn(IF_ICMPNE, l19);
+      mv.visitVarInsn(ALOAD, 3);
+      mv.visitInsn(ICONST_1);
+      mv.visitInsn(AALOAD);
+      Label l20 = new Label();
+      mv.visitJumpInsn(GOTO, l20);
+      mv.visitLabel(l19);
+      // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          0,
+          new Object[] {});
+      mv.visitVarInsn(ALOAD, 3);
+      mv.visitInsn(ICONST_0);
+      mv.visitInsn(AALOAD);
+      mv.visitLabel(l20);
+      // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[F"});
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          1,
+          new Object[] {"[[F"});
+      mv.visitVarInsn(ASTORE, 2);
+      mv.visitVarInsn(ILOAD, 0);
+      mv.visitIntInsn(BIPUSH, 7);
+      Label l21 = new Label();
+      mv.visitJumpInsn(IF_ICMPNE, l21);
+      mv.visitVarInsn(ALOAD, 2);
+      mv.visitInsn(ICONST_1);
+      mv.visitInsn(AALOAD);
+      Label l22 = new Label();
+      mv.visitJumpInsn(GOTO, l22);
+      mv.visitLabel(l21);
+      // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          0,
+          new Object[] {});
+      mv.visitVarInsn(ALOAD, 2);
+      mv.visitInsn(ICONST_0);
+      mv.visitInsn(AALOAD);
+      mv.visitLabel(l22);
+      // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[F"});
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          1,
+          new Object[] {"[F"});
+      mv.visitVarInsn(ASTORE, 1);
+      mv.visitLabel(l1);
+      mv.visitJumpInsn(GOTO, l6);
+      mv.visitLabel(l2);
+      // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]
+      // {"java/lang/NullPointerException"});
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          9,
+          new Object[] {
+            Opcodes.INTEGER,
+            "[F",
+            "[[F",
+            "[[[F",
+            "[[[[F",
+            "[[[[[F",
+            "[[[[[[F",
+            "[[[[[[[F",
+            "[[[[[[[[F"
+          },
+          1,
+          new Object[] {"java/lang/NullPointerException"});
+      mv.visitVarInsn(ASTORE, 9);
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitLdcInsn("null pointer");
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      mv.visitJumpInsn(GOTO, l6);
+      mv.visitLabel(l4);
+      // mv.visitFrame(Opcodes.F_FULL, 1, new Object[] {Opcodes.INTEGER}, 0, new Object[] {});
+      mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {Opcodes.INTEGER}, 0, new Object[] {});
+      Label l23 = new Label();
+      mv.visitJumpInsn(GOTO, l23);
+      mv.visitLabel(l5);
+      // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/RuntimeException"});
+      mv.visitFrame(
+          Opcodes.F_NEW,
+          1,
+          new Object[] {Opcodes.INTEGER},
+          1,
+          new Object[] {"java/lang/RuntimeException"});
+      mv.visitVarInsn(ASTORE, 1);
+      mv.visitLdcInsn(new Float("-1.0"));
+      mv.visitInsn(FRETURN);
+      mv.visitLabel(l23);
+      // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {Opcodes.INTEGER}, 0, new Object[] {});
+      mv.visitLdcInsn(new Float("42.0"));
+      mv.visitInsn(FRETURN);
+      mv.visitMaxs(4, 10);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+      mv.visitCode();
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitInsn(ARRAYLENGTH);
+      mv.visitInsn(ICONST_1);
+      mv.visitInsn(IADD);
+      mv.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest",
+          "foo",
+          "(I)F",
+          false);
+      mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(F)V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(3, 1);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
new file mode 100644
index 0000000..7866e1b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class ArrayDimensionGreaterThanSevenTestRunner extends DebugTestBase {
+
+  private static final Class CLASS = ArrayDimensionGreaterThanSevenTest.class;
+  private static final String NAME = CLASS.getCanonicalName();
+
+  private DebugTestConfig getR8CfConfig(String s, Consumer<InternalOptions> optionsConsumer)
+      throws IOException, com.android.tools.r8.CompilationFailedException {
+    Path cfOut = temp.getRoot().toPath().resolve(s);
+    ToolHelper.runR8(
+        R8Command.builder()
+            .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
+            .setMode(CompilationMode.DEBUG)
+            .setOutput(cfOut, OutputMode.ClassFile)
+            .build(),
+        optionsConsumer);
+    return new CfDebugTestConfig(cfOut);
+  }
+
+  private Stream<DebuggeeState> createStream(DebugTestConfig config) throws Exception {
+    return streamDebugTest(config, NAME, ANDROID_FILTER);
+  }
+
+  @Test
+  @Ignore("b/111296969")
+  // Once R8 does not use expanded frames this can be enabled again.
+  public void test() throws Exception {
+    DebugTestConfig cfConfig = new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+    DebugTestConfig d8Config = new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+    DebugTestConfig r8JarConfig =
+        getR8CfConfig("r8jar.jar", options -> options.enableCfFrontend = false);
+    DebugTestConfig r8CfConfig =
+        getR8CfConfig("r8cf.jar", options -> options.enableCfFrontend = true);
+    new DebugStreamComparator()
+        .add("CF", createStream(cfConfig))
+        .add("R8/CF", createStream(r8CfConfig))
+        .add("R8/Jar", createStream(r8JarConfig))
+        .add("D8", createStream(d8Config))
+        .compare();
+  }
+
+  @Test
+  // Verify that ASM fails when using expanded frames directly.
+  // See b/111296969
+  public void runTestOnAsmDump() throws Exception {
+    Path out = temp.getRoot().toPath().resolve("out.jar");
+    ArchiveConsumer consumer = new ArchiveConsumer(out);
+    consumer.accept(
+        ArrayDimensionGreaterThanSevenTestDump.dump(),
+        DescriptorUtils.javaTypeToDescriptor(NAME),
+        null);
+    consumer.finished(null);
+    ProcessResult result = ToolHelper.runJava(out, NAME);
+    assertEquals("Expected ASM to fail when using visitFrame(F_NEW, ...)", 1, result.exitCode);
+    assertThat(result.stderr, containsString("java.lang.NoClassDefFoundError: F"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
index f2e96b3..f010357 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
@@ -3,11 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
+
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.google.common.collect.ImmutableList;
@@ -25,45 +27,64 @@
 @RunWith(Parameterized.class)
 public class DebugInfoWhenInliningTest extends DebugTestBase {
 
+  public enum Config {
+    CF,
+    DEX_NO_FORCE_JUMBO,
+    DEX_FORCE_JUMBO
+  };
+
   private static final String SOURCE_FILE = "Inlining1.java";
 
   private DebugTestConfig makeConfig(
       LineNumberOptimization lineNumberOptimization,
-      boolean writeProguardMap)
+      boolean writeProguardMap,
+      RuntimeKind runtimeKind)
       throws Exception {
-    AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
+    DebugTestConfig config = null;
     Path outdir = temp.newFolder().toPath();
-    Path outjar = outdir.resolve("dex_r8_compiled.jar");
-    Path proguardMapPath = writeProguardMap ? outdir.resolve("proguard.map") : null;
+    Path outjar = outdir.resolve("r8_compiled.jar");
     R8Command.Builder builder =
-        R8Command.builder()
-            .addProgramFiles(DEBUGGEE_JAR)
-            .setMinApiLevel(minSdk.getLevel())
-            .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
-            .setMode(CompilationMode.RELEASE)
-            .setOutput(outjar, OutputMode.DexIndexed);
-    if (proguardMapPath != null) {
-      builder.setProguardMapOutputPath(proguardMapPath);
+        R8Command.builder().addProgramFiles(DEBUGGEE_JAR).setMode(CompilationMode.RELEASE);
+
+    if (runtimeKind == RuntimeKind.DEX) {
+      AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
+      builder
+          .setMinApiLevel(minSdk.getLevel())
+          .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
+          .setOutput(outjar, OutputMode.DexIndexed);
+      config = new DexDebugTestConfig(outjar);
+    } else {
+      assert (runtimeKind == RuntimeKind.CF);
+      builder.setOutput(outjar, OutputMode.ClassFile);
+      config = new CfDebugTestConfig(outjar);
     }
+
+    if (writeProguardMap) {
+      Path proguardMapPath = outdir.resolve("proguard.map");
+      builder.setProguardMapOutputPath(proguardMapPath);
+      config.setProguardMap(proguardMapPath);
+    }
+
     ToolHelper.runR8(
         builder.build(), options -> {
           options.lineNumberOptimization = lineNumberOptimization;
           options.testing.forceJumboStringProcessing = forceJumboStringProcessing;
         });
-    DebugTestConfig config = new DexDebugTestConfig(outjar);
-    config.setProguardMap(proguardMapPath);
+
     return config;
   }
 
   private boolean forceJumboStringProcessing;
+  private RuntimeKind runtimeKind;
 
-  @Parameters(name="forceJumbo: {0}")
-  public static Collection<Boolean> data() {
-    return Arrays.asList(true, false);
+  @Parameters(name = "config: {0}")
+  public static Collection<Config> data() {
+    return Arrays.asList(Config.values());
   }
 
-  public DebugInfoWhenInliningTest(boolean forceJumboStringProcessing) {
-    this.forceJumboStringProcessing = forceJumboStringProcessing;
+  public DebugInfoWhenInliningTest(Config config) {
+    this.forceJumboStringProcessing = config == Config.DEX_FORCE_JUMBO;
+    this.runtimeKind = config == Config.CF ? RuntimeKind.CF : RuntimeKind.DEX;
   }
 
   @Test
@@ -75,7 +96,7 @@
     // (innermost callee) the line numbers are actually 7, 7, 32, 32, ... but even if the positions
     // are emitted duplicated in the dex code, the debugger stops only when there's a change.
     int[] lineNumbers = {7, 32, 11, 7};
-    testEachLine(makeConfig(LineNumberOptimization.OFF, false), lineNumbers);
+    testEachLine(makeConfig(LineNumberOptimization.OFF, false, runtimeKind), lineNumbers);
   }
 
   @Test
@@ -87,13 +108,13 @@
     // (innermost callee) the line numbers are actually 7, 7, 32, 32, ... but even if the positions
     // are emitted duplicated in the dex code, the debugger stops only when there's a change.
     int[] lineNumbers = {7, 32, 11, 7};
-    testEachLine(makeConfig(LineNumberOptimization.OFF, true), lineNumbers);
+    testEachLine(makeConfig(LineNumberOptimization.OFF, true, runtimeKind), lineNumbers);
   }
 
   @Test
   public void testEachLineOptimized() throws Throwable {
     int[] lineNumbers = {1, 2, 3, 4, 5, 6, 7, 8};
-    testEachLine(makeConfig(LineNumberOptimization.ON, false), lineNumbers);
+    testEachLine(makeConfig(LineNumberOptimization.ON, false, runtimeKind), lineNumbers);
   }
 
   @Test
@@ -130,7 +151,7 @@
                 new SignatureAndLine("void Inlining2.differentFileMultilevelInliningLevel1()", 36),
                 new SignatureAndLine("void main(java.lang.String[])", 26)));
     testEachLine(
-        makeConfig(LineNumberOptimization.ON, true), lineNumbers, inlineFramesList);
+        makeConfig(LineNumberOptimization.ON, true, runtimeKind), lineNumbers, inlineFramesList);
   }
 
   private void testEachLine(DebugTestConfig config, int[] lineNumbers) throws Throwable {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
index eb85a7c..3a83e30 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
@@ -214,7 +214,8 @@
       try {
         if (done) {
           assertTrue(
-              "Not all streams completed at the same time",
+              "Not all streams completed at the same time. "
+                  + "Set 'DebugTestBase.DEBUG_TEST = true' to aid in diagnosing the issue.",
               states.stream().allMatch(Objects::isNull));
           return;
         } else {
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index bda9ac5..6877617 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -10,11 +10,22 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.graph.invokesuper2.C0;
+import com.android.tools.r8.graph.invokesuper2.C1;
+import com.android.tools.r8.graph.invokesuper2.C2;
+import com.android.tools.r8.graph.invokesuper2.I0;
+import com.android.tools.r8.graph.invokesuper2.I1;
+import com.android.tools.r8.graph.invokesuper2.I2;
+import com.android.tools.r8.graph.invokesuper2.I3;
+import com.android.tools.r8.graph.invokesuper2.I4;
+import com.android.tools.r8.graph.invokesuper2.Main;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
 import java.util.Collections;
 import org.junit.Test;
 
@@ -200,6 +211,63 @@
     AndroidApp processedApp = processApplication(application);
     assertEquals("42", runArt(processedApp));
   }
+
+  @Test
+  public void testLookupSuperTarget() throws Exception {
+    String pkg = Main.class.getPackage().getName().replace('.', '/');
+
+    AndroidApp.Builder builder = AndroidApp.builder();
+    for (Class clazz : new Class[]{
+        I0.class, I1.class, I2.class, I3.class, I4.class,
+        C0.class, C1.class, C2.class,
+        Main.class}) {
+      builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
+      // At least java.lang.Object is needed as interface method lookup have special handling
+      // of methods on java.lang.Object.
+      builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+    }
+    AndroidApp application = builder.build();
+    AppInfo appInfo = getAppInfo(application);
+    DexItemFactory factory = appInfo.dexItemFactory;
+
+    DexType i0 = factory.createType("L" + pkg + "/I0;");
+    DexType i1 = factory.createType("L" + pkg + "/I1;");
+    DexType i2 = factory.createType("L" + pkg + "/I2;");
+    DexType i3 = factory.createType("L" + pkg + "/I3;");
+    DexType i4 = factory.createType("L" + pkg + "/I4;");
+    DexType c0 = factory.createType("L" + pkg + "/C0;");
+    DexType c1 = factory.createType("L" + pkg + "/C1;");
+    DexType c2 = factory.createType("L" + pkg + "/C2;");
+
+    DexProto mProto = factory.createProto(factory.intType);
+    DexString m = factory.createString("m");
+    DexMethod mOnC0 = factory.createMethod(c0, mProto, m);
+    DexMethod mOnC1 = factory.createMethod(c1, mProto, m);
+    DexMethod mOnI0 = factory.createMethod(i0, mProto, m);
+    DexMethod mOnI1 = factory.createMethod(i1, mProto, m);
+    DexMethod mOnI2 = factory.createMethod(i2, mProto, m);
+    DexMethod mOnI3 = factory.createMethod(i3, mProto, m);
+    DexMethod mOnI4 = factory.createMethod(i4, mProto, m);
+
+    assertEquals(mOnI0, appInfo.lookupSuperTarget(mOnC0, c1).method);
+    assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI1, c1).method);
+    assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI2, c1).method);
+
+    assertEquals(mOnI0, appInfo.lookupSuperTarget(mOnC1, c2).method);
+    assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI3, c2).method);
+    assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI4, c2).method);
+
+    // Copy classes to run on the Java VM.
+    Path out = temp.newFolder().toPath();
+    copyTestClasses(out, I0.class, I1.class, I2.class, I3.class, I4.class);
+    copyTestClasses(out, C0.class, C1.class, C2.class, Main.class);
+    ProcessResult result = ToolHelper.runJava(out, Main.class.getCanonicalName());
+    assertEquals(0, result.exitCode);
+
+    // Process the application and expect the same result on Art.
+    AndroidApp processedApp = processApplication(application);
+    assertEquals(result.stdout, runArt(processedApp, Main.class.getCanonicalName()));
+  }
 }
 
 
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/C0.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/C0.java
new file mode 100644
index 0000000..8e92fb4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/C0.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokesuper2;
+
+public class C0  implements I0 {
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/C1.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/C1.java
new file mode 100644
index 0000000..ff98da9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/C1.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokesuper2;
+
+public class C1 extends C0 implements I1, I2 {
+  public int m() {
+    // super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/C0.m()I
+    System.out.println(super.m());
+    // I1.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I1.m:()I
+    System.out.println(I1.super.m());
+    // I2.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I2.m:()I
+    System.out.println(I2.super.m());
+    return 3;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/C2.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/C2.java
new file mode 100644
index 0000000..abdf7ff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/C2.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokesuper2;
+
+public class C2 extends C0 implements I3, I4 {
+  public int m() {
+    // super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/C0.m()I
+    System.out.println(super.m());
+    // I1.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I3.m:()I
+    System.out.println(I3.super.m());
+    // I2.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I4.m:()I
+    System.out.println(I4.super.m());
+    return 3;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I0.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I0.java
new file mode 100644
index 0000000..02b59a6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I0.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokesuper2;
+
+public interface I0 {
+  default int m() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I1.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I1.java
new file mode 100644
index 0000000..0593d90
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I1.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokesuper2;
+
+public interface I1 extends I0 {
+  default int m() {
+    return 1;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I2.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I2.java
new file mode 100644
index 0000000..22956c3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I2.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokesuper2;
+
+public interface I2 extends I0 {
+  default int m() {
+    return 2;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I3.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I3.java
new file mode 100644
index 0000000..e64154e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I3.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokesuper2;
+
+public interface I3 extends I1 {
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I4.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I4.java
new file mode 100644
index 0000000..ddc5ad0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I4.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokesuper2;
+
+public interface I4 extends I2 {
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/Main.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/Main.java
new file mode 100644
index 0000000..6a2a3b9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/Main.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokesuper2;
+
+public class Main {
+
+  public static void main(String[] args) {
+    System.out.println(new C1().m());
+    System.out.println(new C2().m());
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java b/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java
new file mode 100644
index 0000000..56699e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java
@@ -0,0 +1,214 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.callgraph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.conversion.CallGraph.CycleEliminator;
+import com.android.tools.r8.ir.conversion.CallGraph.Node;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BooleanSupplier;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class CycleEliminationTest extends TestBase {
+
+  private static class Configuration {
+
+    final Collection<Node> nodes;
+    final Set<Node> forceInline;
+    final BooleanSupplier test;
+
+    Configuration(Collection<Node> nodes, Set<Node> forceInline, BooleanSupplier test) {
+      this.nodes = nodes;
+      this.forceInline = forceInline;
+      this.test = test;
+    }
+  }
+
+  private DexItemFactory dexItemFactory = new DexItemFactory();
+
+  @Rule public final ExpectedException exception = ExpectedException.none();
+
+  @Test
+  public void testSimpleCycle() {
+    Node method = createNode("n1");
+    Node forceInlinedMethod = createForceInlinedNode("n2");
+
+    Iterable<Collection<Node>> orderings =
+        ImmutableList.of(
+            ImmutableList.of(method, forceInlinedMethod),
+            ImmutableList.of(forceInlinedMethod, method));
+
+    for (Collection<Node> nodes : orderings) {
+      // Create a cycle between the two nodes.
+      method.addCallee(forceInlinedMethod);
+      forceInlinedMethod.addCallee(method);
+
+      // Check that the cycle eliminator finds the cycle.
+      CycleEliminator cycleEliminator = new CycleEliminator(nodes, new InternalOptions());
+      assertEquals(1, cycleEliminator.breakCycles());
+
+      // The edge from method to forceInlinedMethod should be removed to ensure that force inlining
+      // will work.
+      assertTrue(forceInlinedMethod.isLeaf());
+
+      // Check that the cycle eliminator agrees that there are no more cycles left.
+      assertEquals(0, cycleEliminator.breakCycles());
+    }
+  }
+
+  @Test
+  public void testSimpleCycleWithCyclicForceInlining() {
+    Node method = createForceInlinedNode("n1");
+    Node forceInlinedMethod = createForceInlinedNode("n2");
+
+    // Create a cycle between the two nodes.
+    method.addCallee(forceInlinedMethod);
+    forceInlinedMethod.addCallee(method);
+
+    CycleEliminator cycleEliminator =
+        new CycleEliminator(ImmutableList.of(method, forceInlinedMethod), new InternalOptions());
+
+    exception.expect(CompilationError.class);
+    exception.expectMessage(CycleEliminator.CYCLIC_FORCE_INLINING_MESSAGE);
+
+    // Should throw because force inlining will fail.
+    cycleEliminator.breakCycles();
+  }
+
+  @Test
+  public void testGraphWithNestedCycles() {
+    Node n1 = createNode("n1");
+    Node n2 = createNode("n2");
+    Node n3 = createNode("n3");
+
+    BooleanSupplier canInlineN1 =
+        () -> {
+          // The node n1 should be force inlined into n2 and n3, so these edges must be kept.
+          assertTrue(n2.hasCallee(n1));
+          assertTrue(n3.hasCallee(n1));
+          // Furthermore, the edge from n1 to n2 must be removed.
+          assertFalse(n1.hasCallee(n2));
+          return true;
+        };
+
+    BooleanSupplier canInlineN3 =
+        () -> {
+          // The node n3 should be force inlined into n2, so this edge must be kept.
+          assertTrue(n2.hasCallee(n3));
+          // Furthermore, one of the edges n1 -> n2 and n3 -> n1 must be removed.
+          assertFalse(n1.hasCallee(n2) && n3.hasCallee(n1));
+          return true;
+        };
+
+    BooleanSupplier canInlineN1N3 =
+        () -> {
+          // The edge n1 -> n2 must be removed.
+          assertFalse(n1.hasCallee(n2));
+          // Check that both can be force inlined.
+          return canInlineN1.getAsBoolean() && canInlineN3.getAsBoolean();
+        };
+
+    List<Collection<Node>> orderings =
+        ImmutableList.of(
+            ImmutableList.of(n1, n2, n3),
+            ImmutableList.of(n1, n3, n2),
+            ImmutableList.of(n2, n1, n3),
+            ImmutableList.of(n2, n3, n1),
+            ImmutableList.of(n3, n1, n2),
+            ImmutableList.of(n3, n2, n1));
+
+    List<Configuration> configurations = new ArrayList<>();
+    // All orderings, where no methods are marked as force inline.
+    orderings
+        .stream()
+        .map(ordering -> new Configuration(ordering, ImmutableSet.of(), null))
+        .forEach(configurations::add);
+    // All orderings, where n1 is marked as force inline
+    // (the configuration where n2 is marked as force inline is symmetric).
+    orderings
+        .stream()
+        .map(ordering -> new Configuration(ordering, ImmutableSet.of(n1), canInlineN1))
+        .forEach(configurations::add);
+    // All orderings, where n3 is marked as force inline.
+    orderings
+        .stream()
+        .map(ordering -> new Configuration(ordering, ImmutableSet.of(n3), canInlineN3))
+        .forEach(configurations::add);
+    // All orderings, where n1 and n3 are marked as force inline.
+    orderings
+        .stream()
+        .map(ordering -> new Configuration(ordering, ImmutableSet.of(n1, n3), canInlineN1N3))
+        .forEach(configurations::add);
+
+    for (Configuration configuration : configurations) {
+      // Create a cycle between the three nodes.
+      n1.addCallee(n2);
+      n2.addCallee(n3);
+      n3.addCallee(n1);
+
+      // Create a cycle in the graph between node n1 and n2.
+      n2.addCallee(n1);
+
+      for (Node node : configuration.nodes) {
+        if (configuration.forceInline.contains(node)) {
+          node.method.markForceInline();
+        } else {
+          node.method.unsetForceInline();
+        }
+      }
+
+      // Check that the cycle eliminator finds the cycles.
+      CycleEliminator cycleEliminator =
+          new CycleEliminator(configuration.nodes, new InternalOptions());
+      int numberOfCycles = cycleEliminator.breakCycles();
+      if (numberOfCycles == 1) {
+        // If only one cycle was removed, then it must be the edge from n1 -> n2 that was removed.
+        assertTrue(n1.isLeaf());
+      } else {
+        // Check that the cycle eliminator found both cycles.
+        assertEquals(2, numberOfCycles);
+      }
+
+      // Check that the cycle eliminator agrees that there are no more cycles left.
+      assertEquals(0, cycleEliminator.breakCycles());
+
+      // Check that force inlining is guaranteed to succeed.
+      if (configuration.test != null) {
+        assertTrue(configuration.test.getAsBoolean());
+      }
+    }
+  }
+
+  private Node createNode(String methodName) {
+    DexMethod signature =
+        dexItemFactory.createMethod(
+            dexItemFactory.objectType,
+            dexItemFactory.createProto(dexItemFactory.voidType),
+            methodName);
+    return new Node(new DexEncodedMethod(signature, null, null, null, null));
+  }
+
+  private Node createForceInlinedNode(String methodName) {
+    Node node = createNode(methodName);
+    node.method.markForceInline();
+    return node;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 3239191..e7a8285 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.optimize.classinliner.code.C;
 import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
 import com.android.tools.r8.ir.optimize.classinliner.invalidroot.InvalidRootsTestClass;
+import com.android.tools.r8.ir.optimize.classinliner.lambdas.LambdasTestClass;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
@@ -301,6 +302,37 @@
     assertFalse(inspector.clazz(InvalidRootsTestClass.B.class).isPresent());
   }
 
+  @Test
+  public void testDesugaredLambdas() throws Exception {
+    byte[][] classes = {
+        ToolHelper.getClassAsBytes(LambdasTestClass.class),
+        ToolHelper.getClassAsBytes(LambdasTestClass.Iface.class),
+        ToolHelper.getClassAsBytes(LambdasTestClass.IfaceUtil.class),
+    };
+    AndroidApp app = runR8(buildAndroidApp(classes), LambdasTestClass.class);
+
+    String javaOutput = runOnJava(LambdasTestClass.class);
+    String artOutput = runOnArt(app, LambdasTestClass.class);
+    assertEquals(javaOutput, artOutput);
+
+    DexInspector inspector = new DexInspector(app);
+    ClassSubject clazz = inspector.clazz(LambdasTestClass.class);
+
+    assertEquals(
+        Sets.newHashSet(
+            "java.lang.StringBuilder"),
+        collectTypes(clazz, "testStatelessLambda", "void"));
+
+    assertEquals(
+        Sets.newHashSet(
+            "java.lang.StringBuilder"),
+        collectTypes(clazz, "testStatefulLambda", "void", "java.lang.String", "java.lang.String"));
+
+    assertEquals(0,
+        inspector.allClasses().stream()
+            .filter(ClassSubject::isSynthesizedJavaLambdaClass).count());
+  }
+
   private Set<String> collectTypes(
       ClassSubject clazz, String methodName, String retValue, String... params) {
     return Stream.concat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
new file mode 100644
index 0000000..130f895
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.lambdas;
+
+public class LambdasTestClass {
+  private static int ID = 0;
+
+  private static int nextInt() {
+    return ID++;
+  }
+
+  private static String next() {
+    return Integer.toString(nextInt());
+  }
+
+  public interface Iface {
+    String foo();
+  }
+
+  public static class IfaceUtil {
+    public static void act(Iface iface) {
+      System.out.println("" + next() + "> " + iface.foo());
+    }
+  }
+
+  public static void main(String[] args) {
+    LambdasTestClass test = new LambdasTestClass();
+    test.testStatelessLambda();
+    test.testStatefulLambda(next(), next());
+  }
+
+  public static String exact() {
+    return next();
+  }
+
+  public static String almost(String... s) {
+    return next();
+  }
+
+  private synchronized void testStatelessLambda() {
+    IfaceUtil.act(() -> next());
+    IfaceUtil.act(LambdasTestClass::next);
+    IfaceUtil.act(LambdasTestClass::exact);
+    IfaceUtil.act(LambdasTestClass::almost);
+  }
+
+  private synchronized void testStatefulLambda(String a, String b) {
+    IfaceUtil.act(() -> a);
+    IfaceUtil.act(() -> a + b);
+    IfaceUtil.act((a + b)::toLowerCase);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 82381dd..9c15ce7 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -353,12 +353,16 @@
     return out.toByteArray();
   }
 
-  public List<byte[]> buildClasses() throws Exception {
-    List<byte[]> result = new ArrayList<>();
+  public ImmutableList.Builder<byte[]> buildClasses(ImmutableList.Builder<byte[]> builder)
+      throws Exception {
     for (ClassBuilder clazz : classes) {
-      result.add(compile(clazz));
+      builder.add(compile(clazz));
     }
-    return result;
+    return builder;
+  }
+
+  public List<byte[]> buildClasses() throws Exception {
+    return buildClasses(ImmutableList.builder()).build();
   }
 
   public AndroidApp build() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
index b9ff7ea..f3f1211 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
 import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -21,6 +20,7 @@
 import static org.objectweb.asm.Opcodes.RETURN;
 import static org.objectweb.asm.Opcodes.V1_8;
 
+import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DiagnosticsChecker;
 import com.android.tools.r8.R8Command;
@@ -33,11 +33,17 @@
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.FieldVisitor;
 import org.objectweb.asm.MethodVisitor;
 
+@RunWith(Parameterized.class)
 public class MinifierClassSignatureTest extends TestBase {
   /*
 
@@ -62,6 +68,16 @@
   String outerSignature = "<T:Ljava/lang/Object;>Ljava/lang/Object;";
   String extendsInnerSignature = "LOuter<TT;>.Inner;";
   String extendsInnerInnerSignature = "LOuter<TT;>.Inner.InnerInner;";
+  Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public MinifierClassSignatureTest(Backend backend) {
+    this.backend = backend;
+  }
 
   private byte[] dumpSimple(String classSignature) throws Exception {
 
@@ -290,26 +306,36 @@
       Consumer<DexInspector> inspect)
       throws Exception {
     DiagnosticsChecker checker = new DiagnosticsChecker();
-    DexInspector inspector = new DexInspector(
-      ToolHelper.runR8(R8Command.builder(checker)
-          .addClassProgramData(dumpSimple(signatures.get("Simple")), Origin.unknown())
-          .addClassProgramData(dumpBase(signatures.get("Base")), Origin.unknown())
-          .addClassProgramData(dumpOuter(signatures.get("Outer")), Origin.unknown())
-          .addClassProgramData(dumpInner(signatures.get("Outer$Inner")), Origin.unknown())
-          .addClassProgramData(
-              dumpExtendsInner(signatures.get("Outer$ExtendsInner")), Origin.unknown())
-          .addClassProgramData(
-              dumpInnerInner(signatures.get("Outer$Inner$InnerInner")), Origin.unknown())
-          .addClassProgramData(
-              dumpExtendsInnerInner(
-                  signatures.get("Outer$Inner$ExtendsInnerInner")), Origin.unknown())
-          .addProguardConfiguration(ImmutableList.of(
-              "-keepattributes InnerClasses,EnclosingMethod,Signature",
-              "-keep,allowobfuscation class **"
-          ), Origin.unknown())
-          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-          .setProguardMapConsumer(StringConsumer.emptyConsumer())
-          .build()));
+    assert (backend == Backend.CF || backend == Backend.DEX);
+    DexInspector inspector =
+        new DexInspector(
+            ToolHelper.runR8(
+                R8Command.builder(checker)
+                    .addClassProgramData(dumpSimple(signatures.get("Simple")), Origin.unknown())
+                    .addClassProgramData(dumpBase(signatures.get("Base")), Origin.unknown())
+                    .addClassProgramData(dumpOuter(signatures.get("Outer")), Origin.unknown())
+                    .addClassProgramData(dumpInner(signatures.get("Outer$Inner")), Origin.unknown())
+                    .addClassProgramData(
+                        dumpExtendsInner(signatures.get("Outer$ExtendsInner")), Origin.unknown())
+                    .addClassProgramData(
+                        dumpInnerInner(signatures.get("Outer$Inner$InnerInner")), Origin.unknown())
+                    .addClassProgramData(
+                        dumpExtendsInnerInner(signatures.get("Outer$Inner$ExtendsInnerInner")),
+                        Origin.unknown())
+                    .addProguardConfiguration(
+                        ImmutableList.of(
+                            "-keepattributes InnerClasses,EnclosingMethod,Signature",
+                            "-keep,allowobfuscation class **"),
+                        Origin.unknown())
+                    .setProgramConsumer(
+                        backend == Backend.DEX
+                            ? DexIndexedConsumer.emptyConsumer()
+                            : ClassFileConsumer.emptyConsumer())
+                    .setProguardMapConsumer(StringConsumer.emptyConsumer())
+                    .build(),
+                options -> {
+                  options.testing.suppressExperimentalCfBackendWarning = true;
+                }));
     // All classes are kept, and renamed.
     assertThat(inspector.clazz("Simple"), isRenamed());
     assertThat(inspector.clazz("Base"), isRenamed());
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
index e48ce56..a5351e2 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
@@ -21,6 +21,7 @@
 import static org.objectweb.asm.Opcodes.RETURN;
 import static org.objectweb.asm.Opcodes.V1_8;
 
+import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DiagnosticsChecker;
 import com.android.tools.r8.R8Command;
@@ -34,13 +35,18 @@
 import com.android.tools.r8.utils.DexInspector.FieldSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Map;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.FieldVisitor;
 import org.objectweb.asm.MethodVisitor;
 
-
+@RunWith(Parameterized.class)
 public class MinifierFieldSignatureTest extends TestBase {
   /*
 
@@ -59,6 +65,16 @@
   private String anArrayOfXSignature = "[TX;";
   private String aFieldsOfXSignature = "LFields<TX;>;";
   private String aFieldsOfXInnerSignature = "LFields<TX;>.Inner;";
+  private Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public MinifierFieldSignatureTest(Backend backend) {
+    this.backend = backend;
+  }
 
   public byte[] dumpFields(Map<String, String> signatures) throws Exception {
 
@@ -164,17 +180,27 @@
       Consumer<DexInspector> inspect)
       throws Exception {
     DiagnosticsChecker checker = new DiagnosticsChecker();
-    DexInspector inspector = new DexInspector(
-      ToolHelper.runR8(R8Command.builder(checker)
-          .addClassProgramData(dumpFields(signatures), Origin.unknown())
-          .addClassProgramData(dumpInner(), Origin.unknown())
-          .addProguardConfiguration(ImmutableList.of(
-              "-keepattributes InnerClasses,EnclosingMethod,Signature",
-              "-keep,allowobfuscation class ** { *; }"
-          ), Origin.unknown())
-          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-          .setProguardMapConsumer(StringConsumer.emptyConsumer())
-          .build()));
+    assert (backend == Backend.CF || backend == Backend.DEX);
+    DexInspector inspector =
+        new DexInspector(
+            ToolHelper.runR8(
+                R8Command.builder(checker)
+                    .addClassProgramData(dumpFields(signatures), Origin.unknown())
+                    .addClassProgramData(dumpInner(), Origin.unknown())
+                    .addProguardConfiguration(
+                        ImmutableList.of(
+                            "-keepattributes InnerClasses,EnclosingMethod,Signature",
+                            "-keep,allowobfuscation class ** { *; }"),
+                        Origin.unknown())
+                    .setProgramConsumer(
+                        backend == Backend.DEX
+                            ? DexIndexedConsumer.emptyConsumer()
+                            : ClassFileConsumer.emptyConsumer())
+                    .setProguardMapConsumer(StringConsumer.emptyConsumer())
+                    .build(),
+                options -> {
+                  options.testing.suppressExperimentalCfBackendWarning = true;
+                }));
     // All classes are kept, and renamed.
     ClassSubject clazz = inspector.clazz("Fields");
     assertThat(clazz, isRenamed());
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
index 41750ae..baeedbb 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
@@ -24,6 +24,7 @@
 import static org.objectweb.asm.Opcodes.RETURN;
 import static org.objectweb.asm.Opcodes.V1_8;
 
+import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DiagnosticsChecker;
 import com.android.tools.r8.R8Command;
@@ -37,12 +38,18 @@
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Map;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.FieldVisitor;
 import org.objectweb.asm.MethodVisitor;
 
+@RunWith(Parameterized.class)
 public class MinifierMethodSignatureTest extends TestBase {
   /*
 
@@ -61,6 +68,16 @@
   private String parameterizedReturnSignature = "()LMethods<TX;>.Inner;";
   private String parameterizedArgumentsSignature = "(TX;LMethods<TX;>.Inner;)V";
   private String parametrizedThrowsSignature = "()V^TX;";
+  Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public MinifierMethodSignatureTest(Backend backend) {
+    this.backend = backend;
+  }
 
   private byte[] dumpMethods(Map<String, String> signatures) throws Exception {
 
@@ -185,17 +202,27 @@
       Consumer<DexInspector> inspect)
       throws Exception {
     DiagnosticsChecker checker = new DiagnosticsChecker();
-    DexInspector inspector = new DexInspector(
-      ToolHelper.runR8(R8Command.builder(checker)
-          .addClassProgramData(dumpMethods(signatures), Origin.unknown())
-          .addClassProgramData(dumpInner(), Origin.unknown())
-          .addProguardConfiguration(ImmutableList.of(
-              "-keepattributes InnerClasses,EnclosingMethod,Signature",
-              "-keep,allowobfuscation class ** { *; }"
-          ), Origin.unknown())
-          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-          .setProguardMapConsumer(StringConsumer.emptyConsumer())
-          .build()));
+    assert (backend == Backend.CF || backend == Backend.DEX);
+    DexInspector inspector =
+        new DexInspector(
+            ToolHelper.runR8(
+                R8Command.builder(checker)
+                    .addClassProgramData(dumpMethods(signatures), Origin.unknown())
+                    .addClassProgramData(dumpInner(), Origin.unknown())
+                    .addProguardConfiguration(
+                        ImmutableList.of(
+                            "-keepattributes InnerClasses,EnclosingMethod,Signature",
+                            "-keep,allowobfuscation class ** { *; }"),
+                        Origin.unknown())
+                    .setProgramConsumer(
+                        backend == Backend.DEX
+                            ? DexIndexedConsumer.emptyConsumer()
+                            : ClassFileConsumer.emptyConsumer())
+                    .setProguardMapConsumer(StringConsumer.emptyConsumer())
+                    .build(),
+                options -> {
+                  options.testing.suppressExperimentalCfBackendWarning = true;
+                }));
     // All classes are kept, and renamed.
     ClassSubject clazz = inspector.clazz("Methods");
     assertThat(clazz, isRenamed());
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index c929eaf..878cb7c 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.DexInstructionSubject;
 import com.android.tools.r8.utils.DexInspector.InstructionSubject;
 import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
@@ -82,7 +83,8 @@
     assertTrue(insn.isInvoke());
     assertTrue(((InvokeInstructionSubject) insn)
         .invokedMethod().name.toString().equals("throwNpe"));
-    assertTrue(nextInstruction(instructions).isConst4());
+    insn = nextInstruction(instructions);
+    assertTrue(insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
     assertTrue(nextInstruction(instructions).isThrow());
     assertFalse(instructions.hasNext());
 
@@ -105,7 +107,8 @@
       assertTrue(((InvokeInstructionSubject) insn)
           .invokedMethod().name.toString().equals("throwNpe"));
     }
-    assertTrue(nextInstruction(instructions).isConst4());
+    insn = nextInstruction(instructions);
+    assertTrue(insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
     assertTrue(nextInstruction(instructions).isThrow());
     assertFalse(instructions.hasNext());
 
@@ -119,7 +122,8 @@
     assertTrue(insn.isInvoke());
     assertTrue(((InvokeInstructionSubject) insn)
         .invokedMethod().name.toString().equals("innerNotReachable"));
-    assertTrue(nextInstruction(instructions).isConst4());
+    insn = nextInstruction(instructions);
+    assertTrue(insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
     assertTrue(nextInstruction(instructions).isThrow());
     assertFalse(instructions.hasNext());
   }
diff --git a/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
new file mode 100644
index 0000000..84fc5c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
@@ -0,0 +1,361 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b111250398;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.Iget;
+import com.android.tools.r8.code.IgetObject;
+import com.android.tools.r8.code.Sget;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import org.junit.Test;
+
+// Copy of javax.inject.Provider.
+interface Provider<T> {
+  T get();
+}
+
+// Copy of dagger.internal.SingleClass.
+final class SingleCheck<T> implements Provider<T> {
+  private static final Object UNINITIALIZED = new Object();
+
+  private volatile Provider<T> provider;
+  private volatile Object instance = UNINITIALIZED;
+
+  private SingleCheck(Provider<T> provider) {
+    assert provider != null;
+    this.provider = provider;
+  }
+
+  @SuppressWarnings("unchecked") // cast only happens when result comes from the delegate provider
+  @Override
+  public T get() {
+    Object local = instance;
+    if (local == UNINITIALIZED) {
+      // provider is volatile and might become null after the check, so retrieve the provider first
+      Provider<T> providerReference = provider;
+      if (providerReference == null) {
+        // The provider was null, so the instance must already be set
+        local = instance;
+      } else {
+        local = providerReference.get();
+        instance = local;
+
+        // Null out the reference to the provider. We are never going to need it again, so we can
+        // make it eligible for GC.
+        provider = null;
+      }
+    }
+    return (T) local;
+  }
+
+  // This method is not relevant for the test.
+  /*
+  public static <P extends Provider<T>, T> Provider<T> provider(P provider) {
+    // If a scoped @Binds delegates to a scoped binding, don't cache the value again.
+    if (provider instanceof SingleCheck || provider instanceof DoubleCheck) {
+      return provider;
+    }
+    return new SingleCheck<T>(checkNotNull(provider));
+  }
+  */
+}
+
+// Several field gets on non-volatile and volatile fields on the same class.
+class A {
+  int t;
+  int f;
+  static int sf;
+  volatile int v;
+  static volatile int sv;
+
+  public void mf() {
+    t = f;
+    t = f;
+    t = f;
+    t = f;
+    t = f;
+  }
+
+  public void mfWithMonitor() {
+    t = f;
+    synchronized (this) {
+      t = f;
+      t = f;
+    }
+    t = f;
+    t = f;
+  }
+
+  public void msf() {
+    t = sf;
+    t = sf;
+    t = sf;
+    t = sf;
+    t = sf;
+  }
+
+  public void mv() {
+    t = v;
+    t = v;
+    t = v;
+    t = v;
+    t = v;
+  }
+
+  public void msv() {
+    t = sv;
+    t = sv;
+    t = sv;
+    t = sv;
+    t = sv;
+  }
+}
+
+// Several field gets on non-volatile and volatile fields on different class.
+class B {
+  int t;
+
+  public void mf(A a) {
+    t = a.f;
+    t = a.f;
+    t = a.f;
+    t = a.f;
+    t = a.f;
+  }
+
+  public void msf() {
+    t = A.sf;
+    t = A.sf;
+    t = A.sf;
+    t = A.sf;
+    t = A.sf;
+  }
+
+  public void mv(A a) {
+    t = a.v;
+    t = a.v;
+    t = a.v;
+    t = a.v;
+    t = a.v;
+  }
+
+  public void msv() {
+    t = A.sv;
+    t = A.sv;
+    t = A.sv;
+    t = A.sv;
+    t = A.sv;
+  }
+}
+
+// Modified sample from http://tutorials.jenkov.com/java-concurrency/volatile.html.
+class C {
+  private int years;
+  private int months;
+  private volatile int days;
+
+  public int totalDays() {
+    int total = this.days;
+    total += months * 30;
+    total += years * 365;
+    return total;
+  }
+
+  public int totalDaysTimes2() {
+    int total = this.days;
+    total += months * 30;
+    total += years * 365;
+    total += this.days;
+    total += months * 30;
+    total += years * 365;
+    return total;
+  }
+
+  public int totalDaysTimes3() {
+    int total = this.days;
+    total += months * 30;
+    total += years * 365;
+    total += this.days;
+    total += months * 30;
+    total += years * 365;
+    total += this.days;
+    total += months * 30;
+    total += years * 365;
+    return total;
+  }
+
+  public void update(int years, int months, int days){
+    this.years  = years;
+    this.months = months;
+    this.days   = days;
+  }
+}
+
+public class B111250398 extends TestBase {
+
+  private void releaseMode(InternalOptions options) {
+    options.debug = false;
+  }
+
+  private long countIget(DexCode code, DexField field) {
+    return Arrays.stream(code.instructions)
+        .filter(instruction -> instruction instanceof Iget)
+        .map(instruction -> (Iget) instruction)
+        .filter(get -> get.getField() == field)
+        .count();
+  }
+
+  private long countSget(DexCode code, DexField field) {
+    return Arrays.stream(code.instructions)
+        .filter(instruction -> instruction instanceof Sget)
+        .map(instruction -> (Sget) instruction)
+        .filter(get -> get.getField() == field)
+        .count();
+  }
+
+  private long countIgetObject(MethodSubject method, FieldSubject field) {
+    return Arrays.stream(method.getMethod().getCode().asDexCode().instructions)
+        .filter(instruction -> instruction instanceof IgetObject)
+        .map(instruction -> (IgetObject) instruction)
+        .filter(get -> get.getField() == field.getField().field)
+        .count();
+  }
+
+  private void check(DexInspector inspector, int mfOnBGets, int msfOnBGets) {
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+    MethodSubject mfOnA = classA.method("void", "mf", ImmutableList.of());
+    assertThat(mfOnA, isPresent());
+    MethodSubject mfWithMonitorOnA = classA.method("void", "mfWithMonitor", ImmutableList.of());
+    assertThat(mfWithMonitorOnA, isPresent());
+    MethodSubject msfOnA = classA.method("void", "msf", ImmutableList.of());
+    assertThat(msfOnA, isPresent());
+    MethodSubject mvOnA = classA.method("void", "mv", ImmutableList.of());
+    assertThat(mvOnA, isPresent());
+    MethodSubject msvOnA = classA.method("void", "msv", ImmutableList.of());
+    assertThat(msvOnA, isPresent());
+    FieldSubject fOnA = classA.field("int", "f");
+    assertThat(fOnA, isPresent());
+    FieldSubject sfOnA = classA.field("int", "sf");
+    assertThat(sfOnA, isPresent());
+    FieldSubject vOnA = classA.field("int", "v");
+    assertThat(vOnA, isPresent());
+    FieldSubject svOnA = classA.field("int", "sv");
+    assertThat(svOnA, isPresent());
+    ClassSubject classB = inspector.clazz(B.class);
+    assertThat(classB, isPresent());
+    MethodSubject mfOnB = classB.method("void", "mf", ImmutableList.of(classA.getOriginalName()));
+    assertThat(mfOnB, isPresent());
+    MethodSubject msfOnB = classB.method("void", "msf", ImmutableList.of());
+    assertThat(msfOnB, isPresent());
+    MethodSubject mvOnB = classB.method("void", "mv", ImmutableList.of(classA.getOriginalName()));
+    assertThat(mvOnB, isPresent());
+    MethodSubject msvOnB = classB.method("void", "msv", ImmutableList.of());
+    assertThat(msvOnB, isPresent());
+    // Field load of volatile fields are never eliminated.
+    assertEquals(5, countIget(mvOnA.getMethod().getCode().asDexCode(), vOnA.getField().field));
+    assertEquals(5, countSget(msvOnA.getMethod().getCode().asDexCode(), svOnA.getField().field));
+    assertEquals(5, countIget(mvOnB.getMethod().getCode().asDexCode(), vOnA.getField().field));
+    assertEquals(5, countSget(msvOnB.getMethod().getCode().asDexCode(), svOnA.getField().field));
+    // For fields on the same class both separate compilation (D8) and whole program
+    // compilation (R8) will eliminate field loads on non-volatile fields.
+    assertEquals(1, countIget(mfOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
+    assertEquals(1, countSget(msfOnA.getMethod().getCode().asDexCode(), sfOnA.getField().field));
+    // TODO(111380066). This could be 2 in stead of 4, but right now the optimization tracks the
+    // combined set of fields for all successors, and for synchronized code all blocks have
+    // exceptional edges for ensuring monitor exit causing the active load to be invalidated for
+    // both normal and exceptional successors.
+    assertEquals(4,
+        countIget(mfWithMonitorOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
+
+    // For fields on other class both separate compilation (D8) and whole program
+    // compilation (R8) will differ in the eliminated field loads of non-volatile fields.
+    assertEquals(mfOnBGets,
+        countIget(mfOnB.getMethod().getCode().asDexCode(), fOnA.getField().field));
+    assertEquals(msfOnBGets,
+        countSget(msfOnB.getMethod().getCode().asDexCode(), sfOnA.getField().field));
+  }
+
+  @Test
+  public void testSeparateCompilation() throws Exception {
+    DexInspector inspector =
+        new DexInspector(compileWithD8(readClasses(A.class, B.class), this::releaseMode));
+    check(inspector, 5, 5);
+  }
+
+  @Test
+  public void testWholeProgram() throws Exception {
+    DexInspector inspector =
+        new DexInspector(compileWithR8(readClasses(A.class, B.class), this::releaseMode));
+    // The reason for getting two Igets in B.mf is that the first Iget inserts a NonNull
+    // instruction which creates a new value for the remaining Igets.
+    check(inspector, 2, 1);
+  }
+
+  private void checkMixed(AndroidApp app) throws Exception{
+    DexInspector inspector = new DexInspector(app);
+    ClassSubject classC = inspector.clazz(C.class);
+    assertThat(classC, isPresent());
+    MethodSubject totalDays = classC.method("int", "totalDays", ImmutableList.of());
+    assertThat(totalDays, isPresent());
+    MethodSubject totalDaysTimes2 = classC.method("int", "totalDaysTimes2", ImmutableList.of());
+    assertThat(totalDaysTimes2, isPresent());
+    MethodSubject totalDaysTimes3 = classC.method("int", "totalDaysTimes3", ImmutableList.of());
+    assertThat(totalDaysTimes3, isPresent());
+    FieldSubject years = classC.field("int", "years");
+    assertThat(years, isPresent());
+    FieldSubject months = classC.field("int", "months");
+    assertThat(months, isPresent());
+    FieldSubject days = classC.field("int", "days");
+    assertThat(days, isPresent());
+
+
+    for (FieldSubject field : new FieldSubject[]{years, months, days}) {
+      assertEquals(1,
+          countIget(totalDays.getMethod().getCode().asDexCode(), field.getField().field));
+      assertEquals(2,
+          countIget(totalDaysTimes2.getMethod().getCode().asDexCode(), field.getField().field));
+      assertEquals(3,
+          countIget(totalDaysTimes3.getMethod().getCode().asDexCode(), field.getField().field));
+    }
+  }
+
+  @Test
+  public void testMixedVolatileNonVolatile() throws Exception {
+    AndroidApp app = readClasses(C.class);
+    checkMixed(compileWithD8(app, this::releaseMode));
+    checkMixed(compileWithR8(app, this::releaseMode));
+  }
+
+  private void checkDaggerSingleProviderGet(AndroidApp app) throws Exception {
+    DexInspector inspector = new DexInspector(app);
+    MethodSubject get =
+        inspector.clazz(SingleCheck.class).method("java.lang.Object", "get", ImmutableList.of());
+    assertThat(get, isPresent());
+    FieldSubject instance =
+        inspector.clazz(SingleCheck.class).field("java.lang.Object", "instance");
+    assertEquals(2, countIgetObject(get, instance));
+  }
+
+  @Test
+  public void testDaggerSingleProvider() throws Exception {
+    AndroidApp app = readClasses(Provider.class, SingleCheck.class);
+    checkDaggerSingleProviderGet(compileWithD8(app, this::releaseMode));
+    checkDaggerSingleProviderGet(compileWithR8(app, this::releaseMode));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java b/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
new file mode 100644
index 0000000..772ef09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite.assertions;
+
+public class ChromuimAssertionHookMock {
+  public static void assertFailureHandler(AssertionError assertion) {
+    System.out.println("Got AssertionError " + assertion);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java b/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
index cc56790..49fe85e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
@@ -16,7 +16,9 @@
   }
 
   int getX() {
+    System.out.println("1");
     assert condition();
+    System.out.println("2");
     return x;
   }
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index 3c724c0..6574e7b 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -4,17 +4,139 @@
 
 package com.android.tools.r8.rewrite.assertions;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+import java.util.function.Function;
 import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+// This ASM class visitor has been adapted from
+// https://chromium.googlesource.com/chromium/src/+/164e81fcd0828b40f5496e9025349ea728cde7f5/build/android/bytecode/java/org/chromium/bytecode/AssertionEnablerClassAdapter.java
+// See b/110887293.
+
+/**
+ * An ClassVisitor for replacing Java ASSERT statements with a function by modifying Java bytecode.
+ *
+ * We do this in two steps, first step is to enable assert.
+ * Following bytecode is generated for each class with ASSERT statements:
+ * 0: ldc #8 // class CLASSNAME
+ * 2: invokevirtual #9 // Method java/lang/Class.desiredAssertionStatus:()Z
+ * 5: ifne 12
+ * 8: iconst_1
+ * 9: goto 13
+ * 12: iconst_0
+ * 13: putstatic #2 // Field $assertionsDisabled:Z
+ * Replaces line #13 to the following:
+ * 13: pop
+ * Consequently, $assertionsDisabled is assigned the default value FALSE.
+ * This is done in the first if statement in overridden visitFieldInsn. We do this per per-assert.
+ *
+ * Second step is to replace assert statement with a function:
+ * The followed instructions are generated by a java assert statement:
+ * getstatic     #3     // Field $assertionsDisabled:Z
+ * ifne          118    // Jump to instruction as if assertion if not enabled
+ * ...
+ * ifne          19
+ * new           #4     // class java/lang/AssertionError
+ * dup
+ * ldc           #5     // String (don't have this line if no assert message given)
+ * invokespecial #6     // Method java/lang/AssertionError.
+ * athrow
+ * Replace athrow with:
+ * invokestatic  #7     // Method org/chromium/base/JavaExceptionReporter.assertFailureHandler
+ * goto          118
+ * JavaExceptionReporter.assertFailureHandler is a function that handles the AssertionError,
+ * 118 is the instruction to execute as if assertion if not enabled.
+ */
+class AssertionEnablerClassAdapter extends ClassVisitor {
+  AssertionEnablerClassAdapter(ClassVisitor visitor) {
+    super(Opcodes.ASM6, visitor);
+  }
+
+  @Override
+  public MethodVisitor visitMethod(final int access, final String name, String desc,
+      String signature, String[] exceptions) {
+    return new RewriteAssertMethodVisitor(
+        Opcodes.ASM5, super.visitMethod(access, name, desc, signature, exceptions));
+  }
+
+  static class RewriteAssertMethodVisitor extends MethodVisitor {
+    static final String ASSERTION_DISABLED_NAME = "$assertionsDisabled";
+    static final String INSERT_INSTRUCTION_NAME = "assertFailureHandler";
+    static final String INSERT_INSTRUCTION_DESC =
+        Type.getMethodDescriptor(Type.VOID_TYPE, Type.getObjectType("java/lang/AssertionError"));
+    static final boolean INSERT_INSTRUCTION_ITF = false;
+
+    boolean mStartLoadingAssert;
+    Label mGotoLabel;
+
+    public RewriteAssertMethodVisitor(int api, MethodVisitor mv) {
+      super(api, mv);
+    }
+
+    @Override
+    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+      if (opcode == Opcodes.PUTSTATIC && name.equals(ASSERTION_DISABLED_NAME)) {
+        super.visitInsn(Opcodes.POP); // enable assert
+      } else if (opcode == Opcodes.GETSTATIC && name.equals(ASSERTION_DISABLED_NAME)) {
+        mStartLoadingAssert = true;
+        super.visitFieldInsn(opcode, owner, name, desc);
+      } else {
+        super.visitFieldInsn(opcode, owner, name, desc);
+      }
+    }
+
+    @Override
+    public void visitJumpInsn(int opcode, Label label) {
+      if (mStartLoadingAssert && opcode == Opcodes.IFNE && mGotoLabel == null) {
+        mGotoLabel = label;
+      }
+      super.visitJumpInsn(opcode, label);
+    }
+
+    @Override
+    public void visitInsn(int opcode) {
+      if (!mStartLoadingAssert || opcode != Opcodes.ATHROW) {
+        super.visitInsn(opcode);
+      } else {
+        super.visitMethodInsn(
+            Opcodes.INVOKESTATIC,
+            ChromuimAssertionHookMock.class.getCanonicalName().replace('.', '/'),
+            INSERT_INSTRUCTION_NAME,
+            INSERT_INSTRUCTION_DESC,
+            INSERT_INSTRUCTION_ITF);
+        super.visitJumpInsn(Opcodes.GOTO, mGotoLabel);
+        mStartLoadingAssert = false;
+        mGotoLabel = null;
+      }
+    }
+  }
+}
 
 public class RemoveAssertionsTest extends TestBase {
 
@@ -38,4 +160,75 @@
         clazz.method(new MethodSignature(Constants.CLASS_INITIALIZER_NAME, "void", new String[]{}));
     assertTrue(!clinit.isPresent());
   }
+
+  private Path buildTestToCf(Consumer<InternalOptions> consumer) throws Exception {
+    Path outputJar = temp.getRoot().toPath().resolve("output.jar");
+    R8Command command =
+        ToolHelper.prepareR8CommandBuilder(readClasses(ClassWithAssertions.class))
+            .setMode(CompilationMode.DEBUG)
+            .setOutput(outputJar, OutputMode.ClassFile)
+            .build();
+    ToolHelper.runR8(command, consumer);
+    return outputJar;
+  }
+
+  @Test
+  public void testCfOutput() throws Exception {
+    String main = ClassWithAssertions.class.getCanonicalName();
+    ProcessResult result;
+    // Assertion is hit.
+    result = ToolHelper.runJava(buildTestToCf(options -> {}), "-ea", main, "0");
+    assertEquals(1, result.exitCode);
+    assertEquals("1\n".replace("\n", System.lineSeparator()), result.stdout);
+    // Assertion is not hit.
+    result = ToolHelper.runJava(buildTestToCf(options -> {}), "-ea", main, "1");
+    assertEquals(0, result.exitCode);
+    assertEquals("1\n2\n".replace("\n", System.lineSeparator()), result.stdout);
+    // Assertion is hit, but removed.
+    result = ToolHelper.runJava(
+        buildTestToCf(
+            options -> options.disableAssertions = true), "-ea", main, "0");
+    assertEquals(0, result.exitCode);
+    assertEquals("1\n2\n".replace("\n", System.lineSeparator()), result.stdout);
+  }
+
+  private byte[] identity(byte[] classBytes) {
+    return classBytes;
+  }
+
+  private byte[] chromiumAssertionEnabler(byte[] classBytes) {
+    ClassWriter writer = new ClassWriter(0);
+    new ClassReader(classBytes).accept(new AssertionEnablerClassAdapter(writer), 0);
+    return writer.toByteArray();
+  }
+
+  private AndroidApp runRegress110887293(Function<byte[], byte[]> rewriter) throws Exception {
+    return ToolHelper.runR8(
+        R8Command.builder()
+            .addClassProgramData(
+                rewriter.apply(ToolHelper.getClassAsBytes(ClassWithAssertions.class)),
+                Origin.unknown())
+            .addClassProgramData(
+                ToolHelper.getClassAsBytes(ChromuimAssertionHookMock.class), Origin.unknown())
+            .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+            .setMode(CompilationMode.DEBUG)
+            .build());
+  }
+
+  @Test
+  public void regress110887293() throws Exception {
+    AndroidApp app;
+    // Assertions removed for default assertion code.
+    app = runRegress110887293(this::identity);
+    assertEquals("1\n2\n", runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "0"));
+    assertEquals("1\n2\n", runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "1"));
+    // Assertions not removed when default assertion code is not present.
+    app = runRegress110887293(this::chromiumAssertionEnabler);
+    assertEquals(
+        "1\nGot AssertionError java.lang.AssertionError\n2\n".replace("\n", System.lineSeparator()),
+        runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "0"));
+    assertEquals(
+        "1\n2\n".replace("\n", System.lineSeparator()),
+        runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "1"));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
index 5270cd4..86c4242 100644
--- a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
@@ -37,28 +36,23 @@
     builder.addStaticField("stringField", "Ljava/lang/String;");
     builder.addStaticField("testField", "LTest;");
 
-    boolean isDalvik = ToolHelper.getDexVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST);
-    String additionalConstZero = isDalvik ? "const v0, 0" : "";
-    String additionalConstZeroWide = isDalvik ? "const-wide v0, 0" : "";
-
     builder.addStaticMethod("void", "test", ImmutableList.of(),
         2,
-        "const               v0, 0",
+        "const               v0, 0", // single non-float typed zero (ie, int type)
         "sput-boolean        v0, LTest;->booleanField:Z",
         "sput-byte           v0, LTest;->byteField:B",
+        "sput-char           v0, LTest;->charField:C",
         "sput-short          v0, LTest;->shortField:S",
         "sput                v0, LTest;->intField:I",
-        // Dalvik 4.x. does not require a new const 0 here.
+        "const               v0, 0", // float typed zero
         "sput                v0, LTest;->floatField:F",
-        additionalConstZero,  // Required for Dalvik 4.x.
-        "sput-char           v0, LTest;->charField:C",
-        "const               v0, 0",
+        "const               v0, 0", // reference typed null
         "sput-object         v0, LTest;->objectField:Ljava/lang/Object;",
         "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
         "sput-object         v0, LTest;->testField:LTest;",
-        "const-wide          v0, 0",
+        "const-wide          v0, 0", // wide typed long
         "sput-wide           v0, LTest;->longField:J",
-        additionalConstZeroWide,  // Required for Dalvik 4.x.
+        "const-wide          v0, 0", // wide typed double
         "sput-wide           v0, LTest;->doubleField:D",
         "return-void");
 
@@ -103,28 +97,23 @@
     builder.addInstanceField("stringField", "Ljava/lang/String;");
     builder.addInstanceField("testField", "LTest;");
 
-    boolean isDalvik = ToolHelper.getDexVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST);
-    String additionalConstZero = isDalvik ? "const v0, 0" : "";
-    String additionalConstZeroWide = isDalvik ? "const-wide v0, 0" : "";
-
     builder.addInstanceMethod("void", "test", ImmutableList.of(),
         2,
         "const               v0, 0",
         "iput-boolean        v0, p0, LTest;->booleanField:Z",
         "iput-byte           v0, p0, LTest;->byteField:B",
+        "iput-char           v0, p0, LTest;->charField:C",
         "iput-short          v0, p0, LTest;->shortField:S",
         "iput                v0, p0, LTest;->intField:I",
-        // Dalvik 4.x. does not require a new const 0 here.
+        "const               v0, 0",
         "iput                v0, p0, LTest;->floatField:F",
-        additionalConstZero,  // Required for Dalvik 4.x.
-        "iput-char           v0, p0, LTest;->charField:C",
         "const               v0, 0",
         "iput-object         v0, p0, LTest;->objectField:Ljava/lang/Object;",
         "iput-object         v0, p0, LTest;->stringField:Ljava/lang/String;",
         "iput-object         v0, p0, LTest;->testField:LTest;",
         "const-wide          v0, 0",
         "iput-wide           v0, p0, LTest;->longField:J",
-        additionalConstZeroWide,  // Required for Dalvik 4.x.
+        "const-wide          v0, 0",
         "iput-wide           v0, p0, LTest;->doubleField:D",
         "return-void");
 
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index eba3be8..059a24a 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -54,6 +54,7 @@
 import com.android.tools.r8.code.SputWide;
 import com.android.tools.r8.code.Throw;
 import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -81,6 +82,7 @@
 import com.android.tools.r8.naming.signature.GenericSignatureAction;
 import com.android.tools.r8.naming.signature.GenericSignatureParser;
 import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.utils.DexInspector.FieldAccessInstructionSubject;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -106,8 +108,6 @@
   private final ClassNameMapper mapping;
   private final BiMap<String, String> originalToObfuscatedMapping;
 
-  private final InstructionSubjectFactory factory = new InstructionSubjectFactory();
-
   public static MethodSignature MAIN =
       new MethodSignature("main", "void", new String[]{"java.lang.String[]"});
 
@@ -304,18 +304,18 @@
     return obfuscatedType;
   }
 
-  public abstract class Subject {
+  public abstract static class Subject {
 
     public abstract boolean isPresent();
     public abstract boolean isRenamed();
   }
 
-  public abstract class AnnotationSubject extends Subject {
+  public abstract static class AnnotationSubject extends Subject {
 
     public abstract DexEncodedAnnotation getAnnotation();
   }
 
-  public class FoundAnnotationSubject extends AnnotationSubject {
+  public static class FoundAnnotationSubject extends AnnotationSubject {
 
     private final DexAnnotation annotation;
 
@@ -339,7 +339,7 @@
     }
   }
 
-  public class AbsentAnnotationSubject extends AnnotationSubject {
+  public static class AbsentAnnotationSubject extends AnnotationSubject {
 
     @Override
     public boolean isPresent() {
@@ -357,8 +357,7 @@
     }
   }
 
-
-  public abstract class ClassSubject extends Subject {
+  public abstract static class ClassSubject extends Subject {
 
     public abstract void forAllMethods(Consumer<FoundMethodSubject> inspection);
 
@@ -423,6 +422,8 @@
 
     public abstract boolean isAnonymousClass();
 
+    public abstract boolean isSynthesizedJavaLambdaClass();
+
     public abstract String getOriginalSignatureAttribute();
 
     public abstract String getFinalSignatureAttribute();
@@ -514,6 +515,11 @@
     }
 
     @Override
+    public boolean isSynthesizedJavaLambdaClass() {
+      return false;
+    }
+
+    @Override
     public String getOriginalSignatureAttribute() {
       return null;
     }
@@ -709,6 +715,11 @@
     }
 
     @Override
+    public boolean isSynthesizedJavaLambdaClass() {
+      return dexClass.type.getName().contains("$Lambda$");
+    }
+
+    @Override
     public String getOriginalSignatureAttribute() {
       return DexInspector.this.getOriginalSignatureAttribute(
           dexClass.annotations, GenericSignatureParser::parseClassSignature);
@@ -725,7 +736,7 @@
     }
   }
 
-  public abstract class MemberSubject extends Subject {
+  public abstract static class MemberSubject extends Subject {
 
     public abstract boolean isPublic();
 
@@ -748,7 +759,7 @@
     }
   }
 
-  public abstract class MethodSubject extends MemberSubject {
+  public abstract static class MethodSubject extends MemberSubject {
 
     public abstract boolean isAbstract();
 
@@ -958,7 +969,7 @@
 
     @Override
     public Iterator<InstructionSubject> iterateInstructions() {
-      return new InstructionIterator(this);
+      return createInstructionIterator(this);
     }
 
     @Override
@@ -973,7 +984,7 @@
     }
   }
 
-  public abstract class FieldSubject extends MemberSubject {
+  public abstract static class FieldSubject extends MemberSubject {
     public abstract boolean hasExplicitStaticValue();
 
     public abstract DexEncodedField getField();
@@ -987,7 +998,7 @@
     public abstract String getFinalSignatureAttribute();
   }
 
-  public class AbsentFieldSubject extends FieldSubject {
+  public static class AbsentFieldSubject extends FieldSubject {
 
     @Override
     public boolean isPublic() {
@@ -1180,91 +1191,139 @@
     }
   }
 
-  private class InstructionSubjectFactory {
+  public interface InstructionSubject {
+    boolean isFieldAccess();
 
-    InstructionSubject create(Instruction instruction) {
-      if (isInvoke(instruction)) {
-        return new InvokeInstructionSubject(this, instruction);
-      } else if (isFieldAccess(instruction)) {
-        return new FieldAccessInstructionSubject(this, instruction);
-      } else {
-        return new InstructionSubject(this, instruction);
-      }
+    boolean isInvokeVirtual();
+
+    boolean isInvokeInterface();
+
+    boolean isInvokeDirect();
+
+    boolean isInvokeSuper();
+
+    boolean isInvokeStatic();
+
+    boolean isNop();
+
+    boolean isConstString();
+
+    boolean isConstString(String value);
+
+    boolean isGoto();
+
+    boolean isIfNez();
+
+    boolean isIfEqz();
+
+    boolean isReturnVoid();
+
+    boolean isThrow();
+
+    default boolean isInvoke() {
+      return isInvokeVirtual()
+          || isInvokeInterface()
+          || isInvokeDirect()
+          || isInvokeSuper()
+          || isInvokeStatic();
+    }
+  }
+
+  public InstructionSubject createInstructionSubject(Instruction instruction) {
+    DexInstructionSubject dexInst = new DexInstructionSubject(instruction);
+    if (dexInst.isInvoke()) {
+      return new InvokeDexInstructionSubject(instruction);
+    } else if (dexInst.isFieldAccess()) {
+      return new FieldAccessDexInstructionSubject(instruction);
+    } else {
+      return dexInst;
+    }
+  }
+
+  public class DexInstructionSubject implements InstructionSubject {
+    protected Instruction instruction;
+
+    public DexInstructionSubject(Instruction instruction) {
+      this.instruction = instruction;
     }
 
-    boolean isInvoke(Instruction instruction) {
-      return isInvokeVirtual(instruction)
-          || isInvokeInterface(instruction)
-          || isInvokeDirect(instruction)
-          || isInvokeSuper(instruction)
-          || isInvokeStatic(instruction);
+    @Override
+    public boolean isFieldAccess() {
+      return isInstanceGet() || isInstancePut() || isStaticGet() || isStaticSet();
     }
 
-    boolean isInvokeVirtual(Instruction instruction) {
+    @Override
+    public boolean isInvokeVirtual() {
       return instruction instanceof InvokeVirtual || instruction instanceof InvokeVirtualRange;
     }
 
-    boolean isInvokeInterface(Instruction instruction) {
+    @Override
+    public boolean isInvokeInterface() {
       return instruction instanceof InvokeInterface || instruction instanceof InvokeInterfaceRange;
     }
 
-    boolean isInvokeDirect(Instruction instruction) {
+    @Override
+    public boolean isInvokeDirect() {
       return instruction instanceof InvokeDirect || instruction instanceof InvokeDirectRange;
     }
 
-    boolean isInvokeSuper(Instruction instruction) {
+    @Override
+    public boolean isInvokeSuper() {
       return instruction instanceof InvokeSuper || instruction instanceof InvokeSuperRange;
     }
 
-    boolean isInvokeStatic(Instruction instruction) {
+    @Override
+    public boolean isInvokeStatic() {
       return instruction instanceof InvokeStatic || instruction instanceof InvokeStaticRange;
     }
 
-    boolean isNop(Instruction instruction) {
+    @Override
+    public boolean isNop() {
       return instruction instanceof Nop;
     }
 
-    boolean isGoto(Instruction instruction) {
-      return instruction instanceof Goto;
-    }
-
-    boolean isReturnVoid(Instruction instruction) {
-      return instruction instanceof ReturnVoid;
-    }
-
-    boolean isConst4(Instruction instruction) {
-      return instruction instanceof Const4;
-    }
-
-    boolean isThrow(Instruction instruction) {
-      return instruction instanceof Throw;
-    }
-
-    boolean isConstString(Instruction instruction) {
+    @Override
+    public boolean isConstString() {
       return instruction instanceof ConstString;
     }
 
-    boolean isConstString(Instruction instruction, String value) {
+    @Override
+    public boolean isConstString(String value) {
       return instruction instanceof ConstString
           && ((ConstString) instruction).BBBB.toSourceString().equals(value);
     }
 
-    boolean isIfNez(Instruction instruction) {
+    @Override
+    public boolean isGoto() {
+
+      return instruction instanceof Goto;
+    }
+
+    @Override
+    public boolean isIfNez() {
       return instruction instanceof IfNez;
     }
 
-    boolean isIfEqz(Instruction instruction) {
+    @Override
+    public boolean isIfEqz() {
       return instruction instanceof IfEqz;
     }
 
-    boolean isFieldAccess(Instruction instruction) {
-      return isInstanceGet(instruction)
-          || isInstancePut(instruction)
-          || isStaticGet(instruction)
-          || isStaticSet(instruction);
+    @Override
+    public boolean isReturnVoid() {
+      return instruction instanceof ReturnVoid;
     }
 
-    boolean isInstanceGet(Instruction instruction) {
+    @Override
+    public boolean isThrow() {
+      return instruction instanceof Throw;
+    }
+
+    public boolean isConst4() {
+      return instruction instanceof Const4;
+    }
+
+    public boolean isInstanceGet() {
       return instruction instanceof Iget
           || instruction instanceof IgetBoolean
           || instruction instanceof IgetByte
@@ -1274,7 +1333,7 @@
           || instruction instanceof IgetObject;
     }
 
-    boolean isInstancePut(Instruction instruction) {
+    public boolean isInstancePut() {
       return instruction instanceof Iput
           || instruction instanceof IputBoolean
           || instruction instanceof IputByte
@@ -1284,7 +1343,7 @@
           || instruction instanceof IputObject;
     }
 
-    boolean isStaticGet(Instruction instruction) {
+    public boolean isStaticGet() {
       return instruction instanceof Sget
           || instruction instanceof SgetBoolean
           || instruction instanceof SgetByte
@@ -1294,7 +1353,7 @@
           || instruction instanceof SgetObject;
     }
 
-    boolean isStaticSet(Instruction instruction) {
+    public boolean isStaticSet() {
       return instruction instanceof Sput
           || instruction instanceof SputBoolean
           || instruction instanceof SputByte
@@ -1305,89 +1364,21 @@
     }
   }
 
-  public class InstructionSubject {
+  public interface InvokeInstructionSubject extends InstructionSubject {
+    TypeSubject holder();
 
-    protected final InstructionSubjectFactory factory;
-    protected final Instruction instruction;
-
-    protected InstructionSubject(InstructionSubjectFactory factory, Instruction instruction) {
-      this.factory = factory;
-      this.instruction = instruction;
-    }
-
-    public boolean isInvoke() {
-      return factory.isInvoke(instruction);
-    }
-
-    public boolean isFieldAccess() {
-      return factory.isFieldAccess(instruction);
-    }
-
-    public boolean isInvokeVirtual() {
-      return factory.isInvokeVirtual(instruction);
-    }
-
-    public boolean isInvokeInterface() {
-      return factory.isInvokeInterface(instruction);
-    }
-
-    public boolean isInvokeDirect() {
-      return factory.isInvokeDirect(instruction);
-    }
-
-    public boolean isInvokeSuper() {
-      return factory.isInvokeSuper(instruction);
-    }
-
-    public boolean isInvokeStatic() {
-      return factory.isInvokeStatic(instruction);
-    }
-
-    boolean isFieldAccess(Instruction instruction) {
-      return factory.isFieldAccess(instruction);
-    }
-
-    public boolean isNop() {
-      return factory.isNop(instruction);
-    }
-
-    public boolean isConstString() {
-      return factory.isConstString(instruction);
-    }
-
-    public boolean isConstString(String value) {
-      return factory.isConstString(instruction, value);
-    }
-
-    public boolean isGoto() {
-      return factory.isGoto(instruction);
-    }
-
-    public boolean isIfNez() {
-      return factory.isIfNez(instruction);
-    }
-
-    public boolean isIfEqz() {
-      return factory.isIfEqz(instruction);
-    }
-
-    public boolean isReturnVoid() {
-      return factory.isReturnVoid(instruction);
-    }
-
-    public boolean isConst4() {
-      return factory.isConst4(instruction);
-    }
-
-    public boolean isThrow() {
-      return factory.isThrow(instruction);
-    }
+    DexMethod invokedMethod();
   }
 
-  public class InvokeInstructionSubject extends InstructionSubject {
+  public interface FieldAccessInstructionSubject extends InstructionSubject {
+    TypeSubject holder();
+  }
 
-    InvokeInstructionSubject(InstructionSubjectFactory factory, Instruction instruction) {
-      super(factory, instruction);
+  public class InvokeDexInstructionSubject extends DexInstructionSubject
+      implements InvokeInstructionSubject {
+
+    public InvokeDexInstructionSubject(Instruction instruction) {
+      super(instruction);
       assert isInvoke();
     }
 
@@ -1396,156 +1387,41 @@
     }
 
     public DexMethod invokedMethod() {
-      if (instruction instanceof InvokeVirtual) {
-        return ((InvokeVirtual) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeVirtualRange) {
-        return ((InvokeVirtualRange) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeInterface) {
-        return ((InvokeInterface) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeInterfaceRange) {
-        return ((InvokeInterfaceRange) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeDirect) {
-        return ((InvokeDirect) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeDirectRange) {
-        return ((InvokeDirectRange) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeSuper) {
-        return ((InvokeSuper) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeSuperRange) {
-        return ((InvokeSuperRange) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeDirect) {
-        return ((InvokeDirect) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeDirectRange) {
-        return ((InvokeDirectRange) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeStatic) {
-        return ((InvokeStatic) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeStaticRange) {
-        return ((InvokeStaticRange) instruction).getMethod();
-      }
-      assert false;
-      return null;
+      return instruction.getMethod();
     }
   }
 
-  public class FieldAccessInstructionSubject extends InstructionSubject {
+  public class FieldAccessDexInstructionSubject extends DexInstructionSubject
+      implements FieldAccessInstructionSubject {
 
-    FieldAccessInstructionSubject(InstructionSubjectFactory factory, Instruction instruction) {
-      super(factory, instruction);
+    public FieldAccessDexInstructionSubject(Instruction instruction) {
+      super(instruction);
       assert isFieldAccess();
     }
 
     public TypeSubject holder() {
-      return new TypeSubject(accessedField().getHolder());
-    }
-
-    public DexField accessedField() {
-      if (instruction instanceof Iget) {
-        return ((Iget) instruction).getField();
-      }
-      if (instruction instanceof IgetBoolean) {
-        return ((IgetBoolean) instruction).getField();
-      }
-      if (instruction instanceof IgetByte) {
-        return ((IgetByte) instruction).getField();
-      }
-      if (instruction instanceof IgetShort) {
-        return ((IgetShort) instruction).getField();
-      }
-      if (instruction instanceof IgetChar) {
-        return ((IgetChar) instruction).getField();
-      }
-      if (instruction instanceof IgetWide) {
-        return ((IgetWide) instruction).getField();
-      }
-      if (instruction instanceof IgetObject) {
-        return ((IgetObject) instruction).getField();
-      }
-      if (instruction instanceof Iput) {
-        return ((Iput) instruction).getField();
-      }
-      if (instruction instanceof IputBoolean) {
-        return ((IputBoolean) instruction).getField();
-      }
-      if (instruction instanceof IputByte) {
-        return ((IputByte) instruction).getField();
-      }
-      if (instruction instanceof IputShort) {
-        return ((IputShort) instruction).getField();
-      }
-      if (instruction instanceof IputChar) {
-        return ((IputChar) instruction).getField();
-      }
-      if (instruction instanceof IputWide) {
-        return ((IputWide) instruction).getField();
-      }
-      if (instruction instanceof IputObject) {
-        return ((IputObject) instruction).getField();
-      }
-      if (instruction instanceof Sget) {
-        return ((Sget) instruction).getField();
-      }
-      if (instruction instanceof SgetBoolean) {
-        return ((SgetBoolean) instruction).getField();
-      }
-      if (instruction instanceof SgetByte) {
-        return ((SgetByte) instruction).getField();
-      }
-      if (instruction instanceof SgetShort) {
-        return ((SgetShort) instruction).getField();
-      }
-      if (instruction instanceof SgetChar) {
-        return ((SgetChar) instruction).getField();
-      }
-      if (instruction instanceof SgetWide) {
-        return ((SgetWide) instruction).getField();
-      }
-      if (instruction instanceof SgetObject) {
-        return ((SgetObject) instruction).getField();
-      }
-      if (instruction instanceof Sput) {
-        return ((Sput) instruction).getField();
-      }
-      if (instruction instanceof SputBoolean) {
-        return ((SputBoolean) instruction).getField();
-      }
-      if (instruction instanceof SputByte) {
-        return ((SputByte) instruction).getField();
-      }
-      if (instruction instanceof SputShort) {
-        return ((SputShort) instruction).getField();
-      }
-      if (instruction instanceof SputChar) {
-        return ((SputChar) instruction).getField();
-      }
-      if (instruction instanceof SputWide) {
-        return ((SputWide) instruction).getField();
-      }
-      if (instruction instanceof SputObject) {
-        return ((SputObject) instruction).getField();
-      }
-      assert false;
-      return null;
+      return new TypeSubject(instruction.getField().getHolder());
     }
   }
 
-  private class InstructionIterator implements Iterator<InstructionSubject> {
+  private interface InstructionIterator extends Iterator<InstructionSubject> {}
+
+  private InstructionIterator createInstructionIterator(MethodSubject method) {
+    Code code = method.getMethod().getCode();
+    assert code != null && code.isDexCode();
+    return new DexInstructionIterator(method);
+  }
+
+  private class DexInstructionIterator implements InstructionIterator {
 
     private final DexCode code;
     private int index;
 
-    InstructionIterator(MethodSubject method) {
+    DexInstructionIterator(MethodSubject method) {
       assert method.isPresent();
-      this.code = method.getMethod().getCode().asDexCode();
+      Code code = method.getMethod().getCode();
+      assert code != null && code.isDexCode();
+      this.code = code.asDexCode();
       this.index = 0;
     }
 
@@ -1559,7 +1435,7 @@
       if (index == code.instructions.length) {
         throw new NoSuchElementException();
       }
-      return factory.create(code.instructions[index++]);
+      return createInstructionSubject(code.instructions[index++]);
     }
   }
 
@@ -1570,7 +1446,7 @@
     private InstructionSubject pendingNext = null;
 
     FilteredInstructionIterator(MethodSubject method, Predicate<InstructionSubject> predicate) {
-      this.iterator = new InstructionIterator(method);
+      this.iterator = createInstructionIterator(method);
       this.predicate = predicate;
       hasNext();
     }
diff --git a/tools/proguard.py b/tools/proguard.py
index efcf560..640094f 100755
--- a/tools/proguard.py
+++ b/tools/proguard.py
@@ -15,14 +15,14 @@
 PROGUARD_JAR = os.path.join(utils.REPO_ROOT, 'third_party', 'proguard',
     'proguard_internal_159423826', 'ProGuard_deploy.jar')
 
-def run(args, track_memory_file = None):
+def run(args, track_memory_file = None, stdout=None, stderr=None):
   cmd = []
   if track_memory_file:
     cmd.extend(['tools/track_memory.sh', track_memory_file])
   cmd.extend(['java', '-jar', PROGUARD_JAR])
   cmd.extend(args)
   utils.PrintCmd(cmd)
-  subprocess.check_call(cmd)
+  subprocess.call(cmd, stdout=stdout, stderr=stderr)
 
 def Main():
   run(sys.argv[1:])
diff --git a/tools/run_proguard_dx_on_app.py b/tools/run_proguard_dx_on_app.py
index a953229..4624f26 100755
--- a/tools/run_proguard_dx_on_app.py
+++ b/tools/run_proguard_dx_on_app.py
@@ -115,7 +115,10 @@
     track_memory_file = None
     if options.print_memoryuse:
       track_memory_file = join(temp, utils.MEMORY_USE_TMP_FILE)
-    proguard.run(args, track_memory_file = track_memory_file)
+    proguard.run(
+        args,
+        track_memory_file = track_memory_file,
+        stdout=open(os.devnull, 'w'))
     if options.print_memoryuse:
       proguard_memoryuse = utils.grep_memoryuse(track_memory_file)