Merge "Temporarily disable -adaptresourcefilecontents in youtube and gmail"
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/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/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/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/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..77a369d 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,7 @@
   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 cannot be resolved. */
   private final Set<DexMethod> brokenSuperInvokes = Sets.newIdentityHashSet();
   /**
    * This map keeps a view of all virtual methods that are reachable from virtual invokes. A method
@@ -1520,7 +1520,7 @@
      * 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 cannot be resolved. */
     public final SortedSet<DexMethod> brokenSuperInvokes;
     /**
      * Set of all items that have to be kept independent of whether they are used.
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/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/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())