Merge "Create field and method arrays directly in class merger"
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 6549848..63c1173 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.3.4-dev";
+  public static final String LABEL = "1.3.5-dev";
 
   private Version() {
   }
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 845eecd..69126be 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -25,10 +25,9 @@
 import com.android.tools.r8.dex.JumboStringRewriter;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.graph.AppInfo.ResolutionResult;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo.ParameterUsage;
+import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
@@ -45,13 +44,11 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
-import java.util.stream.Collectors;
 
 public class DexEncodedMethod extends KeyedDexItem<DexMethod> implements ResolutionResult {
 
@@ -621,51 +618,6 @@
     }
   }
 
-  public static final class ParameterUsagesInfo {
-    private ImmutableList<ParameterUsage> parametersUsages;
-
-    public ParameterUsagesInfo(List<ParameterUsage> usages) {
-      assert !usages.isEmpty();
-      parametersUsages = ImmutableList.copyOf(usages);
-      assert parametersUsages.size() ==
-          parametersUsages.stream().map(usage -> usage.index).collect(Collectors.toSet()).size();
-    }
-
-    public static abstract class ParameterUsage {
-      public final int index;
-
-      ParameterUsage(int index) {
-        this.index = index;
-      }
-    }
-
-    public static class SingleCallOfArgumentMethod extends ParameterUsage {
-      public final Invoke.Type type;
-      public final DexMethod method;
-
-      public SingleCallOfArgumentMethod(int index, Type type, DexMethod method) {
-        super(index);
-        this.type = type;
-        this.method = method;
-      }
-    }
-
-    public static class NotUsed extends ParameterUsage {
-      public NotUsed(int index) {
-        super(index);
-      }
-    }
-
-    ParameterUsage getParameterUsage(int parameter) {
-      for (ParameterUsage usage : parametersUsages) {
-        if (usage.index == parameter) {
-          return usage;
-        }
-      }
-      return null;
-    }
-  }
-
   public static class OptimizationInfo {
 
     private int returnedArgument = -1;
diff --git a/src/main/java/com/android/tools/r8/graph/ParameterUsagesInfo.java b/src/main/java/com/android/tools/r8/graph/ParameterUsagesInfo.java
new file mode 100644
index 0000000..84cf331
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ParameterUsagesInfo.java
@@ -0,0 +1,102 @@
+// 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;
+
+import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class ParameterUsagesInfo {
+  private ImmutableList<ParameterUsage> parametersUsages;
+
+  public ParameterUsagesInfo(List<ParameterUsage> usages) {
+    assert !usages.isEmpty();
+    parametersUsages = ImmutableList.copyOf(usages);
+    assert parametersUsages.size() ==
+        parametersUsages.stream().map(usage -> usage.index).collect(Collectors.toSet()).size();
+  }
+
+  ParameterUsage getParameterUsage(int parameter) {
+    for (ParameterUsage usage : parametersUsages) {
+      if (usage.index == parameter) {
+        return usage;
+      }
+    }
+    return null;
+  }
+
+  public final static class ParameterUsage {
+    public final int index;
+    public final Set<Type> ifZeroTest;
+    public final List<Pair<Invoke.Type, DexMethod>> callsReceiver;
+    public final boolean returnValue;
+
+    ParameterUsage(int index, Set<Type> ifZeroTest,
+        List<Pair<Invoke.Type, DexMethod>> callsReceiver, boolean returnValue) {
+      this.index = index;
+      this.ifZeroTest = ifZeroTest.isEmpty()
+          ? Collections.emptySet() : ImmutableSet.copyOf(ifZeroTest);
+      this.callsReceiver = callsReceiver.isEmpty()
+          ? Collections.emptyList() : ImmutableList.copyOf(callsReceiver);
+      this.returnValue = returnValue;
+    }
+
+    public boolean notUsed() {
+      return ifZeroTest == null && callsReceiver == null && !returnValue;
+    }
+  }
+
+  public static class ParameterUsageBuilder {
+    private final int index;
+    private final Value arg;
+    private final Set<Type> ifZeroTestTypes = new HashSet<>();
+    private final List<Pair<Invoke.Type, DexMethod>> callsOnReceiver = new ArrayList<>();
+    private boolean returnValue = false;
+
+    public ParameterUsageBuilder(Value arg, int index) {
+      this.arg = arg;
+      this.index = index;
+    }
+
+    // Returns false if the instruction is not supported.
+    public boolean note(Instruction instruction) {
+      if (instruction.isInvokeMethodWithReceiver() &&
+          instruction.inValues().lastIndexOf(arg) == 0) {
+        callsOnReceiver.add(new Pair<>(
+            instruction.asInvokeMethodWithReceiver().getType(),
+            instruction.asInvokeMethodWithReceiver().getInvokedMethod()));
+        return true;
+      }
+
+      if (instruction.isIf() && instruction.asIf().isZeroTest()) {
+        assert instruction.inValues().size() == 1 && instruction.inValues().get(0) == arg;
+        ifZeroTestTypes.add(instruction.asIf().getType());
+        return true;
+      }
+
+      if (instruction.isReturn()) {
+        assert instruction.inValues().size() == 1 && instruction.inValues().get(0) == arg;
+        returnValue = true;
+        return true;
+      }
+
+      return false;
+    }
+
+    public ParameterUsage build() {
+      return new ParameterUsage(index, ifZeroTestTypes, callsOnReceiver, returnValue);
+    }
+  }
+}
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 819c6f2..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
@@ -659,6 +659,7 @@
     for (Instruction instruction : getInstructions()) {
       if (instruction.outValue != null) {
         instruction.outValue.clearUsers();
+        instruction.setOutValue(null);
       }
       for (Value value : instruction.inValues) {
         value.removeUser(instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index 58b61af..1adcd11 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -390,7 +390,7 @@
     ImmutableList<BasicBlock> normalExits = inlinee.computeNormalExitBlocks();
     if (normalExits.isEmpty()) {
       assert inlineeCanThrow;
-      // TODO(sgjesse): Remove this restriction.
+      // TODO(sgjesse): Remove this restriction (see b/64432527).
       assert !invokeBlock.hasCatchHandlers();
       blocksToRemove.addAll(
           invokePredecessor.unlink(invokeBlock, new DominatorTree(code)));
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index cb5aad0..72728a8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -138,13 +138,10 @@
       if (predecessors.size() <= 1) {
         continue;
       }
-      // If any of the edges to the block are critical, we need to insert new blocks on each
-      // containing the move-exception instruction which must remain the first instruction.
-      if (block.entry() instanceof MoveException) {
-        nextBlockNumber = block.splitCriticalExceptionEdges(
-            nextBlockNumber, valueNumberGenerator, newBlocks::add);
-        continue;
-      }
+
+      // Exceptional edges are given unique header blocks and can have at most one predecessor.
+      assert !block.entry().isMoveException();
+
       for (int predIndex = 0; predIndex < predecessors.size(); predIndex++) {
         BasicBlock pred = predecessors.get(predIndex);
         if (!pred.hasOneNormalExit()) {
@@ -164,6 +161,27 @@
     blocks.addAll(newBlocks);
   }
 
+  public boolean verifySplitCriticalEdges() {
+    for (BasicBlock block : blocks) {
+      // If there are multiple incoming edges, check each has a split block.
+      List<BasicBlock> predecessors = block.getPredecessors();
+      if (predecessors.size() > 1) {
+        for (BasicBlock predecessor : predecessors) {
+          assert predecessor.hasOneNormalExit();
+          assert predecessor.getSuccessors().get(0) == block;
+        }
+      }
+      // If there are outgoing exceptional edges, check that each has a split block.
+      if (block.hasCatchHandlers()) {
+        for (BasicBlock handler : block.getCatchHandlers().getUniqueTargets()) {
+          assert handler.getPredecessors().size() == 1;
+          assert handler.getPredecessors().get(0) == block;
+        }
+      }
+    }
+    return true;
+  }
+
   /**
    * Trace blocks and attempt to put fallthrough blocks immediately after the block that
    * falls through. When we fail to do that we create a new fallthrough block with an explicit
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 309e1d0..4cbebcf 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
@@ -586,7 +586,7 @@
   }
 
   @Override
-  public int getMoveExceptionRegister() {
+  public int getMoveExceptionRegister(int instructionIndex) {
     return CfState.Slot.STACK_OFFSET;
   }
 
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 31ee706..b59794b 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
@@ -21,12 +21,12 @@
 import com.android.tools.r8.code.InvokeSuperRange;
 import com.android.tools.r8.code.InvokeVirtual;
 import com.android.tools.r8.code.InvokeVirtualRange;
+import com.android.tools.r8.code.MoveException;
 import com.android.tools.r8.code.MoveResult;
 import com.android.tools.r8.code.MoveResultObject;
 import com.android.tools.r8.code.MoveResultWide;
 import com.android.tools.r8.code.SwitchPayload;
 import com.android.tools.r8.code.Throw;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexCode.Try;
@@ -185,14 +185,19 @@
   }
 
   @Override
-  public int getMoveExceptionRegister() {
-    // No register, move-exception is manually entered during construction.
+  public int getMoveExceptionRegister(int instructionIndex) {
+    Instruction instruction = code.instructions[instructionIndex];
+    if (instruction instanceof MoveException) {
+      MoveException moveException = (MoveException) instruction;
+      return moveException.AA;
+    }
     return -1;
   }
 
   @Override
   public Position getDebugPositionAtOffset(int offset) {
-    throw new Unreachable();
+    DexDebugEntry entry = getDebugEntryAtOffset(offset);
+    return entry == null ? preamblePosition : getCanonicalPositionAppendCaller(entry);
   }
 
   @Override
@@ -225,23 +230,30 @@
     }
   }
 
+  private DexDebugEntry getDebugEntryAtOffset(int offset) {
+    DexDebugEntry current = null;
+    if (debugEntries != null) {
+      for (DexDebugEntry entry : debugEntries) {
+        if (entry.address > offset) {
+          break;
+        }
+        current = entry;
+      }
+    }
+    return current;
+  }
+
   private void updateDebugPosition(int instructionIndex, IRBuilder builder) {
     if (debugEntries == null || debugEntries.isEmpty()) {
       return;
     }
-    DexDebugEntry current = null;
     int offset = instructionOffset(instructionIndex);
-    for (DexDebugEntry entry : debugEntries) {
-      if (entry.address > offset) {
-        break;
-      }
-      current = entry;
-    }
-    if (current == null) {
+    DexDebugEntry entry = getDebugEntryAtOffset(offset);
+    if (entry == null) {
       currentPosition = preamblePosition;
     } else {
-      currentPosition = getCanonicalPositionAppendCaller(current);
-      if (current.lineEntry && current.address == offset) {
+      currentPosition = getCanonicalPositionAppendCaller(entry);
+      if (entry.lineEntry && entry.address == offset) {
         builder.addDebugPosition(currentPosition);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 5be54f5..963cf41 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -93,6 +93,8 @@
 import it.unimi.dsi.fastutil.ints.IntIterator;
 import it.unimi.dsi.fastutil.ints.IntList;
 import it.unimi.dsi.fastutil.ints.IntSet;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -142,6 +144,13 @@
     }
   }
 
+  private static class SplitBlockWorklistItem extends WorklistItem {
+
+    public SplitBlockWorklistItem(BasicBlock block) {
+      super(block, -1);
+    }
+  }
+
   /**
    * Representation of lists of values that can be used as keys in maps. A list of
    * values is equal to another list of values if it contains exactly the same values
@@ -224,6 +233,10 @@
       return all;
     }
 
+    boolean hasJustOneNormalExit() {
+      return normalSuccessors.size() == 1 && exceptionalSuccessors.isEmpty();
+    }
+
     BlockInfo split(
         int blockStartOffset, int fallthroughOffset, Int2ReferenceMap<BlockInfo> targets) {
       BlockInfo fallthroughInfo = new BlockInfo();
@@ -279,6 +292,7 @@
 
   // Mapping from instruction offsets to basic-block targets.
   private final Int2ReferenceSortedMap<BlockInfo> targets = new Int2ReferenceAVLTreeMap<>();
+  private final Reference2IntMap<BasicBlock> offsets = new Reference2IntOpenHashMap<>();
 
   // Worklist of reachable blocks.
   private final Queue<Integer> traceBlocksWorklist = new LinkedList<>();
@@ -296,7 +310,6 @@
   private final LinkedList<BasicBlock> blocks = new LinkedList<>();
 
   private BasicBlock currentBlock = null;
-  private final List<BasicBlock.Pair> needGotoToCatchBlocks = new ArrayList<>();
   final private ValueNumberGenerator valueNumberGenerator;
   private final DexEncodedMethod method;
   private final AppInfo appInfo;
@@ -365,7 +378,9 @@
     source.setUp();
 
     // Create entry block (at a non-targetable address).
-    targets.put(INITIAL_BLOCK_OFFSET, new BlockInfo());
+    BlockInfo initialBlockInfo = new BlockInfo();
+    targets.put(INITIAL_BLOCK_OFFSET, initialBlockInfo);
+    offsets.put(initialBlockInfo.block, INITIAL_BLOCK_OFFSET);
 
     // Process reachable code paths starting from instruction 0.
     int instCount = source.instructionCount();
@@ -411,9 +426,6 @@
     // Check that the last block is closed and does not fall off the end.
     assert currentBlock == null;
 
-    // Handle where a catch handler hits the same block as the fallthrough.
-    handleFallthroughToCatchBlock();
-
     // Verify that we have properly filled all blocks
     // Must be after handle-catch (which has delayed edges),
     // but before handle-exit (which does not maintain predecessor counts).
@@ -434,9 +446,8 @@
     // Package up the IR code.
     IRCode ir = new IRCode(options, method, blocks, valueNumberGenerator, hasDebugPositions);
 
-    // Split critical edges to make sure that we have a place to insert phi moves if
-    // necessary.
-    ir.splitCriticalEdges();
+    // Verify critical edges are split so we have a place to insert phi moves if necessary.
+    assert ir.verifySplitCriticalEdges();
 
     for (BasicBlock block : blocks) {
       block.deduplicatePhis();
@@ -550,6 +561,12 @@
       // Process synthesized move-exception block specially.
       if (item instanceof MoveExceptionWorklistItem) {
         processMoveExceptionItem((MoveExceptionWorklistItem) item);
+        closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
+        continue;
+      }
+      // Split blocks are just pending close.
+      if (item instanceof SplitBlockWorklistItem) {
+        closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
         continue;
       }
       // Build IR for each dex instruction in the block.
@@ -571,20 +588,20 @@
 
   private void processMoveExceptionItem(MoveExceptionWorklistItem moveExceptionItem) {
     // TODO(zerny): Link with outer try-block handlers, if any. b/65203529
-    int moveExceptionDest = source.getMoveExceptionRegister();
-    assert moveExceptionDest >= 0;
     int targetIndex = source.instructionIndex(moveExceptionItem.targetOffset);
-    Value out = writeRegister(moveExceptionDest, ValueType.OBJECT, ThrowingInfo.NO_THROW, null);
-    Position position = source.getDebugPositionAtOffset(moveExceptionItem.targetOffset);
-    MoveException moveException = new MoveException(out);
-    moveException.setPosition(position);
-    currentBlock.add(moveException);
+    int moveExceptionDest = source.getMoveExceptionRegister(targetIndex);
+    if (moveExceptionDest >= 0) {
+      Value out = writeRegister(moveExceptionDest, ValueType.OBJECT, ThrowingInfo.NO_THROW, null);
+      Position position = source.getDebugPositionAtOffset(moveExceptionItem.targetOffset);
+      MoveException moveException = new MoveException(out);
+      moveException.setPosition(position);
+      currentBlock.add(moveException);
+    }
     Goto exit = new Goto();
     currentBlock.add(exit);
     BasicBlock targetBlock = getTarget(moveExceptionItem.targetOffset);
     currentBlock.link(targetBlock);
     addToWorklist(targetBlock, targetIndex);
-    closeCurrentBlock();
   }
 
   // Helper to resolve switch payloads and build switch instructions (dex code only).
@@ -976,11 +993,8 @@
   public void addGoto(int targetOffset) {
     addInstruction(new Goto());
     BasicBlock targetBlock = getTarget(targetOffset);
-    if (currentBlock.hasCatchSuccessor(targetBlock)) {
-      needGotoToCatchBlocks.add(new BasicBlock.Pair(currentBlock, targetBlock));
-    } else {
-      currentBlock.link(targetBlock);
-    }
+    assert !currentBlock.hasCatchSuccessor(targetBlock);
+    currentBlock.link(targetBlock);
     addToWorklist(targetBlock, source.instructionIndex(targetOffset));
     closeCurrentBlock();
   }
@@ -1292,25 +1306,20 @@
   }
 
   public void addMoveException(int dest) {
-    Value out = writeRegister(dest, ValueType.OBJECT, ThrowingInfo.NO_THROW);
-    assert !out.hasLocalInfo();
-    MoveException instruction = new MoveException(out);
-    assert !instruction.instructionTypeCanThrow();
-    if (currentBlock.getInstructions().size() == 1 && currentBlock.entry().isDebugPosition()) {
-      InstructionListIterator it = currentBlock.listIterator();
-      Instruction entry = it.next();
-      assert entry.getPosition().equals(source.getCurrentPosition());
-      attachLocalValues(instruction);
-      it.replaceCurrentInstruction(instruction);
-      return;
+    assert !currentBlock.getPredecessors().isEmpty();
+    assert currentBlock.getPredecessors().stream().allMatch(b -> b.entry().isMoveException());
+    assert verifyValueIsMoveException(readRegister(dest, ValueType.OBJECT));
+  }
+
+  private static boolean verifyValueIsMoveException(Value value) {
+    if (value.isPhi()) {
+      for (Value operand : value.asPhi().getOperands()) {
+        assert operand.definition.isMoveException();
+      }
+    } else {
+      assert value.definition.isMoveException();
     }
-    if (!currentBlock.getInstructions().isEmpty()) {
-      throw new CompilationError("Invalid MoveException instruction encountered. "
-          + "The MoveException instruction is not the first instruction in the block in "
-          + method.qualifiedName()
-          + ".");
-    }
-    addInstruction(instruction);
+    return true;
   }
 
   public void addMoveResult(int dest) {
@@ -1513,7 +1522,7 @@
   public void addThrow(int value) {
     Value in = readRegister(value, ValueType.OBJECT);
     addInstruction(new Throw(in));
-    closeCurrentBlock();
+    closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
   }
 
   public void addOr(NumericType type, int dest, int left, int right) {
@@ -1805,24 +1814,47 @@
     if (!throwingInstructionInCurrentBlock) {
       return;
     }
-    BasicBlock block = new BasicBlock();
+    // Note that when splitting the block in two we must update the CFG information so that we can
+    // correctly identify if the normal exits of the constructed block must be split once it is
+    // closed.
+    int currentBlockOffset = getOffset(currentBlock);
+    BlockInfo currentBlockInfo = getBlockInfo(currentBlockOffset);
+
+    BlockInfo info = new BlockInfo();
+    BasicBlock block = info.block;
     block.setNumber(nextBlockNumber++);
     blocks.add(block);
-    block.incrementUnfilledPredecessorCount();
+
+    // Compute some unused offset for the new block and link it in the CFG.
     int freshOffset = INITIAL_BLOCK_OFFSET - 1;
     while (targets.containsKey(freshOffset)) {
       freshOffset--;
     }
-    targets.put(freshOffset, null);
+    targets.put(freshOffset, info);
+    offsets.put(block, freshOffset);
+
+    // Copy over the exceptional successors.
     for (int offset : source.getCurrentCatchHandlers().getUniqueTargets()) {
+      info.addExceptionalSuccessor(offset);
       BlockInfo target = targets.get(offset);
       assert !target.block.isSealed();
       target.block.incrementUnfilledPredecessorCount();
       target.addExceptionalPredecessor(freshOffset);
     }
+
+    // Move all normal successors to the new block.
+    currentBlockInfo.normalSuccessors.forEach(info::addNormalSuccessor);
+    currentBlockInfo.normalSuccessors.clear();
+
+    // Link the two blocks.
     addInstruction(new Goto());
     currentBlock.link(block);
-    closeCurrentBlock();
+    block.incrementUnfilledPredecessorCount();
+    currentBlockInfo.addNormalSuccessor(freshOffset);
+    info.addNormalPredecessor(currentBlockOffset);
+
+    // The new block can only have a single predecessor and so we don't need to split between them.
+    closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
     setCurrentBlock(block);
   }
 
@@ -1843,29 +1875,19 @@
         assert !throwingInstructionInCurrentBlock;
         throwingInstructionInCurrentBlock = true;
         List<BasicBlock> targets = new ArrayList<>(catchHandlers.getAllTargets().size());
-        int moveExceptionDest = source.getMoveExceptionRegister();
-        if (moveExceptionDest < 0) {
-          for (int targetOffset : catchHandlers.getAllTargets()) {
-            BasicBlock target = getTarget(targetOffset);
-            addToWorklist(target, source.instructionIndex(targetOffset));
-            targets.add(target);
+        // Construct unique move-exception header blocks for each unique target.
+        Map<BasicBlock, BasicBlock> moveExceptionHeaders =
+            new IdentityHashMap<>(catchHandlers.getUniqueTargets().size());
+        for (int targetOffset : catchHandlers.getAllTargets()) {
+          BasicBlock target = getTarget(targetOffset);
+          BasicBlock header = moveExceptionHeaders.get(target);
+          if (header == null) {
+            header = new BasicBlock();
+            header.incrementUnfilledPredecessorCount();
+            moveExceptionHeaders.put(target, header);
+            ssaWorklist.add(new MoveExceptionWorklistItem(header, targetOffset));
           }
-        } else {
-          // If there is a well-defined move-exception destination register (eg, compiling from
-          // Java-bytecode) then we construct move-exception header blocks for each unique target.
-          Map<BasicBlock, BasicBlock> moveExceptionHeaders =
-              new IdentityHashMap<>(catchHandlers.getUniqueTargets().size());
-          for (int targetOffset : catchHandlers.getAllTargets()) {
-            BasicBlock target = getTarget(targetOffset);
-            BasicBlock header = moveExceptionHeaders.get(target);
-            if (header == null) {
-              header = new BasicBlock();
-              header.incrementUnfilledPredecessorCount();
-              moveExceptionHeaders.put(target, header);
-              ssaWorklist.add(new MoveExceptionWorklistItem(header, targetOffset));
-            }
-            targets.add(header);
-          }
+          targets.add(header);
         }
         currentBlock.linkCatchSuccessors(catchHandlers.getGuards(), targets);
       }
@@ -1907,6 +1929,7 @@
         info = new BlockInfo();
       }
       targets.put(offset, info);
+      offsets.put(info.block, offset);
     }
     return info;
   }
@@ -1980,11 +2003,23 @@
 
   // Private block helpers.
 
+  private BlockInfo getBlockInfo(int offset) {
+    return targets.get(offset);
+  }
+
+  private BlockInfo getBlockInfo(BasicBlock block) {
+    return getBlockInfo(getOffset(block));
+  }
+
   private BasicBlock getTarget(int offset) {
     return targets.get(offset).block;
   }
 
-  private void closeCurrentBlock() {
+  private int getOffset(BasicBlock block) {
+    return offsets.getInt(block);
+  }
+
+  private void closeCurrentBlockGuaranteedNotToNeedEdgeSplitting() {
     // TODO(zerny): To ensure liveness of locals throughout the entire block, we might want to
     // insert reads before closing the block. It is unclear if we can rely on a local-end to ensure
     // liveness in all blocks where the local should be live.
@@ -1994,48 +2029,41 @@
     throwingInstructionInCurrentBlock = false;
   }
 
+  private void closeCurrentBlock() {
+    assert currentBlock != null;
+    BlockInfo info = getBlockInfo(currentBlock);
+    if (!info.hasJustOneNormalExit()) {
+      // Exceptional edges are always split on construction, so no need to split edges to them.
+      for (int successorOffset : info.normalSuccessors) {
+        BlockInfo successorInfo = getBlockInfo(successorOffset);
+        if (successorInfo.predecessorCount() > 1) {
+          BasicBlock splitBlock = createSplitEdgeBlock(currentBlock, successorInfo.block);
+          ssaWorklist.add(new SplitBlockWorklistItem(splitBlock));
+        }
+      }
+    }
+    closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
+  }
+
+  private static BasicBlock createSplitEdgeBlock(BasicBlock source, BasicBlock target) {
+    BasicBlock splitBlock = new BasicBlock();
+    splitBlock.add(new Goto());
+    splitBlock.incrementUnfilledPredecessorCount();
+    splitBlock.getPredecessors().add(source);
+    splitBlock.getSuccessors().add(target);
+    source.replaceSuccessor(target, splitBlock);
+    target.replacePredecessor(source, splitBlock);
+    return splitBlock;
+  }
+
   private void closeCurrentBlockWithFallThrough(BasicBlock nextBlock) {
     assert currentBlock != null;
     addInstruction(new Goto());
-    if (currentBlock.hasCatchSuccessor(nextBlock)) {
-      needGotoToCatchBlocks.add(new BasicBlock.Pair(currentBlock, nextBlock));
-    } else {
-      currentBlock.link(nextBlock);
-    }
+    assert !currentBlock.hasCatchSuccessor(nextBlock);
+    currentBlock.link(nextBlock);
     closeCurrentBlock();
   }
 
-  private void handleFallthroughToCatchBlock() {
-    // When a catch handler for a block goes to the same block as the fallthrough for that
-    // block the graph only has one edge there. In these cases we add an additional block so the
-    // catch edge goes through that and then make the fallthrough go through a new direct edge.
-    for (BasicBlock.Pair pair : needGotoToCatchBlocks) {
-      BasicBlock source = pair.first;
-      BasicBlock target = pair.second;
-
-      // New block with one unfilled predecessor.
-      BasicBlock newBlock = BasicBlock.createGotoBlock(nextBlockNumber++, target);
-      blocks.add(newBlock);
-      newBlock.incrementUnfilledPredecessorCount();
-
-      // Link blocks.
-      source.replaceSuccessor(target, newBlock);
-      newBlock.getPredecessors().add(source);
-      source.getSuccessors().add(target);
-      target.getPredecessors().add(newBlock);
-
-      // Check that the successor indexes are correct.
-      assert source.hasCatchSuccessor(newBlock);
-      assert !source.hasCatchSuccessor(target);
-
-      // Mark the filled predecessors to the blocks.
-      if (source.isFilled()) {
-        newBlock.filledPredecessor(this);
-      }
-      target.filledPredecessor(this);
-    }
-  }
-
   /**
    * Change to control-flow graph to avoid repeated phi operands when all the same values
    * flow in from multiple predecessors.
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 dce8aa6..3288aa9 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
@@ -745,7 +745,7 @@
           Suppliers.memoize(() -> inliner.createDefaultOracle(
               method, code, effectivelyFinalTypeEnvironment,
               isProcessedConcurrently, callSiteInformation,
-              Integer.MAX_VALUE, Integer.MAX_VALUE)
+              Integer.MAX_VALUE / 2, Integer.MAX_VALUE / 2)
           )
       );
       assert code.isConsistentSSA();
@@ -782,6 +782,10 @@
     }
     codeRewriter.identifyParameterUsages(method, code, feedback);
 
+    if (options.canHaveNumberConversionRegisterAllocationBug()) {
+      codeRewriter.workaroundNumberConversionRegisterAllocationBug(code);
+    }
+
     printMethod(code, "Optimized IR (SSA)");
     finalizeIR(method, code, feedback);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 5c25fba..53bbb7e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -594,7 +594,12 @@
   }
 
   @Override
-  public int getMoveExceptionRegister() {
+  public int getMoveExceptionRegister(int instructionIndex) {
+    return getMoveExceptionRegister();
+  }
+
+  // In classfiles the register is always on top of stack.
+  private int getMoveExceptionRegister() {
     return state.startOfStack;
   }
 
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 ad6d66d..c26aecf 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
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo;
+import com.android.tools.r8.graph.ParameterUsagesInfo;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import java.util.BitSet;
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 326fa03..2b1a6db 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
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo;
+import com.android.tools.r8.graph.ParameterUsagesInfo;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import java.util.BitSet;
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 359c90d..a547b36 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
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo;
+import com.android.tools.r8.graph.ParameterUsagesInfo;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import java.util.BitSet;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
index e95790a..bf2b51f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
@@ -59,7 +59,8 @@
   void resolveAndBuildNewArrayFilledData(int arrayRef, int payloadOffset, IRBuilder builder);
 
   CatchHandlers<Integer> getCurrentCatchHandlers();
-  int getMoveExceptionRegister();
+
+  int getMoveExceptionRegister(int instructionIndex);
 
   // For debugging/verification purpose.
   boolean verifyRegister(int register);
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 22e3c32..ee64165 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
@@ -13,10 +13,6 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo.NotUsed;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo.SingleCallOfArgumentMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialClassInitializer;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialInstanceInitializer;
@@ -37,6 +33,9 @@
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.ParameterUsagesInfo;
+import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
+import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsageBuilder;
 import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.ArrayPut;
@@ -63,6 +62,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeNewArray;
+import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
@@ -850,28 +850,27 @@
     List<Value> values = code.collectArguments(true);
     for (int i = 0; i < values.size(); i++) {
       Value value = values.get(i);
-
-      int numberOfAllUsages = value.numberOfAllUsers();
-      if (numberOfAllUsages == 0) {
-        usages.add(new NotUsed(i));
+      if (value.numberOfPhiUsers() > 0) {
         continue;
       }
-
-      if (numberOfAllUsages == 1 && value.numberOfUsers() == 1) {
-        Instruction instruction = value.singleUniqueUser();
-        if (instruction.isInvokeMethodWithReceiver() &&
-            instruction.inValues().lastIndexOf(value) == 0) {
-          usages.add(new SingleCallOfArgumentMethod(i,
-              instruction.asInvokeMethodWithReceiver().getType(),
-              instruction.asInvokeMethodWithReceiver().getInvokedMethod()));
-        }
-        continue;
+      ParameterUsage usage = collectParameterUsages(i, value);
+      if (usage != null) {
+        usages.add(usage);
       }
     }
-
     feedback.setParameterUsages(method, usages.isEmpty() ? null : new ParameterUsagesInfo(usages));
   }
 
+  private ParameterUsage collectParameterUsages(int i, Value value) {
+    ParameterUsageBuilder builder = new ParameterUsageBuilder(value, i);
+    for (Instruction user : value.uniqueUsers()) {
+      if (!builder.note(user)) {
+        return null;
+      }
+    }
+    return builder.build();
+  }
+
   // This method defines trivial instance initializer as follows:
   //
   // ** The initializer may call the initializer of the base class, which
@@ -3008,4 +3007,61 @@
       }
     }
   }
+
+  // See comment for InternalOptions.canHaveNumberConversionRegisterAllocationBug().
+  public void workaroundNumberConversionRegisterAllocationBug(IRCode code) {
+    final Supplier<DexMethod> javaLangDoubleisNaN = Suppliers.memoize(() ->
+     dexItemFactory.createMethod(
+        dexItemFactory.createString("Ljava/lang/Double;"),
+        dexItemFactory.createString("isNaN"),
+        dexItemFactory.booleanDescriptor,
+        new DexString[]{dexItemFactory.doubleDescriptor}));
+
+    ListIterator<BasicBlock> blocks = code.listIterator();
+    while (blocks.hasNext()) {
+      BasicBlock block = blocks.next();
+      InstructionListIterator it = block.listIterator();
+      while (it.hasNext()) {
+        Instruction instruction = it.next();
+        if (instruction.isArithmeticBinop() || instruction.isNeg()) {
+          for (Value value : instruction.inValues()) {
+            // Insert a call to Double.isNaN on each value which come from a number conversion
+            // to double and flows into an arithmetic instruction. This seems to break the traces
+            // in the Dalvik JIT and avoid the bug where the generated ARM code can clobber float
+            // values in a single-precision registers with double values written to
+            // double-precision registers. See b/77496850 for examples.
+            if (!value.isPhi()
+                && value.definition.isNumberConversion()
+                && value.definition.asNumberConversion().to == NumericType.DOUBLE) {
+              Value newValue = code.createValue(
+                  instruction.outValue().outType(), instruction.getLocalInfo());
+              InvokeStatic invokeIsNaN =
+                  new InvokeStatic(javaLangDoubleisNaN.get(), newValue, ImmutableList.of(value));
+              invokeIsNaN.setPosition(instruction.getPosition());
+
+              // Insert the invoke before the current instruction.
+              it.previous();
+              BasicBlock blockWithInvokeNaN =
+                  block.hasCatchHandlers() ? it.split(code, blocks) : block;
+              if (blockWithInvokeNaN != block) {
+                // If we split, add the invoke at the end of the original block.
+                it = block.listIterator(block.getInstructions().size());
+                it.previous();
+                it.add(invokeIsNaN);
+                // Continue iteration in the split block.
+                block = blockWithInvokeNaN;
+                it = block.listIterator();
+              } else {
+                // Otherwise, add it to the current block.
+                it.add(invokeIsNaN);
+              }
+              // Skip over the instruction causing the invoke to be inserted.
+              Instruction temp = it.next();
+              assert temp == instruction;
+            }
+          }
+        }
+      }
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 6cb8f6e..e749479 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -1099,7 +1099,7 @@
     }
 
     @Override
-    public int getMoveExceptionRegister() {
+    public int getMoveExceptionRegister(int instructionIndex) {
       throw new Unreachable();
     }
 
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 4d6e3b1..b3bde90 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
@@ -9,9 +9,6 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.DexEncodedMethod.OptimizationInfo;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo.NotUsed;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo.SingleCallOfArgumentMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialClassInitializer;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialInstanceInitializer;
@@ -19,6 +16,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
@@ -38,7 +36,9 @@
 import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.LinkedList;
@@ -48,6 +48,9 @@
 import java.util.function.Supplier;
 
 final class InlineCandidateProcessor {
+  private static final ImmutableSet<If.Type> ALLOWED_ZERO_TEST_TYPES =
+      ImmutableSet.of(If.Type.EQ, If.Type.NE);
+
   private final DexItemFactory factory;
   private final AppInfoWithLiveness appInfo;
   private final Predicate<DexType> isClassEligible;
@@ -441,6 +444,12 @@
       return null;
     }
 
+    // Don't inline code w/o normal returns into block with catch handlers (b/64432527).
+    if (initInvoke.getBlock().hasCatchHandlers() &&
+        definition.getOptimizationInfo().neverReturnsNormally()) {
+      return null;
+    }
+
     // If the superclass of the initializer is NOT java.lang.Object, the super class
     // initializer being called must be classified as TrivialInstanceInitializer.
     //
@@ -473,16 +482,16 @@
     if (invoke.inValues().lastIndexOf(eligibleInstance) > 0) {
       return null; // Instance passed as an argument.
     }
-    return isEligibleMethodCall(invoke.getInvokedMethod(),
+    return isEligibleMethodCall(!invoke.getBlock().hasCatchHandlers(), invoke.getInvokedMethod(),
         eligibility -> !eligibility.returnsReceiver ||
             invoke.outValue() == null || invoke.outValue().numberOfAllUsers() == 0);
   }
 
   private InliningInfo isEligibleIndirectMethodCall(DexMethod callee) {
-    return isEligibleMethodCall(callee, eligibility -> !eligibility.returnsReceiver);
+    return isEligibleMethodCall(false, callee, eligibility -> !eligibility.returnsReceiver);
   }
 
-  private InliningInfo isEligibleMethodCall(
+  private InliningInfo isEligibleMethodCall(boolean allowMethodsWithoutNormalReturns,
       DexMethod callee, Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
 
     DexEncodedMethod singleTarget = findSingleTarget(callee);
@@ -493,8 +502,9 @@
       return null; // Don't inline itself.
     }
 
-    ClassInlinerEligibility eligibility =
-        singleTarget.getOptimizationInfo().getClassInlinerEligibility();
+    OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
+
+    ClassInlinerEligibility eligibility = optimizationInfo.getClassInlinerEligibility();
     if (eligibility == null) {
       return null;
     }
@@ -505,6 +515,11 @@
       return null;
     }
 
+    // Don't inline code w/o normal returns into block with catch handlers (b/64432527).
+    if (!allowMethodsWithoutNormalReturns && optimizationInfo.neverReturnsNormally()) {
+      return null;
+    }
+
     if (!singleTarget.isInliningCandidate(method, Reason.SIMPLE, appInfo)) {
       // We won't be able to inline it here.
 
@@ -532,13 +547,24 @@
   //      eligible according to the same rules defined for direct method call eligibility
   //      (except we require the method receiver to not be used in return instruction)
   //
+  //   -- eligible instance is used in zero-test 'if' instructions testing if the value
+  //      is null/not-null (since we know the instance is not null, those checks can
+  //      be rewritten)
+  //
   //   -- method itself can be inlined
   //
-  private synchronized boolean isExtraMethodCallEligible(
+  private boolean isExtraMethodCallEligible(
       Supplier<InliningOracle> defaultOracle, InvokeMethod invokeMethod) {
 
     List<Value> arguments = Lists.newArrayList(invokeMethod.inValues());
 
+    // Don't consider constructor invocations and super calls, since
+    // we don't want to forcibly inline them.
+    if (invokeMethod.isInvokeSuper() ||
+        (invokeMethod.isInvokeDirect() && factory.isConstructor(invokeMethod.getInvokedMethod()))) {
+      return false;
+    }
+
     // Remove receiver from arguments.
     if (invokeMethod.isInvokeMethodWithReceiver()) {
       if (arguments.get(0) == eligibleInstance) {
@@ -556,6 +582,11 @@
 
     OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
 
+    // Don't inline code w/o normal returns into block with catch handlers (b/64432527).
+    if (invokeMethod.getBlock().hasCatchHandlers() && optimizationInfo.neverReturnsNormally()) {
+      return false;
+    }
+
     // Go through all arguments, see if all usages of eligibleInstance are good.
     for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
       Value argument = arguments.get(argIndex);
@@ -569,35 +600,44 @@
         return false;  // Don't know anything.
       }
 
-      if (parameterUsage instanceof NotUsed) {
+      if (parameterUsage.notUsed()) {
         // Reference can be removed since it's not used.
         unusedArguments.add(new Pair<>(invokeMethod, argIndex));
         continue;
       }
 
-      if (parameterUsage instanceof SingleCallOfArgumentMethod) {
-        // Method exactly one time calls a method on passed eligibleInstance.
-        SingleCallOfArgumentMethod info = (SingleCallOfArgumentMethod) parameterUsage;
-        if (info.type != Type.VIRTUAL && info.type != Type.INTERFACE) {
-          return false; // Don't support direct and super calls yet.
-        }
-
-        // Is the method called indirectly still eligible?
-        InliningInfo potentialInliningInfo = isEligibleIndirectMethodCall(info.method);
-        if (potentialInliningInfo != null) {
-          // Check if the method is inline-able by standard inliner.
-          InlineAction inlineAction =
-              invokeMethod.computeInlining(defaultOracle.get(), method.method.holder);
-          if (inlineAction != null) {
-            extraMethodCalls.put(invokeMethod, new InliningInfo(singleTarget, null));
-            continue;
-          }
-        }
-
+      if (parameterUsage.returnValue &&
+          !(invokeMethod.outValue() == null || invokeMethod.outValue().numberOfAllUsers() == 0)) {
+        // Used as return value which is not ignored.
         return false;
       }
 
-      return false; // All other argument usages are not eligible.
+      if (!Sets.difference(parameterUsage.ifZeroTest, ALLOWED_ZERO_TEST_TYPES).isEmpty()) {
+        // Used in unsupported zero-check-if kinds.
+        return false;
+      }
+
+      for (Pair<Type, DexMethod> call : parameterUsage.callsReceiver) {
+        if (call.getFirst() != Type.VIRTUAL && call.getFirst() != Type.INTERFACE) {
+          // Don't support direct and super calls yet.
+          return false;
+        }
+
+        // Is the method called indirectly still eligible?
+        InliningInfo potentialInliningInfo = isEligibleIndirectMethodCall(call.getSecond());
+        if (potentialInliningInfo == null) {
+          return false;
+        }
+
+        // Check if the method is inline-able by standard inliner.
+        InlineAction inlineAction =
+            invokeMethod.computeInlining(defaultOracle.get(), method.method.holder);
+        if (inlineAction == null) {
+          return false;
+        }
+      }
+
+      extraMethodCalls.put(invokeMethod, new InliningInfo(singleTarget, null));
     }
 
     // Looks good.
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index cebcc8c..08d0cae 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -354,7 +354,8 @@
         // avoid a bug where the index variable could end up being uninitialized.
         if (code.options.canHaveBoundsCheckEliminationBug()
             && move.from.getValue().isConstNumber()
-            && move.type == MoveType.SINGLE) {
+            && move.type == MoveType.SINGLE
+            && allocator.unadjustedRealRegisterFromAllocated(move.to.getRegister()) < 256) {
           scheduler.addMove(
               new RegisterMove(move.to.getRegister(), move.type, move.from.getValue().definition));
         } else {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index 80a439a..c64d8ed 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -209,7 +209,7 @@
   }
 
   @Override
-  public int getMoveExceptionRegister() {
+  public int getMoveExceptionRegister(int instructionIndex) {
     throw new Unreachable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 20e8d57..baacb1e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -112,7 +112,10 @@
   private final Set<DexField> protoLiteFields = Sets.newIdentityHashSet();
   private final Set<DexItem> identifierNameStrings = Sets.newIdentityHashSet();
 
-  /** Set of method signatures used in invoke-super instructions that cannot not be resolved. */
+  /**
+   * Set of method signatures used in invoke-super instructions that either cannot be resolved or
+   * resolve to a private method (leading to an IllegalAccessError).
+   */
   private final Set<DexMethod> brokenSuperInvokes = Sets.newIdentityHashSet();
   /**
    * This map keeps a view of all virtual methods that are reachable from virtual invokes. A method
@@ -1032,6 +1035,9 @@
       reportMissingMethod(method);
       return;
     }
+    if (target.accessFlags.isPrivate()) {
+      brokenSuperInvokes.add(method);
+    }
     assert !superInvokeDependencies.containsKey(from) || !superInvokeDependencies.get(from)
         .contains(target);
     if (Log.ENABLED) {
@@ -1520,7 +1526,10 @@
      * Set of all methods referenced in static invokes;
      */
     public final SortedSet<DexMethod> staticInvokes;
-    /** Set of method signatures used in invoke-super instructions that cannot not be resolved. */
+    /**
+     * Set of method signatures used in invoke-super instructions that either cannot be resolved or
+     * resolve to a private method (leading to an IllegalAccessError).
+     */
     public final SortedSet<DexMethod> brokenSuperInvokes;
     /**
      * Set of all items that have to be kept independent of whether they are used.
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index c4276dc..1de3f17 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -873,6 +873,7 @@
         ruleBuilder.setName(IdentifierPatternWithWildcards.withoutWildcards("<init>"));
         ruleBuilder.setArguments(parseArgumentList());
       } else {
+        TextPosition firstStart = getPosition();
         IdentifierPatternWithWildcards first =
             acceptIdentifierWithBackreference(IdentifierType.ANY);
         if (first != null) {
@@ -885,6 +886,7 @@
               ruleBuilder.setName(first);
               ruleBuilder.setArguments(parseArgumentList());
             } else {
+              TextPosition secondStart = getPosition();
               IdentifierPatternWithWildcards second =
                   acceptIdentifierWithBackreference(IdentifierType.ANY);
               if (second != null) {
@@ -897,6 +899,12 @@
                           ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory));
                   ruleBuilder.setArguments(parseArgumentList());
                 } else {
+                  if (first.hasUnusualCharacters()) {
+                    warnUnusualCharacters("type", first.pattern, "field", firstStart);
+                  }
+                  if (second.hasUnusualCharacters()) {
+                    warnUnusualCharacters("field name", second.pattern, "field", secondStart);
+                  }
                   ruleBuilder.setRuleType(ProguardMemberType.FIELD);
                   ruleBuilder.setName(second);
                   ruleBuilder
@@ -1490,6 +1498,16 @@
           "Option -" + optionName + " overrides -" + victim, origin, getPosition(start)));
     }
 
+    private void warnUnusualCharacters(
+        String kind, String pattern, String ruleType, TextPosition start) {
+      reporter.warning(new StringDiagnostic(
+          "The " + kind + " \"" + pattern + "\" is used in a " + ruleType + " rule. The "
+              + "characters in this " + kind + " are legal for the JVM, "
+              + "but unlikely to originate from a source language. "
+              + "Maybe this is not the rule you are looking for.",
+          origin, getPosition(start)));
+    }
+
     private void failPartiallyImplementedOption(String optionName, TextPosition start) {
       throw reporter.fatalError(new StringDiagnostic(
           "Option " + optionName + " currently not supported", origin, getPosition(start)));
@@ -1528,5 +1546,25 @@
     boolean isMatchAllNames() {
       return pattern.equals("*");
     }
+
+    boolean hasUnusualCharacters() {
+      if (pattern.contains("<") || pattern.contains(">")) {
+        int angleStartCount = 0;
+        int angleEndCount = 0;
+        for (int i = 0; i < pattern.length(); i++) {
+          char c = pattern.charAt(i);
+          if (c == '<') {
+            angleStartCount++;
+          }
+          if (c == '>') {
+            angleEndCount++;
+          }
+        }
+        // Check that start/end angles are matched, and *only* used for well-formed wildcard
+        // backreferences (e.g. '<1>', but not '<<1>>', '<<*>>' or '>1<').
+        return !(angleStartCount == angleEndCount && angleStartCount == wildcards.size());
+      }
+      return false;
+    }
   }
 }
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 ec54641..eb8ee0e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -323,7 +323,7 @@
     boolean printOutdatedToolchain = false;
     if (warningInvalidParameterAnnotations.size() > 0) {
       // TODO(b/67626202): Add a regression test with a program that hits this issue.
-      reporter.warning(
+      reporter.info(
           new StringDiagnostic(
               "Invalid parameter counts in MethodParameter attributes. "
                   + "This is likely due to Proguard having removed a parameter."));
@@ -348,7 +348,7 @@
       for (List<Pair<DexEncodedMethod, String>> methods : warningInvalidDebugInfo.values()) {
         count += methods.size();
       }
-      reporter.warning(
+      reporter.info(
           new StringDiagnostic(
               "Stripped invalid locals information from "
                   + count
@@ -359,13 +359,13 @@
           builder.append("\n  ").append(method.getFirst().toSourceString());
           builder.append("\n  ").append(method.getSecond());
         }
-        reporter.warning(new StringDiagnostic(builder.toString(), origin));
+        reporter.info(new StringDiagnostic(builder.toString(), origin));
       }
       printed = true;
       printOutdatedToolchain = true;
     }
     if (missingEnclosingMembers.size() > 0) {
-      reporter.warning(
+      reporter.info(
           new StringDiagnostic(
               "InnerClass annotations are missing corresponding EnclosingMember annotations."
                   + " Such InnerClass annotations are ignored."));
@@ -631,4 +631,12 @@
   public boolean canHaveArtStringNewInitBug() {
     return minApiLevel <= AndroidApiLevel.P.getLevel();
   }
+
+  // Dalvik tracing JIT may perform invalid optimizations when int/float values are converted to
+  // double and used in arithmetic operations.
+  //
+  // See b/77496850.
+  public boolean canHaveNumberConversionRegisterAllocationBug() {
+    return minApiLevel <= AndroidApiLevel.K.getLevel();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
index 2f45ae2..d79aaf0 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -116,9 +116,14 @@
         messageParts);
   }
 
+  public static Diagnostic checkDiagnostics(List<Diagnostic> diagnostics, int index, Path path,
+      int lineStart, int columnStart, String... messageParts) {
+    return checkDiagnostic(diagnostics.get(index), path, lineStart, columnStart, messageParts);
+  }
+
   public static Diagnostic checkDiagnostics(List<Diagnostic> diagnostics, Path path,
       int lineStart, int columnStart, String... messageParts) {
     assertEquals(1, diagnostics.size());
-    return checkDiagnostic(diagnostics.get(0), path, lineStart, columnStart, messageParts);
+    return checkDiagnostics(diagnostics, 0, path, lineStart, columnStart, messageParts);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index e7344a4..0cf2f53 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -196,7 +196,7 @@
 
   public void runCatchHandlerTest(boolean codeThrows, boolean twoGuards) throws Exception {
     final int secondBlockInstructions = 4;
-    final int initialBlockCount = 5;
+    final int initialBlockCount = 6;
     // Try split between all instructions in second block.
     for (int i = 1; i < secondBlockInstructions; i++) {
       TestApplication test = codeWithCatchHandlers(codeThrows, twoGuards);
@@ -235,7 +235,7 @@
   public void runCatchHandlerSplitThreeTest(boolean codeThrows, boolean twoGuards)
       throws Exception {
     final int secondBlockInstructions = 4;
-    final int initialBlockCount = 5;
+    final int initialBlockCount = 6;
     // Try split out all instructions in second block.
     for (int i = 1; i < secondBlockInstructions - 1; i++) {
       TestApplication test = codeWithCatchHandlers(codeThrows, twoGuards);
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 51a5262..3239191 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
@@ -9,6 +9,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.OutputMode;
@@ -27,6 +28,7 @@
 import com.android.tools.r8.ir.optimize.classinliner.builders.Tuple;
 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.trivial.ClassWithFinal;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
@@ -254,6 +256,51 @@
     assertFalse(inspector.clazz(C.F.class).isPresent());
   }
 
+  @Test
+  public void testInvalidatedRoot() throws Exception {
+    String prefix = "com.android.tools.r8.ir.optimize.classinliner.invalidroot.";
+
+    byte[][] classes = {
+        ToolHelper.getClassAsBytes(InvalidRootsTestClass.class),
+        ToolHelper.getClassAsBytes(InvalidRootsTestClass.A.class),
+        ToolHelper.getClassAsBytes(InvalidRootsTestClass.B.class),
+        ToolHelper.getClassAsBytes(InvalidRootsTestClass.NeverReturnsNormally.class),
+        ToolHelper.getClassAsBytes(InvalidRootsTestClass.InitNeverReturnsNormally.class)
+    };
+    AndroidApp app = runR8(buildAndroidApp(classes), InvalidRootsTestClass.class);
+
+    String javaOutput = runOnJava(InvalidRootsTestClass.class);
+    String artOutput = runOnArt(app, InvalidRootsTestClass.class);
+    assertEquals(javaOutput, artOutput);
+
+    DexInspector inspector = new DexInspector(app);
+    ClassSubject clazz = inspector.clazz(InvalidRootsTestClass.class);
+
+    assertEquals(
+        Sets.newHashSet(prefix + "InvalidRootsTestClass$NeverReturnsNormally"),
+        collectTypes(clazz, "testExtraNeverReturnsNormally", "void"));
+
+    assertEquals(
+        Sets.newHashSet(prefix + "InvalidRootsTestClass$NeverReturnsNormally"),
+        collectTypes(clazz, "testDirectNeverReturnsNormally", "void"));
+
+    assertEquals(
+        Sets.newHashSet(prefix + "InvalidRootsTestClass$InitNeverReturnsNormally"),
+        collectTypes(clazz, "testInitNeverReturnsNormally", "void"));
+
+    assertTrue(inspector.clazz(InvalidRootsTestClass.NeverReturnsNormally.class).isPresent());
+    assertTrue(inspector.clazz(InvalidRootsTestClass.InitNeverReturnsNormally.class).isPresent());
+
+    assertEquals(
+        Sets.newHashSet(
+            "java.lang.StringBuilder",
+            "java.lang.RuntimeException"),
+        collectTypes(clazz, "testRootInvalidatesAfterInlining", "void"));
+
+    assertFalse(inspector.clazz(InvalidRootsTestClass.A.class).isPresent());
+    assertFalse(inspector.clazz(InvalidRootsTestClass.B.class).isPresent());
+  }
+
   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/invalidroot/InvalidRootsTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java
new file mode 100644
index 0000000..26dc8cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java
@@ -0,0 +1,129 @@
+// 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.invalidroot;
+
+public class InvalidRootsTestClass {
+  private static int ID = 0;
+
+  private static String next() {
+    return Integer.toString(ID++);
+  }
+
+  public static void main(String[] args) {
+    InvalidRootsTestClass test = new InvalidRootsTestClass();
+    test.testExtraNeverReturnsNormally();
+    test.testDirectNeverReturnsNormally();
+    test.testInitNeverReturnsNormally();
+    test.testRootInvalidatesAfterInlining();
+  }
+
+  private synchronized void testExtraNeverReturnsNormally() {
+    testExtraNeverReturnsNormallyA();
+    testExtraNeverReturnsNormallyB();
+
+    try {
+      NeverReturnsNormally a = new NeverReturnsNormally();
+      neverReturnsNormallyExtra(next(), a);
+    } catch (RuntimeException re) {
+      System.out.println(re.toString());
+    }
+  }
+
+  private synchronized void testExtraNeverReturnsNormallyA() {
+    try {
+      neverReturnsNormallyExtra(next(), null);
+    } catch (RuntimeException re) {
+      System.out.println(re.toString());
+    }
+  }
+
+  private synchronized void testExtraNeverReturnsNormallyB() {
+    try {
+      neverReturnsNormallyExtra(next(), null);
+    } catch (RuntimeException re) {
+      System.out.println(re.toString());
+    }
+  }
+
+  private synchronized void testDirectNeverReturnsNormally() {
+    try {
+      NeverReturnsNormally a = new NeverReturnsNormally();
+      System.out.println(a.foo());
+    } catch (RuntimeException re) {
+      System.out.println(re.toString());
+    }
+  }
+
+  private synchronized void testInitNeverReturnsNormally() {
+    try {
+      new InitNeverReturnsNormally();
+    } catch (RuntimeException re) {
+      System.out.println(re.toString());
+    }
+  }
+
+  private void neverReturnsNormallyExtra(String prefix, NeverReturnsNormally a) {
+    throw new RuntimeException("neverReturnsNormallyExtra(" +
+        prefix + ", " + (a == null ? "null" : a.foo()) + "): " + next());
+  }
+
+  public static class NeverReturnsNormally {
+    public String foo() {
+      throw new RuntimeException("NeverReturnsNormally::foo(): " + next());
+    }
+  }
+
+  public static class InitNeverReturnsNormally {
+    public InitNeverReturnsNormally() {
+      throw new RuntimeException("InitNeverReturnsNormally::init(): " + next());
+    }
+
+    public String foo() {
+      return "InitNeverReturnsNormally::foo(): " + next();
+    }
+  }
+
+  private synchronized void testRootInvalidatesAfterInlining() {
+    A a = new A();
+    try {
+      notInlinedExtraMethod(next(), a);
+      System.out.println(new B().foo() + " " + next());
+      testRootInvalidatesAfterInliningA(a);
+      testRootInvalidatesAfterInliningB(a);
+    } catch (RuntimeException re) {
+      System.out.println(re.toString());
+    }
+  }
+
+  private void notInlinedExtraMethod(String prefix, A a) {
+    System.out.println("notInlinedExtraMethod(" +
+        prefix + ", " + (a == null ? "null" : a.foo()) + "): " + next());
+    if (a != null) {
+      throw new RuntimeException(
+          "notInlinedExtraMethod(" + prefix + ", " + a.foo() + "): " + next());
+    }
+    System.out.println("notInlinedExtraMethod(" + prefix + ", null): " + next());
+  }
+
+  private void testRootInvalidatesAfterInliningA(A a) {
+    notInlinedExtraMethod(next(), a);
+  }
+
+  private void testRootInvalidatesAfterInliningB(A a) {
+    notInlinedExtraMethod(next(), a);
+  }
+
+  public static class A {
+    public String foo() {
+      return "B::foo(" + next() + ")";
+    }
+  }
+
+  public static class B {
+    public String foo() {
+      return "B::foo(" + next() + ")";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index a690429..81e12a1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -114,6 +114,21 @@
           "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1").isPresent());
       assertTrue(inspector.clazz(
           "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1").isPresent());
+      assertTrue(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1").isPresent());
+      assertTrue(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1").isPresent());
+      assertTrue(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1").isPresent());
+      assertTrue(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1")
+          .isPresent());
+      assertTrue(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1")
+          .isPresent());
+      assertTrue(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1")
+          .isPresent());
     });
 
     runTest("class_inliner_lambda_k_style", mainClassName, true, (app) -> {
@@ -136,6 +151,31 @@
 
       assertFalse(inspector.clazz(
           "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1").isPresent());
+
+      assertEquals(
+          Sets.newHashSet(),
+          collectAccessedLambdaTypes(lambdaCheck, clazz, "testBigExtraMethod"));
+
+      assertFalse(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1").isPresent());
+      assertFalse(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1").isPresent());
+      assertFalse(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1").isPresent());
+
+      assertEquals(
+          Sets.newHashSet(),
+          collectAccessedLambdaTypes(lambdaCheck, clazz, "testBigExtraMethodReturningLambda"));
+
+      assertFalse(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1")
+          .isPresent());
+      assertFalse(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1")
+          .isPresent());
+      assertFalse(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1")
+          .isPresent());
     });
   }
 
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 4214a67..101bf06 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -766,7 +766,7 @@
     }
 
     @Override
-    public int getMoveExceptionRegister() {
+    public int getMoveExceptionRegister(int instructionIndex) {
       throw new Unreachable();
     }
 
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 3e46098..c929eaf 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -22,12 +22,12 @@
 import com.google.common.collect.ImmutableList;
 import java.util.Iterator;
 import java.util.function.BiConsumer;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class NeverReturnsNormallyTest extends TestBase {
   private void runTest(
-      BiConsumer<DexInspector, CompilationMode> inspection, CompilationMode mode) throws Exception {
+      BiConsumer<DexInspector, CompilationMode> inspection,
+      boolean enableClassInliner, CompilationMode mode) throws Exception {
     R8Command.Builder builder = R8Command.builder();
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
@@ -49,7 +49,8 @@
             "-allowaccessmodification"
         ),
         Origin.unknown());
-    AndroidApp app = ToolHelper.runR8(builder.build());
+    AndroidApp app = ToolHelper.runR8(builder.build(),
+        opts -> opts.enableClassInlining = enableClassInliner);
     inspection.accept(new DexInspector(app), mode);
 
     // Run on Art to check generated code against verifier.
@@ -128,10 +129,11 @@
     return instructions.next();
   }
 
-  @Ignore("b/110736241")
   @Test
   public void test() throws Exception {
-    runTest(this::validate, CompilationMode.DEBUG);
-    runTest(this::validate, CompilationMode.RELEASE);
+    runTest(this::validate, true, CompilationMode.DEBUG);
+    runTest(this::validate, true, CompilationMode.RELEASE);
+    runTest(this::validate, false, CompilationMode.DEBUG);
+    runTest(this::validate, false, CompilationMode.RELEASE);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java b/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
new file mode 100644
index 0000000..03614b9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
@@ -0,0 +1,507 @@
+// 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.b77496850;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.InvokeStatic;
+import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.utils.AndroidApiLevel;
+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.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class B77496850 extends TestBase {
+
+  static final String LOGTAG = "";
+
+  // Mock class for the code in PathParser below.
+  public static class Path {
+    void rLineTo(int x, int y) {
+    }
+    void cubicTo(float a, float b, float c, float d, float e, float f) {
+    }
+  }
+
+  // Mock class for the code in PathParser below.
+  public static class Log {
+    static void w(String x, String y) {
+    }
+  }
+
+  // Code copied from Android support library:
+  // https://android.googlesource.com/platform/frameworks/support/+/9791ac540f94c318f6602123d7000bfc55909b81/compat/src/main/java/android/support/v4/graphics/PathParser.java
+  public static class PathParser {
+
+    private static void drawArc(Path p,
+        float x0,
+        float y0,
+        float x1,
+        float y1,
+        float a,
+        float b,
+        float theta,
+        boolean isMoreThanHalf,
+        boolean isPositiveArc) {
+      /* Convert rotation angle from degrees to radians */
+      double thetaD = Math.toRadians(theta);
+      /* Pre-compute rotation matrix entries */
+      double cosTheta = Math.cos(thetaD);
+      double sinTheta = Math.sin(thetaD);
+      /* Transform (x0, y0) and (x1, y1) into unit space */
+      /* using (inverse) rotation, followed by (inverse) scale */
+      double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+      double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+      double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+      double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+      /* Compute differences and averages */
+      double dx = x0p - x1p;
+      double dy = y0p - y1p;
+      double xm = (x0p + x1p) / 2;
+      double ym = (y0p + y1p) / 2;
+      /* Solve for intersecting unit circles */
+      double dsq = dx * dx + dy * dy;
+      if (dsq == 0.0) {
+        Log.w(LOGTAG, " Points are coincident");
+        return; /* Points are coincident */
+      }
+      double disc = 1.0 / dsq - 1.0 / 4.0;
+      if (disc < 0.0) {
+        Log.w(LOGTAG, "Points are too far apart " + dsq);
+        float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+        drawArc(p, x0, y0, x1, y1, a * adjust,
+            b * adjust, theta, isMoreThanHalf, isPositiveArc);
+        return; /* Points are too far apart */
+      }
+      double s = Math.sqrt(disc);
+      double sdx = s * dx;
+      double sdy = s * dy;
+      double cx;
+      double cy;
+      if (isMoreThanHalf == isPositiveArc) {
+        cx = xm - sdy;
+        cy = ym + sdx;
+      } else {
+        cx = xm + sdy;
+        cy = ym - sdx;
+      }
+      double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+      double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+      double sweep = (eta1 - eta0);
+      if (isPositiveArc != (sweep >= 0)) {
+        if (sweep > 0) {
+          sweep -= 2 * Math.PI;
+        } else {
+          sweep += 2 * Math.PI;
+        }
+      }
+      cx *= a;
+      cy *= b;
+      double tcx = cx;
+      cx = cx * cosTheta - cy * sinTheta;
+      cy = tcx * sinTheta + cy * cosTheta;
+      arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+    }
+
+    /**
+     * Converts an arc to cubic Bezier segments and records them in p.
+     *
+     * @param p     The target for the cubic Bezier segments
+     * @param cx    The x coordinate center of the ellipse
+     * @param cy    The y coordinate center of the ellipse
+     * @param a     The radius of the ellipse in the horizontal direction
+     * @param b     The radius of the ellipse in the vertical direction
+     * @param e1x   E(eta1) x coordinate of the starting point of the arc
+     * @param e1y   E(eta2) y coordinate of the starting point of the arc
+     * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
+     * @param start The start angle of the arc on the ellipse
+     * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+     */
+    private static void arcToBezier(Path p,
+        double cx,
+        double cy,
+        double a,
+        double b,
+        double e1x,
+        double e1y,
+        double theta,
+        double start,
+        double sweep) {
+      // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
+      // and http://www.spaceroots.org/documents/ellipse/node22.html
+      // Maximum of 45 degrees per cubic Bezier segment
+      int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
+      double eta1 = start;
+      double cosTheta = Math.cos(theta);
+      double sinTheta = Math.sin(theta);
+      double cosEta1 = Math.cos(eta1);
+      double sinEta1 = Math.sin(eta1);
+      double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+      double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+      double anglePerSegment = sweep / numSegments;
+      for (int i = 0; i < numSegments; i++) {
+        double eta2 = eta1 + anglePerSegment;
+        double sinEta2 = Math.sin(eta2);
+        double cosEta2 = Math.cos(eta2);
+        double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
+        double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
+        double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+        double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+        double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+        double alpha =
+            Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+        double q1x = e1x + alpha * ep1x;
+        double q1y = e1y + alpha * ep1y;
+        double q2x = e2x - alpha * ep2x;
+        double q2y = e2y - alpha * ep2y;
+        // Adding this no-op call to workaround a proguard related issue.
+        p.rLineTo(0, 0);
+        p.cubicTo((float) q1x,
+            (float) q1y,
+            (float) q2x,
+            (float) q2y,
+            (float) e2x,
+            (float) e2y);
+        eta1 = eta2;
+        e1x = e2x;
+        e1y = e2y;
+        ep1x = ep2x;
+        ep1y = ep2y;
+      }
+    }
+  }
+
+  // Same code as PathParser above, but with exception handlers in the two methods.
+  public static class PathParserWithExceptionHandler {
+
+    private static void drawArc(Path p,
+        float x0,
+        float y0,
+        float x1,
+        float y1,
+        float a,
+        float b,
+        float theta,
+        boolean isMoreThanHalf,
+        boolean isPositiveArc) {
+      try {
+        /* Convert rotation angle from degrees to radians */
+        double thetaD = Math.toRadians(theta);
+        /* Pre-compute rotation matrix entries */
+        double cosTheta = Math.cos(thetaD);
+        double sinTheta = Math.sin(thetaD);
+        /* Transform (x0, y0) and (x1, y1) into unit space */
+        /* using (inverse) rotation, followed by (inverse) scale */
+        double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+        double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+        double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+        double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+        /* Compute differences and averages */
+        double dx = x0p - x1p;
+        double dy = y0p - y1p;
+        double xm = (x0p + x1p) / 2;
+        double ym = (y0p + y1p) / 2;
+        /* Solve for intersecting unit circles */
+        double dsq = dx * dx + dy * dy;
+        if (dsq == 0.0) {
+          Log.w(LOGTAG, " Points are coincident");
+          return; /* Points are coincident */
+        }
+        double disc = 1.0 / dsq - 1.0 / 4.0;
+        if (disc < 0.0) {
+          Log.w(LOGTAG, "Points are too far apart " + dsq);
+          float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+          drawArc(p, x0, y0, x1, y1, a * adjust,
+              b * adjust, theta, isMoreThanHalf, isPositiveArc);
+          return; /* Points are too far apart */
+        }
+        double s = Math.sqrt(disc);
+        double sdx = s * dx;
+        double sdy = s * dy;
+        double cx;
+        double cy;
+        if (isMoreThanHalf == isPositiveArc) {
+          cx = xm - sdy;
+          cy = ym + sdx;
+        } else {
+          cx = xm + sdy;
+          cy = ym - sdx;
+        }
+        double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+        double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+        double sweep = (eta1 - eta0);
+        if (isPositiveArc != (sweep >= 0)) {
+          if (sweep > 0) {
+            sweep -= 2 * Math.PI;
+          } else {
+            sweep += 2 * Math.PI;
+          }
+        }
+        cx *= a;
+        cy *= b;
+        double tcx = cx;
+        cx = cx * cosTheta - cy * sinTheta;
+        cy = tcx * sinTheta + cy * cosTheta;
+        arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+      } catch (Throwable t) {
+        // Ignore.
+      }
+    }
+
+    /**
+     * Converts an arc to cubic Bezier segments and records them in p.
+     *
+     * @param p     The target for the cubic Bezier segments
+     * @param cx    The x coordinate center of the ellipse
+     * @param cy    The y coordinate center of the ellipse
+     * @param a     The radius of the ellipse in the horizontal direction
+     * @param b     The radius of the ellipse in the vertical direction
+     * @param e1x   E(eta1) x coordinate of the starting point of the arc
+     * @param e1y   E(eta2) y coordinate of the starting point of the arc
+     * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
+     * @param start The start angle of the arc on the ellipse
+     * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+     */
+    private static void arcToBezier(Path p,
+        double cx,
+        double cy,
+        double a,
+        double b,
+        double e1x,
+        double e1y,
+        double theta,
+        double start,
+        double sweep) {
+      try {
+        // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
+        // and http://www.spaceroots.org/documents/ellipse/node22.html
+        // Maximum of 45 degrees per cubic Bezier segment
+        int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
+        double eta1 = start;
+        double cosTheta = Math.cos(theta);
+        double sinTheta = Math.sin(theta);
+        double cosEta1 = Math.cos(eta1);
+        double sinEta1 = Math.sin(eta1);
+        double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+        double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+        double anglePerSegment = sweep / numSegments;
+        for (int i = 0; i < numSegments; i++) {
+          double eta2 = eta1 + anglePerSegment;
+          double sinEta2 = Math.sin(eta2);
+          double cosEta2 = Math.cos(eta2);
+          double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
+          double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
+          double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+          double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+          double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+          double alpha =
+              Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+          double q1x = e1x + alpha * ep1x;
+          double q1y = e1y + alpha * ep1y;
+          double q2x = e2x - alpha * ep2x;
+          double q2y = e2y - alpha * ep2y;
+          // Adding this no-op call to workaround a proguard related issue.
+          p.rLineTo(0, 0);
+          p.cubicTo((float) q1x,
+              (float) q1y,
+              (float) q2x,
+              (float) q2y,
+              (float) e2x,
+              (float) e2y);
+          eta1 = eta2;
+          e1x = e2x;
+          e1y = e2y;
+          ep1x = ep2x;
+          ep1y = ep2y;
+        }
+      } catch (Throwable t) {
+        // Ignore.
+      }
+    }
+
+  }
+
+  // Reproduction from b/77496850.
+  public static class Reproduction {
+    public int test() {
+      int count = 0;
+      for (int i = 0; i < 1000; i++){
+        count += arcToBezier(1.0, 1.0, 2.0);
+      }
+      return count;
+    }
+
+    private static int arcToBezier(double a, double b, double sweep) {
+      int count = 0;
+
+      int numSegments = (int) sweep;
+
+      double cosTheta = 0.5;
+      double sinTheta = 0.5;
+      double cosEta1 = 0.5;
+      double sinEta1 = 0.5;
+      double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+      double anglePerSegment = sweep / numSegments;
+
+      for (int i = 0; i < numSegments; i++) {
+        count++;
+      }
+      if (numSegments != count) {
+        return 1;
+      }
+      return 0;
+    }
+
+    public static void main(String[] args) {
+      for (int i = 0; i < 100; i++) {
+        System.out.println(new Reproduction().test());
+      }
+    }
+  }
+
+  // Reproduction from b/77496850 with exception handler.
+  public static class ReproductionWithExceptionHandler {
+    public int test() {
+      int count = 0;
+      for (int i = 0; i < 1000; i++){
+        count += arcToBezier(1.0, 1.0, 2.0);
+      }
+      return count;
+    }
+
+    private static int arcToBezier(double a, double b, double sweep) {
+      try {
+        int count = 0;
+
+        int numSegments = (int) sweep;
+
+        double cosTheta = 0.5;
+        double sinTheta = 0.5;
+        double cosEta1 = 0.5;
+        double sinEta1 = 0.5;
+        double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+        double anglePerSegment = sweep / numSegments;
+
+        for (int i = 0; i < numSegments; i++) {
+          count++;
+        }
+        if (numSegments != count) {
+          return 1;
+        }
+        return 0;
+      } catch (Throwable t) {
+        return 1;
+      }
+    }
+
+    public static void main(String[] args) {
+      for (int i = 0; i < 100; i++) {
+        System.out.println(new Reproduction().test());
+      }
+    }
+  }
+
+  private int countInvokeDoubleIsNan(DexItemFactory factory, DexCode code) {
+    int count = 0;
+    DexMethod doubleIsNaN = factory.createMethod(
+        factory.createString("Ljava/lang/Double;"),
+        factory.createString("isNaN"),
+        factory.booleanDescriptor,
+        new DexString[]{factory.doubleDescriptor});
+    for (int i = 0; i < code.instructions.length; i++) {
+      if (code.instructions[i] instanceof InvokeStatic) {
+        InvokeStatic invoke = (InvokeStatic) code.instructions[i];
+        if (invoke.getMethod() == doubleIsNaN) {
+          count++;
+        }
+      }
+    }
+    return count;
+  }
+
+  private void checkPathParserMethods(AndroidApp app, Class testClass, int a, int b)
+      throws Exception {
+    DexInspector inspector = new DexInspector(app);
+    DexItemFactory factory = inspector.getFactory();
+    ClassSubject clazz = inspector.clazz(testClass);
+    MethodSubject drawArc = clazz.method(
+        "void",
+        "drawArc",
+        ImmutableList.of(
+            getClass().getCanonicalName() + "$Path",
+            "float", "float", "float", "float", "float", "float", "float", "boolean", "boolean"));
+    MethodSubject arcToBezier = clazz.method(
+        "void",
+        "arcToBezier",
+        ImmutableList.of(
+            getClass().getCanonicalName() + "$Path",
+            "double", "double", "double", "double", "double", "double",
+            "double", "double", "double"));
+    assertEquals(a, countInvokeDoubleIsNan(factory, drawArc.getMethod().getCode().asDexCode()));
+    assertEquals(b, countInvokeDoubleIsNan(factory, arcToBezier.getMethod().getCode().asDexCode()));
+  }
+
+  private void runTestPathParser(
+      Tool compiler, Class testClass, AndroidApiLevel apiLevel, int a, int b)
+      throws Exception {
+    AndroidApp app = readClasses(Path.class, Log.class, testClass);
+    if (compiler == Tool.D8) {
+      app = compileWithD8(app, o -> o.minApiLevel = apiLevel.getLevel());
+    } else {
+      assert compiler == Tool.R8;
+      app = compileWithR8(app, "-keep class * { *; }", o -> o.minApiLevel = apiLevel.getLevel());
+    }
+    checkPathParserMethods(app, testClass, a, b);
+  }
+
+  @Test
+  public void testSupportLibraryPathParser() throws Exception{
+    for (Tool tool : Tool.values()) {
+      runTestPathParser(tool, PathParser.class, AndroidApiLevel.K, 14, 1);
+      runTestPathParser(tool, PathParser.class, AndroidApiLevel.L, 0, 0);
+      runTestPathParser(tool, PathParserWithExceptionHandler.class, AndroidApiLevel.K, 14, 1);
+      runTestPathParser(tool, PathParserWithExceptionHandler.class, AndroidApiLevel.L, 0, 0);
+    }
+  }
+
+  private void runTestReproduction(
+      Tool compiler, Class testClass, AndroidApiLevel apiLevel, int expectedInvokeDoubleIsNanCount)
+      throws Exception {
+    AndroidApp app = readClasses(testClass);
+    if (compiler == Tool.D8) {
+      app = compileWithD8(app, o -> o.minApiLevel = apiLevel.getLevel());
+    } else {
+      assert compiler == Tool.R8;
+      app = compileWithR8(app, "-keep class * { *; }", o -> o.minApiLevel = apiLevel.getLevel());
+    }
+    DexInspector inspector = new DexInspector(app);
+    DexItemFactory factory = inspector.getFactory();
+    ClassSubject clazz = inspector.clazz(testClass);
+    MethodSubject arcToBezier = clazz.method(
+        "int", "arcToBezier", ImmutableList.of("double", "double", "double"));
+    assertEquals(
+      expectedInvokeDoubleIsNanCount,
+      countInvokeDoubleIsNan(factory, arcToBezier.getMethod().getCode().asDexCode()));
+  }
+
+  @Test
+  public void testReproduction() throws Exception{
+    for (Tool tool : Tool.values()) {
+      runTestReproduction(tool, Reproduction.class, AndroidApiLevel.K, tool == Tool.D8 ? 1 : 0);
+      runTestReproduction(tool, Reproduction.class, AndroidApiLevel.L, 0);
+      runTestReproduction(
+          tool, ReproductionWithExceptionHandler.class, AndroidApiLevel.K, tool == Tool.D8 ? 1 : 0);
+      runTestReproduction(tool, ReproductionWithExceptionHandler.class, AndroidApiLevel.L, 0);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index c176ff3..f484bc8 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.DiagnosticsChecker.checkDiagnostics;
 import static com.android.tools.r8.shaking.ProguardConfigurationSourceStrings.createConfigurationForTesting;
+import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -14,9 +16,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import static org.hamcrest.core.StringContains.containsString;
-
-import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -25,10 +24,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.position.TextPosition;
-import com.android.tools.r8.position.TextRange;
 import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
 import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
@@ -694,7 +689,7 @@
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     Path path = Paths.get(PACKAGE_OBFUSCATION_5);
     parser.parse(path);
-    checkDiagnostic(handler.warnings, path, 6, 1,
+    checkDiagnostics(handler.warnings, path, 6, 1,
         "repackageclasses", "overrides", "flattenpackagehierarchy");
     ProguardConfiguration config = parser.getConfig();
     assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
@@ -708,7 +703,7 @@
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     Path path = Paths.get(PACKAGE_OBFUSCATION_6);
     parser.parse(path);
-    checkDiagnostic(handler.warnings, path, 6, 1,
+    checkDiagnostics(handler.warnings, path, 6, 1,
         "repackageclasses", "overrides", "flattenpackagehierarchy");
     ProguardConfiguration config = parser.getConfig();
     assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
@@ -735,7 +730,7 @@
       parser.parse(path);
       fail("Expect to fail due to the lack of file name.");
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, path, 6, 14, "File name expected");
+      checkDiagnostics(handler.errors, path, 6, 14, "File name expected");
     }
   }
 
@@ -755,7 +750,7 @@
           .parse(path);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, path, 6, 10,"does-not-exist.flags");
+      checkDiagnostics(handler.errors, path, 6, 10,"does-not-exist.flags");
     }
   }
 
@@ -767,7 +762,7 @@
           .parse(path);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, path, 6,2, "does-not-exist.flags");
+      checkDiagnostics(handler.errors, path, 6,2, "does-not-exist.flags");
     }
   }
 
