Merge commit 'ed1d94f032b04f833b5efdeaa7459e977c38c710' into dev-release
diff --git a/.gitignore b/.gitignore
index b8c394a..90f5857 100644
--- a/.gitignore
+++ b/.gitignore
@@ -87,6 +87,10 @@
 third_party/dart-sdk.tar.gz
 third_party/ddmlib
 third_party/ddmlib.tar.gz
+third_party/dependencies/
+third_party/dependencies.tar.gz
+third_party/dependencies_new/
+third_party/dependencies_new.tar.gz
 third_party/desugar/desugar_*.tar.gz
 third_party/desugar/desugar_*/
 third_party/framework
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 875a4fe..7e8904d 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -1124,15 +1124,22 @@
       protos.clear();
       types.clear();
       strings.clear();
+      callSites.clear();
+      methodHandles.clear();
 
       indexedItemsReferencedFromClassesInTransaction.clear();
     }
 
     public boolean isEmpty() {
-      return classes.isEmpty() && fields.isEmpty() && methods.isEmpty() && protos.isEmpty()
-          && types.isEmpty() && strings.isEmpty();
+      return classes.isEmpty()
+          && fields.isEmpty()
+          && methods.isEmpty()
+          && protos.isEmpty()
+          && types.isEmpty()
+          && strings.isEmpty()
+          && callSites.isEmpty()
+          && methodHandles.isEmpty();
     }
-
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index ee86680..54fecaa 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -236,6 +236,10 @@
     return self();
   }
 
+  public boolean isPromotedFromPrivateToPublic() {
+    return isDemoted(Constants.ACC_PRIVATE) && isPromoted(Constants.ACC_PUBLIC);
+  }
+
   public boolean isPromotedToPublic() {
     return isPromoted(Constants.ACC_PUBLIC);
   }
@@ -277,6 +281,10 @@
     modifiedFlags &= ~flag;
   }
 
+  protected boolean isDemoted(int flag) {
+    return wasSet(flag) && !isSet(flag);
+  }
+
   protected boolean isPromoted(int flag) {
     return !wasSet(flag) && isSet(flag);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index aff2b31..4eff317 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -41,6 +41,7 @@
 import com.android.tools.r8.shaking.AssumeInfoCollection;
 import com.android.tools.r8.shaking.KeepClassInfo;
 import com.android.tools.r8.shaking.KeepFieldInfo;
+import com.android.tools.r8.shaking.KeepInfo;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.KeepMethodInfo;
 import com.android.tools.r8.shaking.LibraryModeledPredicate;
@@ -679,6 +680,15 @@
     return keepInfo;
   }
 
+  public KeepInfo<?, ?> getKeepInfo(ProgramDefinition definition) {
+    return definition
+        .getReference()
+        .apply(
+            clazz -> getKeepInfo(definition.asProgramClass()),
+            field -> getKeepInfo(definition.asProgramField()),
+            method -> getKeepInfo(definition.asProgramMethod()));
+  }
+
   public KeepClassInfo getKeepInfo(DexProgramClass clazz) {
     return getKeepInfo().getClassInfo(clazz);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ClassDefinition.java b/src/main/java/com/android/tools/r8/graph/ClassDefinition.java
index 589dd76..a021545 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassDefinition.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.references.ClassReference;
+import java.util.function.BiConsumer;
+import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
 public interface ClassDefinition extends Definition {
@@ -15,6 +17,11 @@
 
   void forEachClassMethod(Consumer<? super DexClassAndMethod> consumer);
 
+  void forEachImmediateSuperClassMatching(
+      DexDefinitionSupplier definitions,
+      BiPredicate<? super DexType, ? super DexClass> predicate,
+      BiConsumer<? super DexType, ? super DexClass> consumer);
+
   MethodCollection getMethodCollection();
 
   ClassReference getClassReference();
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 1d7c8cf..30909c5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -37,6 +37,7 @@
 import java.util.ListIterator;
 import java.util.Set;
 import java.util.function.BiConsumer;
+import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -803,6 +804,20 @@
     }
   }
 
