Merge "Print retraced stack-trace on test-error"
diff --git a/build.gradle b/build.gradle
index 8ff95c8..93d7e1b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -678,7 +678,6 @@
     baseName = "r8tests"
     from sourceSets.test.output
     if (!project.hasProperty('exclude_deps')) {
-        relocate('com.google.common', 'com.android.tools.r8.com.google.common')
         relocate('org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm')
     }
 }
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index a55033a..5eb0151 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -25,7 +25,7 @@
 @Keep
 public class GenerateMainDexListCommand extends BaseCommand {
 
-  private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
+  private final List<ProguardConfigurationRule> mainDexKeepRules;
   private final StringConsumer mainDexListConsumer;
   private final GraphConsumer mainDexKeptGraphConsumer;
   private final DexItemFactory factory;
@@ -108,7 +108,7 @@
         return new GenerateMainDexListCommand(isPrintHelp(), isPrintVersion());
       }
 
-      ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
+      List<ProguardConfigurationRule> mainDexKeepRules;
       if (this.mainDexRules.isEmpty()) {
         mainDexKeepRules = ImmutableList.of();
       } else {
@@ -199,7 +199,7 @@
   private GenerateMainDexListCommand(
       DexItemFactory factory,
       AndroidApp inputApp,
-      ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
+      List<ProguardConfigurationRule> mainDexKeepRules,
       StringConsumer mainDexListConsumer,
       GraphConsumer mainDexKeptGraphConsumer,
       Reporter reporter) {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 39630f3..cca17bf 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -357,7 +357,7 @@
     private R8Command makeR8Command() {
       Reporter reporter = getReporter();
       DexItemFactory factory = new DexItemFactory();
-      ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
+      List<ProguardConfigurationRule> mainDexKeepRules;
       if (this.mainDexRules.isEmpty()) {
         mainDexKeepRules = ImmutableList.of();
       } else {
@@ -510,7 +510,7 @@
 
   static final String USAGE_MESSAGE = R8CommandParser.USAGE_MESSAGE;
 
-  private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
+  private final List<ProguardConfigurationRule> mainDexKeepRules;
   private final ProguardConfiguration proguardConfiguration;
   private final boolean enableTreeShaking;
   private final boolean enableMinification;
@@ -571,7 +571,7 @@
   private R8Command(
       AndroidApp inputApp,
       ProgramConsumer programConsumer,
-      ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
+      List<ProguardConfigurationRule> mainDexKeepRules,
       StringConsumer mainDexListConsumer,
       ProguardConfiguration proguardConfiguration,
       CompilationMode mode,
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index fd04436..a45064e 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -57,7 +57,7 @@
     return Collections.unmodifiableSet(missingClasses);
   }
 
-  public ImmutableSet<DexType> subtypes(DexType type) {
+  public Set<DexType> subtypes(DexType type) {
     assert type.isClassType();
     ImmutableSet<DexType> subtypes = subtypeMap.get(type);
     return subtypes == null ? ImmutableSet.of() : subtypes;
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
index 07e9440..8eb786a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableMap;
+import java.util.Map;
 import java.util.SortedSet;
 import java.util.TreeSet;
 
@@ -17,7 +18,7 @@
   public final DexString sourceFile;
   public final boolean prologueEnd;
   public final boolean epilogueBegin;
-  public final ImmutableMap<Integer, DebugLocalInfo> locals;
+  public final Map<Integer, DebugLocalInfo> locals;
   public final DexMethod method;
   public final Position callerPosition;
 
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 99833f4..1dcd217 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
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
@@ -1297,10 +1298,13 @@
   }
 
   public static BasicBlock createRethrowBlock(
-      IRCode code, Position position, TypeLatticeElement guardTypeLattice) {
+      IRCode code, Position position, DexType guard, AppInfo appInfo, InternalOptions options) {
+    TypeLatticeElement guardTypeLattice = TypeLatticeElement.fromDexType(guard, false, appInfo);
     BasicBlock block = new BasicBlock();
     MoveException moveException = new MoveException(
-        new Value(code.valueNumberGenerator.next(), guardTypeLattice, null));
+        new Value(code.valueNumberGenerator.next(), guardTypeLattice, null),
+        guard,
+        options);
     moveException.setPosition(position);
     Throw throwInstruction = new Throw(moveException.outValue);
     throwInstruction.setPosition(position);
@@ -1518,7 +1522,10 @@
    * Clone catch successors from `fromBlock` into this block.
    */
   public void copyCatchHandlers(
-      IRCode code, ListIterator<BasicBlock> blockIterator, BasicBlock fromBlock) {
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      BasicBlock fromBlock,
+      InternalOptions options) {
     if (catchHandlers != null && catchHandlers.hasCatchAll()) {
       return;
     }
@@ -1538,7 +1545,8 @@
       catchSuccessor.splitCriticalExceptionEdges(
           code.getHighestBlockNumber() + 1,
           code.valueNumberGenerator,
-          blockIterator::add);
+          blockIterator::add,
+          options);
     }
   }
 
@@ -1558,16 +1566,19 @@
   public int splitCriticalExceptionEdges(
       int nextBlockNumber,
       ValueNumberGenerator valueNumberGenerator,
-      Consumer<BasicBlock> onNewBlock) {
+      Consumer<BasicBlock> onNewBlock,
+      InternalOptions options) {
     List<BasicBlock> predecessors = getMutablePredecessors();
     boolean hasMoveException = entry().isMoveException();
     TypeLatticeElement exceptionTypeLattice = null;
+    DexType exceptionType = null;
     MoveException move = null;
     Position position = entry().getPosition();
     if (hasMoveException) {
       // Remove the move-exception instruction.
       move = entry().asMoveException();
       exceptionTypeLattice = move.outValue().getTypeLattice();
+      exceptionType = move.getExceptionType();
       assert move.getDebugValues().isEmpty();
       getInstructions().remove(0);
     }
@@ -1588,7 +1599,7 @@
             exceptionTypeLattice,
             move.getLocalInfo());
         values.add(value);
-        MoveException newMove = new MoveException(value);
+        MoveException newMove = new MoveException(value, exceptionType, options);
         newBlock.add(newMove);
         newMove.setPosition(position);
       }
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 af2dae6..1f9c5e6 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
@@ -278,7 +278,7 @@
         } else {
           nextBlock = null;
         }
-        currentBlock.copyCatchHandlers(code, blocksIterator, invokeBlock);
+        currentBlock.copyCatchHandlers(code, blocksIterator, invokeBlock, code.options);
         if (nextBlock != null) {
           BasicBlock b = blocksIterator.next();
           assert b == nextBlock;
@@ -311,7 +311,7 @@
       if (inlinedBlock.hasCatchHandlers()) {
         // The block already has catch handlers, so it has only one throwing instruction, and no
         // splitting is required.
-        inlinedBlock.copyCatchHandlers(code, blocksIterator, invokeBlock);
+        inlinedBlock.copyCatchHandlers(code, blocksIterator, invokeBlock, code.options);
       } else {
         // The block does not have catch handlers, so it can have several throwing instructions.
         // Therefore the block must be split after each throwing instruction, and the catch
@@ -427,7 +427,7 @@
     BasicBlock inlineEntry = inlinee.blocks.getFirst();
 
     BasicBlock inlineExit = null;
-    ImmutableList<BasicBlock> normalExits = inlinee.computeNormalExitBlocks();
+    List<BasicBlock> normalExits = inlinee.computeNormalExitBlocks();
     if (!normalExits.isEmpty()) {
       // Ensure and locate the single return instruction of the inlinee.
       InstructionListIterator inlineeIterator =
@@ -516,9 +516,7 @@
   }
 
   private InstructionListIterator ensureSingleReturnInstruction(
-      AppInfo appInfo,
-      IRCode code,
-      ImmutableList<BasicBlock> normalExits) {
+      AppInfo appInfo, IRCode code, List<BasicBlock> normalExits) {
     if (normalExits.size() == 1) {
       InstructionListIterator it = normalExits.get(0).listIterator();
       it.nextUntil(Instruction::isReturn);
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 5644ed7..f961b80 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
@@ -711,7 +711,7 @@
     return new IRCodeInstructionsIterator(this);
   }
 
-  public ImmutableList<BasicBlock> computeNormalExitBlocks() {
+  public List<BasicBlock> computeNormalExitBlocks() {
     ImmutableList.Builder<BasicBlock> builder = ImmutableList.builder();
     for (BasicBlock block : blocks) {
       if (block.exit().isReturn()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 7cdcd76..fae3557 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -7,21 +7,22 @@
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import com.android.tools.r8.utils.InternalOptions;
 
 public class MoveException extends Instruction {
+  private final DexType exceptionType;
+  private final InternalOptions options;
 
-  public MoveException(Value dest) {
+  public MoveException(Value dest, DexType exceptionType, InternalOptions options) {
     super(dest);
+    this.exceptionType = exceptionType;
+    this.options = options;
     dest.markNeverNull();
   }
 
@@ -48,7 +49,13 @@
 
   @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
-    return other.isMoveException();
+    if (!other.isMoveException()) {
+      return false;
+    }
+    if (options.canHaveExceptionTypeBug()) {
+      return other.asMoveException().exceptionType == exceptionType;
+    }
+    return true;
   }
 
   @Override
@@ -88,34 +95,17 @@
     return true;
   }
 
-  public static Set<DexType> collectExceptionTypes(
-      BasicBlock currentBlock, DexItemFactory dexItemFactory) {
-    Set<DexType> exceptionTypes = new HashSet<>(currentBlock.getPredecessors().size());
-    for (BasicBlock block : currentBlock.getPredecessors()) {
-      int size = block.getCatchHandlers().size();
-      List<BasicBlock> targets = block.getCatchHandlers().getAllTargets();
-      List<DexType> guards = block.getCatchHandlers().getGuards();
-      for (int i = 0; i < size; i++) {
-        if (targets.get(i) == currentBlock) {
-          DexType guard = guards.get(i);
-          exceptionTypes.add(
-              guard == DexItemFactory.catchAllType
-                  ? dexItemFactory.throwableType
-                  : guard);
-        }
-      }
-    }
-    return exceptionTypes;
-  }
-
   @Override
   public DexType computeVerificationType(TypeVerificationHelper helper) {
-    return helper.join(collectExceptionTypes(getBlock(), helper.getFactory()));
+    return exceptionType;
   }
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    Set<DexType> exceptionTypes = collectExceptionTypes(getBlock(), appInfo.dexItemFactory);
-    return TypeLatticeElement.joinTypes(exceptionTypes, false, appInfo);
+    return TypeLatticeElement.fromDexType(exceptionType, false, appInfo);
+  }
+
+  public DexType getExceptionType() {
+    return exceptionType;
   }
 }
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 007cac1..835399c 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
@@ -101,6 +101,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
@@ -116,7 +117,6 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.IdentityHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
@@ -193,11 +193,14 @@
   }
 
   private static class MoveExceptionWorklistItem extends WorklistItem {
+    private final DexType guard;
     private final int sourceOffset;
     private final int targetOffset;
 
-    private MoveExceptionWorklistItem(BasicBlock block, int sourceOffset, int targetOffset) {
+    private MoveExceptionWorklistItem(
+        BasicBlock block, DexType guard, int sourceOffset, int targetOffset) {
       super(block, -1);
+      this.guard = guard;
       this.sourceOffset = sourceOffset;
       this.targetOffset = targetOffset;
     }
@@ -707,12 +710,9 @@
     // construction and prior to building the IR.
     for (BlockInfo info : targets.values()) {
       if (info != null && info.block == block) {
-        assert info.predecessorCount() == block.getPredecessors().size();
+        assert info.predecessorCount() == nonSplitPredecessorCount(block);
         assert info.normalSuccessors.size() == block.getNormalSuccessors().size();
-        if (block.hasCatchHandlers()) {
-          assert info.exceptionalSuccessors.size()
-              == block.getCatchHandlers().getUniqueTargets().size();
-        } else {
+        if (!block.hasCatchHandlers()) {
           assert !block.canThrow()
               || info.exceptionalSuccessors.isEmpty()
               || (info.exceptionalSuccessors.size() == 1
@@ -726,6 +726,38 @@
     return true;
   }
 
+  private int nonSplitPredecessorCount(BasicBlock block) {
+    Set<BasicBlock> set = Sets.newIdentityHashSet();
+    for (BasicBlock predecessor : block.getPredecessors()) {
+      if (offsets.containsKey(predecessor)) {
+        set.add(predecessor);
+      } else {
+        assert predecessor.getSuccessors().size() == 1;
+        assert predecessor.getPredecessors().size() == 1;
+        assert trivialGotoBlockPotentiallyWithMoveException(predecessor);
+        // Combine the exceptional edges to just one, for normal edges that have been split
+        // record them separately. That means that we are checking that there are the expected
+        // number of normal edges and some number of exceptional edges (which we count as one edge).
+        if (predecessor.getPredecessors().get(0).hasCatchSuccessor(predecessor)) {
+          set.add(predecessor.getPredecessors().get(0));
+        } else {
+          set.add(predecessor);
+        }
+      }
+    }
+    return set.size();
+  }
+
+  // Check that all instructions are either move-exception, goto or debug instructions.
+  private boolean trivialGotoBlockPotentiallyWithMoveException(BasicBlock block) {
+    for (Instruction instruction : block.getInstructions()) {
+      assert instruction.isMoveException()
+          || instruction.isGoto()
+          || instruction.isDebugInstruction();
+    }
+    return true;
+  }
+
   private void processWorklist() {
     for (WorklistItem item = ssaWorklist.poll(); item != null; item = ssaWorklist.poll()) {
       if (item.block.isFilled()) {
@@ -782,10 +814,10 @@
     int moveExceptionDest = source.getMoveExceptionRegister(targetIndex);
     Position position = source.getCanonicalDebugPositionAtOffset(moveExceptionItem.targetOffset);
     if (moveExceptionDest >= 0) {
-      Set<DexType> exceptionTypes = MoveException.collectExceptionTypes(currentBlock, getFactory());
-      TypeLatticeElement typeLattice = TypeLatticeElement.joinTypes(exceptionTypes, false, appInfo);
+      TypeLatticeElement typeLattice =
+          TypeLatticeElement.fromDexType(moveExceptionItem.guard, false, appInfo);
       Value out = writeRegister(moveExceptionDest, typeLattice, ThrowingInfo.NO_THROW, null);
-      MoveException moveException = new MoveException(out);
+      MoveException moveException = new MoveException(out, moveExceptionItem.guard, options);
       moveException.setPosition(position);
       currentBlock.add(moveException);
     }
@@ -2196,21 +2228,22 @@
         assert !throwingInstructionInCurrentBlock;
         throwingInstructionInCurrentBlock = true;
         List<BasicBlock> targets = new ArrayList<>(catchHandlers.getAllTargets().size());
-        // 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, currentInstructionOffset, targetOffset));
-          }
+        Set<BasicBlock> moveExceptionTargets = Sets.newIdentityHashSet();
+        catchHandlers.forEach((type, targetOffset) -> {
+          DexType exceptionType = type == options.itemFactory.catchAllType
+              ? options.itemFactory.throwableType
+              : type;
+          BasicBlock header = new BasicBlock();
+          header.incrementUnfilledPredecessorCount();
+          ssaWorklist.add(
+              new MoveExceptionWorklistItem(
+                  header, exceptionType, currentInstructionOffset, targetOffset));
           targets.add(header);
-        }
+          BasicBlock target = getTarget(targetOffset);
+          if (!moveExceptionTargets.add(target)) {
+            target.incrementUnfilledPredecessorCount();
+          }
+        });
         currentBlock.linkCatchSuccessors(catchHandlers.getGuards(), targets);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index b92d772..5814486 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -332,6 +332,6 @@
     BasicBlock currentBlock = newInstance.getBlock();
     BasicBlock nextBlock = instructions.split(code, blocks);
     assert !instructions.hasNext();
-    nextBlock.copyCatchHandlers(code, blocks, currentBlock);
+    nextBlock.copyCatchHandlers(code, blocks, currentBlock, code.options);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
index 293065e..2be7320 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
@@ -394,7 +394,7 @@
       }
       // Copy catch handlers after all blocks are split.
       for (BasicBlock newBlock : newBlocks) {
-        newBlock.copyCatchHandlers(code, blocks, currentBlock);
+        newBlock.copyCatchHandlers(code, blocks, currentBlock, code.options);
       }
     }
 
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 0349327..5c1fae1 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
@@ -321,7 +321,9 @@
       BasicBlock rethrowBlock = BasicBlock.createRethrowBlock(
           code,
           lastSelfRecursiveCall.getPosition(),
-          TypeLatticeElement.fromDexType(guard, true, appInfo));
+          guard,
+          appInfo,
+          options);
       code.blocks.add(rethrowBlock);
       // Add catch handler to the block containing the last recursive call.
       newBlock.addCatchHandler(rethrowBlock, guard);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index fc9f368..c46504c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import java.util.ArrayList;
@@ -46,7 +45,7 @@
       IRCode code, RegisterAllocator allocator, int overhead) {
     Collection<BasicBlock> blocks = code.blocks;
     BasicBlock normalExit = null;
-    ImmutableList<BasicBlock> normalExits = code.computeNormalExitBlocks();
+    List<BasicBlock> normalExits = code.computeNormalExitBlocks();
     if (normalExits.size() > 1) {
       normalExit = new BasicBlock();
       normalExit.getMutablePredecessors().addAll(normalExits);
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index c1afba9..9cdb95c 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.utils.BiMapContainer;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
@@ -96,7 +97,7 @@
   }
 
   private final ImmutableMap<String, ClassNamingForNameMapper> classNameMappings;
-  private ImmutableBiMap<String, String> nameMapping;
+  private BiMapContainer<String, String> nameMapping;
 
   private final Map<Signature, Signature> signatureMap = new HashMap<>();
 
@@ -195,13 +196,14 @@
     }
   }
 
-  public BiMap<String, String> getObfuscatedToOriginalMapping() {
+  public BiMapContainer<String, String> getObfuscatedToOriginalMapping() {
     if (nameMapping == null) {
       ImmutableBiMap.Builder<String, String> builder = ImmutableBiMap.builder();
       for (String name : classNameMappings.keySet()) {
         builder.put(name, classNameMappings.get(name).originalName);
       }
-      nameMapping = builder.build();
+      BiMap<String, String> classNameMappings = builder.build();
+      nameMapping = new BiMapContainer<>(classNameMappings, classNameMappings.inverse());
     }
     return nameMapping;
   }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 1f0e3de..ad084e4 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -33,7 +33,6 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -41,6 +40,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -60,8 +60,8 @@
 
   private final Map<DexType, DexString> renaming = Maps.newIdentityHashMap();
   private final Map<String, Namespace> states = new HashMap<>();
-  private final ImmutableList<String> packageDictionary;
-  private final ImmutableList<String> classDictionary;
+  private final List<String> packageDictionary;
+  private final List<String> classDictionary;
   private final boolean keepInnerClassStructure;
 
   private final Set<DexType> noObfuscationTypes;
diff --git a/src/main/java/com/android/tools/r8/naming/ClassRenamingMapper.java b/src/main/java/com/android/tools/r8/naming/ClassRenamingMapper.java
index 71cbc4f..8609fff 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassRenamingMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassRenamingMapper.java
@@ -4,10 +4,11 @@
 
 package com.android.tools.r8.naming;
 
-import com.google.common.collect.BiMap;
+import com.android.tools.r8.utils.BiMapContainer;
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
+import java.util.Map;
 
 /**
  * Provides a translation between class names based on a source and target proguard map.
@@ -28,10 +29,11 @@
     ImmutableBiMap.Builder<String, String> translationBuilder = ImmutableBiMap.builder();
     ImmutableSet.Builder<String> newClasses = ImmutableSet.builder();
 
-    BiMap<String, String> sourceObfuscatedToOriginal = originalMap.getObfuscatedToOriginalMapping();
-    BiMap<String, String> sourceOriginalToObfuscated = sourceObfuscatedToOriginal.inverse();
-    BiMap<String, String> targetObfuscatedToOriginal = targetMap.getObfuscatedToOriginalMapping();
-    BiMap<String, String> targetOriginalToObfuscated = targetObfuscatedToOriginal.inverse();
+    Map<String, String> sourceOriginalToObfuscated =
+        originalMap.getObfuscatedToOriginalMapping().inverse;
+
+    BiMapContainer<String, String> targetMapping = targetMap.getObfuscatedToOriginalMapping();
+    Map<String, String> targetOriginalToObfuscated = targetMapping.inverse;
 
     for (String originalName : sourceOriginalToObfuscated.keySet()) {
       String sourceObfuscatedName = sourceOriginalToObfuscated.get(originalName);
@@ -44,8 +46,8 @@
     }
 
     ImmutableBiMap<String, String> translation = translationBuilder.build();
-    ImmutableSet<String> unusedNames = ImmutableSet
-        .copyOf(Sets.difference(targetObfuscatedToOriginal.keySet(), translation.values()));
+    ImmutableSet<String> unusedNames =
+        ImmutableSet.copyOf(Sets.difference(targetMapping.original.keySet(), translation.values()));
 
     return new ClassRenamingMapper(translation, newClasses.build(), unusedNames);
   }
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
index 312991e..7f28744 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
@@ -9,8 +9,8 @@
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableList;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
 
@@ -19,7 +19,7 @@
   protected final AppInfoWithLiveness appInfo;
   protected final RootSet rootSet;
   protected final InternalOptions options;
-  protected final ImmutableList<String> dictionary;
+  protected final List<String> dictionary;
 
   protected final Map<MemberType, DexString> renaming = new IdentityHashMap<>();
   protected final Map<DexType, NamingState<StateType, ?>> states = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/naming/NamingState.java b/src/main/java/com/android/tools/r8/naming/NamingState.java
index 4beff54..999579a 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingState.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.HashBasedTable;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Table;
@@ -24,13 +23,13 @@
   private final NamingState<ProtoType, KeyType> parent;
   private final Map<KeyType, InternalState> usedNames = new HashMap<>();
   private final DexItemFactory itemFactory;
-  private final ImmutableList<String> dictionary;
+  private final List<String> dictionary;
   private final Function<ProtoType, KeyType> keyTransform;
   private final boolean useUniqueMemberNames;
 
   static <S, T extends CachedHashValueDexItem> NamingState<T, S> createRoot(
       DexItemFactory itemFactory,
-      ImmutableList<String> dictionary,
+      List<String> dictionary,
       Function<T, S> keyTransform,
       boolean useUniqueMemberNames) {
     return new NamingState<>(null, itemFactory, dictionary, keyTransform, useUniqueMemberNames);
@@ -39,7 +38,7 @@
   private NamingState(
       NamingState<ProtoType, KeyType> parent,
       DexItemFactory itemFactory,
-      ImmutableList<String> dictionary,
+      List<String> dictionary,
       Function<ProtoType, KeyType> keyTransform,
       boolean useUniqueMemberNames) {
     this.parent = parent;
diff --git a/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java b/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java
index c67af00..9badf35 100644
--- a/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java
+++ b/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java
@@ -7,6 +7,7 @@
 import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.List;
 
 /**
  * Implements class path filtering as per
@@ -21,9 +22,9 @@
   private final Path path;
   private final ImmutableList<String> pattern;
 
-  public FilteredClassPath(Path path, ImmutableList<String> pattern) {
+  public FilteredClassPath(Path path, List<String> pattern) {
     this.path = path;
-    this.pattern = pattern;
+    this.pattern = ImmutableList.copyOf(pattern);
   }
 
   private FilteredClassPath(Path path) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index c153fe3..0f511d4 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -466,11 +466,11 @@
     return dexItemFactory;
   }
 
-  public ImmutableList<FilteredClassPath> getInjars() {
+  public List<FilteredClassPath> getInjars() {
     return injars;
   }
 
-  public ImmutableList<FilteredClassPath> getLibraryjars() {
+  public List<FilteredClassPath> getLibraryjars() {
     return libraryjars;
   }
 
@@ -554,7 +554,7 @@
     return dontNotePatterns;
   }
 
-  public ImmutableList<ProguardConfigurationRule> getRules() {
+  public List<ProguardConfigurationRule> getRules() {
     return rules;
   }
 
@@ -562,15 +562,15 @@
     return overloadAggressively && !useUniqueClassMemberNames;
   }
 
-  public ImmutableList<String> getObfuscationDictionary() {
+  public List<String> getObfuscationDictionary() {
     return obfuscationDictionary;
   }
 
-  public ImmutableList<String> getClassObfuscationDictionary() {
+  public List<String> getClassObfuscationDictionary() {
     return classObfuscationDictionary;
   }
 
-  public ImmutableList<String> getPackageObfuscationDictionary() {
+  public List<String> getPackageObfuscationDictionary() {
     return packageObfuscationDictionary;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/BiMapContainer.java b/src/main/java/com/android/tools/r8/utils/BiMapContainer.java
new file mode 100644
index 0000000..e8f4f2f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/BiMapContainer.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2019, 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.utils;
+
+import java.util.Map;
+
+/**
+ * BiMapContainer is a utility class that can be used when testing BiMaps in R8, reducing the need
+ * to relocate com.google.common in our tests.
+ *
+ * @param <K> The type of keys
+ * @param <V> The type of values
+ */
+public class BiMapContainer<K, V> {
+
+  public final Map<K, V> original;
+  public final Map<K, V> inverse;
+
+  public BiMapContainer(Map<K, V> original, Map<K, V> inverse) {
+    this.original = original;
+    this.inverse = inverse;
+  }
+}
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 c0c6e59..1e39494 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -261,7 +261,7 @@
   public boolean debug = false;
   public final TestingOptions testing = new TestingOptions();
 
-  public ImmutableList<ProguardConfigurationRule> mainDexKeepRules = ImmutableList.of();
+  public List<ProguardConfigurationRule> mainDexKeepRules = ImmutableList.of();
   public boolean minimalMainDex;
   /**
    * Enable usage of InheritanceClassInDexDistributor for multidex legacy builds.
@@ -875,4 +875,17 @@
     // Marshmallow and Nougat arm64 devices and they do not have the bug.
     return minApiLevel < AndroidApiLevel.M.getLevel();
   }
+
+  // The Art VM for Android N through P has a bug in the JIT that means that if the same
+  // exception block with a move-exception instruction is targeted with more than one type
+  // of exception the JIT will incorrectly assume that the exception object has one of these
+  // types and will optimize based on that one type instead of taking all the types into account.
+  //
+  // In order to workaround that, we always generate distinct move-exception instructions for
+  // distinct dex types.
+  //
+  // See b/120164595.
+  public boolean canHaveExceptionTypeBug() {
+    return minApiLevel < AndroidApiLevel.Q.getLevel();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index e6a1fcd..fb61f45 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -779,7 +779,7 @@
       public String getObfuscatedClassName(String originalClassName) {
         // TODO(tamaskenez) Watch for inline methods (we can be in a different class).
         String obfuscatedClassName =
-            classNameMapper.getObfuscatedToOriginalMapping().inverse().get(originalClassName);
+            classNameMapper.getObfuscatedToOriginalMapping().inverse.get(originalClassName);
         return obfuscatedClassName == null ? originalClassName : obfuscatedClassName;
       }
 
@@ -788,7 +788,7 @@
           String originalClassName, String originalMethodName, String methodSignatureOrNull) {
         ClassNamingForNameMapper naming;
         String obfuscatedClassName =
-            classNameMapper.getObfuscatedToOriginalMapping().inverse().get(originalClassName);
+            classNameMapper.getObfuscatedToOriginalMapping().inverse.get(originalClassName);
         if (obfuscatedClassName != null) {
           naming = classNameMapper.getClassNaming(obfuscatedClassName);
         } else {
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 90aad41..c416c6e 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -201,7 +201,7 @@
 
   public void runCatchHandlerTest(boolean codeThrows, boolean twoGuards) throws Exception {
     final int secondBlockInstructions = 4;
-    final int initialBlockCount = 6;
+    final int initialBlockCount = twoGuards ? 7 : 6;
     // Try split between all instructions in second block.
     for (int i = 1; i < secondBlockInstructions; i++) {
       TestApplication test = codeWithCatchHandlers(codeThrows, twoGuards);
@@ -240,7 +240,7 @@
   public void runCatchHandlerSplitThreeTest(boolean codeThrows, boolean twoGuards)
       throws Exception {
     final int secondBlockInstructions = 4;
-    final int initialBlockCount = 6;
+    final int initialBlockCount = twoGuards ? 7 : 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/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
index bfd3b76..437f2dd 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -471,7 +471,7 @@
         break;
     }
     if (typeName != null) {
-      String renamedName = mapper.getObfuscatedToOriginalMapping().inverse().get(typeName);
+      String renamedName = mapper.getObfuscatedToOriginalMapping().inverse.get(typeName);
       assertNotNull(renamedName);
       assertNotEquals(typeName, renamedName);
       return renamedName.replace('.', '/') + suffix;
@@ -506,7 +506,7 @@
         break;
     }
     if (samePackageAsType != null) {
-      String renamedName = mapper.getObfuscatedToOriginalMapping().inverse().get(samePackageAsType);
+      String renamedName = mapper.getObfuscatedToOriginalMapping().inverse.get(samePackageAsType);
       assertNotNull(renamedName);
       assertNotEquals(samePackageAsType, renamedName);
       if (renamedName.contains(".")) {
diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
index 983abaa..8a8f137 100644
--- a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
@@ -60,7 +60,7 @@
   @Test
   public void parseMapWithPackageInfo() throws IOException {
     ClassNameMapper mapper = ClassNameMapper.mapperFromString(EXAMPLE_MAP_WITH_PACKAGE_INFO);
-    Assert.assertTrue(mapper.getObfuscatedToOriginalMapping().isEmpty());
+    Assert.assertTrue(mapper.getObfuscatedToOriginalMapping().original.isEmpty());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java b/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java
index 08e86e6..9d633f2 100644
--- a/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java
+++ b/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java
@@ -4,16 +4,14 @@
 
 package com.android.tools.r8.regress.b120164595;
 
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.matchers.JUnitMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import org.junit.Test;
 
@@ -72,8 +70,7 @@
         },
         DexVm.ART_9_0_0_HOST
     );
-    // TODO(120164595): Remove when workaround lands.
-    assertNotEquals(artResult.exitCode, 0);
-    assertTrue(artResult.stderr.contains("Expected NullPointerException"));
+    assertEquals(0, artResult.exitCode);
+    assertFalse(artResult.stderr.contains("Expected NullPointerException"));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java b/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
index ba79ec5..1c28054 100644
--- a/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
+++ b/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
@@ -67,7 +67,7 @@
   private String pathForThisPackage(AndroidApp app) throws Exception {
     ClassNameMapper mapper =
         ClassNameMapper.mapperFromString(app.getProguardMapOutputData().getString());
-    String x = mapper.getObfuscatedToOriginalMapping().inverse().get(Main.class.getCanonicalName());
+    String x = mapper.getObfuscatedToOriginalMapping().inverse.get(Main.class.getCanonicalName());
     return x.substring(0, x.lastIndexOf('.')).replace('.', '/');
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 3f53530..1f520ff 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -37,11 +37,11 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.BiMapContainer;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
-import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.lang.reflect.Method;
@@ -61,7 +61,8 @@
   private final DexApplication application;
   final DexItemFactory dexItemFactory;
   private final ClassNameMapper mapping;
-  final BiMap<String, String> originalToObfuscatedMapping;
+  final Map<String, String> originalToObfuscatedMapping;
+  final Map<String, String> obfuscatedToOriginalMapping;
 
   public static MethodSignature MAIN =
       new MethodSignature("main", "void", new String[] {"java.lang.String[]"});
@@ -83,10 +84,13 @@
       throws IOException, ExecutionException {
     if (mappingFile != null) {
       this.mapping = ClassNameMapper.mapperFromFile(Paths.get(mappingFile));
-      originalToObfuscatedMapping = this.mapping.getObfuscatedToOriginalMapping().inverse();
+      BiMapContainer<String, String> nameMapping = this.mapping.getObfuscatedToOriginalMapping();
+      obfuscatedToOriginalMapping = nameMapping.original;
+      originalToObfuscatedMapping = nameMapping.inverse;
     } else {
       this.mapping = null;
       originalToObfuscatedMapping = null;
+      obfuscatedToOriginalMapping = null;
     }
     Timing timing = new Timing("CodeInspector");
     InternalOptions options = new InternalOptions();
@@ -97,7 +101,7 @@
     dexItemFactory = options.itemFactory;
     AndroidApp input = AndroidApp.builder().addProgramFiles(files).build();
     application = new ApplicationReader(input, options, timing).read();
-    }
+  }
 
   public CodeInspector(AndroidApp app) throws IOException, ExecutionException {
     this(
@@ -139,8 +143,14 @@
     dexItemFactory = application.dexItemFactory;
     this.application = application;
     this.mapping = application.getProguardMap();
-    originalToObfuscatedMapping =
-        mapping == null ? null : mapping.getObfuscatedToOriginalMapping().inverse();
+    if (mapping == null) {
+      originalToObfuscatedMapping = null;
+      obfuscatedToOriginalMapping = null;
+    } else {
+      BiMapContainer<String, String> nameMapping = mapping.getObfuscatedToOriginalMapping();
+      obfuscatedToOriginalMapping = nameMapping.original;
+      originalToObfuscatedMapping = nameMapping.inverse;
+    }
   }
 
   public DexItemFactory getFactory() {
@@ -251,7 +261,7 @@
         name = obfuscated;
       } else {
         // Figure out if the name is an already obfuscated name.
-        String original = originalToObfuscatedMapping.inverse().get(name);
+        String original = obfuscatedToOriginalMapping.get(name);
         if (original != null) {
           naming = mapping.getClassNaming(name);
         }
@@ -393,8 +403,8 @@
     @Override
     public String parsedTypeName(String name) {
       String type = name;
-      if (originalToObfuscatedMapping != null) {
-        String original = mapType(originalToObfuscatedMapping.inverse(), name);
+      if (obfuscatedToOriginalMapping != null) {
+        String original = mapType(obfuscatedToOriginalMapping, name);
         type = original != null ? original : name;
       }
       signature.append(type);
@@ -409,7 +419,7 @@
         String minifiedEnclosing = originalToObfuscatedMapping.get(enclosingType);
         if (minifiedEnclosing != null) {
           assert !minifiedEnclosing.contains("[");
-          type = mapType(originalToObfuscatedMapping.inverse(), minifiedEnclosing + "$" + name);
+          type = mapType(obfuscatedToOriginalMapping, minifiedEnclosing + "$" + name);
           if (type != null) {
             assert type.startsWith(enclosingType + "$");
             name = type.substring(enclosingType.length() + 1);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index 7efc794..92fc0a0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -83,7 +83,7 @@
     // whereas the final signature is for X.a is "a a"
     String obfuscatedType = signature.type;
     String originalType =
-        codeInspector.mapType(codeInspector.originalToObfuscatedMapping.inverse(), obfuscatedType);
+        codeInspector.mapType(codeInspector.obfuscatedToOriginalMapping, obfuscatedType);
     String fieldType = originalType != null ? originalType : obfuscatedType;
     FieldSignature lookupSignature = new FieldSignature(signature.name, fieldType);
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 2b4825e..5643091 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -4,9 +4,6 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.code.Instruction;
@@ -125,12 +122,11 @@
     String[] OriginalParameters = new String[signature.parameters.length];
     for (int i = 0; i < OriginalParameters.length; i++) {
       String obfuscated = signature.parameters[i];
-      String original = codeInspector.originalToObfuscatedMapping.inverse().get(obfuscated);
+      String original = codeInspector.obfuscatedToOriginalMapping.get(obfuscated);
       OriginalParameters[i] = original != null ? original : obfuscated;
     }
     String obfuscatedReturnType = signature.type;
-    String originalReturnType =
-        codeInspector.originalToObfuscatedMapping.inverse().get(obfuscatedReturnType);
+    String originalReturnType = codeInspector.obfuscatedToOriginalMapping.get(obfuscatedReturnType);
     String returnType = originalReturnType != null ? originalReturnType : obfuscatedReturnType;
 
     MethodSignature lookupSignature =
diff --git a/tools/golem_build.py b/tools/golem_build.py
index 6d3a9b7..d55def1 100755
--- a/tools/golem_build.py
+++ b/tools/golem_build.py
@@ -8,8 +8,8 @@
 import gradle
 import sys
 
-GRADLE_ARGS = ['--no-daemon']
-BUILD_TARGETS = ['R8', 'D8', 'buildExampleJars', 'CompatDx',
+GRADLE_ARGS = ['--no-daemon', '-Pno_internal']
+BUILD_TARGETS = ['R8', 'D8', 'R8Lib', 'buildExampleJars', 'CompatDx',
                  'downloadAndroidCts', 'downloadDx']
 
 def Main():
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 56ea215..244c093 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -117,7 +117,8 @@
   return subprocess.check_output(['git', 'clone', git_url]).strip()
 
 def GitPull():
-  return subprocess.call(['git', 'pull']) == 0
+  # Use --no-edit to accept the auto-generated merge message, if any.
+  return subprocess.call(['git', 'pull', '--no-edit']) == 0
 
 def GitCheckout(file):
   return subprocess.check_output(['git', 'checkout', file]).strip()
@@ -164,8 +165,12 @@
   if not os.path.exists(checkout_dir):
     with utils.ChangedWorkingDirectory(WORKING_DIR):
       GitClone(git_repo)
-  else:
+  elif options.pull:
     with utils.ChangedWorkingDirectory(checkout_dir):
+      # Checkout build.gradle to avoid merge conflicts.
+      if IsTrackedByGit('build.gradle'):
+        GitCheckout('build.gradle')
+
       if not GitPull():
         result['status'] = 'failed'
         result['error_message'] = 'Unable to pull from remote'
@@ -185,7 +190,7 @@
 
   with utils.ChangedWorkingDirectory(checkout_dir):
     for shrinker in SHRINKERS:
-      if options.shrinker is not None and shrinker != options.shrinker:
+      if options.shrinker and shrinker not in options.shrinker:
         continue
 
       apk_dest = None
@@ -313,8 +318,12 @@
       print('  skipped ({})'.format(error_message))
       continue
 
-    baseline = result_per_shrinker.get('proguard', {}).get('dex_size', -1)
-    for shrinker, result in result_per_shrinker.iteritems():
+    baseline = float(
+        result_per_shrinker.get('proguard', {}).get('dex_size', -1))
+    for shrinker in SHRINKERS:
+      if shrinker not in result_per_shrinker:
+        continue
+      result = result_per_shrinker.get(shrinker)
       build_status = result.get('build_status')
       if build_status != 'success':
         warn('  {}: {}'.format(shrinker, build_status))
@@ -323,10 +332,13 @@
         dex_size = result.get('dex_size')
         if dex_size != baseline and baseline >= 0:
           if dex_size < baseline:
-            success('    dex size: {} ({})'.format(
-              dex_size, dex_size - baseline))
-          elif dex_size > baseline:
-            warn('    dex size: {} ({})'.format(dex_size, dex_size - baseline))
+            success('    dex size: {} ({}, -{}%)'.format(
+              dex_size, dex_size - baseline,
+              round((1.0 - dex_size / baseline) * 100), 1))
+          elif dex_size >= baseline:
+            warn('    dex size: {} ({}, +{}%)'.format(
+              dex_size, dex_size - baseline,
+              round((baseline - dex_size) / dex_size * 100, 1)))
         else:
           print('    dex size: {}'.format(dex_size))
         if options.monkey:
@@ -345,18 +357,26 @@
                     help='Whether to install and run app(s) with monkey',
                     default=False,
                     action='store_true')
+  result.add_option('--pull',
+                    help='Whether to pull the latest version of each app',
+                    default=False,
+                    action='store_true')
   result.add_option('--sign_apks',
                     help='Whether the APKs should be signed',
                     default=False,
                     action='store_true')
   result.add_option('--shrinker',
-                    help='The shrinker to use (by default, all are run)',
-                    choices=SHRINKERS)
+                    help='The shrinkers to use (by default, all are run)',
+                    action='append')
   result.add_option('--disable_tot',
                     help='Whether to disable the use of the ToT version of R8',
                     default=False,
                     action='store_true')
-  return result.parse_args(argv)
+  (options, args) = result.parse_args(argv)
+  if options.shrinker:
+    for shrinker in options.shrinker:
+      assert shrinker in SHRINKERS
+  return (options, args)
 
 def main(argv):
   global SHRINKERS