@@ -930,7 +925,7 @@
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     Path path = Paths.get(DONT_OPTIMIZE_OVERRIDES_PASSES);
     parser.parse(path);
-    checkDiagnostic(handler.warnings, path, 7, 1,
+    checkDiagnostics(handler.warnings, path, 7, 1,
         "Ignoring", "-optimizationpasses");
     ProguardConfiguration config = parser.getConfig();
     assertFalse(config.isOptimizing());
@@ -942,7 +937,7 @@
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     Path path = Paths.get(OPTIMIZATION_PASSES);
     parser.parse(path);
-    checkDiagnostic(handler.warnings, path, 5, 1,
+    checkDiagnostics(handler.warnings, path, 5, 1,
         "Ignoring", "-optimizationpasses");
     ProguardConfiguration config = parser.getConfig();
     assertTrue(config.isOptimizing());
@@ -957,7 +952,7 @@
       parser.parse(path);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, path, 6, 1, "Missing n");
+      checkDiagnostics(handler.errors, path, 6, 1, "Missing n");
     }
   }
 
@@ -970,8 +965,8 @@
       parser.parse(path);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, path, 5, 1, "Unsupported option",
-          "-skipnonpubliclibraryclasses");
+      checkDiagnostics(handler.errors, path, 5, 1,
+          "Unsupported option", "-skipnonpubliclibraryclasses");
     }
   }
 
