Merge "Revert "Assert that definitionFor is only called on non-null class types.""
diff --git a/build.gradle b/build.gradle
index c205b2d..33d967f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1443,8 +1443,13 @@
         def out = new StringBuffer()
         def err = new StringBuffer()
         def command = "tools/retrace.py"
+        def header = "RETRACED STACKTRACE";
+        if (System.getenv('BUILDBOT_BUILDERNAME') != null
+                && !System.getenv('BUILDBOT_BUILDERNAME').endsWith("_release")) {
+            header += ": (${command} --commit_hash ${System.getenv('BUILDBOT_REVISION')})";
+        }
         out.append("\n--------------------------------------\n")
-        out.append("RETRACED STACKTRACE\n")
+        out.append("${header}\n")
         out.append("--------------------------------------\n")
         Process process = command.execute()
         def processIn = new PrintStream(process.getOut())
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index c76865e..d9940f3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -448,6 +448,13 @@
   }
 
   public boolean classInitializationMayHaveSideEffects(AppInfo appInfo) {
+    return classInitializationMayHaveSideEffects(appInfo, Predicates.alwaysFalse());
+  }
+
+  public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
+    if (ignore.test(type)) {
+      return false;
+    }
     if (hasNonTrivialClassInitializer()) {
       return true;
     }
@@ -455,11 +462,11 @@
       return true;
     }
     for (DexType iface : interfaces.values) {
-      if (iface.classInitializationMayHaveSideEffects(appInfo)) {
+      if (iface.classInitializationMayHaveSideEffects(appInfo, ignore)) {
         return true;
       }
     }
-    if (superType != null && superType.classInitializationMayHaveSideEffects(appInfo)) {
+    if (superType != null && superType.classInitializationMayHaveSideEffects(appInfo, ignore)) {
       return true;
     }
     return false;
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 31edee9..0c48ea9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -23,6 +23,7 @@
 import java.util.TreeSet;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
 
 public class DexType extends DexReference implements PresortedComparable<DexType> {
 
@@ -119,9 +120,9 @@
     return implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.serializableType);
   }
 
-  public boolean classInitializationMayHaveSideEffects(AppInfo appInfo) {
+  public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
     DexClass clazz = appInfo.definitionFor(this);
-    return clazz == null || clazz.classInitializationMayHaveSideEffects(appInfo);
+    return clazz == null || clazz.classInitializationMayHaveSideEffects(appInfo, ignore);
   }
 
   public boolean isUnknown() {
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 012c4a4..09094aa 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -73,22 +73,24 @@
   }
 
   public DirectMappedDexApplication rewrittenWithLense(GraphLense graphLense) {
-    assert mappingIsValid(graphLense, programClasses.getAllTypes());
-    assert mappingIsValid(graphLense, libraryClasses.keySet());
     // As a side effect, this will rebuild the program classes and library classes maps.
-    return this.builder().build().asDirect();
+    DirectMappedDexApplication rewrittenApplication = this.builder().build().asDirect();
+    assert rewrittenApplication.mappingIsValid(graphLense, programClasses.getAllTypes());
+    assert rewrittenApplication.mappingIsValid(graphLense, libraryClasses.keySet());
+    return rewrittenApplication;
   }
 
   private boolean mappingIsValid(GraphLense graphLense, Iterable<DexType> types) {
-    // The lense might either map to a different type that is already present in the application
+    // The lens might either map to a different type that is already present in the application
     // (e.g. relinking a type) or it might encode a type that was renamed, in which case the
     // original type will point to a definition that was renamed.
     for (DexType type : types) {
       DexType renamed = graphLense.lookupType(type);
       if (renamed != type) {
-        if (definitionFor(type).type != renamed && definitionFor(renamed) == null) {
-          return false;
+        if (definitionFor(type) == null && definitionFor(renamed) != null) {
+          continue;
         }
+        assert definitionFor(type).type == renamed || definitionFor(renamed) != null;
       }
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 898afed..b93c867 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -359,6 +359,8 @@
     return new Builder();
   }
 
+  public abstract DexType getOriginalType(DexType type);
+
   public abstract DexField getOriginalFieldSignature(DexField field);
 
   public abstract DexMethod getOriginalMethodSignature(DexMethod method);
@@ -370,16 +372,16 @@
   public DexEncodedMethod mapDexEncodedMethod(
       AppInfo appInfo, DexEncodedMethod originalEncodedMethod) {
     DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method);
-    if (newMethod != originalEncodedMethod.method) {
-      // We can't directly use AppInfo#definitionFor(DexMethod) since definitions may not be
-      // updated either yet.
-      DexClass newHolder = appInfo.definitionFor(newMethod.holder);
-      assert newHolder != null;
-      DexEncodedMethod newEncodedMethod = newHolder.lookupMethod(newMethod);
-      assert newEncodedMethod != null;
-      return newEncodedMethod;
-    }
-    return originalEncodedMethod;
+    // Note that:
+    // * Even if `newMethod` is the same as `originalEncodedMethod.method`, we still need to look it
+    //   up, since `originalEncodedMethod` may be obsolete.
+    // * We can't directly use AppInfo#definitionFor(DexMethod) since definitions may not be
+    //   updated either yet.
+    DexClass newHolder = appInfo.definitionFor(newMethod.holder);
+    assert newHolder != null;
+    DexEncodedMethod newEncodedMethod = newHolder.lookupMethod(newMethod);
+    assert newEncodedMethod != null;
+    return newEncodedMethod;
   }
 
   public abstract DexType lookupType(DexType type);
@@ -391,7 +393,7 @@
   }
 
   public abstract GraphLenseLookupResult lookupMethod(
-      DexMethod method, DexEncodedMethod context, Type type);
+      DexMethod method, DexMethod context, Type type);
 
   public abstract RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method);
 
@@ -626,6 +628,11 @@
     }
 
     @Override
+    public DexType getOriginalType(DexType type) {
+      return type;
+    }
+
+    @Override
     public DexField getOriginalFieldSignature(DexField field) {
       return field;
     }
@@ -651,8 +658,7 @@
     }
 
     @Override
-    public GraphLenseLookupResult lookupMethod(
-        DexMethod method, DexEncodedMethod context, Type type) {
+    public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
       return new GraphLenseLookupResult(method, type);
     }
 
@@ -673,14 +679,14 @@
   }
 
   /**
-   * GraphLense implementation with a parent lense using a simple mapping for type, method and
-   * field mapping.
+   * GraphLense implementation with a parent lense using a simple mapping for type, method and field
+   * mapping.
    *
-   * Subclasses can override the lookup methods.
+   * <p>Subclasses can override the lookup methods.
    *
-   * For method mapping where invocation type can change just override
-   * {@link #mapInvocationType(DexMethod, DexMethod, DexEncodedMethod, Type)} if
-   * the default name mapping applies, and only invocation type might need to change.
+   * <p>For method mapping where invocation type can change just override {@link
+   * #mapInvocationType(DexMethod, DexMethod, DexMethod, Type)} if the default name mapping applies,
+   * and only invocation type might need to change.
    */
   public static class NestedGraphLense extends GraphLense {
 
@@ -715,6 +721,11 @@
     }
 
     @Override
+    public DexType getOriginalType(DexType type) {
+      return previousLense.getOriginalType(type);
+    }
+
+    @Override
     public DexField getOriginalFieldSignature(DexField field) {
       DexField originalField =
           originalFieldSignatures != null
@@ -772,9 +783,12 @@
     }
 
     @Override
-    public GraphLenseLookupResult lookupMethod(
-        DexMethod method, DexEncodedMethod context, Type type) {
-      GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
+    public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
+      DexMethod previousContext =
+          originalMethodSignatures != null
+              ? originalMethodSignatures.getOrDefault(context, context)
+              : context;
+      GraphLenseLookupResult previous = previousLense.lookupMethod(method, previousContext, type);
       DexMethod newMethod = methodMap.get(previous.getMethod());
       if (newMethod == null) {
         return previous;
@@ -782,7 +796,7 @@
       // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
       // that only subclasses which are known to need it actually do it?
       return new GraphLenseLookupResult(
-          newMethod, mapInvocationType(newMethod, method, context, previous.getType()));
+          newMethod, mapInvocationType(newMethod, method, previous.getType()));
     }
 
     @Override
