Merge commit '5260d7981c1849f8a6f3f6ac9e198c196c1aa46c' into dev-release
diff --git a/build.gradle b/build.gradle
index 360f21a..3032180 100644
--- a/build.gradle
+++ b/build.gradle
@@ -68,6 +68,14 @@
 
 // Custom source set for example tests and generated tests.
 sourceSets {
+    main11 {
+        java {
+            srcDirs = ['src/main/java']
+        }
+        resources {
+            srcDirs = ['src/main/resources']
+        }
+    }
     test {
         java {
             srcDirs = [
@@ -240,6 +248,27 @@
     implementation group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
     implementation group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
     implementation files('third_party/android_jar/api-database/api-database.jar')
+
+    main11Implementation "net.sf.jopt-simple:jopt-simple:$joptSimpleVersion"
+    main11Implementation "com.google.code.gson:gson:$gsonVersion"
+    // Include all of guava when compiling the code, but exclude annotations that we don't
+    // need from the packaging.
+    main11CompileOnly("com.google.guava:guava:$guavaVersion")
+    main11Implementation("com.google.guava:guava:$guavaVersion", {
+        exclude group: 'com.google.errorprone'
+        exclude group: 'com.google.code.findbugs'
+        exclude group: 'com.google.j2objc'
+        exclude group: 'org.codehaus.mojo'
+    })
+    main11Implementation group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion
+    main11Implementation "org.jetbrains.kotlinx:kotlinx-metadata-jvm:$kotlinExtMetadataJVMVersion"
+    main11Implementation group: 'org.ow2.asm', name: 'asm', version: asmVersion
+    main11Implementation group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
+    main11Implementation group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
+    main11Implementation group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
+    main11Implementation group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
+    main11Implementation files('third_party/android_jar/api-database/api-database.jar')
+
     examplesTestNGRunnerCompile group: 'org.testng', name: 'testng', version: testngVersion
     testCompile sourceSets.examples.output
     testCompile "junit:junit:$junitVersion"
@@ -272,7 +301,6 @@
 def r8DesugaredPath = "$buildDir/libs/r8desugared.jar"
 def r8LibGeneratedKeepRulesPath = "$buildDir/generated/keep.txt"
 def r8LibTestPath = "$buildDir/classes/r8libtest"
-def java11ClassFiles = "$buildDir/classes/java/mainJava11"
 
 def osString = OperatingSystem.current().isLinux() ? "linux" :
         OperatingSystem.current().isMacOsX() ? "mac" : "windows"
@@ -593,6 +621,11 @@
         JavaVersion.VERSION_1_10,
         false)
 setJdkCompilationWithCompatibility(
+        sourceSets.main11.compileJavaTaskName,
+        'jdk-11',
+        JavaVersion.VERSION_11,
+        false)
+setJdkCompilationWithCompatibility(
         sourceSets.examplesJava11.compileJavaTaskName,
         'jdk-11',
         JavaVersion.VERSION_11,
@@ -608,25 +641,6 @@
         JavaVersion.VERSION_17,
         false)
 
-task compileMainWithJava11 (type: JavaCompile) {
-    dependsOn downloadDeps
-    def jdkDir = 'third_party/openjdk/jdk-11/'
-    options.fork = true
-    options.forkOptions.jvmArgs = []
-    if (OperatingSystem.current().isLinux()) {
-        options.forkOptions.javaHome = file(jdkDir + 'linux')
-    } else if (OperatingSystem.current().isMacOsX()) {
-        options.forkOptions.javaHome = file(jdkDir + 'osx/Contents/Home')
-    } else {
-        options.forkOptions.javaHome = file(jdkDir + 'windows')
-    }
-    source = sourceSets.main.allSource
-    destinationDir = file(java11ClassFiles)
-    sourceCompatibility = JavaVersion.VERSION_11
-    targetCompatibility = JavaVersion.VERSION_11
-    classpath = sourceSets.main.compileClasspath
-}
-
 task provideJdk11TestsDependencies(type: org.gradle.api.tasks.Copy) {
     from sourceSets.examplesTestNGRunner.compileClasspath
     include "**/**.jar"
@@ -762,8 +776,7 @@
 }
 
 task repackageSources11(type: ShadowJar) {
-    dependsOn compileMainWithJava11
-    from file(java11ClassFiles)
+    from sourceSets.main11.output
     mergeServiceFiles(it)
     baseName 'sources_main_11'
 }
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index c292f33..51c1d2f 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -50,6 +50,7 @@
   private final List<Consumer<Inspector>> outputInspections;
   private final int threadCount;
   private final DumpInputFlags dumpInputFlags;
+  private final MapIdProvider mapIdProvider;
 
   BaseCompilerCommand(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
@@ -66,6 +67,7 @@
     outputInspections = null;
     threadCount = ThreadUtils.NOT_SPECIFIED;
     dumpInputFlags = DumpInputFlags.noDump();
+    mapIdProvider = null;
   }
 
   BaseCompilerCommand(
@@ -82,7 +84,8 @@
       List<AssertionsConfiguration> assertionsConfiguration,
       List<Consumer<Inspector>> outputInspections,
       int threadCount,
-      DumpInputFlags dumpInputFlags) {
+      DumpInputFlags dumpInputFlags,
+      MapIdProvider mapIdProvider) {
     super(app);
     assert minApiLevel > 0;
     assert mode != null;
@@ -99,6 +102,7 @@
     this.outputInspections = outputInspections;
     this.threadCount = threadCount;
     this.dumpInputFlags = dumpInputFlags;
+    this.mapIdProvider = mapIdProvider;
   }
 
   /**
@@ -148,6 +152,10 @@
     return desugarState;
   }
 
+  public MapIdProvider getMapIdProvider() {
+    return mapIdProvider;
+  }
+
   /** True if the output dex files has checksum information encoded in it. False otherwise. */
   public boolean getIncludeClassesChecksum() {
     return includeClassesChecksum;
@@ -217,6 +225,7 @@
     private List<Consumer<Inspector>> outputInspections = new ArrayList<>();
     protected StringConsumer proguardMapConsumer = null;
     private DumpInputFlags dumpInputFlags = DumpInputFlags.noDump();
+    private MapIdProvider mapIdProvider = null;
 
     abstract CompilationMode defaultCompilationMode();
 
@@ -516,6 +525,16 @@
       return desugarState;
     }
 
+    /** Set a custom provider for defining the map id for the build. */
+    public B setMapIdProvider(MapIdProvider mapIdProvider) {
+      this.mapIdProvider = mapIdProvider;
+      return self();
+    }
+
+    public MapIdProvider getMapIdProvider() {
+      return mapIdProvider;
+    }
+
     @Deprecated
     public B addSpecialLibraryConfiguration(String configuration) {
       return addDesugaredLibraryConfiguration(configuration);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 671fb6b..0a7bcd1 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -318,6 +318,7 @@
           mainDexKeepRules,
           getThreadCount(),
           getDumpInputFlags(),
+          getMapIdProvider(),
           factory);
     }
   }
@@ -399,6 +400,7 @@
       ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
       int threadCount,
       DumpInputFlags dumpInputFlags,
+      MapIdProvider mapIdProvider,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -414,7 +416,8 @@
         assertionsConfiguration,
         outputInspections,
         threadCount,
-        dumpInputFlags);
+        dumpInputFlags,
+        mapIdProvider);
     this.intermediate = intermediate;
     this.desugarGraphConsumer = desugarGraphConsumer;
     this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 7c4ac49..c36a796 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -213,6 +213,7 @@
     InternalOptions getInternalOptions() {
       InternalOptions internal = new InternalOptions();
       internal.useSmaliSyntax = useSmali;
+      internal.readDebugSetFileEvent = true;
       return internal;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/DumpOptions.java b/src/main/java/com/android/tools/r8/DumpOptions.java
index 1c0e170..e8c6a49 100644
--- a/src/main/java/com/android/tools/r8/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/DumpOptions.java
@@ -34,6 +34,7 @@
   private static final String TREE_SHAKING_KEY = "tree-shaking";
   private static final String MINIFICATION_KEY = "minification";
   private static final String FORCE_PROGUARD_COMPATIBILITY_KEY = "force-proguard-compatibility";
+  private static final String SYSTEM_PROPERTY_PREFIX = "system-property-";
 
   private final Tool tool;
   private final CompilationMode compilationMode;
@@ -110,6 +111,15 @@
     addOptionalDumpEntry(builder, TREE_SHAKING_KEY, treeShaking);
     addOptionalDumpEntry(builder, MINIFICATION_KEY, minification);
     addOptionalDumpEntry(builder, FORCE_PROGUARD_COMPATIBILITY_KEY, forceProguardCompatibility);
+    System.getProperties()
+        .stringPropertyNames()
+        .forEach(
+            name -> {
+              if (name.startsWith("com.android.tools.r8.")) {
+                String value = System.getProperty(name);
+                addDumpEntry(builder, SYSTEM_PROPERTY_PREFIX + name, value);
+              }
+            });
     return builder.toString();
   }
 
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 7cd95e5..fc11da4 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -100,6 +100,7 @@
       List<Consumer<Inspector>> outputInspections,
       int threadCount,
       DumpInputFlags dumpInputFlags,
+      MapIdProvider mapIdProvider,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -115,7 +116,8 @@
         assertionsConfiguration,
         outputInspections,
         threadCount,
-        dumpInputFlags);
+        dumpInputFlags,
+        mapIdProvider);
     this.d8Command = d8Command;
     this.r8Command = r8Command;
     this.libraryConfiguration = libraryConfiguration;
@@ -409,6 +411,7 @@
           getOutputInspections(),
           getThreadCount(),
           getDumpInputFlags(),
+          getMapIdProvider(),
           factory);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/MapIdEnvironment.java b/src/main/java/com/android/tools/r8/MapIdEnvironment.java
new file mode 100644
index 0000000..f1e4260
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/MapIdEnvironment.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, 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;
+
+/** Environment made available when defining a custom map id for a build. */
+@Keep
+public interface MapIdEnvironment {
+
+  /** Get the computed hash for the mapping file content. */
+  String getMapHash();
+}
diff --git a/src/main/java/com/android/tools/r8/MapIdProvider.java b/src/main/java/com/android/tools/r8/MapIdProvider.java
new file mode 100644
index 0000000..9af775f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/MapIdProvider.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2021, 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;
+
+/**
+ * Interface for providing a custom map-id to the compiler.
+ *
+ * <p>The map-id is inserted in the mapping file output and included in the compiler marker
+ * information. The map-id can be used to provide an key/identifier to automate the lookup of
+ * mapping file information for builds. For example, by including it in the source-file part of
+ * program stacktraces. See {@code SourceFileProvider}.
+ */
+@Keep
+@FunctionalInterface
+public interface MapIdProvider {
+
+  /**
+   * Return the map-id content.
+   *
+   * @param environment An environment of values available for use when defining the map id.
+   * @return A non-null string that will be used as the map id.
+   */
+  String get(MapIdEnvironment environment);
+}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index fdf35d6..bb22da1 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -585,7 +585,8 @@
               synthesizedClassPrefix,
               skipDump,
               getThreadCount(),
-              getDumpInputFlags());
+              getDumpInputFlags(),
+              getMapIdProvider());
 
       return command;
     }
@@ -746,7 +747,8 @@
       String synthesizedClassPrefix,
       boolean skipDump,
       int threadCount,
-      DumpInputFlags dumpInputFlags) {
+      DumpInputFlags dumpInputFlags,
+      MapIdProvider mapIdProvider) {
     super(
         inputApp,
         mode,
@@ -761,7 +763,8 @@
         assertionsConfiguration,
         outputInspections,
         threadCount,
-        dumpInputFlags);
+        dumpInputFlags,
+        mapIdProvider);
     assert proguardConfiguration != null;
     assert mainDexKeepRules != null;
     this.mainDexKeepRules = mainDexKeepRules;
@@ -874,6 +877,8 @@
       internal.enableVerticalClassMerging = false;
     }
 
+    internal.mapIdProvider = getMapIdProvider();
+
     // Amend the proguard-map consumer with options from the proguard configuration.
     internal.proguardMapConsumer =
         wrapStringConsumer(
diff --git a/src/main/java/com/android/tools/r8/StringConsumer.java b/src/main/java/com/android/tools/r8/StringConsumer.java
index 843d145..d0e05cc 100644
--- a/src/main/java/com/android/tools/r8/StringConsumer.java
+++ b/src/main/java/com/android/tools/r8/StringConsumer.java
@@ -48,6 +48,7 @@
   }
 
   /** Empty consumer to request the production of the resource but ignore its value. */
+  @Keep
   class EmptyConsumer implements StringConsumer {
 
     private static final EmptyConsumer EMPTY_CONSUMER = new EmptyConsumer();
@@ -65,7 +66,7 @@
 
   /** Forwarding consumer to delegate to an optional existing consumer. */
   @Keep
-  public class ForwardingConsumer implements StringConsumer {
+  class ForwardingConsumer implements StringConsumer {
 
     private final StringConsumer consumer;
 
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiForHashingClass.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiForHashingClass.java
new file mode 100644
index 0000000..058201f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiForHashingClass.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2021, 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.androidapi;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.function.BiConsumer;
+
+/**
+ * This is an interface for all generated classes from api-versions.xml for building a database from
+ * a serialized hashed format.
+ */
+public interface AndroidApiForHashingClass {
+
+  DexType getType();
+
+  AndroidApiLevel getApiLevel();
+
+  void visitMethodsWithApiLevels(BiConsumer<DexMethod, AndroidApiLevel> consumer);
+
+  void visitFieldsWithApiLevels(BiConsumer<DexField, AndroidApiLevel> consumer);
+}
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
new file mode 100644
index 0000000..551085a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
@@ -0,0 +1,159 @@
+// Copyright (c) 2021, 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.androidapi;
+
+import static com.android.tools.r8.utils.AndroidApiLevel.NOT_SET;
+import static com.android.tools.r8.utils.AndroidApiLevel.UNKNOWN;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.structural.DefaultHashingVisitor;
+import com.android.tools.r8.utils.structural.HasherWrapper;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.ObjectInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class AndroidApiLevelHashingDatabaseImpl implements AndroidApiLevelDatabase {
+
+  public static HasherWrapper getDefaultHasher() {
+    return HasherWrapper.murmur3128Hasher();
+  }
+
+  private final Int2ReferenceMap<AndroidApiLevel> lookupNonAmbiguousCache =
+      new Int2ReferenceOpenHashMap<AndroidApiLevel>();
+  private final Map<String, AndroidApiLevel> ambiguousHashesWithApiLevel = new HashMap<>();
+  private final Map<DexReference, AndroidApiLevel> ambiguousCache = new IdentityHashMap<>();
+  private final DexItemFactory factory;
+
+  public AndroidApiLevelHashingDatabaseImpl(
+      DexItemFactory factory, List<AndroidApiForHashingClass> predefinedApiTypeLookup) {
+    this.factory = factory;
+    loadData();
+    predefinedApiTypeLookup.forEach(
+        apiClass -> {
+          DexType type = apiClass.getType();
+          lookupNonAmbiguousCache.put(type.hashCode(), NOT_SET);
+          ambiguousCache.put(type, apiClass.getApiLevel());
+          apiClass.visitMethodsWithApiLevels(
+              (method, apiLevel) -> {
+                lookupNonAmbiguousCache.put(method.hashCode(), NOT_SET);
+                ambiguousCache.put(method, apiLevel);
+              });
+          apiClass.visitFieldsWithApiLevels(
+              (field, apiLevel) -> {
+                lookupNonAmbiguousCache.put(field.hashCode(), NOT_SET);
+                ambiguousCache.put(field, apiLevel);
+              });
+        });
+  }
+
+  private void loadData() {
+    int[] hashIndices;
+    byte[] apiLevels;
+    List<String> ambiguous;
+    try (InputStream indicesInputStream =
+            getClass()
+                .getClassLoader()
+                .getResourceAsStream("api_database/api_database_hash_lookup.ser");
+        ObjectInputStream indicesObjectStream = new ObjectInputStream(indicesInputStream);
+        InputStream apiInputStream =
+            getClass()
+                .getClassLoader()
+                .getResourceAsStream("api_database/api_database_api_level.ser");
+        ObjectInputStream apiObjectStream = new ObjectInputStream(apiInputStream);
+        InputStream ambiguousInputStream =
+            getClass()
+                .getClassLoader()
+                .getResourceAsStream("api_database/api_database_ambiguous.txt")) {
+      hashIndices = (int[]) indicesObjectStream.readObject();
+      apiLevels = (byte[]) apiObjectStream.readObject();
+      ambiguous =
+          new BufferedReader(new InputStreamReader(ambiguousInputStream, StandardCharsets.UTF_8))
+              .lines()
+              .collect(Collectors.toList());
+    } catch (IOException | ClassNotFoundException e) {
+      throw new RuntimeException("Could not build api database");
+    }
+    assert hashIndices.length == apiLevels.length;
+    for (int i = 0; i < hashIndices.length; i++) {
+      byte apiLevel = apiLevels[i];
+      lookupNonAmbiguousCache.put(
+          hashIndices[i], apiLevel == -1 ? NOT_SET : AndroidApiLevel.getAndroidApiLevel(apiLevel));
+    }
+    ambiguous.forEach(this::parseAmbiguous);
+  }
+
+  /**
+   * All elements in the ambiguous map are on the form <key>:<api-level>. The reason for this
+   * additional map is that the keys collide for the items using the ordinary hashing function.
+   */
+  private void parseAmbiguous(String ambiguous) {
+    String[] split = ambiguous.split(":");
+    if (split.length != 2) {
+      throw new CompilationError("Expected two entries in ambiguous map");
+    }
+    ambiguousHashesWithApiLevel.put(
+        split[0], AndroidApiLevel.getAndroidApiLevel(Integer.parseInt(split[1])));
+  }
+
+  @Override
+  public AndroidApiLevel getTypeApiLevel(DexType type) {
+    return lookupApiLevel(type);
+  }
+
+  @Override
+  public AndroidApiLevel getMethodApiLevel(DexMethod method) {
+    return lookupApiLevel(method);
+  }
+
+  @Override
+  public AndroidApiLevel getFieldApiLevel(DexField field) {
+    return lookupApiLevel(field);
+  }
+
+  private AndroidApiLevel lookupApiLevel(DexReference reference) {
+    AndroidApiLevel result = lookupNonAmbiguousCache.getOrDefault(reference.hashCode(), UNKNOWN);
+    if (result != NOT_SET) {
+      return result;
+    }
+    return ambiguousCache.computeIfAbsent(
+        reference,
+        ignored -> {
+          HasherWrapper defaultHasher = getDefaultHasher();
+          reference.accept(
+              type -> DefaultHashingVisitor.run(type, defaultHasher, DexType::acceptHashing),
+              field ->
+                  DefaultHashingVisitor.run(field, defaultHasher, StructuralItem::acceptHashing),
+              method ->
+                  DefaultHashingVisitor.run(method, defaultHasher, StructuralItem::acceptHashing));
+          String existingHash = defaultHasher.hash().toString();
+          AndroidApiLevel androidApiLevel = ambiguousHashesWithApiLevel.get(existingHash);
+          if (androidApiLevel == null) {
+            throw new CompilationError(
+                "Failed to find api level for reference: "
+                    + reference.toSourceString()
+                    + " with hash value: "
+                    + existingHash);
+          }
+          return androidApiLevel;
+        });
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseImpl.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelObjectDatabaseImpl.java
similarity index 61%
rename from src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseImpl.java
rename to src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelObjectDatabaseImpl.java
index d98013e..e48b221 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseImpl.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelObjectDatabaseImpl.java
@@ -16,11 +16,13 @@
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.function.BiFunction;
 
-public class AndroidApiLevelDatabaseImpl implements AndroidApiLevelDatabase {
+public class AndroidApiLevelObjectDatabaseImpl implements AndroidApiLevelDatabase {
 
-  private final HashMap<DexType, AndroidApiClass> predefinedApiTypeLookup;
+  private final Map<DexType, AndroidApiClass> predefinedApiTypeLookup;
 
   private final AndroidApiClass SENTINEL =
       new AndroidApiClass(null) {
@@ -52,8 +54,57 @@
         }
       };
 
-  public AndroidApiLevelDatabaseImpl(HashMap<DexType, AndroidApiClass> predefinedApiTypeLookup) {
-    this.predefinedApiTypeLookup = predefinedApiTypeLookup;
+  public AndroidApiLevelObjectDatabaseImpl(
+      List<AndroidApiForHashingClass> predefinedApiTypeLookupForHashing) {
+    Map<DexType, AndroidApiClass> predefinedMap = new HashMap<>();
+    for (AndroidApiForHashingClass androidApiClass : predefinedApiTypeLookupForHashing) {
+      predefinedMap.put(
+          androidApiClass.getType(),
+          new AndroidApiClass(androidApiClass.getType().asClassReference()) {
+            @Override
+            public AndroidApiLevel getApiLevel() {
+              return androidApiClass.getApiLevel();
+            }
+
+            @Override
+            public int getMemberCount() {
+              return 0;
+            }
+
+            @Override
+            protected TraversalContinuation visitFields(
+                BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor,
+                ClassReference holder,
+                int minApiClass) {
+              Box<TraversalContinuation> continuationBox =
+                  new Box<>(TraversalContinuation.CONTINUE);
+              androidApiClass.visitFieldsWithApiLevels(
+                  (field, apiLevel) -> {
+                    if (continuationBox.get().shouldContinue()) {
+                      continuationBox.set(visitor.apply(field.asFieldReference(), apiLevel));
+                    }
+                  });
+              return continuationBox.get();
+            }
+
+            @Override
+            protected TraversalContinuation visitMethods(
+                BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor,
+                ClassReference holder,
+                int minApiClass) {
+              Box<TraversalContinuation> continuationBox =
+                  new Box<>(TraversalContinuation.CONTINUE);
+              androidApiClass.visitMethodsWithApiLevels(
+                  (method, apiLevel) -> {
+                    if (continuationBox.get().shouldContinue()) {
+                      continuationBox.set(visitor.apply(method.asMethodReference(), apiLevel));
+                    }
+                  });
+              return continuationBox.get();
+            }
+          });
+    }
+    this.predefinedApiTypeLookup = predefinedMap;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
index 8ca1b59..df4cefd 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
@@ -10,7 +10,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import java.util.HashMap;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
 
 public class AndroidApiReferenceLevelCache {
 
@@ -19,13 +20,21 @@
   private final AppView<?> appView;
 
   private AndroidApiReferenceLevelCache(AppView<?> appView) {
-    this(appView, new HashMap<>());
+    this(appView, ImmutableList.of());
   }
 
   private AndroidApiReferenceLevelCache(
-      AppView<?> appView, HashMap<DexType, AndroidApiClass> predefinedApiTypeLookup) {
+      AppView<?> appView, List<AndroidApiForHashingClass> predefinedApiTypeLookupForHashing) {
     this.appView = appView;
-    androidApiLevelDatabase = new AndroidApiLevelDatabaseImpl(predefinedApiTypeLookup);
+    // TODO(b/199934316): When the implementation has been decided on, remove the others.
+    if (appView.options().apiModelingOptions().useHashingDatabase) {
+      androidApiLevelDatabase =
+          new AndroidApiLevelHashingDatabaseImpl(
+              appView.dexItemFactory(), predefinedApiTypeLookupForHashing);
+    } else {
+      androidApiLevelDatabase =
+          new AndroidApiLevelObjectDatabaseImpl(predefinedApiTypeLookupForHashing);
+    }
     desugaredLibraryConfiguration = appView.options().desugaredLibraryConfiguration;
   }
 
@@ -40,18 +49,12 @@
         }
       };
     }
-    // The apiTypeLookup is build lazily except for the mocked api types that we define in tests
-    // externally.
-    HashMap<DexType, AndroidApiClass> predefinedApiTypeLookup = new HashMap<>();
+    ImmutableList.Builder<AndroidApiForHashingClass> builder = ImmutableList.builder();
     appView
         .options()
         .apiModelingOptions()
-        .visitMockedApiReferences(
-            (classReference, androidApiClass) ->
-                predefinedApiTypeLookup.put(
-                    appView.dexItemFactory().createType(classReference.getDescriptor()),
-                    androidApiClass));
-    return new AndroidApiReferenceLevelCache(appView, predefinedApiTypeLookup);
+        .visitMockedApiLevelsForReferences(appView.dexItemFactory(), builder::add);
+    return new AndroidApiReferenceLevelCache(appView, builder.build());
   }
 
   public AndroidApiLevel lookupMax(DexReference reference, AndroidApiLevel minApiLevel) {
@@ -61,10 +64,14 @@
   public AndroidApiLevel lookup(DexReference reference) {
     DexType contextType = reference.getContextType();
     if (contextType.isArrayType()) {
+      if (reference.isDexMethod()
+          && reference.asDexMethod().match(appView.dexItemFactory().objectMembers.clone)) {
+        return appView.options().minApiLevel;
+      }
       return lookup(contextType.toBaseType(appView.dexItemFactory()));
     }
     if (contextType.isPrimitiveType() || contextType.isVoidType()) {
-      return AndroidApiLevel.B;
+      return appView.options().minApiLevel;
     }
     DexClass clazz = appView.definitionFor(contextType);
     if (clazz == null) {
@@ -73,6 +80,9 @@
     if (!clazz.isLibraryClass()) {
       return appView.options().minApiLevel;
     }
+    if (isReferenceToJavaLangObject(reference)) {
+      return appView.options().minApiLevel;
+    }
     if (desugaredLibraryConfiguration.isSupported(reference, appView)) {
       // If we end up desugaring the reference, the library classes is bridged by j$ which is part
       // of the program.
@@ -83,4 +93,12 @@
         androidApiLevelDatabase::getFieldApiLevel,
         androidApiLevelDatabase::getMethodApiLevel);
   }
+
+  private boolean isReferenceToJavaLangObject(DexReference reference) {
+    if (reference.getContextType() == appView.dexItemFactory().objectType) {
+      return true;
+    }
+    return reference.isDexMethod()
+        && appView.dexItemFactory().objectMembers.isObjectMember(reference.asDexMethod());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index cfad75e..2220c7d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -87,7 +87,7 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerCheckCast(type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index b7524be..78fbcd2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -118,7 +118,7 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerConstClass(type, iterator);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
index e6e1f0d..f86c213 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -200,7 +200,7 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerTypeReference(reference.getType());
     registry.registerMethodHandle(
         reference.getBootstrapMethod(), NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index 7391677..b495441 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -73,7 +73,7 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerMethodHandle(handle, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index e85db92..3578a70 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -71,7 +71,7 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerProto(type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index 924fa7e..1958b5c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -94,7 +94,7 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     if (nameComputationInfo.needsToRegisterReference()) {
       assert item.isDexType();
       registry.registerTypeReference(item.asDexType());
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index cd8d6b7..a2f4e80 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -111,7 +111,7 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     switch (opcode) {
       case Opcodes.GETFIELD:
         registry.registerInstanceFieldRead(field);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
index 8eb0db7..929a010 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -87,7 +87,7 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerInitClass(clazz);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index 8eab765..8b82246 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -96,7 +96,7 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerInstanceOf(type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 188c575..ab73479 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -92,7 +92,7 @@
   }
 
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     // Intentionally empty.
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 625430b..e9cd796 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -15,7 +14,6 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
@@ -34,7 +32,6 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import java.util.Arrays;
@@ -106,7 +103,8 @@
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
     MethodLookupResult lookup =
-        graphLens.lookupMethod(method, context.getReference(), getInvokeType(context));
+        graphLens.lookupMethod(
+            method, context.getReference(), getInvokeType(context, dexItemFactory));
     DexMethod rewrittenMethod = lookup.getReference();
     String owner = namingLens.lookupInternalName(rewrittenMethod.holder);
     String name = namingLens.lookupName(rewrittenMethod).toString();
@@ -121,8 +119,8 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
-    Type invokeType = getInvokeType(context);
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    Type invokeType = getInvokeType(context, registry.dexItemFactory());
     switch (invokeType) {
       case DIRECT:
         registry.registerInvokeDirect(method);
@@ -146,7 +144,7 @@
 
   // We should avoid interpreting a CF invoke using DEX semantics.
   @Deprecated
-  public Invoke.Type getInvokeType(DexClassAndMethod context) {
+  private Invoke.Type getInvokeType(DexClassAndMethod context, DexItemFactory dexItemFactory) {
     switch (opcode) {
       case Opcodes.INVOKEINTERFACE:
         return Type.INTERFACE;
@@ -155,8 +153,8 @@
         return Type.VIRTUAL;
 
       case Opcodes.INVOKESPECIAL:
-        if (method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME)
-            || method.holder == context.getHolderType()) {
+        if (method.isInstanceInitializer(dexItemFactory)
+            || method.getHolderType() == context.getHolderType()) {
           return Type.DIRECT;
         }
         return Type.SUPER;
@@ -247,7 +245,7 @@
           canonicalMethod = method;
           type =
               computeInvokeTypeForInvokeSpecial(
-                  builder.appView, method, code.getOriginalHolder(), code.getOrigin());
+                  builder.appView, method, builder.getProgramMethod(), code.getOriginalHolder());
           break;
         }
       case Opcodes.INVOKESTATIC:
@@ -296,8 +294,7 @@
       case Opcodes.INVOKESPECIAL:
         {
           Type actualInvokeType =
-              computeInvokeTypeForInvokeSpecial(
-                  appView, method, code.getOriginalHolder(), context.getOrigin());
+              computeInvokeTypeForInvokeSpecial(appView, method, context, code.getOriginalHolder());
           type = graphLens.lookupMethod(target, context.getReference(), actualInvokeType).getType();
         }
         break;
@@ -362,55 +359,38 @@
   }
 
   private Type computeInvokeTypeForInvokeSpecial(
-      AppView<?> appView, DexMethod method, DexType originalHolder, Origin origin) {
+      AppView<?> appView, DexMethod method, ProgramMethod context, DexType originalHolder) {
     if (appView.dexItemFactory().isConstructor(method)) {
       return Type.DIRECT;
     }
-    if (originalHolder == method.holder) {
-      return invokeTypeForInvokeSpecialToNonInitMethodOnHolder(appView, origin);
+    if (originalHolder != method.getHolderType()) {
+      return Type.SUPER;
     }
-    return Type.SUPER;
+    return invokeTypeForInvokeSpecialToNonInitMethodOnHolder(context, appView.graphLens());
   }
 
   private Type invokeTypeForInvokeSpecialToNonInitMethodOnHolder(
-      AppView<?> appView, Origin origin) {
-    boolean desugaringEnabled = appView.options().isInterfaceMethodDesugaringEnabled();
-    MethodLookupResult lookupResult = appView.graphLens().lookupMethod(method, method, Type.DIRECT);
-    if (lookupResult.getType() == Type.VIRTUAL) {
-      // The method has been publicized. We can't always expect private methods that have been
-      // publicized to be final. For example, if a private method A.m() is publicized, and A is
-      // subsequently merged with a class B, with declares a public non-final method B.m(), then the
-      // horizontal class merger will merge A.m() and B.m() into a new non-final public method.
-      return Type.VIRTUAL;
-    }
-    DexMethod rewrittenMethod = lookupResult.getReference();
-    DexEncodedMethod definition = lookupMethodOnHolder(appView, rewrittenMethod);
+      ProgramMethod context, GraphLens graphLens) {
+    MethodLookupResult lookupResult =
+        graphLens.lookupMethod(method, context.getReference(), Type.DIRECT);
+    DexEncodedMethod definition = context.getHolder().lookupMethod(lookupResult.getReference());
     if (definition == null) {
-      // The method is not defined on the class, we can use super to target. When desugaring
-      // default interface methods, it is expected they are targeted with invoke-direct.
-      return this.itf && desugaringEnabled ? Type.DIRECT : Type.SUPER;
+      return Type.SUPER;
     }
-    if (definition.isPrivateMethod() || !definition.isVirtualMethod()) {
-      return Type.DIRECT;
-    }
-    if (definition.isFinal()) {
-      // This method is final which indicates no subtype will overwrite it, we can use
-      // invoke-virtual.
-      return Type.VIRTUAL;
-    }
-    if (itf && definition.isDefaultMethod()) {
-      return desugaringEnabled ? Type.DIRECT : Type.SUPER;
-    }
-    // We cannot emulate the semantics of invoke-special in this case and should throw a compilation
-    // error.
-    throw new CompilationError("Failed to compile unsupported use of invokespecial", origin);
-  }
 
-  private DexEncodedMethod lookupMethodOnHolder(AppView<?> appView, DexMethod method) {
-    // Directly lookup the program type for holder. This bypasses lookup order as well as looks
-    // directly on the application data, which bypasses and indirection or validation.
-    DexProgramClass clazz = appView.appInfo().unsafeDirectProgramTypeLookup(method.getHolderType());
-    assert clazz != null;
-    return clazz.lookupMethod(method);
+    if (context.getHolder().isInterface()) {
+      // On interfaces invoke-special should be mapped to invoke-super if the invoke-special
+      // instruction is used to target a default interface method.
+      if (definition.belongsToVirtualPool()) {
+        return Type.SUPER;
+      }
+    } else {
+      // Due to desugaring of invoke-special instructions that target virtual methods, this invoke
+      // should only target a virtual method if the method has been publicized in R8 (in which case
+      // the invoke instruction has a pending rewrite to invoke-virtual).
+      assert definition.isPrivate() || lookupResult.getType().isVirtual();
+    }
+
+    return Type.DIRECT;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index 3138b27..644ed7d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -127,7 +127,7 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerCallSite(callSite);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index e83b18d..b798257 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -98,7 +98,7 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerTypeReference(type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index 0fe8c96..ff87575 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -87,7 +87,7 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerNewInstance(type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index 559f3ba..769fe22 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -133,7 +133,7 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     if (!type.isPrimitiveArrayType()) {
       registry.registerTypeReference(type);
     }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
index 89f8686..335cce5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
@@ -90,7 +90,7 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerNewUnboxedEnumInstance(type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSafeCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfSafeCheckCast.java
index e1473d3..9fe64c6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSafeCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSafeCheckCast.java
@@ -24,7 +24,7 @@
 
   @Override
   void internalRegisterUse(
-      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerSafeCheckCast(getType());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/CheckCast.java b/src/main/java/com/android/tools/r8/code/CheckCast.java
index 9f3739a..cb21b8c 100644
--- a/src/main/java/com/android/tools/r8/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/code/CheckCast.java
@@ -82,7 +82,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerCheckCast(getType());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/ConstClass.java b/src/main/java/com/android/tools/r8/code/ConstClass.java
index b96f4aa..f57556a 100644
--- a/src/main/java/com/android/tools/r8/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/code/ConstClass.java
@@ -72,7 +72,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerConstClass(getType(), null);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
index 2f05b0b..0df963f 100644
--- a/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
@@ -67,7 +67,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerMethodHandle(
         getMethodHandle(), MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
   }
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
index 7662fe4..478d3b5 100644
--- a/src/main/java/com/android/tools/r8/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
@@ -66,7 +66,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerProto(getMethodType());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/DexInitClass.java b/src/main/java/com/android/tools/r8/code/DexInitClass.java
index 8e85bd0..545c24c 100644
--- a/src/main/java/com/android/tools/r8/code/DexInitClass.java
+++ b/src/main/java/com/android/tools/r8/code/DexInitClass.java
@@ -107,7 +107,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInitClass(clazz);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
index 1a434fd..70e9c4f 100644
--- a/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
@@ -107,7 +107,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     if (nameComputationInfo.needsToRegisterReference()) {
       assert getItem().isDexType();
       registry.registerTypeReference(getItem().asDexType());
diff --git a/src/main/java/com/android/tools/r8/code/DexNewUnboxedEnumInstance.java b/src/main/java/com/android/tools/r8/code/DexNewUnboxedEnumInstance.java
index f9c26e6..9909ac6 100644
--- a/src/main/java/com/android/tools/r8/code/DexNewUnboxedEnumInstance.java
+++ b/src/main/java/com/android/tools/r8/code/DexNewUnboxedEnumInstance.java
@@ -69,7 +69,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerNewUnboxedEnumInstance(getType());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/Iget.java b/src/main/java/com/android/tools/r8/code/Iget.java
index 3625037..71c11c3 100644
--- a/src/main/java/com/android/tools/r8/code/Iget.java
+++ b/src/main/java/com/android/tools/r8/code/Iget.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInstanceFieldRead(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/IgetBoolean.java b/src/main/java/com/android/tools/r8/code/IgetBoolean.java
index 0df2731..36c00b6 100644
--- a/src/main/java/com/android/tools/r8/code/IgetBoolean.java
+++ b/src/main/java/com/android/tools/r8/code/IgetBoolean.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInstanceFieldRead(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/IgetByte.java b/src/main/java/com/android/tools/r8/code/IgetByte.java
index f3cb157..8d57bb5 100644
--- a/src/main/java/com/android/tools/r8/code/IgetByte.java
+++ b/src/main/java/com/android/tools/r8/code/IgetByte.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInstanceFieldRead(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/IgetChar.java b/src/main/java/com/android/tools/r8/code/IgetChar.java
index 2c0793c..2ba0e26 100644
--- a/src/main/java/com/android/tools/r8/code/IgetChar.java
+++ b/src/main/java/com/android/tools/r8/code/IgetChar.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInstanceFieldRead(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/IgetObject.java b/src/main/java/com/android/tools/r8/code/IgetObject.java
index 802409e..c685229 100644
--- a/src/main/java/com/android/tools/r8/code/IgetObject.java
+++ b/src/main/java/com/android/tools/r8/code/IgetObject.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInstanceFieldRead(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/IgetShort.java b/src/main/java/com/android/tools/r8/code/IgetShort.java
index a8ca9a7..77667ba 100644
--- a/src/main/java/com/android/tools/r8/code/IgetShort.java
+++ b/src/main/java/com/android/tools/r8/code/IgetShort.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInstanceFieldRead(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/IgetWide.java b/src/main/java/com/android/tools/r8/code/IgetWide.java
index bd63727..a71b95a 100644
--- a/src/main/java/com/android/tools/r8/code/IgetWide.java
+++ b/src/main/java/com/android/tools/r8/code/IgetWide.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInstanceFieldRead(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InstanceOf.java b/src/main/java/com/android/tools/r8/code/InstanceOf.java
index 6c9d960..c1b8293 100644
--- a/src/main/java/com/android/tools/r8/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/code/InstanceOf.java
@@ -68,7 +68,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInstanceOf(getType());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index ccfd713..96e754e 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -385,7 +385,7 @@
     return this.equals(other);
   }
 
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     // Intentionally empty
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/code/InvokeCustom.java
index 1355f13..ea07822 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeCustom.java
@@ -54,7 +54,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerCallSite(getCallSite());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java b/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java
index 2d09e4f..c057c94 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java
@@ -59,7 +59,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerCallSite(getCallSite());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/code/InvokeDirect.java
index 6786de8..4f5938f 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeDirect.java
@@ -44,7 +44,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInvokeDirect(getMethod());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java b/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
index 843d968..6f232c0 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
@@ -44,7 +44,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInvokeDirect(getMethod());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/code/InvokeInterface.java
index 89b8a16..2e20c9c 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeInterface.java
@@ -44,7 +44,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInvokeInterface(getMethod());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java b/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
index e710b8c..7592e3d 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
@@ -44,7 +44,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInvokeInterface(getMethod());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java b/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
index 45eb6e8..07cf3a0 100644
--- a/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
@@ -47,7 +47,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInvokeDirect(getMethod());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/code/InvokeStatic.java
index 796b183..fb198e4 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeStatic.java
@@ -44,7 +44,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInvokeStatic(getMethod());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java b/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
index 20809c9..de84a90 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
@@ -44,7 +44,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInvokeStatic(getMethod());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/code/InvokeSuper.java
index d2d6e28..24ab80e 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeSuper.java
@@ -44,7 +44,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInvokeSuper(getMethod());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java b/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
index 70fd726..ac820c9 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
@@ -44,7 +44,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInvokeSuper(getMethod());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
index 9b9e064..27e37c5 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
@@ -54,7 +54,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInvokeVirtual(getMethod());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
index a4f2e98..e93733f 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
@@ -54,7 +54,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInvokeVirtual(getMethod());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/Iput.java b/src/main/java/com/android/tools/r8/code/Iput.java
index ea170c7..76f90af 100644
--- a/src/main/java/com/android/tools/r8/code/Iput.java
+++ b/src/main/java/com/android/tools/r8/code/Iput.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInstanceFieldWrite(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/IputBoolean.java b/src/main/java/com/android/tools/r8/code/IputBoolean.java
index 8a992c7..634657d 100644
--- a/src/main/java/com/android/tools/r8/code/IputBoolean.java
+++ b/src/main/java/com/android/tools/r8/code/IputBoolean.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInstanceFieldWrite(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/IputByte.java b/src/main/java/com/android/tools/r8/code/IputByte.java
index 4005875..b579bec 100644
--- a/src/main/java/com/android/tools/r8/code/IputByte.java
+++ b/src/main/java/com/android/tools/r8/code/IputByte.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInstanceFieldWrite(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/IputChar.java b/src/main/java/com/android/tools/r8/code/IputChar.java
index 55f72fe..1b0d42d 100644
--- a/src/main/java/com/android/tools/r8/code/IputChar.java
+++ b/src/main/java/com/android/tools/r8/code/IputChar.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInstanceFieldWrite(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/IputObject.java b/src/main/java/com/android/tools/r8/code/IputObject.java
index 5ca2f90..816f81f 100644
--- a/src/main/java/com/android/tools/r8/code/IputObject.java
+++ b/src/main/java/com/android/tools/r8/code/IputObject.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInstanceFieldWrite(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/IputShort.java b/src/main/java/com/android/tools/r8/code/IputShort.java
index bf41801..dfa9ff7 100644
--- a/src/main/java/com/android/tools/r8/code/IputShort.java
+++ b/src/main/java/com/android/tools/r8/code/IputShort.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInstanceFieldWrite(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/IputWide.java b/src/main/java/com/android/tools/r8/code/IputWide.java
index 4462875..261dc26 100644
--- a/src/main/java/com/android/tools/r8/code/IputWide.java
+++ b/src/main/java/com/android/tools/r8/code/IputWide.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerInstanceFieldWrite(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/NewArray.java b/src/main/java/com/android/tools/r8/code/NewArray.java
index a0104a2..35b3187 100644
--- a/src/main/java/com/android/tools/r8/code/NewArray.java
+++ b/src/main/java/com/android/tools/r8/code/NewArray.java
@@ -54,7 +54,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerTypeReference(getType());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/NewInstance.java b/src/main/java/com/android/tools/r8/code/NewInstance.java
index 44d2613..1aed86e 100644
--- a/src/main/java/com/android/tools/r8/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/code/NewInstance.java
@@ -72,7 +72,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerNewInstance(getType());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/SafeCheckCast.java b/src/main/java/com/android/tools/r8/code/SafeCheckCast.java
index e4af2a0..7fff773 100644
--- a/src/main/java/com/android/tools/r8/code/SafeCheckCast.java
+++ b/src/main/java/com/android/tools/r8/code/SafeCheckCast.java
@@ -25,7 +25,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerSafeCheckCast(getType());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Sget.java b/src/main/java/com/android/tools/r8/code/Sget.java
index 0c727ac..c2d93df 100644
--- a/src/main/java/com/android/tools/r8/code/Sget.java
+++ b/src/main/java/com/android/tools/r8/code/Sget.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerStaticFieldRead(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/SgetBoolean.java b/src/main/java/com/android/tools/r8/code/SgetBoolean.java
index 3efb6b1..3d79934 100644
--- a/src/main/java/com/android/tools/r8/code/SgetBoolean.java
+++ b/src/main/java/com/android/tools/r8/code/SgetBoolean.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerStaticFieldRead(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/SgetByte.java b/src/main/java/com/android/tools/r8/code/SgetByte.java
index 9a40297..08f2b97 100644
--- a/src/main/java/com/android/tools/r8/code/SgetByte.java
+++ b/src/main/java/com/android/tools/r8/code/SgetByte.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerStaticFieldRead(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/SgetChar.java b/src/main/java/com/android/tools/r8/code/SgetChar.java
index 429eaa6..5ce7f8c 100644
--- a/src/main/java/com/android/tools/r8/code/SgetChar.java
+++ b/src/main/java/com/android/tools/r8/code/SgetChar.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerStaticFieldRead(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/SgetObject.java b/src/main/java/com/android/tools/r8/code/SgetObject.java
index 6b1130d..e4ae240 100644
--- a/src/main/java/com/android/tools/r8/code/SgetObject.java
+++ b/src/main/java/com/android/tools/r8/code/SgetObject.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerStaticFieldRead(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/SgetShort.java b/src/main/java/com/android/tools/r8/code/SgetShort.java
index 37f331e..62e2de7 100644
--- a/src/main/java/com/android/tools/r8/code/SgetShort.java
+++ b/src/main/java/com/android/tools/r8/code/SgetShort.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerStaticFieldRead(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/SgetWide.java b/src/main/java/com/android/tools/r8/code/SgetWide.java
index 638ca0c..962d0c1 100644
--- a/src/main/java/com/android/tools/r8/code/SgetWide.java
+++ b/src/main/java/com/android/tools/r8/code/SgetWide.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerStaticFieldRead(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/Sput.java b/src/main/java/com/android/tools/r8/code/Sput.java
index e6f3b4e..e9e7f56 100644
--- a/src/main/java/com/android/tools/r8/code/Sput.java
+++ b/src/main/java/com/android/tools/r8/code/Sput.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerStaticFieldWrite(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/SputBoolean.java b/src/main/java/com/android/tools/r8/code/SputBoolean.java
index 6dae9b2..d3ba949 100644
--- a/src/main/java/com/android/tools/r8/code/SputBoolean.java
+++ b/src/main/java/com/android/tools/r8/code/SputBoolean.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerStaticFieldWrite(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/SputByte.java b/src/main/java/com/android/tools/r8/code/SputByte.java
index c6cb521..e1c09f5 100644
--- a/src/main/java/com/android/tools/r8/code/SputByte.java
+++ b/src/main/java/com/android/tools/r8/code/SputByte.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerStaticFieldWrite(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/SputChar.java b/src/main/java/com/android/tools/r8/code/SputChar.java
index 727ba25..2497f76 100644
--- a/src/main/java/com/android/tools/r8/code/SputChar.java
+++ b/src/main/java/com/android/tools/r8/code/SputChar.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerStaticFieldWrite(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/SputObject.java b/src/main/java/com/android/tools/r8/code/SputObject.java
index 4850664..43a92b6 100644
--- a/src/main/java/com/android/tools/r8/code/SputObject.java
+++ b/src/main/java/com/android/tools/r8/code/SputObject.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerStaticFieldWrite(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/SputShort.java b/src/main/java/com/android/tools/r8/code/SputShort.java
index eb33e33..2335606 100644
--- a/src/main/java/com/android/tools/r8/code/SputShort.java
+++ b/src/main/java/com/android/tools/r8/code/SputShort.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerStaticFieldWrite(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/SputWide.java b/src/main/java/com/android/tools/r8/code/SputWide.java
index 8104f75..278418a 100644
--- a/src/main/java/com/android/tools/r8/code/SputWide.java
+++ b/src/main/java/com/android/tools/r8/code/SputWide.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void registerUse(UseRegistry registry) {
+  public void registerUse(UseRegistry<?> registry) {
     registry.registerStaticFieldWrite(getField());
   }
 
diff --git a/src/main/java/com/android/tools/r8/contexts/CompilationContext.java b/src/main/java/com/android/tools/r8/contexts/CompilationContext.java
index f037883..d3db39a 100644
--- a/src/main/java/com/android/tools/r8/contexts/CompilationContext.java
+++ b/src/main/java/com/android/tools/r8/contexts/CompilationContext.java
@@ -6,8 +6,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.hash.Hasher;
-import com.google.common.hash.Hashing;
+import com.android.tools.r8.utils.structural.HasherWrapper;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
@@ -143,9 +142,9 @@
 
     private StringBuilder buildSuffix(StringBuilder builder) {
       // TODO(b/172194101): Sanitize the method descriptor instead of hashing.
-      Hasher hasher = Hashing.sha256().newHasher();
+      HasherWrapper hasher = HasherWrapper.sha256Hasher();
       method.getReference().hash(hasher);
-      return builder.append('$').append(hasher.hash().toString());
+      return builder.append('$').append(hasher.hashCodeAsString());
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index 7595a40..0e1edc9 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -8,12 +8,15 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
+import java.util.Comparator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -155,7 +158,14 @@
       // TODO(b/134734081): Stream the consumer instead of building the String.
       StringBuilder sb = new StringBuilder();
       String cr = System.lineSeparator();
-      for (DexType type : toKeep.keySet()) {
+      Comparator<DexReference> comparator =
+          new Comparator<DexReference>() {
+            @Override
+            public int compare(DexReference o1, DexReference o2) {
+              return o1.compareTo(o2);
+            }
+          };
+      for (DexType type : CollectionUtils.sort(toKeep.keySet(), getComparator())) {
         KeepStruct keepStruct = toKeep.get(type);
         sb.append("-keep class ").append(convertType(type));
         if (keepStruct.all) {
@@ -167,7 +177,7 @@
           continue;
         }
         sb.append(" {").append(cr);
-        for (DexField field : keepStruct.fields) {
+        for (DexField field : CollectionUtils.sort(keepStruct.fields, getComparator())) {
           sb.append("    ")
               .append(convertType(field.type))
               .append(" ")
@@ -175,7 +185,7 @@
               .append(";")
               .append(cr);
         }
-        for (DexMethod method : keepStruct.methods) {
+        for (DexMethod method : CollectionUtils.sort(keepStruct.methods, getComparator())) {
           sb.append("    ")
               .append(convertType(method.proto.returnType))
               .append(" ")
@@ -194,6 +204,15 @@
       options.desugaredLibraryKeepRuleConsumer.accept(sb.toString(), options.reporter);
       options.desugaredLibraryKeepRuleConsumer.finished(options.reporter);
     }
+
+    private static <T extends DexReference> Comparator<T> getComparator() {
+      return new Comparator<T>() {
+        @Override
+        public int compare(T o1, T o2) {
+          return o1.compareTo(o2);
+        }
+      };
+    }
   }
 
   public static class NopCodeToKeep extends CodeToKeep {
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 03e0e31..0601b5c 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -581,7 +581,9 @@
         case Constants.DBG_SET_FILE: {
           int nameIdx = dexReader.getUleb128p1();
           DexString sourceFile = nameIdx == NO_INDEX ? null : indexedItems.getString(nameIdx);
-          events.add(dexItemFactory.createSetFile(sourceFile));
+          if (options.readDebugSetFileEvent) {
+            events.add(dexItemFactory.createSetFile(sourceFile));
+          }
           break;
         }
         default: {
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 1bbd88a..dfd4f1c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.dex.DebugBytecodeWriter;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
@@ -457,6 +458,13 @@
     }
   }
 
+  /**
+   * Unused/unsupported set-file event.
+   *
+   * <p>The set-file event is unused by all DEX VMs and incorrect on some older VMs. It is
+   * represented in the type of events for completeness, but should never be emitted as part of
+   * writing DEX code.
+   */
   public static class SetFile extends DexDebugEvent {
 
     DexString fileName;
@@ -468,8 +476,7 @@
     @Override
     public void writeOn(
         DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
-      writer.putByte(Constants.DBG_SET_FILE);
-      writer.putString(fileName);
+      throw new InternalCompilerError("Unused/unsupported SetFile event should never be written");
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index fd2c110..1f86536 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
+import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import java.util.Collections;
@@ -49,6 +50,13 @@
   }
 
   @Override
+  public void acceptHashing(HashingVisitor visitor) {
+    visitor.visitDexType(holder);
+    visitor.visitDexString(name);
+    getReferencedTypes().forEach(visitor::visitDexType);
+  }
+
+  @Override
   public DexField self() {
     return this;
   }
@@ -192,6 +200,7 @@
     return type.toSourceString() + " " + holder.toSourceString() + "." + name.toSourceString();
   }
 
+  @Override
   public DexField withHolder(DexType holder, DexItemFactory dexItemFactory) {
     return dexItemFactory.createField(holder, type, name);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index d31d8b7..24c2a3a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1370,6 +1370,11 @@
     public final DexMethod constructor;
     public final DexMethod finalize;
     public final DexMethod toString;
+    public final DexMethod notify;
+    public final DexMethod notifyAll;
+    public final DexMethod wait;
+    public final DexMethod waitLong;
+    public final DexMethod waitLongInt;
 
     private ObjectMembers() {
       // The clone method is installed on each array, so one has to use method.match(clone).
@@ -1382,6 +1387,36 @@
           finalizeMethodName, voidType.descriptor, DexString.EMPTY_ARRAY);
       toString = createMethod(objectDescriptor,
           toStringMethodName, stringDescriptor, DexString.EMPTY_ARRAY);
+      notify =
+          createMethod(objectDescriptor, notifyMethodName, voidDescriptor, DexString.EMPTY_ARRAY);
+      notifyAll =
+          createMethod(
+              objectDescriptor, notifyAllMethodName, voidDescriptor, DexString.EMPTY_ARRAY);
+      wait = createMethod(objectDescriptor, waitMethodName, voidDescriptor, DexString.EMPTY_ARRAY);
+      waitLong =
+          createMethod(
+              objectDescriptor, waitMethodName, voidDescriptor, new DexString[] {longDescriptor});
+      waitLongInt =
+          createMethod(
+              objectDescriptor,
+              waitMethodName,
+              voidDescriptor,
+              new DexString[] {longDescriptor, intDescriptor});
+    }
+
+    public boolean isObjectMember(DexMethod method) {
+      return method.match(clone)
+          || method.match(getClass)
+          || method.match(constructor)
+          || method.match(finalize)
+          || method.match(toString)
+          || method.match(hashCode)
+          || method.match(equals)
+          || method.match(notify)
+          || method.match(notifyAll)
+          || method.match(wait)
+          || method.match(waitLong)
+          || method.match(waitLongInt);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
index 9b62855..1f5566c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -60,4 +60,6 @@
   public Iterable<DexType> getReferencedBaseTypes(DexItemFactory dexItemFactory) {
     return Iterables.transform(getReferencedTypes(), type -> type.toBaseType(dexItemFactory));
   }
+
+  public abstract DexMember<D, R> withHolder(DexType holder, DexItemFactory dexItemFactory);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index d7f282f..4f23975 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
+import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import java.util.ArrayList;
@@ -60,6 +61,13 @@
     return visitor.visitDexMethod(this, other);
   }
 
+  @Override
+  public void acceptHashing(HashingVisitor visitor) {
+    visitor.visitDexType(holder);
+    visitor.visitDexString(name);
+    getReferencedTypes().forEach(visitor::visitDexType);
+  }
+
   public DexType getArgumentType(int argumentIndex, boolean isStatic) {
     if (isStatic) {
       return getParameter(argumentIndex);
@@ -207,9 +215,7 @@
 
   @Override
   public int computeHashCode() {
-    return holder.hashCode()
-        + proto.hashCode() * 7
-        + name.hashCode() * 31;
+    return holder.hashCode() * 7 + proto.hashCode() * 29 + name.hashCode() * 31;
   }
 
   @Override
@@ -225,7 +231,7 @@
 
   @Override
   public boolean match(DexMethod method) {
-    return match(method.getProto(), method.getName());
+    return method == this || match(method.getProto(), method.getName());
   }
 
   public boolean match(DexMethodSignature method) {
@@ -300,10 +306,11 @@
   }
 
   public DexMethod withHolder(DexDefinition definition, DexItemFactory dexItemFactory) {
-    return withHolder(definition.getReference(), dexItemFactory);
+    return withHolder(definition.getContextType(), dexItemFactory);
   }
 
-  public DexMethod withHolder(DexReference reference, DexItemFactory dexItemFactory) {
+  @Override
+  public DexMethod withHolder(DexType reference, DexItemFactory dexItemFactory) {
     return dexItemFactory.createMethod(reference.getContextType(), proto, name);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java
index dc30560..990eccc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProto.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -55,7 +55,7 @@
 
   @Override
   public int computeHashCode() {
-    return shorty.hashCode() + returnType.hashCode() * 7 + parameters.hashCode() * 31;
+    return shorty.hashCode() * 7 + returnType.hashCode() * 13 + parameters.hashCode() * 31;
   }
 
   public DexType getReturnType() {
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 4d1790e..7e1cd24 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -52,7 +52,7 @@
     definition.parameterAnnotationsList.collectIndexedItems(indexedItems);
   }
 
-  public void registerCodeReferences(UseRegistry registry) {
+  public void registerCodeReferences(UseRegistry<?> registry) {
     Code code = getDefinition().getCode();
     if (code != null) {
       if (Log.ENABLED) {
@@ -62,7 +62,7 @@
     }
   }
 
-  public <T> T registerCodeReferencesWithResult(UseRegistryWithResult<T> registry) {
+  public <R> R registerCodeReferencesWithResult(UseRegistryWithResult<R, ?> registry) {
     registerCodeReferences(registry);
     return registry.getResult();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 3913383..396fa40 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -7,9 +7,11 @@
 import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.ListIterator;
 
-public abstract class UseRegistry {
+public abstract class UseRegistry<T extends Definition> {
 
-  private DexItemFactory factory;
+  private final T context;
+  private final DexItemFactory factory;
+
   private TraversalContinuation continuation = TraversalContinuation.CONTINUE;
 
   public enum MethodHandleUse {
@@ -17,7 +19,8 @@
     NOT_ARGUMENT_TO_LAMBDA_METAFACTORY
   }
 
-  public UseRegistry(DexItemFactory factory) {
+  public UseRegistry(T context, DexItemFactory factory) {
+    this.context = context;
     this.factory = factory;
   }
 
@@ -25,11 +28,19 @@
     method.registerCodeReferences(this);
   }
 
+  public DexItemFactory dexItemFactory() {
+    return factory;
+  }
+
   public void doBreak() {
     assert continuation.shouldContinue();
     continuation = TraversalContinuation.BREAK;
   }
 
+  public final T getContext() {
+    return context;
+  }
+
   public TraversalContinuation getTraversalContinuation() {
     return continuation;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistryWithResult.java b/src/main/java/com/android/tools/r8/graph/UseRegistryWithResult.java
index 916a2f9..ee31347 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistryWithResult.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistryWithResult.java
@@ -4,24 +4,24 @@
 
 package com.android.tools.r8.graph;
 
-public abstract class UseRegistryWithResult<T> extends UseRegistry {
+public abstract class UseRegistryWithResult<R, T extends Definition> extends UseRegistry<T> {
 
-  private T result;
+  private R result;
 
-  public UseRegistryWithResult(DexItemFactory factory) {
-    super(factory);
+  public UseRegistryWithResult(T context, DexItemFactory factory) {
+    super(context, factory);
   }
 
-  public UseRegistryWithResult(DexItemFactory factory, T defaultResult) {
-    super(factory);
+  public UseRegistryWithResult(T context, DexItemFactory factory, R defaultResult) {
+    super(context, factory);
     this.result = defaultResult;
   }
 
-  public T getResult() {
+  public R getResult() {
     return result;
   }
 
-  public void setResult(T result) {
+  public void setResult(R result) {
     this.result = result;
     doBreak();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java
index 036a381..3a9f744 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.horizontalclassmerging.code;
 
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.UseRegistry;
@@ -37,7 +38,7 @@
   }
 
   @Override
-  public Consumer<UseRegistry> getRegistryCallback() {
+  public Consumer<UseRegistry> getRegistryCallback(DexClassAndMethod method) {
     return this::registerReachableDefinitions;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/VirtualMethodEntryPointSynthesizedCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/VirtualMethodEntryPointSynthesizedCode.java
index c258558..7cfd3dc 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/VirtualMethodEntryPointSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/VirtualMethodEntryPointSynthesizedCode.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.horizontalclassmerging.code;
 
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -15,7 +16,10 @@
 import java.util.function.Consumer;
 
 public class VirtualMethodEntryPointSynthesizedCode extends SynthesizedCode {
+
+  private final DexItemFactory dexItemFactory;
   private final Int2ReferenceSortedMap<DexMethod> mappedMethods;
+  private final DexMethod superMethod;
 
   public VirtualMethodEntryPointSynthesizedCode(
       Int2ReferenceSortedMap<DexMethod> mappedMethods,
@@ -33,7 +37,9 @@
                 method,
                 position,
                 originalMethod));
+    this.dexItemFactory = factory;
     this.mappedMethods = mappedMethods;
+    this.superMethod = superMethod;
   }
 
   private static DexMethod computeSuperMethodTarget(
@@ -47,11 +53,11 @@
   }
 
   @Override
-  public Consumer<UseRegistry> getRegistryCallback() {
-    return this::registerReachableDefinitions;
+  public Consumer<UseRegistry> getRegistryCallback(DexClassAndMethod method) {
+    return registry -> registerReachableDefinitions(method, registry);
   }
 
-  private void registerReachableDefinitions(UseRegistry registry) {
+  private void registerReachableDefinitions(DexClassAndMethod method, UseRegistry registry) {
     assert registry.getTraversalContinuation().shouldContinue();
     for (DexMethod mappedMethod : mappedMethods.values()) {
       registry.registerInvokeDirect(mappedMethod);
@@ -59,6 +65,11 @@
         return;
       }
     }
+    DexMethod superMethodTarget =
+        computeSuperMethodTarget(superMethod, method.asProgramMethod(), dexItemFactory);
+    if (superMethodTarget != null) {
+      registry.registerInvokeSuper(superMethodTarget);
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
index 6e9e19f..9336e2b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
@@ -65,13 +65,13 @@
     // Check that all accesses from [clazz] to classes or members from the current package of
     // [clazz] will continue to work. This is guaranteed if the methods of [clazz] do not access
     // any private or protected classes or members from the current package of [clazz].
-    IllegalAccessDetector registry = new IllegalAccessDetector(appView, clazz);
     TraversalContinuation result =
         clazz.traverseProgramMethods(
             method -> {
-              registry.setContext(method);
-              method.registerCodeReferences(registry);
-              if (registry.foundIllegalAccess()) {
+              boolean foundIllegalAccess =
+                  method.registerCodeReferencesWithResult(
+                      new IllegalAccessDetector(appView, method));
+              if (foundIllegalAccess) {
                 return TraversalContinuation.BREAK;
               }
               return TraversalContinuation.CONTINUE;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index e98e0d2..0aa7237 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -287,13 +287,10 @@
     return true;
   }
 
-  class TrivialFieldAccessUseRegistry extends UseRegistry {
-
-    private final ProgramMethod method;
+  class TrivialFieldAccessUseRegistry extends UseRegistry<ProgramMethod> {
 
     TrivialFieldAccessUseRegistry(ProgramMethod method) {
-      super(appView.dexItemFactory());
-      this.method = method;
+      super(method, appView.dexItemFactory());
     }
 
     private void registerFieldAccess(DexField reference, boolean isStatic, boolean isWrite) {
@@ -307,8 +304,8 @@
       DexEncodedField definition = field.getDefinition();
 
       if (definition.isStatic() != isStatic
-          || appView.isCfByteCodePassThrough(method.getDefinition())
-          || resolutionResult.isAccessibleFrom(method, appView).isPossiblyFalse()) {
+          || appView.isCfByteCodePassThrough(getContext().getDefinition())
+          || resolutionResult.isAccessibleFrom(getContext(), appView).isPossiblyFalse()) {
         recordAccessThatCannotBeOptimized(field, definition);
         return;
       }
@@ -328,7 +325,7 @@
 
       if (constantFields.contains(definition)
           || (!isWrite && nonConstantFields.contains(definition))) {
-        methodsToReprocess.add(method);
+        methodsToReprocess.add(getContext());
       }
     }
 
@@ -352,7 +349,7 @@
           AbstractAccessContexts accessContexts =
               fieldAccesses.computeIfAbsent(field, ignore -> new ConcreteAccessContexts());
           assert accessContexts.isConcrete();
-          accessContexts.asConcrete().recordAccess(field.getReference(), method);
+          accessContexts.asConcrete().recordAccess(field.getReference(), getContext());
         } else if (!otherAccessContexts.isTop()) {
           // Now both read and written.
           fieldAccesses.put(field, AbstractAccessContexts.unknown());
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
index e8770c7..8c71936 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
 
@@ -34,7 +35,10 @@
 
   @Override
   public Reason computeInliningReason(
-      InvokeMethod invoke, ProgramMethod target, ProgramMethod context) {
+      InvokeMethod invoke,
+      ProgramMethod target,
+      ProgramMethod context,
+      MethodProcessor methodProcessor) {
     if (references.isAbstractGeneratedMessageLiteBuilder(context.getHolder())
         && invoke.isInvokeSuper()) {
       // Aggressively inline invoke-super calls inside the GeneratedMessageLite builders. Such
@@ -44,7 +48,7 @@
     }
     return references.isDynamicMethod(target) || references.isDynamicMethodBridge(target)
         ? computeInliningReasonForDynamicMethod(invoke, target, context)
-        : parent.computeInliningReason(invoke, target, context);
+        : parent.computeInliningReason(invoke, target, context, methodProcessor);
   }
 
   private Reason computeInliningReasonForDynamicMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 2be47ca..f6f7d45 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -106,13 +106,13 @@
 
   abstract boolean verifyAllMethodsWithCodeExists();
 
-  class InvokeExtractor extends UseRegistry {
+  class InvokeExtractor extends UseRegistry<ProgramMethod> {
 
     private final Node currentMethod;
     private final Predicate<ProgramMethod> targetTester;
 
     InvokeExtractor(Node currentMethod, Predicate<ProgramMethod> targetTester) {
-      super(appView.dexItemFactory());
+      super(currentMethod.getProgramMethod(), appView.dexItemFactory());
       this.currentMethod = currentMethod;
       this.targetTester = targetTester;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
index 993315b..162e0bb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
@@ -18,13 +18,13 @@
    *
    * <p>For pinned methods (methods kept through Proguard keep rules) this will always answer <code>
    * false</code>.
-   *
-   * @param method
    */
   public abstract boolean hasSingleCallSite(ProgramMethod method);
 
   public abstract boolean hasDoubleCallSite(ProgramMethod method);
 
+  public abstract void unsetCallSiteInformation(ProgramMethod method);
+
   public static CallSiteInformation empty() {
     return EmptyCallSiteInformation.EMPTY_INFO;
   }
@@ -42,6 +42,11 @@
     public boolean hasDoubleCallSite(ProgramMethod method) {
       return false;
     }
+
+    @Override
+    public void unsetCallSiteInformation(ProgramMethod method) {
+      // Intentionally empty.
+    }
   }
 
   static class CallGraphBasedCallSiteInformation extends CallSiteInformation {
@@ -95,5 +100,11 @@
     public boolean hasDoubleCallSite(ProgramMethod method) {
       return doubleCallSite.contains(method.getReference());
     }
+
+    @Override
+    public void unsetCallSiteInformation(ProgramMethod method) {
+      singleCallSite.remove(method.getReference());
+      doubleCallSite.remove(method.getReference());
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 6359283..1d1a698 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -677,8 +677,7 @@
     {
       timing.begin("Build primary method processor");
       PrimaryMethodProcessor primaryMethodProcessor =
-          PrimaryMethodProcessor.create(
-              appView.withLiveness(), postMethodProcessorBuilder, executorService, timing);
+          PrimaryMethodProcessor.create(appView.withLiveness(), executorService, timing);
       timing.end();
       timing.begin("IR conversion phase 1");
       assert appView.graphLens() == graphLensForPrimaryOptimizationPass;
@@ -1977,4 +1976,16 @@
     }
     return previous;
   }
+
+  /**
+   * Called when a method is pruned as a result of optimizations during IR processing in R8, to
+   * allow optimizations that track sets of methods to fixup their state.
+   */
+  public void pruneMethod(ProgramMethod method) {
+    assert appView.enableWholeProgramOptimizations();
+    assert method.getHolder().lookupMethod(method.getReference()) == null;
+    if (inliner != null) {
+      inliner.pruneMethod(method);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LibraryDesugaredChecker.java b/src/main/java/com/android/tools/r8/ir/conversion/LibraryDesugaredChecker.java
index 7707d9e..4533478 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LibraryDesugaredChecker.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LibraryDesugaredChecker.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 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;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
@@ -16,7 +17,8 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.graph.UseRegistryWithResult;
+import com.google.common.collect.Iterables;
 
 public class LibraryDesugaredChecker {
   private final AppView<?> appView;
@@ -34,7 +36,7 @@
     return tracer.isLibraryDesugared();
   }
 
-  private static class IsLibraryDesugaredTracer extends UseRegistry {
+  private static class IsLibraryDesugaredTracer {
 
     private final DexString jDollarDescriptorPrefix;
     private final AppView<?> appView;
@@ -43,7 +45,6 @@
 
     public IsLibraryDesugaredTracer(
         AppView<?> appView, DexString jDollarDescriptorPrefix, DexProgramClass clazz) {
-      super(appView.dexItemFactory());
       this.jDollarDescriptorPrefix = jDollarDescriptorPrefix;
       this.appView = appView;
       this.clazz = clazz;
@@ -59,10 +60,10 @@
 
     private void registerClass(DexProgramClass clazz) {
       if (clazz.superType != null) {
-        registerTypeReference(clazz.superType);
+        registerType(clazz.superType);
       }
       for (DexType implementsType : clazz.interfaces.values) {
-        registerTypeReference(implementsType);
+        registerType(implementsType);
       }
       if (isLibraryDesugared) {
         return;
@@ -87,10 +88,10 @@
     }
 
     private void registerMethod(DexMethod method) {
-      for (DexType type : method.getParameters().values) {
-        registerTypeReference(type);
+      for (DexType type : method.getParameters()) {
+        registerType(type);
       }
-      registerTypeReference(method.getReturnType());
+      registerType(method.getReturnType());
     }
 
     private void registerField(DexEncodedField field) {
@@ -107,77 +108,107 @@
           }
         }
       }
-      method.registerCodeReferences(this);
+
+      if (!isLibraryDesugared) {
+        isLibraryDesugared =
+            method.registerCodeReferencesWithResult(
+                new IsLibraryDesugaredUseRegistry(method, appView.dexItemFactory()));
+      }
     }
 
-    @Override
-    public void registerInitClass(DexType type) {
-      registerType(type);
-    }
+    private class IsLibraryDesugaredUseRegistry
+        extends UseRegistryWithResult<Boolean, ProgramMethod> {
 
-    @Override
-    public void registerInvokeVirtual(DexMethod method) {
-      registerMethod(method);
-    }
+      public IsLibraryDesugaredUseRegistry(ProgramMethod context, DexItemFactory factory) {
+        super(context, factory, false);
+      }
 
-    @Override
-    public void registerInvokeDirect(DexMethod method) {
-      registerMethod(method);
-    }
+      private boolean registerField(DexField field) {
+        return registerType(field.getHolderType()) || registerType(field.getType());
+      }
 
-    @Override
-    public void registerInvokeStatic(DexMethod method) {
-      registerMethod(method);
-    }
+      private boolean registerMethod(DexMethod method) {
+        return registerType(method.getReturnType())
+            || Iterables.any(method.getParameters(), this::registerType);
+      }
 
-    @Override
-    public void registerInvokeInterface(DexMethod method) {
-      registerMethod(method);
-    }
+      private boolean registerType(DexType type) {
+        if (type.descriptor.startsWith(jDollarDescriptorPrefix)) {
+          setResult(true);
+          return true;
+        }
+        return false;
+      }
 
-    @Override
-    public void registerInvokeStatic(DexMethod method, boolean itf) {
-      registerMethod(method);
-    }
+      @Override
+      public void registerInitClass(DexType type) {
+        registerType(type);
+      }
 
-    @Override
-    public void registerInvokeSuper(DexMethod method) {
-      registerMethod(method);
-    }
+      @Override
+      public void registerInvokeVirtual(DexMethod method) {
+        registerMethod(method);
+      }
 
-    @Override
-    public void registerInstanceFieldRead(DexField field) {
-      registerField(field);
-    }
+      @Override
+      public void registerInvokeDirect(DexMethod method) {
+        registerMethod(method);
+      }
 
-    @Override
-    public void registerInstanceFieldWrite(DexField field) {
-      registerField(field);
-    }
+      @Override
+      public void registerInvokeStatic(DexMethod method) {
+        registerMethod(method);
+      }
 
-    @Override
-    public void registerNewInstance(DexType type) {
-      registerType(type);
-    }
+      @Override
+      public void registerInvokeInterface(DexMethod method) {
+        registerMethod(method);
+      }
 
-    @Override
-    public void registerStaticFieldRead(DexField field) {
-      registerField(field);
-    }
+      @Override
+      public void registerInvokeStatic(DexMethod method, boolean itf) {
+        registerMethod(method);
+      }
 
-    @Override
-    public void registerStaticFieldWrite(DexField field) {
-      registerField(field);
-    }
+      @Override
+      public void registerInvokeSuper(DexMethod method) {
+        registerMethod(method);
+      }
 
-    @Override
-    public void registerTypeReference(DexType type) {
-      registerType(type);
-    }
+      @Override
+      public void registerInstanceFieldRead(DexField field) {
+        registerField(field);
+      }
 
-    @Override
-    public void registerInstanceOf(DexType type) {
-      registerType(type);
+      @Override
+      public void registerInstanceFieldWrite(DexField field) {
+        registerField(field);
+      }
+
+      @Override
+      public void registerNewInstance(DexType type) {
+        registerType(type);
+      }
+
+      @Override
+      public void registerStaticFieldRead(DexField field) {
+        registerField(field);
+      }
+
+      @Override
+      public void registerStaticFieldWrite(DexField field) {
+        registerField(field);
+      }
+
+      @Override
+      public void registerTypeReference(DexType type) {
+        registerType(type);
+      }
+
+      @Override
+      public void registerInstanceOf(DexType type) {
+        registerType(type);
+      }
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 5219a50..80b9cbd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -37,7 +37,7 @@
   void methodReturnsAbstractValue(
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue);
 
-  void unsetAbstractReturnValue(DexEncodedMethod method);
+  void unsetAbstractReturnValue(ProgramMethod method);
 
   void methodReturnsObjectWithUpperBoundType(
       DexEncodedMethod method, AppView<?> appView, TypeElement type);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index 9857098..d0b0349 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -12,6 +12,10 @@
     return false;
   }
 
+  public boolean isPostMethodProcessor() {
+    return false;
+  }
+
   public abstract MethodProcessingContext createMethodProcessingContext(ProgramMethod method);
 
   public abstract boolean isProcessedConcurrently(ProgramMethod method);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index a25e9dc..a54b717 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -50,6 +50,11 @@
   }
 
   @Override
+  public boolean isPostMethodProcessor() {
+    return true;
+  }
+
+  @Override
   public boolean shouldApplyCodeRewritings(ProgramMethod method) {
     assert !wave.contains(method);
     return !processed.contains(method);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index 9bd75fe..bea221a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -38,29 +38,25 @@
 
   private final AppView<?> appView;
   private final CallSiteInformation callSiteInformation;
-  private final PostMethodProcessor.Builder postMethodProcessorBuilder;
   private final Deque<SortedProgramMethodSet> waves;
 
   private ProcessorContext processorContext;
 
   private PrimaryMethodProcessor(
       AppView<AppInfoWithLiveness> appView,
-      PostMethodProcessor.Builder postMethodProcessorBuilder,
       CallGraph callGraph) {
     this.appView = appView;
     this.callSiteInformation = callGraph.createCallSiteInformation(appView);
-    this.postMethodProcessorBuilder = postMethodProcessorBuilder;
-    this.waves = createWaves(appView, callGraph, callSiteInformation);
+    this.waves = createWaves(appView, callGraph);
   }
 
   static PrimaryMethodProcessor create(
       AppView<AppInfoWithLiveness> appView,
-      PostMethodProcessor.Builder postMethodProcessorBuilder,
       ExecutorService executorService,
       Timing timing)
       throws ExecutionException {
     CallGraph callGraph = CallGraph.builder(appView).build(executorService, timing);
-    return new PrimaryMethodProcessor(appView, postMethodProcessorBuilder, callGraph);
+    return new PrimaryMethodProcessor(appView, callGraph);
   }
 
   @Override
@@ -84,29 +80,18 @@
     return callSiteInformation;
   }
 
-  private Deque<SortedProgramMethodSet> createWaves(
-      AppView<?> appView, CallGraph callGraph, CallSiteInformation callSiteInformation) {
+  private Deque<SortedProgramMethodSet> createWaves(AppView<?> appView, CallGraph callGraph) {
     InternalOptions options = appView.options();
     Deque<SortedProgramMethodSet> waves = new ArrayDeque<>();
     Set<Node> nodes = callGraph.nodes;
-    ProgramMethodSet reprocessing = ProgramMethodSet.create();
     int waveCount = 1;
     while (!nodes.isEmpty()) {
       SortedProgramMethodSet wave = callGraph.extractLeaves();
-      wave.forEach(
-          method -> {
-            if (callSiteInformation.hasSingleCallSite(method) && options.enableInlining) {
-              callGraph.cycleEliminationResult.forEachRemovedCaller(method, reprocessing::add);
-            }
-          });
       waves.addLast(wave);
       if (Log.ENABLED && Log.isLoggingEnabledFor(PrimaryMethodProcessor.class)) {
         Log.info(getClass(), "Wave #%d: %d", waveCount++, wave.size());
       }
     }
-    if (!reprocessing.isEmpty()) {
-      postMethodProcessorBuilder.put(reprocessing);
-    }
     options.testing.waveModifier.accept(waves);
     return waves;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
index 430d8ad..88fc606 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
@@ -109,22 +109,20 @@
                 new NestBasedAccessDesugaringUseRegistry(method, eventConsumer)));
   }
 
-  private class NestBasedAccessDesugaringUseRegistry extends UseRegistry {
+  private class NestBasedAccessDesugaringUseRegistry extends UseRegistry<ClasspathMethod> {
 
     private final NestBasedAccessDesugaringEventConsumer eventConsumer;
-    private final ClasspathMethod context;
 
     NestBasedAccessDesugaringUseRegistry(
         ClasspathMethod context, NestBasedAccessDesugaringEventConsumer eventConsumer) {
-      super(appView.dexItemFactory());
+      super(context, appView.dexItemFactory());
       this.eventConsumer = eventConsumer;
-      this.context = context;
     }
 
     private void registerFieldAccess(DexField reference, boolean isGet) {
       DexClassAndField field =
           reference.lookupMemberOnClass(appView.definitionForHolder(reference));
-      if (field != null && needsDesugaring(field, context)) {
+      if (field != null && needsDesugaring(field, getContext())) {
         ensureFieldAccessBridge(field, isGet, eventConsumer);
       }
     }
@@ -135,7 +133,7 @@
       }
       DexClassAndMethod method =
           reference.lookupMemberOnClass(appView.definitionForHolder(reference));
-      if (method != null && needsDesugaring(method, context)) {
+      if (method != null && needsDesugaring(method, getContext())) {
         ensureMethodBridge(method, eventConsumer);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
index f9ca2e9..8f658a3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
@@ -453,10 +453,12 @@
                   assert method.isClasspathMethod();
                   return appView
                       .getSyntheticItems()
-                      .createFixedClasspathClass(
+                      .ensureFixedClasspathClass(
                           SyntheticKind.INIT_TYPE_ARGUMENT,
                           method.asClasspathMethod().getHolder(),
-                          dexItemFactory)
+                          appView,
+                          ignored -> {},
+                          ignored -> {})
                       .getType();
                 }
               });
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 0332cce..264eda0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -33,9 +32,10 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
+import com.android.tools.r8.ir.optimize.Inliner.InlineResult;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
-import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.Inliner.RetryAction;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
@@ -123,13 +123,6 @@
       ProgramMethod singleTarget,
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
-    MethodOptimizationInfo targetOptimizationInfo = singleTargetMethod.getOptimizationInfo();
-    if (targetOptimizationInfo.neverInline()) {
-      whyAreYouNotInliningReporter.reportMarkedAsNeverInline();
-      return false;
-    }
-
     // Do not inline if the inlinee is greater than the api caller level.
     // TODO(b/188498051): We should not force inline lower api method calls.
     if (reason != Reason.FORCE
@@ -147,10 +140,10 @@
       return false;
     }
 
-    if (method.getDefinition() == singleTargetMethod) {
+    if (method.isStructurallyEqualTo(singleTarget)) {
       // Cannot handle recursive inlining at this point.
       // Force inlined method should never be recursive.
-      assert !targetOptimizationInfo.forceInline();
+      assert !singleTarget.getOptimizationInfo().forceInline();
       whyAreYouNotInliningReporter.reportRecursiveMethod();
       return false;
     }
@@ -188,20 +181,22 @@
     }
 
     if (reason == Reason.DUAL_CALLER) {
+      assert methodProcessor.isPrimaryMethodProcessor() || methodProcessor.isPostMethodProcessor();
       if (satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
         // When we have a method with two call sites, we simply inline the method as we normally do
         // when the method is small. We still need to ensure that the other call site is also
         // inlined, though. Therefore, we record here that we have seen one of the two call sites
         // as we normally do.
-        inliner.recordDoubleInliningCandidate(method, singleTarget);
-      } else if (inliner.isDoubleInliningEnabled()) {
-        if (!inliner.satisfiesRequirementsForDoubleInlining(method, singleTarget)) {
+        inliner.recordDoubleInliningCandidate(method, singleTarget, methodProcessor);
+      } else if (inliner.isDoubleInliningEnabled(methodProcessor)) {
+        if (!inliner.satisfiesRequirementsForDoubleInlining(
+            method, singleTarget, methodProcessor)) {
           whyAreYouNotInliningReporter.reportInvalidDoubleInliningCandidate();
           return false;
         }
       } else {
         // TODO(b/142300882): Should in principle disallow inlining in this case.
-        inliner.recordDoubleInliningCandidate(method, singleTarget);
+        inliner.recordDoubleInliningCandidate(method, singleTarget, methodProcessor);
       }
     } else if (reason == Reason.SIMPLE
         && !satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
@@ -265,7 +260,7 @@
   }
 
   @Override
-  public InlineAction computeInlining(
+  public InlineResult computeInlining(
       InvokeMethod invoke,
       SingleResolutionResult resolutionResult,
       ProgramMethod singleTarget,
@@ -288,11 +283,21 @@
       return null;
     }
 
-    Reason reason = reasonStrategy.computeInliningReason(invoke, singleTarget, context);
+    Reason reason =
+        reasonStrategy.computeInliningReason(invoke, singleTarget, context, methodProcessor);
     if (reason == Reason.NEVER) {
       return null;
     }
 
+    if (reason == Reason.SIMPLE
+        && !singleTarget.getDefinition().isProcessed()
+        && methodProcessor.isPrimaryMethodProcessor()) {
+      // The single target has this method as single caller, but the single target is not yet
+      // processed. Enqueue the context for processing in the secondary optimization pass to allow
+      // the single caller inlining to happen.
+      return new RetryAction();
+    }
+
     if (!singleTarget
         .getDefinition()
         .isInliningCandidate(method, reason, appView.appInfo(), whyAreYouNotInliningReporter)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index 41b6fe6..0e26fa3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
+import com.android.tools.r8.ir.optimize.Inliner.InlineResult;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -61,7 +62,7 @@
   }
 
   @Override
-  public InlineAction computeInlining(
+  public InlineResult computeInlining(
       InvokeMethod invoke,
       SingleResolutionResult resolutionResult,
       ProgramMethod singleTarget,
@@ -79,12 +80,7 @@
     if (info == null) {
       return null;
     }
-
     assert method.getDefinition() != info.target.getDefinition();
-    // Even though call to Inliner::performForcedInlining is supposed to be controlled by
-    // the caller, it's still suspicious if we want to force inline something that is marked
-    // with neverInline() flag.
-    assert !info.target.getDefinition().getOptimizationInfo().neverInline();
     assert passesInliningConstraints(
         invoke, resolutionResult, info.target, Reason.FORCE, whyAreYouNotInliningReporter);
     return new InlineAction(info.target, invoke, Reason.FORCE);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 7591f78..2f61964 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -86,8 +86,11 @@
   private final LensCodeRewriter lensCodeRewriter;
   final MainDexInfo mainDexInfo;
 
+  // The set of callers of single caller methods where the single caller method could not be inlined
+  // due to not being processed at the time of inlining.
+  private final LongLivedProgramMethodSetBuilder<ProgramMethodSet> singleInlineCallers;
+
   // State for inlining methods which are known to be called twice.
-  private boolean applyDoubleInlining = false;
   private LongLivedProgramMethodSetBuilder<ProgramMethodSet> doubleInlineCallers;
   private final ProgramMethodSet doubleInlineSelectedTargets = ProgramMethodSet.create();
   private final Map<DexEncodedMethod, ProgramMethod> doubleInlineeCandidates =
@@ -106,6 +109,8 @@
             : ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
     this.lensCodeRewriter = lensCodeRewriter;
     this.mainDexInfo = appView.appInfo().getMainDexInfo();
+    this.singleInlineCallers =
+        LongLivedProgramMethodSetBuilder.createConcurrentForIdentitySet(appView.graphLens());
     availableApiExceptions =
         appView.options().canHaveDalvikCatchHandlerVerificationBug()
             ? new AvailableApiExceptions(appView.options())
@@ -149,8 +154,8 @@
     return false;
   }
 
-  boolean isDoubleInliningEnabled() {
-    return applyDoubleInlining;
+  boolean isDoubleInliningEnabled(MethodProcessor methodProcessor) {
+    return methodProcessor.isPostMethodProcessor();
   }
 
   private ConstraintWithTarget instructionAllowedForInlining(
@@ -210,19 +215,20 @@
   }
 
   synchronized boolean satisfiesRequirementsForDoubleInlining(
-      ProgramMethod method, ProgramMethod target) {
-    if (applyDoubleInlining) {
+      ProgramMethod method, ProgramMethod target, MethodProcessor methodProcessor) {
+    if (isDoubleInliningEnabled(methodProcessor)) {
       // Don't perform the actual inlining if this was not selected.
       return doubleInlineSelectedTargets.contains(target);
     }
 
     // Just preparing for double inlining.
-    recordDoubleInliningCandidate(method, target);
+    recordDoubleInliningCandidate(method, target, methodProcessor);
     return false;
   }
 
-  synchronized void recordDoubleInliningCandidate(ProgramMethod method, ProgramMethod target) {
-    if (applyDoubleInlining) {
+  synchronized void recordDoubleInliningCandidate(
+      ProgramMethod method, ProgramMethod target, MethodProcessor methodProcessor) {
+    if (isDoubleInliningEnabled(methodProcessor)) {
       return;
     }
 
@@ -250,9 +256,23 @@
     // The double inline callers are always rewritten up until the graph lens of the primary
     // optimization pass, so we can safely merge them into the methods to reprocess (which may be
     // rewritten with a newer graph lens).
-    postMethodProcessorBuilder.getMethodsToReprocessBuilder().merge(doubleInlineCallers);
+    postMethodProcessorBuilder
+        .getMethodsToReprocessBuilder()
+        .rewrittenWithLens(appView)
+        .merge(
+            doubleInlineCallers
+                .rewrittenWithLens(appView)
+                .removeIf(
+                    appView,
+                    method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()))
+        .merge(
+            singleInlineCallers
+                .rewrittenWithLens(appView)
+                .removeIf(
+                    appView,
+                    method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()));
     doubleInlineCallers = null;
-    applyDoubleInlining = true;
+    singleInlineCallers.clear();
   }
 
   /**
@@ -571,7 +591,18 @@
     }
   }
 
-  public static class InlineAction {
+  public abstract static class InlineResult {
+
+    InlineAction asInlineAction() {
+      return null;
+    }
+
+    boolean isRetryAction() {
+      return false;
+    }
+  }
+
+  public static class InlineAction extends InlineResult {
 
     public final ProgramMethod target;
     public final Invoke invoke;
@@ -586,6 +617,11 @@
       this.reason = reason;
     }
 
+    @Override
+    InlineAction asInlineAction() {
+      return this;
+    }
+
     void setShouldSynthesizeInitClass() {
       assert !shouldSynthesizeNullCheckForReceiver;
       shouldSynthesizeInitClass = true;
@@ -799,6 +835,14 @@
     }
   }
 
+  public static class RetryAction extends InlineResult {
+
+    @Override
+    boolean isRetryAction() {
+      return true;
+    }
+  }
+
   static class InlineeWithReason {
 
     final Reason reason;
@@ -1004,7 +1048,7 @@
               oracle.isForcedInliningOracle()
                   ? NopWhyAreYouNotInliningReporter.getInstance()
                   : WhyAreYouNotInliningReporter.createFor(singleTarget, appView, context);
-          InlineAction action =
+          InlineResult inlineResult =
               oracle.computeInlining(
                   invoke,
                   resolutionResult,
@@ -1012,11 +1056,18 @@
                   context,
                   classInitializationAnalysis,
                   whyAreYouNotInliningReporter);
-          if (action == null) {
+          if (inlineResult == null) {
             assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
             continue;
           }
 
+          if (inlineResult.isRetryAction()) {
+            enqueueMethodForReprocessing(context);
+            continue;
+          }
+
+          InlineAction action = inlineResult.asInlineAction();
+
           DexProgramClass downcastClass = getDowncastTypeIfNeeded(strategy, invoke, singleTarget);
           if (downcastClass != null
               && AccessControl.isClassAccessible(downcastClass, context, appView)
@@ -1240,6 +1291,14 @@
     assert IteratorUtils.peekNext(blockIterator) == firstInlineeBlock;
   }
 
+  public void enqueueMethodForReprocessing(ProgramMethod method) {
+    singleInlineCallers.add(method, appView.graphLens());
+  }
+
+  public void pruneMethod(ProgramMethod method) {
+    singleInlineCallers.remove(method.getReference(), appView.graphLens());
+  }
+
   public static boolean verifyNoMethodsInlinedDueToSingleCallSite(AppView<?> appView) {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       for (DexEncodedMethod method : clazz.methods()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index 1e5c3ae..f839991 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
+import com.android.tools.r8.ir.optimize.Inliner.InlineResult;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 
@@ -29,7 +29,7 @@
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
 
-  InlineAction computeInlining(
+  InlineResult computeInlining(
       InvokeMethod invoke,
       SingleResolutionResult resolutionResult,
       ProgramMethod singleTarget,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index e4229c2..26430a4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -25,14 +25,11 @@
 import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
 import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 
-import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -43,9 +40,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues.EnumStaticFieldValues;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
@@ -78,7 +73,6 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
@@ -132,7 +126,6 @@
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
 import java.util.function.Function;
-import java.util.function.Predicate;
 
 public class EnumUnboxerImpl extends EnumUnboxer {
 
@@ -919,203 +912,6 @@
     }
   }
 
-  private class EnumAccessibilityUseRegistry extends UseRegistry {
-
-    private ProgramMethod context;
-    private Constraint constraint;
-
-    public EnumAccessibilityUseRegistry(DexItemFactory factory) {
-      super(factory);
-    }
-
-    public Constraint computeConstraint(ProgramMethod method) {
-      constraint = Constraint.ALWAYS;
-      context = method;
-      method.registerCodeReferences(this);
-      return constraint;
-    }
-
-    public Constraint deriveConstraint(DexType targetHolder, AccessFlags<?> flags) {
-      DexProgramClass contextHolder = context.getHolder();
-      if (targetHolder == contextHolder.type) {
-        return Constraint.ALWAYS;
-      }
-      if (flags.isPublic()) {
-        return Constraint.ALWAYS;
-      }
-      if (flags.isPrivate()) {
-        // Enum unboxing is currently happening only cf to dex, and no class should be in a nest
-        // at this point. If that is the case, we just don't unbox the enum, or we would need to
-        // support Constraint.SAMENEST in the enum unboxer.
-        assert !contextHolder.isInANest();
-        // Only accesses within the enum are allowed since all enum methods and fields will be
-        // moved to the same class, and the enum itself becomes an integer, which is
-        // accessible everywhere.
-        return Constraint.NEVER;
-      }
-      assert flags.isProtected() || flags.isPackagePrivate();
-      // Protected is in practice equivalent to package private in this analysis since we are
-      // accessing the member from an enum context where subclassing is limited.
-      // At this point we don't support unboxing enums with subclasses, so we assume either
-      // same package access, or we just don't unbox.
-      // The only protected methods in java.lang.Enum are clone, finalize and the constructor.
-      // Besides calls to the constructor in the instance initializer, Enums with calls to such
-      // methods cannot be unboxed.
-      return targetHolder.isSamePackage(contextHolder.type) ? Constraint.PACKAGE : Constraint.NEVER;
-    }
-
-    @Override
-    public void registerTypeReference(DexType type) {
-      if (type.isArrayType()) {
-        registerTypeReference(type.toBaseType(factory));
-        return;
-      }
-
-      if (type.isPrimitiveType()) {
-        return;
-      }
-
-      DexClass definition = appView.definitionFor(type);
-      if (definition == null) {
-        constraint = Constraint.NEVER;
-        return;
-      }
-      constraint = constraint.meet(deriveConstraint(type, definition.accessFlags));
-    }
-
-    @Override
-    public void registerInitClass(DexType type) {
-      registerTypeReference(type);
-    }
-
-    @Override
-    public void registerInstanceOf(DexType type) {
-      registerTypeReference(type);
-    }
-
-    @Override
-    public void registerNewInstance(DexType type) {
-      registerTypeReference(type);
-    }
-
-    @Override
-    public void registerInvokeVirtual(DexMethod method) {
-      registerVirtualInvoke(method, false);
-    }
-
-    @Override
-    public void registerInvokeInterface(DexMethod method) {
-      registerVirtualInvoke(method, true);
-    }
-
-    private void registerVirtualInvoke(DexMethod method, boolean isInterface) {
-      if (method.holder.isArrayType()) {
-        return;
-      }
-      // Perform resolution and derive unboxing constraints based on the accessibility of the
-      // resolution result.
-      MethodResolutionResult resolutionResult =
-          appView.appInfo().resolveMethod(method, isInterface);
-      if (!resolutionResult.isVirtualTarget()) {
-        constraint = Constraint.NEVER;
-        return;
-      }
-      registerTarget(
-          resolutionResult.getInitialResolutionHolder(), resolutionResult.getSingleTarget());
-    }
-
-    private void registerTarget(DexClass initialResolutionHolder, DexEncodedMember<?, ?> target) {
-      if (target == null) {
-        // This will fail at runtime.
-        constraint = Constraint.NEVER;
-        return;
-      }
-      DexType resolvedHolder = target.getHolderType();
-      if (initialResolutionHolder == null) {
-        constraint = Constraint.NEVER;
-        return;
-      }
-      Constraint memberConstraint = deriveConstraint(resolvedHolder, target.getAccessFlags());
-      // We also have to take the constraint of the initial resolution holder into account.
-      Constraint classConstraint =
-          deriveConstraint(initialResolutionHolder.type, initialResolutionHolder.accessFlags);
-      Constraint instructionConstraint = memberConstraint.meet(classConstraint);
-      constraint = instructionConstraint.meet(constraint);
-    }
-
-    @Override
-    public void registerInvokeDirect(DexMethod method) {
-      registerSingleTargetInvoke(method, DexEncodedMethod::isDirectMethod);
-    }
-
-    @Override
-    public void registerInvokeStatic(DexMethod method) {
-      registerSingleTargetInvoke(method, DexEncodedMethod::isStatic);
-    }
-
-    private void registerSingleTargetInvoke(
-        DexMethod method, Predicate<DexEncodedMethod> methodValidator) {
-      if (method.holder.isArrayType()) {
-        return;
-      }
-      MethodResolutionResult resolutionResult =
-          appView.appInfo().unsafeResolveMethodDueToDexFormat(method);
-      DexEncodedMethod target = resolutionResult.getSingleTarget();
-      if (target == null || !methodValidator.test(target)) {
-        constraint = Constraint.NEVER;
-        return;
-      }
-      registerTarget(resolutionResult.getInitialResolutionHolder(), target);
-    }
-
-    @Override
-    public void registerInvokeSuper(DexMethod method) {
-      // Invoke-super can only target java.lang.Enum methods since we do not unbox enums with
-      // subclasses. Calls to java.lang.Object methods would have resulted in the enum to be marked
-      // as unboxable. The methods of java.lang.Enum called are already analyzed in the enum
-      // unboxer analysis, so invoke-super is always valid.
-      assert method.holder == factory.enumType;
-    }
-
-    @Override
-    public void registerCallSite(DexCallSite callSite) {
-      // This is reached after lambda desugaring, so this should not be a lambda call site.
-      // We do not unbox enums with invoke custom since it's not clear the accessibility
-      // constraints would be correct if the method holding the invoke custom is moved to
-      // another class.
-      assert appView.options().isGeneratingClassFiles()
-          || !factory.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
-      constraint = Constraint.NEVER;
-    }
-
-    private void registerFieldInstruction(DexField field) {
-      FieldResolutionResult fieldResolutionResult = appView.appInfo().resolveField(field, context);
-      registerTarget(
-          fieldResolutionResult.getInitialResolutionHolder(),
-          fieldResolutionResult.getResolvedField());
-    }
-
-    @Override
-    public void registerInstanceFieldRead(DexField field) {
-      registerFieldInstruction(field);
-    }
-
-    @Override
-    public void registerInstanceFieldWrite(DexField field) {
-      registerFieldInstruction(field);
-    }
-
-    @Override
-    public void registerStaticFieldRead(DexField field) {
-      registerFieldInstruction(field);
-    }
-
-    @Override
-    public void registerStaticFieldWrite(DexField field) {
-      registerFieldInstruction(field);
-    }
-  }
-
   private void analyzeInitializers() {
     enumUnboxingCandidatesInfo.forEachCandidate(
         enumClass -> {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index c085286..24d3ad4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -165,11 +165,6 @@
   }
 
   @Override
-  public boolean neverInline() {
-    return false;
-  }
-
-  @Override
   public boolean checksNullReceiverBeforeAnySideEffect() {
     return UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index aff19ef..08ef5d9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -92,8 +92,6 @@
 
   public abstract boolean forceInline();
 
-  public abstract boolean neverInline();
-
   public abstract boolean checksNullReceiverBeforeAnySideEffect();
 
   public abstract boolean triggersClassInitBeforeAnySideEffect();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index e28b6ad..f93dc2a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -137,7 +137,7 @@
       Timing timing) {
     DexEncodedMethod definition = method.getDefinition();
     identifyBridgeInfo(definition, code, feedback, timing);
-    analyzeReturns(code, feedback, timing);
+    analyzeReturns(code, feedback, methodProcessor, timing);
     if (options.enableInlining) {
       identifyInvokeSemanticsForInlining(definition, code, feedback, timing);
     }
@@ -166,13 +166,15 @@
     timing.end();
   }
 
-  private void analyzeReturns(IRCode code, OptimizationFeedback feedback, Timing timing) {
+  private void analyzeReturns(
+      IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor, Timing timing) {
     timing.begin("Identify returns argument");
-    analyzeReturns(code, feedback);
+    analyzeReturns(code, feedback, methodProcessor);
     timing.end();
   }
 
-  private void analyzeReturns(IRCode code, OptimizationFeedback feedback) {
+  private void analyzeReturns(
+      IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
     ProgramMethod context = code.context();
     DexEncodedMethod method = context.getDefinition();
     List<BasicBlock> normalExits = code.computeNormalExitBlocks();
@@ -204,7 +206,7 @@
           feedback.methodReturnsAbstractValue(method, appView, abstractReturnValue);
           if (checkCastAndInstanceOfMethodSpecialization != null) {
             checkCastAndInstanceOfMethodSpecialization.addCandidateForOptimization(
-                context, abstractReturnValue);
+                context, abstractReturnValue, methodProcessor);
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index dd6f917..9f1ea2d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -361,6 +361,10 @@
     return isFlagSet(HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG);
   }
 
+  void unsetInlinedIntoSingleCallSite() {
+    clearFlag(HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG);
+  }
+
   void markInlinedIntoSingleCallSite() {
     setFlag(HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG);
   }
@@ -456,11 +460,6 @@
   }
 
   @Override
-  public boolean neverInline() {
-    return inlining == InlinePreference.NeverInline;
-  }
-
-  @Override
   public boolean checksNullReceiverBeforeAnySideEffect() {
     return isFlagSet(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG);
   }
@@ -596,13 +595,6 @@
     inlining = InlinePreference.Default;
   }
 
-  // TODO(b/140214568): Should be package-private.
-  public void markNeverInline() {
-    // For concurrent scenarios we should allow the flag to be already set
-    assert inlining == InlinePreference.Default || inlining == InlinePreference.NeverInline;
-    inlining = InlinePreference.NeverInline;
-  }
-
   void markCheckNullReceiverBeforeAnySideEffect(boolean mark) {
     setFlag(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG, mark);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 472a65b..1b4acdd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -199,7 +199,7 @@
   }
 
   @Override
-  public synchronized void unsetAbstractReturnValue(DexEncodedMethod method) {
+  public synchronized void unsetAbstractReturnValue(ProgramMethod method) {
     getMethodOptimizationInfoForUpdating(method).unsetAbstractReturnValue();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 366a1ac..8b13771 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -79,7 +79,7 @@
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {}
 
   @Override
-  public void unsetAbstractReturnValue(DexEncodedMethod method) {}
+  public void unsetAbstractReturnValue(ProgramMethod method) {}
 
   @Override
   public void methodReturnsObjectWithUpperBoundType(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index f33bf52..86ea927 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -107,8 +107,10 @@
   }
 
   @Override
-  public void unsetAbstractReturnValue(DexEncodedMethod method) {
-    method.getMutableOptimizationInfo().unsetAbstractReturnValue();
+  public void unsetAbstractReturnValue(ProgramMethod method) {
+    if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
+      method.getDefinition().getMutableOptimizationInfo().unsetAbstractReturnValue();
+    }
   }
 
   @Override
@@ -242,4 +244,13 @@
   public void setUnusedArguments(ProgramMethod method, BitSet unusedArguments) {
     method.getDefinition().getMutableOptimizationInfo().setUnusedArguments(unusedArguments);
   }
+
+  public void unsetInlinedIntoSingleCallSite(ProgramMethod method) {
+    if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
+      method
+          .getOptimizationInfo()
+          .asMutableMethodOptimizationInfo()
+          .unsetInlinedIntoSingleCallSite();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
index c6b0aa4..43b302b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.conversion.CallSiteInformation;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -31,7 +32,10 @@
 
   @Override
   public Reason computeInliningReason(
-      InvokeMethod invoke, ProgramMethod target, ProgramMethod context) {
+      InvokeMethod invoke,
+      ProgramMethod target,
+      ProgramMethod context,
+      MethodProcessor methodProcessor) {
     DexEncodedMethod targetMethod = target.getDefinition();
     DexMethod targetReference = target.getReference();
     if (targetMethod.getOptimizationInfo().forceInline()) {
@@ -52,7 +56,7 @@
     if (isSingleCallerInliningTarget(target)) {
       return Reason.SINGLE_CALLER;
     }
-    if (isDoubleInliningTarget(target)) {
+    if (isDoubleInliningTarget(target, methodProcessor)) {
       return Reason.DUAL_CALLER;
     }
     return Reason.SIMPLE;
@@ -72,11 +76,13 @@
     return true;
   }
 
-  private boolean isDoubleInliningTarget(ProgramMethod candidate) {
-    // 10 is found from measuring.
-    if (callSiteInformation.hasDoubleCallSite(candidate)
-        || inliner.isDoubleInlineSelectedTarget(candidate)) {
-      return candidate.getDefinition().getCode().estimatedSizeForInliningAtMost(10);
+  private boolean isDoubleInliningTarget(ProgramMethod candidate, MethodProcessor methodProcessor) {
+    if (methodProcessor.isPrimaryMethodProcessor() || methodProcessor.isPostMethodProcessor()) {
+      if (callSiteInformation.hasDoubleCallSite(candidate)
+          || inliner.isDoubleInlineSelectedTarget(candidate)) {
+        // 10 is found from measuring.
+        return candidate.getDefinition().getCode().estimatedSizeForInliningAtMost(10);
+      }
     }
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java
index ae23a74..aed242b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 
 public class FixedInliningReasonStrategy implements InliningReasonStrategy {
@@ -18,7 +19,10 @@
 
   @Override
   public Reason computeInliningReason(
-      InvokeMethod invoke, ProgramMethod target, ProgramMethod context) {
+      InvokeMethod invoke,
+      ProgramMethod target,
+      ProgramMethod context,
+      MethodProcessor methodProcessor) {
     return reason;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
index f2ae52a..00ff53c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
@@ -6,9 +6,14 @@
 
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 
 public interface InliningReasonStrategy {
 
-  Reason computeInliningReason(InvokeMethod invoke, ProgramMethod target, ProgramMethod context);
+  Reason computeInliningReason(
+      InvokeMethod invoke,
+      ProgramMethod target,
+      ProgramMethod context,
+      MethodProcessor methodProcessor);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 0fbb615..dd67717 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -385,7 +385,8 @@
 
       if (instruction.isInvokeCustom()) {
         // Just invalidate any candidates referenced from non-static context.
-        CallSiteReferencesInvalidator invalidator = new CallSiteReferencesInvalidator(factory);
+        CallSiteReferencesInvalidator invalidator =
+            new CallSiteReferencesInvalidator(context, factory);
         invalidator.registerCallSite(instruction.asInvokeCustom().getCallSite());
         continue;
       }
@@ -726,10 +727,10 @@
     new StaticizingProcessor(appView, this, converter).run(feedback, executorService);
   }
 
-  private class CallSiteReferencesInvalidator extends UseRegistry {
+  private class CallSiteReferencesInvalidator extends UseRegistry<ProgramMethod> {
 
-    private CallSiteReferencesInvalidator(DexItemFactory factory) {
-      super(factory);
+    private CallSiteReferencesInvalidator(ProgramMethod context, DexItemFactory factory) {
+      super(context, factory);
     }
 
     private void registerMethod(DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
index 906ac7f..18a964f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
@@ -15,10 +15,10 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 
@@ -34,7 +34,7 @@
  *
  * <p>TODO(b/151596599): Also handle methods that implement casts.
  */
-public class CheckCastAndInstanceOfMethodSpecialization implements Action {
+public class CheckCastAndInstanceOfMethodSpecialization {
 
   private static final OptimizationFeedbackSimple feedback =
       OptimizationFeedbackSimple.getInstance();
@@ -52,14 +52,16 @@
     this.converter = converter;
   }
 
-  public void addCandidateForOptimization(ProgramMethod method, AbstractValue abstractReturnValue) {
+  public void addCandidateForOptimization(
+      ProgramMethod method, AbstractValue abstractReturnValue, MethodProcessor methodProcessor) {
     if (!converter.isInWave()) {
       return;
     }
+    assert methodProcessor.isPrimaryMethodProcessor();
     if (isCandidateForInstanceOfOptimization(method, abstractReturnValue)) {
       synchronized (this) {
         if (candidatesForInstanceOfOptimization.isEmpty()) {
-          converter.addWaveDoneAction(this);
+          converter.addWaveDoneAction(() -> execute(methodProcessor));
         }
         candidatesForInstanceOfOptimization.add(method);
       }
@@ -72,18 +74,18 @@
         && abstractReturnValue.isSingleBoolean();
   }
 
-  @Override
-  public void execute() {
+  public void execute(MethodProcessor methodProcessor) {
     assert !candidatesForInstanceOfOptimization.isEmpty();
     ProgramMethodSet processed = ProgramMethodSet.create();
     for (ProgramMethod method : candidatesForInstanceOfOptimization) {
       if (!processed.contains(method)) {
-        processCandidateForInstanceOfOptimization(method);
+        processCandidateForInstanceOfOptimization(method, methodProcessor);
       }
     }
   }
 
-  private void processCandidateForInstanceOfOptimization(ProgramMethod method) {
+  private void processCandidateForInstanceOfOptimization(
+      ProgramMethod method, MethodProcessor methodProcessor) {
     DexEncodedMethod definition = method.getDefinition();
     if (!definition.isNonPrivateVirtualMethod()) {
       return;
@@ -140,16 +142,27 @@
           parentMethodDefinition.getCode().buildIR(parentMethod, appView, parentMethod.getOrigin());
       converter.markProcessed(code, feedback);
       // Fixup method optimization info (the method no longer returns a constant).
-      feedback.unsetAbstractReturnValue(parentMethod.getDefinition());
+      feedback.unsetAbstractReturnValue(parentMethod);
       feedback.unsetClassInlinerMethodConstraint(parentMethod);
     } else {
       return;
     }
 
-    method.getHolder().removeMethod(method.getReference());
-
     appView.withArgumentPropagator(
         argumentPropagator -> argumentPropagator.transferArgumentInformation(method, parentMethod));
+
+    // Each call to the override is now a call to the parent method, so it is no longer true that
+    // parent method has been inlined into its single call site.
+    feedback.unsetInlinedIntoSingleCallSite(parentMethod);
+
+    // For the same reason, we no longer have single or dual caller information for the parent
+    // method.
+    methodProcessor.getCallSiteInformation().unsetCallSiteInformation(parentMethod);
+
+    // Remove the method and notify other optimizations that the override has been removed to allow
+    // the optimizations to fixup their state.
+    method.getHolder().removeMethod(method.getReference());
+    converter.pruneMethod(method);
   }
 
   private ProgramMethod resolveOnSuperClass(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
index 2c1b9a3..5c73aee 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
@@ -30,7 +30,7 @@
 
   public abstract SourceCodeProvider getSourceCodeProvider();
 
-  public abstract Consumer<UseRegistry> getRegistryCallback();
+  public abstract Consumer<UseRegistry> getRegistryCallback(DexClassAndMethod method);
 
   @Override
   public boolean isEmptyVoidMethod() {
@@ -78,7 +78,7 @@
   }
 
   private void internalRegisterCodeReferences(DexClassAndMethod method, UseRegistry registry) {
-    getRegistryCallback().accept(registry);
+    getRegistryCallback(method).accept(registry);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index 6bd2080..c06c056 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.synthetic;
 
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import java.util.function.Consumer;
 
@@ -21,5 +22,5 @@
   }
 
   @Override
-  public abstract Consumer<UseRegistry> getRegistryCallback();
+  public abstract Consumer<UseRegistry> getRegistryCallback(DexClassAndMethod method);
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
index 85e7b1a..362f5f5 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -4,6 +4,8 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.MapIdEnvironment;
+import com.android.tools.r8.MapIdProvider;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.Version;
 import com.android.tools.r8.errors.Unreachable;
@@ -33,17 +35,19 @@
 
   // Hash of the Proguard map (excluding the header up to and including the hash marker).
   public static class ProguardMapId {
+    private final String id;
     private final String hash;
 
-    private ProguardMapId(String id) {
+    private ProguardMapId(String id, String hash) {
       assert id != null;
-      this.hash = id;
+      assert hash != null;
+      this.id = id;
+      this.hash = hash;
     }
 
-    /** Truncated prefix of the content hash. */
-    // TODO(b/201269335): Update this to be a full "source-file marker".
+    /** Id for the map file (user defined or a truncated prefix of the content hash). */
     public String getId() {
-      return hash.substring(0, PG_MAP_ID_LENGTH);
+      return id;
     }
 
     /** The actual content hash. */
@@ -84,7 +88,7 @@
   private ProguardMapId computeProguardMapId() {
     ProguardMapIdBuilder builder = new ProguardMapIdBuilder();
     classNameMapper.write(builder);
-    return builder.build();
+    return builder.build(options.mapIdProvider);
   }
 
   private void writeBody() {
@@ -138,14 +142,31 @@
 
     private final Hasher hasher = Hashing.sha256().newHasher();
 
+    private MapIdProvider getProviderOrDefault(MapIdProvider provider) {
+      return provider != null
+          ? provider
+          : environment -> environment.getMapHash().substring(0, PG_MAP_ID_LENGTH);
+    }
+
+    private MapIdEnvironment getEnvironment(String hash) {
+      return new MapIdEnvironment() {
+        @Override
+        public String getMapHash() {
+          return hash;
+        }
+      };
+    }
+
     @Override
     public ProguardMapIdBuilder accept(String string) {
       hasher.putString(string, StandardCharsets.UTF_8);
       return this;
     }
 
-    public ProguardMapId build() {
-      return new ProguardMapId(hasher.hash().toString());
+    public ProguardMapId build(MapIdProvider mapIdProvider) {
+      String hash = hasher.hash().toString();
+      String id = getProviderOrDefault(mapIdProvider).get(getEnvironment(hash));
+      return new ProguardMapId(id, hash);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
index b8ddc06..da906a8 100644
--- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -4,15 +4,10 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexDebugEvent;
-import com.android.tools.r8.graph.DexDebugEvent.SetFile;
-import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.shaking.ProguardConfiguration;
-import java.util.Arrays;
 
 /**
  * Visit program {@link DexClass}es and replace their sourceFile with the given string.
@@ -30,54 +25,24 @@
   }
 
   public void run() {
+    boolean isMinifying = appView.options().isMinifying();
     ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration();
-    String renameSourceFile = proguardConfiguration.getRenameSourceFileAttribute();
-    boolean hasRenameSourceFileAttribute = renameSourceFile != null;
-    // Return early if a user wants to keep the current source file attribute as-is.
-    if (!hasRenameSourceFileAttribute
-        && proguardConfiguration.getKeepAttributes().sourceFile
-        && appView.options().forceProguardCompatibility) {
+    boolean hasKeptNonRenamedSourceFile =
+        proguardConfiguration.getRenameSourceFileAttribute() == null
+            && proguardConfiguration.getKeepAttributes().sourceFile;
+    // If source file is kept without a rewrite, it is only modified it in a minifing full-mode.
+    if (hasKeptNonRenamedSourceFile
+        && (!isMinifying || appView.options().forceProguardCompatibility)) {
       return;
     }
-    boolean isMinifying = appView.options().isMinifying();
     assert !isMinifying || appView.appInfo().hasLiveness();
-    // Now, the user wants either to remove source file attribute or to rename it for non-kept
-    // classes.
     DexString defaultRenaming = getSourceFileRenaming(proguardConfiguration);
     for (DexClass clazz : application.classes()) {
-      // We only parse sourceFile if -keepattributes SourceFile, but for compat we should add
-      // a source file name, otherwise line positions will not be printed on the JVM or old version
-      // of ART.
-      if (!hasRenameSourceFileAttribute
-          && proguardConfiguration.getKeepAttributes().sourceFile
-          && !(isMinifying && appView.withLiveness().appInfo().isMinificationAllowed(clazz.type))) {
+      if (hasKeptNonRenamedSourceFile
+          && !appView.withLiveness().appInfo().isMinificationAllowed(clazz.type)) {
         continue;
       }
       clazz.sourceFile = defaultRenaming;
-      clazz.forEachMethod(encodedMethod -> {
-        // Abstract methods do not have code_item.
-        if (encodedMethod.shouldNotHaveCode()) {
-          return;
-        }
-        Code code = encodedMethod.getCode();
-        if (code == null) {
-          return;
-        }
-        if (code.isDexCode()) {
-          DexDebugInfo dexDebugInfo = code.asDexCode().getDebugInfo();
-          if (dexDebugInfo == null) {
-            return;
-          }
-          // Thanks to a single global source file, we can safely remove DBG_SET_FILE entirely.
-          dexDebugInfo.events =
-              Arrays.stream(dexDebugInfo.events)
-                  .filter(dexDebugEvent -> !(dexDebugEvent instanceof SetFile))
-                  .toArray(DexDebugEvent[]::new);
-        } else {
-          assert code.isCfCode();
-          // CF has nothing equivalent to SetFile, so there is nothing to remove.
-        }
-      });
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java b/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
index fadc223..9fcd27a 100644
--- a/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
+++ b/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
@@ -7,14 +7,16 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 
-public class InvokeSingleTargetExtractor extends UseRegistry {
+public class InvokeSingleTargetExtractor extends UseRegistry<ProgramMethod> {
+
   private InvokeKind kind = InvokeKind.NONE;
   private DexMethod target;
 
-  public InvokeSingleTargetExtractor(DexItemFactory factory) {
-    super(factory);
+  public InvokeSingleTargetExtractor(ProgramMethod context, DexItemFactory factory) {
+    super(context, factory);
   }
 
   private void setTarget(DexMethod target, InvokeKind kind) {
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 2b9252f..5dedc07 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -397,7 +397,7 @@
         method -> {
           if (method.getDefinition().hasCode()) {
             method.registerCodeReferences(
-                new UseRegistry(appView.dexItemFactory()) {
+                new UseRegistry<ProgramMethod>(method, appView.dexItemFactory()) {
 
                   @Override
                   public void registerInstanceFieldRead(DexField field) {
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
index 9d9a731..a851131 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
@@ -101,10 +101,9 @@
         executorService);
   }
 
-  private static class NonReboundMemberReferencesRegistry extends UseRegistry {
+  private static class NonReboundMemberReferencesRegistry extends UseRegistry<ProgramMethod> {
 
     private final AppInfoWithClassHierarchy appInfo;
-    private final ProgramMethod context;
     private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection;
     private final MethodAccessInfoCollection.ConcurrentBuilder methodAccessInfoCollectionBuilder;
     private final Set<DexField> seenFieldReferences;
@@ -117,9 +116,8 @@
         MethodAccessInfoCollection.ConcurrentBuilder methodAccessInfoCollectionBuilder,
         Set<DexField> seenFieldReferences,
         Set<DexMethod> seenMethodReferences) {
-      super(appView.dexItemFactory());
+      super(context, appView.dexItemFactory());
       this.appInfo = appView.appInfo();
-      this.context = context;
       this.fieldAccessInfoCollection = fieldAccessInfoCollection;
       this.methodAccessInfoCollectionBuilder = methodAccessInfoCollectionBuilder;
       this.seenFieldReferences = seenFieldReferences;
@@ -209,7 +207,7 @@
       if (method.getHolderType().isArrayType()) {
         return;
       }
-      DexClass holder = appInfo.definitionFor(method.getHolderType(), context);
+      DexClass holder = appInfo.definitionFor(method.getHolderType(), getContext());
       if (holder == null) {
         return;
       }
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index a990959..43220e6 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -40,7 +40,7 @@
       return false;
     }
     InvokeSingleTargetExtractor targetExtractor =
-        new InvokeSingleTargetExtractor(appView.dexItemFactory());
+        new InvokeSingleTargetExtractor(method, appView.dexItemFactory());
     method.registerCodeReferences(targetExtractor);
     DexMethod target = targetExtractor.getTarget();
     // javac-generated visibility forward bridge method has same descriptor (name, signature and
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 645bac0..080e691 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.optimize.argumentpropagation;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethodSignature;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -20,11 +21,15 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
 import com.google.common.collect.Sets;
+import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
 
 /** Optimization that propagates information about arguments from call sites to method entries. */
 public class ArgumentPropagator {
@@ -144,14 +149,26 @@
     timing.end();
 
     // Set the optimization info on each method.
+    Map<Set<DexProgramClass>, DexMethodSignatureSet> interfaceDispatchOutsideProgram =
+        new IdentityHashMap<>();
     populateParameterOptimizationInfo(
-        immediateSubtypingInfo, stronglyConnectedProgramComponents, executorService, timing);
+        immediateSubtypingInfo,
+        stronglyConnectedProgramComponents,
+        (stronglyConnectedProgramComponent, signature) -> {
+          interfaceDispatchOutsideProgram
+              .computeIfAbsent(
+                  stronglyConnectedProgramComponent, (unused) -> DexMethodSignatureSet.create())
+              .add(signature);
+        },
+        executorService,
+        timing);
 
     // Using the computed optimization info, build a graph lens that describes the mapping from
     // methods with constant parameters to methods with the constant parameters removed.
     Set<DexProgramClass> affectedClasses = Sets.newConcurrentHashSet();
     ArgumentPropagatorGraphLens graphLens =
-        new ArgumentPropagatorProgramOptimizer(appView, immediateSubtypingInfo)
+        new ArgumentPropagatorProgramOptimizer(
+                appView, immediateSubtypingInfo, interfaceDispatchOutsideProgram)
             .run(stronglyConnectedProgramComponents, affectedClasses::add, executorService, timing);
 
     // Find all the code objects that need reprocessing.
@@ -174,6 +191,7 @@
   private void populateParameterOptimizationInfo(
       ImmediateProgramSubtypingInfo immediateSubtypingInfo,
       List<Set<DexProgramClass>> stronglyConnectedProgramComponents,
+      BiConsumer<Set<DexProgramClass>, DexMethodSignature> interfaceDispatchOutsideProgram,
       ExecutorService executorService,
       Timing timing)
       throws ExecutionException {
@@ -189,7 +207,8 @@
             immediateSubtypingInfo,
             codeScannerResult,
             reprocessingCriteriaCollection,
-            stronglyConnectedProgramComponents)
+            stronglyConnectedProgramComponents,
+            interfaceDispatchOutsideProgram)
         .populateOptimizationInfo(executorService, timing);
     reprocessingCriteriaCollection = null;
     timing.end();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
index eff9893..30bab53 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
@@ -110,7 +110,7 @@
                       methodsToReprocessInClass.add(method);
                     } else {
                       AffectedMethodUseRegistry registry =
-                          new AffectedMethodUseRegistry(appView, graphLens);
+                          new AffectedMethodUseRegistry(appView, method, graphLens);
                       if (method.registerCodeReferencesWithResult(registry)) {
                         methodsToReprocessInClass.add(method);
                       }
@@ -125,14 +125,16 @@
             methodsToReprocessBuilder.addAll(methodsToReprocessForClass, currentGraphLens));
   }
 
-  static class AffectedMethodUseRegistry extends UseRegistryWithResult<Boolean> {
+  static class AffectedMethodUseRegistry extends UseRegistryWithResult<Boolean, ProgramMethod> {
 
     private final AppView<AppInfoWithLiveness> appView;
     private final ArgumentPropagatorGraphLens graphLens;
 
     AffectedMethodUseRegistry(
-        AppView<AppInfoWithLiveness> appView, ArgumentPropagatorGraphLens graphLens) {
-      super(appView.dexItemFactory(), false);
+        AppView<AppInfoWithLiveness> appView,
+        ProgramMethod context,
+        ArgumentPropagatorGraphLens graphLens) {
+      super(context, appView.dexItemFactory(), false);
       this.appView = appView;
       this.graphLens = graphLens;
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
index 7118043..5470ed5 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethodSignature;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
@@ -42,6 +43,7 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
 import java.util.stream.IntStream;
 
 /**
@@ -58,17 +60,22 @@
   private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
   private final List<Set<DexProgramClass>> stronglyConnectedProgramComponents;
 
+  private final BiConsumer<Set<DexProgramClass>, DexMethodSignature>
+      interfaceDispatchOutsideProgram;
+
   ArgumentPropagatorOptimizationInfoPopulator(
       AppView<AppInfoWithLiveness> appView,
       ImmediateProgramSubtypingInfo immediateSubtypingInfo,
       MethodStateCollectionByReference methodStates,
       ArgumentPropagatorReprocessingCriteriaCollection reprocessingCriteriaCollection,
-      List<Set<DexProgramClass>> stronglyConnectedProgramComponents) {
+      List<Set<DexProgramClass>> stronglyConnectedProgramComponents,
+      BiConsumer<Set<DexProgramClass>, DexMethodSignature> interfaceDispatchOutsideProgram) {
     this.appView = appView;
     this.immediateSubtypingInfo = immediateSubtypingInfo;
     this.methodStates = methodStates;
     this.reprocessingCriteriaCollection = reprocessingCriteriaCollection;
     this.stronglyConnectedProgramComponents = stronglyConnectedProgramComponents;
+    this.interfaceDispatchOutsideProgram = interfaceDispatchOutsideProgram;
   }
 
   /**
@@ -114,7 +121,12 @@
     //
     // To handle this we first propagate any argument information stored for I.m() to A.m() by doing
     // a top-down traversal over the interfaces in the strongly connected component.
-    new InterfaceMethodArgumentPropagator(appView, immediateSubtypingInfo, methodStates)
+    new InterfaceMethodArgumentPropagator(
+            appView,
+            immediateSubtypingInfo,
+            methodStates,
+            signature ->
+                interfaceDispatchOutsideProgram.accept(stronglyConnectedComponent, signature))
         .run(stronglyConnectedComponent);
 
     // Now all the argument information for a given method is guaranteed to be stored on a supertype
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 57611a3..51120e1 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
@@ -100,14 +100,18 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
+  private final Map<Set<DexProgramClass>, DexMethodSignatureSet> interfaceDispatchOutsideProgram;
 
   private final Map<DexClass, DexMethodSignatureSet> libraryVirtualMethods =
       new ConcurrentHashMap<>();
 
   public ArgumentPropagatorProgramOptimizer(
-      AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
+      AppView<AppInfoWithLiveness> appView,
+      ImmediateProgramSubtypingInfo immediateSubtypingInfo,
+      Map<Set<DexProgramClass>, DexMethodSignatureSet> interfaceDispatchOutsideProgram) {
     this.appView = appView;
     this.immediateSubtypingInfo = immediateSubtypingInfo;
+    this.interfaceDispatchOutsideProgram = interfaceDispatchOutsideProgram;
   }
 
   public ArgumentPropagatorGraphLens run(
@@ -121,7 +125,12 @@
         ThreadUtils.processItemsWithResults(
             stronglyConnectedProgramComponents,
             classes ->
-                new StronglyConnectedComponentOptimizer().optimize(classes, affectedClassConsumer),
+                new StronglyConnectedComponentOptimizer()
+                    .optimize(
+                        classes,
+                        interfaceDispatchOutsideProgram.getOrDefault(
+                            classes, DexMethodSignatureSet.empty()),
+                        affectedClassConsumer),
             executorService);
     timing.end();
 
@@ -204,8 +213,9 @@
     //  similarly to the way we deal with call chains in argument propagation. If a field is only
     //  assigned the parameter of a given method, we would add the flow constraint "parameter p ->
     //  field f".
-    public ArgumentPropagatorGraphLens.Builder optimize(
+    private ArgumentPropagatorGraphLens.Builder optimize(
         Set<DexProgramClass> stronglyConnectedProgramClasses,
+        DexMethodSignatureSet interfaceDispatchOutsideProgram,
         Consumer<DexProgramClass> affectedClassConsumer) {
       // First reserve pinned method signatures.
       reservePinnedMethodSignatures(stronglyConnectedProgramClasses);
@@ -213,7 +223,8 @@
       // To ensure that we preserve the overriding relationships between methods, we only remove a
       // constant or unused parameter from a virtual method when it can be removed from all other
       // virtual methods in the component with the same method signature.
-      computePrototypeChangesForVirtualMethods(stronglyConnectedProgramClasses);
+      computePrototypeChangesForVirtualMethods(
+          stronglyConnectedProgramClasses, interfaceDispatchOutsideProgram);
 
       // Build a graph lens while visiting the classes in the component.
       // TODO(b/190154391): Consider visiting the interfaces first, and then processing the
@@ -225,7 +236,7 @@
       stronglyConnectedProgramClassesWithDeterministicOrder.sort(
           Comparator.comparing(DexClass::getType));
       for (DexProgramClass clazz : stronglyConnectedProgramClassesWithDeterministicOrder) {
-        if (visitClass(clazz, partialGraphLensBuilder)) {
+        if (visitClass(clazz, interfaceDispatchOutsideProgram, partialGraphLensBuilder)) {
           affectedClassConsumer.accept(clazz);
         }
       }
@@ -276,7 +287,8 @@
     }
 
     private void computePrototypeChangesForVirtualMethods(
-        Set<DexProgramClass> stronglyConnectedProgramClasses) {
+        Set<DexProgramClass> stronglyConnectedProgramClasses,
+        DexMethodSignatureSet interfaceDispatchOutsideProgram) {
       // Group the virtual methods in the component by their signatures.
       Map<DexMethodSignature, ProgramMethodSet> virtualMethodsBySignature =
           computeVirtualMethodsBySignature(stronglyConnectedProgramClasses);
@@ -284,7 +296,9 @@
           (signature, methods) -> {
             // Check that there are no keep rules that prohibit prototype changes from any of the
             // methods.
-            if (Iterables.any(methods, method -> !isPrototypeChangesAllowed(method))) {
+            if (Iterables.any(
+                methods,
+                method -> !isPrototypeChangesAllowed(method, interfaceDispatchOutsideProgram))) {
               return;
             }
 
@@ -340,11 +354,13 @@
       return virtualMethodsBySignature;
     }
 
-    private boolean isPrototypeChangesAllowed(ProgramMethod method) {
+    private boolean isPrototypeChangesAllowed(
+        ProgramMethod method, DexMethodSignatureSet interfaceDispatchOutsideProgram) {
       return appView.getKeepInfo(method).isParameterRemovalAllowed(options)
           && !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
           && !appView.appInfo().isBootstrapMethod(method)
-          && !appView.appInfo().isMethodTargetedByInvokeDynamic(method);
+          && !appView.appInfo().isMethodTargetedByInvokeDynamic(method)
+          && !interfaceDispatchOutsideProgram.contains(method);
     }
 
     private SingleValue getReturnValueForVirtualMethods(
@@ -416,7 +432,9 @@
 
     // Returns true if the class was changed as a result of argument propagation.
     private boolean visitClass(
-        DexProgramClass clazz, ArgumentPropagatorGraphLens.Builder partialGraphLensBuilder) {
+        DexProgramClass clazz,
+        DexMethodSignatureSet interfaceDispatchOutsideProgram,
+        ArgumentPropagatorGraphLens.Builder partialGraphLensBuilder) {
       BooleanBox affected = new BooleanBox();
       DexMethodSignatureSet instanceInitializerSignatures = DexMethodSignatureSet.create();
       clazz.forEachProgramInstanceInitializer(instanceInitializerSignatures::add);
@@ -424,7 +442,8 @@
           method -> {
             RewrittenPrototypeDescription prototypeChanges =
                 method.getDefinition().belongsToDirectPool()
-                    ? computePrototypeChangesForDirectMethod(method, instanceInitializerSignatures)
+                    ? computePrototypeChangesForDirectMethod(
+                        method, interfaceDispatchOutsideProgram, instanceInitializerSignatures)
                     : computePrototypeChangesForVirtualMethod(method);
             DexMethod newMethodSignature = getNewMethodSignature(method, prototypeChanges);
             if (newMethodSignature != method.getReference()) {
@@ -518,9 +537,11 @@
     }
 
     private RewrittenPrototypeDescription computePrototypeChangesForDirectMethod(
-        ProgramMethod method, DexMethodSignatureSet instanceInitializerSignatures) {
+        ProgramMethod method,
+        DexMethodSignatureSet interfaceDispatchOutsideProgram,
+        DexMethodSignatureSet instanceInitializerSignatures) {
       assert method.getDefinition().belongsToDirectPool();
-      if (!isPrototypeChangesAllowed(method)) {
+      if (!isPrototypeChangesAllowed(method, interfaceDispatchOutsideProgram)) {
         return RewrittenPrototypeDescription.none();
       }
       // TODO(b/199864962): Allow parameter removal from check-not-null classified methods.
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
index e1e5f63..0b617b1 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexMethodSignature;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
 import com.android.tools.r8.graph.MethodResolutionResult;
@@ -43,12 +44,15 @@
   // methods) on the seen but not finished interfaces.
   final Map<DexProgramClass, MethodStateCollectionBySignature> methodStatesToPropagate =
       new IdentityHashMap<>();
+  final Consumer<DexMethodSignature> interfaceDispatchOutsideProgram;
 
   public InterfaceMethodArgumentPropagator(
       AppView<AppInfoWithLiveness> appView,
       ImmediateProgramSubtypingInfo immediateSubtypingInfo,
-      MethodStateCollectionByReference methodStates) {
+      MethodStateCollectionByReference methodStates,
+      Consumer<DexMethodSignature> interfaceDispatchOutsideProgram) {
     super(appView, immediateSubtypingInfo, methodStates);
+    this.interfaceDispatchOutsideProgram = interfaceDispatchOutsideProgram;
   }
 
   @Override
@@ -135,6 +139,12 @@
                     return;
                   }
 
+                  assert resolutionResult.isSingleResolution();
+                  if (!resolutionResult.getResolutionPair().isProgramMethod()) {
+                    interfaceDispatchOutsideProgram.accept(interfaceMethod);
+                    return;
+                  }
+
                   ProgramMethod resolvedMethod = resolutionResult.getResolvedProgramMethod();
                   if (resolvedMethod == null
                       || resolvedMethod.getHolder().isInterface()
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
index a0ac6e8..e1ee3cb 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
@@ -32,12 +32,11 @@
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
-public class RepackagingUseRegistry extends UseRegistry {
+public class RepackagingUseRegistry extends UseRegistry<ProgramDefinition> {
 
   private final AppInfoWithLiveness appInfo;
   private final InternalOptions options;
   private final RepackagingConstraintGraph constraintGraph;
-  private final ProgramDefinition context;
   private final InitClassLens initClassLens;
   private final RepackagingConstraintGraph.Node node;
   private final RepackagingConstraintGraph.Node missingTypeNode;
@@ -47,11 +46,10 @@
       RepackagingConstraintGraph constraintGraph,
       ProgramDefinition context,
       RepackagingConstraintGraph.Node missingTypeNode) {
-    super(appView.dexItemFactory());
+    super(context, appView.dexItemFactory());
     this.appInfo = appView.appInfo();
     this.options = appView.options();
     this.constraintGraph = constraintGraph;
-    this.context = context;
     this.initClassLens = appView.initClassLens();
     this.node = constraintGraph.getNode(context.getDefinition());
     this.missingTypeNode = missingTypeNode;
@@ -63,7 +61,7 @@
       return true;
     }
     if (accessFlags.isProtected()
-        && !appInfo.isSubtype(context.getContextType(), referencedClass.getType())) {
+        && !appInfo.isSubtype(getContext().getContextType(), referencedClass.getType())) {
       return true;
     }
     return false;
@@ -77,7 +75,7 @@
     }
     if (accessFlags.isProtected()) {
       if (!appInfo.isSubtype(
-          context.getContextType(), resolutionResult.getResolvedHolder().getType())) {
+          getContext().getContextType(), resolutionResult.getResolvedHolder().getType())) {
         return true;
       }
       // Check for assignability if we are generating CF:
@@ -85,7 +83,8 @@
       if (isInvoke
           && options.isGeneratingClassFiles()
           && !appInfo.isSubtype(
-              resolutionResult.getInitialResolutionHolder().getType(), context.getContextType())) {
+              resolutionResult.getInitialResolutionHolder().getType(),
+              getContext().getContextType())) {
         return true;
       }
     }
@@ -129,7 +128,7 @@
       MethodResolutionResult methodResult = resolutionResult.asMethodResolutionResult();
       if (methodResult.isClassNotFoundResult()
           || methodResult.isArrayCloneMethodResult()
-          || methodResult.isNoSuchMethodErrorResult(context.getContextClass(), appInfo)) {
+          || methodResult.isNoSuchMethodErrorResult(getContext().getContextClass(), appInfo)) {
         return;
       }
       node.addNeighbor(missingTypeNode);
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
index 60a6967..8cc522d 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
@@ -111,16 +111,7 @@
                         .rewritePosition(stackTraceContext.getRewritePosition())));
           }
         }
-        // Mapped ranges can have references to overloaded signatures. We distinguish those by
-        // looking at the cardinal mapping range.
-        for (MappedRange mappedRangeForPosition : mappedRangesForPosition) {
-          if (narrowedRanges.isEmpty()
-              || mappedRangeForPosition.originalRange == null
-              || !mappedRangeForPosition.originalRange.isCardinal) {
-            narrowedRanges.add(new Pair<>(mappedRange.getFirst(), new ArrayList<>()));
-          }
-          ListUtils.last(narrowedRanges).getSecond().add(mappedRangeForPosition);
-        }
+        narrowedRanges.add(new Pair<>(mappedRange.getFirst(), mappedRangesForPosition));
       }
     }
     return new RetraceFrameResultImpl(
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java
index e26e351..a50643a 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java
@@ -171,7 +171,7 @@
 
   // TODO(b/145731185): Extend support for identifiers with strings inside back ticks.
   private static final String javaIdentifierSegment =
-      "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*";
+      "\\p{javaJavaIdentifierStart}(?:-|\\p{javaJavaIdentifierPart})*";
 
   private static final String METHOD_NAME_REGULAR_EXPRESSION =
       "(?:(" + javaIdentifierSegment + "|\\<init\\>|\\<clinit\\>))";
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 25f01cf..d48a739 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -21,10 +21,9 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.ListIterator;
 
-public class DefaultEnqueuerUseRegistry extends UseRegistry {
+public class DefaultEnqueuerUseRegistry extends UseRegistry<ProgramMethod> {
 
   protected final AppView<? extends AppInfoWithClassHierarchy> appView;
-  private final ProgramMethod context;
   protected final Enqueuer enqueuer;
   private final AndroidApiLevelCompute computeApiLevel;
   private AndroidApiLevel maxApiReferenceLevel;
@@ -34,156 +33,151 @@
       ProgramMethod context,
       Enqueuer enqueuer,
       AndroidApiLevelCompute computeApiLevel) {
-    super(appView.dexItemFactory());
+    super(context, appView.dexItemFactory());
     this.appView = appView;
-    this.context = context;
     this.enqueuer = enqueuer;
     this.computeApiLevel = computeApiLevel;
     this.maxApiReferenceLevel = appView.options().minApiLevel;
   }
 
-  public ProgramMethod getContext() {
-    return context;
-  }
-
   public DexProgramClass getContextHolder() {
-    return context.getHolder();
+    return getContext().getHolder();
   }
 
   public DexEncodedMethod getContextMethod() {
-    return context.getDefinition();
+    return getContext().getDefinition();
   }
 
   @Override
   public void registerInitClass(DexType clazz) {
-    enqueuer.traceInitClass(clazz, context);
+    enqueuer.traceInitClass(clazz, getContext());
   }
 
   @Override
   public void registerInvokeVirtual(DexMethod invokedMethod) {
     setMaxApiReferenceLevel(invokedMethod);
-    enqueuer.traceInvokeVirtual(invokedMethod, context);
+    enqueuer.traceInvokeVirtual(invokedMethod, getContext());
   }
 
   @Override
   public void registerInvokeDirect(DexMethod invokedMethod) {
     setMaxApiReferenceLevel(invokedMethod);
-    enqueuer.traceInvokeDirect(invokedMethod, context);
+    enqueuer.traceInvokeDirect(invokedMethod, getContext());
   }
 
   @Override
   public void registerInvokeStatic(DexMethod invokedMethod) {
     setMaxApiReferenceLevel(invokedMethod);
-    enqueuer.traceInvokeStatic(invokedMethod, context);
+    enqueuer.traceInvokeStatic(invokedMethod, getContext());
   }
 
   @Override
   public void registerInvokeInterface(DexMethod invokedMethod) {
     setMaxApiReferenceLevel(invokedMethod);
-    enqueuer.traceInvokeInterface(invokedMethod, context);
+    enqueuer.traceInvokeInterface(invokedMethod, getContext());
   }
 
   @Override
   public void registerInvokeSuper(DexMethod invokedMethod) {
     setMaxApiReferenceLevel(invokedMethod);
-    enqueuer.traceInvokeSuper(invokedMethod, context);
+    enqueuer.traceInvokeSuper(invokedMethod, getContext());
   }
 
   @Override
   public void registerInstanceFieldRead(DexField field) {
     setMaxApiReferenceLevel(field);
-    enqueuer.traceInstanceFieldRead(field, context);
+    enqueuer.traceInstanceFieldRead(field, getContext());
   }
 
   @Override
   public void registerInstanceFieldReadFromMethodHandle(DexField field) {
     setMaxApiReferenceLevel(field);
-    enqueuer.traceInstanceFieldReadFromMethodHandle(field, context);
+    enqueuer.traceInstanceFieldReadFromMethodHandle(field, getContext());
   }
 
   @Override
   public void registerInstanceFieldWrite(DexField field) {
     setMaxApiReferenceLevel(field);
-    enqueuer.traceInstanceFieldWrite(field, context);
+    enqueuer.traceInstanceFieldWrite(field, getContext());
   }
 
   @Override
   public void registerInstanceFieldWriteFromMethodHandle(DexField field) {
     setMaxApiReferenceLevel(field);
-    enqueuer.traceInstanceFieldWriteFromMethodHandle(field, context);
+    enqueuer.traceInstanceFieldWriteFromMethodHandle(field, getContext());
   }
 
   @Override
   public void registerNewInstance(DexType type) {
     setMaxApiReferenceLevel(type);
-    enqueuer.traceNewInstance(type, context);
+    enqueuer.traceNewInstance(type, getContext());
   }
 
   @Override
   public void registerStaticFieldRead(DexField field) {
     setMaxApiReferenceLevel(field);
-    enqueuer.traceStaticFieldRead(field, context);
+    enqueuer.traceStaticFieldRead(field, getContext());
   }
 
   @Override
   public void registerStaticFieldReadFromMethodHandle(DexField field) {
     setMaxApiReferenceLevel(field);
-    enqueuer.traceStaticFieldReadFromMethodHandle(field, context);
+    enqueuer.traceStaticFieldReadFromMethodHandle(field, getContext());
   }
 
   @Override
   public void registerStaticFieldWrite(DexField field) {
     setMaxApiReferenceLevel(field);
-    enqueuer.traceStaticFieldWrite(field, context);
+    enqueuer.traceStaticFieldWrite(field, getContext());
   }
 
   @Override
   public void registerStaticFieldWriteFromMethodHandle(DexField field) {
     setMaxApiReferenceLevel(field);
-    enqueuer.traceStaticFieldWriteFromMethodHandle(field, context);
+    enqueuer.traceStaticFieldWriteFromMethodHandle(field, getContext());
   }
 
   @Override
   public void registerConstClass(
       DexType type, ListIterator<? extends CfOrDexInstruction> iterator) {
-    enqueuer.traceConstClass(type, context, iterator);
+    enqueuer.traceConstClass(type, getContext(), iterator);
   }
 
   @Override
   public void registerCheckCast(DexType type) {
-    enqueuer.traceCheckCast(type, context);
+    enqueuer.traceCheckCast(type, getContext());
   }
 
   @Override
   public void registerSafeCheckCast(DexType type) {
-    enqueuer.traceSafeCheckCast(type, context);
+    enqueuer.traceSafeCheckCast(type, getContext());
   }
 
   @Override
   public void registerTypeReference(DexType type) {
-    enqueuer.traceTypeReference(type, context);
+    enqueuer.traceTypeReference(type, getContext());
   }
 
   @Override
   public void registerInstanceOf(DexType type) {
-    enqueuer.traceInstanceOf(type, context);
+    enqueuer.traceInstanceOf(type, getContext());
   }
 
   @Override
   public void registerExceptionGuard(DexType guard) {
-    enqueuer.traceExceptionGuard(guard, context);
+    enqueuer.traceExceptionGuard(guard, getContext());
   }
 
   @Override
   public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
     super.registerMethodHandle(methodHandle, use);
-    enqueuer.traceMethodHandle(methodHandle, use, context);
+    enqueuer.traceMethodHandle(methodHandle, use, getContext());
   }
 
   @Override
   public void registerCallSite(DexCallSite callSite) {
     super.registerCallSite(callSite);
-    enqueuer.traceCallSite(callSite, context);
+    enqueuer.traceCallSite(callSite, getContext());
   }
 
   private void setMaxApiReferenceLevel(DexReference reference) {
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index ab19637..b8f2b8f 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -30,14 +29,12 @@
 public class MainDexDirectReferenceTracer {
   private final AnnotationDirectReferenceCollector annotationDirectReferenceCollector =
       new AnnotationDirectReferenceCollector();
-  private final DirectReferencesCollector codeDirectReferenceCollector;
 
   private final AppInfoWithClassHierarchy appInfo;
   private final Consumer<DexType> consumer;
 
   public MainDexDirectReferenceTracer(
       AppInfoWithClassHierarchy appInfo, Consumer<DexType> consumer) {
-    this.codeDirectReferenceCollector = new DirectReferencesCollector(appInfo.dexItemFactory());
     this.appInfo = appInfo;
     this.consumer = consumer;
   }
@@ -59,12 +56,12 @@
             traceMethodDirectDependencies(definition.getReference(), consumer);
             return definition.hasCode();
           },
-          method -> method.registerCodeReferences(codeDirectReferenceCollector));
+          this::runOnCode);
     }
   }
 
   public void runOnCode(ProgramMethod method) {
-    method.registerCodeReferences(codeDirectReferenceCollector);
+    method.registerCodeReferences(new DirectReferencesCollector(method));
   }
 
   public static boolean hasReferencesOutsideMainDexClasses(
@@ -102,10 +99,10 @@
     }
   }
 
-  private class DirectReferencesCollector extends UseRegistry {
+  private class DirectReferencesCollector extends UseRegistry<ProgramMethod> {
 
-    private DirectReferencesCollector(DexItemFactory factory) {
-      super(factory);
+    private DirectReferencesCollector(ProgramMethod context) {
+      super(context, appInfo.dexItemFactory());
     }
 
     @Override
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 8128db2..6ebd54b 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -52,6 +53,7 @@
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
 import com.android.tools.r8.graph.TreeFixerBase;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.graph.UseRegistryWithResult;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -440,19 +442,6 @@
       }
       return false;
     }
-    // Only merge if api reference level of source class is equal to target class.
-    if (appView.options().apiModelingOptions().enableApiCallerIdentification) {
-      AndroidApiLevel sourceApiLevel =
-          sourceClass.getApiReferenceLevel(appView, apiReferenceLevelCache::lookupMax);
-      AndroidApiLevel targetApiLevel =
-          targetClass.getApiReferenceLevel(appView, apiReferenceLevelCache::lookupMax);
-      if (sourceApiLevel != targetApiLevel) {
-        if (Log.ENABLED) {
-          AbortReason.API_REFERENCE_LEVEL.printLogMessageForClass(sourceClass);
-        }
-        return false;
-      }
-    }
     return true;
   }
 
@@ -518,6 +507,20 @@
       }
       return false;
     }
+    // Only merge if api reference level of source class is equal to target class. The check is
+    // somewhat expensive.
+    if (appView.options().apiModelingOptions().enableApiCallerIdentification) {
+      AndroidApiLevel sourceApiLevel =
+          sourceClass.getApiReferenceLevel(appView, apiReferenceLevelCache::lookupMax);
+      AndroidApiLevel targetApiLevel =
+          targetClass.getApiReferenceLevel(appView, apiReferenceLevelCache::lookupMax);
+      if (sourceApiLevel != targetApiLevel) {
+        if (Log.ENABLED) {
+          AbortReason.API_REFERENCE_LEVEL.printLogMessageForClass(sourceClass);
+        }
+        return false;
+      }
+    }
     return true;
   }
 
@@ -559,13 +562,13 @@
     // Check that all accesses from [source] to classes or members from the current package of
     // [source] will continue to work. This is guaranteed if the methods of [source] do not access
     // any private or protected classes or members from the current package of [source].
-    IllegalAccessDetector registry = new IllegalAccessDetector(appView, source);
     TraversalContinuation result =
         source.traverseProgramMethods(
             method -> {
-              registry.setContext(method);
-              method.registerCodeReferences(registry);
-              if (registry.foundIllegalAccess()) {
+              boolean foundIllegalAccess =
+                  method.registerCodeReferencesWithResult(
+                      new IllegalAccessDetector(appView, method));
+              if (foundIllegalAccess) {
                 return TraversalContinuation.BREAK;
               }
               return TraversalContinuation.CONTINUE;
@@ -1977,78 +1980,66 @@
 
   // Searches for a reference to a non-public class, field or method declared in the same package
   // as [source].
-  public static class IllegalAccessDetector extends UseRegistry {
-
-    private boolean foundIllegalAccess;
-    private ProgramMethod context;
+  public static class IllegalAccessDetector extends UseRegistryWithResult<Boolean, ProgramMethod> {
 
     private final AppView<? extends AppInfoWithClassHierarchy> appView;
-    private final DexClass source;
 
     public IllegalAccessDetector(
-        AppView<? extends AppInfoWithClassHierarchy> appView, DexClass source) {
-      super(appView.dexItemFactory());
+        AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
+      super(context, appView.dexItemFactory(), false);
       this.appView = appView;
-      this.source = source;
     }
 
-    public boolean foundIllegalAccess() {
-      return foundIllegalAccess;
-    }
+    private boolean checkFieldReference(DexField field) {
+      DexType baseType =
+          appView.graphLens().lookupType(field.holder.toBaseType(appView.dexItemFactory()));
+      if (baseType.isClassType() && baseType.isSamePackage(getContext().getHolderType())) {
+        if (checkTypeReference(field.holder) || checkTypeReference(field.type)) {
+          return true;
+        }
 
-    public void setContext(ProgramMethod context) {
-      this.context = context;
-    }
-
-    private void checkFieldReference(DexField field) {
-      if (!foundIllegalAccess) {
-        DexType baseType =
-            appView.graphLens().lookupType(field.holder.toBaseType(appView.dexItemFactory()));
-        if (baseType.isClassType() && baseType.isSamePackage(source.type)) {
-          checkTypeReference(field.holder);
-          checkTypeReference(field.type);
-
-          DexEncodedField definition = appView.appInfo().resolveField(field).getResolvedField();
-          if (definition == null || !definition.accessFlags.isPublic()) {
-            foundIllegalAccess = true;
-          }
+        DexEncodedField definition = appView.appInfo().resolveField(field).getResolvedField();
+        if (definition == null || !definition.accessFlags.isPublic()) {
+          setResult(true);
+          return true;
         }
       }
+      return false;
     }
 
-    private void checkMethodReference(DexMethod method, OptionalBool isInterface) {
-      if (!foundIllegalAccess) {
-        DexType baseType =
-            appView.graphLens().lookupType(method.holder.toBaseType(appView.dexItemFactory()));
-        if (baseType.isClassType() && baseType.isSamePackage(source.type)) {
-          checkTypeReference(method.holder);
-          checkTypeReference(method.proto.returnType);
-          for (DexType type : method.proto.parameters.values) {
-            checkTypeReference(type);
-          }
-          MethodResolutionResult resolutionResult =
-              isInterface.isUnknown()
-                  ? appView.appInfo().unsafeResolveMethodDueToDexFormat(method)
-                  : appView.appInfo().resolveMethod(method, isInterface.isTrue());
-          if (!resolutionResult.isSingleResolution()
-              || !resolutionResult.asSingleResolution().getResolvedMethod().isPublic()) {
-            foundIllegalAccess = true;
-          }
+    private boolean checkMethodReference(DexMethod method, OptionalBool isInterface) {
+      DexType baseType =
+          appView.graphLens().lookupType(method.holder.toBaseType(appView.dexItemFactory()));
+      if (baseType.isClassType() && baseType.isSamePackage(getContext().getHolderType())) {
+        if (checkTypeReference(method.holder)
+            || checkTypeReference(method.proto.returnType)
+            || Iterables.any(method.getParameters(), this::checkTypeReference)) {
+          return true;
+        }
+
+        MethodResolutionResult resolutionResult =
+            isInterface.isUnknown()
+                ? appView.appInfo().unsafeResolveMethodDueToDexFormat(method)
+                : appView.appInfo().resolveMethod(method, isInterface.isTrue());
+        if (!resolutionResult.isSingleResolution()
+            || !resolutionResult.asSingleResolution().getResolvedMethod().isPublic()) {
+          setResult(true);
+          return true;
         }
       }
+      return false;
     }
 
-    private void checkTypeReference(DexType type) {
-      if (!foundIllegalAccess) {
-        DexType baseType =
-            appView.graphLens().lookupType(type.toBaseType(appView.dexItemFactory()));
-        if (baseType.isClassType() && baseType.isSamePackage(source.type)) {
-          DexClass clazz = appView.definitionFor(baseType);
-          if (clazz == null || !clazz.accessFlags.isPublic()) {
-            foundIllegalAccess = true;
-          }
+    private boolean checkTypeReference(DexType type) {
+      DexType baseType = appView.graphLens().lookupType(type.toBaseType(appView.dexItemFactory()));
+      if (baseType.isClassType() && baseType.isSamePackage(getContext().getHolderType())) {
+        DexClass clazz = appView.definitionFor(baseType);
+        if (clazz == null || !clazz.accessFlags.isPublic()) {
+          setResult(true);
+          return true;
         }
       }
+      return false;
     }
 
     @Override
@@ -2058,41 +2049,36 @@
 
     @Override
     public void registerInvokeVirtual(DexMethod method) {
-      assert context != null;
       MethodLookupResult lookup =
-          appView.graphLens().lookupMethod(method, context.getReference(), Type.VIRTUAL);
+          appView.graphLens().lookupMethod(method, getContext().getReference(), Type.VIRTUAL);
       checkMethodReference(lookup.getReference(), OptionalBool.FALSE);
     }
 
     @Override
     public void registerInvokeDirect(DexMethod method) {
-      assert context != null;
       MethodLookupResult lookup =
-          appView.graphLens().lookupMethod(method, context.getReference(), Type.DIRECT);
+          appView.graphLens().lookupMethod(method, getContext().getReference(), Type.DIRECT);
       checkMethodReference(lookup.getReference(), OptionalBool.UNKNOWN);
     }
 
     @Override
     public void registerInvokeStatic(DexMethod method) {
-      assert context != null;
       MethodLookupResult lookup =
-          appView.graphLens().lookupMethod(method, context.getReference(), Type.STATIC);
+          appView.graphLens().lookupMethod(method, getContext().getReference(), Type.STATIC);
       checkMethodReference(lookup.getReference(), OptionalBool.UNKNOWN);
     }
 
     @Override
     public void registerInvokeInterface(DexMethod method) {
-      assert context != null;
       MethodLookupResult lookup =
-          appView.graphLens().lookupMethod(method, context.getReference(), Type.INTERFACE);
+          appView.graphLens().lookupMethod(method, getContext().getReference(), Type.INTERFACE);
       checkMethodReference(lookup.getReference(), OptionalBool.TRUE);
     }
 
     @Override
     public void registerInvokeSuper(DexMethod method) {
-      assert context != null;
       MethodLookupResult lookup =
-          appView.graphLens().lookupMethod(method, context.getReference(), Type.SUPER);
+          appView.graphLens().lookupMethod(method, getContext().getReference(), Type.SUPER);
       checkMethodReference(lookup.getReference(), OptionalBool.UNKNOWN);
     }
 
@@ -2184,7 +2170,7 @@
     }
 
     @Override
-    public Consumer<UseRegistry> getRegistryCallback() {
+    public Consumer<UseRegistry> getRegistryCallback(DexClassAndMethod method) {
       return registry -> {
         assert registry.getTraversalContinuation().shouldContinue();
         switch (type) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassDefinition.java
index a05d505..c641e9e 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassDefinition.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.structural.HasherWrapper;
 import com.android.tools.r8.utils.structural.RepresentativeMap;
-import com.google.common.hash.Hasher;
 
 /**
  * Definition of a synthetic classpath class.
@@ -44,7 +44,7 @@
   }
 
   @Override
-  void internalComputeHash(Hasher hasher, RepresentativeMap map) {
+  void internalComputeHash(HasherWrapper hasher, RepresentativeMap map) {
     clazz.hashWithTypeEquivalence(hasher, map);
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index 7c1decc..36c6573 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -8,10 +8,9 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.structural.HasherWrapper;
 import com.android.tools.r8.utils.structural.RepresentativeMap;
 import com.google.common.hash.HashCode;
-import com.google.common.hash.Hasher;
-import com.google.common.hash.Hashing;
 
 /**
  * Base type for the definition of a synthetic item.
@@ -70,7 +69,7 @@
       boolean intermediate,
       ClassToFeatureSplitMap classToFeatureSplitMap,
       SyntheticItems syntheticItems) {
-    Hasher hasher = Hashing.murmur3_128().newHasher();
+    HasherWrapper hasher = HasherWrapper.murmur3128Hasher();
     hasher.putInt(kind.id);
     if (getKind().isFixedSuffixSynthetic) {
       // Fixed synthetics are non-shareable. Its unique type is used as the hash key.
@@ -87,7 +86,7 @@
     return hasher.hash();
   }
 
-  abstract void internalComputeHash(Hasher hasher, RepresentativeMap map);
+  abstract void internalComputeHash(HasherWrapper hasher, RepresentativeMap map);
 
   final boolean isEquivalentTo(
       D other,
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index e407c3a..caf0f42 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -492,7 +492,9 @@
       // Recheck if it is present now the lock is held.
       dexClass = appView.definitionFor(type);
       if (dexClass != null) {
-        assert dexClass.isProgramClass();
+        if (!dexClass.isProgramClass()) {
+          errorOnInvalidSyntheticEnsure(dexClass, "program", appView);
+        }
         return dexClass.asProgramClass();
       }
       assert !isSyntheticClass(type);
@@ -625,17 +627,57 @@
     return new ProgramMethod(clazz, methodDefinition);
   }
 
-  public DexClasspathClass createFixedClasspathClass(
-      SyntheticKind kind, DexClasspathClass context, DexItemFactory factory) {
-    // Obtain the outer synthesizing context in the case the context itself is synthetic.
-    // This is to ensure a flat input-type -> synthetic-item mapping.
-    SynthesizingContext outerContext = SynthesizingContext.fromNonSyntheticInputContext(context);
-    DexType type = SyntheticNaming.createFixedType(kind, outerContext, factory);
-    SyntheticClasspathClassBuilder classBuilder =
-        new SyntheticClasspathClassBuilder(type, kind, outerContext, factory);
-    DexClasspathClass clazz = classBuilder.build();
-    addPendingDefinition(new SyntheticClasspathClassDefinition(kind, outerContext, clazz));
-    return clazz;
+  private void errorOnInvalidSyntheticEnsure(DexClass dexClass, String kind, AppView<?> appView) {
+    String classKind =
+        dexClass.isProgramClass()
+            ? "program"
+            : dexClass.isClasspathClass() ? "classpath" : "library";
+    throw appView
+        .reporter()
+        .fatalError(
+            "Cannot ensure "
+                + dexClass.type
+                + " as a synthetic "
+                + kind
+                + " class, because it is already a "
+                + classKind
+                + " class.");
+  }
+
+  private DexClasspathClass internalEnsureDexClasspathClass(
+      SyntheticKind kind,
+      Consumer<SyntheticClasspathClassBuilder> classConsumer,
+      Consumer<DexClasspathClass> onCreationConsumer,
+      SynthesizingContext outerContext,
+      DexType type,
+      AppView<?> appView) {
+    synchronized (type) {
+      DexClass clazz = appView.definitionFor(type);
+      if (clazz != null) {
+        if (!clazz.isClasspathClass()) {
+          errorOnInvalidSyntheticEnsure(clazz, "classpath", appView);
+        }
+        return clazz.asClasspathClass();
+      }
+      SyntheticClasspathClassBuilder classBuilder =
+          new SyntheticClasspathClassBuilder(type, kind, outerContext, appView.dexItemFactory());
+      classConsumer.accept(classBuilder);
+      DexClasspathClass definition = classBuilder.build();
+      addPendingDefinition(new SyntheticClasspathClassDefinition(kind, outerContext, definition));
+      onCreationConsumer.accept(definition);
+      return definition;
+    }
+  }
+
+  public DexClasspathClass ensureFixedClasspathClassFromType(
+      SyntheticKind kind,
+      DexType contextType,
+      AppView<?> appView,
+      Consumer<SyntheticClasspathClassBuilder> classConsumer) {
+    SynthesizingContext outerContext = SynthesizingContext.fromType(contextType);
+    DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
+    return internalEnsureDexClasspathClass(
+        kind, classConsumer, ignored -> {}, outerContext, type, appView);
   }
 
   public DexClasspathClass ensureFixedClasspathClass(
@@ -644,29 +686,12 @@
       AppView<?> appView,
       Consumer<SyntheticClasspathClassBuilder> classConsumer,
       Consumer<DexClasspathClass> onCreationConsumer) {
+    // Obtain the outer synthesizing context in the case the context itself is synthetic.
+    // This is to ensure a flat input-type -> synthetic-item mapping.
     SynthesizingContext outerContext = SynthesizingContext.fromNonSyntheticInputContext(context);
     DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
-    DexClass dexClass = appView.appInfo().definitionForWithoutExistenceAssert(type);
-    if (dexClass != null) {
-      assert dexClass.isClasspathClass();
-      return dexClass.asClasspathClass();
-    }
-    synchronized (context) {
-      dexClass = appView.appInfo().definitionForWithoutExistenceAssert(type);
-      if (dexClass != null) {
-        assert dexClass.isClasspathClass();
-        return dexClass.asClasspathClass();
-      }
-      // Obtain the outer synthesizing context in the case the context itself is synthetic.
-      // This is to ensure a flat input-type -> synthetic-item mapping.
-      SyntheticClasspathClassBuilder classBuilder =
-          new SyntheticClasspathClassBuilder(type, kind, outerContext, appView.dexItemFactory());
-      classConsumer.accept(classBuilder);
-      DexClasspathClass clazz = classBuilder.build();
-      addPendingDefinition(new SyntheticClasspathClassDefinition(kind, outerContext, clazz));
-      onCreationConsumer.accept(clazz);
-      return clazz;
-    }
+    return internalEnsureDexClasspathClass(
+        kind, classConsumer, onCreationConsumer, outerContext, type, appView);
   }
 
   public DexClassAndMethod ensureFixedClasspathClassMethod(
@@ -737,28 +762,6 @@
     return internalEnsureDexProgramClass(kind, fn, onCreationConsumer, outerContext, type, appView);
   }
 
-  public DexClasspathClass ensureFixedClasspathClassFromType(
-      SyntheticKind kind,
-      DexType contextType,
-      AppView<?> appView,
-      Consumer<SyntheticClasspathClassBuilder> fn) {
-    SynthesizingContext outerContext = SynthesizingContext.fromType(contextType);
-    DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
-    synchronized (contextType) {
-      DexClass clazz = appView.definitionFor(type);
-      if (clazz != null) {
-        assert clazz.isClasspathClass();
-        return clazz.asClasspathClass();
-      }
-      SyntheticClasspathClassBuilder classBuilder =
-          new SyntheticClasspathClassBuilder(type, kind, outerContext, appView.dexItemFactory());
-      fn.accept(classBuilder);
-      DexClasspathClass definition = classBuilder.build();
-      addPendingDefinition(new SyntheticClasspathClassDefinition(kind, outerContext, definition));
-      return definition;
-    }
-  }
-
   /** Create a single synthetic method item. */
   public ProgramMethod createMethod(
       SyntheticKind kind,
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
index a9d11c7..d00eb21 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
@@ -6,8 +6,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.structural.HasherWrapper;
 import com.android.tools.r8.utils.structural.RepresentativeMap;
-import com.google.common.hash.Hasher;
 import java.util.function.Consumer;
 
 /**
@@ -59,7 +59,7 @@
   }
 
   @Override
-  void internalComputeHash(Hasher hasher, RepresentativeMap map) {
+  void internalComputeHash(HasherWrapper hasher, RepresentativeMap map) {
     method.getDefinition().hashWithTypeEquivalence(hasher, map);
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassDefinition.java
index 3506dab..077bc76 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassDefinition.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.structural.HasherWrapper;
 import com.android.tools.r8.utils.structural.RepresentativeMap;
-import com.google.common.hash.Hasher;
 import java.util.function.Consumer;
 
 /**
@@ -54,7 +54,7 @@
   }
 
   @Override
-  void internalComputeHash(Hasher hasher, RepresentativeMap map) {
+  void internalComputeHash(HasherWrapper hasher, RepresentativeMap map) {
     clazz.hashWithTypeEquivalence(hasher, map);
   }
 
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
index 0a1ec66..a3700fe 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -283,17 +283,15 @@
           });
     }
 
-    class MethodUseCollector extends UseRegistry {
+    class MethodUseCollector extends UseRegistry<ProgramMethod> {
 
-      private final ProgramMethod context;
       private final GraphLens graphLens;
       private final InitClassLens initClassLens;
       private final DefinitionContext referencedFrom;
 
       public MethodUseCollector(
           ProgramMethod context, GraphLens graphLens, InitClassLens initClassLens) {
-        super(appInfo.dexItemFactory());
-        this.context = context;
+        super(context, appInfo.dexItemFactory());
         this.graphLens = graphLens;
         this.initClassLens = initClassLens;
         this.referencedFrom = DefinitionContextUtils.create(context);
@@ -303,7 +301,7 @@
 
       @Override
       public void registerInvokeDirect(DexMethod method) {
-        MethodLookupResult lookupResult = graphLens.lookupInvokeDirect(method, context);
+        MethodLookupResult lookupResult = graphLens.lookupInvokeDirect(method, getContext());
         assert lookupResult.getType().isDirect();
         DexMethod rewrittenMethod = lookupResult.getReference();
         DexClass holder = appInfo.definitionFor(rewrittenMethod.getHolderType());
@@ -313,14 +311,14 @@
 
       @Override
       public void registerInvokeInterface(DexMethod method) {
-        MethodLookupResult lookupResult = graphLens.lookupInvokeInterface(method, context);
+        MethodLookupResult lookupResult = graphLens.lookupInvokeInterface(method, getContext());
         assert lookupResult.getType().isInterface();
         handleInvokeWithDynamicDispatch(lookupResult);
       }
 
       @Override
       public void registerInvokeStatic(DexMethod method) {
-        MethodLookupResult lookupResult = graphLens.lookupInvokeStatic(method, context);
+        MethodLookupResult lookupResult = graphLens.lookupInvokeStatic(method, getContext());
         assert lookupResult.getType().isStatic();
         DexMethod rewrittenMethod = lookupResult.getReference();
         handleRewrittenMethodResolution(
@@ -329,7 +327,7 @@
 
       @Override
       public void registerInvokeSuper(DexMethod method) {
-        MethodLookupResult lookupResult = graphLens.lookupInvokeSuper(method, context);
+        MethodLookupResult lookupResult = graphLens.lookupInvokeSuper(method, getContext());
         assert lookupResult.getType().isSuper();
         DexMethod rewrittenMethod = lookupResult.getReference();
         MethodResolutionResult resolutionResult =
@@ -341,12 +339,12 @@
         }
         handleRewrittenMethodReference(
             rewrittenMethod,
-            resolutionResult.lookupInvokeSuperTarget(context.getHolder(), appInfo));
+            resolutionResult.lookupInvokeSuperTarget(getContext().getHolder(), appInfo));
       }
 
       @Override
       public void registerInvokeVirtual(DexMethod method) {
-        MethodLookupResult lookupResult = graphLens.lookupInvokeVirtual(method, context);
+        MethodLookupResult lookupResult = graphLens.lookupInvokeVirtual(method, getContext());
         assert lookupResult.getType().isVirtual();
         handleInvokeWithDynamicDispatch(lookupResult);
       }
@@ -489,7 +487,7 @@
 
         // For lambdas that implement an interface, also keep the interface method by simulating an
         // invoke to it from the current context.
-        LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo, context);
+        LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo, getContext());
         if (descriptor != null) {
           for (DexType interfaceType : descriptor.interfaces) {
             DexClass interfaceDefinition = appInfo.definitionFor(interfaceType);
diff --git a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
index 91b0638..6af263f 100644
--- a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
@@ -5,7 +5,9 @@
 package com.android.tools.r8.utils;
 
 import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -24,4 +26,10 @@
       collection.forEach(consumer);
     }
   }
+
+  public static <T> Collection<T> sort(Collection<T> items, Comparator<T> comparator) {
+    ArrayList<T> ts = new ArrayList<>(items);
+    ts.sort(comparator);
+    return ts;
+  }
 }
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 9449f1b..9d2657a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -11,10 +11,11 @@
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DumpOptions;
 import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.MapIdProvider;
 import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.Version;
-import com.android.tools.r8.androidapi.AndroidApiClass;
+import com.android.tools.r8.androidapi.AndroidApiForHashingClass;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Backend;
@@ -33,6 +34,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexLibraryClass;
@@ -85,12 +87,10 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -281,6 +281,7 @@
   public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
   public boolean cfToCfDesugar = false;
   public boolean forceAnnotateSynthetics = false;
+  public boolean readDebugSetFileEvent = false;
 
   public int callGraphLikelySpuriousCallEdgeThreshold = 50;
 
@@ -866,6 +867,8 @@
 
   public Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer = null;
 
+  public MapIdProvider mapIdProvider = null;
+
   public static boolean assertionsEnabled() {
     boolean assertionsEnabled = false;
     assert assertionsEnabled = true; // Intentional side-effect.
@@ -1370,8 +1373,10 @@
 
     public boolean enableApiCallerIdentification = false;
     public boolean checkAllApiReferencesAreSet = true;
+    public boolean useHashingDatabase = true;
 
-    public void visitMockedApiReferences(BiConsumer<ClassReference, AndroidApiClass> consumer) {
+    public void visitMockedApiLevelsForReferences(
+        DexItemFactory factory, Consumer<AndroidApiForHashingClass> consumer) {
       if (methodApiMapping.isEmpty() && fieldApiMapping.isEmpty() && classApiMapping.isEmpty()) {
         return;
       }
@@ -1385,62 +1390,37 @@
       classReferences.forEach(
           classReference -> {
             consumer.accept(
-                classReference,
-                new AndroidApiClass(classReference) {
+                new AndroidApiForHashingClass() {
+                  @Override
+                  public DexType getType() {
+                    return factory.createType(classReference.getDescriptor());
+                  }
+
                   @Override
                   public AndroidApiLevel getApiLevel() {
-                    return classApiMapping.getOrDefault(classReference, AndroidApiLevel.B);
+                    return classApiMapping.getOrDefault(classReference, AndroidApiLevel.UNKNOWN);
                   }
 
                   @Override
-                  public int getMemberCount() {
-                    return 0;
+                  public void visitMethodsWithApiLevels(
+                      BiConsumer<DexMethod, AndroidApiLevel> consumer) {
+                    methodApiMapping.forEach(
+                        (methodReference, apiLevel) -> {
+                          if (methodReference.getHolderClass().equals(classReference)) {
+                            consumer.accept(factory.createMethod(methodReference), apiLevel);
+                          }
+                        });
                   }
 
                   @Override
-                  public TraversalContinuation visitFields(
-                      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor) {
-                    for (Entry<FieldReference, AndroidApiLevel> entry :
-                        fieldApiMapping.entrySet()) {
-                      if (!entry.getKey().getHolderClass().equals(classReference)) {
-                        continue;
-                      }
-                      if (EntryUtils.accept(entry, visitor).shouldBreak()) {
-                        return TraversalContinuation.BREAK;
-                      }
-                    }
-                    return TraversalContinuation.CONTINUE;
-                  }
-
-                  @Override
-                  public TraversalContinuation visitMethods(
-                      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor) {
-                    for (Entry<MethodReference, AndroidApiLevel> entry :
-                        methodApiMapping.entrySet()) {
-                      if (!entry.getKey().getHolderClass().equals(classReference)) {
-                        continue;
-                      }
-                      if (EntryUtils.accept(entry, visitor).shouldBreak()) {
-                        return TraversalContinuation.BREAK;
-                      }
-                    }
-                    return TraversalContinuation.CONTINUE;
-                  }
-
-                  @Override
-                  protected TraversalContinuation visitFields(
-                      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor,
-                      ClassReference holder,
-                      int minApi) {
-                    return null;
-                  }
-
-                  @Override
-                  protected TraversalContinuation visitMethods(
-                      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor,
-                      ClassReference holder,
-                      int minApi) {
-                    return null;
+                  public void visitFieldsWithApiLevels(
+                      BiConsumer<DexField, AndroidApiLevel> consumer) {
+                    fieldApiMapping.forEach(
+                        (fieldReference, apiLevel) -> {
+                          if (fieldReference.getHolderClass().equals(classReference)) {
+                            consumer.accept(factory.createField(fieldReference), apiLevel);
+                          }
+                        });
                   }
                 });
           });
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
index 7687a88..911290b 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
@@ -4,7 +4,10 @@
 
 package com.android.tools.r8.utils.collections;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
@@ -13,6 +16,7 @@
 import com.android.tools.r8.utils.SetUtils;
 import java.util.Set;
 import java.util.function.IntFunction;
+import java.util.function.Predicate;
 
 public class LongLivedProgramMethodSetBuilder<T extends ProgramMethodSet> {
 
@@ -98,15 +102,37 @@
     return this;
   }
 
+  @Deprecated
   public void remove(DexMethod method) {
     methods.remove(method);
   }
 
+  public void remove(DexMethod method, GraphLens currentGraphLens) {
+    assert isEmpty() || verifyIsRewrittenWithLens(currentGraphLens);
+    methods.remove(method);
+  }
+
   public LongLivedProgramMethodSetBuilder<T> removeAll(Iterable<DexMethod> methods) {
     methods.forEach(this::remove);
     return this;
   }
 
+  public LongLivedProgramMethodSetBuilder<T> removeIf(
+      DexDefinitionSupplier definitions, Predicate<ProgramMethod> predicate) {
+    methods.removeIf(
+        method -> {
+          DexProgramClass holder =
+              asProgramClassOrNull(definitions.definitionFor(method.getHolderType()));
+          ProgramMethod definition = method.lookupOnProgramClass(holder);
+          if (definition == null) {
+            assert false;
+            return true;
+          }
+          return predicate.test(definition);
+        });
+    return this;
+  }
+
   public LongLivedProgramMethodSetBuilder<T> rewrittenWithLens(
       AppView<AppInfoWithLiveness> appView) {
     return rewrittenWithLens(appView.graphLens());
diff --git a/src/main/java/com/android/tools/r8/utils/structural/DefaultHashingVisitor.java b/src/main/java/com/android/tools/r8/utils/structural/DefaultHashingVisitor.java
index d1b6653..2062478 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/DefaultHashingVisitor.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/DefaultHashingVisitor.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.utils.structural;
 
 import com.android.tools.r8.utils.structural.StructuralItem.HashingAccept;
-import com.google.common.hash.Hasher;
 
 /**
  * Default visitor for hashing a structural item.
@@ -14,11 +13,11 @@
  */
 public class DefaultHashingVisitor {
 
-  public static <T> void run(T item, Hasher hasher, StructuralMapping<T> accept) {
+  public static <T> void run(T item, HasherWrapper hasher, StructuralMapping<T> accept) {
     run(item, hasher, (i, visitor) -> visitor.visit(i, accept));
   }
 
-  public static <T> void run(T item, Hasher hasher, HashingAccept<T> hashingAccept) {
+  public static <T> void run(T item, HasherWrapper hasher, HashingAccept<T> hashingAccept) {
     HashingVisitorWithTypeEquivalence.run(item, hasher, t -> t, hashingAccept);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/structural/HasherWrapper.java b/src/main/java/com/android/tools/r8/utils/structural/HasherWrapper.java
new file mode 100644
index 0000000..8427cfe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/structural/HasherWrapper.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2021, 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.structural;
+
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+
+/**
+ * This is an interface that mimics the Hasher interface in Guava which allows us to use hashing in
+ * the tests.
+ */
+public interface HasherWrapper {
+
+  void putBoolean(boolean value);
+
+  void putInt(int value);
+
+  void putFloat(float value);
+
+  void putLong(long value);
+
+  void putDouble(double value);
+
+  void putBytes(byte[] content);
+
+  String hashCodeAsString();
+
+  <T> T hash();
+
+  static HasherWrapper sha256Hasher() {
+    return new HasherWrapped(Hashing.sha256().newHasher());
+  }
+
+  static HasherWrapper murmur3128Hasher() {
+    return new HasherWrapped(Hashing.murmur3_128().newHasher());
+  }
+
+  class HasherWrapped implements HasherWrapper {
+
+    private final Hasher hasher;
+
+    public HasherWrapped(Hasher hasher) {
+      this.hasher = hasher;
+    }
+
+    @Override
+    public void putBoolean(boolean value) {
+      hasher.putBoolean(value);
+    }
+
+    @Override
+    public void putInt(int value) {
+      hasher.putInt(value);
+    }
+
+    @Override
+    public void putFloat(float value) {
+      hasher.putFloat(value);
+    }
+
+    @Override
+    public void putLong(long value) {
+      hasher.putLong(value);
+    }
+
+    @Override
+    public void putDouble(double value) {
+      hasher.putDouble(value);
+    }
+
+    @Override
+    public void putBytes(byte[] content) {
+      hasher.putBytes(content);
+    }
+
+    @Override
+    public <T> T hash() {
+      return (T) hasher.hash();
+    }
+
+    @Override
+    public String hashCodeAsString() {
+      return hasher.hash().toString();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitor.java b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitor.java
index 696c454..538ddcd 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitor.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitor.java
@@ -9,11 +9,9 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.structural.StructuralItem.HashingAccept;
-import com.google.common.hash.Hasher;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
-import java.util.function.BiConsumer;
 
 public abstract class HashingVisitor {
 
@@ -55,7 +53,4 @@
   }
 
   public abstract <S> void visit(S item, StructuralMapping<S> accept);
-
-  @Deprecated
-  public abstract <S> void visit(S item, BiConsumer<S, Hasher> hasher);
 }
diff --git a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
index ff0e4ba..303f212 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
@@ -8,9 +8,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.structural.StructuralItem.CompareToAccept;
 import com.android.tools.r8.utils.structural.StructuralItem.HashingAccept;
-import com.google.common.hash.Hasher;
 import java.util.Iterator;
-import java.util.function.BiConsumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.ToDoubleFunction;
@@ -21,19 +19,19 @@
 public class HashingVisitorWithTypeEquivalence extends HashingVisitor {
 
   public static <T> void run(
-      T item, Hasher hasher, RepresentativeMap map, StructuralMapping<T> accept) {
+      T item, HasherWrapper hasher, RepresentativeMap map, StructuralMapping<T> accept) {
     run(item, hasher, map, (i, visitor) -> visitor.visit(i, accept));
   }
 
   public static <T> void run(
-      T item, Hasher hasher, RepresentativeMap map, HashingAccept<T> hashingAccept) {
+      T item, HasherWrapper hasher, RepresentativeMap map, HashingAccept<T> hashingAccept) {
     hashingAccept.acceptHashing(item, new HashingVisitorWithTypeEquivalence(hasher, map));
   }
 
-  private final Hasher hash;
+  private final HasherWrapper hash;
   private final RepresentativeMap representatives;
 
-  private HashingVisitorWithTypeEquivalence(Hasher hash, RepresentativeMap representatives) {
+  private HashingVisitorWithTypeEquivalence(HasherWrapper hash, RepresentativeMap representatives) {
     this.hash = hash;
     this.representatives = representatives;
   }
@@ -85,11 +83,6 @@
     }
   }
 
-  @Override
-  public <S> void visit(S item, BiConsumer<S, Hasher> hasher) {
-    hasher.accept(item, hash);
-  }
-
   private static class ItemSpecification<T>
       extends StructuralSpecification<T, ItemSpecification<T>> {
 
diff --git a/src/main/java/com/android/tools/r8/utils/structural/StructuralItem.java b/src/main/java/com/android/tools/r8/utils/structural/StructuralItem.java
index 8740299..057a18c 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/StructuralItem.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/StructuralItem.java
@@ -3,9 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.structural;
 
-import com.google.common.hash.Hasher;
-import com.google.common.hash.Hashing;
-
 /** Specified types must implement methods to determine equality, hashing and order. */
 public interface StructuralItem<T extends StructuralItem<T>> extends Ordered<T> {
 
@@ -60,24 +57,17 @@
    * <p>This should *not* be overwritten, instead items should overwrite acceptHashing which will
    * ensure that the effect is in place for any HashingVisitor.
    */
-  default void hash(Hasher hasher) {
+  default void hash(HasherWrapper hasher) {
     DefaultHashingVisitor.run(self(), hasher, StructuralItem::acceptHashing);
   }
 
-  /** Hashing method to use from tests to avoid having guava types shared between R8 and tests. */
-  default String hashForTesting() {
-    Hasher hasher = Hashing.sha256().newHasher();
-    hash(hasher);
-    return hasher.hash().toString();
-  }
-
   /**
    * Implementation of the default hashing with a type equivalence on the item.
    *
    * <p>This should *not* be overwritten, instead items should overwrite acceptHashing which will
    * ensure that the effect is in place for any HashingVisitor.
    */
-  default void hashWithTypeEquivalence(Hasher hasher, RepresentativeMap map) {
+  default void hashWithTypeEquivalence(HasherWrapper hasher, RepresentativeMap map) {
     HashingVisitorWithTypeEquivalence.run(self(), hasher, map, StructuralItem::acceptHashing);
   }
 
diff --git a/src/main/resources/api_database/api_database_ambiguous.txt b/src/main/resources/api_database/api_database_ambiguous.txt
new file mode 100644
index 0000000..3ffb464
--- /dev/null
+++ b/src/main/resources/api_database/api_database_ambiguous.txt
@@ -0,0 +1,473 @@
+828623182530e5b5c55265b6a20263be:9
+850665aac5116ea7c5dd3ed4d672cc68:1
+d853b59bd420a046a715750d924360d0:1
+1a9e947630aeed44da4b6e668e8ac214:1
+d96d3dfd316fb498b8989986135bb848:1
+281b8e41d83f2962772157285ee84c38:1
+943b393cd0aa3d44e2609c7a7008e36f:1
+b1f7571f9f4648ce69a9e79455d3df8a:1
+22def9631f444a8421104a4c6fd26845:1
+d448211aa9d792c542c8a12499062780:1
+843e12eb3c7a2c2c7c2d1b64058d619e:1
+6fa6dc2897ec7eaec581306eb1cbacae:1
+1b6414136749889f64248ad232774cd4:1
+aeb599339c06ae54c794c71be6b2b1f0:1
+9a57214d87842677a74f893161a4b9be:1
+aa69312c004e15f4fa8efce471b0cfc1:1
+619d04422f81707ae4354cb9c8b41ed5:1
+8fe152aee223813be38a1103ba72c66e:1
+e19b30d13798c227510c8fb9d5ca20ec:1
+c892a71c9b51dff23b4b6d4d78c46ca0:1
+97d4f84cdaf7386e47d1c8bea51a45b5:1
+e1ed03e2c2a9fd3a5866fb3771530dc9:1
+2efc71cc840f54a63d8ce5ff313a7e2f:1
+e367ef6e11b586255cbde00200814031:1
+e1863e976cd3e82c00e43304db807943:1
+3fb35789d4aee6489350c670908bd59e:1
+e63feec2ab98295f4cd1f09d21e35ce9:1
+f081ee3e1cb0dec48d0127716393edec:1
+1db593f23925e8dc2fb326b2a368335c:1
+089be95001c81698ed7a1ae6eadb480b:1
+62e65d09c285da8da8dbecff2a85eb5e:1
+466714572719ed5745a5b5cd886c880b:1
+080573d2f4292d0789f942bd39af7594:1
+449e3c63d3565b07e6e39ae06b36e9a1:1
+96ca46ef9f7f9ed890508a01899764eb:1
+603e49aeac3c895300c860d6bd596135:1
+0de08429c1f0d8c9804107e8dac151f0:1
+aba1819af7abfe17006adcf74c572eab:1
+a1061d47d7a301608f4f7b396bb81ee3:1
+cf60514a6e7b90495f5a47161478a33f:1
+e8890acb1c71aa0076dffdb89dff32e3:1
+987a12e7a344e556d9544c953d62ae8b:1
+8f28909290b786920a4b9fb635ba6677:1
+a0d0240922deab6b46a7ba1509aa3168:1
+d9eb1dbdb92903db5b787e2e70e34c39:1
+9c45ce862efc619f1cca962c8caeb844:1
+0ef069b7a04f779edb4eddc5d9e68664:1
+e04bb82da6212e6b9993e2f2177d7b34:1
+e97bced61f81e5e6486cabccafd486b9:1
+c2d6153e0b11fa485a1db9b895d7ca4f:1
+4a3f92803a9af2349140ee2d20d4016f:1
+45c104bb4202da85198b615491d1de23:1
+c841eae393bc4c731d85b057b263effa:1
+641c008d5e49914df8db013a5bb050e7:1
+00217564c415ce5c03abf66b6863f30a:1
+a98b9be4a37764162034e849091e2f2a:1
+7275881c062d5fa141b8beef6d60f05b:1
+36779dd0e73608472e1c22fca2914eba:1
+ddda7f2d9e16b789b36d9235d1a4b52a:1
+1e39ef60367e236fa336f96705b2b10f:1
+5b17c7c02bc7ab47a30ea2856738af8c:1
+9963627e831a6315767f99893ca660c1:1
+43baf0e2fd2a825c22e70c4f589441df:1
+eceb74f53e3044ee33a39ed0b6c9cc73:1
+769d9f4e5046a4e0a480b614eb530cb1:1
+844cdb718520e79a957bd880c6e046b5:1
+f25a0a19ee375dbe75922bf1817f37de:1
+fe40c696eb0567e090d8968b387100e4:1
+885d4879046a451b2330409a9c316a86:1
+ebb652ecc8bc2f4a2d616a8294624e1d:1
+60c0494c6547c2aa37081b0a045f2dbb:1
+d97969716904e1a32e4ce6c4de736fac:1
+c12acdd4d8e17d803e32eb32757be4e9:1
+83255b102f7327ffe0819cc9d48d764a:1
+a159e58a81a5d7e46b1d44f845486c25:1
+c9a7fe526ed238e56f029bdd9eadd4a8:1
+a5352e6f5efd89293cad68bde5359174:21
+0873c171286bf72adca073b8232909ad:21
+75fcc63a7299ec405cbbbae6c6f4124a:21
+4242f75bd579b5860e448896bd97161e:21
+f8e3f0c3200fb1dd44acfbd301d96bed:21
+3e972f384811f77fd62ce384910c7605:21
+746752a0385785d6c4a6af907b506ff8:21
+f2c67cccefcb8826a54b39122e002062:21
+e40adee8247464101fcb01d083025c31:21
+d96183e4ed9b9b2b075bd5f8c4364100:21
+0a8ab6673c7b4e87cb2faf2d7005362a:21
+810a68c672694a924abe0f0c59a9a394:21
+fe284dc96dc744beee1ddbb1a8e4c4ee:21
+d34d323d00595f4051cfe0621331317d:21
+711a52bc9e3eafe73e0c64c4ae6d5e6a:21
+542566d5040f1a84a95caf3c78e8d54f:21
+fadc0a0d51e6ccdc60992c058edd2711:21
+ccb12040fbbda66de180c11e6251049d:21
+052dc8ce89610356ae4c22dacb2668f6:21
+eb911a19cc8e2139a72a6bf025f0ef92:21
+8a9b11e0d4a3993f76485c069942489c:21
+b50ba38de9db0ecba8a7618f6138380b:21
+5a50c9965f9cd4ee5a321bbd530cc5b2:21
+301f4d37021f2c80999eedb2219554bd:21
+391832837fd0c5bd42b6d5d58dcd80f6:21
+535462c1df188932ba4375a241ac0a3b:21
+517cb4d902b3c235f31b41bcd012b184:21
+dd9d0f0182859fbb91bfe9cb3504becf:21
+4634339ec4a3dfef9d6a8a22e18c1354:21
+4015e477f56bfed839fc88a1ba749750:21
+be73d1320eb5c1229bc65e73f82039f3:21
+1c2ab72e50012527678e434999c9044a:21
+a09c3b287f292693c49b6ac6139f3aca:21
+67ce8616e53660f5898eaa146c7f3835:21
+5b728323f7668ae4ff0e895afaf15cea:21
+b070068bc98b0eafc27ab9bb38e7cfbb:21
+eadaf0fe065d92924ebd1cade7c57e35:21
+724567eb08f8a1a1dba97c82bd0a8d00:21
+06065f8f4550b0f3530468ae56e4437b:21
+eea2af8f17a87b391353b3878325bbd1:21
+aa462a0178a7561e7c756b157cbeeb38:21
+3af411c682f8264bb7c70794211c6248:21
+b972f761c2e0edfca60661ae2abf0218:21
+da13bac632b4f32422190d415f31bab8:21
+a7c7530bd46ed4e558e4d448f1ab9b7c:21
+e2c48e8e5e90a5dcc4cd070af2c1b20e:21
+0fc8da4c46369ca9dbcee59fb490681c:21
+5bb64f5d5c0764c9c1076e2c00ee3b3c:21
+f92f02539108f4c7034055e630ce5b1b:21
+21206168597de4cc003cf94bb2476441:21
+80ffcde01a4a187455c15c7b11ae7c33:21
+5adbafe8f34e077daeacc70305eed4f1:21
+d7d607c796c81475b0c57f2646b7e662:21
+4f89f7e86a6cb4ab329ea69918ae6f49:21
+0f88600ff268e2f32d56c63ce955aeba:21
+aeb824437b0a504c4a1a3109729a8817:21
+bd00bb3c1478ff33a781c1b48bfa9112:21
+eabb0f12ea319b61bdf53d9f4a6d7044:21
+b5653063b6061db848ef666076d4a44b:21
+1bf6b15eb573dea85120bcc84f419023:21
+71ae94c8959d23501b3a25269ed58f50:21
+c0673d423ec86b469092b2eedbf4e8db:21
+52986dd229c1677a8a8fea05023f7625:21
+364cf0eb88afb77fbc7209d2c1c7273e:21
+072b20ca146065654d29d72fa24fc89a:21
+50ddd3dc05b1198214cb57e9250d7076:21
+3eb1459c5b79cc1a562b145846a1b957:21
+d84c006146e1a54bba7a7122983ef085:21
+3a16ed65c968daf194834ce7a0225b8a:21
+6321e1881f171dc79e50d8e0cbc6ce7d:21
+5800fc71343c619b5e005bbab60912e0:21
+8e087b8bbcd92fe9389bf2f469f383f6:21
+e7e98eb2a51cc3cbf30bd532d8942294:21
+c173773ee22e94ab734c05bd504d3325:21
+5bef782b74608c4de25f4d053ae543a9:21
+ba7c51aed579c0f1b473ef2035b15a17:21
+169dfedc30ae5b95af848f3184206b4b:21
+549d9811413f0c4b84cd1e84290127f1:21
+668fa5f927950cbb34952cdb7e631eb8:21
+580fe13c6b95c05247c1770fff6d0fe9:21
+02d6383509108efe665de4a2aff24702:21
+68749591b1210c540ab306d416f49c68:21
+75269722fb78a1b61a850da9a73984fc:21
+58c819dd55c2558fed93faef88f45e89:21
+e479c8a303ccdaf45f673c78971d6a44:21
+cd94f79ea296e6126eb563a6e82a73b3:21
+ab652b1e99797dd3cc3bfb7e48db5d18:21
+4d3ab1362ff964f3947b437b16aba576:21
+53dea4d4defbb7512a31f4fa659437ae:21
+252e68c63ccea47b1d02a47837383c4e:21
+a9fff0b0f85b598b277aaf29fe537ad8:21
+caf732f52661f4c573b342a3d1da85bc:21
+9401285d5c41e4e0ee4581c62f4a2fc4:21
+5d0c7f0ad2c60ef78ae47f854b90a194:21
+983db0abe1d8976946a8a19eee412c9d:21
+e7ca6d1abe4cd55a491cdf58eb8f4602:17
+a4fc9c89f980d489cd69b5a9a5379279:28
+b63216a8584c12bf002f4ee8c3b0ac3e:19
+1ad1cfa76f9692958656f17b710a0ecc:19
+ebf47efadc4c50002b13da0b1cef0646:18
+106de6ca4d0e49988f3328ce380ff517:18
+2f4aedd17528c30f8a9f16b030ff708b:18
+93aca156e3f15799914f868852723e47:18
+17cbc97604a781acb78c668b2437ffbd:18
+1709449e191fe131236bd0b8c1928b71:18
+1587f690fd5c0c886630334e89e397ff:18
+a6cf882fc575147513b7b1b505fdd335:18
+dc6ad4bf6892204b9fca73d1ee9cab04:18
+9ae673cfbb5d90688edafbf7ec2862e4:18
+acf250fcdbf54725219af8c6a8a614d9:18
+07319a58381faa784dda046d5e8fd8ae:18
+77e978f6318d01be894de8944fdc05b4:18
+81be9a78d26d25c6ff47c7e9af30a92e:18
+e87bf9d0084b39c443e421cdb5d3fc43:18
+9e973ee7f4f5ad967d2ba5bf5d88911c:18
+b47e7bbf50ad208599056f8d0f1cc5ba:18
+47a9e25289b4ece230adcc3b60797712:18
+42e4cc1882d334757afbe74f4d8cec8e:18
+024481748991b858bf7e03b0b87ce125:18
+b6ced7e04b527d83cd660894a0059ea7:18
+717e536c3a13f3602fbaf1f607ee2963:18
+18b540e31006aad6ae7a26d967ec0e0e:18
+f1483fe126d64bd7544dba7fdf0559f4:18
+3c4a0d8270a52e43316a711a2fe83268:18
+2e13bb1bfba423473a6efba65d9c8190:18
+47a5d9010bdeb561176cf070ef483e91:18
+4f2e7d32ed2c7ead316770fbe84d8aae:18
+c625e1090fc4d72afd0673839af5d46f:18
+4cc2dc623ee702db7a5899c5c401a98c:18
+eff51a62305642c720d163085061ed63:18
+780c7e0f80b5479d00e2c3679974644d:18
+69e416f1afc250d6239788b4b1fb9bb4:18
+6d60d78c1ce897131dcf45743a8d6e49:18
+e16e8dcca650cf79557cb76a86edb4e9:18
+d71a32697989404ae782e105a5eb5e4c:18
+bfebe4871ed9c4b5f3300fe8dfa2a0a3:18
+93e215f230901f20bdca460cfe8c42af:18
+8efacada80287ebb7607758a0a82b0a0:18
+487bc1228fd817b641056a1a008984f8:18
+dc6c2915cd6f0a6fd0ab01365349599d:18
+1a3dbf72e4bd135a621b8ad78a7151c1:18
+01fd60d48ba7e1434bb72696282c7113:18
+83c42b475763d64935a9ddfc6a374b64:18
+ed9a26aaf7ddb748bd1e624aeb4fdb81:18
+eeb05b41976f7bb96572330854aaab34:18
+3c17437ba337e7ce8edad092f745a363:18
+8d8fd1ffc3ae24cb43798f4eb41fc4de:18
+003f7ab7088710994a289bab815aacbf:18
+488d943cdcedfca2b076152769fc3c51:18
+cab779407e29ba3377e6a47a4f842ec5:18
+a0ddcf0f7bb4aa9d38db983941f6908d:18
+b64b41d26010783cae2dff88c7e21f1d:18
+aac6d054ad0f9bea1f64702aea698ee3:18
+90b4163293f9e35f7091692b31d3b5d7:18
+9d66fceb1054d5e2ff207591ec216b70:18
+93a65566b576af220207e30cc89ef490:18
+b3248e6a2d819bd8bb42a15179188c52:18
+f95f0545feb040d2d6bfcad6897364d0:18
+058ec3044e95ed7fcaff93a2f3105d6f:18
+39ef3516cb80752cac1c541b43a1c2fa:18
+c4b4a2994711cb342b0c8c8ef409ce8b:18
+719c3b73816fa0db79747ea264abab5a:18
+c1295d79d79340152c6b98f1c9003e8c:18
+1211e656a8eaa4423e7678533a1e7e2a:18
+1d7847b88e0feaecd299b3c5c18be763:18
+e0c7417a74df21b55367f9e1ea6c2d37:18
+e49b0a2bd830bb3b96934490b2bc6ebe:18
+91790d7c5cf146ab3936919184a70015:18
+ec36ea4bb9634f7ec5c4d3c1cc9dd71b:18
+860e7b1a68c02eef7d2e2d7b6a3e13b5:18
+458fda8d9863be24862bb62c910f427d:18
+e05ed0452c12e62ef74e9161159b1231:18
+9419764761c059b9c2f9cf640abfdeb0:18
+ea02175c4ef64b6370f619acd84f3c3c:18
+0b01d40e1ca37aa854e77c98cc153e77:18
+eb12f3e690b201aa171d794a8288af10:18
+b955e5418d490425f99d58f441ba4525:18
+34305e28da7351c9fe8a69e055e1c250:4
+f9a8c4364b7598dbeca0186f60e71ea6:4
+6a6e8a76be9fd95bf68f4be6f6cf1bbf:4
+14a880d95891af72380815d1c91baf73:4
+a2fa13ef7ba19d24adf59f8ecc12f0b4:4
+acd568e2592b223aaec17240031ed688:4
+cf994432aef1a88d7b82ffeecbbb96dc:4
+31927400c1d74c4dbcefe4c8c787ab8a:4
+94961af3e92bca38554a63bef2e453a6:4
+c562088538d99567be8dfdc54f830661:4
+459a7fef5f29ed6bafedadf927e315ee:4
+5c9d5847e56faea367acff998cb9e02b:4
+766fb7e580b7f7211bee8e77b98859b7:4
+53dc80d117986033f21a0b5f8609c87c:4
+3d8114f2ba5b5b51c547181a89a3466f:4
+89418ba487f62fbae3843e5dff5b9040:4
+ebe40ec90a872c67a1c27ae2c1eea950:4
+35462fa6c34cbb3610497e0e7ec5604a:4
+4f7b3ab75c9ad8d5b10fec60265a3809:4
+5123777f5c011dbd46ae38689b72a7d4:4
+38c98f315f4b01c1aa53a76a214a919e:4
+25989076c985cb6136599803fbcb48d5:4
+a96139fddc921f9c720e39737bfc1e23:4
+5cc99cf3cb2f9f5b2f3da2068a3c3b14:4
+9d57682c086fd2c37173c59f7df7f7f1:4
+bae7808504fe1ce016d86125d969287f:4
+b0767396a0f7f31b48ac96880c9cbf8b:4
+e2babe17ce0f4f5b30fee91c06c0e8fa:4
+d1c3eecefff445a312cfee0fa59d1431:4
+d5702e33cc1c8a04039657bec033a3e8:4
+1449b4ca79da7eaf4a3e04a2aa7b764d:4
+f50415b6582d0e25f6949d844bf93ee8:4
+3fef2395a8a091e7dea7a3b99dfeaea6:4
+03e302c7776c88610dafb333323263af:4
+b7d9936f2c7c87d2edac782f5403001b:4
+affb389d45f01bc18197bf0f49f4b118:4
+a4eb5a999085dfe2424b2896e38b25d1:4
+9cc9248b4494b77d7b40bbbf4dcef8b3:4
+043d915c3e3d58cb001a8b32deaffea3:4
+f9d3d4bc8a5944b63f51ae472e86813d:4
+a5e0393e11eadd75dd36fb5379ae018c:4
+5980bcaa38c00e4b837f2e9785bda3d2:4
+cc63328c0b60b9a790f8a1457dfaa511:4
+afdf18c7f6d8a437d84e390721b9b8b3:4
+b08cfcf9149aa4e94b5fb9695271c575:4
+10a9f2ae427efa8d37d59f435e83d62e:4
+d689449577a88d1b20d321d4e5fd32d9:4
+75298a5c8b84105fd9b9df7184dc110a:4
+5c8ed88fdd5d5ccb1ad7a61ecdf1ef9e:4
+35a4cc8262e2a04a80fc506d1551c2b7:4
+7ef43aabb95d2f3ad79438accc09cb4c:4
+aa6a754d71d091d666fea53f8769dfe9:4
+c8341c7fe17e7ae1484fdc25aef13df9:4
+4b6c6d5caf6e3d47ffd3dfdb329842f2:4
+a93c08a489709b0b6402127f3287a0a2:4
+500d600d630b1130b906355e694caf25:4
+e2f20c930af14f873866419d98bb33c5:4
+4f67d44d8a9c73b20f9dccb0fe708910:4
+3207a78e01966b63f857a845d312c5db:4
+dc772adf9d92e1330e1e6c1bc8bbdbea:4
+9a0f97a242e5b5c8bf70dd24775d973a:4
+c51604fe4030e75ce4ed3f545e470eee:4
+e8627b4481475f494ae99206e4d614a4:4
+a3abcd010f778136d77b5c00575faa93:4
+d806274a83ce72b89939c7e1fcc7dbf1:4
+e42535765b64f8f96c6a5b13c5aa30e9:4
+0389b0ce442166bc4298bd49f8ecfbff:4
+e6c5da69c2396e1a4c404a82509edb1e:4
+d62e86b3047119ec0941418ab6365bd4:4
+3c957e719e0ffce99b544fcc9a97ab3a:4
+638115e7b634086640ad6b300846ad6d:4
+f42bda5821df4a01dc97cb7d611a98a2:4
+15884ea4526d7af7c6dd5c5c89b81c97:4
+23fb4c5f959189ae9c90b968647934f3:4
+6b538e97c482ee0993607820484dcb08:4
+e7e718f70ee49560800db2d60270603f:11
+61c32de62fbec72e56083dade766f0f9:11
+f9684960dbadc5206f33484afeba57e1:11
+f849d1e2b1fbe25e1c88aa103ce68255:11
+1186fd5fd194d7f5c3c1b19aa753d315:11
+c74e6a2c593956c2e74a9f89dac73eeb:11
+e528c2b6258e260f575488c35f7683fb:11
+e5c61babf2a6ff8f6939b7a1db7e621f:11
+48921ae81d5ad84778fd75c9f58e1bf9:11
+92e7e194987c3b4db1a4a129af2cd05f:11
+f1d93cdeba77ba27e048fd0fbc6b0bad:11
+2cb543910592d445cf09fec159d0b421:11
+cd3bfb7c471b99eb7b7acb8011e4687f:11
+df936e8a13b49cdb9b5f82e150aca29b:11
+0e452471f905f2fb311c6146b31b5705:11
+b78f4f3edd36b6326873eea475ef2476:11
+fa0bc15e6c1bcb787bdea1073a4e2709:11
+417ebe49420a8d103b8b65c5d574fd84:11
+18f7890363dbd6262668cdcffb0f3a8d:11
+28c73ab584871ffb89a5304f87335f95:11
+aa4a8bfad0ec9e57489297bc67664cfd:11
+9a5dd5c667e21679c42fc7b2cd79769c:11
+59654e2c6df1b6a8a77928223e2ab332:11
+fea47d75f1c8cdb3a12de27678c005b6:11
+c6dd561601e904cee3707ba4cfe1350f:11
+02ceeb351445bd756a73fd5ab2a3d0c4:11
+3a3e699a3fdaaaf0b0d809d114c54e19:11
+ccad7981fb017550a8f7b8925a1d0648:11
+0803d11e374ba9493cfdd8fc17f7d34c:11
+31b7ca8caafc4ac8fd6ab18a01f5e9f5:11
+762dbaa4b1beab4b7113a750639d7f6f:11
+cf1b9fa0beb54f8f57c79446a3186baa:11
+13c8db7cad8e47068720c9f9b9d8213c:11
+d942884e00b9f288de96a8b0733372d9:11
+cd5c01104e0c46080a8d499f87de4991:11
+d8c43560e159bcb899c2180b8e01d8e3:24
+ae9c7aee12dfaaa56e43834dcea91f5f:24
+12233f83b06081671815562dce938d3b:24
+106252e3f31ee17239e1ee4184147deb:24
+1fb2cd38813bad6e5eeef74bcbcb133d:24
+c5bbd4174eee5cb68830801317071dd5:24
+b876313caea388f03200ac720ed528af:24
+f4fa53a97c83899fd2da9f4112f35339:24
+dc32c09ecdd9bda4703aa944cf1eb4ca:24
+07a59dae4166f6d504bb0793c5e75178:24
+efcb5024ce3514453d44735222903a55:24
+43445dc9e50e2c4fc327097d07ff749a:24
+bcb1787dd2def454fd3f31d1b2c0967a:24
+cace738770550444ee1f91000c32c147:24
+2cc65559141cece33ffdbc4e34a2ce3b:24
+b55a09768f05f29b15e1ff3a9feb7c92:24
+54282406a85f4410d4dfc72d5873ee96:24
+1c38913c447705e315ec4486da558c9d:24
+ca3f30645fa7b480b213f4228386a09a:24
+6d8800c5cc8ad9e4f3f28e777207a870:24
+b8dc6f9b9587159f978d643aeb8e4d07:24
+44c0d27dc28d4da28655c3c62a0d7cac:24
+91eb697571f68f5850875286c5e36080:24
+ae908f7abd7d347b0bf39fead3531c12:24
+d27ceaaa92270c450aeee582cc17b985:24
+145e730e204fdca9cf169521a3812ed1:24
+294141fdc8f17ac3c7e4d8687d8ad389:24
+cfde9ea95c663bed6d56df8778b01b79:24
+8569110aa82eb44ff0b12819867c66f8:24
+48de820418038280f70eca64f878c67b:24
+f389ac23dc4ef6641321adf43d712c32:24
+ef4661e0074b431762177e10c2ac8570:24
+46de27224f4cf6fb745e30bbd6aad267:24
+75c79913d4df6b467a67c40ce67a32fc:24
+2a000aca1e17b3b0343ddf151ee313f4:24
+e9d59e0325c6dd1dd78a998365435fac:24
+0a1d88442fc34d0081115d949f1c5fa9:24
+0a7ef5f8b75fa2dd79787b03dff923c9:24
+b247285b58ca44aad532b1c3b5a5dacf:24
+63f61697ff1319a76632338fe0c13ae9:24
+e8df9edc7e29252645d2b8a6a55c4e0f:24
+aaba729f45b1617d59cec26aa41937e3:24
+7e7ae6228d4ab54b90f0f580731941d7:24
+de1c84862deb139962240505133690f0:24
+9660da0a81f885de5a3b16f1f07a1067:24
+9dbabf8883b7a4e120fcbceb00face40:24
+c3f5202b30c04801da693013368a0da2:24
+b73113e1f7c2702cfd997dd84e5ac003:24
+9b5946e54278b49302da6ccd2a3a06e1:24
+c7a5a5244ef8d55b160e29df7e310b76:24
+74fa889a011613ee983f45aed7dcb656:24
+ccb57b751fd0ed3f7244a0e096b19076:24
+dcd61dd6bd1e0fec6888029656162559:24
+c2fa9ac71ea25473f3c384e5d3c3770b:24
+43de03bf819ac106a9a66f3d7b473bd1:24
+3ed4b5f668130ecec1aadb21bf0a671a:24
+ba4d4fc3c411bf5b477323a1329574b3:24
+4e82fa0797beeab61a7a0b9b3ba7505e:24
+1cff05a0b4291e8f289a1794f016eab5:24
+11e854ed754739797dbae75ff3159e82:24
+456c3251cceb566a82d3175390b6a663:24
+76fd080fdaa80390ca05399f4d0b49ae:24
+2daef0ca0e135ad904bf7e931690cf2b:24
+d49bee1da6282ff1efd4017e2c26ff55:24
+94bf636e74064a1f45c5e81777a8b541:24
+b502fdd440592e2d637704ebef9baf01:24
+67395fd629dae7b237421aa91bc12150:24
+b69dd3d2f26fc766f3b4193b089fa71e:24
+5a13a7fac55b3378629744099623199b:24
+6c21462d7f8529e6d017e054b02d96d3:24
+ef1a937ed3ed1277a46cbbfb589bcc7c:24
+a9446b05afd48f274f6469e9617dadde:24
+03b1c2c2b9e04b66ace80c8d891dad71:24
+5af3ef227d419594dc0316392c0fd762:24
+b4146bc53ba79957adf0bf7306b2212c:24
+f1136465059f10586c4fb8379126e010:24
+8796edfd2200b66560e180ecf2c8bd3a:24
+b36bec8fd49158c020b6d8c09cc02baf:26
+799e6e8b1fe3f2c45ec9703491bd67d5:8
+12011f7bb75e99008d3ef5bbe9087e6f:8
+53737c53af7f40961557a85fb4d11b55:8
+9fac9097934ab48cd9de3d124943c3fd:8
+a85598686e913ff9a84c078990e2b74f:8
+9389bbac5d37c3655c2d112b5fb15d79:8
+c7143cf932c4620b38f241a67c698de7:8
+6bb42f291857bf93ef2407e3fe8910b3:8
+d5360a8c6fd960a52c125ad6ee65b369:8
+4c0513c3ea8aa4e0d1671697ade55dc0:8
+9ab999023c47266804e1c23912c49590:8
+c83ba14b99739545b5611680823c5e64:8
+6c3b66ed41f23693ee6ed1a2f74383ca:8
+e064938f6319932b4685797aa810233f:8
+edf457cbc465fa576479f66ed5689a8e:8
+befe448f726cd55c55d708a8ed55b087:8
+4a35639e3583bbc1b36b56f0b50a2849:8
+a2c469af732e034c5017e1fbcbb6fba0:8
+47ef9cb8fb8fa55cfe73612c56fc5a0d:8
+58e29b4d0c775a250e2751d3cb70e44b:8
+2248103eacf690b2060d9f624dee5e41:8
+7aaff576c6f4f28ee8d80236c3943c3a:8
+62068b685c80a1da76ef1824af304e47:5
+beef3d82791567a66852903d9acde2d8:14
+8f8e727dba622a81127060f53efbaf60:14
+a58ec4e669821ad885c5e539bea8f8c0:14
+c183aebc8596eae7dbaf32f831d4bf1a:14
+579a40bd08705acc7099b631706917cd:14
+c979623104ac1df2ff0b56f2eb8f40a3:14
+4a406313d3d1fc7c56f2e4ae88033ef5:14
+0534bb586552faf59923fcbb66c5c9a8:14
+027b76c362f10b646fce08dd34457533:29
diff --git a/src/main/resources/api_database/api_database_api_level.ser b/src/main/resources/api_database/api_database_api_level.ser
new file mode 100644
index 0000000..e9e539a
--- /dev/null
+++ b/src/main/resources/api_database/api_database_api_level.ser
Binary files differ
diff --git a/src/main/resources/api_database/api_database_hash_lookup.ser b/src/main/resources/api_database/api_database_hash_lookup.ser
new file mode 100644
index 0000000..42a3b61
--- /dev/null
+++ b/src/main/resources/api_database/api_database_hash_lookup.ser
Binary files differ
diff --git a/src/test/java/com/android/tools/r8/KotlinTestParameters.java b/src/test/java/com/android/tools/r8/KotlinTestParameters.java
index 534a42c..7996dc4 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestParameters.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestParameters.java
@@ -42,6 +42,14 @@
     return is(compilerVersion) && this.targetVersion == targetVersion;
   }
 
+  public boolean isNewerThanOrEqualTo(KotlinCompilerVersion otherVersion) {
+    return kotlinc.getCompilerVersion().isGreaterThanOrEqualTo(otherVersion);
+  }
+
+  public boolean isOlderThan(KotlinCompilerVersion otherVersion) {
+    return !isNewerThanOrEqualTo(otherVersion);
+  }
+
   public boolean isFirst() {
     return index == 0;
   }
diff --git a/src/test/java/com/android/tools/r8/L8TestRunResult.java b/src/test/java/com/android/tools/r8/L8TestRunResult.java
index a9a9a78..8f6bab8 100644
--- a/src/test/java/com/android/tools/r8/L8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/L8TestRunResult.java
@@ -49,6 +49,12 @@
   }
 
   @Override
+  public <E extends Throwable> L8TestRunResult inspectFailure(
+      ThrowingConsumer<CodeInspector, E> consumer) {
+    throw new Unimplemented();
+  }
+
+  @Override
   public L8TestRunResult disassemble() throws IOException, ExecutionException {
     throw new Unimplemented();
   }
diff --git a/src/test/java/com/android/tools/r8/MarkerMatcher.java b/src/test/java/com/android/tools/r8/MarkerMatcher.java
index d766770..22610d3 100644
--- a/src/test/java/com/android/tools/r8/MarkerMatcher.java
+++ b/src/test/java/com/android/tools/r8/MarkerMatcher.java
@@ -179,6 +179,21 @@
     };
   }
 
+  public static Matcher<Marker> markerPgMapId(Matcher<String> predicate) {
+    return new MarkerMatcher() {
+      @Override
+      protected boolean eval(Marker marker) {
+        return predicate.matches(marker.getPgMapId());
+      }
+
+      @Override
+      protected void explain(Description description) {
+        description.appendText("with pg_map_id matching ");
+        predicate.describeTo(description);
+      }
+    };
+  }
+
   public static Matcher<Marker> markerR8Mode(String r8Mode) {
     return new MarkerMatcher() {
       @Override
diff --git a/src/test/java/com/android/tools/r8/ProguardTestRunResult.java b/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
index 3c2bbfc..c5cccbe 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
-import java.util.concurrent.ExecutionException;
 
 public class ProguardTestRunResult extends SingleTestRunResult<ProguardTestRunResult> {
 
@@ -34,14 +33,8 @@
     return super.getStackTrace().retrace(proguardMap);
   }
 
-  public StackTrace getOriginalStackTrace() {
-    return super.getStackTrace();
-  }
-
   @Override
-  public CodeInspector inspector() throws IOException, ExecutionException {
-    // See comment in base class.
-    assertSuccess();
+  protected CodeInspector internalGetCodeInspector() throws IOException {
     assertNotNull(app);
     return new CodeInspector(app, proguardMap);
   }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 08dd682..5d3c306 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -204,7 +204,7 @@
             b ->
                 b.addProguardConfiguration(
                     getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaringnplus"))
         .run();
   }
 
@@ -244,7 +244,7 @@
             b ->
                 b.addProguardConfiguration(
                     getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 0, "lambdadesugaringnplus"))
         .run();
   }
 
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index b247acf..08bb64b 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -33,7 +32,9 @@
 public abstract class R8RunExamplesCommon {
 
   protected enum Input {
-    DX, JAVAC, JAVAC_ALL, JAVAC_NONE
+    JAVAC,
+    JAVAC_ALL,
+    JAVAC_NONE
   }
 
   protected enum Output {
@@ -90,8 +91,6 @@
 
   private Path getInputFile() {
     switch(input) {
-      case DX:
-        return getOriginalDexFile();
       case JAVAC:
         return getOriginalJarFile("");
       case JAVAC_ALL:
@@ -104,12 +103,7 @@
   }
 
   public R8Command.Builder addInputFile(R8Command.Builder builder) {
-    if (input == Input.DX) {
-      // If input is DEX code, use the tool helper to add the DEX sources as R8 disallows them.
-      ToolHelper.getAppBuilder(builder).addProgramFiles(getOriginalDexFile());
-    } else {
-      builder.addProgramFiles(getInputFile());
-    }
+    builder.addProgramFiles(getInputFile());
     return builder;
   }
 
@@ -121,10 +115,6 @@
     return Paths.get(getExampleDir(), pkg, ToolHelper.DEFAULT_DEX_FILENAME);
   }
 
-  private DexTool getTool() {
-    return input == Input.DX ? DexTool.DX : DexTool.NONE;
-  }
-
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
@@ -171,10 +161,6 @@
   }
 
   private boolean shouldCompileFail() {
-    if (input == Input.DX && getFailingCompileDxToDex().contains(mainClass)) {
-      assert output == Output.DEX;
-      return true;
-    }
     if (output == Output.CF && getFailingCompileCf().contains(mainClass)) {
       return true;
     }
@@ -212,7 +198,7 @@
 
     TestCondition condition =
         output == Output.CF ? getFailingRunCf().get(mainClass) : getFailingRun().get(mainClass);
-    if (condition != null && condition.test(getTool(), compiler, vm.getVersion(), mode)) {
+    if (condition != null && condition.test(DexTool.NONE, compiler, vm.getVersion(), mode)) {
       thrown.expect(Throwable.class);
     }
 
@@ -248,12 +234,12 @@
 
   private boolean shouldMatchJVMOutput(DexVm.Version version) {
     TestCondition condition = getOutputNotIdenticalToJVMOutput().get(mainClass);
-    return condition == null || !condition.test(getTool(), compiler, version, mode);
+    return condition == null || !condition.test(DexTool.NONE, compiler, version, mode);
   }
 
   private boolean shouldSkipVm(DexVm.Version version) {
     TestCondition condition = getSkip().get(mainClass);
-    return condition != null && condition.test(getTool(), compiler, version, mode);
+    return condition != null && condition.test(DexTool.NONE, compiler, version, mode);
   }
 
   protected abstract String getExampleDir();
@@ -264,10 +250,6 @@
 
   protected abstract Set<String> getFailingCompileCfToDex();
 
-  protected Set<String> getFailingCompileDxToDex() {
-    return ImmutableSet.of();
-  }
-
   // TODO(mathiasr): Add CompilerSet for CfToDex so we can fold this into getFailingRun().
   protected abstract Set<String> getFailingRunCfToDex();
 
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index d8a62da..922f2d3 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -95,7 +95,6 @@
           makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.RELEASE, test));
       fullTestList.add(
           makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.DEBUG, test));
-      fullTestList.add(makeTest(Input.DX, CompilerUnderTest.R8, CompilationMode.RELEASE, test));
       fullTestList.add(
           makeTest(
               Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.RELEASE, test, Output.CF));
@@ -158,12 +157,6 @@
   }
 
   @Override
-  protected Set<String> getFailingCompileDxToDex() {
-    return new ImmutableSet.Builder<String>()
-        .build();
-  }
-
-  @Override
   protected Set<String> getFailingCompileCf() {
     return new ImmutableSet.Builder<String>()
         .build();
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 3ef4901..716971d 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ThrowingBiConsumer;
-import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.graphinspector.GraphInspector;
 import java.io.IOException;
@@ -52,10 +51,6 @@
     return super.getStackTrace().retraceAllowExperimentalMapping(proguardMap);
   }
 
-  public StackTrace getOriginalStackTrace() {
-    return super.getStackTrace();
-  }
-
   @Override
   protected CodeInspector internalGetCodeInspector() throws IOException {
     assertNotNull(app);
@@ -63,12 +58,6 @@
   }
 
   public <E extends Throwable> R8TestRunResult inspectOriginalStackTrace(
-      ThrowingConsumer<StackTrace, E> consumer) throws E {
-    consumer.accept(getOriginalStackTrace());
-    return self();
-  }
-
-  public <E extends Throwable> R8TestRunResult inspectOriginalStackTrace(
       ThrowingBiConsumer<StackTrace, CodeInspector, E> consumer) throws E, IOException {
     consumer.accept(getOriginalStackTrace(), internalGetCodeInspector());
     return self();
diff --git a/src/test/java/com/android/tools/r8/SanityCheck.java b/src/test/java/com/android/tools/r8/SanityCheck.java
index a7b1990..df1486c 100644
--- a/src/test/java/com/android/tools/r8/SanityCheck.java
+++ b/src/test/java/com/android/tools/r8/SanityCheck.java
@@ -12,12 +12,14 @@
 
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.util.Enumeration;
+import java.util.Set;
 import java.util.function.Predicate;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
@@ -45,6 +47,11 @@
     }
     boolean licenseSeen = false;
     final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+    Set<String> apiDatabaseFiles =
+        Sets.newHashSet(
+            "api_database/api_database_ambiguous.txt",
+            "api_database/api_database_api_level.ser",
+            "api_database/api_database_hash_lookup.ser");
     while (entries.hasMoreElements()) {
       ZipEntry entry = entries.nextElement();
       String name = entry.getName();
@@ -56,12 +63,16 @@
         licenseSeen = true;
       } else if (entryTester.test(name)) {
         // Allow.
+      } else if (apiDatabaseFiles.contains(name)) {
+        // Allow all api database files.
+        apiDatabaseFiles.remove(name);
       } else if (name.endsWith("/")) {
         assertTrue("Unexpected directory entry in" + jar, allowDirectories);
       } else {
         fail("Unexpected entry '" + name + "' in " + jar);
       }
     }
+    assertTrue(apiDatabaseFiles.isEmpty());
     assertTrue("No LICENSE entry found in " + jar, licenseSeen);
   }
 
diff --git a/src/test/java/com/android/tools/r8/SingleTestRunResult.java b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
index 17546ee..726acbd 100644
--- a/src/test/java/com/android/tools/r8/SingleTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
@@ -60,6 +60,10 @@
   }
 
   public StackTrace getStackTrace() {
+    return getOriginalStackTrace();
+  }
+
+  public StackTrace getOriginalStackTrace() {
     if (runtime.isDex()) {
       return StackTrace.extractFromArt(getStdErr(), runtime.asDex().getVm());
     } else {
@@ -129,6 +133,12 @@
     return self();
   }
 
+  public <E extends Throwable> RR inspectOriginalStackTrace(
+      ThrowingConsumer<StackTrace, E> consumer) throws E {
+    consumer.accept(getOriginalStackTrace());
+    return self();
+  }
+
   public RR disassemble(PrintStream ps) throws IOException, ExecutionException {
     ToolHelper.disassemble(app, ps);
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 90d4f75..bd5ad83 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -38,6 +38,9 @@
   public abstract <E extends Throwable> RR inspect(ThrowingConsumer<CodeInspector, E> consumer)
       throws IOException, ExecutionException, E;
 
+  public abstract <E extends Throwable> RR inspectFailure(
+      ThrowingConsumer<CodeInspector, E> consumer) throws IOException, E;
+
   public abstract RR disassemble() throws IOException, ExecutionException;
 
   public <E extends Throwable> RR apply(ThrowingConsumer<RR, E> fn) throws E {
diff --git a/src/test/java/com/android/tools/r8/TestRunResultCollection.java b/src/test/java/com/android/tools/r8/TestRunResultCollection.java
index e166be4..c5421ac 100644
--- a/src/test/java/com/android/tools/r8/TestRunResultCollection.java
+++ b/src/test/java/com/android/tools/r8/TestRunResultCollection.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -57,6 +58,12 @@
     return inspectIf(Predicates.alwaysTrue(), consumer);
   }
 
+  @Override
+  public <E extends Throwable> RR inspectFailure(ThrowingConsumer<CodeInspector, E> consumer)
+      throws IOException, E {
+    throw new Unimplemented();
+  }
+
   public RR applyIf(Predicate<C> filter, Consumer<TestRunResult<?>> thenConsumer) {
     return applyIf(filter, thenConsumer, r -> {});
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 56a2b80..0d02a13 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -90,6 +90,7 @@
   static final Path[] EMPTY_PATH = {};
 
   public static final String SOURCE_DIR = "src/main/java/";
+  public static final String RESOURCES_DIR = "src/main/resources/";
   public static final String BUILD_DIR = "build/";
   public static final String GENERATED_TEST_BUILD_DIR = BUILD_DIR + "generated/test/";
   public static final String LIBS_DIR = BUILD_DIR + "libs/";
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
new file mode 100644
index 0000000..d7db635
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
@@ -0,0 +1,241 @@
+// Copyright (c) 2021, 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.apimodel;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.androidapi.AndroidApiLevelHashingDatabaseImpl;
+import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.structural.DefaultHashingVisitor;
+import com.android.tools.r8.utils.structural.HasherWrapper;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import java.io.FileOutputStream;
+import java.io.ObjectOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
+
+public class AndroidApiHashingDatabaseBuilderGenerator extends TestBase {
+
+  /**
+   * Generate the information needed for looking up api level of references in the android.jar. This
+   * method will generate three different files and store them in the passed paths. pathToIndices
+   * will be an int array with hashcode entries for each DexReference. The pathToApiLevels is a byte
+   * array with a byte describing the api level that the index in pathToIndices has. To ensure that
+   * this lookup work the generate algorithm tracks all colliding hash codes such and stores them in
+   * another format. The indices map is populated with all colliding entries and a -1 is inserted
+   * for the api level.
+   */
+  public static void generate(
+      List<ParsedApiClass> apiClasses,
+      Path pathToIndices,
+      Path pathToApiLevels,
+      Path ambiguousDefinitions)
+      throws Exception {
+    Map<ClassReference, Map<DexMethod, AndroidApiLevel>> methodMap = new HashMap<>();
+    Map<ClassReference, Map<DexField, AndroidApiLevel>> fieldMap = new HashMap<>();
+    Map<ClassReference, ParsedApiClass> lookupMap = new HashMap<>();
+    DexItemFactory factory = new DexItemFactory();
+
+    Map<Integer, AndroidApiLevel> apiLevelMap = new HashMap<>();
+    Map<Integer, Pair<DexReference, AndroidApiLevel>> reverseMap = new HashMap<>();
+    Map<AndroidApiLevel, Set<DexReference>> ambiguousMap = new HashMap<>();
+    // Populate maps for faster lookup.
+    for (ParsedApiClass apiClass : apiClasses) {
+      DexType type = factory.createType(apiClass.getClassReference().getDescriptor());
+      AndroidApiLevel existing = apiLevelMap.put(type.hashCode(), apiClass.getApiLevel());
+      assert existing == null;
+      reverseMap.put(type.hashCode(), Pair.create(type, apiClass.getApiLevel()));
+    }
+
+    for (ParsedApiClass apiClass : apiClasses) {
+      Map<DexMethod, AndroidApiLevel> methodsForApiClass = new HashMap<>();
+      apiClass.visitMethodReferences(
+          (apiLevel, methods) -> {
+            methods.forEach(
+                method -> methodsForApiClass.put(factory.createMethod(method), apiLevel));
+          });
+      Map<DexField, AndroidApiLevel> fieldsForApiClass = new HashMap<>();
+      apiClass.visitFieldReferences(
+          (apiLevel, fields) -> {
+            fields.forEach(field -> fieldsForApiClass.put(factory.createField(field), apiLevel));
+          });
+      methodMap.put(apiClass.getClassReference(), methodsForApiClass);
+      fieldMap.put(apiClass.getClassReference(), fieldsForApiClass);
+      lookupMap.put(apiClass.getClassReference(), apiClass);
+    }
+
+    BiConsumer<DexReference, AndroidApiLevel> addConsumer =
+        addReferenceToMaps(apiLevelMap, reverseMap, ambiguousMap);
+
+    for (ParsedApiClass apiClass : apiClasses) {
+      computeAllReferencesInHierarchy(
+              lookupMap,
+              factory,
+              factory.createType(apiClass.getClassReference().getDescriptor()),
+              apiClass,
+              AndroidApiLevel.B,
+              new IdentityHashMap<>())
+          .forEach(addConsumer);
+    }
+
+    int[] indices = new int[apiLevelMap.size()];
+    byte[] apiLevel = new byte[apiLevelMap.size()];
+    ArrayList<Integer> integers = new ArrayList<>(apiLevelMap.keySet());
+    for (int i = 0; i < integers.size(); i++) {
+      indices[i] = integers.get(i);
+      AndroidApiLevel androidApiLevel = apiLevelMap.get(integers.get(i));
+      apiLevel[i] =
+          (byte) (androidApiLevel == AndroidApiLevel.NOT_SET ? -1 : androidApiLevel.getLevel());
+    }
+
+    try (FileOutputStream fileOutputStream = new FileOutputStream(pathToIndices.toFile());
+        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
+      objectOutputStream.writeObject(indices);
+    }
+
+    try (FileOutputStream fileOutputStream = new FileOutputStream(pathToApiLevels.toFile());
+        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
+      objectOutputStream.writeObject(apiLevel);
+    }
+
+    String ambiguousMapSerialized = serializeAmbiguousMap(ambiguousMap);
+    Files.write(ambiguousDefinitions, ambiguousMapSerialized.getBytes(StandardCharsets.UTF_8));
+  }
+
+  /** This will serialize a collection of DexReferences with the api level they correspond to. */
+  private static String serializeAmbiguousMap(
+      Map<AndroidApiLevel, Set<DexReference>> ambiguousMap) {
+    Set<String> seen = new HashSet<>();
+    StringBuilder sb = new StringBuilder();
+    ambiguousMap.forEach(
+        (apiLevel, references) -> {
+          references.forEach(
+              reference -> {
+                HasherWrapper defaultHasher = AndroidApiLevelHashingDatabaseImpl.getDefaultHasher();
+                reference.accept(
+                    type -> DefaultHashingVisitor.run(type, defaultHasher, DexType::acceptHashing),
+                    field ->
+                        DefaultHashingVisitor.run(
+                            field, defaultHasher, StructuralItem::acceptHashing),
+                    method ->
+                        DefaultHashingVisitor.run(
+                            method, defaultHasher, StructuralItem::acceptHashing));
+                String referenceHash = defaultHasher.hash().toString();
+                if (!seen.add(referenceHash)) {
+                  throw new RuntimeException(
+                      "More than one item with key: <"
+                          + referenceHash
+                          + ">. The choice of key encoding will need to change to generate a valid"
+                          + " api database");
+                }
+                sb.append(referenceHash).append(":").append(apiLevel.getLevel()).append("\n");
+              });
+        });
+    return sb.toString();
+  }
+
+  private static BiConsumer<DexReference, AndroidApiLevel> addReferenceToMaps(
+      Map<Integer, AndroidApiLevel> apiLevelMap,
+      Map<Integer, Pair<DexReference, AndroidApiLevel>> reverseMap,
+      Map<AndroidApiLevel, Set<DexReference>> ambiguousMap) {
+    return ((reference, apiLevel) -> {
+      AndroidApiLevel existingMethod = apiLevelMap.put(reference.hashCode(), apiLevel);
+      if (existingMethod != null) {
+        apiLevelMap.put(reference.hashCode(), AndroidApiLevel.NOT_SET);
+        Pair<DexReference, AndroidApiLevel> existingPair = reverseMap.get(reference.hashCode());
+        addAmbiguousEntry(existingPair.getSecond(), existingPair.getFirst(), ambiguousMap);
+        addAmbiguousEntry(apiLevel, reference, ambiguousMap);
+      } else {
+        reverseMap.put(reference.hashCode(), Pair.create(reference, apiLevel));
+      }
+    });
+  }
+
+  private static void addAmbiguousEntry(
+      AndroidApiLevel apiLevel,
+      DexReference reference,
+      Map<AndroidApiLevel, Set<DexReference>> ambiguousMap) {
+    ambiguousMap.computeIfAbsent(apiLevel, ignored -> new HashSet<>()).add(reference);
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <T extends DexMember<?, ?>>
+      Map<T, AndroidApiLevel> computeAllReferencesInHierarchy(
+          Map<ClassReference, ParsedApiClass> lookupMap,
+          DexItemFactory factory,
+          DexType holder,
+          ParsedApiClass apiClass,
+          AndroidApiLevel linkLevel,
+          Map<T, AndroidApiLevel> additionMap) {
+    if (!apiClass.getClassReference().getDescriptor().equals(factory.objectDescriptor.toString())) {
+      apiClass.visitMethodReferences(
+          (apiLevel, methodReferences) -> {
+            methodReferences.forEach(
+                methodReference -> {
+                  T member = (T) factory.createMethod(methodReference).withHolder(holder, factory);
+                  addIfNewOrApiLevelIsLower(linkLevel, additionMap, apiLevel, member);
+                });
+          });
+      apiClass.visitFieldReferences(
+          (apiLevel, fieldReferences) -> {
+            fieldReferences.forEach(
+                fieldReference -> {
+                  T member = (T) factory.createField(fieldReference).withHolder(holder, factory);
+                  addIfNewOrApiLevelIsLower(linkLevel, additionMap, apiLevel, member);
+                });
+          });
+      apiClass.visitSuperType(
+          (superType, apiLevel) -> {
+            computeAllReferencesInHierarchy(
+                lookupMap,
+                factory,
+                holder,
+                lookupMap.get(superType),
+                linkLevel.max(apiLevel),
+                additionMap);
+          });
+      apiClass.visitInterface(
+          (iFace, apiLevel) -> {
+            computeAllReferencesInHierarchy(
+                lookupMap,
+                factory,
+                holder,
+                lookupMap.get(iFace),
+                linkLevel.max(apiLevel),
+                additionMap);
+          });
+    }
+    return additionMap;
+  }
+
+  private static <T extends DexMember<?, ?>> void addIfNewOrApiLevelIsLower(
+      AndroidApiLevel linkLevel,
+      Map<T, AndroidApiLevel> additionMap,
+      AndroidApiLevel apiLevel,
+      T member) {
+    AndroidApiLevel currentApiLevel = apiLevel.max(linkLevel);
+    AndroidApiLevel existingApiLevel = additionMap.get(member);
+    if (existingApiLevel == null || currentApiLevel.isLessThanOrEqualTo(existingApiLevel)) {
+      additionMap.put(member, currentApiLevel);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
new file mode 100644
index 0000000..8a0c060
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
@@ -0,0 +1,198 @@
+// Copyright (c) 2021, 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.apimodel;
+
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.androidapi.AndroidApiLevelHashingDatabaseImpl;
+import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.IntBox;
+import com.google.common.collect.ImmutableList;
+import java.io.FileInputStream;
+import java.io.ObjectInputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AndroidApiHashingDatabaseBuilderGeneratorTest extends TestBase {
+
+  protected final TestParameters parameters;
+  private static final Path API_VERSIONS_XML =
+      Paths.get(ToolHelper.THIRD_PARTY_DIR, "android_jar", "api-versions", "api-versions.xml");
+  private static final Path API_DATABASE_HASH_LOOKUP =
+      Paths.get(ToolHelper.RESOURCES_DIR, "api_database", "api_database_hash_lookup.ser");
+  private static final Path API_DATABASE_API_LEVEL =
+      Paths.get(ToolHelper.RESOURCES_DIR, "api_database", "api_database_api_level.ser");
+  private static final Path API_DATABASE_AMBIGUOUS =
+      Paths.get(ToolHelper.RESOURCES_DIR, "api_database", "api_database_ambiguous.txt");
+  private static final AndroidApiLevel API_LEVEL = AndroidApiLevel.R;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public AndroidApiHashingDatabaseBuilderGeneratorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static class GenerateDatabaseResourceFilesResult {
+
+    private final Path indices;
+    private final Path apiLevels;
+    private final Path ambiguous;
+
+    public GenerateDatabaseResourceFilesResult(Path indices, Path apiLevels, Path ambiguous) {
+      this.indices = indices;
+      this.apiLevels = apiLevels;
+      this.ambiguous = ambiguous;
+    }
+  }
+
+  private static GenerateDatabaseResourceFilesResult generateResourcesFiles() throws Exception {
+    return generateResourcesFiles(
+        AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL));
+  }
+
+  private static GenerateDatabaseResourceFilesResult generateResourcesFiles(
+      List<ParsedApiClass> apiClasses) throws Exception {
+    TemporaryFolder temp = new TemporaryFolder();
+    temp.create();
+    Path indices = temp.newFile("indices.ser").toPath();
+    Path apiLevels = temp.newFile("apiLevels.ser").toPath();
+    Path ambiguous = temp.newFile("ambiguous.ser").toPath();
+    AndroidApiHashingDatabaseBuilderGenerator.generate(apiClasses, indices, apiLevels, ambiguous);
+    return new GenerateDatabaseResourceFilesResult(indices, apiLevels, ambiguous);
+  }
+
+  @Test
+  public void testCanParseApiVersionsXml() throws Exception {
+    // This tests makes a rudimentary check on the number of classes, fields and methods in
+    // api-versions.xml to ensure that the runtime tests do not vacuously succeed.
+    List<ParsedApiClass> parsedApiClasses =
+        AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL);
+    IntBox numberOfFields = new IntBox(0);
+    IntBox numberOfMethods = new IntBox(0);
+    parsedApiClasses.forEach(
+        apiClass -> {
+          apiClass.visitFieldReferences(
+              ((apiLevel, fieldReferences) -> {
+                fieldReferences.forEach(field -> numberOfFields.increment());
+              }));
+          apiClass.visitMethodReferences(
+              ((apiLevel, methodReferences) -> {
+                methodReferences.forEach(field -> numberOfMethods.increment());
+              }));
+        });
+    // These numbers will change when updating api-versions.xml
+    assertEquals(4742, parsedApiClasses.size());
+    assertEquals(25144, numberOfFields.get());
+    assertEquals(38661, numberOfMethods.get());
+  }
+
+  @Test
+  public void testDatabaseGenerationUpToDate() throws Exception {
+    GenerateDatabaseResourceFilesResult result = generateResourcesFiles();
+    TestBase.filesAreEqual(result.indices, API_DATABASE_HASH_LOOKUP);
+    TestBase.filesAreEqual(result.apiLevels, API_DATABASE_API_LEVEL);
+    TestBase.filesAreEqual(result.ambiguous, API_DATABASE_AMBIGUOUS);
+  }
+
+  @Test
+  public void initializeApiDatabaseTimeTest() {
+    DexItemFactory factory = new DexItemFactory();
+    long start = System.currentTimeMillis();
+    new AndroidApiLevelHashingDatabaseImpl(factory, ImmutableList.of());
+    long end = System.currentTimeMillis();
+    long timeSpan = end - start;
+    assertTrue("Time used was " + timeSpan, timeSpan < 100);
+  }
+
+  @Test
+  public void testCanLookUpAllParsedApiClassesAndMembers() throws Exception {
+    List<ParsedApiClass> parsedApiClasses =
+        AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL);
+    DexItemFactory factory = new DexItemFactory();
+    AndroidApiLevelHashingDatabaseImpl androidApiLevelDatabase =
+        new AndroidApiLevelHashingDatabaseImpl(factory, ImmutableList.of());
+    parsedApiClasses.forEach(
+        parsedApiClass -> {
+          DexType type = factory.createType(parsedApiClass.getClassReference().getDescriptor());
+          AndroidApiLevel apiLevel = androidApiLevelDatabase.getTypeApiLevel(type);
+          assertEquals(parsedApiClass.getApiLevel(), apiLevel);
+          parsedApiClass.visitMethodReferences(
+              (methodApiLevel, methodReferences) ->
+                  methodReferences.forEach(
+                      methodReference -> {
+                        DexMethod method = factory.createMethod(methodReference);
+                        androidApiLevelDatabase
+                            .getMethodApiLevel(method)
+                            .isLessThanOrEqualTo(methodApiLevel);
+                      }));
+          parsedApiClass.visitFieldReferences(
+              (fieldApiLevel, fieldReferences) ->
+                  fieldReferences.forEach(
+                      fieldReference -> {
+                        DexField field = factory.createField(fieldReference);
+                        androidApiLevelDatabase
+                            .getFieldApiLevel(field)
+                            .isLessThanOrEqualTo(fieldApiLevel);
+                      }));
+        });
+  }
+
+  /**
+   * Main entry point for building a database over references in framework to the api level they
+   * were introduced. Running main will generate a new jar and run tests on it to ensure it is
+   * compatible with R8 sources and works as expected.
+   *
+   * <p>The generated jar depends on r8NoManifestWithoutDeps.
+   *
+   * <p>If the generated jar passes tests it will be moved to third_party/android_jar/api-database/
+   * and override the current file in there.
+   */
+  public static void main(String[] args) throws Exception {
+    List<ParsedApiClass> parsedApiClasses =
+        AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL);
+    GenerateDatabaseResourceFilesResult result = generateResourcesFiles(parsedApiClasses);
+    verifyNoDuplicateHashes(result.indices);
+    Files.move(result.indices, API_DATABASE_HASH_LOOKUP, REPLACE_EXISTING);
+    Files.move(result.apiLevels, API_DATABASE_API_LEVEL, REPLACE_EXISTING);
+    Files.move(result.ambiguous, API_DATABASE_AMBIGUOUS, REPLACE_EXISTING);
+  }
+
+  private static void verifyNoDuplicateHashes(Path indicesPath) throws Exception {
+    Set<Integer> elements = new HashSet<>();
+    int[] indices;
+    try (FileInputStream fileInputStream = new FileInputStream(indicesPath.toFile());
+        ObjectInputStream indicesObjectStream = new ObjectInputStream(fileInputStream)) {
+      indices = (int[]) indicesObjectStream.readObject();
+      for (int index : indices) {
+        assertTrue(elements.add(index));
+      }
+    }
+    assertEquals(elements.size(), indices.length);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiObjectDatabaseBuilderGenerator.java
similarity index 99%
rename from src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
rename to src/test/java/com/android/tools/r8/apimodel/AndroidApiObjectDatabaseBuilderGenerator.java
index 8c139fe..c8b6c24 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiObjectDatabaseBuilderGenerator.java
@@ -43,7 +43,7 @@
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 
-public class AndroidApiDatabaseBuilderGenerator extends TestBase {
+public class AndroidApiObjectDatabaseBuilderGenerator extends TestBase {
 
   public static String generatedMainDescriptor() {
     return descriptor(AndroidApiDatabaseBuilderTemplate.class).replace("Template", "");
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiObjectDatabaseBuilderGeneratorTest.java
similarity index 95%
rename from src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
rename to src/test/java/com/android/tools/r8/apimodel/AndroidApiObjectDatabaseBuilderGeneratorTest.java
index 91accca..299a25a 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiObjectDatabaseBuilderGeneratorTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.apimodel;
 
-import static com.android.tools.r8.apimodel.AndroidApiDatabaseBuilderGenerator.generatedMainDescriptor;
+import static com.android.tools.r8.apimodel.AndroidApiObjectDatabaseBuilderGenerator.generatedMainDescriptor;
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 import static org.junit.Assert.assertEquals;
 
@@ -48,7 +48,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class AndroidApiDatabaseBuilderGeneratorTest extends TestBase {
+public class AndroidApiObjectDatabaseBuilderGeneratorTest extends TestBase {
 
   protected final TestParameters parameters;
   private static final Path API_VERSIONS_XML =
@@ -62,7 +62,7 @@
     return getTestParameters().withNoneRuntime().build();
   }
 
-  public AndroidApiDatabaseBuilderGeneratorTest(TestParameters parameters) {
+  public AndroidApiObjectDatabaseBuilderGeneratorTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -75,7 +75,7 @@
     TemporaryFolder temp = new TemporaryFolder();
     temp.create();
     ZipBuilder builder = ZipBuilder.builder(temp.newFile("out.jar").toPath());
-    AndroidApiDatabaseBuilderGenerator.generate(
+    AndroidApiObjectDatabaseBuilderGenerator.generate(
         apiClasses,
         (descriptor, content) -> {
           try {
@@ -139,10 +139,10 @@
   private static void validateJar(Path generated, List<ParsedApiClass> apiClasses) {
     List<BiFunction<Path, List<ParsedApiClass>, Boolean>> tests =
         ImmutableList.of(
-            AndroidApiDatabaseBuilderGeneratorTest::testGeneratedOutputForVisitClasses,
-            AndroidApiDatabaseBuilderGeneratorTest::testBuildClassesContinue,
-            AndroidApiDatabaseBuilderGeneratorTest::testBuildClassesBreak,
-            AndroidApiDatabaseBuilderGeneratorTest::testNoPlaceHolder);
+            AndroidApiObjectDatabaseBuilderGeneratorTest::testGeneratedOutputForVisitClasses,
+            AndroidApiObjectDatabaseBuilderGeneratorTest::testBuildClassesContinue,
+            AndroidApiObjectDatabaseBuilderGeneratorTest::testBuildClassesBreak,
+            AndroidApiObjectDatabaseBuilderGeneratorTest::testNoPlaceHolder);
     tests.forEach(
         test -> {
           try {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
index 18b35f5..7c4226c 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -41,6 +42,7 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .addHorizontallyMergedClassesInspector(
             inspector -> {
               if (parameters.isDexRuntime()
@@ -66,6 +68,7 @@
     }
   }
 
+  @NoHorizontalClassMerging
   static class ApiSetter {
     static void set() {
       A.api = new Api();
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningFieldTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningFieldTest.java
index f4609d4..5392a35 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningFieldTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningFieldTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.apimodel;
 
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForField;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
@@ -40,7 +41,7 @@
   public void testR8() throws Exception {
     Field apiField = Api.class.getDeclaredField("foo");
     testForR8(parameters.getBackend())
-        .addProgramClasses(ApiCaller.class, ApiCallerCaller.class, ApiBuilder.class, Main.class)
+        .addProgramClasses(ApiCallerCaller.class, ApiBuilder.class, Main.class)
         .addLibraryClasses(Api.class)
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters.getApiLevel())
@@ -48,6 +49,7 @@
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .apply(setMockApiLevelForField(apiField, AndroidApiLevel.L_MR1))
+        .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1))
         .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, AndroidApiLevel.L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .compile()
@@ -55,9 +57,9 @@
             inspector -> {
               if (parameters.isDexRuntime()
                   && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) {
-                assertThat(inspector.clazz(ApiCaller.class), not(isPresent()));
+                assertThat(inspector.clazz(ApiBuilder.class), not(isPresent()));
               } else {
-                assertThat(inspector.clazz(ApiCaller.class), isPresent());
+                assertThat(inspector.clazz(ApiBuilder.class), isPresent());
               }
             })
         .addRunClasspathClasses(Api.class)
@@ -72,19 +74,14 @@
 
   public static class ApiBuilder {
 
-    public static Api build() {
-      return new Api();
+    public Api api;
+
+    public ApiBuilder() {
+      api = new Api();
     }
-  }
 
-  @NoHorizontalClassMerging
-  public static class ApiCaller {
-
-    private Api api;
-
-    public ApiCaller(Api api) {
-      this.api = api;
-      System.out.println(api.foo);
+    public String build() {
+      return api.foo;
     }
   }
 
@@ -93,7 +90,7 @@
 
     @NeverInline
     public static void callCallApi() {
-      new ApiCaller(ApiBuilder.build());
+      System.out.println(new ApiBuilder().build());
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
index a1ecb6a..9446479 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.apimodel;
 
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForField;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
@@ -49,6 +50,7 @@
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .apply(setMockApiLevelForField(apiField, L_MR1))
+        .apply(setMockApiLevelForClass(Api.class, L_MR1))
         .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
index bdaf09d..a480188 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
@@ -37,10 +37,10 @@
   @Test
   public void testR8() throws Exception {
     Method apiMethod = Api.class.getDeclaredMethod("apiLevel22");
-    Method apiCaller = ApiCaller.class.getDeclaredMethod("callInterfaceMethod", Api.class);
-    Method apiCallerCaller = A.class.getDeclaredMethod("noApiCall");
+    Method apiCaller = ApiCaller.class.getDeclaredMethod("callInterfaceMethod", Object.class);
+    Method apiCallerCaller = A.class.getDeclaredMethod("noApiCall", Object.class);
     testForR8(parameters.getBackend())
-        .addProgramClasses(Main.class, A.class, ApiCaller.class)
+        .addProgramClassesAndInnerClasses(Main.class, A.class, ApiCaller.class)
         .addLibraryClasses(Api.class)
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters.getApiLevel())
@@ -66,10 +66,10 @@
   public static class ApiCaller {
 
     @KeepConstantArguments
-    public static void callInterfaceMethod(Api api) {
+    public static void callInterfaceMethod(Object o) {
       System.out.println("ApiCaller::callInterfaceMethod");
-      if (api != null) {
-        api.apiLevel22();
+      if (o != null) {
+        ((Api) o).apiLevel22();
       }
     }
   }
@@ -78,16 +78,24 @@
   public static class A {
 
     @NeverInline
-    public static void noApiCall() {
+    public static void noApiCall(Object o) {
       System.out.println("A::noApiCall");
-      ApiCaller.callInterfaceMethod(null);
+      ApiCaller.callInterfaceMethod(o);
     }
   }
 
   public static class Main {
 
     public static void main(String[] args) {
-      A.noApiCall();
+      A.noApiCall(
+          args.length > 0
+              ? new Api() {
+                @Override
+                public void apiLevel22() {
+                  throw new RuntimeException("Foo");
+                }
+              }
+              : null);
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelVirtualTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
index f09f09d..a366d8a 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelNoInliningOfHigherApiLevelVirtualTest.ApiCaller.callVirtualMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
@@ -49,6 +50,7 @@
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
+        .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1))
         .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, AndroidApiLevel.L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/compilerapi/BinaryCompatibilityTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/BinaryCompatibilityTestCollection.java
index 51a307b..db9f09e 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/BinaryCompatibilityTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/BinaryCompatibilityTestCollection.java
@@ -64,6 +64,9 @@
   /** Additional classes that should always be included together to run tests. */
   public abstract List<Class<?>> getAdditionalClassesForTests();
 
+  /** Additional classes that should always be included together to run the pending tests. */
+  public abstract List<Class<?>> getPendingAdditionalClassesForTests();
+
   /** Additional JVM args supplied to any external execution. */
   public abstract List<String> getVmArgs();
 
@@ -92,7 +95,8 @@
 
   public void runJunitOnTestClass(Class<? extends T> test) throws Exception {
     List<Class<? extends T>> testClasses = Collections.singletonList(test);
-    runJunitOnTestClasses(generateJarForTestClasses(testClasses), testClasses);
+    runJunitOnTestClasses(
+        generateJarForTestClasses(testClasses, getPendingAdditionalClassesForTests()), testClasses);
   }
 
   private void runJunitOnTestClasses(Path testJar, Collection<Class<? extends T>> tests)
@@ -140,10 +144,12 @@
   }
 
   public Path generateJarForCheckedInTestClasses() throws Exception {
-    return generateJarForTestClasses(getCheckedInTestClasses());
+    return generateJarForTestClasses(getCheckedInTestClasses(), Collections.emptyList());
   }
 
-  private Path generateJarForTestClasses(Collection<Class<? extends T>> classes) throws Exception {
+  private Path generateJarForTestClasses(
+      Collection<Class<? extends T>> classes, List<Class<?>> additionalPendingClassesForTest)
+      throws Exception {
     Path jar = getTemp().newFolder().toPath().resolve("test.jar");
     ZipBuilder zipBuilder = ZipBuilder.builder(jar);
     for (Class<? extends T> test : classes) {
@@ -162,6 +168,11 @@
         getAdditionalClassesForTests().stream()
             .map(ToolHelper::getClassFileForTestClass)
             .collect(Collectors.toList()));
+    zipBuilder.addFilesRelative(
+        ToolHelper.getClassPathForTests(),
+        additionalPendingClassesForTest.stream()
+            .map(ToolHelper::getClassFileForTestClass)
+            .collect(Collectors.toList()));
     return zipBuilder.build();
   }
 
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
index 654eafd..100567d 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
@@ -5,8 +5,15 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.compilerapi.mockdata.MockClass;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Collections;
 import java.util.List;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
@@ -20,6 +27,8 @@
 @RunWith(Parameterized.class)
 public abstract class CompilerApiTest {
 
+  public static final Object PARAMETERS = "none";
+
   public static final String API_TEST_MODE_KEY = "API_TEST_MODE";
   public static final String API_TEST_MODE_EXTERNAL = "external";
 
@@ -34,11 +43,13 @@
     if (runtimes != null && !runtimes.contains("none")) {
       return Collections.emptyList();
     }
-    return Collections.singletonList("none");
+    return Collections.singletonList(PARAMETERS);
   }
 
+  @Rule public final TemporaryFolder temp = new TemporaryFolder();
+
   public CompilerApiTest(Object none) {
-    assertEquals("none", none);
+    assertEquals(PARAMETERS, none);
   }
 
   /** Predicate to determine if the test is being run externally. */
@@ -50,4 +61,30 @@
   public boolean isRunningR8Lib() {
     return API_TEST_LIB_YES.equals(System.getProperty(API_TEST_LIB_KEY));
   }
+
+  public Path getNewTempFolder() throws IOException {
+    return temp.newFolder().toPath();
+  }
+
+  public Class<?> getMockClass() {
+    return MockClass.class;
+  }
+
+  public Path getJava8RuntimeJar() {
+    return Paths.get("third_party", "openjdk", "openjdk-rt-1.8", "rt.jar");
+  }
+
+  public List<String> getKeepMainRules(Class<?> clazz) {
+    return Collections.singletonList(
+        "-keep class " + clazz.getName() + " { public static void main(java.lang.String[]); }");
+  }
+
+  public Path getPathForClass(Class<?> clazz) {
+    String file = clazz.getName().replace('.', '/') + ".class";
+    return Paths.get("build", "classes", "java", "test", file);
+  }
+
+  public byte[] getBytesForClass(Class<?> clazz) throws IOException {
+    return Files.readAllBytes(getPathForClass(clazz));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index 8c1e8a5..99c2cc8 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -8,6 +8,8 @@
 import static com.android.tools.r8.ToolHelper.isTestingR8Lib;
 
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.mapid.CustomMapIdTest;
+import com.android.tools.r8.compilerapi.mockdata.MockClass;
 import com.android.tools.r8.compilerapi.testsetup.ApiTestingSetUpTest;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
@@ -26,9 +28,7 @@
       ImmutableList.of(ApiTestingSetUpTest.ApiTest.class);
 
   private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
-      ImmutableList.of(
-          // No pending APIs.
-          );
+      ImmutableList.of(CustomMapIdTest.ApiTest.class);
 
   private final TemporaryFolder temp;
 
@@ -53,7 +53,12 @@
 
   @Override
   public List<Class<?>> getAdditionalClassesForTests() {
-    return ImmutableList.of(CompilerApiTest.class);
+    return ImmutableList.of(CompilerApiTest.class, MockClass.class);
+  }
+
+  @Override
+  public List<Class<?>> getPendingAdditionalClassesForTests() {
+    return ImmutableList.of();
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/compilerapi/mapid/CustomMapIdTest.java b/src/test/java/com/android/tools/r8/compilerapi/mapid/CustomMapIdTest.java
new file mode 100644
index 0000000..ca7f819
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/mapid/CustomMapIdTest.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2021, 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.compilerapi.mapid;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.MapIdEnvironment;
+import com.android.tools.r8.MarkerMatcher;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.ThrowingBiConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.function.Function;
+import org.junit.Test;
+
+public class CustomMapIdTest extends CompilerApiTestRunner {
+
+  public CustomMapIdTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  @Test
+  public void testDefaultMapId() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(test::runDefaultMapId, hash -> hash.substring(0, 7));
+  }
+
+  @Test
+  public void testCustomMapId() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(test::runCustomMapId, hash -> hash);
+  }
+
+  private String getMapHash(String mapping) {
+    String lineHeader = "# pg_map_hash: SHA-256 ";
+    int i = mapping.indexOf(lineHeader);
+    assertTrue(i >= 0);
+    int start = i + lineHeader.length();
+    int end = mapping.indexOf('\n', start);
+    return mapping.substring(start, end);
+  }
+
+  private void runTest(
+      ThrowingBiConsumer<ProgramConsumer, StringConsumer, Exception> test,
+      Function<String, String> hashToId)
+      throws Exception {
+    Path output = temp.newFolder().toPath().resolve("out.jar");
+    StringBuilder mappingBuilder = new StringBuilder();
+    BooleanBox didGetMappingContent = new BooleanBox(false);
+    test.accept(
+        new DexIndexedConsumer.ArchiveConsumer(output),
+        (mappingContent, handler) -> {
+          mappingBuilder.append(mappingContent);
+          didGetMappingContent.set(true);
+        });
+    assertTrue(didGetMappingContent.get());
+
+    // Extract the map hash from the file. This is always set by R8 to a SHA 256 hash.
+    String mappingContent = mappingBuilder.toString();
+    String mapHash = getMapHash(mappingContent);
+    assertEquals(64, mapHash.length());
+
+    // Check the map id is also defined in the map file.
+    String mapId = hashToId.apply(mapHash);
+    assertThat(mappingContent, containsString("pg_map_id: " + mapId + "\n"));
+
+    // Check that the map id is also present in the markers.
+    CodeInspector inspector = new CodeInspector(output);
+    Collection<Marker> markers = inspector.getMarkers();
+    MarkerMatcher.assertMarkersMatch(markers, MarkerMatcher.markerPgMapId(equalTo(mapId)));
+    assertEquals(1, markers.size());
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void runDefaultMapId(ProgramConsumer programConsumer, StringConsumer mappingConsumer)
+        throws Exception {
+      R8.run(
+          R8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addProguardConfiguration(getKeepMainRules(getMockClass()), Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setProgramConsumer(programConsumer)
+              .setProguardMapConsumer(mappingConsumer)
+              .build());
+    }
+
+    public void runCustomMapId(ProgramConsumer programConsumer, StringConsumer mappingConsumer)
+        throws Exception {
+      R8.run(
+          R8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addProguardConfiguration(getKeepMainRules(getMockClass()), Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setMapIdProvider(MapIdEnvironment::getMapHash)
+              .setProgramConsumer(programConsumer)
+              .setProguardMapConsumer(mappingConsumer)
+              .build());
+    }
+
+    @Test
+    public void testDefaultMapId() throws Exception {
+      runDefaultMapId(DexIndexedConsumer.emptyConsumer(), StringConsumer.emptyConsumer());
+    }
+
+    @Test
+    public void testCustomMapId() throws Exception {
+      runCustomMapId(DexIndexedConsumer.emptyConsumer(), StringConsumer.emptyConsumer());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/mockdata/MockClass.java b/src/test/java/com/android/tools/r8/compilerapi/mockdata/MockClass.java
new file mode 100644
index 0000000..340e532
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/mockdata/MockClass.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, 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.compilerapi.mockdata;
+
+// Class to use as data for the compilation.
+public class MockClass {
+
+  public static void main(String[] args) {
+    System.out.println("Hello world!");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index d307de3..004a443 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -20,11 +20,9 @@
 @RunWith(Parameterized.class)
 public class LineNumberOptimizationTest extends DebugTestBase {
 
-  private static final int[] ORIGINAL_LINE_NUMBERS = {20, 7, 8, 28, 8, 20, 21, 12, 21, 22, 16, 22};
   private static final int[] ORIGINAL_LINE_NUMBERS_DEBUG = {
     20, 7, 8, 28, 29, 9, 21, 12, 13, 22, 16, 17
   };
-  private static final int[] OPTIMIZED_LINE_NUMBERS = {1, 1, 2, 1, 2, 1, 2, 3, 2, 3, 4, 3};
 
   private static final String CLASS1 = "LineNumberOptimization1";
   private static final String CLASS2 = "LineNumberOptimization2";
@@ -82,18 +80,6 @@
   }
 
   @Test
-  public void testNotOptimized() throws Throwable {
-    assumeMappingIsNotToPCs();
-    testRelease(makeConfig(LineNumberOptimization.OFF, false, false), ORIGINAL_LINE_NUMBERS);
-  }
-
-  @Test
-  public void testNotOptimizedWithMap() throws Throwable {
-    assumeMappingIsNotToPCs();
-    testRelease(makeConfig(LineNumberOptimization.OFF, true, false), ORIGINAL_LINE_NUMBERS);
-  }
-
-  @Test
   public void testNotOptimizedByEnablingDebug() throws Throwable {
     testDebug(makeConfig(LineNumberOptimization.OFF, false, true), ORIGINAL_LINE_NUMBERS_DEBUG);
   }
@@ -103,20 +89,6 @@
     testDebug(makeConfig(LineNumberOptimization.OFF, true, true), ORIGINAL_LINE_NUMBERS_DEBUG);
   }
 
-  @Test
-  public void testOptimized() throws Throwable {
-    assumeMappingIsNotToPCs();
-    DebugTestConfig config = makeConfig(LineNumberOptimization.ON, false, false);
-    config.allowUsingPcForMissingLineNumberTable();
-    testRelease(config, OPTIMIZED_LINE_NUMBERS);
-  }
-
-  @Test
-  public void testOptimizedWithMap() throws Throwable {
-    assumeMappingIsNotToPCs();
-    testRelease(makeConfig(LineNumberOptimization.ON, true, false), ORIGINAL_LINE_NUMBERS);
-  }
-
   private void testDebug(DebugTestConfig config, int[] lineNumbers) throws Throwable {
     runDebugTest(
         config,
@@ -160,55 +132,4 @@
         checkLine(FILE1, lineNumbers[11]),
         run());
   }
-
-  // If we compile in release mode the line numbers are slightly different from the debug mode.
-  // That's why we need a different set of checks for the release mode.
-  //
-  // In release mode void returns don't have line number information. On the other hand, because of
-  // the line number information is moved as late as possible stepping in the debugger is different:
-  // After a method call we step again on the invoke instructions's line number before moving onto
-  // the next instruction.
-  private void testRelease(DebugTestConfig config, int[] lineNumbers) throws Throwable {
-    runDebugTest(
-        config,
-        CLASS1,
-        breakpoint(CLASS1, "main", MAIN_SIGNATURE),
-        run(),
-        checkMethod(CLASS1, "main", MAIN_SIGNATURE),
-        checkLine(FILE1, lineNumbers[0]),
-        stepInto(),
-        checkMethod(CLASS1, "callThisFromSameFile", "()V"),
-        checkLine(FILE1, lineNumbers[1]),
-        stepOver(),
-        checkMethod(CLASS1, "callThisFromSameFile", "()V"),
-        checkLine(FILE1, lineNumbers[2]),
-        stepInto(INTELLIJ_FILTER),
-        checkMethod(CLASS2, "callThisFromAnotherFile", "()V"),
-        checkLine(FILE2, lineNumbers[3]),
-        stepOver(),
-        checkMethod(CLASS1, "callThisFromSameFile", "()V"),
-        checkLine(FILE1, lineNumbers[4]),
-        stepOver(),
-        checkMethod(CLASS1, "main", MAIN_SIGNATURE),
-        checkLine(FILE1, lineNumbers[5]),
-        stepOver(),
-        checkMethod(CLASS1, "main", MAIN_SIGNATURE),
-        checkLine(FILE1, lineNumbers[6]),
-        stepInto(),
-        checkMethod(CLASS1, "callThisFromSameFile", "(I)V"),
-        checkLine(FILE1, lineNumbers[7]),
-        stepOver(),
-        checkMethod(CLASS1, "main", MAIN_SIGNATURE),
-        checkLine(FILE1, lineNumbers[8]),
-        stepOver(),
-        checkMethod(CLASS1, "main", MAIN_SIGNATURE),
-        checkLine(FILE1, lineNumbers[9]),
-        stepInto(),
-        checkMethod(CLASS1, "callThisFromSameFile", "(II)V"),
-        checkLine(FILE1, lineNumbers[10]),
-        stepOver(),
-        checkMethod(CLASS1, "main", MAIN_SIGNATURE),
-        checkLine(FILE1, lineNumbers[11]),
-        run());
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugSetFileSmaliTest.java b/src/test/java/com/android/tools/r8/debuginfo/DebugSetFileSmaliTest.java
new file mode 100644
index 0000000..e64b7d7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugSetFileSmaliTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2021, 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.debuginfo;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersBuilder;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexDebugEvent.SetFile;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DebugSetFileSmaliTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  public DebugSetFileSmaliTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return TestParametersBuilder.builder()
+        .withDexRuntimes()
+        .withApiLevel(AndroidApiLevel.B)
+        .build();
+  }
+
+  private static final String CLASS_NAME = "Test";
+  private static final String CLASS_SOURCE_FILE = "Test.java";
+  private static final String DEBUG_SET_FILE = "SomeFile.java";
+
+  @Test
+  public void test() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder(CLASS_NAME);
+    builder.setSourceFile(CLASS_SOURCE_FILE);
+    builder.addMainMethod(
+        2,
+        ".line 1",
+        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "    const-string        v1, \"Hello, world!\"",
+        // If the following invoke is not present legacy VMs fail with "invalid debug stream"!?
+        "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        ".source \"" + DEBUG_SET_FILE + "\"",
+        ".line 42",
+        "    const               v0, 0",
+        "    throw v0");
+
+    testForD8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramDexFileData(builder.compile())
+        .compile()
+        .inspect(
+            inspector -> {
+              assertFalse(
+                  Arrays.stream(
+                          inspector
+                              .clazz(CLASS_NAME)
+                              .mainMethod()
+                              .getMethod()
+                              .getCode()
+                              .asDexCode()
+                              .getDebugInfo()
+                              .events)
+                      .anyMatch(e -> e instanceof SetFile));
+            })
+        .run(parameters.getRuntime(), CLASS_NAME)
+        .assertFailureWithErrorThatMatches(
+            containsString("at Test.main(" + CLASS_SOURCE_FILE + ":42)"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
index aeb159d..ee66aab 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
@@ -188,31 +188,31 @@
   private void assertGeneratedKeepRulesAreCorrect(String keepRules) {
     String expectedResult =
         StringUtils.lines(
-            "-keep class j$.util.List$-EL {",
-            "    void sort(java.util.List, java.util.Comparator);",
-            "}",
             "-keep class j$.util.Collection$-EL {",
             "    j$.util.stream.Stream stream(java.util.Collection);",
             "}",
-            "-keep class j$.util.stream.IntStream$-CC {",
-            "    j$.util.stream.IntStream range(int, int);",
-            "}",
             "-keep class j$.util.Comparator$-CC {",
             "    java.util.Comparator comparingInt(j$.util.function.ToIntFunction);",
             "}",
+            "-keep class j$.util.DesugarArrays {",
+            "    j$.util.Spliterator spliterator(java.lang.Object[]);",
+            "    j$.util.Spliterator spliterator(java.lang.Object[], int, int);",
+            "    j$.util.stream.Stream stream(java.lang.Object[]);",
+            "    j$.util.stream.Stream stream(java.lang.Object[], int, int);",
+            "}",
+            "-keep class j$.util.List$-EL {",
+            "    void sort(java.util.List, java.util.Comparator);",
+            "}",
             "-keep class j$.util.Set$-EL {",
             "    j$.util.Spliterator spliterator(java.util.Set);",
             "}",
-            "-keep class j$.util.DesugarArrays {",
-            "    j$.util.Spliterator spliterator(java.lang.Object[]);",
-            "    j$.util.stream.Stream stream(java.lang.Object[], int, int);",
-            "    j$.util.stream.Stream stream(java.lang.Object[]);",
-            "    j$.util.Spliterator spliterator(java.lang.Object[], int, int);",
+            "-keep class j$.util.Spliterator",
+            "-keep class j$.util.function.ToIntFunction { *; }",
+            "-keep class j$.util.stream.IntStream$-CC {",
+            "    j$.util.stream.IntStream range(int, int);",
             "}",
             "-keep class j$.util.stream.IntStream",
-            "-keep class j$.util.stream.Stream",
-            "-keep class j$.util.Spliterator",
-            "-keep class j$.util.function.ToIntFunction { *; }");
+            "-keep class j$.util.stream.Stream");
     assertEquals(expectedResult, keepRules);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
index 8553034..26561ec 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
@@ -133,7 +133,12 @@
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(
                 LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
-            .addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true)
+            .addOptionsModification(
+                options -> {
+                  options.testing.enableD8ResourcesPassThrough = true;
+                  options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer();
+                  options.testing.trackDesugaredAPIConversions = true;
+                })
             .compile();
     TestDiagnosticMessages diagnosticMessages = compile.getDiagnosticMessages();
     assertTrue(
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
index 66bdd07..6fa1514 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.desugar.nestaccesscontrol;
 
-import static com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest.uploadJarsToCloudStorageIfTestFails;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertTrue;
@@ -13,7 +12,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
@@ -55,7 +53,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withCfRuntimes().build();
+    return getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK11).build();
   }
 
   @BeforeClass
@@ -95,7 +93,6 @@
 
   private Path[] jarsToCompare() {
     return new Path[] {
-      ToolHelper.R8_WITH_RELOCATED_DEPS_JAR,
       ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR,
       r8Lib11NoDesugar,
       r8Lib11Desugar
@@ -108,13 +105,8 @@
     Path prevGeneratedJar = null;
     String prevRunResult = null;
     for (Path jar : jarsToCompare()) {
-      // All jars except ToolHelper.R8_WITH_RELOCATED_DEPS_JAR are compiled for JDK11.
-      TestRuntime runtime =
-          jar == ToolHelper.R8_WITH_RELOCATED_DEPS_JAR
-              ? parameters.getRuntime()
-              : TestRuntime.getCheckedInJdk11();
       Path generatedJar =
-          testForExternalR8(Backend.CF, runtime)
+          testForExternalR8(Backend.CF, parameters.getRuntime())
               .useProvidedR8(jar)
               .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION))
               .addKeepRules(HELLO_KEEP)
@@ -140,7 +132,7 @@
   public void testR8() throws Exception {
     Assume.assumeTrue(!ToolHelper.isWindows());
     Assume.assumeTrue(parameters.isCfRuntime());
-    Assume.assumeTrue(CfVm.JDK11 == parameters.getRuntime().asCf().getVm());
+    Assume.assumeTrue(CfVm.JDK11.lessThanOrEqual(parameters.getRuntime().asCf().getVm()));
     Path prevGeneratedJar = null;
     for (Path jar : jarsToCompare()) {
       Path generatedJar =
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToMissingMethodDeclaredInSuperClassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToMissingMethodDeclaredInSuperClassTest.java
new file mode 100644
index 0000000..fca3a67
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToMissingMethodDeclaredInSuperClassTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialToMissingMethodDeclaredInSuperClassTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeSpecialToMissingMethodDeclaredInSuperClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(A.class, Main.class)
+        .addProgramClassFileData(getClassWithTransformedInvoked())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A.foo()");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, Main.class)
+        .addProgramClassFileData(getClassWithTransformedInvoked())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  private byte[] getClassWithTransformedInvoked() throws IOException {
+    return transformer(B.class)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              assertEquals(INVOKEVIRTUAL, opcode);
+              assertEquals("notify", name);
+              continuation.visitMethodInsn(
+                  INVOKESPECIAL, binaryName(B.class), "foo", descriptor, isInterface);
+            })
+        .transform();
+  }
+
+  public static class A {
+
+    public void foo() {
+      System.out.println("A.foo()");
+    }
+  }
+
+  public static class B extends A {
+
+    public void bar() {
+      notify(); // Will be rewritten to invoke-special B.foo() which is missing, but found in A.
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new B().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToMissingMethodDeclaredInSuperInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToMissingMethodDeclaredInSuperInterfaceTest.java
new file mode 100644
index 0000000..2a9e1c0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToMissingMethodDeclaredInSuperInterfaceTest.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialToMissingMethodDeclaredInSuperInterfaceTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeSpecialToMissingMethodDeclaredInSuperInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(A.class, C.class, Main.class)
+        .addProgramClassFileData(getClassWithTransformedInvoked())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A.foo()");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, C.class, Main.class)
+        .addProgramClassFileData(getClassWithTransformedInvoked())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        // TODO(b/202381923): invoke-special is not mapped correctly.
+        .applyIf(
+            parameters.canUseDefaultAndStaticInterfaceMethods(),
+            runResult -> runResult.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            runResult -> runResult.assertSuccessWithOutputLines("A.foo()"));
+  }
+
+  private byte[] getClassWithTransformedInvoked() throws IOException {
+    return transformer(B.class)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              assertEquals(INVOKEVIRTUAL, opcode);
+              assertEquals("notify", name);
+              continuation.visitMethodInsn(
+                  INVOKESPECIAL, binaryName(B.class), "foo", descriptor, true);
+            })
+        .transform();
+  }
+
+  public interface A {
+
+    default void foo() {
+      System.out.println("A.foo()");
+    }
+  }
+
+  public interface B extends A {
+
+    default void bar() {
+      notify(); // Will be rewritten to invoke-special B.foo() which is missing, but found in A.
+    }
+  }
+
+  static class C implements B {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new C().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationTestBase.java
index aea7348..f280e80 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationTestBase.java
@@ -5,14 +5,19 @@
 
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.analysis.ProtoApplicationStats;
 import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -84,15 +89,33 @@
     return builder.build();
   }
 
-  protected List<Path> getLibraryFiles() {
+  protected Path getLibraryFile() {
     Path filtered =
         Paths.get(base).resolve("legacy_YouTubeRelease_combined_library_jars_filtered.jar");
     if (filtered.toFile().exists()) {
-      return ImmutableList.of(filtered);
+      return filtered;
     }
     Path unfiltered = Paths.get(base, "legacy_YouTubeRelease_combined_library_jars.jar");
     assertTrue(unfiltered.toFile().exists());
-    return ImmutableList.of(unfiltered);
+    return unfiltered;
+  }
+
+  Path getLibraryFileWithoutDesugaredLibrary() throws IOException {
+    Path libraryFile = getLibraryFile();
+    Path filteredLibraryFile =
+        Paths.get(libraryFile.toString().replace(".jar", "desugared_lib_filtered.jar"));
+    ArchiveConsumer consumer = new ArchiveConsumer(filteredLibraryFile);
+    ZipUtils.iter(
+        libraryFile,
+        (entry, inputStream) -> {
+          String entryString = entry.toString();
+          if (entryString.endsWith(".class") && !entryString.startsWith("j$")) {
+            byte[] bytes = ByteStreams.toByteArray(inputStream);
+            consumer.accept(ByteDataView.of(bytes), TestBase.extractClassDescriptor(bytes), null);
+          }
+        });
+    consumer.finished(null);
+    return filteredLibraryFile;
   }
 
   protected List<Path> getMainDexRuleFiles() {
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
index 24ae3c1..9ae3939 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Paths;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -43,7 +44,7 @@
     LibrarySanitizer librarySanitizer =
         new LibrarySanitizer(temp)
             .addProgramFiles(getProgramFiles())
-            .addLibraryFiles(getLibraryFiles())
+            .addLibraryFiles(ImmutableList.of(getLibraryFile()))
             .sanitize()
             .assertSanitizedProguardConfigurationIsEmpty();
 
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1612Test.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1612Test.java
index 830d6fc..50442c9 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1612Test.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1612Test.java
@@ -115,7 +115,7 @@
       throws IOException, CompilationFailedException {
     return testForR8(parameters.getBackend())
         .addProgramFiles(getProgramFiles())
-        .addLibraryFiles(getLibraryFiles())
+        .addLibraryFiles(getLibraryFileWithoutDesugaredLibrary())
         .addKeepRuleFiles(getKeepRuleFiles())
         .addDontWarn("android.app.Activity$TranslucentConversionListener")
         .allowDiagnosticMessages()
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
index 0b76040..0c3abdd 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
@@ -115,7 +115,7 @@
       throws IOException, CompilationFailedException {
     return testForR8(parameters.getBackend())
         .addProgramFiles(getProgramFiles())
-        .addLibraryFiles(getLibraryFiles())
+        .addLibraryFiles(getLibraryFileWithoutDesugaredLibrary())
         .addKeepRuleFiles(getKeepRuleFiles())
         .addDontWarn("android.app.Activity$TranslucentConversionListener")
         .allowDiagnosticMessages()
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index e418ced..5bae6d3 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -11,21 +11,23 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.SgetObject;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Streams;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -39,6 +41,9 @@
 @RunWith(Parameterized.class)
 public class KotlinClassInlinerTest extends AbstractR8KotlinTestBase {
 
+  private final String INTERNAL_SYNTHETIC_MAIN_PREFIX =
+      "class_inliner_lambda_j_style.MainKt$$InternalSyntheticLambda";
+
   @Parameterized.Parameters(name = "{0}, {1}")
   public static List<Object[]> data() {
     return buildParameters(
@@ -50,19 +55,12 @@
     super(parameters, kotlinParameters, true);
   }
 
-  private static boolean isKStyleLambda(DexClass clazz) {
-    return clazz.getSuperType().getTypeName().equals("kotlin.jvm.internal.Lambda");
-  }
-
-  private static boolean isJStyleLambda(DexClass clazz) {
-    return clazz.getSuperType().getTypeName().equals(Object.class.getTypeName())
-        && clazz.getInterfaces().size() == 1;
-  }
-
   @Test
   public void testJStyleLambdas() throws Exception {
-    // TODO(b/185497606): Unable to class inline j style lambdas.
-    assumeTrue(kotlinc.isNot(KOTLINC_1_5_0));
+    // SAM interfaces lambdas are implemented by invoke dynamic in kotlin 1.5 unlike 1.4 where a
+    // class is generated for each. In CF we leave invokeDynamic but for DEX we desugar the classes
+    // and merge them.
+    boolean hasKotlinCGeneratedLambdaClasses = kotlinParameters.isOlderThan(KOTLINC_1_5_0);
     String mainClassName = "class_inliner_lambda_j_style.MainKt";
     runTest(
             "class_inliner_lambda_j_style",
@@ -74,7 +72,27 @@
                     .addNoHorizontalClassMergingRule(
                         "class_inliner_lambda_j_style.SamIface$Consumer")
                     .addHorizontallyMergedClassesInspector(
-                        inspector ->
+                        inspector -> {
+                          if (!hasKotlinCGeneratedLambdaClasses && testParameters.isCfRuntime()) {
+                            inspector.assertNoClassesMerged();
+                          } else if (!hasKotlinCGeneratedLambdaClasses) {
+                            Set<Set<DexType>> mergeGroups = inspector.getMergeGroups();
+                            assertEquals(2, mergeGroups.size());
+                            IntBox seenLambdas = new IntBox();
+                            assertTrue(
+                                mergeGroups.stream()
+                                    .flatMap(Collection::stream)
+                                    .allMatch(
+                                        type -> {
+                                          boolean isDesugaredLambda =
+                                              type.toSourceString()
+                                                  .startsWith(INTERNAL_SYNTHETIC_MAIN_PREFIX);
+                                          if (isDesugaredLambda) {
+                                            seenLambdas.increment();
+                                          }
+                                          return isDesugaredLambda;
+                                        }));
+                          } else {
                             inspector
                                 .assertIsCompleteMergeGroup(
                                     "class_inliner_lambda_j_style.MainKt$testStateless$1",
@@ -86,16 +104,31 @@
                                     "class_inliner_lambda_j_style.MainKt$testStateful$2$1",
                                     "class_inliner_lambda_j_style.MainKt$testStateful$3",
                                     "class_inliner_lambda_j_style.MainKt$testStateful2$1",
-                                    "class_inliner_lambda_j_style.MainKt$testStateful3$1"))
+                                    "class_inliner_lambda_j_style.MainKt$testStateful3$1");
+                          }
+                        })
                     .noClassInlining())
         .inspect(
             inspector -> {
-              assertThat(
-                  inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateless$1"),
-                  isPresent());
-              assertThat(
-                  inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1"),
-                  isPresent());
+              if (testParameters.isCfRuntime() && !hasKotlinCGeneratedLambdaClasses) {
+                assertEquals(5, inspector.allClasses().size());
+              } else if (!hasKotlinCGeneratedLambdaClasses) {
+                assertThat(
+                    inspector.clazz(
+                        "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda1"),
+                    isPresent());
+                assertThat(
+                    inspector.clazz(
+                        "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda2"),
+                    isPresent());
+              } else {
+                assertThat(
+                    inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateless$1"),
+                    isPresent());
+                assertThat(
+                    inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1"),
+                    isPresent());
+              }
             });
 
     runTest(
@@ -109,14 +142,28 @@
                         "class_inliner_lambda_j_style.SamIface$Consumer"))
         .inspect(
             inspector -> {
+              if (testParameters.isCfRuntime() && !hasKotlinCGeneratedLambdaClasses) {
+                assertEquals(5, inspector.allClasses().size());
+                return;
+              }
               // TODO(b/173337498): MainKt$testStateless$1 should always be class inlined.
-              assertThat(
-                  inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateless$1"),
-                  notIf(isPresent(), testParameters.isDexRuntime()));
+              if (!hasKotlinCGeneratedLambdaClasses) {
+                assertThat(
+                    inspector.clazz(
+                        "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda1"),
+                    isPresent());
+              } else {
+                assertThat(
+                    inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateless$1"),
+                    notIf(isPresent(), testParameters.isDexRuntime()));
+              }
 
               // TODO(b/173337498): MainKt$testStateful$1 should be class inlined.
               assertThat(
-                  inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1"),
+                  inspector.clazz(
+                      !hasKotlinCGeneratedLambdaClasses
+                          ? "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda2"
+                          : "class_inliner_lambda_j_style.MainKt$testStateful$1"),
                   isPresent());
             });
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
index 41552ac..4fad86a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
@@ -73,8 +73,6 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/185497606): Unable to merge jstyle lambda.
-    assumeTrue(kotlinc.isNot(KOTLINC_1_5_0));
     testForR8(parameters.getBackend())
         .addProgramFiles(getProgramFiles())
         .addKeepMainRule(getMainClassName())
@@ -90,13 +88,15 @@
   }
 
   private void inspect(HorizontallyMergedClassesInspector inspector) throws IOException {
+    boolean hasKotlinCGeneratedLambdaClasses = kotlinParameters.isOlderThan(KOTLINC_1_5_0);
     // Get the Kotlin lambdas in the input.
     KotlinLambdasInInput lambdasInInput =
         KotlinLambdasInInput.create(getProgramFiles(), getTestName());
-    assertEquals(39, lambdasInInput.getNumberOfJStyleLambdas());
+    assertEquals(
+        hasKotlinCGeneratedLambdaClasses ? 39 : 0, lambdasInInput.getNumberOfJStyleLambdas());
     assertEquals(0, lambdasInInput.getNumberOfKStyleLambdas());
 
-    if (!allowAccessModification) {
+    if (!allowAccessModification && hasKotlinCGeneratedLambdaClasses) {
       // Only a subset of all J-style Kotlin lambdas are merged without -allowaccessmodification.
       Set<ClassReference> unmergedLambdas =
           ImmutableSet.of(
@@ -119,8 +119,11 @@
       return;
     }
 
-    // All J-style Kotlin lambdas are merged with -allowaccessmodification.
-    inspector.assertClassReferencesMerged(lambdasInInput.getJStyleLambdas());
+    if (!parameters.isCfRuntime() || hasKotlinCGeneratedLambdaClasses) {
+      // All J-style Kotlin lambdas are merged with -allowaccessmodification or because they are
+      // generated by R8.
+      inspector.assertClassReferencesMerged(lambdasInInput.getJStyleLambdas());
+    }
   }
 
   private String getExpectedOutput() {
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 04921e2..80863b5 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -37,6 +37,7 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -855,7 +856,7 @@
             new SynthesizedCode(
                 (ignored, callerPosition) -> new ReturnVoidCode(voidReturnMethod, callerPosition)) {
               @Override
-              public Consumer<UseRegistry> getRegistryCallback() {
+              public Consumer<UseRegistry> getRegistryCallback(DexClassAndMethod method) {
                 throw new Unreachable();
               }
             };
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java
deleted file mode 100644
index de654b8..0000000
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright (c) 2017, 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;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.code.ConstString;
-import com.android.tools.r8.code.InvokeVirtual;
-import com.android.tools.r8.code.ReturnVoid;
-import com.android.tools.r8.code.SgetObject;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexCode;
-import com.android.tools.r8.graph.DexDebugEvent.SetFile;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.shaking.ProguardConfiguration;
-import com.android.tools.r8.smali.SmaliBuilder;
-import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
-import com.android.tools.r8.smali.SmaliTestBase;
-import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.List;
-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;
-
-/**
- * Tests -renamesourcefileattribute.
- */
-@RunWith(Parameterized.class)
-public class RenameSourceFileSmaliTest extends SmaliTestBase {
-
-  private static final String TEST_FILE = "TestFile.java";
-
-  private static final List<String> DEFAULT_PG_CONFIGS =
-      ImmutableList.of(
-          "-keep class *** { *; }",
-          "-dontoptimize",
-          "-keepattributes SourceFile,LineNumberTable");
-
-  private void configure(ProguardConfiguration.Builder pg) {
-    if (renaming) {
-      pg.setRenameSourceFileAttribute(TEST_FILE);
-    }
-  }
-
-  @Parameter
-  public boolean renaming;
-
-  @Parameters(name="renaming:{0}")
-  public static Object[] parameters() {
-    return new Object[] {true, false};
-  }
-
-  /**
-   * replica of {@link RunArtSmokeTest#test}
-   */
-  @Test
-  public void artSmokeTest() throws Exception {
-    // Build simple "Hello, world!" application.
-    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-    String originalSourceFile = DEFAULT_CLASS_NAME + FileUtils.JAVA_EXTENSION;
-    builder.setSourceFile(originalSourceFile);
-    MethodSignature mainSignature = builder.addMainMethod(
-        2,
-        ".line 1",
-        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
-        "    const-string        v1, \"Hello, world!\"",
-        ".source \"PrintStream.java\"",
-        ".line 337",
-        "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
-        ".source \"" + originalSourceFile + "\"",
-        ".line 2",
-        "    return-void"
-    );
-    Path processedApp = runR8(builder, DEFAULT_PG_CONFIGS, this::configure, null);
-
-    DexClass mainClass = getClass(processedApp, DEFAULT_CLASS_NAME);
-    verifySourceFileInCodeItem(mainClass, originalSourceFile, TEST_FILE);
-
-    DexEncodedMethod mainMethod = getMethod(processedApp, mainSignature);
-    assertNotNull(mainMethod);
-
-    DexCode code = mainMethod.getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof SgetObject);
-    assertTrue(code.instructions[1] instanceof ConstString);
-    assertTrue(code.instructions[2] instanceof InvokeVirtual);
-    assertTrue(code.instructions[3] instanceof ReturnVoid);
-
-    // Run the generated code in Art.
-    String result = runArt(processedApp, DEFAULT_MAIN_CLASS_NAME);
-    assertEquals(StringUtils.lines("Hello, world!"), result);
-
-    verifySourceFileInDebugInfo(code);
-  }
-
-  private void verifySourceFileInCodeItem(DexClass clazz, String original, String rename) {
-    String processedSourceFile = clazz.sourceFile.toString();
-    if (renaming) {
-      assertEquals(rename, processedSourceFile);
-    } else {
-      assertEquals(original, processedSourceFile);
-    }
-  }
-
-  private void verifySourceFileInDebugInfo(DexCode code) {
-    assertNotNull(code.getDebugInfo());
-    assertNotEquals(0, code.getDebugInfo().events.length);
-    long setFileCount =
-        Arrays.stream(code.getDebugInfo().events)
-            .filter(dexDebugEvent -> dexDebugEvent instanceof SetFile)
-            .count();
-    if (renaming) {
-      assertEquals(0, setFileCount);
-    } else {
-      assertNotEquals(0, setFileCount);
-    }
-  }
-
-}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRepackagingTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRepackagingTest.java
new file mode 100644
index 0000000..cb4c8cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRepackagingTest.java
@@ -0,0 +1,123 @@
+// Copyright (c) 2021, 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 com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.R8TestRunResult;
+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.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ApplyMappingRepackagingTest extends TestBase {
+
+  @NeverClassInline
+  public static class A {
+
+    @NeverPropagateValue public int fieldA = 1;
+
+    @NeverPropagateValue public int fieldB = 2;
+
+    @NeverInline
+    public void methodA() {
+      System.out.println("A.methodA");
+    }
+
+    @NeverInline
+    public void methodB() {
+      System.out.println("A.methodB");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    @NeverInline
+    public void foo() {
+      System.out.println("B.foo");
+    }
+  }
+
+  public static class C {
+
+    public static void main(String[] args) {
+      System.out.println(new A().fieldA);
+      System.out.println(new A().fieldB);
+      new A().methodA();
+      new A().methodB();
+      new B().foo();
+    }
+  }
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ApplyMappingRepackagingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testApplyMappingFollowedByMinification()
+      throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
+    String[] pgMap =
+        new String[] {
+          A.class.getTypeName() + " -> baz:", "  int fieldA -> foo", "  void methodA() -> bar"
+        };
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(ApplyMappingRepackagingTest.class)
+            .enableInliningAnnotations()
+            .enableMemberValuePropagationAnnotations()
+            .enableNeverClassInliningAnnotations()
+            .addApplyMapping(StringUtils.lines(pgMap))
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(C.class)
+            .addKeepRules("-repackageclasses")
+            .run(parameters.getRuntime(), C.class)
+            .assertSuccessWithOutputLines("1", "2", "A.methodA", "A.methodB", "B.foo")
+            .inspect(
+                inspector -> {
+                  assertThat(inspector.clazz(B.class), isPresentAndRenamed());
+                  ClassSubject clazzA = inspector.clazz(A.class);
+                  assertThat(clazzA, isPresent());
+                  // TODO(b/202194059): Should be baz
+                  assertNotEquals("baz", clazzA.getFinalName());
+                  FieldSubject fieldA = clazzA.uniqueFieldWithName("fieldA");
+                  assertThat(fieldA, isPresent());
+                  // TODO(b/202194059): Should be foo
+                  assertNotEquals("foo", fieldA.getFinalName());
+                  MethodSubject methodA = clazzA.uniqueMethodWithName("methodA");
+                  assertThat(methodA, isPresent());
+                  // TODO(b/202194059): Should be bar
+                  assertNotEquals("bar", methodA.getFinalName());
+                  assertThat(clazzA.uniqueFieldWithName("fieldB"), isPresentAndRenamed());
+                  assertThat(clazzA.uniqueMethodWithName("methodB"), isPresentAndRenamed());
+                });
+    // Ensure that the proguard map is extended with all the new minified names.
+    for (String pgLine : pgMap) {
+      runResult.proguardMap().contains(pgLine);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java
index 458cb07..4ba3cbf 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java
@@ -10,7 +10,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.retraceproguard.StackTrace.StackTraceLine;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
@@ -24,14 +24,16 @@
 @RunWith(Parameterized.class)
 public class DesugarLambdaRetraceTest extends RetraceTestBase {
 
-  @Parameters(name = "Backend: {0}, mode: {1}, compat: {2}")
+  @Parameters(name = "{0}, mode: {1}, compat: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        ToolHelper.getBackends(), CompilationMode.values(), BooleanUtils.values());
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        CompilationMode.values(),
+        BooleanUtils.values());
   }
 
-  public DesugarLambdaRetraceTest(Backend backend, CompilationMode mode, boolean compat) {
-    super(backend, mode, compat);
+  public DesugarLambdaRetraceTest(TestParameters parameters, CompilationMode mode, boolean compat) {
+    super(parameters, mode, compat);
   }
 
   @Override
@@ -47,7 +49,7 @@
   private int expectedActualStackTraceHeight() {
     // In debug mode the expected stack trace height differs since there is no lambda desugaring
     // for CF.
-    return mode == CompilationMode.RELEASE ? 2 : (backend == Backend.CF ? 4 : 5);
+    return mode == CompilationMode.RELEASE ? 2 : (parameters.isCfRuntime() ? 4 : 5);
   }
 
   private boolean isSynthesizedLambdaFrame(StackTraceLine line) {
@@ -68,7 +70,7 @@
   private void checkIsSameExceptForFileName(
       StackTrace actualStackTrace, StackTrace retracedStackTrace) {
     // Even when SourceFile is present retrace replaces the file name in the stack trace.
-    if (backend == Backend.CF) {
+    if (parameters.isCfRuntime()) {
       // TODO(122440196): Additional code to locate issue.
       if (!isSameExceptForFileName(expectedStackTrace).matches(retracedStackTrace)) {
         System.out.println("Expected original:");
@@ -107,7 +109,7 @@
   private void checkIsSameExceptForFileNameAndLineNumber(
       StackTrace actualStackTrace, StackTrace retracedStackTrace) {
     // Even when SourceFile is present retrace replaces the file name in the stack trace.
-    if (backend == Backend.CF) {
+    if (parameters.isCfRuntime()) {
       // TODO(122440196): Additional code to locate issue.
       if (!isSameExceptForFileNameAndLineNumber(expectedStackTrace).matches(retracedStackTrace)) {
         System.out.println("Expected original:");
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
index 599fa3b..69d3fe6 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestBuilder;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -23,15 +23,17 @@
 @RunWith(Parameterized.class)
 public class DesugarStaticInterfaceMethodsRetraceTest extends RetraceTestBase {
 
-  @Parameters(name = "Backend: {0}, mode: {1}, compat: {2}")
+  @Parameters(name = "{0}, mode: {1}, compat: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        ToolHelper.getBackends(), CompilationMode.values(), BooleanUtils.values());
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        CompilationMode.values(),
+        BooleanUtils.values());
   }
 
   public DesugarStaticInterfaceMethodsRetraceTest(
-      Backend backend, CompilationMode mode, boolean compat) {
-    super(backend, mode, compat);
+      TestParameters parameters, CompilationMode mode, boolean compat) {
+    super(parameters, mode, compat);
   }
 
   @Override
@@ -54,11 +56,13 @@
   public void testSourceFileAndLineNumberTable() throws Exception {
     // TODO(b/186015503): This test fails when mapping via PCs.
     //  also the test should be updated to use TestParameters and api levels.
-    assumeTrue("b/186015503", !backend.isDex() || mode != CompilationMode.RELEASE);
+    assumeTrue("b/186015503", !parameters.isDexRuntime() || mode != CompilationMode.RELEASE);
     // This also fails when desugaring due to the change in companion method stacks.
     assumeTrue(
-        ToolHelper.getMinApiLevelForDexVm()
-            .isGreaterThanOrEqualTo(apiLevelWithDefaultInterfaceMethodsSupport()));
+        parameters.isCfRuntime()
+            || parameters
+                .getApiLevel()
+                .isGreaterThanOrEqualTo(apiLevelWithDefaultInterfaceMethodsSupport()));
     runTest(
         ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
         // For the desugaring to companion classes the retrace stacktrace is still the same
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java
index ae6a985..016d4ac 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java
@@ -11,12 +11,11 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
-import java.util.Collections;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -25,16 +24,20 @@
 @RunWith(Parameterized.class)
 public class InliningRetraceTest extends RetraceTestBase {
 
-  @Parameters(name = "Backend: {0}, mode: {1}")
+  @Parameters(name = "{0}, mode: {1}, compat: {2}")
   public static Collection<Object[]> data() {
-    return ToolHelper.getDexVm().getVersion() == Version.V5_1_1
-        ? Collections.emptyList()
-        : buildParameters(
-            ToolHelper.getBackends(), CompilationMode.values(), BooleanUtils.values());
+    return buildParameters(
+        getTestParameters()
+            .withCfRuntimes()
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build(),
+        CompilationMode.values(),
+        BooleanUtils.values());
   }
 
-  public InliningRetraceTest(Backend backend, CompilationMode mode, boolean value) {
-    super(backend, mode, value);
+  public InliningRetraceTest(TestParameters parameters, CompilationMode mode, boolean value) {
+    super(parameters, mode, value);
   }
 
   @Override
@@ -43,7 +46,12 @@
   }
 
   private int expectedActualStackTraceHeight() {
-    return mode == CompilationMode.RELEASE ? 1 : 4;
+    int height = mode == CompilationMode.RELEASE ? 1 : 4;
+    if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik()) {
+      // Dalvik places a stack trace line in the bottom.
+      height += 1;
+    }
+    return height;
   }
 
   @Test
@@ -60,7 +68,7 @@
   @Test
   public void testLineNumberTableOnly() throws Exception {
     assumeTrue(compat);
-    assumeTrue(backend == Backend.DEX);
+    assumeTrue(parameters.isDexRuntime());
     runTest(
         ImmutableList.of("-keepattributes LineNumberTable"),
         (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
@@ -72,7 +80,7 @@
   @Test
   public void testNoLineNumberTable() throws Exception {
     assumeTrue(compat);
-    assumeTrue(backend == Backend.DEX);
+    assumeTrue(parameters.isDexRuntime());
     runTest(
         ImmutableList.of(),
         (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
index c84c685..2200ea7 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.retraceproguard.StackTrace.StackTraceLine;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
@@ -15,12 +17,13 @@
 import org.junit.Before;
 
 public abstract class RetraceTestBase extends TestBase {
-  protected Backend backend;
+
+  protected TestParameters parameters;
   protected CompilationMode mode;
   protected boolean compat;
 
-  public RetraceTestBase(Backend backend, CompilationMode mode, boolean compat) {
-    this.backend = backend;
+  public RetraceTestBase(TestParameters parameters, CompilationMode mode, boolean compat) {
+    this.parameters = parameters;
     this.mode = mode;
     this.compat = compat;
   }
@@ -39,24 +42,25 @@
   public void setup() throws Exception {
     // Get the expected stack trace by running on the JVM.
     expectedStackTrace =
-        testForJvm()
-            .addTestClasspath()
-            .run(getMainClass())
+        testForRuntime(parameters)
+            .addProgramClasses(getClasses())
+            .run(parameters.getRuntime(), getMainClass())
             .assertFailure()
-            .map(StackTrace::extractFromJvm);
+            .map(StackTrace::extract);
   }
 
   public void runTest(List<String> keepRules, BiConsumer<StackTrace, StackTrace> checker)
       throws Exception {
     R8TestRunResult result =
-        (compat ? testForR8Compat(backend) : testForR8(backend))
+        (compat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
             .setMode(mode)
             .enableProguardTestOptions()
             .addProgramClasses(getClasses())
             .addKeepMainRule(getMainClass())
             .addKeepRules(keepRules)
+            .setMinApi(parameters.getApiLevel())
             .apply(this::configure)
-            .run(getMainClass())
+            .run(parameters.getRuntime(), getMainClass())
             .assertFailure();
 
     // Extract actual stack trace and retraced stack trace from failed run result.
@@ -70,4 +74,9 @@
 
     checker.accept(actualStackTrace, retracedStackTrace);
   }
+
+  protected boolean isNotDalvikNativeStartMethod(StackTraceLine retracedStackTraceLine) {
+    return !(retracedStackTraceLine.className.equals("dalvik.system.NativeStart")
+        && retracedStackTraceLine.methodName.equals("main"));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/StackTrace.java
index be74127..92b78b6 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/StackTrace.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.base.Equivalence;
@@ -203,6 +204,20 @@
     return extractFromJvm(result.getStdErr());
   }
 
+  public static StackTrace extract(SingleTestRunResult<?> result) {
+    Box<StackTrace> stackTraceBox = new Box<>();
+    result.forCfRuntime(
+        ignored -> {
+          stackTraceBox.set(extractFromJvm(result.getStdErr()));
+        });
+    result.forDexRuntimeSatisfying(
+        version -> true,
+        ignored -> {
+          stackTraceBox.set(extractFromArt(result.getStdErr()));
+        });
+    return stackTraceBox.get();
+  }
+
   public StackTrace retrace(String map, Path tempFolder) throws IOException {
     Path mapFile = tempFolder.resolve("map");
     Path stackTraceFile = tempFolder.resolve("stackTrace");
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
index 41c6451..54589a7 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
@@ -12,7 +12,8 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestBuilder;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.naming.retraceproguard.StackTrace.StackTraceLine;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
@@ -28,14 +29,21 @@
 public class VerticalClassMergingRetraceTest extends RetraceTestBase {
   private Set<StackTraceLine> haveSeenLines = new HashSet<>();
 
-  @Parameters(name = "Backend: {0}, mode: {1}, compat: {2}")
+  @Parameters(name = "{0}, mode: {1}, compat: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        ToolHelper.getBackends(), CompilationMode.values(), BooleanUtils.values());
+        getTestParameters()
+            .withCfRuntimes()
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build(),
+        CompilationMode.values(),
+        BooleanUtils.values());
   }
 
-  public VerticalClassMergingRetraceTest(Backend backend, CompilationMode mode, boolean compat) {
-    super(backend, mode, compat);
+  public VerticalClassMergingRetraceTest(
+      TestParameters parameters, CompilationMode mode, boolean compat) {
+    super(parameters, mode, compat);
   }
 
   @Override
@@ -55,7 +63,12 @@
 
   private int expectedActualStackTraceHeight() {
     // In RELEASE mode, a synthetic bridge will be added by vertical class merger.
-    return mode == CompilationMode.RELEASE ? 3 : 2;
+    int height = mode == CompilationMode.RELEASE ? 3 : 2;
+    if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik()) {
+      // Dalvik places a stack trace line in the bottom.
+      height += 1;
+    }
+    return height;
   }
 
   private boolean filterSynthesizedMethodWhenLineNumberAvailable(
@@ -79,7 +92,10 @@
               mode == CompilationMode.DEBUG
                   ? retracedStackTrace
                   : retracedStackTrace.filter(this::filterSynthesizedMethodWhenLineNumberAvailable);
-          assertThat(reprocessedStackTrace, isSameExceptForFileName(expectedStackTrace));
+          assertThat(
+              reprocessedStackTrace.filter(this::isNotDalvikNativeStartMethod),
+              isSameExceptForFileName(
+                  expectedStackTrace.filter(this::isNotDalvikNativeStartMethod)));
           assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
         });
   }
@@ -87,7 +103,7 @@
   @Test
   public void testLineNumberTableOnly() throws Exception {
     assumeTrue(compat);
-    assumeTrue(backend == Backend.DEX);
+    assumeTrue(parameters.isDexRuntime());
     runTest(
         ImmutableList.of("-keepattributes LineNumberTable"),
         (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
@@ -95,7 +111,10 @@
               mode == CompilationMode.DEBUG
                   ? retracedStackTrace
                   : retracedStackTrace.filter(this::filterSynthesizedMethodWhenLineNumberAvailable);
-          assertThat(reprocessedStackTrace, isSameExceptForFileName(expectedStackTrace));
+          assertThat(
+              reprocessedStackTrace.filter(this::isNotDalvikNativeStartMethod),
+              isSameExceptForFileName(
+                  expectedStackTrace.filter(this::isNotDalvikNativeStartMethod)));
           assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
         });
   }
@@ -103,7 +122,7 @@
   @Test
   public void testNoLineNumberTable() throws Exception {
     assumeTrue(compat);
-    assumeTrue(backend == Backend.DEX);
+    assumeTrue(parameters.isDexRuntime());
     haveSeenLines.clear();
     runTest(
         ImmutableList.of(),
@@ -113,7 +132,9 @@
                   ? retracedStackTrace
                   : retracedStackTrace.filter(this::filterSynthesizedMethod);
           assertThat(
-              reprocessedStackTrace, isSameExceptForFileNameAndLineNumber(expectedStackTrace));
+              reprocessedStackTrace.filter(this::isNotDalvikNativeStartMethod),
+              isSameExceptForFileNameAndLineNumber(
+                  expectedStackTrace.filter(this::isNotDalvikNativeStartMethod)));
           assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
         });
   }
diff --git a/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileAttributeCompatTest.java b/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileAttributeCompatTest.java
new file mode 100644
index 0000000..ac17f3c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileAttributeCompatTest.java
@@ -0,0 +1,197 @@
+// Copyright (c) 2021, 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.sourcefile;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.util.function.Supplier;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SourceFileAttributeCompatTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withSystemRuntime().build();
+  }
+
+  public SourceFileAttributeCompatTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private String getOriginalSourceFile() {
+    return new Exception().getStackTrace()[0].getFileName();
+  }
+
+  private void commonSetUp(TestShrinkerBuilder<?, ?, ?, ?, ?> builder) {
+    builder
+        .addProgramClasses(TestClass.class, SemiKept.class, NonKept.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-keep,allowshrinking class " + SemiKept.class.getName() + " { *; }");
+  }
+
+  private void checkSourceFileIsRemoved(SingleTestRunResult<?> result) throws Exception {
+    // TODO(b/202368282): We should likely emit a "default" source file attribute rather than strip.
+    checkSourceFile(result, null, null, null);
+  }
+
+  private void checkSourceFileIsOriginal(SingleTestRunResult<?> result) throws Exception {
+    String originalSourceFile = getOriginalSourceFile();
+    checkSourceFile(result, originalSourceFile, originalSourceFile, originalSourceFile);
+  }
+
+  private void checkSourceFile(
+      SingleTestRunResult<?> result, String keptValue, String semiKeptValue, String nonKeptValue)
+      throws Exception {
+    result.assertFailure();
+    result.inspectOriginalStackTrace(
+        stackTrace -> {
+          StackTraceLine nonKeptLine = stackTrace.get(0);
+          StackTraceLine semiKeptLine = stackTrace.get(1);
+          StackTraceLine keptLine = stackTrace.get(4);
+          assertEquals(getExpectedSourceFile(nonKeptValue), nonKeptLine.fileName);
+          assertEquals(getExpectedSourceFile(semiKeptValue), semiKeptLine.fileName);
+          assertEquals(getExpectedSourceFile(keptValue), keptLine.fileName);
+        });
+    result.inspectFailure(
+        inspector -> {
+          ClassSubject testClass = inspector.clazz(TestClass.class);
+          ClassSubject semiKept = inspector.clazz(SemiKept.class);
+          ClassSubject nonKept = inspector.clazz(NonKept.class);
+          assertEquals(keptValue, getSourceFileString(testClass));
+          assertEquals(semiKeptValue, getSourceFileString(semiKept));
+          assertEquals(nonKeptValue, getSourceFileString(nonKept));
+        });
+  }
+
+  private String getSourceFileString(ClassSubject subject) {
+    DexString sourceFile = subject.getDexProgramClass().getSourceFile();
+    return sourceFile == null ? null : sourceFile.toString();
+  }
+
+  private String getExpectedSourceFile(String expectedSourceFileValue) {
+    return expectedSourceFileValue == null ? "Unknown Source" : expectedSourceFileValue;
+  }
+
+  private <RR extends SingleTestRunResult<RR>> void testJustKeepMain(
+      TestShrinkerBuilder<?, ?, ?, RR, ?> builder, boolean fullMode) throws Exception {
+    // If the source file attribute is not kept then all compilers will strip it throughout.
+    commonSetUp(builder);
+    builder.run(parameters.getRuntime(), TestClass.class).apply(this::checkSourceFileIsRemoved);
+  }
+
+  private <RR extends SingleTestRunResult<RR>> void testDontObfuscate(
+      TestShrinkerBuilder<?, ?, ?, RR, ?> builder, boolean fullMode) throws Exception {
+    // If minification is off then compat compilers retain it, full mode will remove it.
+    commonSetUp(builder);
+    builder
+        .addKeepRules("-dontobfuscate")
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(fullMode, this::checkSourceFileIsRemoved, this::checkSourceFileIsOriginal);
+  }
+
+  private <RR extends SingleTestRunResult<RR>> void testDontOptimize(
+      TestShrinkerBuilder<?, ?, ?, RR, ?> builder, boolean fullMode) throws Exception {
+    // No effect from -dontoptimize
+    commonSetUp(builder);
+    builder
+        .addKeepRules("-dontoptimize")
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkSourceFileIsRemoved);
+  }
+
+  private <RR extends SingleTestRunResult<RR>> void testDontShrink(
+      TestShrinkerBuilder<?, ?, ?, RR, ?> builder, boolean fullMode) throws Exception {
+    // No effect from -dontshrink
+    commonSetUp(builder);
+    builder
+        .addKeepRules("-dontshrink")
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkSourceFileIsRemoved);
+  }
+
+  private <RR extends SingleTestRunResult<RR>> void testKeepSourceFileAttribute(
+      TestShrinkerBuilder<?, ?, ?, RR, ?> builder, boolean fullMode) throws Exception {
+    // If the source file attribute is kept, then PG and compat R8 will preserve it in original
+    // form for every input class. R8 will only preserve it for (soft) pinned classes. Others will
+    // be replaced by 'SourceFile'. The use of 'SourceFile' is to ensure VMs still print lines.
+    // TODO(b/202367773): R8 (non-compat) should rather replace it for all classes like line opt.
+    String originalSourceFile = getOriginalSourceFile();
+    String residualSourceFile = fullMode ? "SourceFile" : originalSourceFile;
+    commonSetUp(builder);
+    builder
+        .addKeepAttributeSourceFile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(
+            result ->
+                checkSourceFile(
+                    result, originalSourceFile, originalSourceFile, residualSourceFile));
+  }
+
+  private <RR extends SingleTestRunResult<RR>> void runAllTests(
+      Supplier<TestShrinkerBuilder<?, ?, ?, RR, ?>> builder, boolean fullMode) throws Exception {
+    testJustKeepMain(builder.get(), fullMode);
+    testDontObfuscate(builder.get(), fullMode);
+    testDontOptimize(builder.get(), fullMode);
+    testDontShrink(builder.get(), fullMode);
+    testKeepSourceFileAttribute(builder.get(), fullMode);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runAllTests(() -> testForR8(parameters.getBackend()), true);
+  }
+
+  @Test
+  public void testCompatR8() throws Exception {
+    runAllTests(() -> testForR8Compat(parameters.getBackend()), false);
+  }
+
+  @Test
+  public void testPG() throws Exception {
+    runAllTests(() -> testForProguard(ProguardVersion.V7_0_0).addDontWarn(getClass()), false);
+  }
+
+  static class NonKept {
+    @Override
+    public String toString() {
+      throw new RuntimeException("BOOM!");
+    }
+  }
+
+  static class SemiKept {
+    final Object o;
+
+    public SemiKept(Object o) {
+      this.o = o;
+    }
+
+    @Override
+    public String toString() {
+      return o.toString();
+    }
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      System.out.println(
+          System.nanoTime() > 0
+              ? new SemiKept(System.nanoTime() > 0 ? new NonKept() : null)
+              : null);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToLibraryOrClasspathMethodTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToLibraryOrClasspathMethodTest.java
new file mode 100644
index 0000000..aa57ae6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToLibraryOrClasspathMethodTest.java
@@ -0,0 +1,202 @@
+// Copyright (c) 2021, 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.argumentpropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+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/202074964.
+@RunWith(Parameterized.class)
+public class UpwardsInterfacePropagationToLibraryOrClasspathMethodTest extends TestBase {
+
+  private enum LibraryOrClasspath {
+    LIBRARY,
+    CLASSPATH;
+
+    private boolean isLibrary() {
+      return this == LIBRARY;
+    }
+  }
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public LibraryOrClasspath libraryOrClasspath;
+
+  @Parameters(name = "{0} {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        LibraryOrClasspath.values());
+  }
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("LibraryClass::libraryMethod(false)", "ProgramClass2::libraryMethod(true)");
+  private static final List<Class<?>> LIBRARY_CLASSES = ImmutableList.of(LibraryClass.class);
+  private static final List<Class<?>> PROGRAM_CLASSES =
+      ImmutableList.of(
+          ProgramClass.class,
+          Delegate.class,
+          Delegater.class,
+          AnotherProgramClass.class,
+          AnotherDelegate.class,
+          AnotherDelegator.class,
+          TestClass.class);
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(LIBRARY_CLASSES)
+        .addProgramClasses(PROGRAM_CLASSES)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.S))
+        .apply(
+            b -> {
+              if (libraryOrClasspath.isLibrary()) {
+                b.addLibraryClasses(LIBRARY_CLASSES);
+              } else {
+                b.addClasspathClasses(LIBRARY_CLASSES);
+              }
+            })
+        .addProgramClasses(PROGRAM_CLASSES)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableNoVerticalClassMergingAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .compile()
+        .addRunClasspathClasses(LibraryClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(
+            inspector -> {
+              assertThat(
+                  inspector.clazz(Delegate.class).method("void", "libraryMethod", "boolean"),
+                  isPresent());
+              // Check that boolean argument to libraryMethod was removed for AnotherProgramClass.
+              inspector
+                  .clazz(AnotherProgramClass.class)
+                  .forAllMethods(
+                      method -> assertEquals(method.getFinalSignature().toDescriptor(), "()V"));
+            })
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  public static class LibraryClass {
+    public void libraryMethod(boolean visible) {
+      System.out.println("LibraryClass::libraryMethod(" + visible + ")");
+    }
+  }
+
+  @NoVerticalClassMerging
+  public interface Delegate {
+    void libraryMethod(boolean visible);
+  }
+
+  @NeverClassInline
+  public static class ProgramClass extends LibraryClass implements Delegate {
+    Delegater delegater;
+
+    public ProgramClass() {
+      delegater = new Delegater(this);
+    }
+
+    @NeverInline
+    public void m() {
+      delegater.m();
+    }
+  }
+
+  @NoVerticalClassMerging
+  @NoHorizontalClassMerging
+  @NeverClassInline
+  public static class Delegater {
+    Delegate delegate;
+
+    Delegater(Delegate delegate) {
+      this.delegate = delegate;
+    }
+
+    public void m() {
+      delegate.libraryMethod(false);
+    }
+  }
+
+  @NoVerticalClassMerging
+  @NoHorizontalClassMerging
+  public interface AnotherDelegate {
+    void libraryMethod(boolean visible);
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  public static class AnotherProgramClass implements AnotherDelegate {
+    AnotherDelegator delegater;
+
+    public AnotherProgramClass() {
+      delegater = new AnotherDelegator(this);
+    }
+
+    @NeverInline
+    public void libraryMethod(boolean visible) {
+      System.out.println("ProgramClass2::libraryMethod(" + visible + ")");
+    }
+
+    @NeverInline
+    public void m() {
+      delegater.m();
+    }
+  }
+
+  @NoVerticalClassMerging
+  @NeverClassInline
+  public static class AnotherDelegator {
+    AnotherDelegate delegate;
+
+    AnotherDelegator(AnotherDelegate delegate) {
+      this.delegate = delegate;
+    }
+
+    public void m() {
+      delegate.libraryMethod(true);
+    }
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      new ProgramClass().m();
+      new AnotherProgramClass().m();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 3bdef43..17ea86d 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -27,10 +27,12 @@
 import com.android.tools.r8.retrace.stacktraces.AmbiguousWithSignatureStackTrace;
 import com.android.tools.r8.retrace.stacktraces.AutoStackTrace;
 import com.android.tools.r8.retrace.stacktraces.CircularReferenceStackTrace;
+import com.android.tools.r8.retrace.stacktraces.ClassWithDashStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ColonInFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.DifferentLineNumberSpanStackTrace;
 import com.android.tools.r8.retrace.stacktraces.FileNameExtensionStackTrace;
 import com.android.tools.r8.retrace.stacktraces.FoundMethodVerboseStackTrace;
+import com.android.tools.r8.retrace.stacktraces.IdentityMappingStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineFileNameWithInnerClassesStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineInOutlineStackTrace;
@@ -100,6 +102,11 @@
   }
 
   @Test
+  public void testClassWithDashStackTrace() throws Exception {
+    runRetraceTest(new ClassWithDashStackTrace());
+  }
+
+  @Test
   public void testCanMapExceptionClass() throws Exception {
     runRetraceTest(new ObfucatedExceptionClassStackTrace());
   }
@@ -352,6 +359,11 @@
     runRetraceTest(new OutsideLineRangeStackTraceTest());
   }
 
+  @Test
+  public void testIdentityMappingStackTrace() throws Exception {
+    runRetraceTest(new IdentityMappingStackTrace());
+  }
+
   private void inspectRetraceTest(
       StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) {
     inspection.accept(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
index 77b8aea..4deb785 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
@@ -80,6 +80,11 @@
   }
 
   @Override
+  public List<Class<?>> getPendingAdditionalClassesForTests() {
+    return ImmutableList.of();
+  }
+
+  @Override
   public List<String> getVmArgs() {
     return ImmutableList.of();
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ClassWithDashStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ClassWithDashStackTrace.java
new file mode 100644
index 0000000..07fb551
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ClassWithDashStackTrace.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2021, 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.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+
+public class ClassWithDashStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return ImmutableList.of(
+        "java.lang.NullPointerException",
+        "\tat I$-CC.staticMethod(I.java:66)",
+        "\tat Main.main(Main.java:73)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "# {\"id\":\"com.android.tools.r8.mapping\",\"version\":\"1.0\"}",
+        "Unused -> I$-CC:",
+        "# {\"id\":\"com.android.tools.r8.synthesized\"}",
+        "    66:66:void I.staticMethod() -> staticMethod",
+        "    66:66:void staticMethod():0 -> staticMethod",
+        "    # {\"id\":\"com.android.tools.r8.synthesized\"}");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return ImmutableList.of(
+        "java.lang.NullPointerException",
+        "\tat I.staticMethod(I.java:66)",
+        "\tat Main.main(Main.java:73)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return ImmutableList.of(
+        "java.lang.NullPointerException",
+        "\tat I.void staticMethod()(I.java:66)",
+        "\tat Main.main(Main.java:73)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/IdentityMappingStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/IdentityMappingStackTrace.java
new file mode 100644
index 0000000..40992ae
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/IdentityMappingStackTrace.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2021, 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.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class IdentityMappingStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "java.lang.IndexOutOfBoundsException", "\tat a.a(:10)", "\tat b.a(:11)", "\tat c.a(:12)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.One -> a:",
+        "  10:10:void foo(int) -> a",
+        "com.android.tools.r8.Other -> b:",
+        "  11:11:void bar(int, int) -> a", // This is an inline frame
+        "  11:11:boolean baz(int, int) -> a",
+        "com.android.tools.r8.Third -> c:",
+        "  12:12:void qux(int) -> a", // This is also an inline frame
+        "  12:12:void other(int, int) -> b",
+        "  12:12:boolean quux(int, int) -> a");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "java.lang.IndexOutOfBoundsException",
+        "\tat com.android.tools.r8.One.foo(One.java:10)",
+        "\tat com.android.tools.r8.Other.bar(Other.java:11)",
+        "\tat com.android.tools.r8.Other.baz(Other.java:11)",
+        "\tat com.android.tools.r8.Third.qux(Third.java:12)",
+        "\tat com.android.tools.r8.Third.quux(Third.java:12)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        "java.lang.IndexOutOfBoundsException",
+        "\tat com.android.tools.r8.One.void foo(int)(One.java:10)",
+        "\tat com.android.tools.r8.Other.void bar(int,int)(Other.java:11)",
+        "\tat com.android.tools.r8.Other.boolean baz(int,int)(Other.java:11)",
+        "\tat com.android.tools.r8.Third.void qux(int)(Third.java:12)",
+        "\tat com.android.tools.r8.Third.boolean quux(int,int)(Third.java:12)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/OverloadSameLineTest.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/OverloadSameLineTest.java
index 277e4b3..ec69283 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/OverloadSameLineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/OverloadSameLineTest.java
@@ -31,23 +31,18 @@
     return Arrays.asList(
         "Exception in thread \"main\" java.lang.NullPointerException",
         "\tat com.android.tools.r8.naming.retrace.Main.overload(Main.java:7)",
-        "<OR> Exception in thread \"main\" java.lang.NullPointerException",
-        "\tat com.android.tools.r8.naming.retrace.Main.overload(Main.java:15)",
-        "<OR> Exception in thread \"main\" java.lang.NullPointerException",
-        "\tat com.android.tools.r8.naming.retrace.Main.overload(Main.java:13)");
+        "\tat com.android.tools.r8.naming.retrace.Main.overload(Main.java:13)",
+        "\tat com.android.tools.r8.naming.retrace.Main.overload(Main.java:15)");
   }
 
   @Override
   public List<String> retraceVerboseStackTrace() {
     return Arrays.asList(
-        "There are 3 ambiguous stack traces.",
         "Exception in thread \"main\" java.lang.NullPointerException",
         "\tat com.android.tools.r8.naming.retrace.Main.void overload()(Main.java:7)",
-        "<OR> Exception in thread \"main\" java.lang.NullPointerException",
-        "\tat com.android.tools.r8.naming.retrace.Main.void overload(int)(Main.java:15)",
-        "<OR> Exception in thread \"main\" java.lang.NullPointerException",
         "\tat com.android.tools.r8.naming.retrace.Main.void"
-            + " overload(java.lang.String)(Main.java:13)");
+            + " overload(java.lang.String)(Main.java:13)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void overload(int)(Main.java:15)");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/structural/StructuralItemsTest.java b/src/test/java/com/android/tools/r8/utils/structural/StructuralItemsTest.java
index b9580ed..147cc34 100644
--- a/src/test/java/com/android/tools/r8/utils/structural/StructuralItemsTest.java
+++ b/src/test/java/com/android/tools/r8/utils/structural/StructuralItemsTest.java
@@ -88,7 +88,9 @@
   }
 
   private String getHash(StructuralItem<?> item) {
-    return item.hashForTesting();
+    HasherWrapper hasherWrapper = HasherWrapper.sha256Hasher();
+    item.hash(hasherWrapper);
+    return hasherWrapper.hashCodeAsString();
   }
 
   @Test
diff --git a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1 b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
index 759844b..ffee6ee 100644
--- a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
+++ b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
@@ -1 +1 @@
-8ddcb2b3cd52273413a538b22438e996e5c0dfcb
\ No newline at end of file
+36741d08e769bd2c5c201698161b3407507c7535
\ No newline at end of file
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 504496e..fb3b901 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -250,6 +250,22 @@
     return True
   return None
 
+def determine_properties(build_properties):
+  args = []
+  for key, value in build_properties.items():
+    # When writing dumps all system properties starting with com.android.tools.r8
+    # are written to the build.properties file in the format
+    # system-property-com.android.tools.r8.XXX=<value>
+    if key.startswith('system-property-'):
+      name = key[len('system-property-'):]
+      if name.endswith('dumpinputtofile') or name.endswith('dumpinputtodirectory'):
+        continue
+      if len(value) == 0:
+        args.append('-D' + name)
+      else:
+        args.append('-D' + name + '=' + value)
+  return args
+
 def download_distribution(args, version, temp):
   if version == 'main':
     return utils.R8_JAR if args.nolib else utils.R8LIB_JAR
@@ -327,6 +343,7 @@
       cmd.append('-Dcom.android.tools.r8.printtimes=1')
     if hasattr(args, 'properties'):
       cmd.extend(args.properties);
+    cmd.extend(determine_properties(build_properties))
     cmd.extend(['-cp', '%s:%s' % (wrapper_dir, jar)])
     if compiler == 'd8':
       cmd.append('com.android.tools.r8.D8')
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 73c932b..440cd97 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -13,7 +13,7 @@
 import time
 
 import archive
-import as_utils
+import gradle
 import gmail_data
 import gmscore_data
 import golem
@@ -653,7 +653,7 @@
 
   args.extend(inputs)
 
-  t0 = time.time()
+  t0 = None
   if options.dump_args_file:
     with open(options.dump_args_file, 'w') as args_file:
       args_file.writelines([arg + os.linesep for arg in args])
@@ -694,8 +694,11 @@
         if options.hash:
           jar = os.path.join(utils.LIBS, 'r8-' + options.hash + '.jar')
           main = 'com.android.tools.r8.' + options.compiler.upper()
+        if should_build(options):
+          gradle.RunGradle(['r8lib' if tool.startswith('r8lib') else 'r8'])
+        t0 = time.time()
         exit_code = toolhelper.run(tool, args,
-            build=should_build(options),
+            build=False,
             debug=not options.no_debug,
             profile=options.profile,
             track_memory_file=options.track_memory_to_file,