@@ -1052,7 +1047,8 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 1, 1, "Unknown option", "-keepclassx");
+      checkDiagnostics(handler.errors, proguardConfig, 1, 1,
+          "Unknown option", "-keepclassx");
     }
   }
 
@@ -1325,7 +1321,7 @@
         parser.parse(createConfigurationForTesting(ImmutableList.of(option + " ,")));
         fail("Expect to fail due to the lack of path filter.");
       } catch (AbortException e) {
-        checkDiagnostic(handler.errors, null, 1, option.length() + 2, "Path filter expected");
+        checkDiagnostics(handler.errors, null, 1, option.length() + 2, "Path filter expected");
       }
     }
   }
@@ -1340,7 +1336,7 @@
         parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,,yyy")));
         fail("Expect to fail due to the lack of path filter.");
       } catch (AbortException e) {
-        checkDiagnostic(handler.errors, null, 1, option.length() + 6, "Path filter expected");
+        checkDiagnostics(handler.errors, null, 1, option.length() + 6, "Path filter expected");
       }
     }
   }
@@ -1355,7 +1351,7 @@
         parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,")));
         fail("Expect to fail due to the lack of path filter.");
       } catch (AbortException e) {
-        checkDiagnostic(handler.errors, null, 1, option.length() + 6, "Path filter expected");
+        checkDiagnostics(handler.errors, null, 1, option.length() + 6, "Path filter expected");
       }
     }
   }