@@ -793,22 +807,20 @@
     /**
      * Default invocation type mapping.
      *
-     * This is an identity mapping. If a subclass need invocation type mapping either override
-     * this method or {@link #lookupMethod(DexMethod, DexEncodedMethod, Type)}
+     * <p>This is an identity mapping. If a subclass need invocation type mapping either override
+     * this method or {@link #lookupMethod(DexMethod, DexMethod, Type)}
      */
-    protected Type mapInvocationType(
-        DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+    protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
       return type;
     }
 
     /**
      * Standard mapping between interface and virtual invoke type.
      *
-     * Handle methods moved from interface to class or class to interface.
+     * <p>Handle methods moved from interface to class or class to interface.
      */
-    final protected Type mapVirtualInterfaceInvocationTypes(
-        AppInfo appInfo, DexMethod newMethod, DexMethod originalMethod,
-        DexEncodedMethod context, Type type) {
+    protected final Type mapVirtualInterfaceInvocationTypes(
+        AppInfo appInfo, DexMethod newMethod, DexMethod originalMethod, Type type) {
       if (type == Type.VIRTUAL || type == Type.INTERFACE) {
         // Get the invoke type of the actual definition.
         DexClass newTargetClass = appInfo.definitionFor(newMethod.holder);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
index 9c3bcc3..3ef167c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
@@ -330,8 +330,7 @@
     if (type.isPrimitiveType()) {
       return PrimitiveTypeLatticeElement.fromDexType(type, asArrayElementType);
     }
-    return appInfo.dexItemFactory.createReferenceTypeLatticeElement(
-        type, nullability, appInfo);
+    return appInfo.dexItemFactory.createReferenceTypeLatticeElement(type, nullability, appInfo);
   }
 
   public boolean isValueTypeCompatible(TypeLatticeElement other) {
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 cb93a5e..773a648 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
@@ -662,9 +662,12 @@
           }
           // After the throwing instruction only debug instructions and the final jump
           // instruction is allowed.
+          // TODO(ager): For now allow const instructions due to the way consts are pushed
+          // towards their use
           if (seenThrowing) {
             assert instruction.isDebugInstruction()
                 || instruction.isJumpInstruction()
+                || instruction.isConstInstruction()
                 || instruction.isStore()
                 || instruction.isPop();
           }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index d9040c5..c12ea40 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -532,7 +532,7 @@
 
     private void processInvoke(Type type, DexMethod method) {
       DexEncodedMethod source = caller.method;
-      GraphLenseLookupResult result = graphLense.lookupMethod(method, source, type);
+      GraphLenseLookupResult result = graphLense.lookupMethod(method, source.method, type);
       method = result.getMethod();
       type = result.getType();
       DexEncodedMethod definition = appInfo.lookup(type, method, source.method.holder);
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 7b55543..f068a6a 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
@@ -1290,6 +1290,7 @@
       CodeRewriter.collapseTrivialGotos(method, code);
       PeepholeOptimizer.optimize(code, registerAllocator);
     }
+    CodeRewriter.removeUnneededMovesOnExitingPaths(code, registerAllocator);
     CodeRewriter.collapseTrivialGotos(method, code);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Final (non-SSA) flow graph for %s:\n%s",
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index cf88f0f..7c22bbf 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -156,7 +156,7 @@
             checkInvokeDirect(method.method, invoke.asInvokeDirect());
           }
           GraphLenseLookupResult lenseLookup =
-              graphLense.lookupMethod(invokedMethod, method, invoke.getType());
+              graphLense.lookupMethod(invokedMethod, method.method, invoke.getType());
           DexMethod actualTarget = lenseLookup.getMethod();
           Invoke.Type actualInvokeType = lenseLookup.getType();
           if (actualInvokeType == Type.VIRTUAL) {
@@ -210,15 +210,7 @@
             iterator.replaceCurrentInstruction(newInvoke);
 
             if (constantReturnMaterializingInstruction != null) {
-              if (block.hasCatchHandlers()) {
-                // Split the block to ensure no instructions after throwing instructions.
-                iterator
-                    .split(code, blocks)
-                    .listIterator()
-                    .add(constantReturnMaterializingInstruction);
-              } else {
-                iterator.add(constantReturnMaterializingInstruction);
-              }
+              iterator.add(constantReturnMaterializingInstruction);
             }
 
             DexType actualReturnType = actualTarget.proto.returnType;
@@ -314,10 +306,11 @@
           }
         } else if (current.isMoveException()) {
           MoveException moveException = current.asMoveException();
-          if (moveException.hasOutValue()) {
-            // Conservatively add the out-value to `newSSAValues` since the catch handler guards
-            // may have been renamed as a result of class merging.
-            newSSAValues.add(moveException.outValue());
+          DexType newExceptionType = graphLense.lookupType(moveException.getExceptionType());
+          if (newExceptionType != moveException.getExceptionType()) {
+            iterator.replaceCurrentInstruction(
+                new MoveException(
+                    makeOutValue(moveException, code, newSSAValues), newExceptionType, options));
           }
         } else if (current.isNewArrayEmpty()) {
           NewArrayEmpty newArrayEmpty = current.asNewArrayEmpty();
@@ -328,7 +321,7 @@
             iterator.replaceCurrentInstruction(newNewArray);
           }
         } else if (current.isNewInstance()) {
-          NewInstance newInstance= current.asNewInstance();
+          NewInstance newInstance = current.asNewInstance();
           DexType newClazz = graphLense.lookupType(newInstance.clazz);
           if (newClazz != newInstance.clazz) {
             NewInstance newNewInstance = new NewInstance(
@@ -464,7 +457,7 @@
       DexMethod invokedMethod = methodHandle.asMethod();
       MethodHandleType oldType = methodHandle.type;
       GraphLenseLookupResult lenseLookup =
-          graphLense.lookupMethod(invokedMethod, context, oldType.toInvokeType());
+          graphLense.lookupMethod(invokedMethod, context.method, oldType.toInvokeType());
       DexMethod rewrittenTarget = lenseLookup.getMethod();
       DexMethod actualTarget;
       MethodHandleType newType;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java
index a8616fb..b708ae9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLense;
@@ -13,14 +11,11 @@
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
-import java.util.Map;
 
 class InterfaceMethodDesugaringLense extends NestedGraphLense {
-  private final Map<DexEncodedMethod, DexEncodedMethod> methodsWithMovedCode;
 
   InterfaceMethodDesugaringLense(
       BiMap<DexMethod, DexMethod> methodMapping,
-      Map<DexEncodedMethod, DexEncodedMethod> methodsWithMovedCode,
       GraphLense previous, DexItemFactory factory) {
     super(
         ImmutableMap.of(),
@@ -30,12 +25,5 @@
         methodMapping.inverse(),
         previous,
         factory);
-    this.methodsWithMovedCode = methodsWithMovedCode;
-  }
-
-  @Override
-  public DexEncodedMethod mapDexEncodedMethod(AppInfo appInfo, DexEncodedMethod original) {
-    return super.mapDexEncodedMethod(
-        appInfo, methodsWithMovedCode.getOrDefault(original, original));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 7a19fb8..10cbabb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -428,7 +428,6 @@
       converter.appView.setGraphLense(
           new InterfaceMethodDesugaringLense(
               processor.movedMethods,
-              processor.methodsWithMovedCode,
               converter.appView.graphLense(),
               factory));
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
index 26f890e..d5ceeb1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLense;
@@ -28,13 +27,12 @@
   }
 
   @Override
-  protected Type mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
     if (methodMap.get(originalMethod) == newMethod) {
       assert type == Type.VIRTUAL || type == Type.DIRECT;
       return Type.STATIC;
     }
-    return super.mapInvocationType(newMethod, originalMethod, context, type);
+    return super.mapInvocationType(newMethod, originalMethod, type);
   }
 
 }
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 3227d45..7758d83 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
@@ -61,6 +61,7 @@
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DebugLocalWrite;
+import com.android.tools.r8.ir.code.DebugLocalsChange;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.Goto;
@@ -78,6 +79,7 @@
 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.Move;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.NewInstance;
@@ -97,6 +99,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
+import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -115,12 +118,18 @@
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntIterator;
 import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
 import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
 import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
@@ -3640,6 +3649,132 @@
     assert code.isConsistentSSA();
   }
 
+  /**
+   * Remove moves that are not actually used by instructions in exiting paths. These moves can arise
+   * due to debug local info needing a particular value and the live-interval for it then moves it
+   * back into the properly assigned register. If the register is only used for debug purposes, it
+   * is safe to just remove the move and update the local information accordingly.
+   */
+  public static void removeUnneededMovesOnExitingPaths(
+      IRCode code, LinearScanRegisterAllocator allocator) {
+    if (!code.options.debug) {
+      return;
+    }
+    for (BasicBlock block : code.blocks) {
+      // Skip non-exit blocks.
+      if (!block.getSuccessors().isEmpty()) {
+        continue;
+      }
+      // Skip blocks with no locals at entry.
+      Int2ReferenceMap<DebugLocalInfo> localsAtEntry = block.getLocalsAtEntry();
+      if (localsAtEntry == null || localsAtEntry.isEmpty()) {
+        continue;
+      }
+      // Find the locals state after spill moves.
+      DebugLocalsChange postSpillLocalsChange = null;
+      for (Instruction instruction : block.getInstructions()) {
+        if (instruction.getNumber() != -1 || postSpillLocalsChange != null) {
+          break;
+        }
+        postSpillLocalsChange = instruction.asDebugLocalsChange();
+      }
+      // Skip if the locals state did not change.
+      if (postSpillLocalsChange == null
+          || !postSpillLocalsChange.apply(new Int2ReferenceOpenHashMap<>(localsAtEntry))) {
+        continue;
+      }
+      // Collect the moves that can safely be removed.
+      Set<Move> unneededMoves = computeUnneededMoves(block, postSpillLocalsChange, allocator);
+      if (unneededMoves.isEmpty()) {
+        continue;
+      }
+      Int2IntMap previousMapping = new Int2IntOpenHashMap();
+      Int2IntMap mapping = new Int2IntOpenHashMap();
+      ListIterator<Instruction> it = block.getInstructions().listIterator();
+      while (it.hasNext()) {
+        Instruction instruction = it.next();
+        if (instruction.isMove()) {
+          Move move = instruction.asMove();
+          if (unneededMoves.contains(move)) {
+            int dst = allocator.getRegisterForValue(move.dest(), move.getNumber());
+            int src = allocator.getRegisterForValue(move.src(), move.getNumber());
+            int mappedSrc = mapping.getOrDefault(src, src);
+            mapping.put(dst, mappedSrc);
+            it.remove();
+          }
+        } else if (instruction.isDebugLocalsChange()) {
+          DebugLocalsChange change = instruction.asDebugLocalsChange();
+          updateDebugLocalsRegisterMap(previousMapping, change.getEnding());
+          updateDebugLocalsRegisterMap(mapping, change.getStarting());
+          previousMapping = mapping;
+          mapping = new Int2IntOpenHashMap(previousMapping);
+        }
+      }
+    }
+  }
+
+  private static Set<Move> computeUnneededMoves(
+      BasicBlock block,
+      DebugLocalsChange postSpillLocalsChange,
+      LinearScanRegisterAllocator allocator) {
+    Set<Move> unneededMoves = Sets.newIdentityHashSet();
+    IntSet usedRegisters = new IntOpenHashSet();
+    IntSet clobberedRegisters = new IntOpenHashSet();
+    // Backwards instruction scan collecting the registers used by actual instructions.
+    boolean inEntrySpillMoves = false;
+    InstructionListIterator it = block.listIterator(block.getInstructions().size());
+    while (it.hasPrevious()) {
+      Instruction instruction = it.previous();
+      if (instruction == postSpillLocalsChange) {
+        inEntrySpillMoves = true;
+      }
+      // If this is a move in the block-entry spill moves check if it is unneeded.
+      if (inEntrySpillMoves && instruction.isMove()) {
+        Move move = instruction.asMove();
+        int dst = allocator.getRegisterForValue(move.dest(), move.getNumber());
+        int src = allocator.getRegisterForValue(move.src(), move.getNumber());
+        if (!usedRegisters.contains(dst) && !clobberedRegisters.contains(src)) {
+          unneededMoves.add(move);
+          continue;
+        }
+      }
+      if (instruction.outValue() != null && instruction.outValue().needsRegister()) {
+        int register =
+            allocator.getRegisterForValue(instruction.outValue(), instruction.getNumber());
+        // The register is defined anew, so uses before this are on distinct values.
+        usedRegisters.remove(register);
+        // Mark it clobbered to avoid any uses in locals after this point to become invalid.
+        clobberedRegisters.add(register);
+      }
+      if (!instruction.inValues().isEmpty()) {
+        for (Value inValue : instruction.inValues()) {
+          if (inValue.needsRegister()) {
+            int register = allocator.getRegisterForValue(inValue, instruction.getNumber());
+            // Record the register as being used.
+            usedRegisters.add(register);
+          }
+        }
+      }
+    }
+    return unneededMoves;
+  }
+
+  private static void updateDebugLocalsRegisterMap(
+      Int2IntMap mapping, Int2ReferenceMap<DebugLocalInfo> locals) {
+    // If nothing is mapped nothing needs to be changed.
+    if (mapping.isEmpty()) {
+      return;
+    }
+    // Locals is final, so we copy and clear it during update.
+    Int2ReferenceMap<DebugLocalInfo> copy = new Int2ReferenceOpenHashMap<>(locals);
+    locals.clear();
+    for (Entry<DebugLocalInfo> entry : copy.int2ReferenceEntrySet()) {
+      int oldRegister = entry.getIntKey();
+      int newRegister = mapping.getOrDefault(oldRegister, oldRegister);
+      locals.put(newRegister, entry.getValue());
+    }
+  }
+
   // Removes calls to Throwable.addSuppressed(Throwable) and rewrites
   // Throwable.getSuppressed() into new Throwable[0].
   //
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 99f9731..4da9f8d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -17,13 +17,11 @@
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
-import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
@@ -31,7 +29,6 @@
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardMemberRule;
 import com.google.common.collect.Sets;
-import java.util.ListIterator;
 import java.util.Set;
 import java.util.function.Predicate;
 
@@ -136,160 +133,151 @@
   public void rewriteWithConstantValues(
       IRCode code, DexType callingContext, Predicate<DexEncodedMethod> isProcessedConcurrently) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
-    ListIterator<BasicBlock> blocks = code.blocks.listIterator();
-    while (blocks.hasNext()) {
-      BasicBlock block = blocks.next();
-      InstructionListIterator iterator = block.listIterator();
-      while (iterator.hasNext()) {
-        Instruction current = iterator.next();
-        if (current.isInvokeMethod()) {
-          InvokeMethod invoke = current.asInvokeMethod();
-          DexMethod invokedMethod = invoke.getInvokedMethod();
-          DexType invokedHolder = invokedMethod.getHolder();
-          if (!invokedHolder.isClassType()) {
-            continue;
-          }
-          // TODO(70550443): Maybe check all methods here.
-          DexEncodedMethod definition = appInfo
-              .lookup(invoke.getType(), invokedMethod, callingContext);
+    InstructionIterator iterator = code.instructionIterator();
+    while (iterator.hasNext()) {
+      Instruction current = iterator.next();
+      if (current.isInvokeMethod()) {
+        InvokeMethod invoke = current.asInvokeMethod();
+        DexMethod invokedMethod = invoke.getInvokedMethod();
+        DexType invokedHolder = invokedMethod.getHolder();
+        if (!invokedHolder.isClassType()) {
+          continue;
+        }
+        // TODO(70550443): Maybe check all methods here.
+        DexEncodedMethod definition = appInfo
+            .lookup(invoke.getType(), invokedMethod, callingContext);
 
-          boolean invokeReplaced = false;
-          ProguardMemberRuleLookup lookup = lookupMemberRule(definition);
-          if (lookup != null) {
-            if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
-                && (invoke.outValue() == null || !invoke.outValue().isUsed())) {
-              // Remove invoke if marked as having no side effects and the return value is not used.
-              iterator.remove();
-              invokeReplaced = true;
-            } else if (invoke.outValue() != null && invoke.outValue().isUsed()) {
-              // Check to see if a constant value can be assumed.
-              Instruction replacement =
-                  constantReplacementFromProguardRule(lookup.rule, code, invoke);
-              if (replacement != null) {
-                affectedValues.add(replacement.outValue());
-                replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
-                invokeReplaced = true;
-              } else {
-                // Check to see if a value range can be assumed.
-                setValueRangeFromProguardRule(lookup.rule, current.outValue());
-              }
-            }
-          }
-
-          // If no Proguard rule could replace the instruction check for knowledge about the
-          // return value.
-          if (!invokeReplaced && invoke.outValue() != null) {
-            DexEncodedMethod target = invoke.lookupSingleTarget(appInfo, callingContext);
-            if (target != null) {
-              if (target.getOptimizationInfo().neverReturnsNull() && invoke.outValue()
-                  .canBeNull()) {
-                Value knownToBeNonNullValue = invoke.outValue();
-                knownToBeNonNullValue.markNeverNull();
-                TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
-                assert typeLattice.isNullable() && typeLattice.isReference();
-                knownToBeNonNullValue.narrowing(appInfo, typeLattice.asNonNullable());
-                affectedValues.addAll(knownToBeNonNullValue.affectedValues());
-              }
-              if (target.getOptimizationInfo().returnsConstant()) {
-                long constant = target.getOptimizationInfo().getReturnedConstant();
-                ConstNumber replacement = createConstNumberReplacement(
-                    code, constant, invoke.outValue().getTypeLattice(), invoke.getLocalInfo());
-                affectedValues.add(replacement.outValue());
-                invoke.outValue().replaceUsers(replacement.outValue());
-                invoke.setOutValue(null);
-                replacement.setPosition(invoke.getPosition());
-                invoke.moveDebugValues(replacement);
-                if (current.getBlock().hasCatchHandlers()) {
-                  iterator.split(code, blocks).listIterator().add(replacement);
-                } else {
-                  iterator.add(replacement);
-                }
-              }
-            }
-          }
-        } else if (current.isInstancePut()) {
-          InstancePut instancePut = current.asInstancePut();
-          DexField field = instancePut.getField();
-          DexEncodedField target = appInfo.lookupInstanceTarget(field.getHolder(), field);
-          if (target != null) {
-            // Remove writes to dead (i.e. never read) fields.
-            if (!isFieldRead(target, false) && instancePut.object().isNeverNull()) {
-              iterator.remove();
-            }
-          }
-        } else if (current.isStaticGet()) {
-          StaticGet staticGet = current.asStaticGet();
-          DexField field = staticGet.getField();
-          DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
-          ProguardMemberRuleLookup lookup = null;
-          if (target != null) {
-            // Check if a this value is known const.
-            Instruction replacement = target.valueAsConstInstruction(appInfo, staticGet.dest());
-            if (replacement == null) {
-              lookup = lookupMemberRule(target);
-              if (lookup != null) {
-                replacement = constantReplacementFromProguardRule(lookup.rule, code, staticGet);
-              }
-            }
-            if (replacement == null) {
-              // If no const replacement was found, at least store the range information.
-              if (lookup != null) {
-                setValueRangeFromProguardRule(lookup.rule, staticGet.dest());
-              }
-            }
+        boolean invokeReplaced = false;
+        ProguardMemberRuleLookup lookup = lookupMemberRule(definition);
+        if (lookup != null) {
+          if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
+              && (invoke.outValue() == null || !invoke.outValue().isUsed())) {
+            // Remove invoke if marked as having no side effects and the return value is not used.
+            iterator.remove();
+            invokeReplaced = true;
+          } else if (invoke.outValue() != null && invoke.outValue().isUsed()) {
+            // Check to see if a constant value can be assumed.
+            Instruction replacement =
+                constantReplacementFromProguardRule(lookup.rule, code, invoke);
             if (replacement != null) {
               affectedValues.add(replacement.outValue());
-              // Ignore assumenosideeffects for fields.
-              if (lookup != null && lookup.type == RuleType.ASSUME_VALUES) {
-                replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
-              } else {
-                iterator.replaceCurrentInstruction(replacement);
-              }
-            } else if (staticGet.dest() != null) {
-              // In case the class holder of this static field satisfying following criteria:
-              //   -- cannot trigger other static initializer except for its own
-              //   -- is final
-              //   -- has a class initializer which is classified as trivial
-              //      (see CodeRewriter::computeClassInitializerInfo) and
-              //      initializes the field being accessed
-              //
-              // ... and the field itself is not pinned by keep rules (in which case it might
-              // be updated outside the class constructor, e.g. via reflections), it is safe
-              // to assume that the static-get instruction reads the value it initialized value
-              // in class initializer and is never null.
-              //
-              DexClass holderDefinition = appInfo.definitionFor(field.getHolder());
-              if (holderDefinition != null
-                  && holderDefinition.accessFlags.isFinal()
-                  && !appInfo.canTriggerStaticInitializer(field.getHolder(), true)) {
-                Value outValue = staticGet.dest();
-                DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
-                if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
-                  TrivialInitializer info =
-                      classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
-                  if (info != null
-                      && ((TrivialClassInitializer) info).field == field
-                      && !appInfo.isPinned(field)
-                      && outValue.canBeNull()) {
-                    outValue.markNeverNull();
-                    TypeLatticeElement typeLattice = outValue.getTypeLattice();
-                    assert typeLattice.isNullable() && typeLattice.isReference();
-                    outValue.narrowing(appInfo, typeLattice.asNonNullable());
-                    affectedValues.addAll(outValue.affectedValues());
-                  }
+              replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
+              invokeReplaced = true;
+            } else {
+              // Check to see if a value range can be assumed.
+              setValueRangeFromProguardRule(lookup.rule, current.outValue());
+            }
+          }
+        }
+
+        // If no Proguard rule could replace the instruction check for knowledge about the
+        // return value.
+        if (!invokeReplaced && invoke.outValue() != null) {
+          DexEncodedMethod target = invoke.lookupSingleTarget(appInfo, callingContext);
+          if (target != null) {
+            if (target.getOptimizationInfo().neverReturnsNull() && invoke.outValue().canBeNull()) {
+              Value knownToBeNonNullValue = invoke.outValue();
+              knownToBeNonNullValue.markNeverNull();
+              TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
+              assert typeLattice.isNullable() && typeLattice.isReference();
+              knownToBeNonNullValue.narrowing(appInfo, typeLattice.asNonNullable());
+              affectedValues.addAll(knownToBeNonNullValue.affectedValues());
+            }
+            if (target.getOptimizationInfo().returnsConstant()) {
+              long constant = target.getOptimizationInfo().getReturnedConstant();
+              ConstNumber replacement = createConstNumberReplacement(
+                  code, constant, invoke.outValue().getTypeLattice(), invoke.getLocalInfo());
+              affectedValues.add(replacement.outValue());
+              invoke.outValue().replaceUsers(replacement.outValue());
+              invoke.setOutValue(null);
+              replacement.setPosition(invoke.getPosition());
+              invoke.moveDebugValues(replacement);
+              iterator.add(replacement);
+            }
+          }
+        }
+      } else if (current.isInstancePut()) {
+        InstancePut instancePut = current.asInstancePut();
+        DexField field = instancePut.getField();
+        DexEncodedField target = appInfo.lookupInstanceTarget(field.getHolder(), field);
+        if (target != null) {
+          // Remove writes to dead (i.e. never read) fields.
+          if (!isFieldRead(target, false) && instancePut.object().isNeverNull()) {
+            iterator.remove();
+          }
+        }
+      } else if (current.isStaticGet()) {
+        StaticGet staticGet = current.asStaticGet();
+        DexField field = staticGet.getField();
+        DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
+        ProguardMemberRuleLookup lookup = null;
+        if (target != null) {
+          // Check if a this value is known const.
+          Instruction replacement = target.valueAsConstInstruction(appInfo, staticGet.dest());
+          if (replacement == null) {
+            lookup = lookupMemberRule(target);
+            if (lookup != null) {
+              replacement = constantReplacementFromProguardRule(lookup.rule, code, staticGet);
+            }
+          }
+          if (replacement == null) {
+            // If no const replacement was found, at least store the range information.
+            if (lookup != null) {
+              setValueRangeFromProguardRule(lookup.rule, staticGet.dest());
+            }
+          }
+          if (replacement != null) {
+            affectedValues.add(replacement.outValue());
+            // Ignore assumenosideeffects for fields.
+            if (lookup != null && lookup.type == RuleType.ASSUME_VALUES) {
+              replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
+            } else {
+              iterator.replaceCurrentInstruction(replacement);
+            }
+          } else if (staticGet.dest() != null) {
+            // In case the class holder of this static field satisfying following criteria:
+            //   -- cannot trigger other static initializer except for its own
+            //   -- is final
+            //   -- has a class initializer which is classified as trivial
+            //      (see CodeRewriter::computeClassInitializerInfo) and
+            //      initializes the field being accessed
+            //
+            // ... and the field itself is not pinned by keep rules (in which case it might
+            // be updated outside the class constructor, e.g. via reflections), it is safe
+            // to assume that the static-get instruction reads the value it initialized value
+            // in class initializer and is never null.
+            //
+            DexClass holderDefinition = appInfo.definitionFor(field.getHolder());
+            if (holderDefinition != null
+                && holderDefinition.accessFlags.isFinal()
+                && !appInfo.canTriggerStaticInitializer(field.getHolder(), true)) {
+              Value outValue = staticGet.dest();
+              DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
+              if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
+                TrivialInitializer info =
+                    classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
+                if (info != null
+                    && ((TrivialClassInitializer) info).field == field
+                    && !appInfo.isPinned(field)
+                    && outValue.canBeNull()) {
+                  outValue.markNeverNull();
+                  TypeLatticeElement typeLattice = outValue.getTypeLattice();
+                  assert typeLattice.isNullable() && typeLattice.isReference();
+                  outValue.narrowing(appInfo, typeLattice.asNonNullable());
+                  affectedValues.addAll(outValue.affectedValues());
                 }
               }
             }
           }
-        } else if (current.isStaticPut()) {
-          StaticPut staticPut = current.asStaticPut();
-          DexField field = staticPut.getField();
-          DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
-          if (target != null) {
-            // Remove writes to dead (i.e. never read) fields.
-            if (!isFieldRead(target, true)) {
-              iterator.removeOrReplaceByDebugLocalRead();
-            }
+        }
+      } else if (current.isStaticPut()) {
+        StaticPut staticPut = current.asStaticPut();
+        DexField field = staticPut.getField();
+        DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
+        if (target != null) {
+          // Remove writes to dead (i.e. never read) fields.
+          if (!isFieldRead(target, true)) {
+            iterator.removeOrReplaceByDebugLocalRead();
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
index 9bdd55e..6613534 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
@@ -36,14 +36,12 @@
   }
 
   @Override
-  protected Type mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod,
-      DexEncodedMethod context, Type type) {
+  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
     if (methodMap.get(originalMethod) == newMethod) {
       assert type == Type.VIRTUAL || type == Type.DIRECT;
       return Type.STATIC;
     }
-    return super.mapInvocationType(newMethod, originalMethod, context, type);
+    return super.mapInvocationType(newMethod, originalMethod, type);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
index 3395753..b445efc 100644
--- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
@@ -39,7 +39,7 @@
     if (opcode == org.objectweb.asm.Opcodes.NEW) {
       registry.registerNewInstance(type);
     } else if (opcode == Opcodes.CHECKCAST) {
-        registry.registerCheckCast(type);
+      registry.registerCheckCast(type);
     } else {
       registry.registerTypeReference(type);
     }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index 61695f1..e670925 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -170,7 +170,9 @@
                           appInfo.definitionFor(cnst.getItem().asDexType()),
                           cnst.getClassNameComputationInfo()))
                   : lens.lookupName(cnst.getItem(), appInfo.dexItemFactory);
-          instructions[i] = new ConstString(cnst.AA, replacement);
+          ConstString constString = new ConstString(cnst.AA, replacement);
+          constString.setOffset(instruction.getOffset());
+          instructions[i] = constString;
         }
       }
     } else {
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
index 98a0da4..5e19d48 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.optimize;
 
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLense;
@@ -47,11 +46,8 @@
     return new Builder(appInfo);
   }
 
-
   @Override
-  protected Type mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
-    return super.mapVirtualInterfaceInvocationTypes(
-        appInfo, newMethod, originalMethod, context, type);
+  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
+    return super.mapVirtualInterfaceInvocationTypes(appInfo, newMethod, originalMethod, type);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
index 2db2dce..c685d84 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
@@ -15,6 +15,7 @@
 import java.util.Set;
 
 final class PublicizerLense extends NestedGraphLense {
+
   private final AppView appView;
   private final Set<DexMethod> publicizedMethods;
 
@@ -34,8 +35,7 @@
   }
 
   @Override
-  public GraphLenseLookupResult lookupMethod(
-      DexMethod method, DexEncodedMethod context, Type type) {
+  public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
     GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
     method = previous.getMethod();
     type = previous.getType();
@@ -46,7 +46,7 @@
     return super.lookupMethod(method, context, type);
   }
 
-  private boolean publicizedMethodIsPresentOnHolder(DexMethod method, DexEncodedMethod context) {
+  private boolean publicizedMethodIsPresentOnHolder(DexMethod method, DexMethod context) {
     GraphLenseLookupResult lookup =
         appView.graphLense().lookupMethod(method, context, Type.VIRTUAL);
     DexMethod signatureInCurrentWorld = lookup.getMethod();
diff --git a/src/main/java/com/android/tools/r8/references/ArrayReference.java b/src/main/java/com/android/tools/r8/references/ArrayReference.java
index 10d2a31..96cb926 100644
--- a/src/main/java/com/android/tools/r8/references/ArrayReference.java
+++ b/src/main/java/com/android/tools/r8/references/ArrayReference.java
@@ -10,12 +10,13 @@
 @Keep
 public final class ArrayReference implements TypeReference {
 
-  private final int dimentions;
+  private final int dimensions;
   private final TypeReference baseType;
   private String descriptor;
 
-  private ArrayReference(int dimentions, TypeReference baseType, String descriptor) {
-    this.dimentions = dimentions;
+  private ArrayReference(int dimensions, TypeReference baseType, String descriptor) {
+    assert dimensions > 0;
+    this.dimensions = dimensions;
     this.baseType = baseType;
     this.descriptor = descriptor;
   }
@@ -23,15 +24,18 @@
   static ArrayReference fromDescriptor(String descriptor) {
     for (int i = 0; i < descriptor.length(); i++) {
       if (descriptor.charAt(i) != '[') {
-        return new ArrayReference(
-            i, Reference.typeFromDescriptor(descriptor.substring(i)), descriptor);
+        if (i > 0) {
+          return new ArrayReference(
+              i, Reference.typeFromDescriptor(descriptor.substring(i)), descriptor);
+        }
+        break;
       }
     }
     throw new Unreachable("Invalid array type descriptor: " + descriptor);
   }
 
-  public int getDimentions() {
-    return dimentions;
+  public int getDimensions() {
+    return dimensions;
   }
 
   public TypeReference getMemberType() {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index b9f7554..d793e3b 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT;
 import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
 
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfo.ResolutionResult;
@@ -53,6 +52,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
@@ -211,13 +211,13 @@
   private final Set<DexProgramClass> mergeCandidates = new LinkedHashSet<>();
 
   // Map from source class to target class.
-  private final Map<DexType, DexType> mergedClasses = new HashMap<>();
+  private final Map<DexType, DexType> mergedClasses = new IdentityHashMap<>();
 
   // Map from target class to the super classes that have been merged into the target class.
-  private final Map<DexType, Set<DexType>> mergedClassesInverse = new HashMap<>();
+  private final Map<DexType, Set<DexType>> mergedClassesInverse = new IdentityHashMap<>();
 
   // Set of types that must not be merged into their subtype.
-  private final Set<DexType> pinnedTypes = new HashSet<>();
+  private final Set<DexType> pinnedTypes = Sets.newIdentityHashSet();
 
   // The resulting graph lense that should be used after class merging.
   private final VerticalClassMergerGraphLense.Builder renamedMembersLense;
@@ -423,7 +423,8 @@
       return false;
     }
     DexClass targetClass = appInfo.definitionFor(clazz.type.getSingleSubtype());
-    if (clazz.hasClassInitializer() && targetClass.hasClassInitializer()) {
+    if ((clazz.hasClassInitializer() && targetClass.hasClassInitializer())
+        || targetClass.classInitializationMayHaveSideEffects(appInfo, type -> type == clazz.type)) {
       // TODO(herhut): Handle class initializers.
       if (Log.ENABLED) {
         AbortReason.STATIC_INITIALIZERS.printLogMessageForClass(clazz);
@@ -685,7 +686,7 @@
     if (Log.ENABLED) {
       Log.debug(getClass(), "Merged %d classes.", mergedClasses.size());
     }
-    return renamedMembersLense.build(graphLense, mergedClasses, synthesizedBridges, appInfo);
+    return renamedMembersLense.build(graphLense, mergedClasses, appInfo);
   }
 
   private boolean methodResolutionMayChange(DexClass source, DexClass target) {
@@ -1430,42 +1431,19 @@
       for (DexProgramClass clazz : appInfo.classes()) {
         clazz.setDirectMethods(substituteTypesIn(clazz.directMethods()));
         clazz.setVirtualMethods(substituteTypesIn(clazz.virtualMethods()));
-        clazz.setVirtualMethods(removeDupes(clazz.virtualMethods()));
         clazz.setStaticFields(substituteTypesIn(clazz.staticFields()));
         clazz.setInstanceFields(substituteTypesIn(clazz.instanceFields()));
       }
+      for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
+        synthesizedBridge.updateMethodSignatures(this::fixupMethod);
+      }
       // Record type renamings so check-cast and instance-of checks are also fixed.
       for (DexType type : mergedClasses.keySet()) {
-        DexType fixed = fixupType(type);
-        lense.map(type, fixed);
+        lense.map(type, fixupType(type));
       }
       return lense.build(application.dexItemFactory, graphLense);
     }
 
-    private DexEncodedMethod[] removeDupes(DexEncodedMethod[] methods) {
-      if (methods == null) {
-        return null;
-      }
-      Map<DexMethod, DexEncodedMethod> filtered = new IdentityHashMap<>();
-      for (DexEncodedMethod method : methods) {
-        DexEncodedMethod previous = filtered.put(method.method, method);
-        if (previous != null) {
-          if (!previous.accessFlags.isBridge()) {
-            if (!method.accessFlags.isBridge()) {
-              throw new CompilationError(
-                  "Class merging produced invalid result on: " + previous.toSourceString());
-            } else {
-              filtered.put(previous.method, previous);
-            }
-          }
-        }
-      }
-      if (filtered.size() == methods.length) {
-        return methods;
-      }
-      return filtered.values().toArray(DexEncodedMethod.EMPTY_ARRAY);
-    }
-
     private DexEncodedMethod[] substituteTypesIn(DexEncodedMethod[] methods) {
       if (methods == null) {
         return null;
@@ -1473,12 +1451,9 @@
       for (int i = 0; i < methods.length; i++) {
         DexEncodedMethod encodedMethod = methods[i];
         DexMethod method = encodedMethod.method;
-        DexProto newProto = getUpdatedProto(method.proto);
-        DexType newHolder = fixupType(method.holder);
-        DexMethod newMethod = application.dexItemFactory.createMethod(newHolder, newProto,
-            method.name);
-        if (newMethod != encodedMethod.method) {
-          lense.move(encodedMethod.method, newMethod);
+        DexMethod newMethod = fixupMethod(method);
+        if (newMethod != method) {
+          lense.move(method, newMethod);
           methods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
         }
       }
@@ -1503,7 +1478,12 @@
       return fields;
     }
 
-    private DexProto getUpdatedProto(DexProto proto) {
+    private DexMethod fixupMethod(DexMethod method) {
+      return application.dexItemFactory.createMethod(
+          fixupType(method.holder), fixupProto(method.proto), method.name);
+    }
+
+    private DexProto fixupProto(DexProto proto) {
       DexProto result = protoFixupCache.get(proto);
       if (result == null) {
         DexType returnType = fixupType(proto.returnType);
@@ -1520,12 +1500,13 @@
         DexType fixed = fixupType(base);
         if (base == fixed) {
           return type;
-        } else {
-          return type.replaceBaseType(fixed, application.dexItemFactory);
         }
+        return type.replaceBaseType(fixed, application.dexItemFactory);
       }
-      while (mergedClasses.containsKey(type)) {
-        type = mergedClasses.get(type);
+      if (type.isClassType()) {
+        while (mergedClasses.containsKey(type)) {
+          type = mergedClasses.get(type);
+        }
       }
       return type;
     }
@@ -1678,6 +1659,11 @@
     }
 
     @Override
+    public DexType getOriginalType(DexType type) {
+      throw new Unreachable();
+    }
+
+    @Override
     public DexField getOriginalFieldSignature(DexField field) {
       throw new Unreachable();
     }
@@ -1703,8 +1689,7 @@
     }
 
     @Override
-    public GraphLenseLookupResult lookupMethod(
-        DexMethod method, DexEncodedMethod context, Type type) {
+    public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
       // First look up the method using the existing graph lense (for example, the type will have
       // changed if the method was publicized by ClassAndMemberPublicizer).
       GraphLenseLookupResult lookup = graphLense.lookupMethod(method, context, type);
@@ -1748,7 +1733,7 @@
   public static class IllegalAccessDetector extends UseRegistry {
 
     private boolean foundIllegalAccess = false;
-    private DexEncodedMethod context = null;
+    private DexMethod context = null;
 
     private final AppView<? extends AppInfo> appView;
     private final DexClass source;
@@ -1764,7 +1749,7 @@
     }
 
     public void setContext(DexEncodedMethod context) {
-      this.context = context;
+      this.context = context.method;
     }
 
     private boolean checkFieldReference(DexField field) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 20a05ab..6576433 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -14,17 +13,14 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.shaking.VerticalClassMerger.SynthesizedBridgeCode;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
 
 // This graph lense is instantiated during vertical class merging. The graph lense is context
 // sensitive in the enclosing class of a given invoke *and* the type of the invoke (e.g., invoke-
@@ -55,7 +51,7 @@
 
   private final Map<DexType, Map<DexMethod, GraphLenseLookupResult>>
       contextualVirtualToDirectMethodMaps;
-  private final Set<DexMethod> mergedMethods;
+  private Set<DexMethod> mergedMethods;
   private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges;
 
   public VerticalClassMergerGraphLense(
@@ -83,19 +79,29 @@
   }
 
   @Override
+  public DexType getOriginalType(DexType type) {
+    return previousLense.getOriginalType(type);
+  }
+
+  @Override
   public DexMethod getOriginalMethodSignature(DexMethod method) {
     return super.getOriginalMethodSignature(
         originalMethodSignaturesForBridges.getOrDefault(method, method));
   }
 
   @Override
-  public GraphLenseLookupResult lookupMethod(
-      DexMethod method, DexEncodedMethod context, Type type) {
+  public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
     assert isContextFreeForMethod(method) || (context != null && type != null);
-    GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
-    if (previous.getType() == Type.SUPER && !mergedMethods.contains(context.method)) {
+    DexMethod previousContext =
+        originalMethodSignaturesForBridges.containsKey(context)
+            ? originalMethodSignaturesForBridges.get(context)
+            : originalMethodSignatures != null
+                ? originalMethodSignatures.getOrDefault(context, context)
+                : context;
+    GraphLenseLookupResult previous = previousLense.lookupMethod(method, previousContext, type);
+    if (previous.getType() == Type.SUPER && !mergedMethods.contains(context)) {
       Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
-          contextualVirtualToDirectMethodMaps.get(context.method.holder);
+          contextualVirtualToDirectMethodMaps.get(context.holder);
       if (virtualToDirectMethodMap != null) {
         GraphLenseLookupResult lookup = virtualToDirectMethodMap.get(previous.getMethod());
         if (lookup != null) {
@@ -113,10 +119,8 @@
   }
 
   @Override
-  protected Type mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
-    return super.mapVirtualInterfaceInvocationTypes(
-        appInfo, newMethod, originalMethod, context, type);
+  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
+    return super.mapVirtualInterfaceInvocationTypes(appInfo, newMethod, originalMethod, type);
   }
 
   @Override
@@ -170,7 +174,6 @@
     public GraphLense build(
         GraphLense previousLense,
         Map<DexType, DexType> mergedClasses,
-        List<SynthesizedBridgeCode> synthesizedBridges,
         AppInfo appInfo) {
       if (fieldMap.isEmpty()
           && methodMap.isEmpty()
@@ -179,14 +182,6 @@
       }
       Map<DexProto, DexProto> cache = new HashMap<>();
       BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
-      // Update all synthesized bridges.
-      Function<DexMethod, DexMethod> synthesizedBridgeTransformer =
-          method ->
-              getMethodSignatureAfterClassMerging(
-                  method, mergedClasses, appInfo.dexItemFactory, cache);
-      for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
-        synthesizedBridge.updateMethodSignatures(synthesizedBridgeTransformer);
-      }
       // Build new graph lense.
       return new VerticalClassMergerGraphLense(
           appInfo,
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 f34e059..4e66abf 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -825,7 +825,7 @@
   //
   // We used to insert a empty loop at the end, however, mediatek has an optimizer
   // on lollipop devices that cannot deal with an unreachable infinite loop, so we
-  // couldn't do that. See b/119895393.2
+  // couldn't do that. See b/119895393.
   public boolean canHaveTracingPastInstructionsStreamBug() {
     return minApiLevel < AndroidApiLevel.L.getLevel();
   }
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index ec12f82..c9be1b8 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -150,16 +151,17 @@
       // At this point we don't know if we really need to add this class to the builder.
       // It depends on whether any methods/fields are renamed or some methods contain positions.
       // Create a supplier which creates a new, cached ClassNaming.Builder on-demand.
+      DexType originalType = graphLense.getOriginalType(clazz.type);
       DexString renamedClassName = namingLens.lookupDescriptor(clazz.getType());
       Supplier<ClassNaming.Builder> onDemandClassNamingBuilder =
           Suppliers.memoize(
               () ->
                   classNameMapperBuilder.classNamingBuilder(
                       DescriptorUtils.descriptorToJavaType(renamedClassName.toString()),
-                      clazz.toString()));
+                      originalType.toSourceString()));
 
       // If the class is renamed add it to the classNamingBuilder.
-      addClassToClassNaming(clazz, renamedClassName, onDemandClassNamingBuilder);
+      addClassToClassNaming(originalType, renamedClassName, onDemandClassNamingBuilder);
 
       // First transfer renamed fields to classNamingBuilder.
       addFieldsToClassNaming(graphLense, namingLens, clazz, onDemandClassNamingBuilder);
@@ -306,10 +308,12 @@
   }
 
   @SuppressWarnings("ReturnValueIgnored")
-  private static void addClassToClassNaming(DexProgramClass clazz, DexString renamedClassName,
+  private static void addClassToClassNaming(
+      DexType originalType,
+      DexString renamedClassName,
       Supplier<Builder> onDemandClassNamingBuilder) {
     // We do know we need to create a ClassNaming.Builder if the class itself had been renamed.
-    if (!clazz.toString().equals(renamedClassName.toString())) {
+    if (originalType.descriptor != renamedClassName) {
       // Not using return value, it's registered in classNameMapperBuilder
       onDemandClassNamingBuilder.get();
     }
diff --git a/src/main/keep-compatdx.txt b/src/main/keep-compatdx.txt
index 75a8012..7275f7f 100644
--- a/src/main/keep-compatdx.txt
+++ b/src/main/keep-compatdx.txt
@@ -4,3 +4,13 @@
 
 -keep public class com.android.tools.r8.compatdx.CompatDx { public static void main(java.lang.String[]); }
 -keepattributes LineNumberTable
+
+# JvmMetadataExtensions must be kept because it'll be used indirectly through java.util.ServiceLoader.
+-keep, allowobfuscation public class com.android.tools.r8.jetbrains.kotlinx.metadata.jvm.impl.JvmMetadataExtensions { public <init>(); }
+
+# For now we need to keep MetadataExtensions to avoid minifying (obfuscating) the name, because
+# '-adaptresourcefilenames' doesn't rename it (the package name is not encoded in directories).
+-keep public class com.android.tools.r8.jetbrains.kotlinx.metadata.impl.extensions.MetadataExtensions { }
+
+# The contents of this file is ...JvmMetadataExtensions, which is renamed.
+-adaptresourcefilecontents META-INF/services/com.android.tools.r8.jetbrains.kotlinx.metadata.impl.extensions.MetadataExtensions
diff --git a/src/main/keep-compatproguard.txt b/src/main/keep-compatproguard.txt
index e29d13e..7944c1a 100644
--- a/src/main/keep-compatproguard.txt
+++ b/src/main/keep-compatproguard.txt
@@ -4,3 +4,13 @@
 
 -keep public class com.android.tools.r8.compatproguard.CompatProguard { public static void main(java.lang.String[]); }
 -keepattributes LineNumberTable
+
+# JvmMetadataExtensions must be kept because it'll be used indirectly through java.util.ServiceLoader.
+-keep, allowobfuscation public class com.android.tools.r8.jetbrains.kotlinx.metadata.jvm.impl.JvmMetadataExtensions { public <init>(); }
+
+# For now we need to keep MetadataExtensions to avoid minifying (obfuscating) the name, because
+# '-adaptresourcefilenames' doesn't rename it (the package name is not encoded in directories).
+-keep public class com.android.tools.r8.jetbrains.kotlinx.metadata.impl.extensions.MetadataExtensions { }
+
+# The contents of this file is ...JvmMetadataExtensions, which is renamed.
+-adaptresourcefilecontents META-INF/services/com.android.tools.r8.jetbrains.kotlinx.metadata.impl.extensions.MetadataExtensions
diff --git a/src/test/examples/multidex005/ArrayReference.java b/src/test/examples/multidex005/ArrayReference.java
new file mode 100644
index 0000000..b877486
--- /dev/null
+++ b/src/test/examples/multidex005/ArrayReference.java
@@ -0,0 +1,12 @@
+// 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 multidex005;
+
+public class ArrayReference {
+
+  public Object directReference() {
+    return new DirectlyReferenced[1];
+  }
+}
diff --git a/src/test/examples/multidex005/CheckCastReference.java b/src/test/examples/multidex005/CheckCastReference.java
new file mode 100644
index 0000000..3e04911
--- /dev/null
+++ b/src/test/examples/multidex005/CheckCastReference.java
@@ -0,0 +1,12 @@
+// 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 multidex005;
+
+public class CheckCastReference {
+
+  public Object directReference(Object obj) {
+    return (DirectlyReferenced) obj;
+  }
+}
diff --git a/src/test/examples/multidex005/InstanceOfReference.java b/src/test/examples/multidex005/InstanceOfReference.java
new file mode 100644
index 0000000..136deb8
--- /dev/null
+++ b/src/test/examples/multidex005/InstanceOfReference.java
@@ -0,0 +1,12 @@
+// 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 multidex005;
+
+public class InstanceOfReference {
+
+  public boolean directReference(Object obj) {
+    return obj instanceof DirectlyReferenced;
+  }
+}
diff --git a/src/test/examples/multidex005/main-dex-rules-10.txt b/src/test/examples/multidex005/main-dex-rules-10.txt
new file mode 100644
index 0000000..1c4629f
--- /dev/null
+++ b/src/test/examples/multidex005/main-dex-rules-10.txt
@@ -0,0 +1,7 @@
+# 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.
+
+-keep public class *.CheckCastReference {
+  <init>();
+}
diff --git a/src/test/examples/multidex005/main-dex-rules-8.txt b/src/test/examples/multidex005/main-dex-rules-8.txt
new file mode 100644
index 0000000..6e86e48
--- /dev/null
+++ b/src/test/examples/multidex005/main-dex-rules-8.txt
@@ -0,0 +1,7 @@
+# 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.
+
+-keep public class *.ArrayReference {
+  <init>();
+}
diff --git a/src/test/examples/multidex005/main-dex-rules-9.txt b/src/test/examples/multidex005/main-dex-rules-9.txt
new file mode 100644
index 0000000..d27beec
--- /dev/null
+++ b/src/test/examples/multidex005/main-dex-rules-9.txt
@@ -0,0 +1,7 @@
+# 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.
+
+-keep public class *.InstanceOfReference {
+  <init>();
+}
diff --git a/src/test/examples/multidex005/ref-list-10.txt b/src/test/examples/multidex005/ref-list-10.txt
new file mode 100644
index 0000000..d110f05
--- /dev/null
+++ b/src/test/examples/multidex005/ref-list-10.txt
@@ -0,0 +1,8 @@
+Lmultidex005/CheckCastReference;
+Lmultidex005/DirectlyReferenced;
+Lmultidex005/Interface1;
+Lmultidex005/Interface2;
+Lmultidex005/Interface3;
+Lmultidex005/SuperClass;
+Lmultidex005/SuperInterface;
+Lmultidex005/SuperSuperClass;
diff --git a/src/test/examples/multidex005/ref-list-8.txt b/src/test/examples/multidex005/ref-list-8.txt
new file mode 100644
index 0000000..3937484
--- /dev/null
+++ b/src/test/examples/multidex005/ref-list-8.txt
@@ -0,0 +1,8 @@
+Lmultidex005/ArrayReference;
+Lmultidex005/DirectlyReferenced;
+Lmultidex005/Interface1;
+Lmultidex005/Interface2;
+Lmultidex005/Interface3;
+Lmultidex005/SuperClass;
+Lmultidex005/SuperInterface;
+Lmultidex005/SuperSuperClass;
diff --git a/src/test/examples/multidex005/ref-list-9.txt b/src/test/examples/multidex005/ref-list-9.txt
new file mode 100644
index 0000000..c36015f
--- /dev/null
+++ b/src/test/examples/multidex005/ref-list-9.txt
@@ -0,0 +1,8 @@
+Lmultidex005/DirectlyReferenced;
+Lmultidex005/InstanceOfReference;
+Lmultidex005/Interface1;
+Lmultidex005/Interface2;
+Lmultidex005/Interface3;
+Lmultidex005/SuperClass;
+Lmultidex005/SuperInterface;
+Lmultidex005/SuperSuperClass;
diff --git a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
index c4425b1..1a4e0ec 100644
--- a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
@@ -4,8 +4,12 @@
 
 package com.android.tools.r8;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApp;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Matcher;
 
 public class Dex2OatTestRunResult extends TestRunResult<Dex2OatTestRunResult> {
 
@@ -17,4 +21,15 @@
   protected Dex2OatTestRunResult self() {
     return this;
   }
+
+  public Dex2OatTestRunResult assertNoVerificationErrors() {
+    assertSuccess();
+    Matcher<? super String> matcher =
+        CoreMatchers.not(CoreMatchers.containsString("Verification error"));
+    assertThat(
+        errorMessage("Run dex2oat produced verification errors.", matcher.toString()),
+        getStdErr(),
+        matcher);
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index a032fd6..89d0fff 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -108,11 +108,11 @@
     return builder.toString();
   }
 
-  private String errorMessage(String message) {
+  String errorMessage(String message) {
     return errorMessage(message, null);
   }
 
-  private String errorMessage(String message, String expected) {
+  String errorMessage(String message, String expected) {
     StringBuilder builder = new StringBuilder(message).append('\n');
     if (expected != null) {
       if (expected.contains(System.lineSeparator())) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/StaticInitializerTest.java b/src/test/java/com/android/tools/r8/classmerging/StaticInitializerTest.java
new file mode 100644
index 0000000..ce89fe1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/StaticInitializerTest.java
@@ -0,0 +1,73 @@
+// 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.classmerging;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StaticInitializerTest extends TestBase {
+
+  private final Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public StaticInitializerTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput = StringUtils.lines("In A.m()", "In B.<clinit>()", "In B.m()");
+
+    if (backend == Backend.CF) {
+      testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+    }
+
+    testForR8(backend)
+        .addInnerClasses(StaticInitializerTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .run(TestClass.class)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A.m();
+      B.m();
+    }
+  }
+
+  // Cannot be merged into B because that would change the semantics due to <clinit>.
+  static class A {
+
+    @NeverInline
+    public static void m() {
+      System.out.println("In A.m()");
+    }
+  }
+
+  static class B extends A {
+
+    static {
+      System.out.println("In B.<clinit>()");
+    }
+
+    @NeverInline
+    public static void m() {
+      System.out.println("In B.m()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 2284fa5..c4151d1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -86,16 +86,13 @@
 
   private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
       throws IOException, ExecutionException, CompilationFailedException {
-    ToolHelper.runR8(
-        R8Command.builder()
-            .setOutput(Paths.get(temp.getRoot().getCanonicalPath()), OutputMode.DexIndexed)
+    inspector =
+        testForR8(Backend.DEX)
             .addProgramFiles(EXAMPLE_JAR)
-            .addProguardConfigurationFiles(proguardConfig)
-            .setDisableMinification(true)
-            .build(),
-        optionsConsumer);
-    inspector = new CodeInspector(
-        Paths.get(temp.getRoot().getCanonicalPath()).resolve("classes.dex"));
+            .addKeepRuleFiles(proguardConfig)
+            .addOptionsModification(optionsConsumer)
+            .compile()
+            .inspector();
   }
 
   private CodeInspector inspector;
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 31170d3..3e5a03a 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -219,6 +219,21 @@
   }
 
   @Test
+  public void traceMainDexList005_8() throws Throwable {
+    doTest5(8);
+  }
+
+  @Test
+  public void traceMainDexList005_9() throws Throwable {
+    doTest5(9);
+  }
+
+  @Test
+  public void traceMainDexList005_10() throws Throwable {
+    doTest5(10);
+  }
+
+  @Test
   public void traceMainDexList006() throws Throwable {
     doTest(
         "traceMainDexList006",
diff --git a/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java b/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java
index e9bf581..378cf0e 100644
--- a/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java
+++ b/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java
@@ -27,14 +27,11 @@
   private void test(Path mapping) throws Exception {
     testForR8(Backend.DEX)
         .addInnerClasses(PrintMappingTest.class)
-        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-keep,allowobfuscation class " + TestClass.class.getTypeName())
         .addKeepRules("-printmapping " + mapping)
         .compile();
     assertTrue(mapping.toFile().exists());
   }
 
-  static class TestClass {
-
-    public static void main(String[] args) {}
-  }
+  static class TestClass {}
 }
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 1c28054..5b7a11e 100644
--- a/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
+++ b/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.DataResourceConsumer;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringResource;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -65,10 +66,15 @@
 
   // Return the package name in the app for this package.
   private String pathForThisPackage(AndroidApp app) throws Exception {
-    ClassNameMapper mapper =
-        ClassNameMapper.mapperFromString(app.getProguardMapOutputData().getString());
-    String x = mapper.getObfuscatedToOriginalMapping().inverse.get(Main.class.getCanonicalName());
-    return x.substring(0, x.lastIndexOf('.')).replace('.', '/');
+    String name;
+    if (app.getProguardMapOutputData() != null) {
+      ClassNameMapper mapper =
+          ClassNameMapper.mapperFromString(app.getProguardMapOutputData().getString());
+      name = mapper.getObfuscatedToOriginalMapping().inverse.get(Main.class.getCanonicalName());
+    } else {
+      name = Main.class.getTypeName();
+    }
+    return name.substring(0, name.lastIndexOf('.')).replace('.', '/');
   }
 
   private void checkResourceNames(Set<String> resourceNames, String expectedPackageName) {
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index b302615..0b246a5 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -260,9 +260,8 @@
     }
 
     if (inspection != null) {
-      CodeInspector inspector = new CodeInspector(out,
-          minify.isMinify() ? proguardMap.toString()
-              : null);
+      CodeInspector inspector =
+          new CodeInspector(out, minify.isMinify() ? proguardMap.toString() : null);
       inspection.accept(inspector);
     }
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
index f65c1d4..3833645 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
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 9983320..a9058ff 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
@@ -45,6 +45,7 @@
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.lang.reflect.Method;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
@@ -82,13 +83,14 @@
   public CodeInspector(
       List<Path> files, String mappingFile, Consumer<InternalOptions> optionsConsumer)
       throws IOException, ExecutionException {
-    if (mappingFile != null) {
-      this.mapping = ClassNameMapper.mapperFromFile(Paths.get(mappingFile));
-      BiMapContainer<String, String> nameMapping = this.mapping.getObfuscatedToOriginalMapping();
+    Path mappingPath = mappingFile != null ? Paths.get(mappingFile) : null;
+    if (mappingPath != null && Files.exists(mappingPath)) {
+      mapping = ClassNameMapper.mapperFromFile(mappingPath);
+      BiMapContainer<String, String> nameMapping = mapping.getObfuscatedToOriginalMapping();
       obfuscatedToOriginalMapping = nameMapping.original;
       originalToObfuscatedMapping = nameMapping.inverse;
     } else {
-      this.mapping = null;
+      mapping = null;
       originalToObfuscatedMapping = null;
       obfuscatedToOriginalMapping = null;
     }