+  @Override
+  public void forEachImmediateSuperClassMatching(
+      DexDefinitionSupplier definitions,
+      BiPredicate<? super DexType, ? super DexClass> predicate,
+      BiConsumer<? super DexType, ? super DexClass> consumer) {
+    forEachImmediateSupertype(
+        supertype -> {
+          DexClass superclass = definitions.definitionFor(supertype);
+          if (predicate.test(supertype, superclass)) {
+            consumer.accept(supertype, superclass);
+          }
+        });
+  }
+
   public void forEachImmediateSupertype(Consumer<DexType> fn) {
     if (superType != null) {
       fn.accept(superType);
diff --git a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
index 521d6c7..8b9a38b 100644
--- a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
@@ -9,6 +9,7 @@
 import static com.google.common.base.Predicates.alwaysTrue;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -32,8 +33,18 @@
 
   public static ImmediateProgramSubtypingInfo create(
       AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return internalCreate(appView, appView.appInfo().classes());
+  }
+
+  public static ImmediateProgramSubtypingInfo createWithDeterministicOrder(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return internalCreate(appView, appView.appInfo().classesWithDeterministicOrder());
+  }
+
+  private static ImmediateProgramSubtypingInfo internalCreate(
+      AppView<? extends AppInfoWithClassHierarchy> appView, Collection<DexProgramClass> classes) {
     Map<DexProgramClass, List<DexProgramClass>> immediateSubtypes = new IdentityHashMap<>();
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
+    for (DexProgramClass clazz : classes) {
       clazz.forEachImmediateSupertype(
           supertype -> {
             DexProgramClass superclass = asProgramClassOrNull(appView.definitionFor(supertype));
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
index 24c8b0a..77d8fbd 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -372,7 +372,7 @@
       DexEncodedMethod method = directMethods[i];
       DexEncodedMethod newMethod = replacement.apply(method);
       assert newMethod != null;
-      if (method != newMethod) {
+      if (method != newMethod || !method.belongsToDirectPool()) {
         if (belongsToDirectPool(newMethod)) {
           directMethods[i] = newMethod;
         } else {
@@ -404,7 +404,7 @@
     for (int i = 0; i < virtualMethods.length; i++) {
       DexEncodedMethod method = virtualMethods[i];
       DexEncodedMethod newMethod = replacement.apply(method);
-      if (method != newMethod) {
+      if (method != newMethod || !method.belongsToVirtualPool()) {
         if (belongsToVirtualPool(newMethod)) {
           virtualMethods[i] = newMethod;
         } else {
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index a31e669..47d8a02 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -269,6 +269,13 @@
     backing.replaceMethods(replacement);
   }
 
+  @SuppressWarnings("unchecked")
+  public <T extends DexClassAndMethod> void replaceClassAndMethods(
+      Function<T, DexEncodedMethod> replacement) {
+    assert holder.isProgramClass();
+    replaceMethods(method -> replacement.apply((T) DexClassAndMethod.create(holder, method)));
+  }
+
   public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
     resetDirectMethodCaches();
     backing.replaceDirectMethods(replacement);
diff --git a/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java b/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java
index 8a4c7bd..ce17027 100644
--- a/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java
+++ b/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodSignature;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -17,33 +16,34 @@
 import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
+import com.android.tools.r8.optimize.utils.ConcurrentNonProgramMethodsCollection;
+import com.android.tools.r8.optimize.utils.NonProgramMethodsCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepMethodInfo;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
+import com.android.tools.r8.utils.collections.DexMethodSignatureBiMap;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
 public class ConcurrentMethodFixup {
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final Map<ClasspathOrLibraryClass, DexMethodSignatureSet> nonProgramVirtualMethods =
-      new ConcurrentHashMap<>();
+  private final NonProgramMethodsCollection nonProgramVirtualMethods;
   private final ProgramClassFixer programClassFixer;
 
   public ConcurrentMethodFixup(
       AppView<AppInfoWithLiveness> appView, ProgramClassFixer programClassFixer) {
     this.appView = appView;
+    this.nonProgramVirtualMethods =
+        ConcurrentNonProgramMethodsCollection.createVirtualMethodsCollection(appView);
     this.programClassFixer = programClassFixer;
   }
 
@@ -77,7 +77,8 @@
   private void processConnectedProgramComponents(Set<DexProgramClass> classes) {
     List<DexProgramClass> sorted = new ArrayList<>(classes);
     sorted.sort(Comparator.comparing(DexClass::getType));
-    BiMap<DexMethodSignature, DexMethodSignature> componentSignatures = HashBiMap.create();
+    DexMethodSignatureBiMap<DexMethodSignature> componentSignatures =
+        new DexMethodSignatureBiMap<>();
 
     // 1) Reserve all library overrides and pinned virtual methods.
     reserveComponentPinnedAndInterfaceMethodSignatures(sorted, componentSignatures);
@@ -105,7 +106,7 @@
   private void processClass(
       DexProgramClass clazz,
       Set<DexProgramClass> processedClasses,
-      BiMap<DexMethodSignature, DexMethodSignature> componentSignatures) {
+      DexMethodSignatureBiMap<DexMethodSignature> componentSignatures) {
     assert !clazz.isInterface();
     if (!processedClasses.add(clazz)) {
       return;
@@ -122,7 +123,7 @@
   private void processInterface(
       DexProgramClass clazz,
       Set<DexProgramClass> processedInterfaces,
-      BiMap<DexMethodSignature, DexMethodSignature> componentSignatures) {
+      DexMethodSignatureBiMap<DexMethodSignature> componentSignatures) {
     assert clazz.isInterface();
     if (!processedInterfaces.add(clazz)) {
       return;
@@ -148,7 +149,7 @@
   }
 
   private MethodNamingUtility createMethodNamingUtility(
-      BiMap<DexMethodSignature, DexMethodSignature> inheritedSignatures, DexProgramClass clazz) {
+      DexMethodSignatureBiMap<DexMethodSignature> componentSignatures, DexProgramClass clazz) {
     BiMap<DexMethod, DexMethod> localSignatures = HashBiMap.create();
     clazz.forEachProgramInstanceInitializer(
         method -> {
@@ -156,12 +157,12 @@
             localSignatures.put(method.getReference(), method.getReference());
           }
         });
-    return new MethodNamingUtility(appView.dexItemFactory(), inheritedSignatures, localSignatures);
+    return new MethodNamingUtility(appView.dexItemFactory(), componentSignatures, localSignatures);
   }
 
   private void reserveComponentPinnedAndInterfaceMethodSignatures(
       List<DexProgramClass> stronglyConnectedProgramClasses,
-      BiMap<DexMethodSignature, DexMethodSignature> componentSignatures) {
+      DexMethodSignatureBiMap<DexMethodSignature> componentSignatures) {
     Set<ClasspathOrLibraryClass> seenNonProgramClasses = Sets.newIdentityHashSet();
     for (DexProgramClass clazz : stronglyConnectedProgramClasses) {
       // If a private or static method is pinned, we need to reserve the mapping to avoid creating
@@ -173,47 +174,16 @@
               componentSignatures.put(method.getMethodSignature(), method.getMethodSignature());
             }
           });
-      clazz.forEachImmediateSupertype(
-          supertype -> {
-            DexClass superclass = appView.definitionFor(supertype);
-            if (superclass != null
-                && !superclass.isProgramClass()
-                && seenNonProgramClasses.add(superclass.asClasspathOrLibraryClass())) {
-              for (DexMethodSignature vMethod :
-                  getOrComputeNonProgramVirtualMethods(superclass.asClasspathOrLibraryClass())) {
-                componentSignatures.put(vMethod, vMethod);
-              }
-            }
-          });
+      clazz.forEachImmediateSuperClassMatching(
+          appView,
+          (supertype, superclass) ->
+              superclass != null
+                  && !superclass.isProgramClass()
+                  && seenNonProgramClasses.add(superclass.asClasspathOrLibraryClass()),
+          (supertype, superclass) ->
+              componentSignatures.putAllToIdentity(
+                  nonProgramVirtualMethods.getOrComputeNonProgramMethods(
+                      superclass.asClasspathOrLibraryClass())));
     }
   }
-
-  private DexMethodSignatureSet getOrComputeNonProgramVirtualMethods(
-      ClasspathOrLibraryClass clazz) {
-    DexMethodSignatureSet libraryMethodsOnClass = nonProgramVirtualMethods.get(clazz);
-    if (libraryMethodsOnClass != null) {
-      return libraryMethodsOnClass;
-    }
-    return computeNonProgramVirtualMethods(clazz);
-  }
-
-  private DexMethodSignatureSet computeNonProgramVirtualMethods(
-      ClasspathOrLibraryClass classpathOrLibraryClass) {
-    DexClass clazz = classpathOrLibraryClass.asDexClass();
-    DexMethodSignatureSet libraryMethodsOnClass = DexMethodSignatureSet.create();
-    clazz.forEachImmediateSupertype(
-        supertype -> {
-          DexClass superclass = appView.definitionFor(supertype);
-          if (superclass != null) {
-            assert !superclass.isProgramClass();
-            libraryMethodsOnClass.addAll(
-                getOrComputeNonProgramVirtualMethods(superclass.asClasspathOrLibraryClass()));
-          }
-        });
-    clazz.forEachClassMethodMatching(
-        DexEncodedMethod::belongsToVirtualPool,
-        method -> libraryMethodsOnClass.add(method.getMethodSignature()));
-    nonProgramVirtualMethods.put(classpathOrLibraryClass, libraryMethodsOnClass);
-    return libraryMethodsOnClass;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/fixup/MethodNamingUtility.java b/src/main/java/com/android/tools/r8/graph/fixup/MethodNamingUtility.java
index 977e01c..688853d 100644
--- a/src/main/java/com/android/tools/r8/graph/fixup/MethodNamingUtility.java
+++ b/src/main/java/com/android/tools/r8/graph/fixup/MethodNamingUtility.java
@@ -10,18 +10,19 @@
 import com.android.tools.r8.graph.DexMethodSignature;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.collections.DexMethodSignatureBiMap;
 import com.google.common.collect.BiMap;
 import java.util.function.BiConsumer;
 
 public class MethodNamingUtility {
 
   private final DexItemFactory factory;
-  private final BiMap<DexMethodSignature, DexMethodSignature> inheritedSignatures;
+  private final DexMethodSignatureBiMap<DexMethodSignature> inheritedSignatures;
   private final BiMap<DexMethod, DexMethod> localSignatures;
 
   public MethodNamingUtility(
       DexItemFactory factory,
-      BiMap<DexMethodSignature, DexMethodSignature> inheritedSignatures,
+      DexMethodSignatureBiMap<DexMethodSignature> inheritedSignatures,
       BiMap<DexMethod, DexMethod> localSignatures) {
     this.factory = factory;
     this.inheritedSignatures = inheritedSignatures;
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 a46b0a2..d72358a 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
@@ -42,6 +42,8 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -692,18 +694,28 @@
     return false;
   }
 
-  private boolean consistentDefUseChains() {
-    Set<Value> values = Sets.newIdentityHashSet();
+  private void addValueAndCheckUniqueNumber(Int2ReferenceMap<Value> values, Value value) {
+    assert value != null;
+    int number = value.getNumber();
+    Value old = values.put(number, value);
+    assert options.testing.ignoreValueNumbering
+            || old == null
+            || old == value
+            || (number == -1 && value.isValueOnStack())
+        : "Multiple value definitions with number " + number + ": " + value + " and " + old;
+  }
 
+  private boolean consistentDefUseChains() {
+    Int2ReferenceMap<Value> values = new Int2ReferenceOpenHashMap<>();
     for (BasicBlock block : blocks) {
       int predecessorCount = block.getPredecessors().size();
       // Check that all phi uses are consistent.
       for (Phi phi : block.getPhis()) {
         assert !phi.isTrivialPhi();
         assert phi.getOperands().size() == predecessorCount;
-        values.add(phi);
+        addValueAndCheckUniqueNumber(values, phi);
         for (Value value : phi.getOperands()) {
-          values.add(value);
+          addValueAndCheckUniqueNumber(values, value);
           assert value.uniquePhiUsers().contains(phi);
           assert !phi.hasLocalInfo() || phi.getLocalInfo() == value.getLocalInfo();
           assert value.isPhi() || value.definition.hasBlock();
@@ -713,21 +725,21 @@
         assert instruction.getBlock() == block;
         Value outValue = instruction.outValue();
         if (outValue != null) {
-          values.add(outValue);
+          addValueAndCheckUniqueNumber(values, outValue);
           assert outValue.definition == instruction;
         }
         for (Value value : instruction.inValues()) {
-          values.add(value);
+          addValueAndCheckUniqueNumber(values, value);
           assert value.uniqueUsers().contains(instruction);
         }
         for (Value value : instruction.getDebugValues()) {
-          values.add(value);
+          addValueAndCheckUniqueNumber(values, value);
           assert value.debugUsers().contains(instruction);
         }
       }
     }
 
-    for (Value value : values) {
+    for (Value value : values.values()) {
       assert verifyValue(value);
       assert consistentValueUses(value);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java
index 5560d5b..313f4d1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java
@@ -9,7 +9,9 @@
 import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation.CallGraphBasedCallSiteInformation;
 import com.android.tools.r8.ir.conversion.callgraph.CycleEliminator.CycleEliminationResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.Collection;
 import java.util.Iterator;
@@ -77,7 +79,10 @@
   }
 
   private ProgramMethodSet extractNodes(Predicate<Node> predicate, Consumer<Node> clean) {
-    ProgramMethodSet result = ProgramMethodSet.create();
+    ProgramMethodSet result =
+        InternalOptions.DETERMINISTIC_DEBUGGING
+            ? SortedProgramMethodSet.create()
+            : ProgramMethodSet.create();
     Set<Node> removed = Sets.newIdentityHashSet();
     Iterator<Node> nodeIterator = nodes.values().iterator();
     while (nodeIterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
index 93ad3ff..a0edfc5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
@@ -4,9 +4,11 @@
 
 package com.android.tools.r8.ir.conversion.passes;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.And;
 import com.android.tools.r8.ir.code.Binop;
@@ -42,18 +44,17 @@
 
   private Map<Class<?>, BinopDescriptor> createBinopDescriptors() {
     ImmutableMap.Builder<Class<?>, BinopDescriptor> builder = ImmutableMap.builder();
-    builder.put(Add.class, new BinopDescriptor(0, 0, null, null));
-    builder.put(Sub.class, new BinopDescriptor(null, 0, null, null));
-    builder.put(Mul.class, new BinopDescriptor(1, 1, 0, 0));
-    // The following two can be improved if we handle ZeroDivide.
-    builder.put(Div.class, new BinopDescriptor(null, 1, null, null));
-    builder.put(Rem.class, new BinopDescriptor(null, null, null, null));
-    builder.put(And.class, new BinopDescriptor(ALL_BITS_SET, ALL_BITS_SET, 0, 0));
-    builder.put(Or.class, new BinopDescriptor(0, 0, ALL_BITS_SET, ALL_BITS_SET));
-    builder.put(Xor.class, new BinopDescriptor(0, 0, null, null));
-    builder.put(Shl.class, new BinopDescriptor(null, 0, 0, null));
-    builder.put(Shr.class, new BinopDescriptor(null, 0, 0, null));
-    builder.put(Ushr.class, new BinopDescriptor(null, 0, 0, null));
+    builder.put(Add.class, BinopDescriptor.ADD);
+    builder.put(Sub.class, BinopDescriptor.SUB);
+    builder.put(Mul.class, BinopDescriptor.MUL);
+    builder.put(Div.class, BinopDescriptor.DIV);
+    builder.put(Rem.class, BinopDescriptor.REM);
+    builder.put(And.class, BinopDescriptor.AND);
+    builder.put(Or.class, BinopDescriptor.OR);
+    builder.put(Xor.class, BinopDescriptor.XOR);
+    builder.put(Shl.class, BinopDescriptor.SHL);
+    builder.put(Shr.class, BinopDescriptor.SHR);
+    builder.put(Ushr.class, BinopDescriptor.USHR);
     return builder.build();
   }
 
@@ -64,24 +65,176 @@
    * - i is right identity if for each x in K, x * i = x.
    * - a is left absorbing if for each x in K, a * x = a.
    * - a is right absorbing if for each x in K, x * a = a.
+   * In a space K, a binop * is associative if for each x,y,z in K, (x * y) * z = x * (y * z).
    * </code>
    */
-  private static class BinopDescriptor {
+  private enum BinopDescriptor {
+    ADD(0, 0, null, null, true) {
+      @Override
+      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+        return Add.create(numericType, dest, left, right);
+      }
+
+      @Override
+      int evaluate(int left, int right) {
+        return left + right;
+      }
+
+      @Override
+      long evaluate(long left, long right) {
+        return left + right;
+      }
+    },
+    SUB(null, 0, null, null, false) {
+      @Override
+      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+        return new Sub(numericType, dest, left, right);
+      }
+
+      @Override
+      int evaluate(int left, int right) {
+        return left - right;
+      }
+
+      @Override
+      long evaluate(long left, long right) {
+        return left - right;
+      }
+    },
+    MUL(1, 1, 0, 0, true) {
+      @Override
+      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+        return Mul.create(numericType, dest, left, right);
+      }
+
+      @Override
+      int evaluate(int left, int right) {
+        return left * right;
+      }
+
+      @Override
+      long evaluate(long left, long right) {
+        return left * right;
+      }
+    },
+    // The following two can be improved if we handle ZeroDivide.
+    DIV(null, 1, null, null, false),
+    REM(null, null, null, null, false),
+    AND(ALL_BITS_SET, ALL_BITS_SET, 0, 0, true) {
+      @Override
+      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+        return And.create(numericType, dest, left, right);
+      }
+
+      @Override
+      int evaluate(int left, int right) {
+        return left & right;
+      }
+
+      @Override
+      long evaluate(long left, long right) {
+        return left & right;
+      }
+    },
+    OR(0, 0, ALL_BITS_SET, ALL_BITS_SET, true) {
+      @Override
+      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+        return Or.create(numericType, dest, left, right);
+      }
+
+      @Override
+      int evaluate(int left, int right) {
+        return left | right;
+      }
+
+      @Override
+      long evaluate(long left, long right) {
+        return left | right;
+      }
+    },
+    XOR(0, 0, null, null, true) {
+      @Override
+      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+        return Xor.create(numericType, dest, left, right);
+      }
+
+      @Override
+      int evaluate(int left, int right) {
+        return left ^ right;
+      }
+
+      @Override
+      long evaluate(long left, long right) {
+        return left ^ right;
+      }
+    },
+    SHL(null, 0, 0, null, false) {
+      @Override
+      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+        return new Shl(numericType, dest, left, right);
+      }
+
+      @Override
+      boolean isShift() {
+        return true;
+      }
+    },
+    SHR(null, 0, 0, null, false) {
+      @Override
+      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+        return new Shr(numericType, dest, left, right);
+      }
+
+      @Override
+      boolean isShift() {
+        return true;
+      }
+    },
+    USHR(null, 0, 0, null, false) {
+      @Override
+      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+        return new Ushr(numericType, dest, left, right);
+      }
+
+      @Override
+      boolean isShift() {
+        return true;
+      }
+    };
 
     final Integer leftIdentity;
     final Integer rightIdentity;
     final Integer leftAbsorbing;
     final Integer rightAbsorbing;
+    final boolean associativeAndCommutative;
 
-    private BinopDescriptor(
+    BinopDescriptor(
         Integer leftIdentity,
         Integer rightIdentity,
         Integer leftAbsorbing,
-        Integer rightAbsorbing) {
+        Integer rightAbsorbing,
+        boolean associativeAndCommutative) {
       this.leftIdentity = leftIdentity;
       this.rightIdentity = rightIdentity;
       this.leftAbsorbing = leftAbsorbing;
       this.rightAbsorbing = rightAbsorbing;
+      this.associativeAndCommutative = associativeAndCommutative;
+    }
+
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      throw new Unreachable();
+    }
+
+    int evaluate(int left, int right) {
+      throw new Unreachable();
+    }
+
+    long evaluate(long left, long right) {
+      throw new Unreachable();
+    }
+
+    boolean isShift() {
+      return false;
     }
   }
 
@@ -106,30 +259,10 @@
             || binop.getNumericType() == NumericType.LONG) {
           BinopDescriptor binopDescriptor = descriptors.get(binop.getClass());
           assert binopDescriptor != null;
-          ConstNumber constNumber = getConstNumber(binop.leftValue());
-          if (constNumber != null) {
-            if (simplify(
-                binop,
-                iterator,
-                constNumber,
-                binopDescriptor.leftIdentity,
-                binop.rightValue(),
-                binopDescriptor.leftAbsorbing,
-                binop.leftValue())) {
-              continue;
-            }
+          if (identityAbsorbingSimplification(iterator, binop, binopDescriptor)) {
+            continue;
           }
-          constNumber = getConstNumber(binop.rightValue());
-          if (constNumber != null) {
-            simplify(
-                binop,
-                iterator,
-                constNumber,
-                binopDescriptor.rightIdentity,
-                binop.leftValue(),
-                binopDescriptor.rightAbsorbing,
-                binop.rightValue());
-          }
+          successiveSimplification(iterator, binop, binopDescriptor, code);
         }
       }
     }
@@ -137,6 +270,148 @@
     assert code.isConsistentSSA(appView);
   }
 
+  private void successiveSimplification(
+      InstructionListIterator iterator, Binop binop, BinopDescriptor binopDescriptor, IRCode code) {
+    if (binop.outValue().hasDebugUsers()) {
+      return;
+    }
+    ConstNumber constBLeft = getConstNumber(binop.leftValue());
+    ConstNumber constBRight = getConstNumber(binop.rightValue());
+    if ((constBLeft != null && constBRight != null)
+        || (constBLeft == null && constBRight == null)) {
+      return;
+    }
+    Value otherValue = constBLeft == null ? binop.leftValue() : binop.rightValue();
+    if (otherValue.isPhi() || !otherValue.getDefinition().isBinop()) {
+      return;
+    }
+    Binop prevBinop = otherValue.getDefinition().asBinop();
+    ConstNumber constALeft = getConstNumber(prevBinop.leftValue());
+    ConstNumber constARight = getConstNumber(prevBinop.rightValue());
+    if ((constALeft != null && constARight != null)
+        || (constALeft == null && constARight == null)) {
+      return;
+    }
+    ConstNumber constB = constBLeft == null ? constBRight : constBLeft;
+    ConstNumber constA = constALeft == null ? constARight : constALeft;
+    Value input = constALeft == null ? prevBinop.leftValue() : prevBinop.rightValue();
+    // We have two successive binops so that a,b constants, x the input and a * x * b.
+    if (prevBinop.getClass() == binop.getClass()) {
+      if (binopDescriptor.associativeAndCommutative) {
+        // a * x * b => x * (a * b) where (a * b) is a constant.
+        assert binop.isCommutative();
+        Value newConst = addNewConstNumber(code, iterator, constB, constA, binopDescriptor);
+        replaceBinop(iterator, code, input, newConst, binopDescriptor);
+      } else if (binopDescriptor.isShift()) {
+        // x shift: a shift: b => x shift: (a + b) where a + b is a constant.
+        if (constBRight != null && constARight != null) {
+          Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD);
+          replaceBinop(iterator, code, input, newConst, binopDescriptor);
+        }
+      } else if (binop.isSub() && constBRight != null) {
+        // a - x - b => (a - b) - x where (a - b) is a constant.
+        // x - a - b => x - (a + b) where (a + b) is a constant.
+        // We ignore b - (x - a) and b - (a - x) with constBRight != null.
+        if (constARight == null) {
+          Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB);
+          replaceBinop(iterator, code, newConst, input, BinopDescriptor.SUB);
+        } else {
+          Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD);
+          replaceBinop(iterator, code, input, newConst, BinopDescriptor.SUB);
+        }
+      }
+    } else {
+      if (binop.isSub() && prevBinop.isAdd() && constBRight != null) {
+        // x + a - b => x + (a - b) where (a - b) is a constant.
+        // a + x - b => x + (a - b) where (a - b) is a constant.
+        // We ignore b - (x + a) and b - (a + x) with constBRight != null.
+        Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB);
+        replaceBinop(iterator, code, newConst, input, BinopDescriptor.ADD);
+      } else if (binop.isAdd() && prevBinop.isSub()) {
+        // x - a + b => x - (a - b) where (a - b) is a constant.
+        // a - x + b => (a + b) - x where (a + b) is a constant.
+        if (constALeft == null) {
+          Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB);
+          replaceBinop(iterator, code, input, newConst, BinopDescriptor.SUB);
+        } else {
+          Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD);
+          replaceBinop(iterator, code, newConst, input, BinopDescriptor.SUB);
+        }
+      }
+    }
+  }
+
+  private void replaceBinop(
+      InstructionListIterator iterator,
+      IRCode code,
+      Value left,
+      Value right,
+      BinopDescriptor binopDescriptor) {
+    Binop newBinop = instantiateBinop(code, left, right, binopDescriptor);
+    iterator.replaceCurrentInstruction(newBinop);
+    // We need to reset the iterator state after replaceCurrentInstruction so that Iterator#remove()
+    // can work in identityAbsorbingSimplification by calling previous then next.
+    iterator.previous();
+    iterator.next();
+    identityAbsorbingSimplification(iterator, newBinop, binopDescriptor);
+  }
+
+  private Binop instantiateBinop(IRCode code, Value left, Value right, BinopDescriptor descriptor) {
+    TypeElement representative = left.getType().isInt() ? right.getType() : left.getType();
+    Value newValue = code.createValue(representative);
+    NumericType numericType = representative.isInt() ? NumericType.INT : NumericType.LONG;
+    return descriptor.instantiate(numericType, newValue, left, right);
+  }
+
+  private Value addNewConstNumber(
+      IRCode code,
+      InstructionListIterator iterator,
+      ConstNumber left,
+      ConstNumber right,
+      BinopDescriptor descriptor) {
+    TypeElement representative =
+        left.outValue().getType().isInt() ? right.outValue().getType() : left.outValue().getType();
+    long result =
+        representative.isInt()
+            ? descriptor.evaluate(left.getIntValue(), right.getIntValue())
+            : descriptor.evaluate(left.getLongValue(), right.getLongValue());
+    iterator.previous();
+    Value value =
+        iterator.insertConstNumberInstruction(
+            code, appView.options(), result, left.outValue().getType());
+    iterator.next();
+    return value;
+  }
+
+  private boolean identityAbsorbingSimplification(
+      InstructionListIterator iterator, Binop binop, BinopDescriptor binopDescriptor) {
+    ConstNumber constNumber = getConstNumber(binop.leftValue());
+    if (constNumber != null) {
+      if (simplify(
+          binop,
+          iterator,
+          constNumber,
+          binopDescriptor.leftIdentity,
+          binop.rightValue(),
+          binopDescriptor.leftAbsorbing,
+          binop.leftValue())) {
+        return true;
+      }
+    }
+    constNumber = getConstNumber(binop.rightValue());
+    if (constNumber != null) {
+      return simplify(
+          binop,
+          iterator,
+          constNumber,
+          binopDescriptor.rightIdentity,
+          binop.leftValue(),
+          binopDescriptor.rightAbsorbing,
+          binop.rightValue());
+    }
+    return false;
+  }
+
   private ConstNumber getConstNumber(Value val) {
     ConstNumber constNumber = getConstNumberIfConstant(val);
     if (constNumber != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 69d5822..088c331 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -80,7 +80,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeSet;
 import java.util.function.Function;
 
 final class InlineCandidateProcessor {
@@ -695,8 +694,7 @@
 
   private void removeFieldReadsFromNewInstance(
       IRCode code, Set<Value> affectedValues, AssumeRemover assumeRemover) {
-    TreeSet<InstanceGet> uniqueInstanceGetUsersWithDeterministicOrder =
-        new TreeSet<>(Comparator.comparingInt(x -> x.outValue().getNumber()));
+    List<InstanceGet> uniqueInstanceGetUsersWithDeterministicOrder = new ArrayList<>();
     for (Instruction user : eligibleInstance.uniqueUsers()) {
       if (user.isInstanceGet()) {
         assumeRemover.markAssumeDynamicTypeUsersForRemoval(user.outValue());
@@ -722,6 +720,7 @@
               + user);
     }
 
+    uniqueInstanceGetUsersWithDeterministicOrder.sort(Comparator.comparing(Instruction::outValue));
     Map<DexField, FieldValueHelper> fieldHelpers = new IdentityHashMap<>();
     for (InstanceGet user : uniqueInstanceGetUsersWithDeterministicOrder) {
       // Replace a field read with appropriate value.
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 5a71db9..da66dfd 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.naming.mappinginformation.OutlineCallsiteMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.OutlineMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.CollectionUtils;
@@ -632,6 +633,15 @@
           MappingInformation::asRewriteFrameMappingInformation);
     }
 
+    public OutlineMappingInformation getOutlineMappingInformation() {
+      List<OutlineMappingInformation> outlineMappingInformation =
+          filter(
+              MappingInformation::isOutlineMappingInformation,
+              MappingInformation::asOutlineMappingInformation);
+      assert outlineMappingInformation.size() <= 1;
+      return outlineMappingInformation.isEmpty() ? null : outlineMappingInformation.get(0);
+    }
+
     public int getOriginalLineNumber(int lineNumberAfterMinification) {
       if (minifiedRange == null) {
         // General mapping without concrete line numbers: "a() -> b"
@@ -659,6 +669,10 @@
       }
     }
 
+    public Range getOriginalRangeOrIdentity() {
+      return originalRange != null ? originalRange : minifiedRange;
+    }
+
     @Override
     public Signature getOriginalSignature() {
       return signature;
@@ -761,6 +775,11 @@
       return Collections.unmodifiableList(additionalMappingInformation);
     }
 
+    public void setAdditionalMappingInformationInternal(
+        List<MappingInformation> mappingInformation) {
+      this.additionalMappingInformation = mappingInformation;
+    }
+
     public MappedRange partitionOnMinifiedRange(Range minifiedRange) {
       if (minifiedRange.equals(this.minifiedRange)) {
         return this;
@@ -776,5 +795,15 @@
       }
       return splitMappedRange;
     }
+
+    public boolean isOriginalRangePreamble() {
+      return originalRange != null && originalRange.isPreamble();
+    }
+
+    public MappedRange withMinifiedRange(Range newMinifiedRange) {
+      return newMinifiedRange.equals(minifiedRange)
+          ? this
+          : new MappedRange(newMinifiedRange, signature, originalRange, renamedName);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
index 22cf5e4..d87e036 100644
--- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
+++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.ConsumerUtils;
+import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
@@ -42,12 +43,14 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 public class ComposingBuilder {
@@ -408,7 +411,7 @@
     private static MappedRangeOriginalToMinifiedMap build(List<MappedRange> mappedRanges) {
       Int2ReferenceMap<List<Integer>> positionMap = new Int2ReferenceOpenHashMap<>();
       for (MappedRange mappedRange : mappedRanges) {
-        Range originalRange = mappedRange.originalRange;
+        Range originalRange = mappedRange.getOriginalRangeOrIdentity();
         for (int position = originalRange.from; position <= originalRange.to; position++) {
           // It is perfectly fine to have multiple minified ranges mapping to the same source, we
           // just need to keep the additional information.
@@ -420,11 +423,6 @@
       return new MappedRangeOriginalToMinifiedMap(positionMap);
     }
 
-    public int lookupFirst(int originalPosition) {
-      List<Integer> minifiedPositions = originalToMinified.get(originalPosition);
-      return minifiedPositions == null ? 0 : minifiedPositions.get(0);
-    }
-
     public void visitMinified(int originalPosition, Consumer<Integer> consumer) {
       List<Integer> minifiedPositions = originalToMinified.get(originalPosition);
       if (minifiedPositions != null) {
@@ -608,7 +606,7 @@
                     listSegmentTree.findEntry(firstPositionOfOriginalRange);
                 if (existingEntry == null
                     && firstPositionOfOriginalRange == 0
-                    && !mappedRange.originalRange.isPreamble()) {
+                    && !mappedRange.isOriginalRangePreamble()) {
                   existingEntry =
                       listSegmentTree.findEntry(mappedRange.getLastPositionOfOriginalRange());
                 }
@@ -619,8 +617,7 @@
                 } else {
                   // The original can be discarded if it no longer exists or if the method is
                   // non-throwing.
-                  if (mappedRange.originalRange != null
-                      && !mappedRange.originalRange.isPreamble()
+                  if (!mappedRange.isOriginalRangePreamble()
                       && !options.mappingComposeOptions().allowNonExistingOriginalRanges) {
                     throw new MappingComposeException(
                         "Could not find original starting position of '"
@@ -629,7 +626,7 @@
                             + firstPositionOfOriginalRange);
                   }
                 }
-                assert minified.hasValue();
+                assert minified.hasValue() || mappedRange.getOriginalRangeOrIdentity() == null;
               } else {
                 MappedRange existingMappedRange =
                     existingClassBuilder.methodsWithoutPosition.get(signature);
@@ -646,7 +643,10 @@
             if (composedInlineFrames.isEmpty()) {
               splitOnNewMinifiedRange(
                   composeMappedRangesForMethod(
-                      existingMappedRanges, mappedRange, computedOutlineInformation),
+                      existingClassBuilder,
+                      existingMappedRanges,
+                      mappedRange,
+                      computedOutlineInformation),
                   Collections.emptyList(),
                   newComposedInlineFrames::add);
             } else {
@@ -655,7 +655,10 @@
                     mappedRange.partitionOnMinifiedRange(composedInlineFrame.get(0).minifiedRange);
                 splitOnNewMinifiedRange(
                     composeMappedRangesForMethod(
-                        existingMappedRanges, splitMappedRange, computedOutlineInformation),
+                        existingClassBuilder,
+                        existingMappedRanges,
+                        splitMappedRange,
+                        computedOutlineInformation),
                     composedInlineFrame,
                     newComposedInlineFrames::add);
               }
@@ -668,42 +671,195 @@
               composedInlineFrames = Collections.emptyList();
             }
           }
-          MappedRange lastComposedRange = ListUtils.last(composedRanges);
-          if (computedOutlineInformation.seenOutlineMappingInformation != null) {
+          // Check if we could have inlined an outline which is true if we see both an outline and
+          // call site to patch up.
+          if (!computedOutlineInformation.seenOutlineMappingInformation.isEmpty()
+              && !computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.isEmpty()) {
+            Set<OutlineCallsiteMappingInformation> outlineCallSitesToRemove =
+                Sets.newIdentityHashSet();
+            Set<OutlineMappingInformation> outlinesToRemove = Sets.newIdentityHashSet();
+            // We patch up all ranges from top to bottom with the invariant that all at a given
+            // index, all above have been updated correctly. We will do expansion of frames
+            // when separating out single minified lines, but we keep the outline information
+            // present such that we can fix them when seeing them later.
+            int composedRangeIndex = 0;
+            while (composedRangeIndex < composedRanges.size() - 1) {
+              MappedRange outline = composedRanges.get(composedRangeIndex++);
+              if (outline.isOutlineFrame()
+                  && outline.minifiedRange.equals(
+                      composedRanges.get(composedRangeIndex).minifiedRange)) {
+                // We should replace the inlined outline frame positions with the synthesized
+                // positions from the outline call site.
+                MappedRange outlineCallSite = composedRanges.get(composedRangeIndex);
+                if (outlineCallSite.getOutlineCallsiteInformation().size() != 1) {
+                  // If we have an inlined outline it must be such that the outer frame is an
+                  // outline callsite.
+                  throw new MappingComposeException(
+                      "Expected exactly one outline call site for a mapped range with signature '"
+                          + outlineCallSite.getOriginalSignature()
+                          + "'.");
+                }
+                OutlineCallsiteMappingInformation outlineCallSiteInformation =
+                    outlineCallSite.getOutlineCallsiteInformation().get(0);
+                // The original positions in the outline callsite have been composed, so we have to
+                // find the existing mapped range and iterate the original positions for that range.
+                ComputedMappedRangeForOutline computedInformationForCallSite =
+                    computedOutlineInformation.getComputedRange(
+                        outlineCallSiteInformation, outlineCallSite);
+                if (computedInformationForCallSite == null) {
+                  continue;
+                }
+                Map<Integer, List<MappedRange>> mappedRangesForOutline =
+                    new HashMap<>(outlineCallSiteInformation.getPositions().size());
+                visitOutlineMappedPositions(
+                    outlineCallSiteInformation,
+                    computedInformationForCallSite
+                        .current
+                        .getOriginalSignature()
+                        .asMethodSignature(),
+                    mappedRangesForOutline::put);
+                List<MappedRange> newComposedRanges = new ArrayList<>();
+                // Copy all previous handled mapped ranges into a new list.
+                for (MappedRange previousMappedRanges : composedRanges) {
+                  if (previousMappedRanges == outline) {
+                    break;
+                  }
+                  newComposedRanges.add(previousMappedRanges);
+                }
+                // The original positions in the outline have been composed, so we have to find the
+                // existing mapped range and iterate the original positions for that range.
+                ComputedMappedRangeForOutline computedInformationForOutline =
+                    computedOutlineInformation.getComputedRange(
+                        outline.getOutlineMappingInformation(), outline);
+                if (computedInformationForOutline == null) {
+                  continue;
+                }
+                // The outline could have additional inlined positions in it, but we should be
+                // guaranteed to find call site information on all original line numbers. We
+                // therefore iterate one by one and amend the subsequent outer frames as well.
+                MappedRange current = computedInformationForOutline.current;
+                int minifiedLine = outline.minifiedRange.from;
+                for (int originalLine = current.getOriginalRangeOrIdentity().from;
+                    originalLine <= current.getOriginalRangeOrIdentity().to;
+                    originalLine++) {
+                  // If the outline is itself an inline frame it is bound to only have one original
+                  // position and we can simply insert all inline frames on that position with the
+                  // existing minified range.
+                  Range newMinifiedRange =
+                      outline.originalRange.isCardinal
+                          ? outline.minifiedRange
+                          : new Range(minifiedLine, minifiedLine);
+                  List<MappedRange> outlineMappedRanges = mappedRangesForOutline.get(originalLine);
+                  if (outlineMappedRanges != null) {
+                    outlineMappedRanges.forEach(
+                        range -> {
+                          if (range != ListUtils.last(outlineMappedRanges)) {
+                            newComposedRanges.add(
+                                new MappedRange(
+                                    newMinifiedRange,
+                                    range.getOriginalSignature().asMethodSignature(),
+                                    range.originalRange,
+                                    outlineCallSite.getRenamedName()));
+                          }
+                        });
+                    newComposedRanges.add(
+                        new MappedRange(
+                            newMinifiedRange,
+                            outlineCallSite.getOriginalSignature().asMethodSignature(),
+                            ListUtils.last(outlineMappedRanges).originalRange,
+                            outlineCallSite.getRenamedName()));
+                  }
+                  for (int tailInlineFrameIndex = composedRangeIndex + 1;
+                      tailInlineFrameIndex < composedRanges.size();
+                      tailInlineFrameIndex++) {
+                    MappedRange originalMappedRange = composedRanges.get(tailInlineFrameIndex);
+                    if (!originalMappedRange.minifiedRange.equals(outlineCallSite.minifiedRange)) {
+                      break;
+                    }
+                    MappedRange newMappedRange =
+                        originalMappedRange.withMinifiedRange(newMinifiedRange);
+                    newMappedRange.setAdditionalMappingInformationInternal(
+                        originalMappedRange.getAdditionalMappingInformation());
+                    newComposedRanges.add(newMappedRange);
+                  }
+                  minifiedLine++;
+                }
+                // We have patched up the the inlined outline and all subsequent inline frames
+                // (although some of the subsequent frames above could also be inlined outlines). We
+                // therefore need to copy the remaining frames.
+                boolean seenMinifiedRange = false;
+                for (MappedRange range : composedRanges) {
+                  if (range.minifiedRange.equals(outline.minifiedRange)) {
+                    seenMinifiedRange = true;
+                  } else if (seenMinifiedRange) {
+                    newComposedRanges.add(range);
+                  }
+                }
+                composedRanges = newComposedRanges;
+                outlineCallSitesToRemove.add(outlineCallSiteInformation);
+                outlinesToRemove.add(outline.getOutlineMappingInformation());
+              }
+            }
+            // If we removed any outlines or call site, remove the processing of them.
+            outlineCallSitesToRemove.forEach(
+                computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp::remove);
+            outlinesToRemove.forEach(
+                computedOutlineInformation.seenOutlineMappingInformation::remove);
+          }
+          if (!computedOutlineInformation.seenOutlineMappingInformation.isEmpty()) {
+            MappedRange lastComposedRange = ListUtils.last(composedRanges);
             current
                 .getUpdateOutlineCallsiteInformation(
                     committedPreviousClassBuilder.getRenamedName(),
                     ListUtils.last(newMappedRanges).signature.getName(),
                     lastComposedRange.getRenamedName())
                 .setNewMappedRanges(newMappedRanges);
-            lastComposedRange.addMappingInformation(
-                computedOutlineInformation.seenOutlineMappingInformation,
-                ConsumerUtils.emptyConsumer());
           }
           if (!computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.isEmpty()) {
-            MappedRangeOriginalToMinifiedMap originalToMinifiedMap =
-                MappedRangeOriginalToMinifiedMap.build(newMappedRanges);
+            MappedRange lastComposedRange = ListUtils.last(composedRanges);
+            List<MappedRange> composedRangesFinal = composedRanges;
+            // Outline positions are synthetic positions and they have no position in the residual
+            // program. We therefore have to find the original positions and copy all inline frames
+            // and amend the outermost frame with the residual signature and the next free position.
             List<OutlineCallsiteMappingInformation> outlineCallSites =
                 new ArrayList<>(
-                    computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp);
+                    computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.keySet());
             outlineCallSites.sort(Comparator.comparing(mapping -> mapping.getOutline().toString()));
+            IntBox firstAvailableRange = new IntBox(lastComposedRange.minifiedRange.to + 1);
             for (OutlineCallsiteMappingInformation outlineCallSite : outlineCallSites) {
-              Int2IntSortedMap positionMap = outlineCallSite.getPositions();
-              for (Integer keyPosition : positionMap.keySet()) {
-                int keyPositionInt = keyPosition;
-                int originalDestination = positionMap.get(keyPositionInt);
-                int newDestination = originalToMinifiedMap.lookupFirst(originalDestination);
-                positionMap.put(keyPositionInt, newDestination);
-              }
-              lastComposedRange.addMappingInformation(
-                  outlineCallSite, ConsumerUtils.emptyConsumer());
+              Int2IntSortedMap newPositionMap =
+                  new Int2IntLinkedOpenHashMap(outlineCallSite.getPositions().size());
+              visitOutlineMappedPositions(
+                  outlineCallSite,
+                  memberNaming.getOriginalSignature().asMethodSignature(),
+                  (originalPosition, mappedRangesForOutlinePosition) -> {
+                    int newIndex = firstAvailableRange.getAndIncrement();
+                    Range newMinifiedRange = new Range(newIndex, newIndex);
+                    MappedRange outerMostOutlineFrame =
+                        ListUtils.last(mappedRangesForOutlinePosition);
+                    for (MappedRange inlineMappedRangeInOutlinePosition :
+                        mappedRangesForOutlinePosition) {
+                      if (inlineMappedRangeInOutlinePosition != outerMostOutlineFrame) {
+                        composedRangesFinal.add(
+                            inlineMappedRangeInOutlinePosition.withMinifiedRange(newMinifiedRange));
+                      }
+                    }
+                    composedRangesFinal.add(
+                        new MappedRange(
+                            newMinifiedRange,
+                            lastComposedRange.signature,
+                            outerMostOutlineFrame.originalRange,
+                            lastComposedRange.getRenamedName()));
+                    newPositionMap.put((int) originalPosition, newIndex);
+                    outlineCallSite.setPositionsInternal(newPositionMap);
+                  });
             }
           }
           MethodSignature residualSignature =
               memberNaming
                   .computeResidualSignature(type -> inverseClassMapping.getOrDefault(type, type))
                   .asMethodSignature();
-          if (lastComposedRange.minifiedRange != null) {
+          if (ListUtils.last(composedRanges).minifiedRange != null) {
             SegmentTree<List<MappedRange>> listSegmentTree =
                 methodsWithPosition.computeIfAbsent(
                     residualSignature, ignored -> new SegmentTree<>(false));
@@ -711,12 +867,57 @@
                 minified.getStartOrNoRangeFrom(), minified.getEndOrNoRangeFrom(), composedRanges);
           } else {
             assert composedRanges.size() == 1;
-            methodsWithoutPosition.put(residualSignature, lastComposedRange);
+            methodsWithoutPosition.put(residualSignature, ListUtils.last(composedRanges));
           }
         }
       }
     }
 
+    private void visitOutlineMappedPositions(
+        OutlineCallsiteMappingInformation outlineCallSite,
+        MethodSignature originalSignature,
+        BiConsumer<Integer, List<MappedRange>> outlinePositionConsumer)
+        throws MappingComposeException {
+      Int2IntSortedMap positionMap = outlineCallSite.getPositions();
+      ComposingClassBuilder existingClassBuilder = getExistingClassBuilder(originalSignature);
+      if (existingClassBuilder == null) {
+        throw new MappingComposeException(
+            "Could not find builder with original signature '" + originalSignature + "'.");
+      }
+      SegmentTree<List<MappedRange>> outlineSegmentTree =
+          existingClassBuilder.methodsWithPosition.get(
+              originalSignature.toUnqualifiedSignatureIfQualified().asMethodSignature());
+      if (outlineSegmentTree == null) {
+        throw new MappingComposeException(
+            "Could not find method positions for original signature '" + originalSignature + "'.");
+      }
+      for (Integer keyPosition : positionMap.keySet()) {
+        int keyPositionInt = keyPosition;
+        int originalDestination = positionMap.get(keyPositionInt);
+        List<MappedRange> mappedRanges = outlineSegmentTree.find(originalDestination);
+        if (mappedRanges == null) {
+          throw new MappingComposeException(
+              "Could not find ranges for outline position '"
+                  + keyPosition
+                  + "' with original signature '"
+                  + originalSignature
+                  + "'.");
+        }
+        ExistingMapping existingMapping = computeExistingMapping(mappedRanges);
+        List<MappedRange> mappedRangesForOutlinePosition =
+            existingMapping.getMappedRangesForPosition(originalDestination);
+        if (mappedRangesForOutlinePosition == null) {
+          throw new MappingComposeException(
+              "Could not find ranges for outline position '"
+                  + keyPosition
+                  + "' with original signature '"
+                  + originalSignature
+                  + "'.");
+        }
+        outlinePositionConsumer.accept(keyPositionInt, mappedRangesForOutlinePosition);
+      }
+    }
+
     private void splitOnNewMinifiedRange(
         List<MappedRange> mappedRanges,
         List<MappedRange> previouslyMapped,
@@ -784,6 +985,7 @@
     }
 
     private List<MappedRange> composeMappedRangesForMethod(
+        ComposingClassBuilder existingClassBuilder,
         List<MappedRange> existingRanges,
         MappedRange newRange,
         ComputedOutlineInformation computedOutlineInformation)
@@ -793,10 +995,16 @@
         return Collections.singletonList(newRange);
       }
       MappedRange lastExistingRange = ListUtils.last(existingRanges);
-      if (newRange.originalRange == null) {
+      if (newRange.getOriginalRangeOrIdentity() == null) {
         MappedRange newComposedRange =
             new MappedRange(
-                newRange.minifiedRange, lastExistingRange.signature, null, newRange.renamedName);
+                newRange.minifiedRange,
+                potentiallyQualifySignature(
+                    newRange.signature,
+                    lastExistingRange.signature,
+                    existingClassBuilder.getOriginalName()),
+                null,
+                newRange.renamedName);
         composeMappingInformation(
             newComposedRange.getAdditionalMappingInformation(),
             lastExistingRange.getAdditionalMappingInformation(),
@@ -815,18 +1023,21 @@
       if (existingMappedRanges == null) {
         // If we cannot lookup the original position because it has been removed we compose with
         // the existing method signature.
-        if (newRange.originalRange.isPreamble()
+        if (newRange.isOriginalRangePreamble()
             || (existingRanges.size() == 1 && lastExistingRange.minifiedRange == null)) {
           return Collections.singletonList(
               new MappedRange(
                   newRange.minifiedRange,
-                  lastExistingRange.signature,
+                  potentiallyQualifySignature(
+                      newRange.signature,
+                      lastExistingRange.signature,
+                      existingClassBuilder.getOriginalName()),
                   lastExistingRange.originalRange != null
                           && lastExistingRange.originalRange.span() == 1
                       ? lastExistingRange.originalRange
                       : EMPTY_RANGE,
                   newRange.renamedName));
-        } else if (newRange.originalRange.from == 0) {
+        } else if (newRange.getOriginalRangeOrIdentity().from == 0) {
           // Similar to the trick below we create a synthetic range to map the preamble to.
           Pair<Integer, MappedRange> emptyRange =
               createEmptyRange(
@@ -842,8 +1053,9 @@
       assert lastExistingMappedRange != null;
       // If the existing mapped minified range is equal to the original range of the new range
       // then we have a perfect mapping that we can translate directly.
-      if (lastExistingMappedRange.minifiedRange.equals(newRange.originalRange)) {
+      if (lastExistingMappedRange.minifiedRange.equals(newRange.getOriginalRangeOrIdentity())) {
         computeComposedMappedRange(
+            existingClassBuilder,
             newComposedRanges,
             newRange,
             existingMappedRanges,
@@ -869,6 +1081,7 @@
                 lastExistingMappedRangeForPosition.minifiedRange)) {
           // We have seen an existing range we have to compute a splitting for.
           computeComposedMappedRange(
+              existingClassBuilder,
               newComposedRanges,
               newRange,
               existingMappedRanges,
@@ -911,6 +1124,7 @@
         }
       }
       computeComposedMappedRange(
+          existingClassBuilder,
           newComposedRanges,
           newRange,
           existingMappedRanges,
@@ -1000,6 +1214,7 @@
     }
 
     private void computeComposedMappedRange(
+        ComposingClassBuilder existingClassBuilder,
         List<MappedRange> newComposedRanges,
         MappedRange newMappedRange,
         List<MappedRange> existingMappedRanges,
@@ -1010,9 +1225,9 @@
       Range existingRange = existingMappedRanges.get(0).minifiedRange;
       assert existingMappedRanges.stream().allMatch(x -> x.minifiedRange.equals(existingRange));
       Range newMinifiedRange = new Range(lastStartingMinifiedFrom, position);
-      boolean copyOriginalRange = existingRange.equals(newMappedRange.originalRange);
+      boolean copyOriginalRange = existingRange.equals(newMappedRange.getOriginalRangeOrIdentity());
       for (MappedRange existingMappedRange : existingMappedRanges) {
-        Range existingOriginalRange = existingMappedRange.originalRange;
+        Range existingOriginalRange = existingMappedRange.getOriginalRangeOrIdentity();
         Range newOriginalRange;
         if (copyOriginalRange
             || existingOriginalRange == null
@@ -1022,7 +1237,7 @@
           // Find the window that the new range points to into the original range.
           int existingMinifiedPos = newMappedRange.getOriginalLineNumber(lastStartingMinifiedFrom);
           int newOriginalStart = existingMappedRange.getOriginalLineNumber(existingMinifiedPos);
-          if (newMappedRange.originalRange.span() == 1) {
+          if (newMappedRange.getOriginalRangeOrIdentity().span() == 1) {
             newOriginalRange = new Range(newOriginalStart, newOriginalStart);
           } else {
             assert newMinifiedRange.span() <= existingOriginalRange.span();
@@ -1033,7 +1248,10 @@
         MappedRange computedRange =
             new MappedRange(
                 newMinifiedRange,
-                existingMappedRange.signature,
+                potentiallyQualifySignature(
+                    newMappedRange.signature,
+                    existingMappedRange.signature,
+                    existingClassBuilder.getOriginalName()),
                 newOriginalRange,
                 newMappedRange.renamedName);
         List<MappingInformation> mappingInformationToCompose = new ArrayList<>();
@@ -1042,14 +1260,19 @@
             .forEach(
                 info -> {
                   if (info.isOutlineMappingInformation()) {
-                    computedOutlineInformation.seenOutlineMappingInformation =
-                        info.asOutlineMappingInformation();
+                    computedOutlineInformation
+                        .seenOutlineMappingInformation
+                        .computeIfAbsent(
+                            info.asOutlineMappingInformation(), ignoreArgument(ArrayList::new))
+                        .add(new ComputedMappedRangeForOutline(newMappedRange, computedRange));
                   } else if (info.isOutlineCallsiteInformation()) {
-                    computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.add(
-                        info.asOutlineCallsiteInformation());
-                  } else {
-                    mappingInformationToCompose.add(info);
+                    computedOutlineInformation
+                        .outlineCallsiteMappingInformationToPatchUp
+                        .computeIfAbsent(
+                            info.asOutlineCallsiteInformation(), ignoreArgument(ArrayList::new))
+                        .add(new ComputedMappedRangeForOutline(newMappedRange, computedRange));
                   }
+                  mappingInformationToCompose.add(info);
                 });
         composeMappingInformation(
             computedRange.getAdditionalMappingInformation(),
@@ -1190,6 +1413,14 @@
       }
     }
 
+    private MethodSignature potentiallyQualifySignature(
+        MethodSignature newSignature, MethodSignature signature, String originalHolder) {
+      return !newSignature.isQualified() || signature.isQualified()
+          ? signature
+          : new MethodSignature(
+              originalHolder + "." + signature.name, signature.type, signature.parameters);
+    }
+
     private static class RangeBuilder {
 
       private int start = Integer.MAX_VALUE;
@@ -1217,9 +1448,35 @@
     }
 
     private static class ComputedOutlineInformation {
-      private final Set<OutlineCallsiteMappingInformation>
-          outlineCallsiteMappingInformationToPatchUp = Sets.newIdentityHashSet();
-      private OutlineMappingInformation seenOutlineMappingInformation = null;
+      private final Map<OutlineCallsiteMappingInformation, List<ComputedMappedRangeForOutline>>
+          outlineCallsiteMappingInformationToPatchUp = new IdentityHashMap<>();
+      private final Map<OutlineMappingInformation, List<ComputedMappedRangeForOutline>>
+          seenOutlineMappingInformation = new IdentityHashMap<>();
+
+      private ComputedMappedRangeForOutline getComputedRange(
+          MappingInformation outline, MappedRange current) {
+        List<ComputedMappedRangeForOutline> outlineMappingInformations =
+            outline.isOutlineMappingInformation()
+                ? seenOutlineMappingInformation.get(outline.asOutlineMappingInformation())
+                : outlineCallsiteMappingInformationToPatchUp.get(
+                    outline.asOutlineCallsiteInformation());
+        if (outlineMappingInformations == null) {
+          return null;
+        }
+        return ListUtils.firstMatching(
+            outlineMappingInformations,
+            pair -> pair.composed.minifiedRange.contains(current.minifiedRange.from));
+      }
+    }
+
+    private static class ComputedMappedRangeForOutline {
+      private final MappedRange current;
+      private final MappedRange composed;
+
+      private ComputedMappedRangeForOutline(MappedRange current, MappedRange composed) {
+        this.current = current;
+        this.composed = composed;
+      }
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index d1bfb78..5ddf11e 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -510,6 +510,10 @@
       return new MethodSignature(toUnqualifiedName(), type, parameters);
     }
 
+    public Signature toUnqualifiedSignatureIfQualified() {
+      return isQualified() ? new MethodSignature(toUnqualifiedName(), type, parameters) : this;
+    }
+
     @Override
     public Signature toQualifiedSignature(String holder) {
       return new MethodSignature(holder + "." + name, type, parameters);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index 1a2eaa6..94a7d22 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
-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;
@@ -36,6 +35,8 @@
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorGraphLens.Builder;
 import com.android.tools.r8.optimize.argumentpropagation.utils.ParameterRemovalUtils;
+import com.android.tools.r8.optimize.utils.ConcurrentNonProgramMethodsCollection;
+import com.android.tools.r8.optimize.utils.NonProgramMethodsCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepFieldInfo;
 import com.android.tools.r8.shaking.KeepMethodInfo;
@@ -73,7 +74,6 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
@@ -173,9 +173,7 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
   private final Map<Set<DexProgramClass>, DexMethodSignatureSet> interfaceDispatchOutsideProgram;
-
-  private final Map<DexClass, DexMethodSignatureSet> libraryVirtualMethods =
-      new ConcurrentHashMap<>();
+  private final NonProgramMethodsCollection nonProgramMethodsCollection;
 
   public ArgumentPropagatorProgramOptimizer(
       AppView<AppInfoWithLiveness> appView,
@@ -184,6 +182,8 @@
     this.appView = appView;
     this.immediateSubtypingInfo = immediateSubtypingInfo;
     this.interfaceDispatchOutsideProgram = interfaceDispatchOutsideProgram;
+    this.nonProgramMethodsCollection =
+        ConcurrentNonProgramMethodsCollection.createVirtualMethodsCollection(appView);
   }
 
   public ArgumentPropagatorGraphLens run(
@@ -221,28 +221,6 @@
     return graphLens;
   }
 
-  private DexMethodSignatureSet getOrComputeLibraryVirtualMethods(DexClass clazz) {
-    DexMethodSignatureSet libraryMethodsOnClass = libraryVirtualMethods.get(clazz);
-    if (libraryMethodsOnClass != null) {
-      return libraryMethodsOnClass;
-    }
-    return computeLibraryVirtualMethods(clazz);
-  }
-
-  private DexMethodSignatureSet computeLibraryVirtualMethods(DexClass clazz) {
-    DexMethodSignatureSet libraryMethodsOnClass = DexMethodSignatureSet.create();
-    immediateSubtypingInfo.forEachImmediateSuperClassMatching(
-        clazz,
-        (supertype, superclass) -> superclass != null,
-        (supertype, superclass) ->
-            libraryMethodsOnClass.addAll(getOrComputeLibraryVirtualMethods(superclass)));
-    clazz.forEachClassMethodMatching(
-        DexEncodedMethod::belongsToVirtualPool,
-        method -> libraryMethodsOnClass.add(method.getMethodSignature()));
-    libraryVirtualMethods.put(clazz, libraryMethodsOnClass);
-    return libraryMethodsOnClass;
-  }
-
   public class StronglyConnectedComponentOptimizer {
 
     private final DexItemFactory dexItemFactory = appView.dexItemFactory();
@@ -325,7 +303,7 @@
     private void reservePinnedMethodSignatures(
         Set<DexProgramClass> stronglyConnectedProgramClasses) {
       DexMethodSignatureSet pinnedMethodSignatures = DexMethodSignatureSet.create();
-      Set<DexClass> seenLibraryClasses = Sets.newIdentityHashSet();
+      Set<DexClass> seenNonProgramClasses = Sets.newIdentityHashSet();
       for (DexProgramClass clazz : stronglyConnectedProgramClasses) {
         clazz.forEachProgramMethodMatching(
             method -> !method.isInstanceInitializer(),
@@ -341,9 +319,11 @@
             (supertype, superclass) ->
                 superclass != null
                     && !superclass.isProgramClass()
-                    && seenLibraryClasses.add(superclass),
+                    && seenNonProgramClasses.add(superclass),
             (supertype, superclass) ->
-                pinnedMethodSignatures.addAll(getOrComputeLibraryVirtualMethods(superclass)));
+                pinnedMethodSignatures.addAll(
+                    nonProgramMethodsCollection.getOrComputeNonProgramMethods(
+                        superclass.asClasspathOrLibraryClass())));
       }
       pinnedMethodSignatures.forEach(
           signature ->
diff --git a/src/main/java/com/android/tools/r8/optimize/utils/ConcurrentNonProgramMethodsCollection.java b/src/main/java/com/android/tools/r8/optimize/utils/ConcurrentNonProgramMethodsCollection.java
new file mode 100644
index 0000000..0d5e0eb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/utils/ConcurrentNonProgramMethodsCollection.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2023, 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.optimize.utils;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import java.util.concurrent.ConcurrentHashMap;
+
+public abstract class ConcurrentNonProgramMethodsCollection extends NonProgramMethodsCollection {
+
+  ConcurrentNonProgramMethodsCollection(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    super(appView, new ConcurrentHashMap<>());
+  }
+
+  public static ConcurrentNonProgramMethodsCollection createVirtualMethodsCollection(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return new ConcurrentNonProgramMethodsCollection(appView) {
+      @Override
+      public boolean test(DexClassAndMethod method) {
+        return method.getAccessFlags().belongsToVirtualPool();
+      }
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/utils/NonProgramMethodsCollection.java b/src/main/java/com/android/tools/r8/optimize/utils/NonProgramMethodsCollection.java
new file mode 100644
index 0000000..da93905
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/utils/NonProgramMethodsCollection.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2023, 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.optimize.utils;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathOrLibraryClass;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
+import java.util.Map;
+
+public abstract class NonProgramMethodsCollection {
+
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final Map<DexClass, DexMethodSignatureSet> nonProgramMethods;
+
+  NonProgramMethodsCollection(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      Map<DexClass, DexMethodSignatureSet> nonProgramMethods) {
+    this.appView = appView;
+    this.nonProgramMethods = nonProgramMethods;
+  }
+
+  public DexMethodSignatureSet getOrComputeNonProgramMethods(ClasspathOrLibraryClass clazz) {
+    return getOrComputeNonProgramMethods(clazz.asDexClass());
+  }
+
+  // Parameter is typed as DexClass to account for program classes above classpath or library
+  // classes.
+  private DexMethodSignatureSet getOrComputeNonProgramMethods(DexClass clazz) {
+    DexMethodSignatureSet nonProgramMethodsOnClass = nonProgramMethods.get(clazz);
+    return nonProgramMethodsOnClass != null
+        ? nonProgramMethodsOnClass
+        : computeNonProgramMethods(clazz);
+  }
+
+  private DexMethodSignatureSet computeNonProgramMethods(DexClass clazz) {
+    DexMethodSignatureSet nonProgramMethodsOnClass = DexMethodSignatureSet.create();
+    clazz.forEachImmediateSuperClassMatching(
+        appView,
+        (supertype, superclass) -> superclass != null,
+        (supertype, superclass) ->
+            nonProgramMethodsOnClass.addAll(getOrComputeNonProgramMethods(superclass)));
+    if (!clazz.isProgramClass()) {
+      clazz.forEachClassMethod(
+          method -> {
+            if (test(method)) {
+              nonProgramMethodsOnClass.add(method.getMethodSignature());
+            }
+          });
+    }
+    nonProgramMethods.put(clazz, nonProgramMethodsOnClass);
+    return nonProgramMethodsOnClass;
+  }
+
+  public abstract boolean test(DexClassAndMethod method);
+}
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 b1673ba..ead4063 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -633,7 +633,7 @@
     if (!profileCollectionAdditions.isNop()) {
       for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
         profileCollectionAdditions.applyIfContextIsInProfile(
-            synthesizedBridge.originalMethod,
+            lens.getPreviousMethodSignature(synthesizedBridge.method),
             additionsBuilder -> additionsBuilder.addRule(synthesizedBridge.method));
       }
     }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
index cf548f9..9e8ec26 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
@@ -112,6 +112,7 @@
       DexProgramClass clazz, SyntheticKind kind, AppView<?> appView) {
     // TODO(b/158159959): Consider moving this to the dex writer similar to the CF case.
     assert !appView.options().isGeneratingClassFiles();
+    assert !isDefinitelyNotSyntheticProgramClass(clazz);
     clazz.setAnnotations(
         clazz
             .annotations()
@@ -148,9 +149,6 @@
 
   private static SyntheticMarker internalStripMarkerFromClass(
       DexProgramClass clazz, AppView<?> appView) {
-    if (clazz.superType != appView.dexItemFactory().objectType) {
-      return NO_MARKER;
-    }
     if (isDefinitelyNotSyntheticProgramClass(clazz)) {
       return NO_MARKER;
     }
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 89edec3..538b9ee 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2078,6 +2078,7 @@
     public boolean roundtripThroughLir = false;
     public boolean checkReceiverAlwaysNullInCallSiteOptimization = true;
     public boolean forceInlineAPIConversions = false;
+    public boolean ignoreValueNumbering = false;
     private boolean hasReadCheckDeterminism = false;
     private DeterminismChecker determinismChecker = null;
     public boolean usePcEncodingInCfForTesting = false;
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index c998cf6..d09475d 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -287,7 +287,7 @@
     void accept(T item, int index);
   }
 
-  public static <T> List<T> sort(List<T> items, Comparator<T> comparator) {
+  public static <T> List<T> sort(Collection<T> items, Comparator<T> comparator) {
     List<T> sorted = new ArrayList<>(items);
     sorted.sort(comparator);
     return sorted;
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureBiMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureBiMap.java
new file mode 100644
index 0000000..64ce1ec
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureBiMap.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2023, 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.collections;
+
+import com.google.common.collect.HashBiMap;
+
+public class DexMethodSignatureBiMap<T> extends DexMethodSignatureMap<T> {
+
+  public DexMethodSignatureBiMap() {
+    super(HashBiMap.create());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java
index 84d1cae..bbf5afb 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureMap.java
@@ -4,10 +4,12 @@
 
 package com.android.tools.r8.utils.collections;
 
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodSignature;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -20,7 +22,7 @@
 
   private final Map<DexMethodSignature, T> backing;
 
-  private DexMethodSignatureMap(Map<DexMethodSignature, T> backing) {
+  DexMethodSignatureMap(Map<DexMethodSignature, T> backing) {
     this.backing = backing;
   }
 
@@ -28,10 +30,18 @@
     return new DexMethodSignatureMap<>(new HashMap<>());
   }
 
+  public static <T> DexMethodSignatureMap<T> create(DexMethodSignatureMap<T> map) {
+    return new DexMethodSignatureMap<>(new HashMap<>(map.backing));
+  }
+
   public static <T> DexMethodSignatureMap<T> createLinked() {
     return new DexMethodSignatureMap<>(new LinkedHashMap<>());
   }
 
+  public static <T> DexMethodSignatureMap<T> empty() {
+    return new DexMethodSignatureMap<>(Collections.emptyMap());
+  }
+
   @Override
   public T put(DexMethodSignature signature, T value) {
     return backing.put(signature, value);
@@ -100,6 +110,11 @@
     return backing.replace(key, value);
   }
 
+  public T computeIfAbsent(
+      DexClassAndMethod key, Function<? super DexMethodSignature, ? extends T> mappingFunction) {
+    return computeIfAbsent(key.getMethodSignature(), mappingFunction);
+  }
+
   @Override
   public T computeIfAbsent(
       DexMethodSignature key, Function<? super DexMethodSignature, ? extends T> mappingFunction) {
@@ -189,7 +204,16 @@
   }
 
   @Override
-  public void putAll(Map<? extends DexMethodSignature, ? extends T> m) {}
+  public void putAll(Map<? extends DexMethodSignature, ? extends T> map) {
+    map.forEach(this::put);
+  }
+
+  @SuppressWarnings("unchecked")
+  public void putAllToIdentity(Collection<? extends DexMethodSignature> signatures) {
+    for (DexMethodSignature signature : signatures) {
+      put(signature, (T) signature);
+    }
+  }
 
   public T remove(DexMethodSignature signature) {
     return backing.remove(signature);
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportTest.java
index 3c6e599..8fbadfa 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportTest.java
@@ -8,6 +8,8 @@
 import static com.android.tools.r8.utils.AndroidApiLevel.LATEST;
 import static org.hamcrest.CoreMatchers.containsString;
 
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -56,6 +58,24 @@
   }
 
   @Test
+  public void testIntermediateD8() throws Exception {
+    D8TestCompileResult intermediate =
+        testForD8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .setOutputMode(
+                parameters.isCfRuntime() ? OutputMode.ClassFile : OutputMode.DexFilePerClass)
+            .setMinApi(parameters)
+            .compileWithExpectedDiagnostics(this::checkDiagnostics);
+
+    testForD8(parameters.getBackend())
+        .addProgramFiles(intermediate.writeToZip())
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkExpected);
+  }
+
+  @Test
   public void testR8() throws Exception {
     parameters.assumeR8TestParameters();
     testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/ir/AssociativeIntTest.java b/src/test/java/com/android/tools/r8/ir/AssociativeIntTest.java
new file mode 100644
index 0000000..84e8e70
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/AssociativeIntTest.java
@@ -0,0 +1,463 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AssociativeIntTest extends TestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "Associative",
+          "7",
+          "47",
+          "-2147483644",
+          "-2147483643",
+          "12",
+          "252",
+          "-6",
+          "0",
+          "2",
+          "2",
+          "2",
+          "0",
+          "3",
+          "43",
+          "2147483647",
+          "-2147483645",
+          "3",
+          "43",
+          "2147483646",
+          "-2147483647",
+          "Shift",
+          "64",
+          "1344",
+          "-32",
+          "0",
+          "0",
+          "1",
+          "67108863",
+          "-67108864",
+          "0",
+          "1",
+          "67108863",
+          "67108864",
+          "Sub",
+          "-1",
+          "-41",
+          "-2147483646",
+          "-2147483647",
+          "-3",
+          "37",
+          "2147483642",
+          "2147483643",
+          "Mixed",
+          "3",
+          "43",
+          "-2147483648",
+          "-2147483647",
+          "3",
+          "-37",
+          "-2147483642",
+          "-2147483643",
+          "-1",
+          "-41",
+          "-2147483646",
+          "-2147483647",
+          "25",
+          "-15",
+          "-2147483620",
+          "-2147483621",
+          "3",
+          "43",
+          "-2147483648",
+          "-2147483647",
+          "3",
+          "43",
+          "-2147483648",
+          "-2147483647",
+          "1",
+          "41",
+          "2147483646",
+          "2147483647",
+          "Double Associative",
+          "12",
+          "52",
+          "84",
+          "1764",
+          "2",
+          "2",
+          "7",
+          "47",
+          "4",
+          "44",
+          "Double Shift",
+          "128",
+          "2688",
+          "0",
+          "0",
+          "0",
+          "0",
+          "Double Sub",
+          "-1",
+          "-41",
+          "-10",
+          "30",
+          "Double Mixed",
+          "-4",
+          "36",
+          "7",
+          "-33",
+          "-4",
+          "36",
+          "5",
+          "45");
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public AssociativeIntTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(Main.class);
+    for (FoundMethodSubject method :
+        clazz.allMethods(m -> m.getParameters().size() > 0 && m.getParameter(0).is("int"))) {
+      assertEquals(
+          method.getOriginalName().contains("NotSimplified") ? 2 : 1,
+          method
+              .streamInstructions()
+              .filter(i -> i.isIntArithmeticBinop() || i.isIntLogicalBinop())
+              .count());
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      simple();
+      doubleOps();
+    }
+
+    @NeverInline
+    private static void simple() {
+      // Associative + * & | ^.
+      System.out.println("Associative");
+      add(2);
+      add(42);
+      add(Integer.MAX_VALUE);
+      add(Integer.MIN_VALUE);
+      mul(2);
+      mul(42);
+      mul(Integer.MAX_VALUE);
+      mul(Integer.MIN_VALUE);
+      and(2);
+      and(42);
+      and(Integer.MAX_VALUE);
+      and(Integer.MIN_VALUE);
+      or(2);
+      or(42);
+      or(Integer.MAX_VALUE);
+      or(Integer.MIN_VALUE);
+      xor(2);
+      xor(42);
+      xor(Integer.MAX_VALUE);
+      xor(Integer.MIN_VALUE);
+
+      // Shift composition.
+      System.out.println("Shift");
+      shl(2);
+      shl(42);
+      shl(Integer.MAX_VALUE);
+      shl(Integer.MIN_VALUE);
+      shr(2);
+      shr(42);
+      shr(Integer.MAX_VALUE);
+      shr(Integer.MIN_VALUE);
+      ushr(2);
+      ushr(42);
+      ushr(Integer.MAX_VALUE);
+      ushr(Integer.MIN_VALUE);
+
+      // Special for -.
+      System.out.println("Sub");
+      sub(2);
+      sub(42);
+      sub(Integer.MAX_VALUE);
+      sub(Integer.MIN_VALUE);
+      sub2(2);
+      sub2(42);
+      sub2(Integer.MAX_VALUE);
+      sub2(Integer.MIN_VALUE);
+
+      // Mixed for + and -.
+      System.out.println("Mixed");
+      addSub(2);
+      addSub(42);
+      addSub(Integer.MAX_VALUE);
+      addSub(Integer.MIN_VALUE);
+      subAdd(2);
+      subAdd(42);
+      subAdd(Integer.MAX_VALUE);
+      subAdd(Integer.MIN_VALUE);
+      addSubNotSimplified_1(2);
+      addSubNotSimplified_1(42);
+      addSubNotSimplified_1(Integer.MAX_VALUE);
+      addSubNotSimplified_1(Integer.MIN_VALUE);
+      addSubNotSimplified_2(2);
+      addSubNotSimplified_2(42);
+      addSubNotSimplified_2(Integer.MAX_VALUE);
+      addSubNotSimplified_2(Integer.MIN_VALUE);
+      addSubNotSimplified_3(2);
+      addSubNotSimplified_3(42);
+      addSubNotSimplified_3(Integer.MAX_VALUE);
+      addSubNotSimplified_3(Integer.MIN_VALUE);
+      addSub2(2);
+      addSub2(42);
+      addSub2(Integer.MAX_VALUE);
+      addSub2(Integer.MIN_VALUE);
+      subAdd2(2);
+      subAdd2(42);
+      subAdd2(Integer.MAX_VALUE);
+      subAdd2(Integer.MIN_VALUE);
+    }
+
+    @NeverInline
+    private static void doubleOps() {
+      // Associative + * & | ^.
+      System.out.println("Double Associative");
+      addDouble(2);
+      addDouble(42);
+      mulDouble(2);
+      mulDouble(42);
+      andDouble(2);
+      andDouble(42);
+      orDouble(2);
+      orDouble(42);
+      xorDouble(2);
+      xorDouble(42);
+
+      // Shift composition.
+      System.out.println("Double Shift");
+      shlDouble(2);
+      shlDouble(42);
+      shrDouble(2);
+      shrDouble(42);
+      ushrDouble(2);
+      ushrDouble(42);
+
+      // Special for -.
+      System.out.println("Double Sub");
+      subDouble(2);
+      subDouble(42);
+      sub2Double(2);
+      sub2Double(42);
+
+      // Mixed for + and -.
+      System.out.println("Double Mixed");
+      addSubDouble(2);
+      addSubDouble(42);
+      subAddDouble(2);
+      subAddDouble(42);
+      addSub2Double(2);
+      addSub2Double(42);
+      subAdd2Double(2);
+      subAdd2Double(42);
+    }
+
+    @NeverInline
+    public static void add(int x) {
+      System.out.println(3 + x + 2);
+    }
+
+    @NeverInline
+    public static void mul(int x) {
+      System.out.println(3 * x * 2);
+    }
+
+    @NeverInline
+    public static void and(int x) {
+      System.out.println(3 & x & 2);
+    }
+
+    @NeverInline
+    public static void or(int x) {
+      System.out.println(3 | x | 2);
+    }
+
+    @NeverInline
+    public static void xor(int x) {
+      System.out.println(3 ^ x ^ 2);
+    }
+
+    @NeverInline
+    public static void shl(int x) {
+      System.out.println(x << 2 << 3);
+    }
+
+    @NeverInline
+    public static void shr(int x) {
+      System.out.println(x >> 2 >> 3);
+    }
+
+    @NeverInline
+    public static void ushr(int x) {
+      System.out.println(x >>> 2 >>> 3);
+    }
+
+    @NeverInline
+    public static void sub(int x) {
+      System.out.println(3 - x - 2);
+    }
+
+    @NeverInline
+    public static void sub2(int x) {
+      System.out.println(x - 3 - 2);
+    }
+
+    @NeverInline
+    public static void addSub(int x) {
+      System.out.println(3 + x - 2);
+    }
+
+    @NeverInline
+    public static void addSub2(int x) {
+      System.out.println(x + 3 - 2);
+    }
+
+    @NeverInline
+    public static void addSubNotSimplified_1(int x) {
+      System.out.println(14 - (x + 13));
+    }
+
+    @NeverInline
+    public static void addSubNotSimplified_2(int x) {
+      System.out.println(14 - (x - 13));
+    }
+
+    @NeverInline
+    public static void addSubNotSimplified_3(int x) {
+      System.out.println(14 - (13 - x));
+    }
+
+    @NeverInline
+    public static void subAdd(int x) {
+      System.out.println(3 - x + 2);
+    }
+
+    @NeverInline
+    public static void subAdd2(int x) {
+      System.out.println(x - 3 + 2);
+    }
+
+    @NeverInline
+    public static void addDouble(int x) {
+      System.out.println(3 + x + 2 + 5);
+    }
+
+    @NeverInline
+    public static void mulDouble(int x) {
+      System.out.println(3 * x * 2 * 7);
+    }
+
+    @NeverInline
+    public static void andDouble(int x) {
+      System.out.println(3 & x & 2 & 7);
+    }
+
+    @NeverInline
+    public static void orDouble(int x) {
+      System.out.println(3 | x | 2 | 7);
+    }
+
+    @NeverInline
+    public static void xorDouble(int x) {
+      System.out.println(3 ^ x ^ 2 ^ 7);
+    }
+
+    @NeverInline
+    public static void shlDouble(int x) {
+      System.out.println(x << 2 << 3 << 1);
+    }
+
+    @NeverInline
+    public static void shrDouble(int x) {
+      System.out.println(x >> 2 >> 3 >> 1);
+    }
+
+    @NeverInline
+    public static void ushrDouble(int x) {
+      System.out.println(x >>> 2 >>> 3 >>> 1);
+    }
+
+    @NeverInline
+    public static void subDouble(int x) {
+      System.out.println(3 - x - 2);
+    }
+
+    @NeverInline
+    public static void sub2Double(int x) {
+      System.out.println(x - 3 - 2 - 7);
+    }
+
+    @NeverInline
+    public static void addSubDouble(int x) {
+      System.out.println(3 + x - 2 - 7);
+    }
+
+    @NeverInline
+    public static void addSub2Double(int x) {
+      System.out.println(x + 3 - 2 - 7);
+    }
+
+    @NeverInline
+    public static void subAddDouble(int x) {
+      System.out.println(3 - x + 2 + 4);
+    }
+
+    @NeverInline
+    public static void subAdd2Double(int x) {
+      System.out.println(x - 3 + 2 + 4);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/AssociativeLongTest.java b/src/test/java/com/android/tools/r8/ir/AssociativeLongTest.java
new file mode 100644
index 0000000..1d041b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/AssociativeLongTest.java
@@ -0,0 +1,424 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AssociativeLongTest extends TestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "Associative",
+          "7",
+          "47",
+          "-9223372036854775804",
+          "-9223372036854775803",
+          "12",
+          "252",
+          "-6",
+          "0",
+          "2",
+          "2",
+          "2",
+          "0",
+          "3",
+          "43",
+          "9223372036854775807",
+          "-9223372036854775805",
+          "3",
+          "43",
+          "9223372036854775806",
+          "-9223372036854775807",
+          "Shift",
+          "64",
+          "1344",
+          "-32",
+          "0",
+          "0",
+          "1",
+          "288230376151711743",
+          "-288230376151711744",
+          "0",
+          "1",
+          "288230376151711743",
+          "288230376151711744",
+          "Sub",
+          "-1",
+          "-41",
+          "-9223372036854775806",
+          "-9223372036854775807",
+          "-3",
+          "37",
+          "9223372036854775802",
+          "9223372036854775803",
+          "Mixed",
+          "3",
+          "43",
+          "-9223372036854775808",
+          "-9223372036854775807",
+          "3",
+          "-37",
+          "-9223372036854775802",
+          "-9223372036854775803",
+          "3",
+          "43",
+          "-9223372036854775808",
+          "-9223372036854775807",
+          "1",
+          "41",
+          "9223372036854775806",
+          "9223372036854775807",
+          "Double Associative",
+          "12",
+          "52",
+          "84",
+          "1764",
+          "2",
+          "2",
+          "7",
+          "47",
+          "4",
+          "44",
+          "Double Shift",
+          "128",
+          "2688",
+          "0",
+          "0",
+          "0",
+          "0",
+          "Double Sub",
+          "-1",
+          "-41",
+          "-10",
+          "30",
+          "Double Mixed",
+          "-4",
+          "36",
+          "7",
+          "-33",
+          "-4",
+          "36",
+          "5",
+          "45");
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public AssociativeLongTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(Main.class);
+    for (FoundMethodSubject method :
+        clazz.allMethods(m -> m.getParameters().size() > 0 && m.getParameter(0).is("long"))) {
+      assertEquals(
+          1,
+          method
+              .streamInstructions()
+              .filter(i -> i.isLongArithmeticBinop() || i.isLongLogicalBinop())
+              .count());
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      simple();
+      doubleOps();
+    }
+
+    @NeverInline
+    private static void simple() {
+      // Associative + * & | ^.
+      System.out.println("Associative");
+      add(2);
+      add(42);
+      add(Long.MAX_VALUE);
+      add(Long.MIN_VALUE);
+      mul(2);
+      mul(42);
+      mul(Long.MAX_VALUE);
+      mul(Long.MIN_VALUE);
+      and(2);
+      and(42);
+      and(Long.MAX_VALUE);
+      and(Long.MIN_VALUE);
+      or(2);
+      or(42);
+      or(Long.MAX_VALUE);
+      or(Long.MIN_VALUE);
+      xor(2);
+      xor(42);
+      xor(Long.MAX_VALUE);
+      xor(Long.MIN_VALUE);
+
+      // Shift composition.
+      System.out.println("Shift");
+      shl(2);
+      shl(42);
+      shl(Long.MAX_VALUE);
+      shl(Long.MIN_VALUE);
+      shr(2);
+      shr(42);
+      shr(Long.MAX_VALUE);
+      shr(Long.MIN_VALUE);
+      ushr(2);
+      ushr(42);
+      ushr(Long.MAX_VALUE);
+      ushr(Long.MIN_VALUE);
+
+      // Special for -.
+      System.out.println("Sub");
+      sub(2);
+      sub(42);
+      sub(Long.MAX_VALUE);
+      sub(Long.MIN_VALUE);
+      sub2(2);
+      sub2(42);
+      sub2(Long.MAX_VALUE);
+      sub2(Long.MIN_VALUE);
+
+      // Mixed for + and -.
+      System.out.println("Mixed");
+      addSub(2);
+      addSub(42);
+      addSub(Long.MAX_VALUE);
+      addSub(Long.MIN_VALUE);
+      subAdd(2);
+      subAdd(42);
+      subAdd(Long.MAX_VALUE);
+      subAdd(Long.MIN_VALUE);
+      addSub2(2);
+      addSub2(42);
+      addSub2(Long.MAX_VALUE);
+      addSub2(Long.MIN_VALUE);
+      subAdd2(2);
+      subAdd2(42);
+      subAdd2(Long.MAX_VALUE);
+      subAdd2(Long.MIN_VALUE);
+    }
+
+    @NeverInline
+    private static void doubleOps() {
+      // Associative + * & | ^.
+      System.out.println("Double Associative");
+      addDouble(2);
+      addDouble(42);
+      mulDouble(2);
+      mulDouble(42);
+      andDouble(2);
+      andDouble(42);
+      orDouble(2);
+      orDouble(42);
+      xorDouble(2);
+      xorDouble(42);
+
+      // Shift composition.
+      System.out.println("Double Shift");
+      shlDouble(2);
+      shlDouble(42);
+      shrDouble(2);
+      shrDouble(42);
+      ushrDouble(2);
+      ushrDouble(42);
+
+      // Special for -.
+      System.out.println("Double Sub");
+      subDouble(2);
+      subDouble(42);
+      sub2Double(2);
+      sub2Double(42);
+
+      // Mixed for + and -.
+      System.out.println("Double Mixed");
+      addSubDouble(2);
+      addSubDouble(42);
+      subAddDouble(2);
+      subAddDouble(42);
+      addSub2Double(2);
+      addSub2Double(42);
+      subAdd2Double(2);
+      subAdd2Double(42);
+    }
+
+    @NeverInline
+    public static void add(long x) {
+      System.out.println(3L + x + 2L);
+    }
+
+    @NeverInline
+    public static void mul(long x) {
+      System.out.println(3L * x * 2L);
+    }
+
+    @NeverInline
+    public static void and(long x) {
+      System.out.println(3L & x & 2L);
+    }
+
+    @NeverInline
+    public static void or(long x) {
+      System.out.println(3L | x | 2L);
+    }
+
+    @NeverInline
+    public static void xor(long x) {
+      System.out.println(3L ^ x ^ 2L);
+    }
+
+    @NeverInline
+    public static void shl(long x) {
+      System.out.println(x << 2L << 3L);
+    }
+
+    @NeverInline
+    public static void shr(long x) {
+      System.out.println(x >> 2L >> 3L);
+    }
+
+    @NeverInline
+    public static void ushr(long x) {
+      System.out.println(x >>> 2L >>> 3L);
+    }
+
+    @NeverInline
+    public static void sub(long x) {
+      System.out.println(3L - x - 2L);
+    }
+
+    @NeverInline
+    public static void sub2(long x) {
+      System.out.println(x - 3L - 2L);
+    }
+
+    @NeverInline
+    public static void addSub(long x) {
+      System.out.println(3L + x - 2L);
+    }
+
+    @NeverInline
+    public static void addSub2(long x) {
+      System.out.println(x + 3L - 2L);
+    }
+
+    @NeverInline
+    public static void subAdd(long x) {
+      System.out.println(3L - x + 2L);
+    }
+
+    @NeverInline
+    public static void subAdd2(long x) {
+      System.out.println(x - 3L + 2L);
+    }
+
+    @NeverInline
+    public static void addDouble(long x) {
+      System.out.println(3L + x + 2L + 5);
+    }
+
+    @NeverInline
+    public static void mulDouble(long x) {
+      System.out.println(3L * x * 2L * 7L);
+    }
+
+    @NeverInline
+    public static void andDouble(long x) {
+      System.out.println(3L & x & 2L & 7L);
+    }
+
+    @NeverInline
+    public static void orDouble(long x) {
+      System.out.println(3L | x | 2L | 7L);
+    }
+
+    @NeverInline
+    public static void xorDouble(long x) {
+      System.out.println(3L ^ x ^ 2L ^ 7L);
+    }
+
+    @NeverInline
+    public static void shlDouble(long x) {
+      System.out.println(x << 2L << 3L << 1L);
+    }
+
+    @NeverInline
+    public static void shrDouble(long x) {
+      System.out.println(x >> 2L >> 3L >> 1L);
+    }
+
+    @NeverInline
+    public static void ushrDouble(long x) {
+      System.out.println(x >>> 2L >>> 3L >>> 1L);
+    }
+
+    @NeverInline
+    public static void subDouble(long x) {
+      System.out.println(3L - x - 2L);
+    }
+
+    @NeverInline
+    public static void sub2Double(long x) {
+      System.out.println(x - 3L - 2L - 7L);
+    }
+
+    @NeverInline
+    public static void addSubDouble(long x) {
+      System.out.println(3L + x - 2L - 7L);
+    }
+
+    @NeverInline
+    public static void addSub2Double(long x) {
+      System.out.println(x + 3L - 2L - 7L);
+    }
+
+    @NeverInline
+    public static void subAddDouble(long x) {
+      System.out.println(3L - x + 2L + 4L);
+    }
+
+    @NeverInline
+    public static void subAdd2Double(long x) {
+      System.out.println(x - 3L + 2L + 4L);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingIntTest.java b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingIntTest.java
new file mode 100644
index 0000000..9d7b58d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingIntTest.java
@@ -0,0 +1,603 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IdentityAbsorbingIntTest extends TestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "2147483647",
+          "2147483647",
+          "2147483647",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "2147483646",
+          "2147483646",
+          "2147483646",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-2147483648",
+          "-2147483648",
+          "-2147483648",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-2147483647",
+          "-2147483647",
+          "-2147483647",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "1",
+          "1",
+          "1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public IdentityAbsorbingIntTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    inspector
+        .clazz(Main.class)
+        .forAllMethods(
+            m ->
+                assertTrue(
+                    m.streamInstructions()
+                        .noneMatch(i -> i.isIntLogicalBinop() || i.isIntArithmeticBinop())));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      intTests(Integer.MAX_VALUE);
+      intTests(Integer.MAX_VALUE - 1);
+      intTests(Integer.MIN_VALUE);
+      intTests(Integer.MIN_VALUE + 1);
+      intTests(System.currentTimeMillis() > 0 ? 0 : 1);
+      intTests(System.currentTimeMillis() > 0 ? 1 : 9);
+      intTests(System.currentTimeMillis() > 0 ? -1 : 1);
+    }
+
+    private static void intTests(int val) {
+      identityIntTest(val);
+      absorbingIntTest(val);
+      identityDoubleIntTest(val);
+      absorbingDoubleIntTest(val);
+      chainIntTest(val);
+      associativeIdentityIntTest(val);
+    }
+
+    @NeverInline
+    private static void identityDoubleIntTest(int val) {
+      System.out.println(val + 0 + 0);
+      System.out.println(0 + val + 0);
+      System.out.println(0 + 0 + val);
+      System.out.println(val - 0 - 0);
+      System.out.println(val * 1 * 1);
+      System.out.println(1 * val * 1);
+      System.out.println(1 * 1 * val);
+      System.out.println(val / 1 / 1);
+
+      System.out.println(val & -1 & -1);
+      System.out.println(-1 & val & -1);
+      System.out.println(-1 & -1 & val);
+      System.out.println(val | 0 | 0);
+      System.out.println(0 | val | 0);
+      System.out.println(0 | 0 | val);
+      System.out.println(val ^ 0 ^ 0);
+      System.out.println(0 ^ val ^ 0);
+      System.out.println(0 ^ 0 ^ val);
+      System.out.println(val << 0 << 0);
+      System.out.println(val >> 0 >> 0);
+      System.out.println(val >>> 0 >>> 0);
+    }
+
+    @NeverInline
+    private static void identityIntTest(int val) {
+      System.out.println(val + 0);
+      System.out.println(0 + val);
+      System.out.println(val - 0);
+      System.out.println(val * 1);
+      System.out.println(1 * val);
+      System.out.println(val / 1);
+
+      System.out.println(val & -1);
+      System.out.println(-1 & val);
+      System.out.println(val | 0);
+      System.out.println(0 | val);
+      System.out.println(val ^ 0);
+      System.out.println(0 ^ val);
+      System.out.println(val << 0);
+      System.out.println(val >> 0);
+      System.out.println(val >>> 0);
+    }
+
+    @NeverInline
+    private static void associativeIdentityIntTest(int val) {
+      int minusOne = -1;
+      System.out.println(val + 1 + minusOne);
+      System.out.println(val + 1 - 1);
+      System.out.println(val - 1 - minusOne);
+    }
+
+    @NeverInline
+    private static void absorbingDoubleIntTest(int val) {
+      System.out.println(val * 0 * 0);
+      System.out.println(0 * val * 0);
+      System.out.println(0 * 0 * val);
+      // val would need to be proven non zero.
+      // System.out.println(0 / val);
+      // System.out.println(0 % val);
+
+      System.out.println(0 & 0 & val);
+      System.out.println(0 & val & 0);
+      System.out.println(val & 0 & 0);
+      System.out.println(-1 | -1 | val);
+      System.out.println(-1 | val | -1);
+      System.out.println(val | -1 | -1);
+      System.out.println(0 << 0 << val);
+      System.out.println(0 >> 0 >> val);
+      System.out.println(0 >>> 0 >>> val);
+    }
+
+    @NeverInline
+    private static void absorbingIntTest(int val) {
+      System.out.println(val * 0);
+      System.out.println(0 * val);
+      // val would need to be proven non zero.
+      // System.out.println(0 / val);
+      // System.out.println(0 % val);
+
+      System.out.println(0 & val);
+      System.out.println(val & 0);
+      System.out.println(-1 | val);
+      System.out.println(val | -1);
+      System.out.println(0 << val);
+      System.out.println(0 >> val);
+      System.out.println(0 >>> val);
+    }
+
+    private static void chainIntTest(int val) {
+      int abs = System.currentTimeMillis() > 0 ? val * 0 : 0 * val;
+      System.out.println(abs * val);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingLongTest.java b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingLongTest.java
new file mode 100644
index 0000000..a1f0c1c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingLongTest.java
@@ -0,0 +1,590 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IdentityAbsorbingLongTest extends TestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775807",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "9223372036854775806",
+          "9223372036854775806",
+          "9223372036854775806",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775808",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "-9223372036854775807",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "1",
+          "1",
+          "1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1",
+          "0",
+          "0",
+          "0",
+          "-1",
+          "-1",
+          "-1");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public IdentityAbsorbingLongTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    inspector
+        .clazz(Main.class)
+        .forAllMethods(
+            m ->
+                assertTrue(
+                    m.streamInstructions()
+                        .noneMatch(i -> i.isLongArithmeticBinop() || i.isLongLogicalBinop())));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      longTests(Long.MAX_VALUE);
+      longTests(Long.MAX_VALUE - 1);
+      longTests(Long.MIN_VALUE);
+      longTests(Long.MIN_VALUE + 1);
+      longTests(System.currentTimeMillis() > 0 ? 0L : 1L);
+      longTests(System.currentTimeMillis() > 0 ? 1L : 9L);
+      longTests(System.currentTimeMillis() > 0 ? -1L : 1L);
+    }
+
+    private static void longTests(long val) {
+      identityLongTest(val);
+      absorbingLongTest(val);
+      identityDoubleLongTest(val);
+      absorbingDoubleLongTest(val);
+      associativeIdentityLongTest(val);
+    }
+
+    @NeverInline
+    private static void identityDoubleLongTest(long val) {
+      System.out.println(val + 0L + 0L);
+      System.out.println(0L + val + 0L);
+      System.out.println(0L + 0L + val);
+      System.out.println(val - 0L - 0L);
+      System.out.println(val * 1L * 1L);
+      System.out.println(1L * val * 1L);
+      System.out.println(1L * 1L * val);
+      System.out.println(val / 1L / 1L);
+
+      System.out.println(val & -1L & -1L);
+      System.out.println(-1L & val & -1L);
+      System.out.println(-1L & -1L & val);
+      System.out.println(val | 0L | 0L);
+      System.out.println(0L | val | 0L);
+      System.out.println(0L | 0L | val);
+      System.out.println(val ^ 0L ^ 0L);
+      System.out.println(0L ^ val ^ 0L);
+      System.out.println(0L ^ 0L ^ val);
+      System.out.println(val << 0L << 0L);
+      System.out.println(val >> 0L >> 0L);
+      System.out.println(val >>> 0L >>> 0L);
+    }
+
+    @NeverInline
+    private static void identityLongTest(long val) {
+      System.out.println(val + 0L);
+      System.out.println(0L + val);
+      System.out.println(val - 0L);
+      System.out.println(val * 1L);
+      System.out.println(1L * val);
+      System.out.println(val / 1L);
+
+      System.out.println(val & -1L);
+      System.out.println(-1L & val);
+      System.out.println(val | 0L);
+      System.out.println(0L | val);
+      System.out.println(val ^ 0L);
+      System.out.println(0L ^ val);
+      System.out.println(val << 0L);
+      System.out.println(val >> 0L);
+      System.out.println(val >>> 0L);
+    }
+
+    @NeverInline
+    private static void associativeIdentityLongTest(long val) {
+      long minusOne = -1L;
+      System.out.println(val + 1L + minusOne);
+      System.out.println(val + 1L - 1L);
+      System.out.println(val - 1L - minusOne);
+    }
+
+    @NeverInline
+    private static void absorbingDoubleLongTest(long val) {
+      System.out.println(val * 0L * 0L);
+      System.out.println(0L * val * 0L);
+      System.out.println(0L * 0L * val);
+      // val would need to be proven non zero.
+      // System.out.println(0L / val);
+      // System.out.println(0L % val);
+
+      System.out.println(0L & 0L & val);
+      System.out.println(0L & val & 0L);
+      System.out.println(val & 0L & 0L);
+      System.out.println(-1L | -1L | val);
+      System.out.println(-1L | val | -1L);
+      System.out.println(val | -1L | -1L);
+      System.out.println(0L << 0L << val);
+      System.out.println(0L >> 0L >> val);
+      System.out.println(0L >>> 0L >>> val);
+    }
+
+    @NeverInline
+    private static void absorbingLongTest(long val) {
+      System.out.println(val * 0L);
+      System.out.println(0L * val);
+      // val would need to be proven non zero.
+      // System.out.println(0L / val);
+      // System.out.println(0L % val);
+
+      System.out.println(0L & val);
+      System.out.println(val & 0L);
+      System.out.println(-1L | val);
+      System.out.println(val | -1L);
+      System.out.println(0L << val);
+      System.out.println(0L >> val);
+      System.out.println(0L >>> val);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingTest.java b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingTest.java
deleted file mode 100644
index 390d6ca..0000000
--- a/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingTest.java
+++ /dev/null
@@ -1,1063 +0,0 @@
-// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir;
-
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class IdentityAbsorbingTest extends TestBase {
-
-  private static final String EXPECTED_RESULT =
-      StringUtils.lines(
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "2147483647",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "2147483646",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "-2147483648",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "-2147483647",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "9223372036854775807",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "9223372036854775806",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "-9223372036854775808",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "-9223372036854775807",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "0",
-          "-1",
-          "-1",
-          "-1",
-          "0",
-          "0",
-          "0");
-
-  private final TestParameters parameters;
-
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build();
-  }
-
-  public IdentityAbsorbingTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void testD8() throws Exception {
-    testForRuntime(parameters)
-        .addProgramClasses(Main.class)
-        .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutput(EXPECTED_RESULT);
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
-        .addProgramClasses(Main.class)
-        .addKeepMainRule(Main.class)
-        .enableInliningAnnotations()
-        .setMinApi(parameters)
-        .compile()
-        .inspect(this::inspect)
-        .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutput(EXPECTED_RESULT);
-  }
-
-  private void inspect(CodeInspector inspector) {
-    inspector
-        .clazz(Main.class)
-        .forAllMethods(
-            m ->
-                assertTrue(
-                    m.streamInstructions()
-                        .noneMatch(
-                            i -> i.isIntOrLongLogicalBinop() || i.isIntOrLongArithmeticBinop())));
-  }
-
-  static class Main {
-
-    public static void main(String[] args) {
-      intTests(Integer.MAX_VALUE);
-      intTests(Integer.MAX_VALUE - 1);
-      intTests(Integer.MIN_VALUE);
-      intTests(Integer.MIN_VALUE + 1);
-      intTests(System.currentTimeMillis() > 0 ? 0 : 1);
-      intTests(System.currentTimeMillis() > 0 ? 1 : 9);
-      intTests(System.currentTimeMillis() > 0 ? -1 : 1);
-
-      longTests(Long.MAX_VALUE);
-      longTests(Long.MAX_VALUE - 1);
-      longTests(Long.MIN_VALUE);
-      longTests(Long.MIN_VALUE + 1);
-      longTests(System.currentTimeMillis() > 0 ? 0L : 1L);
-      longTests(System.currentTimeMillis() > 0 ? 1L : 9L);
-      longTests(System.currentTimeMillis() > 0 ? -1L : 1L);
-    }
-
-    private static void longTests(long val) {
-      identityLongTest(val);
-      absorbingLongTest(val);
-      identityDoubleLongTest(val);
-      absorbingDoubleLongTest(val);
-    }
-
-    private static void intTests(int val) {
-      identityIntTest(val);
-      absorbingIntTest(val);
-      identityDoubleIntTest(val);
-      absorbingDoubleIntTest(val);
-      chainIntTest(val);
-    }
-
-    @NeverInline
-    private static void identityDoubleIntTest(int val) {
-      System.out.println(val + 0 + 0);
-      System.out.println(0 + val + 0);
-      System.out.println(0 + 0 + val);
-      System.out.println(val - 0 - 0);
-      System.out.println(val * 1 * 1);
-      System.out.println(1 * val * 1);
-      System.out.println(1 * 1 * val);
-      System.out.println(val / 1 / 1);
-
-      System.out.println(val & -1 & -1);
-      System.out.println(-1 & val & -1);
-      System.out.println(-1 & -1 & val);
-      System.out.println(val | 0 | 0);
-      System.out.println(0 | val | 0);
-      System.out.println(0 | 0 | val);
-      System.out.println(val ^ 0 ^ 0);
-      System.out.println(0 ^ val ^ 0);
-      System.out.println(0 ^ 0 ^ val);
-      System.out.println(val << 0 << 0);
-      System.out.println(val >> 0 >> 0);
-      System.out.println(val >>> 0 >>> 0);
-    }
-
-    @NeverInline
-    private static void identityDoubleLongTest(long val) {
-      System.out.println(val + 0L + 0L);
-      System.out.println(0L + val + 0L);
-      System.out.println(0L + 0L + val);
-      System.out.println(val - 0L - 0L);
-      System.out.println(val * 1L * 1L);
-      System.out.println(1L * val * 1L);
-      System.out.println(1L * 1L * val);
-      System.out.println(val / 1L / 1L);
-
-      System.out.println(val & -1L & -1L);
-      System.out.println(-1L & val & -1L);
-      System.out.println(-1L & -1L & val);
-      System.out.println(val | 0L | 0L);
-      System.out.println(0L | val | 0L);
-      System.out.println(0L | 0L | val);
-      System.out.println(val ^ 0L ^ 0L);
-      System.out.println(0L ^ val ^ 0L);
-      System.out.println(0L ^ 0L ^ val);
-      System.out.println(val << 0L << 0L);
-      System.out.println(val >> 0L >> 0L);
-      System.out.println(val >>> 0L >>> 0L);
-    }
-
-    @NeverInline
-    private static void identityIntTest(int val) {
-      System.out.println(val + 0);
-      System.out.println(0 + val);
-      System.out.println(val - 0);
-      System.out.println(val * 1);
-      System.out.println(1 * val);
-      System.out.println(val / 1);
-
-      System.out.println(val & -1);
-      System.out.println(-1 & val);
-      System.out.println(val | 0);
-      System.out.println(0 | val);
-      System.out.println(val ^ 0);
-      System.out.println(0 ^ val);
-      System.out.println(val << 0);
-      System.out.println(val >> 0);
-      System.out.println(val >>> 0);
-    }
-
-    @NeverInline
-    private static void identityLongTest(long val) {
-      System.out.println(val + 0L);
-      System.out.println(0L + val);
-      System.out.println(val - 0L);
-      System.out.println(val * 1L);
-      System.out.println(1L * val);
-      System.out.println(val / 1L);
-
-      System.out.println(val & -1L);
-      System.out.println(-1L & val);
-      System.out.println(val | 0L);
-      System.out.println(0L | val);
-      System.out.println(val ^ 0L);
-      System.out.println(0L ^ val);
-      System.out.println(val << 0L);
-      System.out.println(val >> 0L);
-      System.out.println(val >>> 0L);
-    }
-
-    @NeverInline
-    private static void absorbingDoubleIntTest(int val) {
-      System.out.println(val * 0 * 0);
-      System.out.println(0 * val * 0);
-      System.out.println(0 * 0 * val);
-      // val would need to be proven non zero.
-      // System.out.println(0 / val);
-      // System.out.println(0 % val);
-
-      System.out.println(0 & 0 & val);
-      System.out.println(0 & val & 0);
-      System.out.println(val & 0 & 0);
-      System.out.println(-1 | -1 | val);
-      System.out.println(-1 | val | -1);
-      System.out.println(val | -1 | -1);
-      System.out.println(0 << 0 << val);
-      System.out.println(0 >> 0 >> val);
-      System.out.println(0 >>> 0 >>> val);
-    }
-
-    @NeverInline
-    private static void absorbingDoubleLongTest(long val) {
-      System.out.println(val * 0L * 0L);
-      System.out.println(0L * val * 0L);
-      System.out.println(0L * 0L * val);
-      // val would need to be proven non zero.
-      // System.out.println(0L / val);
-      // System.out.println(0L % val);
-
-      System.out.println(0L & 0L & val);
-      System.out.println(0L & val & 0L);
-      System.out.println(val & 0L & 0L);
-      System.out.println(-1L | -1L | val);
-      System.out.println(-1L | val | -1L);
-      System.out.println(val | -1L | -1L);
-      System.out.println(0L << 0L << val);
-      System.out.println(0L >> 0L >> val);
-      System.out.println(0L >>> 0L >>> val);
-    }
-
-    @NeverInline
-    private static void absorbingIntTest(int val) {
-      System.out.println(val * 0);
-      System.out.println(0 * val);
-      // val would need to be proven non zero.
-      // System.out.println(0 / val);
-      // System.out.println(0 % val);
-
-      System.out.println(0 & val);
-      System.out.println(val & 0);
-      System.out.println(-1 | val);
-      System.out.println(val | -1);
-      System.out.println(0 << val);
-      System.out.println(0 >> val);
-      System.out.println(0 >>> val);
-    }
-
-    @NeverInline
-    private static void absorbingLongTest(long val) {
-      System.out.println(val * 0L);
-      System.out.println(0L * val);
-      // val would need to be proven non zero.
-      // System.out.println(0L / val);
-      // System.out.println(0L % val);
-
-      System.out.println(0L & val);
-      System.out.println(val & 0L);
-      System.out.println(-1L | val);
-      System.out.println(val | -1L);
-      System.out.println(0L << val);
-      System.out.println(0L >> val);
-      System.out.println(0L >>> val);
-    }
-
-    private static void chainIntTest(int val) {
-      int abs = System.currentTimeMillis() > 0 ? val * 0 : 0 * val;
-      System.out.println(abs * val);
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index d89807c..9100c82 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -72,6 +72,10 @@
       MethodSubject method,
       List<IRCode> additionalCode)
       throws ExecutionException {
+    // Some tests play fast and loose with IR and the SSA value numbers are not generally unique.
+    if (additionalCode != null && !additionalCode.isEmpty()) {
+      options.testing.ignoreValueNumbering = true;
+    }
     AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(application.asDirect());
     appView.setAppServices(AppServices.builder(appView).build());
     ProfileCollectionAdditions profileCollectionAdditions = ProfileCollectionAdditions.nop();
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index 0d23c77..25fc777 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -9,9 +9,11 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
+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.NumberGenerator;
+import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
@@ -69,6 +71,20 @@
       this.code = method.buildIR();
       this.additionalCode = additionalCode;
       this.consumers = new AndroidAppConsumers(appView.options());
+      int largestValueNumber = -1;
+      for (BasicBlock block : code.blocks) {
+        for (Phi phi : block.getPhis()) {
+          largestValueNumber = Math.max(largestValueNumber, phi.getNumber());
+        }
+        for (Instruction instruction : block.getInstructions()) {
+          if (instruction.hasOutValue()) {
+            largestValueNumber = Math.max(largestValueNumber, instruction.outValue().getNumber());
+          }
+        }
+      }
+      while (valueNumberGenerator.peek() <= largestValueNumber) {
+        valueNumberGenerator.next();
+      }
     }
 
     public int countArgumentInstructions() {
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodWithLineNumberTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodWithLineNumberTest.java
index a6aded1..132b0f5 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodWithLineNumberTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodWithLineNumberTest.java
@@ -46,7 +46,7 @@
           "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
           "com.foo -> b:",
           "    1:1:int f1(boolean) -> f2",
-          "    8:8:void f1(int) -> f3",
+          "    8:8:void f1(int):2:2 -> f3",
           "    # {'id':'com.android.tools.r8.residualsignature','signature':'(Z)V'}");
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineFollowedByInlineTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineFollowedByInlineTest.java
index 04ffc15..dbfcdb5 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineFollowedByInlineTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineFollowedByInlineTest.java
@@ -58,9 +58,9 @@
       StringUtils.unixLines(
           "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
           "com.bar -> c:",
-          "    2:2:void inlinee1():43:43 -> y",
+          "    2:2:void com.foo.inlinee1():43:43 -> y",
           "    2:2:void foo.bar.baz.inlinee1():41 -> y",
-          "    2:2:void caller():40 -> y",
+          "    2:2:void com.foo.caller():40 -> y",
           "    2:2:void inlinee2():42:42 -> y",
           "    2:2:void foo.bar.baz.inlinee1():41 -> y",
           "    2:2:void caller():40 -> y",
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineResidualQualifiedTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineResidualQualifiedTest.java
new file mode 100644
index 0000000..1912d84
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineResidualQualifiedTest.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2023, 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.mappingcompose;
+
+import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MappingComposer;
+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.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ComposeInlineResidualQualifiedTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  private static final String mappingFoo =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "com.foo -> A:",
+          "    1:3:void method1():42:44 -> x",
+          "com.bar -> B:",
+          "    4:6:void method2():104:106 -> x");
+  private static final String mappingBar =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "B -> C:",
+          "    1:3:void A.x():2:2 -> y",
+          "    1:3:void x():4 -> y",
+          "    2:3:void x():5:6 -> y");
+  private static final String mappingResult =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "com.bar -> C:",
+          "    1:3:void com.foo.method1():43:43 -> y",
+          "    1:3:void method2():104:104 -> y",
+          "    2:3:void method2():105:106 -> y",
+          "com.foo -> A:");
+
+  @Test
+  public void testCompose() throws Exception {
+    ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+    ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+    String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+    assertEquals(mappingResult, doubleToSingleQuote(composed));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMethodWithLineNumberRemovedTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMethodWithLineNumberRemovedTest.java
new file mode 100644
index 0000000..9b28d99
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMethodWithLineNumberRemovedTest.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2023, 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.mappingcompose;
+
+import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MappingComposer;
+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.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/*
+ * This is a regression test for b/284925475.
+ */
+@RunWith(Parameterized.class)
+public class ComposeMethodWithLineNumberRemovedTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  private static final String mappingFoo =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "package.FieldDefinition$FullFieldDefinition -> package.internal.Th:",
+          "package.FieldDefinition -> package.internal.Uh:",
+          "# {'id':'sourceFile','fileName':'FieldDefinition.java'}",
+          "    1:1:void <init>():13:13 -> <init>",
+          "    1:1:package.FieldDefinition$FullFieldDefinition asFullFieldDefinition():0:0 -> a",
+          "    # {'id':'com.android.tools.r8.residualsignature',"
+              + "'signature':'()Lpackage/internal/Th;'}");
+  private static final String mappingBar =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "package.ClassReference ->" + " package.ClassReference:",
+          "package.internal.Th -> package.other_internal.F1:",
+          "package.internal.Uh -> package.other_internal.M1:",
+          "# {'id':'sourceFile','fileName':'R8_new_hash_name'}",
+          "    1:1:void <init>() -> <init>",
+          "    package.internal.Th a() -> b",
+          "    # {'id':'com.android.tools.r8.residualsignature',"
+              + "'signature':'()Lpackage/retrace_internal/F1;'}");
+  private static final String mappingResult =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "package.ClassReference -> package.ClassReference:",
+          "package.FieldDefinition -> package.other_internal.M1:",
+          "# {'id':'sourceFile','fileName':'FieldDefinition.java'}",
+          "    package.internal.Th a() -> b",
+          "    # {'id':'com.android.tools.r8.residualsignature',"
+              + "'signature':'()Lpackage/retrace_internal/F1;'}",
+          "    1:1:void <init>():13:13 -> <init>",
+          "package.FieldDefinition$FullFieldDefinition -> package.other_internal.F1:");
+
+  @Test
+  public void testCompose() throws Exception {
+    ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+    ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+    String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+    assertEquals(mappingResult, doubleToSingleQuote(composed));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodTest.java
index 80c4dec..2991061 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodTest.java
@@ -46,7 +46,7 @@
           "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
           "com.bar -> b:",
           "    int some.other.Class.f1() -> h1",
-          "    void f2() -> h2",
+          "    void com.foo.f2() -> h2",
           "com.foo -> a:");
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodWithPositionTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodWithPositionTest.java
index 68d2c86..db17b8d 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodWithPositionTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMovedMethodWithPositionTest.java
@@ -48,7 +48,7 @@
           "com.bar -> b:",
           "    12:14:int some.other.Class.f1():102:104 -> h11",
           "    15:16:int some.other.Class.f1():102:103 -> h12",
-          "    22:23:void f2():114:114 -> h2",
+          "    22:23:void com.foo.f2():114:114 -> h2",
           "com.foo -> a:");
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineHavingInlineInlinedTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineHavingInlineInlinedTest.java
new file mode 100644
index 0000000..03e93e6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineHavingInlineInlinedTest.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2023, 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.mappingcompose;
+
+import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MappingComposer;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+public class ComposeOutlineHavingInlineInlinedTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  private static final String mappingFoo =
+      StringUtils.unixLines(
+          "# { id: 'com.android.tools.r8.mapping', version: '2.2' }",
+          "outline.Class -> A:",
+          "    1:2:int some.inlinee():75:76 -> a",
+          "    1:2:int outline():0 -> a",
+          "    # { 'id':'com.android.tools.r8.outline' }",
+          "outline.Callsite -> X:",
+          "    1:2:int outlineCaller(int):41:42 -> s",
+          "    3:3:int outlineCaller(int):0:0 -> s",
+          "    # { 'id':'com.android.tools.r8.outlineCallsite',"
+              + "'positions': { '1': 9, '2': 10 },"
+              + "'outline':'La;a()I' }",
+          "    9:9:int outlineCaller(int):23 -> s",
+          "    10:10:int foo.bar.baz.outlineCaller(int):98:98 -> s",
+          "    10:10:int outlineCaller(int):24 -> s");
+  private static final String mappingBar =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "X -> Y:",
+          "    1:1:int A.a():1:1 -> s",
+          "    1:1:int s(int):3 -> s",
+          "    2:4:int another.inline():102:104 -> s",
+          "    2:4:int A.a():2 -> s",
+          "    2:4:int s(int):3 -> s");
+  private static final String mappingResult =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "outline.Callsite -> Y:",
+          "    1:1:int some.inlinee():75:75 -> s",
+          "    1:1:int outlineCaller(int):23 -> s",
+          "    2:4:int another.inline():102:104 -> s",
+          "    2:4:int some.inlinee():76:76 -> s",
+          "    2:4:int foo.bar.baz.outlineCaller(int):98:98 -> s",
+          "    2:4:int outlineCaller(int):24 -> s",
+          "outline.Class -> A:");
+
+  @Test
+  public void testCompose() throws Exception {
+    ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+    ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+    String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+    assertEquals(mappingResult, doubleToSingleQuote(composed));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlineTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlineTest.java
new file mode 100644
index 0000000..0c50e79
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlineTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2023, 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.mappingcompose;
+
+import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MappingComposer;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+public class ComposeOutlineInlineTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  private static final String mappingFoo =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "package.Class$$ExternalSyntheticOutline0 -> package.internal.X:",
+          "# {'id':'sourceFile','fileName':'R8$$SyntheticClass'}",
+          "# {'id':'com.android.tools.r8.synthesized'}",
+          "    1:2:long package.Int2IntLinkedOpenHashMap$$InternalSyntheticOutline$HASH$0"
+              + ".m(long,long,long):0:1"
+              + " -> a",
+          "      # {'id':'com.android.tools.r8.synthesized'}",
+          "      # {'id':'com.android.tools.r8.outline'}",
+          "package.Class -> package.internal.Y:",
+          "# {'id':'sourceFile','fileName':'FieldDefinition.java'}",
+          "    1:6:void foo():21:26 -> a",
+          "    7:7:void foo():0:0 -> a",
+          "    # {'id':'com.android.tools.r8.outlineCallsite',"
+              + "'positions':{'1':10,'2':11},"
+              + "'outline':'Lpackage/internal/X;a(JJJ)J'}",
+          "    8:9:void foo():38:39 -> a",
+          "    10:10:void inlineeInOutline():1337:1337 -> a",
+          "    10:10:void foo():42 -> a",
+          "    11:11:void foo():44:44 -> a");
+  private static final String mappingBar =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "package.internal.Y -> package.new_internal.Y:",
+          "    1:6:void a() -> b",
+          "    7:8:long package.internal.X.a(long,long,long):1:2 -> b",
+          "    7:8:void a():7 -> b",
+          "    9:10:void a():8:9 -> b");
+  private static final String mappingResult =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "package.Class -> package.new_internal.Y:",
+          "# {'id':'sourceFile','fileName':'FieldDefinition.java'}",
+          "    1:6:void foo():21:26 -> b",
+          "    7:7:void inlineeInOutline():1337:1337 -> b",
+          "    7:7:void foo():42 -> b",
+          "    8:8:void foo():44:44 -> b",
+          "    9:10:void foo():38:39 -> b",
+          "package.Class$$ExternalSyntheticOutline0 -> package.internal.X:",
+          "# {'id':'sourceFile','fileName':'R8$$SyntheticClass'}",
+          "# {'id':'com.android.tools.r8.synthesized'}");
+
+  @Test
+  public void testCompose() throws Exception {
+    ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+    ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+    String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+    assertEquals(mappingResult, doubleToSingleQuote(composed));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlinedIntoOutlineAndInlinedTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlinedIntoOutlineAndInlinedTest.java
new file mode 100644
index 0000000..9dc41ea
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineInlinedIntoOutlineAndInlinedTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2023, 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.mappingcompose;
+
+import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MappingComposer;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+public class ComposeOutlineInlinedIntoOutlineAndInlinedTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  private static final String mappingFoo =
+      StringUtils.unixLines(
+          "# { id: 'com.android.tools.r8.mapping', version: '2.2' }",
+          "Outline1 -> A:",
+          "    1:2:int outline1():0:1 -> a",
+          "    # { 'id':'com.android.tools.r8.outline' }",
+          "Outline2 -> B:",
+          "    1:2:int outline2():0:1 -> a",
+          "    # { 'id':'com.android.tools.r8.outline' }",
+          "outline.Callsite1 -> X:",
+          "    1:2:int caller1(int):41:42 -> s",
+          "    3:3:int caller1(int):0:0 -> s",
+          "    # { 'id':'com.android.tools.r8.outlineCallsite',"
+              + "'positions': { '1': 9, '2': 10 },"
+              + "'outline':'La;a()I' }",
+          "    4:6:int caller1(int):48:49 -> s",
+          "    9:9:int caller1(int):23 -> s",
+          "    10:10:int caller1(int):24 -> s",
+          "outline.Callsite2 -> Y:",
+          "    1:1:int caller2(int):0:0 -> s",
+          "    # { 'id':'com.android.tools.r8.outlineCallsite',"
+              + "'positions': { '1': 2, '2': 3 },"
+              + "'outline':'La;a()I' }",
+          "    2:2:int caller2(int):23:23 -> s",
+          "    3:3:int caller2(int):24:24 -> s");
+  private static final String mappingBar =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "Y -> Z:",
+          "    1:2:int A.a():1:2 -> a",
+          "    1:2:int X.s(int):3 -> a",
+          "    1:2:int B.a():1 -> a",
+          "    1:2:int s(int):1 -> a",
+          "    3:3:int B.a():2:2 -> a",
+          "    3:3:int s(int):1 -> a");
+  private static final String mappingResult =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "Outline1 -> A:",
+          "Outline2 -> B:",
+          "outline.Callsite1 -> X:",
+          "outline.Callsite2 -> Z:",
+          "    1:1:int outline.Callsite1.caller1(int):23 -> a",
+          "    1:1:int caller2(int):23:23 -> a",
+          "    2:2:int outline.Callsite1.caller1(int):24 -> a",
+          "    2:2:int caller2(int):23:23 -> a",
+          "    3:3:int caller2(int):24:24 -> a");
+
+  @Test
+  public void testCompose() throws Exception {
+    ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+    ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+    String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+    assertEquals(mappingResult, doubleToSingleQuote(composed));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java
index 657b551..f722cc3 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java
@@ -41,8 +41,9 @@
           "    5:5:int foo.bar.baz.outlineCaller(int):98:98 -> s",
           "    5:5:int outlineCaller(int):24 -> s",
           "    27:27:int outlineCaller(int):0:0 -> s",
-          "    # { 'id':'com.android.tools.r8.outlineCallsite', 'positions': { '1': 4, '2': 5 },"
-              + " 'outline':'La;a()I' }");
+          "    # { 'id':'com.android.tools.r8.outlineCallsite',"
+              + "'positions': { '1': 4, '2': 5 },"
+              + "'outline':'La;a()I' }");
   private static final String mappingBar =
       StringUtils.unixLines("# {'id':'com.android.tools.r8.mapping','version':'2.2'}", "a -> b:");
   private static final String mappingBaz =
@@ -51,18 +52,18 @@
           "b -> c:",
           "    4:5:int a():1:2 -> m",
           "x -> y:",
-          "    8:9:int s(int):4:5 -> o",
           "    42:42:int s(int):27:27 -> o");
   private static final String mappingResult =
       StringUtils.unixLines(
           "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
           "outline.Callsite -> y:",
-          "    8:8:int outlineCaller(int):23 -> o",
-          "    9:9:int foo.bar.baz.outlineCaller(int):98:98 -> o",
-          "    9:9:int outlineCaller(int):24 -> o",
           "    42:42:int outlineCaller(int):0:0 -> o",
-          "    #"
-              + " {'id':'com.android.tools.r8.outlineCallsite','positions':{'4':8,'5':9},'outline':'Lc;m()I'}",
+          "    # {'id':'com.android.tools.r8.outlineCallsite',"
+              + "'positions':{'4':43,'5':44},"
+              + "'outline':'Lc;m()I'}",
+          "    43:43:int outlineCaller(int):23 -> o",
+          "    44:44:int foo.bar.baz.outlineCaller(int):98:98 -> s",
+          "    44:44:int outlineCaller(int):24 -> o",
           "outline.Class -> c:",
           "    4:5:int some.inlinee():75:76 -> m",
           "    4:5:int outline():0 -> m",
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithIdRangeTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithIdRangeTest.java
new file mode 100644
index 0000000..20b1cb8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineWithIdRangeTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2023, 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.mappingcompose;
+
+import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MappingComposer;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+public class ComposeOutlineWithIdRangeTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  private static final String mappingFoo =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "package.Class$$ExternalSyntheticOutline0 -> package.internal.X:",
+          "# {'id':'sourceFile','fileName':'R8$$SyntheticClass'}",
+          "# {'id':'com.android.tools.r8.synthesized'}",
+          "    1:3:long package.Int2IntLinkedOpenHashMap$$InternalSyntheticOutline$HASH$0"
+              + ".m(long,long,long):0:2"
+              + " -> a",
+          "      # {'id':'com.android.tools.r8.synthesized'}",
+          "      # {'id':'com.android.tools.r8.outline'}",
+          "package.Class -> package.internal.Y:",
+          "# {'id':'sourceFile','fileName':'FieldDefinition.java'}",
+          "    1:6:void foo():21:26 -> a",
+          "    7:7:void foo():0:0 -> a",
+          "    # {'id':'com.android.tools.r8.outlineCallsite',"
+              + "'positions':{'1':10,'2':11},"
+              + "'outline':'Lpackage/internal/X;a(JJJ)J'}",
+          "    8:9:void foo():38:39 -> a",
+          "    10:10:void inlineeInOutline():1337:1337 -> a",
+          "    10:10:void foo():42 -> a",
+          "    11:11:void foo():44:44 -> a");
+  private static final String mappingBar =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "package.internal.X -> package.new_internal.X:",
+          "    1:3:long a(long,long,long) -> b",
+          "package.internal.Y -> package.new_internal.Y:",
+          "    1:8:void a() -> b");
+  private static final String mappingResult =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "package.Class -> package.new_internal.Y:",
+          "# {'id':'sourceFile','fileName':'FieldDefinition.java'}",
+          "    1:6:void foo():21:26 -> b",
+          "    7:7:void foo():0:0 -> b",
+          "    # {'id':'com.android.tools.r8.outlineCallsite',"
+              + "'positions':{'1':9,'2':10},"
+              + "'outline':'Lpackage/new_internal/X;b(JJJ)J'}",
+          "    8:8:void foo():38:38 -> b",
+          "    9:9:void inlineeInOutline():1337:1337 -> a",
+          "    9:9:void foo():42 -> b",
+          "    10:10:void foo():44:44 -> b",
+          "package.Class$$ExternalSyntheticOutline0 -> package.new_internal.X:",
+          "# {'id':'sourceFile','fileName':'R8$$SyntheticClass'}",
+          "# {'id':'com.android.tools.r8.synthesized'}",
+          "    1:3:long package.Int2IntLinkedOpenHashMap$$InternalSyntheticOutline$HASH$0"
+              + ".m(long,long,long):0:2 -> b",
+          "    # {'id':'com.android.tools.r8.synthesized'}",
+          "    # {'id':'com.android.tools.r8.outline'}");
+
+  @Test
+  public void testCompose() throws Exception {
+    ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+    ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+    String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+    assertEquals(mappingResult, doubleToSingleQuote(composed));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMovedMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMovedMethodTest.java
new file mode 100644
index 0000000..0059297
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMovedMethodTest.java
@@ -0,0 +1,137 @@
+// Copyright (c) 2023, 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.naming.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MemberSubject;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This is a reproduction of b/269178203. */
+@RunWith(Parameterized.class)
+public class ApplyMappingMovedMethodTest extends TestBase {
+
+  private static final String mapping =
+      StringUtils.lines(
+          "com.android.tools.r8.naming.applymapping.ApplyMappingMovedMethodTest$One -> b.b:",
+          "    void foo() -> m1",
+          "    void"
+              + " com.android.tools.r8.naming.applymapping.ApplyMappingMovedMethodTest$Other.foo()"
+              + " -> m2");
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8ApplyMappingSameCompilation() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(One.class, Other.class, Main.class)
+        .setMinApi(parameters)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .addApplyMapping(mapping)
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertClassesMerged(One.class, Other.class))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("One::foo")
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz("b.b");
+              assertThat(clazz, isPresent());
+              Set<String> expected = new HashSet<>();
+              expected.add("m1");
+              // TODO(b/269178203): We should be able to rewrite this to m2.
+              expected.add("a");
+              assertEquals(
+                  expected,
+                  clazz.allMethods().stream()
+                      .map(MemberSubject::getFinalName)
+                      .collect(Collectors.toSet()));
+            });
+  }
+
+  @Test
+  public void testR8TestReference() throws Exception {
+    R8TestCompileResult libraryResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(One.class, Other.class, Main.class)
+            .setMinApi(parameters)
+            .addKeepMainRule(Main.class)
+            .addHorizontallyMergedClassesInspector(
+                inspector -> inspector.assertClassesMerged(One.class, Other.class))
+            .enableInliningAnnotations()
+            .compile();
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addClasspathClasses(One.class, Other.class)
+        .addKeepAllClassesRule()
+        .addApplyMapping(libraryResult.getProguardMap())
+        .setMinApi(parameters)
+        .compile()
+        .addRunClasspathFiles(libraryResult.writeToZip())
+        .run(parameters.getRuntime(), TestClass.class)
+        // TODO(b/269178203): We could rewrite calls to m2 but it would be better to just keep the
+        //  endpoints in the program when tests are referencing it - in this example it is this
+        //  compilation that is the test and it referencing the libraryResult, which is the program.
+        .assertFailureWithErrorThatThrows(NoClassDefFoundError.class)
+        .assertFailureWithErrorThatMatches(containsString(typeName(Other.class)));
+  }
+
+  public static class One {
+
+    @NeverInline
+    public static void foo() {
+      System.out.println("One::foo");
+    }
+  }
+
+  public static class Other {
+
+    @NeverInline
+    public static void foo() {
+      System.out.println("Other::foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      if (System.currentTimeMillis() > 0) {
+        One.foo();
+      } else {
+        Other.foo();
+      }
+    }
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      One.foo();
+      Other.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/HorizontalMergingWithTryCatchLineNumberTest.java b/src/test/java/com/android/tools/r8/retrace/HorizontalMergingWithTryCatchLineNumberTest.java
new file mode 100644
index 0000000..ff42a2d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/HorizontalMergingWithTryCatchLineNumberTest.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2023, 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.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.retrace.classes.SynthesizeLineNumber;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This is a test for b/283757617 showing how we potentially could change line information at
+ * runtime.
+ */
+@RunWith(Parameterized.class)
+public class HorizontalMergingWithTryCatchLineNumberTest extends TestBase {
+
+  private static final String FILENAME = "SynthesizeLineNumber.java";
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private static final StackTrace expectedStackTrace =
+      StackTrace.builder()
+          .add(
+              StackTraceLine.builder()
+                  .setClassName(typeName(SynthesizeLineNumber.A.class))
+                  .setMethodName("foo")
+                  .setFileName(FILENAME)
+                  .setLineNumber(14)
+                  .build())
+          .add(
+              StackTraceLine.builder()
+                  .setClassName(typeName(SynthesizeLineNumber.Main.class))
+                  .setMethodName("call")
+                  .setFileName(FILENAME)
+                  .setLineNumber(34)
+                  .build())
+          .add(
+              StackTraceLine.builder()
+                  .setClassName(typeName(SynthesizeLineNumber.Main.class))
+                  .setMethodName("main")
+                  .setFileName(FILENAME)
+                  .setLineNumber(28)
+                  .build())
+          .build();
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(SynthesizeLineNumber.class)
+        .run(parameters.getRuntime(), SynthesizeLineNumber.Main.class, "normal")
+        .inspectStackTrace(stackTrace -> assertThat(stackTrace, isSame(expectedStackTrace)));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestCompileResult compilationResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(SynthesizeLineNumber.class)
+            .setMinApi(parameters)
+            .addKeepMainRule(SynthesizeLineNumber.Main.class)
+            .addKeepAttributeLineNumberTable()
+            .enableInliningAnnotations()
+            .addHorizontallyMergedClassesInspector(
+                inspector ->
+                    inspector.assertClassesMerged(
+                        SynthesizeLineNumber.A.class, SynthesizeLineNumber.B.class))
+            .compile();
+    // Mock changes to proguard map to simulate what R8 could emit.
+    String proguardMap =
+        compilationResult.getProguardMap()
+            + "\n    1000:1000:void call(int,boolean):34:34 -> main"
+            + "\n    1000:1000:void main(java.lang.String[]):28 -> main";
+    compilationResult
+        .run(parameters.getRuntime(), SynthesizeLineNumber.Main.class, "synthesize")
+        .inspectOriginalStackTrace(
+            originalStackTrace -> {
+              StackTrace retraced = originalStackTrace.retrace(proguardMap);
+              assertThat(retraced, isSame(expectedStackTrace));
+            });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/classes/SynthesizeLineNumber.java b/src/test/java/com/android/tools/r8/retrace/classes/SynthesizeLineNumber.java
new file mode 100644
index 0000000..a2b4c80
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/classes/SynthesizeLineNumber.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2023, 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.retrace.classes;
+
+import com.android.tools.r8.NeverInline;
+
+public class SynthesizeLineNumber {
+
+  public static class A {
+    @NeverInline
+    public static void foo() throws Exception {
+      throw new Exception("A::foo");
+    }
+  }
+
+  public static class B {
+    @NeverInline
+    public static void bar() throws Exception {
+      throw new Exception("B::bar");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) throws Exception {
+      call(System.currentTimeMillis() > 0 ? 0 : 1, args[0].equals("synthesize"));
+    }
+
+    public static void call(int classId, boolean shouldSynthesizeLineNumber) throws Exception {
+      try {
+        if (classId == 0) {
+          A.foo();
+        } else {
+          B.bar();
+        }
+      } catch (Exception e) {
+        if (shouldSynthesizeLineNumber) {
+          Exception exception = new Exception(e.getMessage(), e.getCause());
+          StackTraceElement[] stackTrace = e.getStackTrace();
+          // The obfuscated class name after repackaging and class name minification.
+          String className = "com.android.tools.r8.retrace.classes.SynthesizeLineNumber$Main";
+          // The obfuscated method name after minification. Since this is inlined into main it is
+          // just main.
+          String obfuscatedMethodName = "main";
+          for (int i = 0; i < stackTrace.length; i++) {
+            StackTraceElement original = stackTrace[i];
+            // The line number range will need to be the range of the try block for catching the
+            // exception.
+            if (original.getClassName().equals(className)
+                && original.getMethodName().equals(obfuscatedMethodName)
+                && original.getLineNumber() >= 0
+                && original.getLineNumber() <= 10) {
+              stackTrace[i] =
+                  new StackTraceElement(
+                      original.getClassName(),
+                      original.getMethodName(),
+                      original.getFileName(),
+                      classId + 1000);
+              break;
+            }
+          }
+          exception.setStackTrace(stackTrace);
+          throw exception;
+        }
+        throw e;
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java
new file mode 100644
index 0000000..4a27e0f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2023, 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.startup;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.profile.ExternalStartupItem;
+import com.android.tools.r8.startup.profile.ExternalStartupMethod;
+import com.android.tools.r8.startup.utils.StartupTestingUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Regression test for b/285021603. */
+@RunWith(Parameterized.class)
+public class SingleCallerBridgeStartupTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    // Create a startup profile with method A.bar() and A.callBarInStartup() to allow inlining
+    // of A.bar() into A.callBarInStartup().
+    MethodReference barMethod = Reference.methodFromMethod(A.class.getDeclaredMethod("bar"));
+    Collection<ExternalStartupItem> startupProfile =
+        ImmutableList.of(
+            ExternalStartupMethod.builder().setMethodReference(barMethod).build(),
+            ExternalStartupMethod.builder()
+                .setMethodReference(
+                    Reference.methodFromMethod(A.class.getDeclaredMethod("callBarInStartup")))
+                .build());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .apply(testBuilder -> StartupTestingUtils.addStartupProfile(testBuilder, startupProfile))
+        .addOptionsModification(
+            options ->
+                // Simulate proto optimization where we allow reprocessing of inlinee.
+                options.inlinerOptions().applyInliningToInlineePredicateForTesting =
+                    (appView, inlinee, inliningDepth) ->
+                        inlinee.getMethodReference().equals(barMethod))
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), Main.class)
+        // TODO(b/285021603): We should not fail here.
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      A.callBarInStartup();
+      A.callBarOutsideStartup();
+    }
+  }
+
+  public static class A {
+
+    private static void foo() {
+      System.out.println("A::foo");
+    }
+
+    private static void bar() {
+      foo();
+    }
+
+    public static void callBarInStartup() {
+      bar();
+    }
+
+    public static void callBarOutsideStartup() {
+      bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 45d6202..79425a3 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -339,17 +339,27 @@
   }
 
   @Override
-  public boolean isIntOrLongArithmeticBinop() {
+  public boolean isIntArithmeticBinop() {
     return instruction instanceof CfArithmeticBinop
-        && (((CfArithmeticBinop) instruction).getType() == NumericType.INT
-            || ((CfArithmeticBinop) instruction).getType() == NumericType.LONG);
+        && ((CfArithmeticBinop) instruction).getType() == NumericType.INT;
   }
 
   @Override
-  public boolean isIntOrLongLogicalBinop() {
+  public boolean isIntLogicalBinop() {
     return instruction instanceof CfLogicalBinop
-        && (((CfLogicalBinop) instruction).getType() == NumericType.INT
-            || ((CfLogicalBinop) instruction).getType() == NumericType.LONG);
+        && ((CfLogicalBinop) instruction).getType() == NumericType.INT;
+  }
+
+  @Override
+  public boolean isLongArithmeticBinop() {
+    return instruction instanceof CfArithmeticBinop
+        && ((CfArithmeticBinop) instruction).getType() == NumericType.LONG;
+  }
+
+  @Override
+  public boolean isLongLogicalBinop() {
+    return instruction instanceof CfLogicalBinop
+        && ((CfLogicalBinop) instruction).getType() == NumericType.LONG;
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 38421a2..bd68422 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -122,6 +122,8 @@
 import com.android.tools.r8.dex.code.DexReturn;
 import com.android.tools.r8.dex.code.DexReturnObject;
 import com.android.tools.r8.dex.code.DexReturnVoid;
+import com.android.tools.r8.dex.code.DexRsubInt;
+import com.android.tools.r8.dex.code.DexRsubIntLit8;
 import com.android.tools.r8.dex.code.DexSget;
 import com.android.tools.r8.dex.code.DexSgetBoolean;
 import com.android.tools.r8.dex.code.DexSgetByte;
@@ -501,69 +503,77 @@
     return instruction instanceof DexSparseSwitch;
   }
 
-  public boolean isIntOrLongArithmeticBinop() {
+  public boolean isIntArithmeticBinop() {
     return instruction instanceof DexMulInt
         || instruction instanceof DexMulIntLit8
         || instruction instanceof DexMulIntLit16
         || instruction instanceof DexMulInt2Addr
-        || instruction instanceof DexMulLong
-        || instruction instanceof DexMulLong2Addr
         || instruction instanceof DexAddInt
         || instruction instanceof DexAddIntLit8
         || instruction instanceof DexAddIntLit16
         || instruction instanceof DexAddInt2Addr
-        || instruction instanceof DexAddLong
-        || instruction instanceof DexAddLong2Addr
+        || instruction instanceof DexRsubInt
+        || instruction instanceof DexRsubIntLit8
         || instruction instanceof DexSubInt
         || instruction instanceof DexSubInt2Addr
-        || instruction instanceof DexSubLong
-        || instruction instanceof DexSubLong2Addr
         || instruction instanceof DexDivInt
         || instruction instanceof DexDivIntLit8
         || instruction instanceof DexDivIntLit16
         || instruction instanceof DexDivInt2Addr
-        || instruction instanceof DexDivLong
-        || instruction instanceof DexDivLong2Addr
         || instruction instanceof DexRemInt
         || instruction instanceof DexRemIntLit8
         || instruction instanceof DexRemIntLit16
-        || instruction instanceof DexRemInt2Addr
+        || instruction instanceof DexRemInt2Addr;
+  }
+
+  public boolean isLongArithmeticBinop() {
+    return instruction instanceof DexMulLong
+        || instruction instanceof DexMulLong2Addr
+        || instruction instanceof DexAddLong
+        || instruction instanceof DexAddLong2Addr
+        || instruction instanceof DexSubLong
+        || instruction instanceof DexSubLong2Addr
+        || instruction instanceof DexDivLong
+        || instruction instanceof DexDivLong2Addr
         || instruction instanceof DexRemLong
         || instruction instanceof DexRemLong2Addr;
   }
 
-  public boolean isIntOrLongLogicalBinop() {
+  public boolean isIntLogicalBinop() {
     return instruction instanceof DexAndInt
         || instruction instanceof DexAndIntLit8
         || instruction instanceof DexAndIntLit16
         || instruction instanceof DexAndInt2Addr
-        || instruction instanceof DexAndLong
-        || instruction instanceof DexAndLong2Addr
         || instruction instanceof DexOrInt
         || instruction instanceof DexOrIntLit8
         || instruction instanceof DexOrIntLit16
         || instruction instanceof DexOrInt2Addr
-        || instruction instanceof DexOrLong
-        || instruction instanceof DexOrLong2Addr
         || instruction instanceof DexXorInt
         || instruction instanceof DexXorIntLit8
         || instruction instanceof DexXorIntLit16
         || instruction instanceof DexXorInt2Addr
-        || instruction instanceof DexXorLong
-        || instruction instanceof DexXorLong2Addr
         || instruction instanceof DexShrInt
         || instruction instanceof DexShrIntLit8
         || instruction instanceof DexShrInt2Addr
-        || instruction instanceof DexShrLong
-        || instruction instanceof DexShrLong2Addr
         || instruction instanceof DexShlInt
         || instruction instanceof DexShlIntLit8
         || instruction instanceof DexShlInt2Addr
-        || instruction instanceof DexShlLong
-        || instruction instanceof DexShlLong2Addr
         || instruction instanceof DexUshrInt
         || instruction instanceof DexUshrIntLit8
-        || instruction instanceof DexUshrInt2Addr
+        || instruction instanceof DexUshrInt2Addr;
+  }
+
+  public boolean isLongLogicalBinop() {
+    return instruction instanceof DexAndLong
+        || instruction instanceof DexAndLong2Addr
+        || instruction instanceof DexOrLong
+        || instruction instanceof DexOrLong2Addr
+        || instruction instanceof DexXorLong
+        || instruction instanceof DexXorLong2Addr
+        || instruction instanceof DexShrLong
+        || instruction instanceof DexShrLong2Addr
+        || instruction instanceof DexShlLong
+        || instruction instanceof DexShlLong2Addr
         || instruction instanceof DexUshrLong
         || instruction instanceof DexUshrLong2Addr;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 6d1bd94..b77029f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -140,9 +140,13 @@
 
   boolean isSparseSwitch();
 
-  boolean isIntOrLongArithmeticBinop();
+  boolean isIntArithmeticBinop();
 
-  boolean isIntOrLongLogicalBinop();
+  boolean isIntLogicalBinop();
+
+  boolean isLongArithmeticBinop();
+
+  boolean isLongLogicalBinop();
 
   boolean isMultiplication();