@@ -1438,7 +1434,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 2, 13,
+      checkDiagnostics(handler.errors, proguardConfig, 2, 13,
           "Use of generics not allowed for java type");
     }
     verifyFailWithProguard6(proguardConfig, "Use of generics not allowed for java type");
@@ -1456,7 +1452,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 2, 13,
+      checkDiagnostics(handler.errors, proguardConfig, 2, 13,
           "Use of generics not allowed for java type");
     }
     verifyFailWithProguard6(proguardConfig, "Use of generics not allowed for java type");
@@ -1489,7 +1485,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(proguardConfig);
-    verifyParserEndsCleanly();
+    checkDiagnostics(handler.warnings, proguardConfig, 3, 7, "The field name \"id<<*>>\" is");
 
     verifyWithProguard6(proguardConfig);
   }
@@ -1508,7 +1504,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 4, 2,
+      checkDiagnostics(handler.errors, proguardConfig, 4, 2,
           "Wildcard", "<4>", "invalid");
     }
     verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (4,");
@@ -1526,7 +1522,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 2, 13,
+      checkDiagnostics(handler.errors, proguardConfig, 2, 13,
           "Wildcard", "<0>", "invalid");
     }
     verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (0,");
@@ -1544,7 +1540,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 3, 1,
+      checkDiagnostics(handler.errors, proguardConfig, 3, 1,
           "Wildcard", "<4>", "invalid");
     }
     verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (4,");
@@ -1562,7 +1558,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 3, 1,
+      checkDiagnostics(handler.errors, proguardConfig, 3, 1,
           "Wildcard", "<2>", "invalid");
     }
     verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (2,");
@@ -1584,7 +1580,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 6, 2,
+      checkDiagnostics(handler.errors, proguardConfig, 6, 2,
           "Wildcard", "<3>", "invalid");
     }
     verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (3,");
@@ -1602,7 +1598,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+      checkDiagnostics(handler.errors, proguardConfig, 1, 1,
           "Expecting", "'-keep'", "after", "'-if'");
     }
     verifyFailWithProguard6(proguardConfig, "Expecting '-keep' option after '-if' option");
@@ -1619,7 +1615,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+      checkDiagnostics(handler.errors, proguardConfig, 1, 1,
           "Expecting", "'-keep'", "after", "'-if'");
     }
     verifyFailWithProguard6(proguardConfig, "Expecting '-keep' option after '-if' option");
@@ -1649,7 +1645,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(proguardConfig);
-    checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+    checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
         "Ignoring", "-assumenoexternalsideeffects");
   }
 
@@ -1663,7 +1659,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(proguardConfig);
-    checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+    checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
         "Ignoring", "-assumenoescapingparameters");
   }
 
@@ -1687,7 +1683,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(proguardConfig);
-    checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+    checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
         "Ignoring", "-assumenoexternalreturnvalues");
   }
 
@@ -1721,7 +1717,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(proguardConfig);
-    checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+    checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
         "Ignoring", "-addconfigurationdebugging");
   }
 
@@ -1735,7 +1731,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(proguardConfig);
-    verifyParserEndsCleanly();
+    checkDiagnostics(handler.warnings, proguardConfig, 2, 5, "The field name \"<fields>\" is");
     verifyWithProguard(proguardConfig);
   }
 
@@ -1757,13 +1753,33 @@
     verifyWithProguard(proguardConfig);
   }
 
+  @Test
+  public void parse_regress110021323() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+      "-keepclassmembernames class A {",
+      "  <public methods>;",
+      "  <public fields>;",
+      "}"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    assertEquals(4, handler.warnings.size());
+    checkDiagnostics(handler.warnings, 0, proguardConfig, 2, 3, "The type \"<public\" is");
+    checkDiagnostics(handler.warnings, 1, proguardConfig, 2, 11, "The field name \"methods>\" is");
+    checkDiagnostics(handler.warnings, 2, proguardConfig, 3, 3, "The type \"<public\" is");
+    checkDiagnostics(handler.warnings, 3, proguardConfig, 3, 11, "The field name \"fields>\" is");
+
+    verifyWithProguard(proguardConfig);
+  }
+
   public void testNotSupported(String option) {
     try {
       reset();
       parser.parse(createConfigurationForTesting(ImmutableList.of(option)));
       fail("Expect to fail due to unsupported option.");
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, null, 1, 1, "Option " + option + " currently not supported");
+      checkDiagnostics(handler.errors, null, 1, 1, "Option " + option + " currently not supported");
     }
   }
 
@@ -1787,31 +1803,6 @@
     assertEquals(0, handler.errors.size());
   }
 
-  // TODO(sgjesse): Change to use DiagnosticsChecker.
-  private Diagnostic checkDiagnostic(List<Diagnostic> diagnostics, Path path, int lineStart,
-      int columnStart, String... messageParts) {
-    assertEquals(1, diagnostics.size());
-    Diagnostic diagnostic = diagnostics.get(0);
-    if (path != null) {
-      assertEquals(path, ((PathOrigin) diagnostic.getOrigin()).getPath());
-    } else {
-      assertSame(Origin.unknown(), diagnostic.getOrigin());
-    }
-    TextPosition position;
-    if (diagnostic.getPosition() instanceof TextRange) {
-      position = ((TextRange) diagnostic.getPosition()).getStart();
-    } else {
-      position = ((TextPosition) diagnostic.getPosition());
-    }
-    assertEquals(lineStart, position.getLine());
-    assertEquals(columnStart, position.getColumn());
-    for (String part : messageParts) {
-      assertTrue(diagnostic.getDiagnosticMessage() + " doesn't contain \"" + part + "\"",
-          diagnostic.getDiagnosticMessage().contains(part));
-    }
-    return diagnostic;
-  }
-
   private void verifyWithProguard(Path proguardConfig) throws Exception {
     if (isRunProguard()) {
       // Add a keep rule for the test class as Proguard will fail if the resulting output jar is
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
index 5845dd3..e543d46 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
@@ -86,18 +86,24 @@
         "  public int method();",
         "}"
     ), this::defaultMethodKept);
-    runTest(ImmutableList.of(
-        "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
-        "  <methods>;",
-        "}"
-    ), this::defaultMethodNotKept);
-    runTest(ImmutableList.of(
-        "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
-        "  <methods>;",
-        "}",
-        "-keep class " + TestClass.class.getCanonicalName() + "{",
-        "  public void useInterfaceMethod();",
-        "}"
-    ), this::defaultMethodAbstract);
+    runTest(
+        ImmutableList.of(
+            "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
+            "  <methods>;",
+            "}",
+            // Prevent InterfaceWithDefaultMethods from being merged into ClassImplementingInterface
+            "-keep class " + InterfaceWithDefaultMethods.class.getCanonicalName()),
+        this::defaultMethodNotKept);
+    runTest(
+        ImmutableList.of(
+            "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
+            "  <methods>;",
+            "}",
+            "-keep class " + TestClass.class.getCanonicalName() + "{",
+            "  public void useInterfaceMethod();",
+            "}",
+            // Prevent InterfaceWithDefaultMethods from being merged into ClassImplementingInterface
+            "-keep class " + InterfaceWithDefaultMethods.class.getCanonicalName()),
+        this::defaultMethodAbstract);
   }
 }
diff --git a/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt b/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt
index 136f72d..06b937e 100644
--- a/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt
+++ b/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt
@@ -11,6 +11,8 @@
 fun main(args: Array<String>) {
     testKotlinSequencesStateless(produceSequence(10))
     testKotlinSequencesStateful(5, 2, produceSequence(10))
+    testBigExtraMethod()
+    testBigExtraMethodReturningLambda()
 }
 
 data class Record(val foo: String, val good: Boolean)
@@ -38,12 +40,57 @@
     }
 }
 
-private fun produceSequence(size: Int): Sequence<String> {
+@Synchronized
+fun testBigExtraMethod() {
+    useRecord()
+    bigUserWithNotNullChecksAndTwoCalls(next()) { next() }
+    testBigExtraMethod2()
+    testBigExtraMethod3()
+}
+
+fun testBigExtraMethod2() {
+    bigUserWithNotNullChecksAndTwoCalls(next()) { next() }
+}
+
+fun testBigExtraMethod3() {
+    bigUserWithNotNullChecksAndTwoCalls(next()) { next() }
+}
+
+fun bigUserWithNotNullChecksAndTwoCalls(id: String, lambda: () -> String): String {
+    useRecord()
+    println("[A] logging call#$id returning ${lambda()}")
+    return "$id: ${lambda()}"
+}
+
+@Synchronized
+fun testBigExtraMethodReturningLambda() {
+    useRecord()
+    bigUserReturningLambda(next()) { next() } // Not used
+    testBigExtraMethodReturningLambda2()
+    testBigExtraMethodReturningLambda3()
+}
+
+fun testBigExtraMethodReturningLambda2() {
+    bigUserReturningLambda(next()) { next() } // Not used
+}
+
+fun testBigExtraMethodReturningLambda3() {
+    bigUserReturningLambda(next()) { next() } // Not used
+}
+
+fun bigUserReturningLambda(id: String, lambda: () -> String): () -> String {
+    useRecord()
+    println("[B] logging call#$id returning ${lambda()}")
+    println("[C] logging call#$id returning ${lambda()}")
+    return lambda
+}
+
+fun produceSequence(size: Int): Sequence<String> {
     var count = size
     return generateSequence { if (count-- > 0) next() else null }
 }
 
 // Need this to make sure testKotlinSequenceXXX is not processed
 // concurrently with invoke() on lambdas.
-fun useRecord() = useRecord2()
-fun useRecord2() = Record("", true)
+@Synchronized fun useRecord() = useRecord2()
+@Synchronized fun useRecord2() = Record("", true)
diff --git a/third_party/gmail/gmail_android_170604.16.tar.gz.sha1 b/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
index f57ba90..9d9985c 100644
--- a/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
+++ b/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
@@ -1 +1 @@
-161c569821a5c9b4cb8e99de764f3449191af084
\ No newline at end of file
+a6d49ef4fb2094672a6f6be039c971727cc9fd34
\ No newline at end of file
diff --git a/third_party/youtube/youtube.android_12.22.tar.gz.sha1 b/third_party/youtube/youtube.android_12.22.tar.gz.sha1
index 8f6813c..056ff59 100644
--- a/third_party/youtube/youtube.android_12.22.tar.gz.sha1
+++ b/third_party/youtube/youtube.android_12.22.tar.gz.sha1
@@ -1 +1 @@
-73c4880898d734064815d0426d8fe84ee6d075b4
\ No newline at end of file
+57b5c53a80ba010d1faef7da1b643f8c72b3e4e8
\ No newline at end of file
diff --git a/tools/build_sample_apk.py b/tools/build_sample_apk.py
index b31ade4..90e34ed 100755
--- a/tools/build_sample_apk.py
+++ b/tools/build_sample_apk.py
@@ -13,7 +13,9 @@
 import shutil
 import subprocess
 import sys
+import time
 import utils
+import uuid
 
 ANDROID_JAR = 'third_party/android_jar/lib-v{api}/android.jar'
 DEFAULT_AAPT = 'aapt' # Assume in path.
@@ -22,6 +24,9 @@
 DEFAULT_JAVAC = 'javac'
 SRC_LOCATION = 'src/com/android/tools/r8/sample/{app}/*.java'
 DEFAULT_KEYSTORE = os.path.join(os.getenv('HOME'), '.android', 'debug.keystore')
+PACKAGE_PREFIX = 'com.android.tools.r8.sample'
+STANDARD_ACTIVITY = "R8Activity"
+BENCHMARK_ITERATIONS = 100
 
 SAMPLE_APKS = [
     'simple',
@@ -46,6 +51,12 @@
   result.add_option('--install',
                     help='Install the app (including featuresplit)',
                     default=False, action='store_true')
+  result.add_option('--benchmark',
+                    help='Benchmark the app on the phone with specialized markers',
+                    default=False, action='store_true')
+  result.add_option('--benchmark-output-dir',
+                    help='Store benchmark results here.',
+                    default=None)
   result.add_option('--app',
                     help='Which app to build',
                     default='simple',
@@ -86,6 +97,13 @@
 def get_split_path(app, split):
   return os.path.join(get_bin_path(app), split, 'classes.dex')
 
+def get_package_name(app):
+  return '%s.%s' % (PACKAGE_PREFIX, app)
+
+def get_qualified_activity(app):
+  # The activity specified to adb start is PACKAGE_NAME/.ACTIVITY
+  return '%s/.%s' % (get_package_name(app), STANDARD_ACTIVITY)
+
 def run_aapt_pack(aapt, api, app):
   with utils.ChangedWorkingDirectory(get_sample_dir(app)):
     args = ['package',
@@ -169,6 +187,71 @@
           dex]
   run_aapt(aapt, args)
 
+def kill(app):
+  args = ['shell', 'am', 'force-stop', get_package_name(app)]
+  run_adb(args)
+
+def start_logcat():
+  return subprocess.Popen(['adb', 'logcat'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+def start(app):
+  args = ['shell', 'am', 'start', '-n', get_qualified_activity(app)]
+  run_adb(args)
+
+def clear_logcat():
+  args = ['logcat', '-c']
+  run_adb(args)
+
+def stop_logcat(popen):
+  popen.terminate()
+  lines = []
+  for l in popen.stdout:
+    if 'System.out' in l:
+      lines.append(l)
+  return lines
+
+def store_or_print_benchmarks(lines, output):
+  single_runs = []
+  total_time = None
+  # We assume that the individual runs are prefixed with 'Took: ' and the total time is
+  # prefixed with 'Total: '. The logcat lines looks like:
+  # 06-28 12:22:00.991 13698 13698 I System.out: Took: 61614
+  for l in lines:
+    if 'Took: ' in l:
+      timing = l.split('Took: ')[1]
+      single_runs.append(timing)
+    if 'Total: ' in l:
+      timing = l.split('Total: ')[1]
+      total_time = timing
+  assert len(single_runs) > 0
+  assert total_time
+  if not output:
+    print 'Individual timings: \n%s' % ''.join(single_runs)
+    print 'Total time: \n%s' % total_time
+    return
+
+  output_dir = os.path.join(output, str(uuid.uuid4()))
+  os.makedirs(output_dir)
+  single_run_file = os.path.join(output_dir, 'single_runs')
+  with open(single_run_file, 'w') as f:
+    f.writelines(single_runs)
+  total_file = os.path.join(output_dir, 'total')
+  with open(total_file, 'w') as f:
+    f.write(total_time)
+  print 'Result stored in %s and %s' % (single_run_file, total_file)
+
+def benchmark(app, output_dir):
+  # Ensure app is not running
+  kill(app)
+  clear_logcat()
+  logcat = start_logcat()
+  start(app)
+  # We could do better here by continiously parsing the logcat for a marker, but
+  # this works nicely with the current setup.
+  time.sleep(3)
+  kill(app)
+  store_or_print_benchmarks(stop_logcat(logcat), output_dir)
+
 def Main():
   (options, args) = parse_options()
   apks = []
@@ -202,7 +285,9 @@
   print('Generated apks available at: %s' % ' '.join(apks))
   if options.install:
     adb_install(apks)
-
+  if options.benchmark:
+    for _ in range(BENCHMARK_ITERATIONS):
+      benchmark(options.app, options.benchmark_output_dir)
 
 if __name__ == '__main__':
   sys.exit(Main())
diff --git a/tools/run_bootstrap_benchmark.py b/tools/run_bootstrap_benchmark.py
index 8230c71..bfc62e6 100755
--- a/tools/run_bootstrap_benchmark.py
+++ b/tools/run_bootstrap_benchmark.py
@@ -49,7 +49,7 @@
     print "BootstrapR8Dex(CodeSize):", os.path.getsize(d8_r8_output)
 
     dex(PINNED_PGR8_JAR, d8_pg_output)
-    print "BootstrapPG(CodeSize):", os.path.getsize(PINNED_PGR8_JAR)
-    print "BootstrapPGDex(CodeSize):", os.path.getsize(d8_pg_output)
+    print "BootstrapR8PG(CodeSize):", os.path.getsize(PINNED_PGR8_JAR)
+    print "BootstrapR8PGDex(CodeSize):", os.path.getsize(d8_pg_output)
 
-  sys.exit(0)
\ No newline at end of file
+  sys.exit(0)