Merge commit '0a5f2ef14924206827b179c5215d5aeb0b2080ce' into dev-release
diff --git a/build.gradle b/build.gradle
index 46548e4..eac0f5c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -264,14 +264,23 @@
     main11Implementation group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
 
     examplesTestNGRunnerCompile group: 'org.testng', name: 'testng', version: testngVersion
+
     testCompile sourceSets.examples.output
     testCompile "junit:junit:$junitVersion"
+    testCompile "com.google.guava:guava:$guavaVersion"
     testCompile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
     testCompile "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
     testCompile group: 'org.smali', name: 'smali', version: smaliVersion
     testCompile files('third_party/jasmin/jasmin-2.4.jar')
     testCompile files('third_party/jdwp-tests/apache-harmony-jdwp-tests-host.jar')
     testCompile files('third_party/ddmlib/ddmlib.jar')
+    testCompile group: 'org.ow2.asm', name: 'asm', version: asmVersion
+    testCompile group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
+    testCompile group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
+    testCompile group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
+    testCompile group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
+    testCompile group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion
+
     jctfCommonCompile "junit:junit:$junitVersion"
     jctfTestsCompile "junit:junit:$junitVersion"
     jctfTestsCompile sourceSets.jctfCommon.output
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 3b3e48e..ad3e92b 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -168,8 +168,7 @@
   private static AppView<AppInfo> readApp(
       AndroidApp inputApp, InternalOptions options, ExecutorService executor, Timing timing)
       throws IOException {
-    PrefixRewritingMapper rewritePrefix =
-        options.desugaredLibrarySpecification.getPrefixRewritingMapper();
+    PrefixRewritingMapper rewritePrefix = options.getPrefixRewritingMapper();
     ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
     LazyLoadedDexApplication app = applicationReader.read(executor);
     AppInfo appInfo = AppInfo.createInitialAppInfo(app, applicationReader.readMainDexClasses(app));
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 1edb900..7d1c9bd 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -63,7 +63,7 @@
 
     MainDexListBuilder.checkForAssumedLibraryTypes(appView.appInfo());
 
-    SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
+    SubtypingInfo subtypingInfo = SubtypingInfo.create(appView);
 
     MainDexRootSet mainDexRootSet =
         MainDexRootSet.builder(appView, subtypingInfo, options.mainDexKeepRules).build(executor);
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index c3bf91f..9ba6c7f 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -166,8 +166,7 @@
     LazyLoadedDexApplication lazyApp =
         new ApplicationReader(inputApp, options, timing).read(executor);
 
-    PrefixRewritingMapper rewritePrefix =
-        options.desugaredLibrarySpecification.getPrefixRewritingMapper();
+    PrefixRewritingMapper rewritePrefix = options.getPrefixRewritingMapper();
 
     DexApplication app = new L8TreePruner(options).prune(lazyApp, rewritePrefix);
     return AppView.createForL8(AppInfo.createInitialAppInfo(app), rewritePrefix);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 9d43396..b96eaef 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -352,7 +352,7 @@
                     options.itemFactory, options.getMinApiLevel()));
           }
         }
-        SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
+        SubtypingInfo subtypingInfo = SubtypingInfo.create(appView);
         appView.setRootSet(
             RootSet.builder(
                     appView,
@@ -583,7 +583,7 @@
               EnqueuerFactory.createForFinalTreeShaking(
                   appView,
                   executorService,
-                  new SubtypingInfo(appView),
+                  SubtypingInfo.create(appView),
                   keptGraphConsumer,
                   prunedTypes);
           if (options.isClassMergingExtensionRequired(enqueuer.getMode())) {
@@ -893,7 +893,7 @@
     // computing from the initially computed main dex root set.
     MainDexInfo mainDexInfo =
         EnqueuerFactory.createForInitialMainDexTracing(
-                appView, executorService, new SubtypingInfo(appView))
+                appView, executorService, SubtypingInfo.create(appView))
             .traceMainDex(executorService, timing);
     appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
   }
@@ -915,7 +915,7 @@
 
     Enqueuer enqueuer =
         EnqueuerFactory.createForFinalMainDexTracing(
-            appView, executorService, new SubtypingInfo(appView), mainDexKeptGraphConsumer);
+            appView, executorService, SubtypingInfo.create(appView), mainDexKeptGraphConsumer);
     // Find classes which may have code executed before secondary dex files installation.
     MainDexInfo mainDexInfo = enqueuer.traceMainDex(executorService, timing);
     appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
@@ -1061,7 +1061,7 @@
     // If there is no kept-graph info, re-run the enqueueing to compute it.
     if (whyAreYouKeepingConsumer == null) {
       whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(null);
-      SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
+      SubtypingInfo subtypingInfo = SubtypingInfo.create(appView);
       if (forMainDex) {
         enqueuer =
             EnqueuerFactory.createForFinalMainDexTracing(
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 929a010..4d42276 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
@@ -117,6 +117,6 @@
       InitClassLens initClassLens) {
     // ..., →
     // ..., value
-    frameBuilder.push(clazz);
+    frameBuilder.push(factory.intType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index c2316e4..14b0ded 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -160,7 +160,7 @@
 
   private static <T extends AppInfo> PrefixRewritingMapper defaultPrefixRewritingMapper(T appInfo) {
     InternalOptions options = appInfo.options();
-    return options.desugaredLibrarySpecification.getPrefixRewritingMapper();
+    return options.getPrefixRewritingMapper();
   }
 
   public static <T extends AppInfo> AppView<T> createForD8(T appInfo) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
index 8618c4e..d0324ee 100644
--- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -120,7 +120,10 @@
   }
 
   @Override
-  public DexField getRenamedFieldSignature(DexField originalField) {
+  public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) {
+    if (this == codeLens) {
+      return originalField;
+    }
     return originalFieldSignatures.inverse().getOrDefault(originalField, originalField);
   }
 
@@ -132,8 +135,9 @@
   }
 
   @Override
-  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
-    return GraphLens.getIdentityLens().lookupPrototypeChangesForMethodDefinition(method);
+  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+      DexMethod method, GraphLens codeLens) {
+    return GraphLens.getIdentityLens().lookupPrototypeChangesForMethodDefinition(method, codeLens);
   }
 
   @Override
@@ -153,7 +157,7 @@
   }
 
   @Override
-  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+  public DexMethod getPreviousMethodSignature(DexMethod method) {
     if (extraOriginalMethodSignatures.containsKey(method)) {
       return extraOriginalMethodSignatures.get(method);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
index 685188e..b2ecd15 100644
--- a/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
+++ b/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
@@ -71,7 +71,7 @@
 
     // Add subtypes to worklist.
     for (DexType subtype : immediateSubtypesProvider.apply(clazz.getType())) {
-      DexClass definition = appView.definitionFor(subtype);
+      DexClass definition = definitionSupplier.contextIndependentDefinitionFor(subtype);
       if (definition != null) {
         if (scope != Scope.ONLY_PROGRAM_CLASSES || definition.isProgramClass()) {
           addDependentsToWorklist(definition);
diff --git a/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java
index 83b09c3..4b48516 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java
@@ -41,7 +41,7 @@
     }
   }
 
-  final AppView<? extends AppInfoWithClassHierarchy> appView;
+  final DexDefinitionSupplier definitionSupplier;
   final Scope scope;
 
   final Set<DexClass> visited = new HashSet<>();
@@ -49,8 +49,8 @@
 
   boolean excludeInterfaces = false;
 
-  ClassHierarchyTraversal(AppView<? extends AppInfoWithClassHierarchy> appView, Scope scope) {
-    this.appView = appView;
+  ClassHierarchyTraversal(DexDefinitionSupplier definitionSupplier, Scope scope) {
+    this.definitionSupplier = definitionSupplier;
     this.scope = scope;
   }
 
@@ -61,8 +61,8 @@
     return self();
   }
 
-  public void visit(Iterable<DexProgramClass> sources, Consumer<T> visitor) {
-    Iterator<DexProgramClass> sourceIterator = sources.iterator();
+  public void visit(Iterable<? extends DexClass> sources, Consumer<T> visitor) {
+    Iterator<? extends DexClass> sourceIterator = sources.iterator();
 
     // Visit the program classes in the order that is implemented by addDependentsToWorklist().
     while (sourceIterator.hasNext() || !worklist.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 6cef0f6..8c6bbc8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -868,12 +868,20 @@
     forEachImmediateInterface(fn);
   }
 
+  public void forEachImmediateSupertype(BiConsumer<DexType, Boolean> fn) {
+    if (superType != null) {
+      fn.accept(superType, false);
+    }
+    forEachImmediateInterface(iface -> fn.accept(iface, true));
+  }
+
   public boolean validInterfaceSignatures() {
     return getClassSignature().superInterfaceSignatures().isEmpty()
         || interfaces.values.length == getClassSignature().superInterfaceSignatures.size();
   }
 
-  public void forEachImmediateInterface(BiConsumer<DexType, ClassTypeSignature> consumer) {
+  public void forEachImmediateInterfaceWithSignature(
+      BiConsumer<DexType, ClassTypeSignature> consumer) {
     assert validInterfaceSignatures();
 
     // If there is no generic signature information don't pass any type arguments.
@@ -896,11 +904,12 @@
     }
   }
 
-  public void forEachImmediateSupertype(BiConsumer<DexType, ClassTypeSignature> consumer) {
+  public void forEachImmediateSupertypeWithSignature(
+      BiConsumer<DexType, ClassTypeSignature> consumer) {
     if (superType != null) {
       consumer.accept(superType, classSignature.superClassSignature);
     }
-    forEachImmediateInterface(consumer);
+    forEachImmediateInterfaceWithSignature(consumer);
   }
 
   public void forEachImmediateInterfaceWithAppliedTypeArguments(
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 3ae4b1f..1aa41dc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -367,10 +367,33 @@
     isInlinableByJavaC = true;
   }
 
-  public boolean isInlinableByJavaC() {
+  public boolean getIsInlinableByJavaC() {
     return isInlinableByJavaC;
   }
 
+  public boolean getOrComputeIsInlinableByJavaC(DexItemFactory dexItemFactory) {
+    if (getIsInlinableByJavaC()) {
+      return true;
+    }
+    if (!isStatic() || !isFinal()) {
+      return false;
+    }
+    if (!hasExplicitStaticValue()) {
+      return false;
+    }
+    if (getType().isPrimitiveType()) {
+      return true;
+    }
+    if (getType() != dexItemFactory.stringType) {
+      return false;
+    }
+    if (!getStaticValue().isDexValueString()) {
+      return false;
+    }
+    markAsInlinableByJavaC();
+    return true;
+  }
+
   public static class Builder {
 
     private DexField field;
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 4c76629..b27f94a 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -321,14 +321,18 @@
     DexMethod original = method;
     while (current.isNonIdentityLens() && current != atGraphLens) {
       NonIdentityGraphLens nonIdentityLens = current.asNonIdentityLens();
-      original = nonIdentityLens.internalGetPreviousMethodSignature(original);
+      original = nonIdentityLens.getPreviousMethodSignature(original);
       current = nonIdentityLens.getPrevious();
     }
     assert atGraphLens == null ? current.isIdentityLens() : (current == atGraphLens);
     return original;
   }
 
-  public abstract DexField getRenamedFieldSignature(DexField originalField);
+  public final DexField getRenamedFieldSignature(DexField originalField) {
+    return getRenamedFieldSignature(originalField, null);
+  }
+
+  public abstract DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens);
 
   public final DexMember<?, ?> getRenamedMemberSignature(DexMember<?, ?> originalMember) {
     return originalMember.isDexField()
@@ -451,8 +455,13 @@
     MethodLookupResult lookupMethod(MethodLookupResult previous);
   }
 
+  public final RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+      DexMethod method) {
+    return lookupPrototypeChangesForMethodDefinition(method, null);
+  }
+
   public abstract RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
-      DexMethod method);
+      DexMethod method, GraphLens codeLens);
 
   public final DexField lookupField(DexField field) {
     return lookupField(field, null);
@@ -485,14 +494,6 @@
     FieldLookupResult lookupField(FieldLookupResult previous);
   }
 
-  public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
-    return null;
-  }
-
-  public DexMethod lookupPutFieldForMethod(DexField field, DexMethod context) {
-    return null;
-  }
-
   public DexReference lookupReference(DexReference reference) {
     return reference.apply(this::lookupType, this::lookupField, this::lookupMethod);
   }
@@ -521,6 +522,10 @@
     return true;
   }
 
+  public boolean hasCustomCodeRewritings() {
+    return false;
+  }
+
   public boolean isAppliedLens() {
     return false;
   }
@@ -529,6 +534,10 @@
     return false;
   }
 
+  public boolean isEnumUnboxerLens() {
+    return false;
+  }
+
   public abstract boolean isIdentityLens();
 
   public boolean isMemberRebindingLens() {
@@ -553,6 +562,10 @@
     return null;
   }
 
+  public boolean isVerticalClassMergerLens() {
+    return false;
+  }
+
   public GraphLens withCodeRewritingsApplied(DexItemFactory dexItemFactory) {
     if (hasCodeRewritings()) {
       return new ClearCodeRewritingGraphLens(dexItemFactory, this);
@@ -600,7 +613,7 @@
   public Map<DexCallSite, ProgramMethodSet> rewriteCallSites(
       Map<DexCallSite, ProgramMethodSet> callSites, DexDefinitionSupplier definitions) {
     Map<DexCallSite, ProgramMethodSet> result = new IdentityHashMap<>();
-    LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(definitions, this);
+    LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(definitions, this, null);
     callSites.forEach(
         (callSite, contexts) -> {
           for (ProgramMethod context : contexts.rewrittenWithLens(definitions, this)) {
@@ -615,9 +628,16 @@
 
   @SuppressWarnings("unchecked")
   public <T extends DexReference> T rewriteReference(T reference) {
+    return rewriteReference(reference, null);
+  }
+
+  @SuppressWarnings("unchecked")
+  public <T extends DexReference> T rewriteReference(T reference, GraphLens codeLens) {
     return (T)
         reference.apply(
-            this::lookupType, this::getRenamedFieldSignature, this::getRenamedMethodSignature);
+            type -> lookupType(type, codeLens),
+            field -> getRenamedFieldSignature(field, codeLens),
+            method -> getRenamedMethodSignature(method, codeLens));
   }
 
   public <T extends DexReference> Set<T> rewriteReferences(Set<T> references) {
@@ -767,9 +787,9 @@
     }
 
     @SuppressWarnings("unchecked")
-    public final <T extends NonIdentityGraphLens> T findPrevious(
+    public final <T extends NonIdentityGraphLens> T find(
         Predicate<NonIdentityGraphLens> predicate) {
-      GraphLens current = getPrevious();
+      GraphLens current = this;
       while (current.isNonIdentityLens()) {
         NonIdentityGraphLens nonIdentityGraphLens = current.asNonIdentityLens();
         if (predicate.test(nonIdentityGraphLens)) {
@@ -780,6 +800,13 @@
       return null;
     }
 
+    @SuppressWarnings("unchecked")
+    public final <T extends NonIdentityGraphLens> T findPrevious(
+        Predicate<NonIdentityGraphLens> predicate) {
+      GraphLens previous = getPrevious();
+      return previous.isNonIdentityLens() ? previous.asNonIdentityLens().find(predicate) : null;
+    }
+
     public final void withAlternativeParentLens(GraphLens lens, Action action) {
       GraphLens oldParent = getPrevious();
       previousLens = lens;
@@ -864,7 +891,7 @@
       }
       return previousLens.internalLookupMethod(
           reference,
-          internalGetPreviousMethodSignature(context),
+          getPreviousMethodSignature(context),
           type,
           codeLens,
           previous -> continuation.lookupMethod(internalDescribeLookupMethod(previous, context)));
@@ -877,7 +904,7 @@
 
     protected abstract DexType internalDescribeLookupClassType(DexType previous);
 
-    protected abstract DexMethod internalGetPreviousMethodSignature(DexMethod method);
+    public abstract DexMethod getPreviousMethodSignature(DexMethod method);
 
     @Override
     public final boolean isIdentityLens() {
@@ -931,7 +958,7 @@
     }
 
     @Override
-    public DexField getRenamedFieldSignature(DexField originalField) {
+    public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) {
       return originalField;
     }
 
@@ -965,7 +992,7 @@
 
     @Override
     public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
-        DexMethod method) {
+        DexMethod method, GraphLens codeLens) {
       return RewrittenPrototypeDescription.none();
     }
 
@@ -1025,8 +1052,10 @@
     }
 
     @Override
-    public DexField getRenamedFieldSignature(DexField originalField) {
-      return getPrevious().getRenamedFieldSignature(originalField);
+    public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) {
+      return this != codeLens
+          ? getPrevious().getRenamedFieldSignature(originalField)
+          : originalField;
     }
 
     @Override
@@ -1043,8 +1072,8 @@
 
     @Override
     public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
-        DexMethod method) {
-      return getIdentityLens().lookupPrototypeChangesForMethodDefinition(method);
+        DexMethod method, GraphLens codeLens) {
+      return getIdentityLens().lookupPrototypeChangesForMethodDefinition(method, codeLens);
     }
 
     @Override
@@ -1083,7 +1112,7 @@
     }
 
     @Override
-    protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+    public DexMethod getPreviousMethodSignature(DexMethod method) {
       return method;
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
index cec5083..521d6c7 100644
--- a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
@@ -99,11 +99,6 @@
         });
   }
 
-  public void forEachImmediateSubClass(
-      DexProgramClass clazz, Consumer<? super DexProgramClass> consumer) {
-    forEachImmediateSubClassMatching(clazz, alwaysTrue(), consumer);
-  }
-
   public void forEachImmediateSubClassMatching(
       DexProgramClass clazz,
       Predicate<? super DexProgramClass> predicate,
diff --git a/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
index e5b3462..6ab93ed 100644
--- a/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
@@ -130,7 +130,10 @@
   }
 
   @Override
-  public DexField getRenamedFieldSignature(DexField originalField) {
+  public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) {
+    if (this == codeLens) {
+      return originalField;
+    }
     DexField renamedField = getPrevious().getRenamedFieldSignature(originalField);
     return internalGetNextFieldSignature(renamedField);
   }
@@ -228,10 +231,14 @@
   }
 
   @Override
-  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
-    DexMethod previous = internalGetPreviousMethodSignature(method);
+  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+      DexMethod method, GraphLens codeLens) {
+    if (this == codeLens) {
+      return getIdentityLens().lookupPrototypeChangesForMethodDefinition(method, codeLens);
+    }
+    DexMethod previous = getPreviousMethodSignature(method);
     RewrittenPrototypeDescription lookup =
-        getPrevious().lookupPrototypeChangesForMethodDefinition(previous);
+        getPrevious().lookupPrototypeChangesForMethodDefinition(previous, codeLens);
     return internalDescribePrototypeChanges(lookup, method);
   }
 
@@ -245,7 +252,7 @@
   }
 
   @Override
-  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+  public DexMethod getPreviousMethodSignature(DexMethod method) {
     return newMethodSignatures.getRepresentativeKeyOrDefault(method, method);
   }
 
@@ -253,16 +260,6 @@
     return newMethodSignatures.getRepresentativeValueOrDefault(method, method);
   }
 
-  @Override
-  public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
-    return getPrevious().lookupGetFieldForMethod(field, context);
-  }
-
-  @Override
-  public DexMethod lookupPutFieldForMethod(DexField field, DexMethod context) {
-    return getPrevious().lookupPutFieldForMethod(field, context);
-  }
-
   /**
    * Default invocation type mapping.
    *
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
index bf3849c..972c83a 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -31,6 +31,7 @@
 import it.unimi.dsi.fastutil.ints.IntLists;
 import it.unimi.dsi.fastutil.ints.IntSortedSet;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
@@ -51,8 +52,13 @@
           }
 
           @Override
+          public boolean isNone() {
+            return true;
+          }
+
+          @Override
           public ArgumentInfo rewrittenWithLens(
-              AppView<AppInfoWithLiveness> appView, GraphLens graphLens) {
+              AppView<AppInfoWithLiveness> appView, GraphLens graphLens, GraphLens codeLens) {
             return this;
           }
 
@@ -80,6 +86,10 @@
       return arg1.combine(arg2);
     }
 
+    public boolean isNone() {
+      return false;
+    }
+
     public boolean isRemovedArgumentInfo() {
       return false;
     }
@@ -100,7 +110,7 @@
     public abstract ArgumentInfo combine(ArgumentInfo info);
 
     public abstract ArgumentInfo rewrittenWithLens(
-        AppView<AppInfoWithLiveness> appView, GraphLens graphLens);
+        AppView<AppInfoWithLiveness> appView, GraphLens graphLens, GraphLens codeLens);
 
     @Override
     public abstract boolean equals(Object obj);
@@ -113,9 +123,15 @@
 
     public static class Builder {
 
+      private boolean checkNullOrZero;
       private SingleValue singleValue;
       private DexType type;
 
+      public Builder setCheckNullOrZero(boolean checkNullOrZero) {
+        this.checkNullOrZero = checkNullOrZero;
+        return this;
+      }
+
       public Builder setSingleValue(SingleValue singleValue) {
         this.singleValue = singleValue;
         return this;
@@ -128,14 +144,16 @@
 
       public RemovedArgumentInfo build() {
         assert type != null;
-        return new RemovedArgumentInfo(singleValue, type);
+        return new RemovedArgumentInfo(checkNullOrZero, singleValue, type);
       }
     }
 
+    private final boolean checkNullOrZero;
     private final SingleValue singleValue;
     private final DexType type;
 
-    private RemovedArgumentInfo(SingleValue singleValue, DexType type) {
+    private RemovedArgumentInfo(boolean checkNullOrZero, SingleValue singleValue, DexType type) {
+      this.checkNullOrZero = checkNullOrZero;
       this.singleValue = singleValue;
       this.type = type;
     }
@@ -156,8 +174,8 @@
       return type;
     }
 
-    public boolean isNeverUsed() {
-      return !hasSingleValue();
+    public boolean isCheckNullOrZeroSet() {
+      return checkNullOrZero;
     }
 
     @Override
@@ -178,12 +196,12 @@
 
     @Override
     public RemovedArgumentInfo rewrittenWithLens(
-        AppView<AppInfoWithLiveness> appView, GraphLens graphLens) {
+        AppView<AppInfoWithLiveness> appView, GraphLens graphLens, GraphLens codeLens) {
       SingleValue rewrittenSingleValue =
-          hasSingleValue() ? singleValue.rewrittenWithLens(appView, graphLens) : null;
-      DexType rewrittenType = graphLens.lookupType(type);
+          hasSingleValue() ? singleValue.rewrittenWithLens(appView, graphLens, codeLens) : null;
+      DexType rewrittenType = graphLens.lookupType(type, codeLens);
       if (rewrittenSingleValue != singleValue || rewrittenType != type) {
-        return new RemovedArgumentInfo(rewrittenSingleValue, rewrittenType);
+        return new RemovedArgumentInfo(checkNullOrZero, rewrittenSingleValue, rewrittenType);
       }
       return this;
     }
@@ -194,12 +212,14 @@
         return false;
       }
       RemovedArgumentInfo other = (RemovedArgumentInfo) obj;
-      return type == other.type && Objects.equals(singleValue, other.singleValue);
+      return checkNullOrZero == other.checkNullOrZero
+          && type == other.type
+          && Objects.equals(singleValue, other.singleValue);
     }
 
     @Override
     public int hashCode() {
-      return Objects.hash(singleValue, type);
+      return Objects.hash(checkNullOrZero, singleValue, type);
     }
   }
 
@@ -214,12 +234,6 @@
       return new Builder();
     }
 
-    public static RewrittenTypeInfo toVoid(
-        DexType oldReturnType, DexItemFactory dexItemFactory, SingleValue singleValue) {
-      assert singleValue != null;
-      return new RewrittenTypeInfo(oldReturnType, dexItemFactory.voidType, null, singleValue);
-    }
-
     public RewrittenTypeInfo(DexType oldType, DexType newType) {
       this(oldType, newType, null, null);
     }
@@ -293,11 +307,14 @@
 
     @Override
     public RewrittenTypeInfo rewrittenWithLens(
-        AppView<AppInfoWithLiveness> appView, GraphLens graphLens) {
-      DexType rewrittenCastType = castType != null ? graphLens.lookupType(castType) : null;
-      DexType rewrittenNewType = graphLens.lookupType(newType);
+        AppView<AppInfoWithLiveness> appView, GraphLens graphLens, GraphLens codeLens) {
+      DexType rewrittenCastType =
+          castType != null ? graphLens.lookupType(castType, codeLens) : null;
+      DexType rewrittenNewType = graphLens.lookupType(newType, codeLens);
       SingleValue rewrittenSingleValue =
-          hasSingleValue() ? getSingleValue().rewrittenWithLens(appView, graphLens) : null;
+          hasSingleValue()
+              ? getSingleValue().rewrittenWithLens(appView, graphLens, codeLens)
+              : null;
       if (rewrittenCastType != castType
           || rewrittenNewType != newType
           || rewrittenSingleValue != singleValue) {
@@ -324,12 +341,6 @@
       return Objects.hash(oldType, newType, singleValue);
     }
 
-    public boolean verifyIsDueToUnboxing(DexItemFactory dexItemFactory) {
-      assert oldType.toBaseType(dexItemFactory).isClassType();
-      assert newType.toBaseType(dexItemFactory).isIntType();
-      return true;
-    }
-
     public static class Builder {
 
       private DexType castType;
@@ -451,9 +462,9 @@
       return removed;
     }
 
-    public int numberOfRemovedNonReceiverArguments(DexEncodedMethod method) {
+    public int numberOfRemovedNonReceiverArguments(ProgramMethod method) {
       return numberOfRemovedArguments()
-          - BooleanUtils.intValue(method.isInstance() && isArgumentRemoved(0));
+          - BooleanUtils.intValue(method.getDefinition().isInstance() && isArgumentRemoved(0));
     }
 
     public boolean hasArgumentInfo(int argumentIndex) {
@@ -469,11 +480,12 @@
     }
 
     public ArgumentInfoCollection rewrittenWithLens(
-        AppView<AppInfoWithLiveness> appView, GraphLens graphLens) {
+        AppView<AppInfoWithLiveness> appView, GraphLens graphLens, GraphLens codeLens) {
       Int2ObjectSortedMap<ArgumentInfo> rewrittenArgumentInfos = new Int2ObjectRBTreeMap<>();
       for (Int2ObjectMap.Entry<ArgumentInfo> entry : argumentInfos.int2ObjectEntrySet()) {
         ArgumentInfo argumentInfo = entry.getValue();
-        ArgumentInfo rewrittenArgumentInfo = argumentInfo.rewrittenWithLens(appView, graphLens);
+        ArgumentInfo rewrittenArgumentInfo =
+            argumentInfo.rewrittenWithLens(appView, graphLens, codeLens);
         if (rewrittenArgumentInfo != argumentInfo) {
           rewrittenArgumentInfos.put(entry.getIntKey(), rewrittenArgumentInfo);
         }
@@ -529,46 +541,6 @@
       }
     }
 
-    public DexMethod rewriteMethod(ProgramMethod method, DexItemFactory dexItemFactory) {
-      if (isEmpty()) {
-        return method.getReference();
-      }
-      DexProto rewrittenProto = rewriteProto(method, dexItemFactory);
-      return method.getReference().withProto(rewrittenProto, dexItemFactory);
-    }
-
-    public DexProto rewriteProto(ProgramMethod method, DexItemFactory dexItemFactory) {
-      return isEmpty()
-          ? method.getProto()
-          : dexItemFactory.createProto(method.getReturnType(), rewriteParameters(method));
-    }
-
-    public DexType[] rewriteParameters(ProgramMethod method) {
-      return rewriteParameters(method.getDefinition());
-    }
-
-    public DexType[] rewriteParameters(DexEncodedMethod encodedMethod) {
-      DexType[] params = encodedMethod.getParameters().values;
-      if (isEmpty()) {
-        return params;
-      }
-      DexType[] newParams =
-          new DexType[params.length - numberOfRemovedNonReceiverArguments(encodedMethod)];
-      int offset = encodedMethod.getFirstNonReceiverArgumentIndex();
-      int newParamIndex = 0;
-      for (int oldParamIndex = 0; oldParamIndex < params.length; oldParamIndex++) {
-        ArgumentInfo argInfo = argumentInfos.get(oldParamIndex + offset);
-        if (argInfo == null) {
-          newParams[newParamIndex++] = params[oldParamIndex];
-        } else if (argInfo.isRewrittenTypeInfo()) {
-          RewrittenTypeInfo rewrittenTypeInfo = argInfo.asRewrittenTypeInfo();
-          assert params[oldParamIndex] == rewrittenTypeInfo.oldType;
-          newParams[newParamIndex++] = rewrittenTypeInfo.newType;
-        }
-      }
-      return newParams;
-    }
-
     public ArgumentInfoCollection combine(ArgumentInfoCollection info) {
       if (isEmpty()) {
         return info;
@@ -782,44 +754,80 @@
   public Instruction getConstantReturn(
       AppView<AppInfoWithLiveness> appView,
       IRCode code,
-      ProgramMethod method,
       Position position,
       TypeAndLocalInfoSupplier info) {
     assert rewrittenReturnInfo != null;
     assert rewrittenReturnInfo.hasSingleValue();
-    assert rewrittenReturnInfo.getSingleValue().isMaterializableInContext(appView, method);
     Instruction instruction =
         rewrittenReturnInfo.getSingleValue().createMaterializingInstruction(appView, code, info);
     instruction.setPosition(position);
     return instruction;
   }
 
+  public boolean verifyConstantReturnAccessibleInContext(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod method, GraphLens codeLens) {
+    SingleValue rewrittenSingleValue =
+        rewrittenReturnInfo
+            .getSingleValue()
+            .rewrittenWithLens(appView, appView.graphLens(), codeLens);
+    assert rewrittenSingleValue.isMaterializableInContext(appView, method);
+    return true;
+  }
+
   public DexMethod rewriteMethod(ProgramMethod method, DexItemFactory dexItemFactory) {
     if (isEmpty()) {
       return method.getReference();
     }
-    DexProto rewrittenProto = rewriteProto(method.getDefinition(), dexItemFactory);
+    DexProto rewrittenProto = rewriteProto(method, dexItemFactory);
     return method.getReference().withProto(rewrittenProto, dexItemFactory);
   }
 
-  public DexProto rewriteProto(DexEncodedMethod encodedMethod, DexItemFactory dexItemFactory) {
+  public DexProto rewriteProto(ProgramMethod method, DexItemFactory dexItemFactory) {
     if (isEmpty()) {
-      return encodedMethod.getReference().proto;
+      return method.getProto();
     }
     DexType newReturnType =
-        rewrittenReturnInfo != null
-            ? rewrittenReturnInfo.newType
-            : encodedMethod.getReference().proto.returnType;
-    DexType[] newParameters = argumentInfoCollection.rewriteParameters(encodedMethod);
+        rewrittenReturnInfo != null ? rewrittenReturnInfo.getNewType() : method.getReturnType();
+    DexType[] newParameters = rewriteParameters(method, dexItemFactory);
     return dexItemFactory.createProto(newReturnType, newParameters);
   }
 
+  public DexType[] rewriteParameters(ProgramMethod method, DexItemFactory dexItemFactory) {
+    DexType[] params = method.getParameters().values;
+    if (isEmpty()) {
+      return params;
+    }
+    DexType[] newParams =
+        new DexType
+            [params.length
+                - argumentInfoCollection.numberOfRemovedNonReceiverArguments(method)
+                + extraParameters.size()];
+    int offset = method.getDefinition().getFirstNonReceiverArgumentIndex();
+    int newParamIndex = 0;
+    for (int oldParamIndex = 0; oldParamIndex < params.length; oldParamIndex++) {
+      ArgumentInfo argInfo = argumentInfoCollection.getArgumentInfo(oldParamIndex + offset);
+      if (argInfo.isNone()) {
+        newParams[newParamIndex++] = params[oldParamIndex];
+      } else if (argInfo.isRewrittenTypeInfo()) {
+        RewrittenTypeInfo rewrittenTypeInfo = argInfo.asRewrittenTypeInfo();
+        assert params[oldParamIndex] == rewrittenTypeInfo.oldType;
+        newParams[newParamIndex++] = rewrittenTypeInfo.newType;
+      }
+    }
+    for (ExtraParameter extraParameter : extraParameters) {
+      newParams[newParamIndex++] = extraParameter.getType(dexItemFactory);
+    }
+    return newParams;
+  }
+
   public RewrittenPrototypeDescription rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens graphLens) {
+      AppView<AppInfoWithLiveness> appView, GraphLens graphLens, GraphLens codeLens) {
     ArgumentInfoCollection newArgumentInfoCollection =
-        argumentInfoCollection.rewrittenWithLens(appView, graphLens);
+        argumentInfoCollection.rewrittenWithLens(appView, graphLens, codeLens);
     RewrittenTypeInfo newRewrittenReturnInfo =
-        hasRewrittenReturnInfo() ? rewrittenReturnInfo.rewrittenWithLens(appView, graphLens) : null;
+        hasRewrittenReturnInfo()
+            ? rewrittenReturnInfo.rewrittenWithLens(appView, graphLens, codeLens)
+            : null;
     if (newArgumentInfoCollection != argumentInfoCollection
         || newRewrittenReturnInfo != rewrittenReturnInfo) {
       return new RewrittenPrototypeDescription(
@@ -844,6 +852,10 @@
     return withExtraParameters(parameters);
   }
 
+  public RewrittenPrototypeDescription withExtraParameters(ExtraParameter... parameters) {
+    return withExtraParameters(Arrays.asList(parameters));
+  }
+
   public RewrittenPrototypeDescription withExtraParameters(List<ExtraParameter> parameters) {
     if (parameters.isEmpty()) {
       return this;
diff --git a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
index 2fa3dfb..0800ede 100644
--- a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
@@ -19,7 +19,6 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentSkipListSet;
 import java.util.function.Consumer;
-import java.util.function.Function;
 
 public class SubtypingInfo {
 
@@ -29,119 +28,124 @@
   // Since most Java types has no sub-types, we can just share an empty immutable set until we
   // need to add to it.
   private static final Set<DexType> NO_DIRECT_SUBTYPE = ImmutableSet.of();
-  // Map from types to their subtyping information.
-  private final DexItemFactory factory;
-
-  private final Map<DexType, TypeInfo> typeInfo = new ConcurrentHashMap<>();
-
   // Map from types to their subtypes.
-  private final Map<DexType, ImmutableSet<DexType>> subtypeMap = new IdentityHashMap<>();
+  private final Map<DexType, Set<DexType>> subtypeMap;
 
-  public SubtypingInfo(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    this(appView.appInfo());
+  private final Map<DexType, TypeInfo> typeInfo;
+
+  private SubtypingInfo(Map<DexType, TypeInfo> typeInfo, Map<DexType, Set<DexType>> subtypeMap) {
+    this.typeInfo = typeInfo;
+    this.subtypeMap = subtypeMap;
   }
 
-  public SubtypingInfo(AppInfoWithClassHierarchy appInfo) {
-    this(appInfo.app().asDirect().allClasses(), appInfo);
+  public static SubtypingInfo create(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return create(appView.appInfo());
   }
 
-  private SubtypingInfo(Collection<DexClass> classes, DexDefinitionSupplier definitions) {
-    factory = definitions.dexItemFactory();
-    // Recompute subtype map if we have modified the graph.
-    populateSubtypeMap(classes, definitions::definitionFor, factory);
+  public static SubtypingInfo create(AppInfoWithClassHierarchy appInfo) {
+    return create(appInfo.app().asDirect().allClasses(), appInfo);
   }
 
-  private void populateSuperType(
+  private static SubtypingInfo create(
+      Collection<DexClass> classes, DexDefinitionSupplier definitions) {
+    Map<DexType, TypeInfo> typeInfo = new ConcurrentHashMap<>();
+    Map<DexType, Set<DexType>> subtypeMap = new IdentityHashMap<>();
+    populateSubtypeMap(classes, subtypeMap, typeInfo, definitions);
+    return new SubtypingInfo(typeInfo, subtypeMap);
+  }
+
+  private static void populateSuperType(
       Map<DexType, Set<DexType>> map,
+      Map<DexType, TypeInfo> typeInfo,
       DexType superType,
       DexClass baseClass,
-      Function<DexType, DexClass> definitions) {
+      DexDefinitionSupplier definitionSupplier) {
     if (superType != null) {
       Set<DexType> set = map.computeIfAbsent(superType, ignore -> new HashSet<>());
       if (set.add(baseClass.type)) {
         // Only continue recursion if type has been added to set.
-        populateAllSuperTypes(map, superType, baseClass, definitions);
+        populateAllSuperTypes(map, typeInfo, superType, baseClass, definitionSupplier);
       }
     }
   }
 
-  private DexItemFactory dexItemFactory() {
-    return factory;
+  private TypeInfo getTypeInfo(DexType type) {
+    return getTypeInfo(type, typeInfo);
   }
 
-  private TypeInfo getTypeInfo(DexType type) {
+  private static TypeInfo getTypeInfo(DexType type, Map<DexType, TypeInfo> typeInfo) {
     assert type != null;
     return typeInfo.computeIfAbsent(type, TypeInfo::new);
   }
 
-  private void populateAllSuperTypes(
+  private static void populateAllSuperTypes(
       Map<DexType, Set<DexType>> map,
+      Map<DexType, TypeInfo> typeInfo,
       DexType holder,
       DexClass baseClass,
-      Function<DexType, DexClass> definitions) {
-    DexClass holderClass = definitions.apply(holder);
+      DexDefinitionSupplier definitionSupplier) {
+    DexClass holderClass = definitionSupplier.contextIndependentDefinitionFor(holder);
     // Skip if no corresponding class is found.
+    TypeInfo typeInfoHere = getTypeInfo(holder, typeInfo);
     if (holderClass != null) {
-      populateSuperType(map, holderClass.superType, baseClass, definitions);
-      if (holderClass.superType != null) {
-        getTypeInfo(holderClass.superType).addDirectSubtype(getTypeInfo(holder));
-      } else {
-        // We found java.lang.Object
-        assert dexItemFactory().objectType == holder;
-      }
-      for (DexType inter : holderClass.interfaces.values) {
-        populateSuperType(map, inter, baseClass, definitions);
-        getTypeInfo(inter).addInterfaceSubtype(holder);
-      }
+      holderClass.forEachImmediateSupertype(
+          (superType, isInterface) -> {
+            populateSuperType(map, typeInfo, superType, baseClass, definitionSupplier);
+            TypeInfo superTypeInfo = getTypeInfo(superType, typeInfo);
+            if (isInterface) {
+              superTypeInfo.addInterfaceSubtype(holder);
+            } else {
+              superTypeInfo.addDirectSubtype(typeInfoHere);
+            }
+          });
       if (holderClass.isInterface()) {
-        getTypeInfo(holder).tagAsInterface();
+        typeInfoHere.tagAsInterface();
       }
     } else {
       // The subtype chain is broken, at least make this type a subtype of Object.
-      if (holder != dexItemFactory().objectType) {
-        getTypeInfo(dexItemFactory().objectType).addDirectSubtype(getTypeInfo(holder));
+      DexType objectType = definitionSupplier.dexItemFactory().objectType;
+      if (holder != objectType) {
+        getTypeInfo(objectType, typeInfo).addDirectSubtype(typeInfoHere);
       }
     }
   }
 
-  private void populateSubtypeMap(
+  private static void populateSubtypeMap(
       Collection<DexClass> classes,
-      Function<DexType, DexClass> definitions,
-      DexItemFactory dexItemFactory) {
-    getTypeInfo(dexItemFactory.objectType).tagAsSubtypeRoot();
-    Map<DexType, Set<DexType>> map = new IdentityHashMap<>();
+      Map<DexType, Set<DexType>> map,
+      Map<DexType, TypeInfo> typeInfo,
+      DexDefinitionSupplier definitionSupplier) {
+    getTypeInfo(definitionSupplier.dexItemFactory().objectType, typeInfo).tagAsSubtypeRoot();
     for (DexClass clazz : classes) {
-      populateAllSuperTypes(map, clazz.type, clazz, definitions);
+      populateAllSuperTypes(map, typeInfo, clazz.type, clazz, definitionSupplier);
     }
-    for (Map.Entry<DexType, Set<DexType>> entry : map.entrySet()) {
-      subtypeMap.put(entry.getKey(), ImmutableSet.copyOf(entry.getValue()));
-    }
-    assert validateLevelsAreCorrect(definitions, dexItemFactory);
+    map.replaceAll((k, v) -> ImmutableSet.copyOf(v));
+    assert validateLevelsAreCorrect(typeInfo, definitionSupplier);
   }
 
-  private boolean validateLevelsAreCorrect(
-      Function<DexType, DexClass> definitions, DexItemFactory dexItemFactory) {
+  private static boolean validateLevelsAreCorrect(
+      Map<DexType, TypeInfo> typeInfo, DexDefinitionSupplier definitionSupplier) {
     Set<DexType> seenTypes = Sets.newIdentityHashSet();
     Deque<DexType> worklist = new ArrayDeque<>();
-    DexType objectType = dexItemFactory.objectType;
+    DexType objectType = definitionSupplier.dexItemFactory().objectType;
     worklist.add(objectType);
     while (!worklist.isEmpty()) {
       DexType next = worklist.pop();
-      DexClass nextHolder = definitions.apply(next);
+      DexClass nextHolder = definitionSupplier.contextIndependentDefinitionFor(next);
       DexType superType;
       if (nextHolder == null) {
         // We might lack the definition of Object, so guard against that.
-        superType = next == dexItemFactory.objectType ? null : dexItemFactory.objectType;
+        superType = next == objectType ? null : objectType;
       } else {
         superType = nextHolder.superType;
       }
       assert !seenTypes.contains(next);
       seenTypes.add(next);
-      TypeInfo nextInfo = getTypeInfo(next);
+      TypeInfo nextInfo = getTypeInfo(next, typeInfo);
       if (superType == null) {
         assert nextInfo.hierarchyLevel == ROOT_LEVEL;
       } else {
-        TypeInfo superInfo = getTypeInfo(superType);
+        TypeInfo superInfo = getTypeInfo(superType, typeInfo);
         assert superInfo.hierarchyLevel == nextInfo.hierarchyLevel - 1
             || (superInfo.hierarchyLevel == ROOT_LEVEL
                 && nextInfo.hierarchyLevel == INTERFACE_LEVEL);
@@ -153,7 +157,7 @@
       } else if (nextHolder != null) {
         // Test that the interfaces of this class are interfaces and have this class as subtype.
         for (DexType iface : nextHolder.interfaces.values) {
-          TypeInfo ifaceInfo = getTypeInfo(iface);
+          TypeInfo ifaceInfo = getTypeInfo(iface, typeInfo);
           assert ifaceInfo.directSubtypes.contains(next);
           assert ifaceInfo.hierarchyLevel == INTERFACE_LEVEL;
         }
@@ -164,7 +168,7 @@
 
   public Set<DexType> subtypes(DexType type) {
     assert type.isClassType();
-    ImmutableSet<DexType> subtypes = subtypeMap.get(type);
+    Set<DexType> subtypes = subtypeMap.get(type);
     return subtypes == null ? ImmutableSet.of() : subtypes;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/TopDownClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/graph/TopDownClassHierarchyTraversal.java
index 613db50..af17ea3 100644
--- a/src/main/java/com/android/tools/r8/graph/TopDownClassHierarchyTraversal.java
+++ b/src/main/java/com/android/tools/r8/graph/TopDownClassHierarchyTraversal.java
@@ -74,7 +74,7 @@
 
     // Add super classes to worklist.
     if (clazz.superType != null) {
-      DexClass definition = appView.definitionFor(clazz.superType);
+      DexClass definition = definitionSupplier.contextIndependentDefinitionFor(clazz.superType);
       if (definition != null && shouldTraverseUpwardsFrom(definition)) {
         addDependentsToWorklist(definition);
       }
@@ -83,7 +83,7 @@
     // Add super interfaces to worklist.
     if (!excludeInterfaces) {
       for (DexType interfaceType : clazz.interfaces.values) {
-        DexClass definition = appView.definitionFor(interfaceType);
+        DexClass definition = definitionSupplier.contextIndependentDefinitionFor(interfaceType);
         if (definition != null && shouldTraverseUpwardsFrom(definition)) {
           addDependentsToWorklist(definition);
         }
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
index 16427bf..f9e2af2 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -5,12 +5,14 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 
 public abstract class TreeFixerBase {
 
@@ -165,21 +167,28 @@
 
   /** Fixup a list of fields. */
   public DexEncodedField[] fixupFields(List<DexEncodedField> fields) {
+    return fixupFields(fields, ConsumerUtils.emptyConsumer());
+  }
+
+  public DexEncodedField[] fixupFields(
+      List<DexEncodedField> fields, Consumer<DexEncodedField.Builder> consumer) {
     if (fields == null) {
       return DexEncodedField.EMPTY_ARRAY;
     }
     DexEncodedField[] newFields = new DexEncodedField[fields.size()];
     for (int i = 0; i < fields.size(); i++) {
-      newFields[i] = fixupField(fields.get(i));
+      newFields[i] = fixupField(fields.get(i), consumer);
     }
     return newFields;
   }
 
-  private DexEncodedField fixupField(DexEncodedField field) {
+  private DexEncodedField fixupField(
+      DexEncodedField field, Consumer<DexEncodedField.Builder> consumer) {
     DexField fieldReference = field.getReference();
     DexField newFieldReference = fixupFieldReference(fieldReference);
     if (newFieldReference != fieldReference) {
-      return recordFieldChange(field, field.toTypeSubstitutedField(appView, newFieldReference));
+      return recordFieldChange(
+          field, field.toTypeSubstitutedField(appView, newFieldReference, consumer));
     }
     return field;
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index 62b09a6..b18a69c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -193,6 +193,11 @@
       representative = ListUtils.first(methods);
     }
 
+    if (representative.getAccessFlags().isAbstract() && superMethod != null) {
+      methods.forEach(method -> lensBuilder.mapMethod(method.getReference(), newMethodReference));
+      return;
+    }
+
     for (ProgramMethod method : methods) {
       if (method.getReference() == representative.getReference()) {
         lensBuilder.moveMethod(method.getReference(), newMethodReference);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
index 5ebe1a6..deb0e02 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
@@ -146,7 +146,7 @@
   @Override
   public Map<DexType, InterfaceInfo> preprocess(
       Collection<MergeGroup> groups, ExecutorService executorService) {
-    SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
+    SubtypingInfo subtypingInfo = SubtypingInfo.create(appView);
     Collection<DexProgramClass> classesOfInterest = computeClassesOfInterest(subtypingInfo);
     Map<DexType, DexMethodSignatureSet> inheritedClassMethodsPerClass =
         computeInheritedClassMethodsPerProgramClass(classesOfInterest);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
index f86a398..6d54364 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
@@ -162,7 +162,7 @@
 
   @Override
   public SubtypingInfo preprocess(Collection<MergeGroup> groups, ExecutorService executorService) {
-    return new SubtypingInfo(appView);
+    return SubtypingInfo.create(appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java
index b6ac534..58033b1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java
@@ -77,5 +77,5 @@
       AppView<AppInfoWithLiveness> appView, ArgumentInfoCollection argumentInfoCollection);
 
   public abstract AbstractFieldSet rewrittenWithLens(
-      AppView<?> appView, GraphLens lens, PrunedItems prunedItems);
+      AppView<?> appView, GraphLens lens, GraphLens codeLens, PrunedItems prunedItems);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
index 757e426..84208bb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
@@ -89,7 +89,7 @@
 
   @Override
   public AbstractFieldSet rewrittenWithLens(
-      AppView<?> appView, GraphLens lens, PrunedItems prunedItems) {
+      AppView<?> appView, GraphLens lens, GraphLens codeLens, PrunedItems prunedItems) {
     assert !isEmpty();
     ConcreteMutableFieldSet rewrittenSet = new ConcreteMutableFieldSet();
     for (DexEncodedField field : fields) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java
index 78fa9c0..65e424f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java
@@ -73,7 +73,7 @@
 
   @Override
   public AbstractFieldSet rewrittenWithLens(
-      AppView<?> appView, GraphLens lens, PrunedItems prunedItems) {
+      AppView<?> appView, GraphLens lens, GraphLens codeLens, PrunedItems prunedItems) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java
index 5de960d..c9dcd7a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java
@@ -44,7 +44,7 @@
 
   @Override
   public AbstractFieldSet rewrittenWithLens(
-      AppView<?> appView, GraphLens lens, PrunedItems prunedItems) {
+      AppView<?> appView, GraphLens lens, GraphLens codeLens, PrunedItems prunedItems) {
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
index 88d8b66..0ccf414 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Phi;
@@ -23,8 +24,11 @@
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final Function<DexType, DexType> mapping;
 
-  public DestructivePhiTypeUpdater(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    this(appView, appView.graphLens()::lookupType);
+  public DestructivePhiTypeUpdater(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      GraphLens graphLens,
+      GraphLens codeLens) {
+    this(appView, type -> graphLens.lookupType(type, codeLens));
   }
 
   public DestructivePhiTypeUpdater(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java
index 0ce069a..754fb43 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java
@@ -207,11 +207,11 @@
       return this;
     }
     TypeElement rewrittenDynamicUpperBoundType =
-        dynamicUpperBoundType.rewrittenWithLens(appView, graphLens, prunedTypes);
+        dynamicUpperBoundType.rewrittenWithLens(appView, graphLens, null, prunedTypes);
     ClassTypeElement rewrittenDynamicLowerBoundClassType = null;
     if (hasDynamicLowerBoundType()) {
       TypeElement rewrittenDynamicLowerBoundType =
-          getDynamicLowerBoundType().rewrittenWithLens(appView, graphLens, prunedTypes);
+          getDynamicLowerBoundType().rewrittenWithLens(appView, graphLens, null, prunedTypes);
       if (rewrittenDynamicLowerBoundType.isClassType()) {
         rewrittenDynamicLowerBoundClassType = rewrittenDynamicLowerBoundType.asClassType();
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
index 1ab6c53..8d2db28 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
@@ -45,7 +45,7 @@
   public DynamicType rewrittenWithLens(
       AppView<AppInfoWithLiveness> appView, GraphLens graphLens, Set<DexType> prunedTypes) {
     TypeElement rewrittenType =
-        getExactClassType().rewrittenWithLens(appView, graphLens, prunedTypes);
+        getExactClassType().rewrittenWithLens(appView, graphLens, null, prunedTypes);
     assert rewrittenType.isClassType() || rewrittenType.isPrimitiveType();
     return rewrittenType.isClassType()
         ? new ExactDynamicType(rewrittenType.asClassType())
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
index e97006c..6451a1c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.ir.code.Value;
+import java.util.Collections;
 import java.util.Set;
 import java.util.function.Function;
 
@@ -85,14 +86,23 @@
 
   public final TypeElement rewrittenWithLens(
       AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens graphLens) {
-    return rewrittenWithLens(appView, graphLens, emptySet());
+    return rewrittenWithLens(appView, graphLens, null);
   }
 
   public final TypeElement rewrittenWithLens(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       GraphLens graphLens,
+      GraphLens codeLens) {
+    return rewrittenWithLens(appView, graphLens, codeLens, Collections.emptySet());
+  }
+
+  public final TypeElement rewrittenWithLens(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      GraphLens graphLens,
+      GraphLens codeLens,
       Set<DexType> prunedTypes) {
-    return fixupClassTypeReferences(appView, graphLens::lookupType, prunedTypes);
+    return fixupClassTypeReferences(
+        appView, type -> graphLens.lookupType(type, codeLens), prunedTypes);
   }
 
   public boolean isNullable() {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index b9d02f9..d62f8e7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -244,7 +244,7 @@
   }
 
   public abstract AbstractValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens);
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens);
 
   @Override
   public abstract boolean equals(Object o);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java
index c1afe58..4253a3d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/BottomValue.java
@@ -24,7 +24,8 @@
   }
 
   @Override
-  public AbstractValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+  public AbstractValue rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NullOrAbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NullOrAbstractValue.java
index 2bf329d..db25101 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/NullOrAbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NullOrAbstractValue.java
@@ -44,8 +44,8 @@
 
   @Override
   public NullOrAbstractValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens) {
-    return new NullOrAbstractValue(value.rewrittenWithLens(appView, lens));
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+    return new NullOrAbstractValue(value.rewrittenWithLens(appView, lens, codeLens));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
index f100893..3238680 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
@@ -73,7 +73,8 @@
   }
 
   @Override
-  public AbstractValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+  public AbstractValue rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java
index e6062f6..1c539c9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java
@@ -87,7 +87,8 @@
   }
 
   @Override
-  public AbstractValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+  public AbstractValue rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index 911bddb..ad8b4d5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -105,7 +105,7 @@
   public boolean isMaterializableInAllContexts(AppView<AppInfoWithLiveness> appView) {
     DexType baseType = type.toBaseType(appView.dexItemFactory());
     if (baseType.isClassType()) {
-      DexClass clazz = appView.definitionFor(type);
+      DexClass clazz = appView.definitionFor(baseType);
       if (clazz == null || !clazz.isPublic() || !clazz.isResolvable(appView)) {
         return false;
       }
@@ -128,8 +128,9 @@
   }
 
   @Override
-  public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
-    assert lens.lookupType(type) == type;
+  public SingleValue rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+    assert lens.lookupType(type, codeLens) == type;
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
index f1fac36..4f4f7cd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
@@ -114,9 +114,11 @@
   }
 
   @Override
-  public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+  public SingleValue rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     return appView
         .abstractValueFactory()
-        .createSingleDexItemBasedStringValue(lens.rewriteReference(item), nameComputationInfo);
+        .createSingleDexItemBasedStringValue(
+            lens.rewriteReference(item, codeLens), nameComputationInfo);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index 0e746d4..cbc7e66 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -135,7 +135,8 @@
   }
 
   @Override
-  public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+  public SingleValue rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     AbstractValueFactory factory = appView.abstractValueFactory();
     if (field.holder == field.type) {
       EnumDataMap enumDataMap = appView.unboxedEnums();
@@ -143,8 +144,8 @@
         return factory.createSingleNumberValue(enumDataMap.getUnboxedValue(field));
       }
     }
-    DexField rewrittenField = lens.lookupField(field);
-    ObjectState rewrittenObjectState = getObjectState().rewrittenWithLens(appView, lens);
+    DexField rewrittenField = lens.lookupField(field, codeLens);
+    ObjectState rewrittenObjectState = getObjectState().rewrittenWithLens(appView, lens, codeLens);
     if (rewrittenField != field || rewrittenObjectState != getObjectState()) {
       return factory.createSingleFieldValue(rewrittenField, rewrittenObjectState);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index 264bec5..b3259b0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -160,7 +160,8 @@
   }
 
   @Override
-  public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+  public SingleValue rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
index f346b24..57cd2e1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
@@ -101,7 +101,8 @@
   }
 
   @Override
-  public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+  public SingleValue rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
index 94cd837..390ac7b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
@@ -56,5 +56,5 @@
 
   @Override
   public abstract SingleValue rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens);
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/StatefulObjectValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/StatefulObjectValue.java
index 39174da..edcfbc6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/StatefulObjectValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/StatefulObjectValue.java
@@ -50,8 +50,9 @@
   }
 
   @Override
-  public AbstractValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
-    return create(getObjectState().rewrittenWithLens(appView, lens));
+  public AbstractValue rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+    return create(getObjectState().rewrittenWithLens(appView, lens, codeLens));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
index 8d4001f..8faaea4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
@@ -29,7 +29,8 @@
   }
 
   @Override
-  public AbstractValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+  public AbstractValue rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
index 1b94542..0d1255e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
@@ -39,7 +39,8 @@
   }
 
   @Override
-  public ObjectState rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+  public ObjectState rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
index 817b166..8311d1e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
@@ -71,10 +71,11 @@
   }
 
   @Override
-  public ObjectState rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+  public ObjectState rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     ObjectState[] newState = new ObjectState[state.length];
     for (int i = 0; i < state.length; i++) {
-      newState[i] = state[i].rewrittenWithLens(appView, lens);
+      newState[i] = state[i].rewrittenWithLens(appView, lens, codeLens);
     }
     return new EnumValuesObjectState(newState);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
index a379776..8c9e449 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
@@ -47,7 +47,8 @@
   }
 
   @Override
-  public ObjectState rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+  public ObjectState rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
index 559fa77..7133b9c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
@@ -42,11 +42,14 @@
   }
 
   @Override
-  public ObjectState rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+  public ObjectState rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     Map<DexField, AbstractValue> rewrittenState = new IdentityHashMap<>();
     state.forEach(
         (field, value) ->
-            rewrittenState.put(lens.lookupField(field), value.rewrittenWithLens(appView, lens)));
+            rewrittenState.put(
+                lens.lookupField(field, codeLens),
+                value.rewrittenWithLens(appView, lens, codeLens)));
     return new NonEmptyObjectState(rewrittenState);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
index b81fb32..bc0762a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
@@ -51,7 +51,7 @@
   public abstract boolean isEmpty();
 
   public abstract ObjectState rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens);
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens);
 
   @Override
   public abstract boolean equals(Object o);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 90ca265..e5f15bd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -1032,13 +1033,13 @@
    *
    * @return true if any guards were renamed.
    */
-  public boolean renameGuardsInCatchHandlers(GraphLens graphLens) {
+  public boolean renameGuardsInCatchHandlers(NonIdentityGraphLens graphLens, GraphLens codeLens) {
     assert hasCatchHandlers();
     boolean anyGuardsRenamed = false;
     List<DexType> newGuards = new ArrayList<>(catchHandlers.getGuards().size());
     for (DexType guard : catchHandlers.getGuards()) {
       // The type may have changed due to class merging.
-      DexType renamed = graphLens.lookupType(guard);
+      DexType renamed = graphLens.lookupType(guard, codeLens);
       newGuards.add(renamed);
       anyGuardsRenamed |= renamed != guard;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 2ad1f65..e1ed280 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -34,6 +34,7 @@
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Set;
+import java.util.function.Consumer;
 
 public class BasicBlockInstructionListIterator implements InstructionListIterator {
 
@@ -350,8 +351,11 @@
   }
 
   @Override
-  public boolean replaceCurrentInstructionByInitClassIfPossible(
-      AppView<AppInfoWithLiveness> appView, IRCode code, DexType type) {
+  public boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
+      AppView<AppInfoWithLiveness> appView,
+      IRCode code,
+      DexType type,
+      Consumer<InitClass> consumer) {
     Instruction toBeReplaced = current;
     assert toBeReplaced != null;
     assert toBeReplaced.isStaticFieldInstruction() || toBeReplaced.isInvokeStatic();
@@ -377,7 +381,9 @@
     DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
     if (clazz != null) {
       Value dest = code.createValue(TypeElement.getInt());
-      replaceCurrentInstruction(new InitClass(dest, clazz.type));
+      InitClass initClass = new InitClass(dest, clazz.type);
+      replaceCurrentInstruction(initClass);
+      consumer.accept(initClass);
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
index 64b59e4..d81f466 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
@@ -355,6 +355,11 @@
   }
 
   @Override
+  public T visit(UnusedArgument instruction) {
+    return null;
+  }
+
+  @Override
   public T visit(Ushr instruction) {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index f4a2cdc..e44b158 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -1231,17 +1232,29 @@
     for (BasicBlock block : blocks) {
       List<Phi> phis = new ArrayList<>(block.getPhis());
       for (Phi phi : phis) {
-        if (phi.hasAnyUsers()) {
-          anyPhisRemoved |= phi.removeTrivialPhi(builder, affectedValues);
-        } else {
-          phi.removeDeadPhi();
+        WorkList<Phi> reachablePhis = WorkList.newIdentityWorkList(phi);
+        if (isDeadPhi(reachablePhis)) {
+          reachablePhis.getSeenSet().forEach(Phi::removeDeadPhi);
           anyPhisRemoved = true;
+        } else {
+          anyPhisRemoved |= phi.removeTrivialPhi(builder, affectedValues);
         }
       }
     }
     return anyPhisRemoved;
   }
 
+  private boolean isDeadPhi(WorkList<Phi> reachablePhis) {
+    while (reachablePhis.hasNext()) {
+      Phi currentPhi = reachablePhis.next();
+      if (currentPhi.hasUsers() || currentPhi.hasDebugUsers()) {
+        return false;
+      }
+      reachablePhis.addIfNotSeen(currentPhi.uniquePhiUsers());
+    }
+    return true;
+  }
+
   public int reserveMarkingColor() {
     assert anyMarkingColorAvailable();
     int color = 1;
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index a68da1a..21b9df5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -19,6 +19,7 @@
 import java.util.ListIterator;
 import java.util.NoSuchElementException;
 import java.util.Set;
+import java.util.function.Consumer;
 
 public class IRCodeInstructionListIterator implements InstructionListIterator {
 
@@ -62,9 +63,13 @@
   }
 
   @Override
-  public boolean replaceCurrentInstructionByInitClassIfPossible(
-      AppView<AppInfoWithLiveness> appView, IRCode code, DexType type) {
-    return instructionIterator.replaceCurrentInstructionByInitClassIfPossible(appView, code, type);
+  public boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
+      AppView<AppInfoWithLiveness> appView,
+      IRCode code,
+      DexType type,
+      Consumer<InitClass> consumer) {
+    return instructionIterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
+        appView, code, type, consumer);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 30f9c5f..ec25297 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -748,6 +748,14 @@
     return null;
   }
 
+  public boolean isUnusedArgument() {
+    return false;
+  }
+
+  public UnusedArgument asUnusedArgument() {
+    return null;
+  }
+
   public boolean isArithmeticBinop() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index bc6bec6..f1eab89 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -15,10 +15,12 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
 import java.util.ListIterator;
 import java.util.Set;
+import java.util.function.Consumer;
 
 public interface InstructionListIterator
     extends InstructionIterator, ListIterator<Instruction>, PreviousUntilIterator<Instruction> {
@@ -109,8 +111,17 @@
 
   boolean replaceCurrentInstructionByNullCheckIfPossible(AppView<?> appView, ProgramMethod context);
 
-  boolean replaceCurrentInstructionByInitClassIfPossible(
-      AppView<AppInfoWithLiveness> appView, IRCode code, DexType type);
+  default boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
+      AppView<AppInfoWithLiveness> appView, IRCode code, DexType type) {
+    return removeOrReplaceCurrentInstructionByInitClassIfPossible(
+        appView, code, type, ConsumerUtils.emptyConsumer());
+  }
+
+  boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
+      AppView<AppInfoWithLiveness> appView,
+      IRCode code,
+      DexType type,
+      Consumer<InitClass> consumer);
 
   void replaceCurrentInstructionWithConstClass(
       AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
index ddb611f..0fe8c68 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
@@ -144,6 +144,8 @@
 
   T visit(Throw instruction);
 
+  T visit(UnusedArgument instruction);
+
   T visit(Ushr instruction);
 
   T visit(Xor instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 37d45df..ffb5d2b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -18,6 +18,7 @@
 import com.google.common.collect.Sets;
 import java.util.ListIterator;
 import java.util.Set;
+import java.util.function.Consumer;
 
 public class LinearFlowInstructionListIterator implements InstructionListIterator {
 
@@ -86,9 +87,13 @@
   }
 
   @Override
-  public boolean replaceCurrentInstructionByInitClassIfPossible(
-      AppView<AppInfoWithLiveness> appView, IRCode code, DexType type) {
-    return currentBlockIterator.replaceCurrentInstructionByInitClassIfPossible(appView, code, type);
+  public boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
+      AppView<AppInfoWithLiveness> appView,
+      IRCode code,
+      DexType type,
+      Consumer<InitClass> consumer) {
+    return currentBlockIterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
+        appView, code, type, consumer);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
index f1527a6..eb6eb85 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
@@ -72,8 +72,9 @@
   int SUB = 63;
   int SWAP = 64;
   int THROW = 65;
-  int USHR = 66;
-  int XOR = 67;
-  int UNINITIALIZED_THIS_LOCAL_READ = 68;
-  int RECORD_FIELD_VALUES = 69;
+  int UNUSED_ARGUMENT = 66;
+  int USHR = 67;
+  int XOR = 68;
+  int UNINITIALIZED_THIS_LOCAL_READ = 69;
+  int RECORD_FIELD_VALUES = 70;
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/UnusedArgument.java b/src/main/java/com/android/tools/r8/ir/code/UnusedArgument.java
new file mode 100644
index 0000000..29fc073
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/UnusedArgument.java
@@ -0,0 +1,109 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.code;
+
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+
+/**
+ * A special instruction to load the value of an argument that has been removed as a result of code
+ * optimizations. Only used in R8 between the point of building IR and lens code rewriting.
+ */
+public class UnusedArgument extends Instruction {
+
+  public UnusedArgument(Value outValue) {
+    super(outValue);
+  }
+
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void buildDex(DexBuilder builder) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public DeadInstructionResult canBeDeadCode(AppView<?> appview, IRCode code) {
+    return DeadInstructionResult.deadIfOutValueIsDead();
+  }
+
+  @Override
+  public DexType computeVerificationType(AppView<?> appView, TypeVerificationHelper helper) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public TypeElement evaluate(AppView<?> appView) {
+    return outValue.getType();
+  }
+
+  @Override
+  public boolean hasInvariantOutType() {
+    return true;
+  }
+
+  @Override
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, ProgramMethod context) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean isUnusedArgument() {
+    return true;
+  }
+
+  @Override
+  public UnusedArgument asUnusedArgument() {
+    return this;
+  }
+
+  @Override
+  public int maxInValueRegister() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public int maxOutValueRegister() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public int opcode() {
+    return Opcodes.UNUSED_ARGUMENT;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ExtraConstantIntParameter.java b/src/main/java/com/android/tools/r8/ir/conversion/ExtraConstantIntParameter.java
index b0544d9..ec2df95 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ExtraConstantIntParameter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ExtraConstantIntParameter.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
@@ -18,6 +19,11 @@
   }
 
   @Override
+  public DexType getType(DexItemFactory dexItemFactory) {
+    return dexItemFactory.intType;
+  }
+
+  @Override
   public TypeElement getTypeElement(AppView<?> appView, DexType argType) {
     assert argType == appView.dexItemFactory().intType;
     return TypeElement.getInt();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ExtraParameter.java b/src/main/java/com/android/tools/r8/ir/conversion/ExtraParameter.java
index 6211fa3..44452f6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ExtraParameter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ExtraParameter.java
@@ -5,12 +5,15 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
 
 public abstract class ExtraParameter {
 
+  public abstract DexType getType(DexItemFactory dexItemFactory);
+
   public abstract TypeElement getTypeElement(AppView<?> appView, DexType argType);
 
   public abstract SingleNumberValue getValue(AppView<?> appView);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ExtraUnusedNullParameter.java b/src/main/java/com/android/tools/r8/ir/conversion/ExtraUnusedNullParameter.java
index a543a0c..0532a48 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ExtraUnusedNullParameter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ExtraUnusedNullParameter.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -12,6 +13,23 @@
 
 public class ExtraUnusedNullParameter extends ExtraParameter {
 
+  private final DexType type;
+
+  @Deprecated
+  public ExtraUnusedNullParameter() {
+    this(null);
+  }
+
+  public ExtraUnusedNullParameter(DexType type) {
+    this.type = type;
+  }
+
+  @Override
+  public DexType getType(DexItemFactory dexItemFactory) {
+    assert type != null;
+    return type;
+  }
+
   @Override
   public TypeElement getTypeElement(AppView<?> appView, DexType argType) {
     return TypeElement.fromDexType(argType, Nullability.maybeNull(), appView);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 5f318cd..133023f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -45,7 +45,6 @@
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.And;
 import com.android.tools.r8.ir.code.Argument;
@@ -114,7 +113,6 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Sub;
 import com.android.tools.r8.ir.code.Throw;
-import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
 import com.android.tools.r8.ir.code.Ushr;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
@@ -405,8 +403,6 @@
   private Value receiverValue;
   private List<Value> argumentValues;
 
-  private List<Instruction> pendingArgumentInstructions;
-
   // Source code to build IR from. Null if already built.
   private SourceCode source;
 
@@ -520,7 +516,6 @@
   }
 
   public List<Value> getArgumentValues() {
-    assert pendingArgumentInstructions == null || pendingArgumentInstructions.isEmpty();
     return argumentValues;
   }
 
@@ -570,7 +565,7 @@
         type =
             TypeElement.fromDexType(
                 removedArgumentInfo.getType(), Nullability.maybeNull(), appView);
-        addConstantOrUnusedArgument(register, removedArgumentInfo);
+        addNonThisArgument(register, type);
       } else {
         DexType argType;
         if (argumentInfo.isRewrittenTypeInfo()) {
@@ -603,14 +598,11 @@
       register += type.requiredRegisters();
       usedArgumentIndex++;
       if (extraParameter instanceof ExtraUnusedNullParameter) {
-        assert argType.isClassType();
-        addExtraUnusedNullArgument(register);
+        addExtraUnusedArgument(register, argType);
       } else {
         addNonThisArgument(register, type);
       }
     }
-
-    flushArgumentInstructions();
   }
 
   /**
@@ -996,11 +988,15 @@
     value.markAsThis();
   }
 
-  private void addExtraUnusedNullArgument(int register) {
+  private void addExtraUnusedArgument(int register, DexType type) {
     // Extra unused null arguments should bypass the register check, they may use registers
     // beyond the limit of what the method can use. They don't have debug information and are
     // always null.
-    Value value = new Value(valueNumberGenerator.next(), TypeElement.getNull(), null);
+    Value value =
+        new Value(
+            valueNumberGenerator.next(),
+            type.isReferenceType() ? TypeElement.getNull() : type.toTypeElement(appView),
+            null);
     addNonThisArgument(new Argument(value, currentBlock.size(), false));
   }
 
@@ -1024,46 +1020,6 @@
     argumentValues.add(argument.outValue());
   }
 
-  public void addConstantOrUnusedArgument(int register, RemovedArgumentInfo info) {
-    handleConstantOrUnusedArgument(register, info);
-  }
-
-  private void handleConstantOrUnusedArgument(
-      int register, RemovedArgumentInfo removedArgumentInfo) {
-    assert removedArgumentInfo != null;
-    if (removedArgumentInfo.hasSingleValue()) {
-      if (pendingArgumentInstructions == null) {
-        pendingArgumentInstructions = new ArrayList<>();
-      }
-      DebugLocalInfo local = getOutgoingLocal(register);
-      SingleValue singleValue = removedArgumentInfo.getSingleValue();
-      TypeElement type =
-          removedArgumentInfo.getType().isReferenceType() && singleValue.isNull()
-              ? getNull()
-              : removedArgumentInfo.getType().toTypeElement(appView);
-      Instruction materializingInstruction =
-          singleValue.createMaterializingInstruction(
-              appView.withClassHierarchy(),
-              method,
-              valueNumberGenerator,
-              TypeAndLocalInfoSupplier.create(type, local));
-      writeRegister(
-          register,
-          materializingInstruction.outValue(),
-          ThrowingInfo.defaultForInstruction(materializingInstruction));
-      pendingArgumentInstructions.add(materializingInstruction);
-    } else {
-      assert removedArgumentInfo.isNeverUsed();
-    }
-  }
-
-  public void flushArgumentInstructions() {
-    if (pendingArgumentInstructions != null) {
-      pendingArgumentInstructions.forEach(this::addInstruction);
-      pendingArgumentInstructions.clear();
-    }
-  }
-
   private static boolean isValidFor(Value value, DebugLocalInfo local) {
     // Invalid debug-info may cause attempt to read a local that is not actually alive.
     // See b/37722432 and regression test {@code jasmin.InvalidDebugInfoTests::testInvalidInfoThrow}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index e5602b2..a52190d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -34,6 +34,7 @@
 import static com.android.tools.r8.utils.ObjectUtils.getBooleanOrElse;
 
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
@@ -50,16 +51,19 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.FieldLookupResult;
 import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfo;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlockIterator;
@@ -82,25 +86,30 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMultiNewArray;
 import com.android.tools.r8.ir.code.InvokeNewArray;
-import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.MoveException;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.SafeCheckCast;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
+import com.android.tools.r8.ir.code.UnusedArgument;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.LazyBox;
 import com.android.tools.r8.verticalclassmerging.InterfaceTypeToClassTypeLensCodeRewriterHelper;
+import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Deque;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -110,30 +119,48 @@
 
 public class LensCodeRewriter {
 
+  private static class GraphLensInterval {
+
+    private final NonIdentityGraphLens graphLens;
+    private final GraphLens codeLens;
+    private final DexMethod method;
+
+    GraphLensInterval(NonIdentityGraphLens graphLens, GraphLens codeLens, DexMethod method) {
+      this.graphLens = graphLens;
+      this.codeLens = codeLens;
+      this.method = method;
+    }
+
+    public NonIdentityGraphLens getGraphLens() {
+      return graphLens;
+    }
+
+    public GraphLens getCodeLens() {
+      return codeLens;
+    }
+
+    public DexMethod getMethod() {
+      return method;
+    }
+  }
+
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final DexItemFactory factory;
   private final EnumUnboxer enumUnboxer;
-  private LensCodeRewriterUtils helper = null;
   private final InternalOptions options;
 
   LensCodeRewriter(AppView<? extends AppInfoWithClassHierarchy> appView, EnumUnboxer enumUnboxer) {
     this.appView = appView;
+    this.factory = appView.dexItemFactory();
     this.enumUnboxer = enumUnboxer;
     this.options = appView.options();
   }
 
-  private LensCodeRewriterUtils getHelper() {
-    // The LensCodeRewriterUtils uses internal caches that are not valid if the graphLens changes.
-    if (helper != null && helper.hasGraphLens(appView.graphLens())) {
-      return helper;
-    }
-    helper = new LensCodeRewriterUtils(appView);
-    return helper;
-  }
-
-  private Value makeOutValue(Instruction insn, IRCode code) {
+  private Value makeOutValue(
+      Instruction insn, IRCode code, NonIdentityGraphLens graphLens, GraphLens codeLens) {
     if (insn.hasOutValue()) {
       TypeElement oldType = insn.getOutType();
-      TypeElement newType = oldType.rewrittenWithLens(appView, appView.graphLens());
+      TypeElement newType = oldType.rewrittenWithLens(appView, graphLens, codeLens);
       return code.createValue(newType, insn.getLocalInfo());
     }
     return null;
@@ -150,21 +177,71 @@
 
   /** Replace type appearances, invoke targets and field accesses with actual definitions. */
   public void rewrite(IRCode code, ProgramMethod method, MethodProcessor methodProcessor) {
-    Set<Phi> affectedPhis = enumUnboxer.rewriteCode(code, methodProcessor);
-    GraphLens graphLens = appView.graphLens();
-    DexItemFactory factory = appView.dexItemFactory();
+    Deque<GraphLensInterval> unappliedLenses = getUnappliedLenses(method);
+    DexMethod originalMethodReference =
+        appView.graphLens().getOriginalMethodSignature(method.getReference());
+    while (!unappliedLenses.isEmpty()) {
+      GraphLensInterval unappliedLens = unappliedLenses.removeLast();
+      RewrittenPrototypeDescription prototypeChanges =
+          unappliedLens
+              .getGraphLens()
+              .lookupPrototypeChangesForMethodDefinition(
+                  unappliedLens.getMethod(), unappliedLens.getCodeLens());
+      rewritePartial(
+          code,
+          method,
+          originalMethodReference,
+          methodProcessor,
+          unappliedLens.getGraphLens(),
+          unappliedLens.getCodeLens(),
+          prototypeChanges);
+    }
+    assert code.hasNoMergedClasses(appView);
+  }
+
+  private void rewritePartial(
+      IRCode code,
+      ProgramMethod method,
+      DexMethod originalMethodReference,
+      MethodProcessor methodProcessor,
+      NonIdentityGraphLens graphLens,
+      GraphLens codeLens,
+      RewrittenPrototypeDescription prototypeChanges) {
     // Rewriting types that affects phi can cause us to compute TOP for cyclic phi's. To solve this
     // we track all phi's that needs to be re-computed.
+    Set<Phi> affectedPhis = Sets.newIdentityHashSet();
+    Set<UnusedArgument> unusedArguments = Sets.newIdentityHashSet();
+    rewriteArguments(
+        code, originalMethodReference, prototypeChanges, affectedPhis, unusedArguments);
+    if (graphLens.hasCustomCodeRewritings()) {
+      assert graphLens.isEnumUnboxerLens();
+      assert graphLens.getPrevious() == codeLens;
+      affectedPhis.addAll(enumUnboxer.rewriteCode(code, methodProcessor, prototypeChanges));
+    }
+    rewritePartialDefault(
+        code, method, graphLens, codeLens, prototypeChanges, affectedPhis, unusedArguments);
+  }
+
+  private void rewritePartialDefault(
+      IRCode code,
+      ProgramMethod method,
+      NonIdentityGraphLens graphLens,
+      GraphLens codeLens,
+      RewrittenPrototypeDescription prototypeChangesForMethod,
+      Set<Phi> affectedPhis,
+      Set<UnusedArgument> unusedArguments) {
     BasicBlockIterator blocks = code.listIterator();
+    LazyBox<LensCodeRewriterUtils> helper =
+        new LazyBox<>(() -> new LensCodeRewriterUtils(appView, graphLens, codeLens));
     InterfaceTypeToClassTypeLensCodeRewriterHelper interfaceTypeToClassTypeRewriterHelper =
-        InterfaceTypeToClassTypeLensCodeRewriterHelper.create(appView, code, methodProcessor);
+        InterfaceTypeToClassTypeLensCodeRewriterHelper.create(appView, code, graphLens, codeLens);
     boolean mayHaveUnreachableBlocks = false;
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
       if (block.hasCatchHandlers() && options.enableVerticalClassMerging) {
-        boolean anyGuardsRenamed = block.renameGuardsInCatchHandlers(graphLens);
+        boolean anyGuardsRenamed = block.renameGuardsInCatchHandlers(graphLens, codeLens);
         if (anyGuardsRenamed) {
-          mayHaveUnreachableBlocks |= unlinkDeadCatchHandlers(block);
+          mayHaveUnreachableBlocks |= unlinkDeadCatchHandlers(block, graphLens, codeLens);
         }
       }
       InstructionListIterator iterator = block.listIterator(code);
@@ -175,9 +252,9 @@
             {
               InvokeCustom invokeCustom = current.asInvokeCustom();
               DexCallSite callSite = invokeCustom.getCallSite();
-              DexCallSite newCallSite = getHelper().rewriteCallSite(callSite, method);
+              DexCallSite newCallSite = helper.computeIfAbsent().rewriteCallSite(callSite, method);
               if (newCallSite != callSite) {
-                Value newOutValue = makeOutValue(invokeCustom, code);
+                Value newOutValue = makeOutValue(invokeCustom, code, graphLens, codeLens);
                 InvokeCustom newInvokeCustom =
                     new InvokeCustom(newCallSite, newOutValue, invokeCustom.inValues());
                 iterator.replaceCurrentInstruction(newInvokeCustom);
@@ -192,10 +269,11 @@
             {
               DexMethodHandle handle = current.asConstMethodHandle().getValue();
               DexMethodHandle newHandle =
-                  getHelper()
+                  helper
+                      .computeIfAbsent()
                       .rewriteDexMethodHandle(handle, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, method);
               if (newHandle != handle) {
-                Value newOutValue = makeOutValue(current, code);
+                Value newOutValue = makeOutValue(current, code, graphLens, codeLens);
                 iterator.replaceCurrentInstruction(new ConstMethodHandle(newOutValue, newHandle));
                 if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
                   affectedPhis.addAll(newOutValue.uniquePhiUsers());
@@ -209,7 +287,10 @@
               InitClass initClass = current.asInitClass();
               new InstructionReplacer(code, current, iterator, affectedPhis)
                   .replaceInstructionIfTypeChanged(
-                      initClass.getClassValue(), (t, v) -> new InitClass(v, t));
+                      initClass.getClassValue(),
+                      (t, v) -> new InitClass(v, t),
+                      graphLens,
+                      codeLens);
             }
             break;
 
@@ -236,7 +317,9 @@
                               factory.createMethod(
                                   mappedHolder, invokedMethod.proto, invokedMethod.name);
                           return Invoke.create(VIRTUAL, actualTarget, null, v, invoke.inValues());
-                        });
+                        },
+                        graphLens,
+                        codeLens);
                 continue;
               }
               if (!invokedHolder.isClassType()) {
@@ -247,7 +330,8 @@
                 checkInvokeDirect(method.getReference(), invoke.asInvokeDirect());
               }
               MethodLookupResult lensLookup =
-                  graphLens.lookupMethod(invokedMethod, method.getReference(), invoke.getType());
+                  graphLens.lookupMethod(
+                      invokedMethod, method.getReference(), invoke.getType(), codeLens);
               DexMethod actualTarget = lensLookup.getReference();
               Invoke.Type actualInvokeType = lensLookup.getType();
 
@@ -313,15 +397,16 @@
                         @Override
                         public TypeElement getOutType() {
                           return graphLens
-                              .lookupType(invokedMethod.getReturnType())
+                              .lookupType(invokedMethod.getReturnType(), codeLens)
                               .toTypeElement(appView);
                         }
                       };
+                  prototypeChanges.verifyConstantReturnAccessibleInContext(
+                      appView.withLiveness(), method, graphLens);
                   constantReturnMaterializingInstruction =
                       prototypeChanges.getConstantReturn(
                           appView.withLiveness(),
                           code,
-                          method,
                           invoke.getPosition(),
                           typeAndLocalInfo);
                   if (invoke.outValue().hasLocalInfo()) {
@@ -350,7 +435,7 @@
                     newOutValue = null;
                   }
                 } else {
-                  newOutValue = makeOutValue(invoke, code);
+                  newOutValue = makeOutValue(invoke, code, graphLens, codeLens);
                 }
 
                 Map<SingleNumberValue, Map<DexType, Value>> parameterMap = new IdentityHashMap<>();
@@ -436,16 +521,10 @@
             {
               InstanceGet instanceGet = current.asInstanceGet();
               DexField field = instanceGet.getField();
-              FieldLookupResult lookup = graphLens.lookupFieldResult(field);
+              FieldLookupResult lookup = graphLens.lookupFieldResult(field, codeLens);
               DexField rewrittenField = rewriteFieldReference(lookup, method);
-              DexMethod replacementMethod =
-                  graphLens.lookupGetFieldForMethod(rewrittenField, method.getReference());
               Value newOutValue = null;
-              if (replacementMethod != null) {
-                newOutValue = makeOutValue(instanceGet, code, rewrittenField);
-                iterator.replaceCurrentInstruction(
-                    new InvokeStatic(replacementMethod, newOutValue, instanceGet.inValues()));
-              } else if (rewrittenField != field) {
+              if (rewrittenField != field) {
                 newOutValue = makeOutValue(instanceGet, code, rewrittenField);
                 iterator.replaceCurrentInstruction(
                     new InstanceGet(newOutValue, instanceGet.object(), rewrittenField));
@@ -478,20 +557,12 @@
             {
               InstancePut instancePut = current.asInstancePut();
               DexField field = instancePut.getField();
-              FieldLookupResult lookup = graphLens.lookupFieldResult(field);
+              FieldLookupResult lookup = graphLens.lookupFieldResult(field, codeLens);
               iterator =
                   insertCastForFieldAssignmentIfNeeded(code, blocks, iterator, instancePut, lookup);
 
               DexField rewrittenField = rewriteFieldReference(lookup, method);
-              DexMethod replacementMethod =
-                  graphLens.lookupPutFieldForMethod(rewrittenField, method.getReference());
-              if (replacementMethod != null) {
-                InvokeStatic replacement =
-                    new InvokeStatic(replacementMethod, null, instancePut.inValues());
-                iterator.replaceCurrentInstruction(replacement);
-                interfaceTypeToClassTypeRewriterHelper.insertCastsForOperandsIfNeeded(
-                    instancePut, replacement, blocks, block, iterator);
-              } else if (rewrittenField != field) {
+              if (rewrittenField != field) {
                 Value rewrittenValue =
                     rewriteValueIfDefault(
                         code, iterator, field.type, rewrittenField.type, instancePut.value());
@@ -509,16 +580,10 @@
             {
               StaticGet staticGet = current.asStaticGet();
               DexField field = staticGet.getField();
-              FieldLookupResult lookup = graphLens.lookupFieldResult(field);
+              FieldLookupResult lookup = graphLens.lookupFieldResult(field, codeLens);
               DexField rewrittenField = rewriteFieldReference(lookup, method);
-              DexMethod replacementMethod =
-                  graphLens.lookupGetFieldForMethod(rewrittenField, method.getReference());
               Value newOutValue = null;
-              if (replacementMethod != null) {
-                newOutValue = makeOutValue(staticGet, code, rewrittenField);
-                iterator.replaceCurrentInstruction(
-                    new InvokeStatic(replacementMethod, newOutValue, staticGet.inValues()));
-              } else if (rewrittenField != field) {
+              if (rewrittenField != field) {
                 newOutValue = makeOutValue(staticGet, code, rewrittenField);
                 iterator.replaceCurrentInstruction(new StaticGet(newOutValue, rewrittenField));
               }
@@ -550,21 +615,12 @@
             {
               StaticPut staticPut = current.asStaticPut();
               DexField field = staticPut.getField();
-              FieldLookupResult lookup = graphLens.lookupFieldResult(field);
+              FieldLookupResult lookup = graphLens.lookupFieldResult(field, codeLens);
               iterator =
                   insertCastForFieldAssignmentIfNeeded(code, blocks, iterator, staticPut, lookup);
 
               DexField actualField = rewriteFieldReference(lookup, method);
-              DexMethod replacementMethod =
-                  graphLens.lookupPutFieldForMethod(actualField, method.getReference());
-
-              if (replacementMethod != null) {
-                InvokeStatic replacement =
-                    new InvokeStatic(replacementMethod, staticPut.outValue(), staticPut.inValues());
-                iterator.replaceCurrentInstruction(replacement);
-                interfaceTypeToClassTypeRewriterHelper.insertCastsForOperandsIfNeeded(
-                    staticPut, replacement, blocks, block, iterator);
-              } else if (actualField != field) {
+              if (actualField != field) {
                 Value rewrittenValue =
                     rewriteValueIfDefault(
                         code, iterator, field.type, actualField.type, staticPut.value());
@@ -583,7 +639,9 @@
                   .replaceInstructionIfTypeChanged(
                       checkCast.getType(),
                       (t, v) ->
-                          new CheckCast(v, checkCast.object(), t, checkCast.ignoreCompatRules()));
+                          new CheckCast(v, checkCast.object(), t, checkCast.ignoreCompatRules()),
+                      graphLens,
+                      codeLens);
             }
             break;
 
@@ -592,7 +650,7 @@
               ConstClass constClass = current.asConstClass();
               new InstructionReplacer(code, current, iterator, affectedPhis)
                   .replaceInstructionIfTypeChanged(
-                      constClass.getValue(), (t, v) -> new ConstClass(v, t));
+                      constClass.getValue(), (t, v) -> new ConstClass(v, t), graphLens, codeLens);
             }
             break;
 
@@ -601,7 +659,10 @@
               InstanceOf instanceOf = current.asInstanceOf();
               new InstructionReplacer(code, current, iterator, affectedPhis)
                   .replaceInstructionIfTypeChanged(
-                      instanceOf.type(), (t, v) -> new InstanceOf(v, instanceOf.value(), t));
+                      instanceOf.type(),
+                      (t, v) -> new InstanceOf(v, instanceOf.value(), t),
+                      graphLens,
+                      codeLens);
             }
             break;
 
@@ -611,7 +672,9 @@
               new InstructionReplacer(code, current, iterator, affectedPhis)
                   .replaceInstructionIfTypeChanged(
                       multiNewArray.getArrayType(),
-                      (t, v) -> new InvokeMultiNewArray(t, v, multiNewArray.inValues()));
+                      (t, v) -> new InvokeMultiNewArray(t, v, multiNewArray.inValues()),
+                      graphLens,
+                      codeLens);
             }
             break;
 
@@ -621,7 +684,9 @@
               new InstructionReplacer(code, current, iterator, affectedPhis)
                   .replaceInstructionIfTypeChanged(
                       newArray.getArrayType(),
-                      (t, v) -> new InvokeNewArray(t, v, newArray.inValues()));
+                      (t, v) -> new InvokeNewArray(t, v, newArray.inValues()),
+                      graphLens,
+                      codeLens);
             }
             break;
 
@@ -630,7 +695,10 @@
               MoveException moveException = current.asMoveException();
               new InstructionReplacer(code, current, iterator, affectedPhis)
                   .replaceInstructionIfTypeChanged(
-                      moveException.getExceptionType(), (t, v) -> new MoveException(v, t, options));
+                      moveException.getExceptionType(),
+                      (t, v) -> new MoveException(v, t, options),
+                      graphLens,
+                      codeLens);
             }
             break;
 
@@ -639,7 +707,10 @@
               NewArrayEmpty newArrayEmpty = current.asNewArrayEmpty();
               new InstructionReplacer(code, current, iterator, affectedPhis)
                   .replaceInstructionIfTypeChanged(
-                      newArrayEmpty.type, (t, v) -> new NewArrayEmpty(v, newArrayEmpty.size(), t));
+                      newArrayEmpty.type,
+                      (t, v) -> new NewArrayEmpty(v, newArrayEmpty.size(), t),
+                      graphLens,
+                      codeLens);
             }
             break;
 
@@ -647,7 +718,7 @@
             {
               DexType type = current.asNewInstance().clazz;
               new InstructionReplacer(code, current, iterator, affectedPhis)
-                  .replaceInstructionIfTypeChanged(type, NewInstance::new);
+                  .replaceInstructionIfTypeChanged(type, NewInstance::new, graphLens, codeLens);
             }
             break;
 
@@ -661,7 +732,7 @@
                 break;
               }
 
-              insertCastForReturnIfNeeded(code, blocks, iterator, ret);
+              insertCastForReturnIfNeeded(code, blocks, iterator, ret, prototypeChangesForMethod);
 
               DexType returnType = code.context().getReturnType();
               Value retValue = ret.returnValue();
@@ -687,23 +758,6 @@
             break;
 
           case ARGUMENT:
-            {
-              Argument argument = current.asArgument();
-              TypeElement currentArgumentType = argument.getOutType();
-              TypeElement newArgumentType =
-                  method
-                      .getArgumentType(argument.getIndex())
-                      .toTypeElement(appView, currentArgumentType.nullability());
-              if (!newArgumentType.equals(currentArgumentType)) {
-                affectedPhis.addAll(argument.outValue().uniquePhiUsers());
-                iterator.replaceCurrentInstruction(
-                    Argument.builder()
-                        .setIndex(argument.getIndex())
-                        .setFreshOutValue(code, newArgumentType)
-                        .setPosition(argument)
-                        .build());
-              }
-            }
             break;
 
           case ASSUME:
@@ -714,7 +768,7 @@
             if (current.hasOutValue()) {
               // For all other instructions, substitute any changed type.
               TypeElement type = current.getOutType();
-              TypeElement substituted = type.rewrittenWithLens(appView, graphLens);
+              TypeElement substituted = type.rewrittenWithLens(appView, graphLens, codeLens);
               if (substituted != type) {
                 current.outValue().setType(substituted);
                 affectedPhis.addAll(current.outValue().uniquePhiUsers());
@@ -728,14 +782,173 @@
       code.removeUnreachableBlocks();
     }
     if (!affectedPhis.isEmpty()) {
-      new DestructivePhiTypeUpdater(appView).recomputeAndPropagateTypes(code, affectedPhis);
+      new DestructivePhiTypeUpdater(appView, graphLens, codeLens)
+          .recomputeAndPropagateTypes(code, affectedPhis);
     }
+    code.removeAllDeadAndTrivialPhis();
+    removeUnusedArguments(method, code, unusedArguments);
 
     // Finalize cast insertion.
     interfaceTypeToClassTypeRewriterHelper.processWorklist();
 
     assert code.isConsistentSSABeforeTypesAreCorrect();
-    assert code.hasNoMergedClasses(appView);
+  }
+
+  // Applies the prototype changes of the current method to the argument instructions:
+  // - Replaces constant arguments by their constant value and then removes the (now unused)
+  //   argument instruction
+  // - Removes unused arguments
+  // - Updates the type of arguments whose type has been strengthened
+  private void rewriteArguments(
+      IRCode code,
+      DexMethod originalMethodReference,
+      RewrittenPrototypeDescription prototypeChanges,
+      Set<Phi> affectedPhis,
+      Set<UnusedArgument> unusedArguments) {
+    List<Instruction> argumentPostlude = new ArrayList<>();
+    int oldArgumentIndex = 0;
+    int newArgumentIndex = 0;
+    InstructionListIterator instructionIterator = code.entryBlock().listIterator(code);
+    while (instructionIterator.hasNext()) {
+      Instruction instruction = instructionIterator.next();
+      if (!instruction.isArgument()) {
+        break;
+      }
+
+      Argument argument = instruction.asArgument();
+      ArgumentInfo argumentInfo =
+          prototypeChanges.getArgumentInfoCollection().getArgumentInfo(oldArgumentIndex);
+      if (argumentInfo.isRemovedArgumentInfo()) {
+        rewriteRemovedArgument(
+            code,
+            instructionIterator,
+            originalMethodReference,
+            argument,
+            argumentInfo.asRemovedArgumentInfo(),
+            affectedPhis,
+            argumentPostlude,
+            unusedArguments);
+      } else {
+        if (argumentInfo.isRewrittenTypeInfo()) {
+          rewriteArgumentType(
+              code,
+              instructionIterator,
+              argument,
+              argumentInfo.asRewrittenTypeInfo(),
+              affectedPhis,
+              newArgumentIndex);
+        } else if (newArgumentIndex != oldArgumentIndex) {
+          instructionIterator.replaceCurrentInstruction(
+              Argument.builder()
+                  .setIndex(newArgumentIndex)
+                  .setFreshOutValue(code, argument.getOutType(), argument.getLocalInfo())
+                  .setPosition(argument.getPosition())
+                  .build());
+        }
+        newArgumentIndex++;
+      }
+      oldArgumentIndex++;
+    }
+
+    instructionIterator.previous();
+
+    if (!argumentPostlude.isEmpty()) {
+      for (Instruction instruction : argumentPostlude) {
+        instructionIterator.add(instruction);
+      }
+    }
+  }
+
+  private void rewriteRemovedArgument(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      DexMethod originalMethodReference,
+      Argument argument,
+      RemovedArgumentInfo removedArgumentInfo,
+      Set<Phi> affectedPhis,
+      List<Instruction> argumentPostlude,
+      Set<UnusedArgument> unusedArguments) {
+    Instruction replacement;
+    if (removedArgumentInfo.hasSingleValue()) {
+      SingleValue singleValue = removedArgumentInfo.getSingleValue();
+      TypeElement type =
+          removedArgumentInfo.getType().isReferenceType() && singleValue.isNull()
+              ? TypeElement.getNull()
+              : removedArgumentInfo.getType().toTypeElement(appView);
+      replacement =
+          singleValue.createMaterializingInstruction(
+              appView, code, TypeAndLocalInfoSupplier.create(type, argument.getLocalInfo()));
+      replacement.setPosition(
+          SourcePosition.builder().setLine(0).setMethod(originalMethodReference).build());
+    } else {
+      TypeElement unusedArgumentType = removedArgumentInfo.getType().toTypeElement(appView);
+      replacement = new UnusedArgument(code.createValue(unusedArgumentType));
+      replacement.setPosition(Position.none());
+      unusedArguments.add(replacement.asUnusedArgument());
+    }
+    argument.outValue().replaceUsers(replacement.outValue());
+    affectedPhis.addAll(replacement.outValue().uniquePhiUsers());
+    argumentPostlude.add(replacement);
+    instructionIterator.removeOrReplaceByDebugLocalRead();
+  }
+
+  private void rewriteArgumentType(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      Argument argument,
+      RewrittenTypeInfo rewrittenTypeInfo,
+      Set<Phi> affectedPhis,
+      int newArgumentIndex) {
+    TypeElement rewrittenType = rewrittenTypeInfo.getNewType().toTypeElement(appView);
+    Argument replacement =
+        Argument.builder()
+            .setIndex(newArgumentIndex)
+            .setFreshOutValue(code, rewrittenType, argument.getLocalInfo())
+            .setPosition(argument.getPosition())
+            .build();
+    instructionIterator.replaceCurrentInstruction(replacement);
+    affectedPhis.addAll(replacement.outValue().uniquePhiUsers());
+  }
+
+  private void removeUnusedArguments(
+      ProgramMethod method, IRCode code, Set<UnusedArgument> unusedArguments) {
+    for (UnusedArgument unusedArgument : unusedArguments) {
+      if (unusedArgument.outValue().hasAnyUsers()) {
+        throw new Unreachable("Unused argument with users in " + method.toSourceString());
+      }
+      InstructionListIterator instructionIterator = unusedArgument.getBlock().listIterator(code);
+      instructionIterator.nextUntil(instruction -> instruction == unusedArgument);
+      instructionIterator.removeOrReplaceByDebugLocalRead();
+    }
+  }
+
+  private Deque<GraphLensInterval> getUnappliedLenses(ProgramMethod method) {
+    Deque<GraphLensInterval> unappliedLenses = new ArrayDeque<>(8);
+    GraphLens codeLens = appView.codeLens();
+    GraphLens currentLens = appView.graphLens();
+    DexMethod currentMethod = method.getReference();
+    while (currentLens != codeLens) {
+      assert currentLens.isNonIdentityLens();
+      NonIdentityGraphLens currentNonIdentityLens = currentLens.asNonIdentityLens();
+      NonIdentityGraphLens fromInclusiveLens = currentNonIdentityLens;
+      if (!currentNonIdentityLens.hasCustomCodeRewritings()) {
+        GraphLens fromInclusiveLensPredecessor = fromInclusiveLens.getPrevious();
+        while (fromInclusiveLensPredecessor.isNonIdentityLens()
+            && !fromInclusiveLensPredecessor.hasCustomCodeRewritings()
+            && fromInclusiveLensPredecessor != codeLens) {
+          fromInclusiveLens = fromInclusiveLensPredecessor.asNonIdentityLens();
+          fromInclusiveLensPredecessor = fromInclusiveLens.getPrevious();
+        }
+      }
+      GraphLensInterval unappliedLens =
+          new GraphLensInterval(
+              currentNonIdentityLens, fromInclusiveLens.getPrevious(), currentMethod);
+      unappliedLenses.addLast(unappliedLens);
+      currentLens = unappliedLens.getCodeLens();
+      currentMethod = currentNonIdentityLens.getOriginalMethodSignature(currentMethod, currentLens);
+    }
+    assert unappliedLenses.size() <= 8;
+    return unappliedLenses;
   }
 
   private InstructionListIterator insertCastForFieldAssignmentIfNeeded(
@@ -778,10 +991,19 @@
       InvokeMethod invoke,
       MethodLookupResult lookup) {
     // If the invoke has been staticized, then synthesize a null check for the receiver.
-    if (!invoke.isInvokeMethodWithReceiver() || !lookup.getType().isStatic()) {
+    if (!invoke.isInvokeMethodWithReceiver()) {
       return iterator;
     }
 
+    ArgumentInfo receiverArgumentInfo =
+        lookup.getPrototypeChanges().getArgumentInfoCollection().getArgumentInfo(0);
+    if (!receiverArgumentInfo.isRemovedArgumentInfo()
+        || !receiverArgumentInfo.asRemovedArgumentInfo().isCheckNullOrZeroSet()) {
+      return iterator;
+    }
+
+    assert lookup.getType().isStatic();
+
     TypeElement receiverType = invoke.asInvokeMethodWithReceiver().getReceiver().getType();
     if (receiverType.isDefinitelyNotNull()) {
       return iterator;
@@ -798,15 +1020,6 @@
         iterator.insertNullCheckInstruction(
             appView, code, blocks, invoke.getFirstArgument(), nullCheckPosition);
 
-    // TODO(b/199864962): Lens code rewriting should follow the order of graph lenses, i.e., enum
-    //  unboxing rewriting should happen after method staticizing.
-    if (receiverType.isClassType()
-        && appView.unboxedEnums().isUnboxedEnum(receiverType.asClassType().getClassType())) {
-      iterator.previousUntil(instruction -> instruction == nullCheck);
-      iterator.next();
-      enumUnboxer.rewriteNullCheck(iterator, nullCheck);
-    }
-
     // Reset the block iterator.
     if (invoke.getBlock().hasCatchHandlers()) {
       BasicBlock splitBlock = invoke.getBlock();
@@ -871,11 +1084,11 @@
   }
 
   private InstructionListIterator insertCastForReturnIfNeeded(
-      IRCode code, BasicBlockIterator blocks, InstructionListIterator iterator, Return ret) {
-    RewrittenPrototypeDescription prototypeChanges =
-        appView
-            .graphLens()
-            .lookupPrototypeChangesForMethodDefinition(code.context().getReference());
+      IRCode code,
+      BasicBlockIterator blocks,
+      InstructionListIterator iterator,
+      Return ret,
+      RewrittenPrototypeDescription prototypeChanges) {
     if (!prototypeChanges.hasRewrittenReturnInfo()
         || !prototypeChanges.getRewrittenReturnInfo().hasCastType()) {
       return iterator;
@@ -982,7 +1195,7 @@
       return;
     }
     DexMethod invokedMethod = invoke.getInvokedMethod();
-    if (invokedMethod.name != appView.dexItemFactory().constructorMethodName) {
+    if (invokedMethod.name != factory.constructorMethodName) {
       // Not a constructor call.
       return;
     }
@@ -1020,7 +1233,8 @@
    *
    * @return true if any dead catch handlers were removed.
    */
-  private boolean unlinkDeadCatchHandlers(BasicBlock block) {
+  private boolean unlinkDeadCatchHandlers(
+      BasicBlock block, NonIdentityGraphLens graphLens, GraphLens codeLens) {
     assert block.hasCatchHandlers();
     CatchHandlers<BasicBlock> catchHandlers = block.getCatchHandlers();
     List<DexType> guards = catchHandlers.getGuards();
@@ -1030,7 +1244,7 @@
     List<BasicBlock> deadCatchHandlers = new ArrayList<>();
     for (int i = 0; i < guards.size(); i++) {
       // The type may have changed due to class merging.
-      DexType guard = appView.graphLens().lookupType(guards.get(i));
+      DexType guard = graphLens.lookupType(guards.get(i), codeLens);
       boolean guardSeenBefore = !previouslySeenGuards.add(guard);
       if (guardSeenBefore) {
         deadCatchHandlers.add(targets.get(i));
@@ -1060,10 +1274,13 @@
     }
 
     void replaceInstructionIfTypeChanged(
-        DexType type, BiFunction<DexType, Value, Instruction> constructor) {
-      DexType newType = appView.graphLens().lookupType(type);
+        DexType type,
+        BiFunction<DexType, Value, Instruction> constructor,
+        NonIdentityGraphLens graphLens,
+        GraphLens codeLens) {
+      DexType newType = graphLens.lookupType(type, codeLens);
       if (newType != type) {
-        Value newOutValue = makeOutValue(current, code);
+        Value newOutValue = makeOutValue(current, code, graphLens, codeLens);
         Instruction newInstruction = constructor.apply(newType, newOutValue);
         iterator.replaceCurrentInstruction(newInstruction);
         if (newOutValue != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
index c411187..0702fc4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
@@ -38,6 +38,7 @@
 
   private final DexDefinitionSupplier definitions;
   private final GraphLens graphLens;
+  private final GraphLens codeLens;
 
   private final Map<DexProto, DexProto> protoFixupCache = new ConcurrentHashMap<>();
 
@@ -46,18 +47,21 @@
   private final Map<DexCallSite, DexCallSite> rewrittenCallSiteCache;
 
   public LensCodeRewriterUtils(AppView<?> appView) {
-    this(appView, appView.graphLens());
+    this(appView, appView.graphLens(), appView.codeLens());
   }
 
   public LensCodeRewriterUtils(AppView<?> appView, boolean enableCallSiteCaching) {
     this.definitions = appView;
     this.graphLens = appView.graphLens();
+    this.codeLens = appView.codeLens();
     this.rewrittenCallSiteCache = enableCallSiteCaching ? new ConcurrentHashMap<>() : null;
   }
 
-  public LensCodeRewriterUtils(DexDefinitionSupplier definitions, GraphLens graphLens) {
+  public LensCodeRewriterUtils(
+      DexDefinitionSupplier definitions, GraphLens graphLens, GraphLens codeLens) {
     this.definitions = definitions;
     this.graphLens = graphLens;
+    this.codeLens = codeLens;
     this.rewrittenCallSiteCache = null;
   }
 
@@ -108,7 +112,7 @@
         LambdaDescriptor.getMainFunctionalInterfaceMethodReference(
             callSite, definitions.dexItemFactory());
     return graphLens
-        .lookupMethod(method, context.getReference(), Invoke.Type.INTERFACE)
+        .lookupMethod(method, context.getReference(), Invoke.Type.INTERFACE, codeLens)
         .getReference()
         .getName();
   }
@@ -119,7 +123,8 @@
       DexMethod invokedMethod = methodHandle.asMethod();
       MethodHandleType oldType = methodHandle.type;
       MethodLookupResult lensLookup =
-          graphLens.lookupMethod(invokedMethod, context.getReference(), oldType.toInvokeType());
+          graphLens.lookupMethod(
+              invokedMethod, context.getReference(), oldType.toInvokeType(), codeLens);
       DexMethod rewrittenTarget = lensLookup.getReference();
       DexMethod actualTarget;
       MethodHandleType newType;
@@ -139,7 +144,7 @@
             definitions
                 .dexItemFactory()
                 .createMethod(
-                    graphLens.lookupType(invokedMethod.holder),
+                    graphLens.lookupType(invokedMethod.holder, codeLens),
                     rewrittenTarget.proto,
                     rewrittenTarget.name);
         newType = oldType;
@@ -166,7 +171,7 @@
       }
     } else {
       DexField field = methodHandle.asField();
-      DexField actualField = graphLens.lookupField(field);
+      DexField actualField = graphLens.lookupField(field, codeLens);
       if (actualField != field) {
         return definitions
             .dexItemFactory()
@@ -211,7 +216,7 @@
         return rewriteDexMethodType(value.asDexValueMethodType());
       case TYPE:
         DexType oldType = value.asDexValueType().value;
-        DexType newType = graphLens.lookupType(oldType);
+        DexType newType = graphLens.lookupType(oldType, codeLens);
         return newType != oldType ? new DexValueType(newType) : value;
       default:
         return value;
@@ -221,7 +226,8 @@
   public DexProto rewriteProto(DexProto proto) {
     return definitions
         .dexItemFactory()
-        .applyClassMappingToProto(proto, graphLens::lookupType, protoFixupCache);
+        .applyClassMappingToProto(
+            proto, type -> graphLens.lookupType(type, codeLens), protoFixupCache);
   }
 
   private DexValueMethodHandle rewriteDexValueMethodHandle(
@@ -231,7 +237,7 @@
     return newHandle != oldHandle ? new DexValueMethodHandle(newHandle) : methodHandle;
   }
 
-  public boolean hasGraphLens(GraphLens graphLens) {
-    return this.graphLens == graphLens;
+  public boolean hasGraphLens(GraphLens graphLens, GraphLens codeLens) {
+    return this.graphLens == graphLens && this.codeLens == codeLens;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index ee7f8e1..49ad698 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -100,8 +100,7 @@
   public static List<DexMethod> generateListOfBackportedMethods(
       AndroidApp androidApp, InternalOptions options, ExecutorService executor) throws IOException {
     List<DexMethod> methods = new ArrayList<>();
-    PrefixRewritingMapper rewritePrefix =
-        options.desugaredLibrarySpecification.getPrefixRewritingMapper();
+    PrefixRewritingMapper rewritePrefix = options.getPrefixRewritingMapper();
     AppInfo appInfo = null;
     if (androidApp != null) {
       DexApplication app =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
index eb4f662..bf96b67 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
@@ -11,9 +11,10 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Map;
 import java.util.Set;
@@ -30,6 +31,8 @@
 
   public abstract DexType rewrittenType(DexType type, AppView<?> appView);
 
+  public abstract DexType rewrittenContextType(DexType type, AppView<?> appView);
+
   public boolean hasRewrittenType(DexType type, AppView<?> appView) {
     return rewrittenType(type, appView) != null;
   }
@@ -48,8 +51,6 @@
 
   public abstract boolean isRewriting();
 
-  public abstract boolean shouldRewriteTypeName(String typeName);
-
   public abstract void forAllRewrittenTypes(Consumer<DexType> consumer);
 
   public static class DesugarPrefixRewritingMapper extends PrefixRewritingMapper {
@@ -57,7 +58,6 @@
     private final Set<DexType> notRewritten = Sets.newConcurrentHashSet();
     private final Map<DexType, DexType> rewritten = new ConcurrentHashMap<>();
     private final Map<DexString, DexString> initialPrefixes;
-    private final Set<String> initialPrefixStrings;
     private final DexItemFactory factory;
     private final boolean l8Compilation;
 
@@ -67,13 +67,10 @@
       this.factory = itemFactory;
       this.l8Compilation = libraryCompilation;
       ImmutableMap.Builder<DexString, DexString> builder = ImmutableMap.builder();
-      ImmutableSet.Builder<String> prefixStringBuilder = ImmutableSet.builder();
       for (String key : prefixes.keySet()) {
-        prefixStringBuilder.add(key);
         builder.put(toDescriptorPrefix(key), toDescriptorPrefix(prefixes.get(key)));
       }
       this.initialPrefixes = builder.build();
-      this.initialPrefixStrings = prefixStringBuilder.build();
       validatePrefixes(prefixes);
     }
 
@@ -129,6 +126,36 @@
       return computePrefix(type, appView);
     }
 
+    @Override
+    public DexType rewrittenContextType(DexType type, AppView<?> appView) {
+      DexType rewritten = rewrittenType(type, appView);
+      if (rewritten != null) {
+        return rewritten;
+      }
+      LegacyDesugaredLibrarySpecification desugaredLibrarySpecification =
+          appView.options().desugaredLibrarySpecification;
+      if (desugaredLibrarySpecification.getEmulateLibraryInterface().containsKey(type)) {
+        return desugaredLibrarySpecification.getEmulateLibraryInterface().get(type);
+      }
+      for (Map<DexType, DexType> value :
+          desugaredLibrarySpecification.getRetargetCoreLibMember().values()) {
+        if (value.containsKey(type)) {
+          // Hack until machine specification are ready.
+          String prefix =
+              DescriptorUtils.getJavaTypeFromBinaryName(
+                  desugaredLibrarySpecification.getSynthesizedLibraryClassesPackagePrefix());
+          String interfaceType = type.toString();
+          int firstPackage = interfaceType.indexOf('.');
+          return appView
+              .dexItemFactory()
+              .createType(
+                  DescriptorUtils.javaTypeToDescriptor(
+                      prefix + interfaceType.substring(firstPackage + 1)));
+        }
+      }
+      return null;
+    }
+
     // Besides L8 compilation, program types should not be rewritten.
     private void failIfRewritingProgramType(DexType type, AppView<?> appView) {
       if (l8Compilation) {
@@ -192,16 +219,54 @@
     public boolean isRewriting() {
       return true;
     }
+  }
+
+  public static class MachineDesugarPrefixRewritingMapper extends PrefixRewritingMapper {
+
+    private final PrefixRewritingMapper mapper;
+    private final Map<DexType, DexType> rewriteType;
+    private final Map<DexType, DexType> rewriteDerivedTypeOnly;
+
+    public MachineDesugarPrefixRewritingMapper(
+        PrefixRewritingMapper mapper, MachineRewritingFlags flags) {
+      this.mapper = mapper;
+      this.rewriteType = new ConcurrentHashMap<>(flags.getRewriteType());
+      rewriteDerivedTypeOnly = flags.getRewriteDerivedTypeOnly();
+    }
 
     @Override
-    public boolean shouldRewriteTypeName(String typeName) {
-      // TODO(b/154800164): We could use tries instead of looking-up everywhere.
-      for (DexString prefix : initialPrefixes.keySet()) {
-        if (typeName.startsWith(prefix.toString())) {
-          return true;
-        }
+    public DexType rewrittenType(DexType type, AppView<?> appView) {
+      assert mapper.rewrittenType(type, appView) == rewriteType.get(type);
+      return rewriteType.get(type);
+    }
+
+    @Override
+    public DexType rewrittenContextType(DexType context, AppView<?> appView) {
+      if (rewriteType.containsKey(context)) {
+        return rewriteType.get(context);
       }
-      return false;
+      return rewriteDerivedTypeOnly.get(context);
+    }
+
+    @Override
+    public void rewriteType(DexType type, DexType rewrittenType) {
+      mapper.rewriteType(type, rewrittenType);
+      rewriteType.compute(
+          type,
+          (t, val) -> {
+            assert val == null || val == rewrittenType;
+            return rewrittenType;
+          });
+    }
+
+    @Override
+    public boolean isRewriting() {
+      return true;
+    }
+
+    @Override
+    public void forAllRewrittenTypes(Consumer<DexType> consumer) {
+      rewriteType.keySet().forEach(consumer);
     }
   }
 
@@ -213,6 +278,11 @@
     }
 
     @Override
+    public DexType rewrittenContextType(DexType type, AppView<?> appView) {
+      return null;
+    }
+
+    @Override
     public void rewriteType(DexType type, DexType rewrittenType) {}
 
     @Override
@@ -221,11 +291,6 @@
     }
 
     @Override
-    public boolean shouldRewriteTypeName(String typeName) {
-      return false;
-    }
-
-    @Override
     public void forAllRewrittenTypes(Consumer<DexType> consumer) {}
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
index 0795a4e..150e6c4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
@@ -201,8 +201,7 @@
                 clazz.isInterface(),
                 eventConsumer)
             .generateCfCode();
-    DexEncodedMethod newMethod =
-        wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
+    DexEncodedMethod newMethod = wrapperSynthesizor.newSynthesizedMethod(methodToInstall, cfCode);
     newMethod.setCode(cfCode, appView);
     if (originalMethod.isLibraryMethodOverride().isTrue()) {
       newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
index 558faf7..987dc8d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
@@ -235,6 +235,7 @@
             .dexItemFactory()
             .createSynthesizedType(
                 DescriptorUtils.javaTypeToDescriptor(VIVIFIED_PREFIX + type.toString()));
+    // We would need to ensure a classpath class for each type to remove this rewriteType call.
     appView.rewritePrefix.rewriteType(vivifiedType, type);
     return vivifiedType;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index 55d61b9..4a8cabe 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -29,7 +29,6 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterConstructorCfCodeProvider;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterThrowRuntimeExceptionCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterVivifiedWrapperCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperConversionCfCodeProvider;
@@ -41,14 +40,11 @@
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 
@@ -387,22 +383,8 @@
                         wrapperField, reverseWrapperField, context)));
   }
 
-  private boolean isInvalidWrapper(DexClass clazz) {
-    return Iterables.any(allImplementedMethods(clazz), DexEncodedMethod::isFinal);
-  }
-
   private CfCode computeProgramConversionMethodCode(
       DexField wrapperField, DexField reverseWrapperField, DexClass context) {
-    if (isInvalidWrapper(context)) {
-      return new APIConverterThrowRuntimeExceptionCfCodeProvider(
-              appView,
-              factory.createString(
-                  "Unsupported conversion for "
-                      + context.type
-                      + ". See compilation time warnings for more details."),
-              wrapperField.holder)
-          .generateCfCode();
-    }
     assert context.isProgramClass();
     return new APIConverterWrapperConversionCfCodeProvider(
             appView, reverseWrapperField, wrapperField)
@@ -459,7 +441,7 @@
 
   private Collection<DexEncodedMethod> synthesizeVirtualMethodsForVivifiedTypeWrapper(
       DexClass dexClass, DexEncodedField wrapperField) {
-    List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
+    Iterable<DexMethod> allImplementedMethods = allImplementedMethods(dexClass);
     List<DexEncodedMethod> generatedMethods = new ArrayList<>();
     // Each method should use only types in their signature, but each method the wrapper forwards
     // to should used only vivified types.
@@ -470,30 +452,23 @@
     //   v2 <- convertTypeToVivifiedType(v0);
     //   v3 <- wrappedValue.foo(v2,v1);
     //   return v3;
-    Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
-    for (DexEncodedMethod dexEncodedMethod : dexMethods) {
-      DexClass holderClass = appView.definitionFor(dexEncodedMethod.getHolderType());
+    for (DexMethod method : allImplementedMethods) {
+      DexClass holderClass = appView.definitionFor(method.getHolderType());
       boolean isInterface;
       if (holderClass == null) {
         assert appView
             .options()
             .desugaredLibrarySpecification
             .getEmulateLibraryInterface()
-            .containsValue(dexEncodedMethod.getHolderType());
+            .containsValue(method.getHolderType());
         isInterface = true;
       } else {
         isInterface = holderClass.isInterface();
       }
       DexMethod methodToInstall =
-          factory.createMethod(
-              wrapperField.getHolderType(),
-              dexEncodedMethod.getReference().proto,
-              dexEncodedMethod.getReference().name);
+          factory.createMethod(wrapperField.getHolderType(), method.proto, method.name);
       CfCode cfCode;
-      if (dexEncodedMethod.isFinal()) {
-        finalMethods.add(dexEncodedMethod.getReference());
-        continue;
-      } else if (dexClass.isProgramClass()) {
+      if (dexClass.isProgramClass()) {
         cfCode =
             new APIConverterVivifiedWrapperCfCodeProvider(
                     appView, methodToInstall, wrapperField.getReference(), this, isInterface)
@@ -501,16 +476,15 @@
       } else {
         cfCode = null;
       }
-      DexEncodedMethod newDexEncodedMethod =
-          newSynthesizedMethod(methodToInstall, dexEncodedMethod, cfCode);
+      DexEncodedMethod newDexEncodedMethod = newSynthesizedMethod(methodToInstall, cfCode);
       generatedMethods.add(newDexEncodedMethod);
     }
-    return finalizeWrapperMethods(generatedMethods, finalMethods);
+    return generatedMethods;
   }
 
   private Collection<DexEncodedMethod> synthesizeVirtualMethodsForTypeWrapper(
       DexClass dexClass, DexEncodedField wrapperField) {
-    List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
+    Iterable<DexMethod> dexMethods = allImplementedMethods(dexClass);
     List<DexEncodedMethod> generatedMethods = new ArrayList<>();
     // Each method should use only vivified types in their signature, but each method the wrapper
     // forwards
@@ -522,87 +496,64 @@
     //   v2 <- convertVivifiedTypeToType(v0);
     //   v3 <- wrappedValue.foo(v2,v1);
     //   return v3;
-    Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
-    for (DexEncodedMethod dexEncodedMethod : dexMethods) {
-      DexClass holderClass = appView.definitionFor(dexEncodedMethod.getHolderType());
+    for (DexMethod method : dexMethods) {
+      DexClass holderClass = appView.definitionFor(method.getHolderType());
       assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
       boolean isInterface = holderClass == null || holderClass.isInterface();
       DexMethod methodToInstall =
           DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
-              dexEncodedMethod.getReference(), wrapperField.getHolderType(), appView);
+              method, wrapperField.getHolderType(), appView);
       CfCode cfCode;
-      if (dexEncodedMethod.isFinal()) {
-        finalMethods.add(dexEncodedMethod.getReference());
-        continue;
-      } else if (dexClass.isProgramClass()) {
+      if (dexClass.isProgramClass()) {
         cfCode =
             new APIConverterWrapperCfCodeProvider(
-                    appView,
-                    dexEncodedMethod.getReference(),
-                    wrapperField.getReference(),
-                    this,
-                    isInterface)
+                    appView, method, wrapperField.getReference(), this, isInterface)
                 .generateCfCode();
       } else {
         cfCode = null;
       }
-      DexEncodedMethod newDexEncodedMethod =
-          newSynthesizedMethod(methodToInstall, dexEncodedMethod, cfCode);
+      DexEncodedMethod newDexEncodedMethod = newSynthesizedMethod(methodToInstall, cfCode);
       generatedMethods.add(newDexEncodedMethod);
     }
-    return finalizeWrapperMethods(generatedMethods, finalMethods);
+    return generatedMethods;
   }
 
-  private Collection<DexEncodedMethod> finalizeWrapperMethods(
-      List<DexEncodedMethod> generatedMethods, Set<DexMethod> finalMethods) {
-    if (finalMethods.isEmpty()) {
-      return generatedMethods;
-    }
-    // Wrapper is invalid, no need to add the virtual methods.
-    reportFinalMethodsInWrapper(finalMethods);
-    return Collections.emptyList();
-  }
-
-  private void reportFinalMethodsInWrapper(Set<DexMethod> methods) {
-    String[] methodArray =
-        methods.stream().map(method -> method.holder + "#" + method.name).toArray(String[]::new);
-    appView
-        .options()
-        .reporter
-        .warning(
-            new StringDiagnostic(
-                "Desugared library API conversion: cannot wrap final methods "
-                    + Arrays.toString(methodArray)
-                    + ". "
-                    + methods.iterator().next().holder
-                    + " is marked as invalid and will throw a runtime exception upon conversion."));
-  }
-
-  DexEncodedMethod newSynthesizedMethod(
-      DexMethod methodToInstall, DexEncodedMethod template, Code code) {
-    MethodAccessFlags newFlags = template.accessFlags.copy();
-    assert newFlags.isPublic();
-    // It can happen that we wrap an abstract method, in which case the wrapping method is no
-    // longer abstract.
-    if (code != null) {
-      newFlags.unsetAbstract();
-    }
-    // TODO(b/146114533): Fix inlining in synthetic methods and remove unsetBridge.
-    newFlags.unsetBridge();
-    newFlags.setSynthetic();
+  DexEncodedMethod newSynthesizedMethod(DexMethod methodToInstall, Code code) {
+    MethodAccessFlags newFlags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false);
+    ComputedApiLevel apiLevelForDefinition =
+        appView.enableWholeProgramOptimizations()
+            ? ComputedApiLevel.notSet()
+            : appView
+                .apiLevelCompute()
+                .computeApiLevelForDefinition(methodToInstall, factory, ComputedApiLevel.unknown());
+    // Since the method is a forwarding method, the api level for code is the same as the
+    // definition.
+    ComputedApiLevel apiLevelForCode = apiLevelForDefinition;
     return DexEncodedMethod.syntheticBuilder()
         .setMethod(methodToInstall)
         .setAccessFlags(newFlags)
         .setCode(code)
-        .setApiLevelForDefinition(template.getApiLevelForDefinition())
-        .setApiLevelForCode(
-            code == null ? ComputedApiLevel.notSet() : template.getApiLevelForCode())
+        .setApiLevelForDefinition(apiLevelForDefinition)
+        .setApiLevelForCode(code == null ? ComputedApiLevel.notSet() : apiLevelForCode)
         .build();
   }
 
-  private List<DexEncodedMethod> allImplementedMethods(DexClass clazz) {
-    return allImplementedMethodsCache.computeIfAbsent(
-        clazz.type, type -> internalAllImplementedMethods(clazz));
+  private Iterable<DexMethod> allImplementedMethods(DexClass clazz) {
+    if (appView.options().testing.machineDesugaredLibrarySpecification != null) {
+      return appView
+          .options()
+          .testing
+          .machineDesugaredLibrarySpecification
+          .getRewritingFlags()
+          .getWrappers()
+          .get(clazz.type);
+    }
+    List<DexEncodedMethod> dexEncodedMethods =
+        allImplementedMethodsCache.computeIfAbsent(
+            clazz.type, type -> internalAllImplementedMethods(clazz));
+    return Iterables.transform(dexEncodedMethods, m -> m.getReference());
   }
 
   private List<DexEncodedMethod> internalAllImplementedMethods(DexClass libraryClass) {
@@ -641,6 +592,7 @@
         workList.add(superClass);
       }
     }
+    assert !Iterables.any(implementedMethods, DexEncodedMethod::isFinal);
     return implementedMethods;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
index c5154cd..6eb2424 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
@@ -37,7 +37,6 @@
    * method is the method on the companion class.
    */
   private final DerivedMethod interfaceMethod;
-
   private final DerivedMethod emulatedDispatchMethod;
   private final DerivedMethod forwardingMethod;
   private final LinkedHashMap<DexType, DerivedMethod> dispatchCases;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
new file mode 100644
index 0000000..0f9a520
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import java.util.Map;
+
+public class EmulatedInterfaceDescriptor {
+  private final DexType rewrittenType;
+  private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedMethods;
+
+  public EmulatedInterfaceDescriptor(
+      DexType rewrittenType, Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedMethods) {
+    this.rewrittenType = rewrittenType;
+    this.emulatedMethods = emulatedMethods;
+  }
+
+  public DexType getRewrittenType() {
+    return rewrittenType;
+  }
+
+  public Map<DexMethod, EmulatedDispatchMethodDescriptor> getEmulatedMethods() {
+    return emulatedMethods;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
index 2a88f9a..501482e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -4,15 +4,18 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification;
 
-/** TODO(b/184026720): Move the rest of the flags. */
 public class MachineDesugaredLibrarySpecification {
 
   private final boolean libraryCompilation;
+  private final MachineTopLevelFlags topLevelFlags;
   private final MachineRewritingFlags rewritingFlags;
 
   public MachineDesugaredLibrarySpecification(
-      boolean libraryCompilation, MachineRewritingFlags rewritingFlags) {
+      boolean libraryCompilation,
+      MachineTopLevelFlags topLevelFlags,
+      MachineRewritingFlags rewritingFlags) {
     this.libraryCompilation = libraryCompilation;
+    this.topLevelFlags = topLevelFlags;
     this.rewritingFlags = rewritingFlags;
   }
 
@@ -20,6 +23,10 @@
     return libraryCompilation;
   }
 
+  public MachineTopLevelFlags getTopLevelFlags() {
+    return topLevelFlags;
+  }
+
   public MachineRewritingFlags getRewritingFlags() {
     return rewritingFlags;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index f73e1c9..a6c487e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -5,8 +5,16 @@
 package com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification;
 
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 public class MachineRewritingFlags {
 
@@ -15,14 +23,33 @@
   }
 
   MachineRewritingFlags(
+      Map<DexType, DexType> rewriteType,
+      Map<DexType, DexType> rewriteDerivedTypeOnly,
       Map<DexMethod, DexMethod> staticRetarget,
       Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget,
-      Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget) {
+      Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget,
+      Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces,
+      Map<DexType, List<DexMethod>> wrappers,
+      Map<DexType, DexType> legacyBackport,
+      Set<DexType> dontRetarget,
+      Map<DexType, Pair<DexType, DexString>> customConversions) {
+    this.rewriteType = rewriteType;
+    this.rewriteDerivedTypeOnly = rewriteDerivedTypeOnly;
     this.staticRetarget = staticRetarget;
     this.nonEmulatedVirtualRetarget = nonEmulatedVirtualRetarget;
     this.emulatedVirtualRetarget = emulatedVirtualRetarget;
+    this.emulatedInterfaces = emulatedInterfaces;
+    this.wrappers = wrappers;
+    this.legacyBackport = legacyBackport;
+    this.dontRetarget = dontRetarget;
+    this.customConversions = customConversions;
   }
 
+  // Rewrites all the references to the keys as well as synthetic types derived from any key.
+  private final Map<DexType, DexType> rewriteType;
+  // Rewrites only synthetic types derived from any key.
+  private final Map<DexType, DexType> rewriteDerivedTypeOnly;
+
   // Static methods to retarget, duplicated to library boundaries.
   private final Map<DexMethod, DexMethod> staticRetarget;
 
@@ -37,6 +64,24 @@
   // Virtual methods to retarget through emulated dispatch.
   private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget;
 
+  // Emulated interface descriptors.
+  private final Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces;
+
+  // Wrappers and the list of methods they implement.
+  private final Map<DexType, List<DexMethod>> wrappers;
+
+  private final Map<DexType, DexType> legacyBackport;
+  private final Set<DexType> dontRetarget;
+  private final Map<DexType, Pair<DexType, DexString>> customConversions;
+
+  public Map<DexType, DexType> getRewriteType() {
+    return rewriteType;
+  }
+
+  public Map<DexType, DexType> getRewriteDerivedTypeOnly() {
+    return rewriteDerivedTypeOnly;
+  }
+
   public Map<DexMethod, DexMethod> getStaticRetarget() {
     return staticRetarget;
   }
@@ -49,16 +94,55 @@
     return emulatedVirtualRetarget;
   }
 
+  public Map<DexType, EmulatedInterfaceDescriptor> getEmulatedInterfaces() {
+    return emulatedInterfaces;
+  }
+
+  public Map<DexType, List<DexMethod>> getWrappers() {
+    return wrappers;
+  }
+
+  public Map<DexType, DexType> getLegacyBackport() {
+    return legacyBackport;
+  }
+
+  public Set<DexType> getDontRetarget() {
+    return dontRetarget;
+  }
+
+  public Map<DexType, Pair<DexType, DexString>> getCustomConversions() {
+    return customConversions;
+  }
+
   public static class Builder {
 
     Builder() {}
 
+    private final Map<DexType, DexType> rewriteType = new IdentityHashMap<>();
+    private final Map<DexType, DexType> rewriteDerivedTypeOnly = new IdentityHashMap<>();
     private final ImmutableMap.Builder<DexMethod, DexMethod> staticRetarget =
         ImmutableMap.builder();
     private final ImmutableMap.Builder<DexMethod, DexMethod> nonEmulatedVirtualRetarget =
         ImmutableMap.builder();
     private final ImmutableMap.Builder<DexMethod, EmulatedDispatchMethodDescriptor>
         emulatedVirtualRetarget = ImmutableMap.builder();
+    private final ImmutableMap.Builder<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
+        ImmutableMap.builder();
+    private final ImmutableMap.Builder<DexType, List<DexMethod>> wrappers = ImmutableMap.builder();
+    private final ImmutableMap.Builder<DexType, DexType> legacyBackport = ImmutableMap.builder();
+    private final ImmutableSet.Builder<DexType> dontRetarget = ImmutableSet.builder();
+    private final ImmutableMap.Builder<DexType, Pair<DexType, DexString>> customConversions =
+        ImmutableMap.builder();
+
+    public void rewriteType(DexType src, DexType target) {
+      assert src != null;
+      assert target != null;
+      rewriteType.put(src, target);
+    }
+
+    public void rewriteDerivedTypeOnly(DexType src, DexType target) {
+      rewriteDerivedTypeOnly.put(src, target);
+    }
 
     public void putStaticRetarget(DexMethod src, DexMethod dest) {
       staticRetarget.put(src, dest);
@@ -68,15 +152,42 @@
       nonEmulatedVirtualRetarget.put(src, dest);
     }
 
+    public void putEmulatedInterface(DexType src, EmulatedInterfaceDescriptor descriptor) {
+      emulatedInterfaces.put(src, descriptor);
+    }
+
     public void putEmulatedVirtualRetarget(DexMethod src, EmulatedDispatchMethodDescriptor dest) {
       emulatedVirtualRetarget.put(src, dest);
     }
 
+    public void addWrapper(DexType wrapperConversion, List<DexMethod> methods) {
+      wrappers.put(wrapperConversion, ImmutableList.copyOf(methods));
+    }
+
+    public void putLegacyBackport(DexType src, DexType target) {
+      legacyBackport.put(src, target);
+    }
+
+    public void addDontRetarget(DexType type) {
+      dontRetarget.add(type);
+    }
+
+    public void putCustomConversion(DexType src, DexType conversionType, DexString conversionName) {
+      customConversions.put(src, new Pair<>(conversionType, conversionName));
+    }
+
     public MachineRewritingFlags build() {
       return new MachineRewritingFlags(
+          rewriteType,
+          rewriteDerivedTypeOnly,
           staticRetarget.build(),
           nonEmulatedVirtualRetarget.build(),
-          emulatedVirtualRetarget.build());
+          emulatedVirtualRetarget.build(),
+          emulatedInterfaces.build(),
+          wrappers.build(),
+          legacyBackport.build(),
+          dontRetarget.build(),
+          customConversions.build());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineTopLevelFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineTopLevelFlags.java
new file mode 100644
index 0000000..f426219
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineTopLevelFlags.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification;
+
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.List;
+
+public class MachineTopLevelFlags {
+
+  private final AndroidApiLevel requiredCompilationAPILevel;
+  private final String synthesizedLibraryClassesPackagePrefix;
+  private final String identifier;
+  private final String jsonSource;
+  // Setting supportAllCallbacksFromLibrary reduces the number of generated call-backs,
+  // more specifically:
+  // - no call-back is generated for emulated interface method overrides (forEach, etc.)
+  // - no call-back is generated inside the desugared library itself.
+  // Such setting decreases significantly the desugared library dex file, but virtual calls from
+  // within the library to desugared library classes instances as receiver may be incorrect, for
+  // example the method forEach in Iterable may be executed over a concrete implementation.
+  private final boolean supportAllCallbacksFromLibrary;
+  private final List<String> extraKeepRules;
+
+  public MachineTopLevelFlags(
+      AndroidApiLevel requiredCompilationAPILevel,
+      String synthesizedLibraryClassesPackagePrefix,
+      String identifier,
+      String jsonSource,
+      boolean supportAllCallbacksFromLibrary,
+      List<String> extraKeepRules) {
+    this.requiredCompilationAPILevel = requiredCompilationAPILevel;
+    this.synthesizedLibraryClassesPackagePrefix = synthesizedLibraryClassesPackagePrefix;
+    this.identifier = identifier;
+    this.jsonSource = jsonSource;
+    this.supportAllCallbacksFromLibrary = supportAllCallbacksFromLibrary;
+    this.extraKeepRules = extraKeepRules;
+  }
+
+  public AndroidApiLevel getRequiredCompilationAPILevel() {
+    return requiredCompilationAPILevel;
+  }
+
+  public String getSynthesizedLibraryClassesPackagePrefix() {
+    return synthesizedLibraryClassesPackagePrefix;
+  }
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public String getJsonSource() {
+    return jsonSource;
+  }
+
+  public boolean isSupportAllCallbacksFromLibrary() {
+    return supportAllCallbacksFromLibrary;
+  }
+
+  public List<String> getExtraKeepRules() {
+    return extraKeepRules;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java
index 7c5d1c3..a54cd8d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java
@@ -139,13 +139,15 @@
     AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
     MethodResolutionResult resolutionResult =
         appInfo.resolveMethod(invokedMethod, cfInvoke.isInterface());
-    if (!resolutionResult.isSingleResolution()) {
-      return NO_REWRITING;
-    }
-    DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
+    // We are required to use the invokedMethod if it does not resolve due to the rewriting of
+    // private methods absent from the library.
+    DexMethod singleTarget =
+        resolutionResult.isSingleResolution()
+            ? resolutionResult.getSingleTarget().getReference()
+            : invokedMethod;
     assert singleTarget != null;
     if (cfInvoke.isInvokeStatic()) {
-      DexMethod retarget = staticRetarget.get(singleTarget.getReference());
+      DexMethod retarget = staticRetarget.get(singleTarget);
       return retarget == null
           ? NO_REWRITING
           : InvokeRetargetingResult.createInvokeRetargetingResult(retarget);
@@ -163,10 +165,8 @@
     return retarget;
   }
 
-  private InvokeRetargetingResult computeNonStaticRetarget(DexEncodedMethod singleTarget) {
-    assert !singleTarget.isStatic();
-    DexMethod reference = singleTarget.getReference();
-    EmulatedDispatchMethodDescriptor descriptor = emulatedVirtualRetarget.get(reference);
+  private InvokeRetargetingResult computeNonStaticRetarget(DexMethod singleTarget) {
+    EmulatedDispatchMethodDescriptor descriptor = emulatedVirtualRetarget.get(singleTarget);
     if (descriptor != null) {
       return new InvokeRetargetingResult(
           true,
@@ -174,7 +174,7 @@
               syntheticHelper.ensureEmulatedHolderDispatchMethod(descriptor, eventConsumer));
     }
     return InvokeRetargetingResult.createInvokeRetargetingResult(
-        nonEmulatedVirtualRetarget.get(reference));
+        nonEmulatedVirtualRetarget.get(singleTarget));
   }
 
   private InvokeRetargetingResult computeSuperRetarget(DexEncodedMethod singleTarget) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
index 5a5768f..5519d95 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
@@ -18,7 +19,6 @@
 import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider;
 import com.android.tools.r8.synthesis.SyntheticClassBuilder;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
-import com.android.tools.r8.utils.DescriptorUtils;
 import java.util.LinkedHashMap;
 
 public class DesugaredLibraryRetargeterSyntheticHelper {
@@ -37,7 +37,8 @@
 
   private DexMethod emulatedHolderDispatchMethod(DexType holder, DerivedMethod method) {
     assert method.getHolderKind() == SyntheticKind.RETARGET_CLASS;
-    return appView.dexItemFactory().createMethod(holder, method.getProto(), method.getName());
+    DexProto newProto = appView.dexItemFactory().prependHolderToProto(method.getMethod());
+    return appView.dexItemFactory().createMethod(holder, newProto, method.getName());
   }
 
   DexMethod emulatedInterfaceDispatchMethod(DexType holder, DerivedMethod method) {
@@ -83,10 +84,7 @@
                   context,
                   appView,
                   classBuilder -> buildHolderDispatchMethod(classBuilder, itfClass, descriptor),
-                  clazz -> {
-                    eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz);
-                    rewriteType(clazz.type);
-                  });
+                  eventConsumer::acceptDesugaredLibraryRetargeterDispatchClasspathClass);
     }
     DexMethod dispatchMethod =
         emulatedHolderDispatchMethod(syntheticClass.type, emulatedDispatchMethod);
@@ -110,10 +108,7 @@
             holderContext,
             appView,
             classBuilder -> buildHolderDispatchMethod(classBuilder, itfClass, descriptor),
-            clazz -> {
-              eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz);
-              rewriteType(clazz.type);
-            });
+            eventConsumer::acceptDesugaredLibraryRetargeterDispatchProgramClass);
   }
 
   public DexClass ensureEmulatedInterfaceDispatchMethod(
@@ -136,10 +131,7 @@
             context,
             appView,
             classBuilder -> buildInterfaceDispatchMethod(classBuilder, descriptor),
-            clazz -> {
-              eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz);
-              rewriteType(clazz.type);
-            });
+            eventConsumer::acceptDesugaredLibraryRetargeterDispatchClasspathClass);
   }
 
   public DexClass ensureEmulatedInterfaceDispatchMethod(
@@ -156,10 +148,7 @@
             itfContext,
             appView,
             classBuilder -> buildInterfaceDispatchMethod(classBuilder, descriptor),
-            clazz -> {
-              eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz);
-              rewriteType(clazz.type);
-            });
+            eventConsumer::acceptDesugaredLibraryRetargeterDispatchProgramClass);
   }
 
   private void buildInterfaceDispatchMethod(
@@ -214,12 +203,4 @@
             methodSig.getHolderType(), forwardingMethod, itfMethod, new LinkedHashMap<>(), appView)
         .generateCfCode();
   }
-
-  private void rewriteType(DexType type) {
-    String newName =
-        appView.options().desugaredLibrarySpecification.convertJavaNameToDesugaredLibrary(type);
-    DexType newType =
-        appView.dexItemFactory().createType(DescriptorUtils.javaTypeToDescriptor(newName));
-    appView.rewritePrefix.rewriteType(type, newType);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java
index eb38e9e..9e1f5e3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java
@@ -115,12 +115,8 @@
                 DerivedMethod forwardingMethod = new DerivedMethod(forwardingDexMethod);
                 DerivedMethod interfaceMethod =
                     new DerivedMethod(methodReference, SyntheticKind.RETARGET_INTERFACE);
-                DexMethod dispatchDexMethod =
-                    appView
-                        .dexItemFactory()
-                        .createMethod(methodReference.getHolderType(), newProto, methodName);
                 DerivedMethod dispatchMethod =
-                    new DerivedMethod(dispatchDexMethod, SyntheticKind.RETARGET_CLASS);
+                    new DerivedMethod(methodReference, SyntheticKind.RETARGET_CLASS);
                 emulatedVirtualRetarget.put(
                     methodReference,
                     new EmulatedDispatchMethodDescriptor(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java
new file mode 100644
index 0000000..2411ba0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java
@@ -0,0 +1,183 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedInterfaceDescriptor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class HumanToMachineEmulatedInterfaceConverter {
+
+  private final AppInfoWithClassHierarchy appInfo;
+  private Map<DexType, List<DexType>> emulatedInterfaceHierarchy;
+
+  public HumanToMachineEmulatedInterfaceConverter(AppInfoWithClassHierarchy appInfo) {
+    this.appInfo = appInfo;
+  }
+
+  public void convertEmulatedInterfaces(
+      HumanRewritingFlags rewritingFlags,
+      AppInfoWithClassHierarchy appInfo,
+      MachineRewritingFlags.Builder builder) {
+    Map<DexType, DexType> emulateInterfaces = rewritingFlags.getEmulateLibraryInterface();
+    Set<DexMethod> dontRewriteInvocation = rewritingFlags.getDontRewriteInvocation();
+    emulatedInterfaceHierarchy = processEmulatedInterfaceHierarchy(appInfo, emulateInterfaces);
+    for (DexType itf : emulateInterfaces.keySet()) {
+      DexProgramClass itfClass = appInfo.contextIndependentDefinitionFor(itf).asProgramClass();
+      assert itfClass != null;
+      Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedMethods = new IdentityHashMap<>();
+      itfClass.forEachProgramVirtualMethodMatching(
+          m -> m.isDefaultMethod() && !dontRewriteInvocation.contains(m.getReference()),
+          method ->
+              emulatedMethods.put(
+                  method.getReference(),
+                  computeEmulatedDispatchDescriptor(
+                      method.getReference(), rewritingFlags, appInfo)));
+      builder.putEmulatedInterface(
+          itf, new EmulatedInterfaceDescriptor(emulateInterfaces.get(itf), emulatedMethods));
+    }
+  }
+
+  private EmulatedDispatchMethodDescriptor computeEmulatedDispatchDescriptor(
+      DexMethod method, HumanRewritingFlags rewritingFlags, AppInfoWithClassHierarchy appInfo) {
+    DerivedMethod forwardingMethod = new DerivedMethod(method, SyntheticKind.COMPANION_CLASS);
+    DexMethod itfDexMethod =
+        appInfo
+            .dexItemFactory()
+            .createMethod(
+                rewritingFlags.getEmulateLibraryInterface().get(method.getHolderType()),
+                method.getProto(),
+                method.getName());
+    DerivedMethod interfaceMethod = new DerivedMethod(itfDexMethod);
+    DerivedMethod dispatchMethod =
+        new DerivedMethod(method, SyntheticKind.EMULATED_INTERFACE_CLASS);
+    LinkedHashMap<DexType, DerivedMethod> dispatchCases = getDispatchCases(rewritingFlags, method);
+    return new EmulatedDispatchMethodDescriptor(
+        interfaceMethod, dispatchMethod, forwardingMethod, dispatchCases);
+  }
+
+  private LinkedHashMap<DexType, DerivedMethod> getDispatchCases(
+      HumanRewritingFlags rewritingFlags, DexMethod method) {
+    // To properly emulate the library interface call, we need to compute the interfaces
+    // inheriting from the interface and manually implement the dispatch with instance of.
+    // The list guarantees that an interface is always after interfaces it extends,
+    // hence reverse iteration.
+    List<DexType> subInterfaces = emulatedInterfaceHierarchy.get(method.getHolderType());
+    LinkedHashMap<DexType, DerivedMethod> extraDispatchCases = new LinkedHashMap<>();
+    // Retarget core lib emulated dispatch handled as part of emulated interface dispatch.
+    Map<DexMethod, DexType> retargetCoreLibMember = rewritingFlags.getRetargetCoreLibMember();
+    for (DexMethod retarget : retargetCoreLibMember.keySet()) {
+      if (retarget.match(method)) {
+        DexClass inClass = appInfo.definitionFor(retarget.getHolderType());
+        if (inClass != null && implementsInterface(inClass, method.getHolderType())) {
+          DexProto newProto = appInfo.dexItemFactory().prependHolderToProto(retarget);
+          DexMethod forwardingDexMethod =
+              appInfo
+                  .dexItemFactory()
+                  .createMethod(retargetCoreLibMember.get(retarget), newProto, retarget.getName());
+          extraDispatchCases.put(retarget.getHolderType(), new DerivedMethod(forwardingDexMethod));
+        }
+      }
+    }
+    if (subInterfaces != null) {
+      for (int i = subInterfaces.size() - 1; i >= 0; i--) {
+        DexClass subInterfaceClass = appInfo.definitionFor(subInterfaces.get(i));
+        assert subInterfaceClass != null;
+        assert subInterfaceClass.isProgramClass();
+        // Else computation of subInterface would have failed.
+        // if the method is implemented, extra dispatch is required.
+        DexEncodedMethod result = subInterfaceClass.lookupVirtualMethod(method);
+        if (result != null && !result.isAbstract()) {
+          assert result.isDefaultMethod();
+          DexMethod reference = result.getReference();
+          extraDispatchCases.put(
+              subInterfaceClass.type, new DerivedMethod(reference, SyntheticKind.COMPANION_CLASS));
+        }
+      }
+    } else {
+      assert extraDispatchCases.size() <= 1;
+    }
+    return extraDispatchCases;
+  }
+
+  private boolean implementsInterface(DexClass clazz, DexType interfaceType) {
+    WorkList<DexType> workList =
+        WorkList.newIdentityWorkList(Arrays.asList(clazz.interfaces.values));
+    while (!workList.isEmpty()) {
+      DexType next = workList.next();
+      if (interfaceType == next) {
+        return true;
+      }
+      DexClass nextClass = appInfo.definitionFor(next);
+      if (nextClass != null) {
+        workList.addIfNotSeen(nextClass.interfaces.values);
+      }
+    }
+    return false;
+  }
+
+  private Map<DexType, List<DexType>> processEmulatedInterfaceHierarchy(
+      AppInfoWithClassHierarchy appInfo, Map<DexType, DexType> emulateInterfaces) {
+    Map<DexType, List<DexType>> emulatedInterfacesHierarchy = new IdentityHashMap<>();
+    Set<DexType> processed = Sets.newIdentityHashSet();
+    ArrayList<DexType> emulatedInterfacesSorted = new ArrayList<>(emulateInterfaces.keySet());
+    emulatedInterfacesSorted.sort(DexType::compareTo);
+    for (DexType interfaceType : emulatedInterfacesSorted) {
+      processEmulatedInterfaceHierarchy(
+          appInfo, emulateInterfaces, interfaceType, processed, emulatedInterfacesHierarchy);
+    }
+    return emulatedInterfacesHierarchy;
+  }
+
+  private void processEmulatedInterfaceHierarchy(
+      AppInfoWithClassHierarchy appInfo,
+      Map<DexType, DexType> emulateInterfaces,
+      DexType interfaceType,
+      Set<DexType> processed,
+      Map<DexType, List<DexType>> emulatedInterfacesHierarchy) {
+    if (processed.contains(interfaceType)) {
+      return;
+    }
+    emulatedInterfacesHierarchy.put(interfaceType, new ArrayList<>());
+    processed.add(interfaceType);
+    DexClass theInterface = appInfo.definitionFor(interfaceType);
+    if (theInterface == null) {
+      return;
+    }
+    WorkList<DexType> workList =
+        WorkList.newIdentityWorkList(Arrays.asList(theInterface.interfaces.values));
+    while (!workList.isEmpty()) {
+      DexType next = workList.next();
+      if (emulateInterfaces.containsKey(next)) {
+        processEmulatedInterfaceHierarchy(
+            appInfo, emulateInterfaces, next, processed, emulatedInterfacesHierarchy);
+        emulatedInterfacesHierarchy.get(next).add(interfaceType);
+        DexClass nextClass = appInfo.definitionFor(next);
+        if (nextClass != null) {
+          workList.addIfNotSeen(nextClass.interfaces.values);
+        }
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
new file mode 100644
index 0000000..6ff7428
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
+public class HumanToMachinePrefixConverter {
+
+  private final AppInfoWithClassHierarchy appInfo;
+
+  public HumanToMachinePrefixConverter(AppInfoWithClassHierarchy appInfo) {
+    this.appInfo = appInfo;
+  }
+
+  private DexString toDescriptorPrefix(String prefix) {
+    return appInfo
+        .dexItemFactory()
+        .createString("L" + DescriptorUtils.getBinaryNameFromJavaType(prefix));
+  }
+
+  public void convertPrefixFlags(
+      HumanRewritingFlags rewritingFlags,
+      MachineRewritingFlags.Builder builder,
+      String synthesizedPrefix) {
+    Map<DexString, DexString> descriptorPrefix = convertRewritePrefix(rewritingFlags);
+    rewriteClasses(descriptorPrefix, builder);
+    rewriteValues(descriptorPrefix, builder, rewritingFlags.getRetargetCoreLibMember());
+    rewriteValues(descriptorPrefix, builder, rewritingFlags.getCustomConversions());
+    rewriteEmulatedInterface(builder, rewritingFlags.getEmulateLibraryInterface());
+    rewriteRetargetKeys(builder, rewritingFlags.getRetargetCoreLibMember(), synthesizedPrefix);
+  }
+
+  public DexType convertJavaNameToDesugaredLibrary(DexType type, String prefix) {
+    String convertedPrefix = DescriptorUtils.getJavaTypeFromBinaryName(prefix);
+    String interfaceType = type.toString();
+    int firstPackage = interfaceType.indexOf('.');
+    return appInfo
+        .dexItemFactory()
+        .createType(
+            DescriptorUtils.javaTypeToDescriptor(
+                convertedPrefix + interfaceType.substring(firstPackage + 1)));
+  }
+
+  private void rewriteRetargetKeys(
+      MachineRewritingFlags.Builder builder, Map<DexMethod, DexType> retarget, String prefix) {
+    for (DexMethod dexMethod : retarget.keySet()) {
+      DexType type = convertJavaNameToDesugaredLibrary(dexMethod.holder, prefix);
+      builder.rewriteDerivedTypeOnly(dexMethod.holder, type);
+    }
+  }
+
+  private void rewriteEmulatedInterface(
+      MachineRewritingFlags.Builder builder, Map<DexType, DexType> emulateLibraryInterface) {
+    emulateLibraryInterface.forEach(builder::rewriteDerivedTypeOnly);
+  }
+
+  private void rewriteValues(
+      Map<DexString, DexString> descriptorPrefix,
+      MachineRewritingFlags.Builder builder,
+      Map<?, DexType> flags) {
+    for (DexType type : flags.values()) {
+      DexType rewrittenType = rewrittenType(descriptorPrefix, type);
+      if (rewrittenType != null) {
+        builder.rewriteType(type, rewrittenType);
+      }
+    }
+  }
+
+  private void rewriteClasses(
+      Map<DexString, DexString> descriptorPrefix, MachineRewritingFlags.Builder builder) {
+    for (DexProgramClass clazz : appInfo.classes()) {
+      DexType type = clazz.type;
+      DexType rewrittenType = rewrittenType(descriptorPrefix, type);
+      if (rewrittenType != null) {
+        builder.rewriteType(type, rewrittenType);
+      }
+    }
+  }
+
+  private DexType rewrittenType(Map<DexString, DexString> descriptorPrefix, DexType type) {
+    DexString prefixToMatch = type.descriptor.withoutArray(appInfo.dexItemFactory());
+    for (DexString prefix : descriptorPrefix.keySet()) {
+      if (prefixToMatch.startsWith(prefix)) {
+        DexString rewrittenTypeDescriptor =
+            type.descriptor.withNewPrefix(
+                prefix, descriptorPrefix.get(prefix), appInfo.dexItemFactory());
+        return appInfo.dexItemFactory().createType(rewrittenTypeDescriptor);
+      }
+    }
+    return null;
+  }
+
+  private ImmutableMap<DexString, DexString> convertRewritePrefix(
+      HumanRewritingFlags rewritingFlags) {
+    Map<String, String> rewritePrefix = rewritingFlags.getRewritePrefix();
+    ImmutableMap.Builder<DexString, DexString> mapBuilder = ImmutableMap.builder();
+    for (String key : rewritePrefix.keySet()) {
+      mapBuilder.put(toDescriptorPrefix(key), toDescriptorPrefix(rewritePrefix.get(key)));
+    }
+    return mapBuilder.build();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java
new file mode 100644
index 0000000..6dce945
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java
@@ -0,0 +1,168 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.BiConsumer;
+
+public class HumanToMachineRetargetConverter {
+
+  private final AppInfoWithClassHierarchy appInfo;
+  private SubtypingInfo subtypingInfo;
+
+  public HumanToMachineRetargetConverter(AppInfoWithClassHierarchy appInfo) {
+    this.appInfo = appInfo;
+  }
+
+  public void convertRetargetFlags(
+      HumanRewritingFlags rewritingFlags, MachineRewritingFlags.Builder builder) {
+    subtypingInfo = SubtypingInfo.create(appInfo);
+    rewritingFlags
+        .getRetargetCoreLibMember()
+        .forEach(
+            (method, type) ->
+                convertRetargetCoreLibMemberFlag(builder, rewritingFlags, method, type));
+  }
+
+  private void convertRetargetCoreLibMemberFlag(
+      MachineRewritingFlags.Builder builder,
+      HumanRewritingFlags rewritingFlags,
+      DexMethod method,
+      DexType type) {
+    DexClass holder = appInfo.definitionFor(method.holder);
+    DexEncodedMethod foundMethod = holder.lookupMethod(method);
+    assert foundMethod != null;
+    if (foundMethod.isStatic()) {
+      convertStaticRetarget(builder, foundMethod, type);
+      return;
+    }
+    if (holder.isFinal() || foundMethod.isFinal()) {
+      convertNonEmulatedVirtualRetarget(builder, foundMethod, type);
+      return;
+    }
+    convertEmulatedVirtualRetarget(builder, rewritingFlags, foundMethod, type);
+  }
+
+  private void convertEmulatedVirtualRetarget(
+      MachineRewritingFlags.Builder builder,
+      HumanRewritingFlags rewritingFlags,
+      DexEncodedMethod src,
+      DexType type) {
+    if (isEmulatedInterfaceDispatch(src, appInfo, rewritingFlags)) {
+      // Handled by emulated interface dispatch.
+      return;
+    }
+    // TODO(b/184026720): Implement library boundaries.
+    DexProto newProto = appInfo.dexItemFactory().prependHolderToProto(src.getReference());
+    DexMethod forwardingDexMethod =
+        appInfo.dexItemFactory().createMethod(type, newProto, src.getName());
+    DerivedMethod forwardingMethod = new DerivedMethod(forwardingDexMethod);
+    DerivedMethod interfaceMethod =
+        new DerivedMethod(src.getReference(), SyntheticKind.RETARGET_INTERFACE);
+    DerivedMethod dispatchMethod =
+        new DerivedMethod(src.getReference(), SyntheticKind.RETARGET_CLASS);
+    LinkedHashMap<DexType, DerivedMethod> dispatchCases = new LinkedHashMap<>();
+    assert validateNoOverride(src, appInfo, subtypingInfo);
+    builder.putEmulatedVirtualRetarget(
+        src.getReference(),
+        new EmulatedDispatchMethodDescriptor(
+            interfaceMethod, dispatchMethod, forwardingMethod, dispatchCases));
+  }
+
+  private boolean validateNoOverride(
+      DexEncodedMethod src, AppInfoWithClassHierarchy appInfo, SubtypingInfo subtypingInfo) {
+    for (DexType subtype : subtypingInfo.subtypes(src.getHolderType())) {
+      DexClass subclass = appInfo.definitionFor(subtype);
+      MethodResolutionResult resolutionResult =
+          appInfo.resolveMethodOn(subclass, src.getReference());
+      if (resolutionResult.isSuccessfulMemberResolutionResult()
+          && resolutionResult.getResolvedMethod().getReference() != src.getReference()) {
+        assert false; // Unsupported.
+      }
+    }
+    return true;
+  }
+
+  private boolean isEmulatedInterfaceDispatch(
+      DexEncodedMethod method,
+      AppInfoWithClassHierarchy appInfo,
+      HumanRewritingFlags humanRewritingFlags) {
+    // Answers true if this method is already managed through emulated interface dispatch.
+    Map<DexType, DexType> emulateLibraryInterface =
+        humanRewritingFlags.getEmulateLibraryInterface();
+    if (emulateLibraryInterface.isEmpty()) {
+      return false;
+    }
+    DexMethod methodToFind = method.getReference();
+    // Look-up all superclass and interfaces, if an emulated interface is found,
+    // and it implements the method, answers true.
+    DexClass dexClass = appInfo.definitionFor(method.getHolderType());
+    // Cannot retarget a method on a virtual method on an emulated interface.
+    assert !emulateLibraryInterface.containsKey(dexClass.getType());
+    return appInfo
+        .traverseSuperTypes(
+            dexClass,
+            (supertype, subclass, isSupertypeAnInterface) ->
+                TraversalContinuation.breakIf(
+                    subclass.isInterface()
+                        && emulateLibraryInterface.containsKey(subclass.getType())
+                        && subclass.lookupMethod(methodToFind) != null))
+        .shouldBreak();
+  }
+
+  private void convertNonEmulatedRetarget(
+      DexEncodedMethod foundMethod,
+      DexType type,
+      AppInfoWithClassHierarchy appInfo,
+      SubtypingInfo subtypingInfo,
+      BiConsumer<DexMethod, DexMethod> consumer) {
+    DexMethod src = foundMethod.getReference();
+    DexMethod dest = src.withHolder(type, appInfo.dexItemFactory());
+    consumer.accept(src, dest);
+    for (DexType subtype : subtypingInfo.subtypes(foundMethod.getHolderType())) {
+      DexClass subclass = appInfo.definitionFor(subtype);
+      MethodResolutionResult resolutionResult = appInfo.resolveMethodOn(subclass, src);
+      if (resolutionResult.isSuccessfulMemberResolutionResult()
+          && resolutionResult.getResolvedMethod().getReference() == src) {
+        consumer.accept(src.withHolder(subtype, appInfo.dexItemFactory()), dest);
+      }
+    }
+  }
+
+  private void convertNonEmulatedVirtualRetarget(
+      MachineRewritingFlags.Builder builder, DexEncodedMethod foundMethod, DexType type) {
+    convertNonEmulatedRetarget(
+        foundMethod,
+        type,
+        appInfo,
+        subtypingInfo,
+        (src, dest) ->
+            builder.putNonEmulatedVirtualRetarget(
+                src,
+                dest.withExtraArgumentPrepended(
+                    foundMethod.getHolderType(), appInfo.dexItemFactory())));
+  }
+
+  private void convertStaticRetarget(
+      MachineRewritingFlags.Builder builder, DexEncodedMethod foundMethod, DexType type) {
+    convertNonEmulatedRetarget(
+        foundMethod, type, appInfo, subtypingInfo, builder::putStaticRetarget);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index d2227cb..bc3a663 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -9,31 +9,20 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.MethodResolutionResult;
-import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineTopLevelFlags;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.TraversalContinuation;
 import java.io.IOException;
 import java.nio.file.Path;
-import java.util.LinkedHashMap;
-import java.util.Map;
 import java.util.concurrent.ExecutorService;
-import java.util.function.BiConsumer;
 
 public class HumanToMachineSpecificationConverter {
 
@@ -43,164 +32,49 @@
     DexApplication app = readApp(androidLib, options);
     AppView<?> appView = AppView.createForD8(AppInfo.createInitialAppInfo(app));
     MachineRewritingFlags machineRewritingFlags =
-        convertRewritingFlags(humanSpec.getRewritingFlags(), appView.appInfoForDesugaring());
+        convertRewritingFlags(
+            humanSpec.getSynthesizedLibraryClassesPackagePrefix(),
+            humanSpec.getRewritingFlags(),
+            appView.appInfoForDesugaring());
+    MachineTopLevelFlags topLevelFlags = convertTopLevelFlags(humanSpec.getTopLevelFlags());
     return new MachineDesugaredLibrarySpecification(
-        humanSpec.isLibraryCompilation(), machineRewritingFlags);
+        humanSpec.isLibraryCompilation(), topLevelFlags, machineRewritingFlags);
+  }
+
+  private MachineTopLevelFlags convertTopLevelFlags(HumanTopLevelFlags topLevelFlags) {
+    return new MachineTopLevelFlags(
+        topLevelFlags.getRequiredCompilationAPILevel(),
+        topLevelFlags.getSynthesizedLibraryClassesPackagePrefix(),
+        topLevelFlags.getIdentifier(),
+        topLevelFlags.getJsonSource(),
+        topLevelFlags.supportAllCallbacksFromLibrary(),
+        topLevelFlags.getExtraKeepRules());
   }
 
   private MachineRewritingFlags convertRewritingFlags(
-      HumanRewritingFlags rewritingFlags, AppInfoWithClassHierarchy appInfo) {
+      String synthesizedPrefix,
+      HumanRewritingFlags rewritingFlags,
+      AppInfoWithClassHierarchy appInfo) {
     MachineRewritingFlags.Builder builder = MachineRewritingFlags.builder();
-    SubtypingInfo subtypingInfo = new SubtypingInfo(appInfo);
+    new HumanToMachineRetargetConverter(appInfo).convertRetargetFlags(rewritingFlags, builder);
+    new HumanToMachineEmulatedInterfaceConverter(appInfo)
+        .convertEmulatedInterfaces(rewritingFlags, appInfo, builder);
+    new HumanToMachinePrefixConverter(appInfo)
+        .convertPrefixFlags(rewritingFlags, builder, synthesizedPrefix);
+    new HumanToMachineWrapperConverter(appInfo).convertWrappers(rewritingFlags, builder);
     rewritingFlags
-        .getRetargetCoreLibMember()
+        .getCustomConversions()
         .forEach(
-            (method, type) ->
-                convertRetargetCoreLibMemberFlag(
-                    builder, rewritingFlags, method, type, appInfo, subtypingInfo));
+            (type, conversionType) ->
+                builder.putCustomConversion(
+                    type, conversionType, appInfo.dexItemFactory().convertMethodName));
+    for (DexType type : rewritingFlags.getDontRetargetLibMember()) {
+      builder.addDontRetarget(type);
+    }
+    rewritingFlags.getBackportCoreLibraryMember().forEach(builder::putLegacyBackport);
     return builder.build();
   }
 
-  private void convertRetargetCoreLibMemberFlag(
-      MachineRewritingFlags.Builder builder,
-      HumanRewritingFlags rewritingFlags,
-      DexMethod method,
-      DexType type,
-      AppInfoWithClassHierarchy appInfo,
-      SubtypingInfo subtypingInfo) {
-    DexClass holder = appInfo.definitionFor(method.holder);
-    DexEncodedMethod foundMethod = holder.lookupMethod(method);
-    assert foundMethod != null;
-    if (foundMethod.isStatic()) {
-      convertStaticRetarget(builder, foundMethod, type, appInfo, subtypingInfo);
-      return;
-    }
-    if (holder.isFinal() || foundMethod.isFinal()) {
-      convertNonEmulatedVirtualRetarget(builder, foundMethod, type, appInfo, subtypingInfo);
-      return;
-    }
-    convertEmulatedVirtualRetarget(
-        builder, rewritingFlags, foundMethod, type, appInfo, subtypingInfo);
-  }
-
-  private void convertEmulatedVirtualRetarget(
-      MachineRewritingFlags.Builder builder,
-      HumanRewritingFlags rewritingFlags,
-      DexEncodedMethod src,
-      DexType type,
-      AppInfoWithClassHierarchy appInfo,
-      SubtypingInfo subtypingInfo) {
-    if (isEmulatedInterfaceDispatch(src, appInfo, rewritingFlags)) {
-      // Handled by emulated interface dispatch.
-      return;
-    }
-    // TODO(b/184026720): Implement library boundaries.
-    DexProto newProto = appInfo.dexItemFactory().prependHolderToProto(src.getReference());
-    DexMethod forwardingDexMethod =
-        appInfo.dexItemFactory().createMethod(type, newProto, src.getName());
-    DerivedMethod forwardingMethod = new DerivedMethod(forwardingDexMethod);
-    DerivedMethod interfaceMethod =
-        new DerivedMethod(src.getReference(), SyntheticKind.RETARGET_INTERFACE);
-    DexMethod dispatchDexMethod =
-        appInfo.dexItemFactory().createMethod(src.getHolderType(), newProto, src.getName());
-    DerivedMethod dispatchMethod =
-        new DerivedMethod(dispatchDexMethod, SyntheticKind.RETARGET_CLASS);
-    LinkedHashMap<DexType, DerivedMethod> dispatchCases = new LinkedHashMap<>();
-    assert validateNoOverride(src, appInfo, subtypingInfo);
-    builder.putEmulatedVirtualRetarget(
-        src.getReference(),
-        new EmulatedDispatchMethodDescriptor(
-            interfaceMethod, dispatchMethod, forwardingMethod, dispatchCases));
-  }
-
-  private boolean validateNoOverride(
-      DexEncodedMethod src, AppInfoWithClassHierarchy appInfo, SubtypingInfo subtypingInfo) {
-    for (DexType subtype : subtypingInfo.subtypes(src.getHolderType())) {
-      DexClass subclass = appInfo.definitionFor(subtype);
-      MethodResolutionResult resolutionResult =
-          appInfo.resolveMethodOn(subclass, src.getReference());
-      if (resolutionResult.isSuccessfulMemberResolutionResult()
-          && resolutionResult.getResolvedMethod().getReference() != src.getReference()) {
-        assert false; // Unsupported.
-      }
-    }
-    return true;
-  }
-
-  private boolean isEmulatedInterfaceDispatch(
-      DexEncodedMethod method,
-      AppInfoWithClassHierarchy appInfo,
-      HumanRewritingFlags humanRewritingFlags) {
-    // Answers true if this method is already managed through emulated interface dispatch.
-    Map<DexType, DexType> emulateLibraryInterface =
-        humanRewritingFlags.getEmulateLibraryInterface();
-    if (emulateLibraryInterface.isEmpty()) {
-      return false;
-    }
-    DexMethod methodToFind = method.getReference();
-    // Look-up all superclass and interfaces, if an emulated interface is found,
-    // and it implements the method, answers true.
-    DexClass dexClass = appInfo.definitionFor(method.getHolderType());
-    // Cannot retarget a method on a virtual method on an emulated interface.
-    assert !emulateLibraryInterface.containsKey(dexClass.getType());
-    return appInfo
-        .traverseSuperTypes(
-            dexClass,
-            (supertype, subclass, isSupertypeAnInterface) ->
-                TraversalContinuation.breakIf(
-                    subclass.isInterface()
-                        && emulateLibraryInterface.containsKey(subclass.getType())
-                        && subclass.lookupMethod(methodToFind) != null))
-        .shouldBreak();
-  }
-
-  private void convertNonEmulatedRetarget(
-      DexEncodedMethod foundMethod,
-      DexType type,
-      AppInfoWithClassHierarchy appInfo,
-      SubtypingInfo subtypingInfo,
-      BiConsumer<DexMethod, DexMethod> consumer) {
-    DexMethod src = foundMethod.getReference();
-    DexMethod dest = src.withHolder(type, appInfo.dexItemFactory());
-    consumer.accept(src, dest);
-    for (DexType subtype : subtypingInfo.subtypes(foundMethod.getHolderType())) {
-      DexClass subclass = appInfo.definitionFor(subtype);
-      MethodResolutionResult resolutionResult = appInfo.resolveMethodOn(subclass, src);
-      if (resolutionResult.isSuccessfulMemberResolutionResult()
-          && resolutionResult.getResolvedMethod().getReference() == src) {
-        consumer.accept(src.withHolder(subtype, appInfo.dexItemFactory()), dest);
-      }
-    }
-  }
-
-  private void convertNonEmulatedVirtualRetarget(
-      MachineRewritingFlags.Builder builder,
-      DexEncodedMethod foundMethod,
-      DexType type,
-      AppInfoWithClassHierarchy appInfo,
-      SubtypingInfo subtypingInfo) {
-    convertNonEmulatedRetarget(
-        foundMethod,
-        type,
-        appInfo,
-        subtypingInfo,
-        (src, dest) ->
-            builder.putNonEmulatedVirtualRetarget(
-                src,
-                dest.withExtraArgumentPrepended(
-                    foundMethod.getHolderType(), appInfo.dexItemFactory())));
-  }
-
-  private void convertStaticRetarget(
-      MachineRewritingFlags.Builder builder,
-      DexEncodedMethod foundMethod,
-      DexType type,
-      AppInfoWithClassHierarchy appInfo,
-      SubtypingInfo subtypingInfo) {
-    convertNonEmulatedRetarget(
-        foundMethod, type, appInfo, subtypingInfo, builder::putStaticRetarget);
-  }
-
   private DexApplication readApp(Path androidLib, InternalOptions options) throws IOException {
     AndroidApp androidApp = AndroidApp.builder().addProgramFile(androidLib).build();
     ApplicationReader applicationReader =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java
new file mode 100644
index 0000000..db87890
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class HumanToMachineWrapperConverter {
+
+  private final AppInfoWithClassHierarchy appInfo;
+
+  public HumanToMachineWrapperConverter(AppInfoWithClassHierarchy appInfo) {
+    this.appInfo = appInfo;
+  }
+
+  public void convertWrappers(
+      HumanRewritingFlags rewritingFlags, MachineRewritingFlags.Builder builder) {
+    for (DexType wrapperConversion : rewritingFlags.getWrapperConversions()) {
+      DexClass wrapperClass = appInfo.definitionFor(wrapperConversion);
+      assert wrapperClass != null;
+      List<DexMethod> methods = allImplementedMethods(wrapperClass);
+      methods.sort(DexMethod::compareTo);
+      builder.addWrapper(wrapperConversion, methods);
+    }
+  }
+
+  private List<DexMethod> allImplementedMethods(DexClass wrapperClass) {
+    LinkedList<DexClass> workList = new LinkedList<>();
+    List<DexMethod> implementedMethods = new ArrayList<>();
+    workList.add(wrapperClass);
+    while (!workList.isEmpty()) {
+      DexClass dexClass = workList.removeFirst();
+      for (DexEncodedMethod virtualMethod : dexClass.virtualMethods()) {
+        if (!virtualMethod.isPrivateMethod()) {
+          assert virtualMethod.isProtectedMethod() || virtualMethod.isPublicMethod();
+          boolean alreadyAdded = false;
+          // This looks quadratic but given the size of the collections met in practice for
+          // desugared libraries (Max ~15) it does not matter.
+          for (DexMethod alreadyImplementedMethod : implementedMethods) {
+            if (alreadyImplementedMethod.match(virtualMethod.getReference())) {
+              alreadyAdded = true;
+              break;
+            }
+          }
+          if (!alreadyAdded) {
+            assert !virtualMethod.isFinal() : "Cannot wrap final method " + virtualMethod;
+            implementedMethods.add(virtualMethod.getReference());
+          }
+        }
+      }
+      for (DexType itf : dexClass.interfaces.values) {
+        DexClass itfClass = appInfo.definitionFor(itf);
+        if (itfClass != null) {
+          workList.add(itfClass);
+        }
+      }
+      if (dexClass.superType != appInfo.dexItemFactory().objectType) {
+        DexClass superClass = appInfo.definitionFor(dexClass.superType);
+        assert superClass != null; // Cannot be null since we started from a LibraryClass.
+        workList.add(superClass);
+      }
+    }
+    return implementedMethods;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index 3a91fef..758812e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -576,7 +576,7 @@
       Map<DexType, GenericSignature.ClassTypeSignature> extraInterfaceSignatures) {
     // TODO(b/182329331): Only handle type arguments for Cf to Cf desugar.
     if (appView.options().cfToCfDesugar && clazz.validInterfaceSignatures()) {
-      clazz.forEachImmediateSupertype(
+      clazz.forEachImmediateSupertypeWithSignature(
           (type, signature) -> {
             if (emulatesInterfaces.contains(type)) {
               extraInterfaceSignatures.put(
@@ -589,7 +589,7 @@
           });
     } else {
       clazz.forEachImmediateSupertype(
-          (type) -> {
+          type -> {
             if (emulatesInterfaces.contains(type)) {
               extraInterfaceSignatures.put(
                   type, new GenericSignature.ClassTypeSignature(helper.getEmulatedInterface(type)));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index a40d69a..d30f717 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -43,7 +43,6 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.synthesis.SyntheticNaming;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.structural.Ordered;
@@ -160,8 +159,6 @@
     Map<DexType, DexType> emulateLibraryInterface =
         options.desugaredLibrarySpecification.getEmulateLibraryInterface();
     for (DexType interfaceType : emulateLibraryInterface.keySet()) {
-      addRewriteRulesForEmulatedInterface(
-          interfaceType, emulateLibraryInterface.get(interfaceType).toSourceString());
       DexClass emulatedInterfaceClass = appView.definitionFor(interfaceType);
       if (emulatedInterfaceClass != null) {
         for (DexEncodedMethod encodedMethod :
@@ -172,35 +169,6 @@
     }
   }
 
-  void addRewriteRulesForEmulatedInterface(
-      DexType emulatedInterface, String rewrittenEmulatedInterface) {
-    addCompanionClassRewriteRule(emulatedInterface, rewrittenEmulatedInterface);
-    appView.rewritePrefix.rewriteType(
-        InterfaceDesugaringSyntheticHelper.getEmulateLibraryInterfaceClassType(
-            emulatedInterface, factory),
-        factory.createType(
-            DescriptorUtils.javaTypeToDescriptor(
-                rewrittenEmulatedInterface
-                    + InterfaceDesugaringSyntheticHelper.EMULATE_LIBRARY_CLASS_NAME_SUFFIX)));
-  }
-
-  private void addCompanionClassRewriteRule(DexType interfaceType, String rewrittenType) {
-    addCompanionClassRewriteRule(interfaceType, rewrittenType, appView);
-  }
-
-  static void addCompanionClassRewriteRule(
-      DexType interfaceType, String rewrittenType, AppView<?> appView) {
-    appView.rewritePrefix.rewriteType(
-        InterfaceDesugaringSyntheticHelper.getCompanionClassType(
-            interfaceType, appView.dexItemFactory()),
-        appView
-            .dexItemFactory()
-            .createType(
-                DescriptorUtils.javaTypeToDescriptor(
-                    rewrittenType
-                        + InterfaceDesugaringSyntheticHelper.COMPANION_CLASS_NAME_SUFFIX)));
-  }
-
   private boolean isAlreadyDesugared(CfInvoke invoke, ProgramMethod context) {
     return Iterables.any(
         precedingDesugarings, desugaring -> desugaring.needsDesugaring(invoke, context));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index 82f80e8..3aec49b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -414,7 +414,7 @@
     }
 
     @Override
-    protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+    public DexMethod getPreviousMethodSignature(DexMethod method) {
       return extraNewMethodSignatures.getRepresentativeKeyOrDefault(
           method, newMethodSignatures.getRepresentativeKeyOrDefault(method, method));
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
index 55cf273..3e8b5cb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
@@ -4,21 +4,25 @@
 package com.android.tools.r8.ir.desugar.itf;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedInterfaceDescriptor;
 import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer.L8ProgramEmulatedInterfaceSynthesizerEventConsumer;
 import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Iterables;
@@ -116,28 +120,34 @@
         method ->
             builder.addMethod(
                 methodBuilder ->
-                    synthesizeEmulatedInterfaceMethod(method, emulatedInterface, methodBuilder)));
-    assert builder.getType()
-        == InterfaceDesugaringSyntheticHelper.getEmulateLibraryInterfaceClassType(
-            emulatedInterface.type, appView.dexItemFactory());
+                    synthesizeEmulatedInterfaceMethod(
+                        method, emulatedInterface, builder.getType(), methodBuilder)));
+  }
+
+  private DexMethod emulatedMethod(DerivedMethod method, DexType holder) {
+    assert method.getHolderKind() == SyntheticKind.EMULATED_INTERFACE_CLASS;
+    DexProto newProto = appView.dexItemFactory().prependHolderToProto(method.getMethod());
+    return appView.dexItemFactory().createMethod(holder, newProto, method.getName());
+  }
+
+  private DexMethod interfaceMethod(DerivedMethod method) {
+    assert method.getHolderKind() == null;
+    return method.getMethod();
   }
 
   private void synthesizeEmulatedInterfaceMethod(
-      ProgramMethod method, DexProgramClass theInterface, SyntheticMethodBuilder methodBuilder) {
+      ProgramMethod method,
+      DexProgramClass theInterface,
+      DexType dispatchType,
+      SyntheticMethodBuilder methodBuilder) {
     assert !method.getDefinition().isStatic();
+    if (appView.options().testing.machineDesugaredLibrarySpecification != null) {
+      synthesizeEmulatedInterfaceMethodFromMachineSpecification(
+          method, theInterface, dispatchType, methodBuilder);
+      return;
+    }
     DexMethod emulatedMethod = helper.emulateInterfaceLibraryMethod(method);
-    methodBuilder
-        .setName(emulatedMethod.getName())
-        .setProto(emulatedMethod.getProto())
-        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
-        .setCode(
-            emulatedInterfaceMethod ->
-                synthesizeCfCode(method.asProgramMethod(), theInterface, emulatedInterfaceMethod));
-  }
-
-  private CfCode synthesizeCfCode(
-      ProgramMethod method, DexProgramClass theInterface, DexMethod emulatedInterfaceMethod) {
-    DexMethod libraryMethod =
+    DexMethod itfMethod =
         method
             .getReference()
             .withHolder(helper.getEmulatedInterface(theInterface.type), appView.dexItemFactory());
@@ -145,13 +155,84 @@
         helper.ensureDefaultAsMethodOfProgramCompanionClassStub(method).getReference();
     LinkedHashMap<DexType, DexMethod> extraDispatchCases =
         getDispatchCases(method, theInterface, companionMethod);
-    return new EmulateDispatchSyntheticCfCodeProvider(
-            emulatedInterfaceMethod.getHolderType(),
-            companionMethod,
-            libraryMethod,
-            extraDispatchCases,
-            appView)
-        .generateCfCode();
+    methodBuilder
+        .setName(emulatedMethod.getName())
+        .setProto(emulatedMethod.getProto())
+        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+        .setCode(
+            emulatedInterfaceMethod ->
+                new EmulateDispatchSyntheticCfCodeProvider(
+                        emulatedMethod.getHolderType(),
+                        companionMethod,
+                        itfMethod,
+                        extraDispatchCases,
+                        appView)
+                    .generateCfCode());
+  }
+
+  private void synthesizeEmulatedInterfaceMethodFromMachineSpecification(
+      ProgramMethod method,
+      DexProgramClass theInterface,
+      DexType dispatchType,
+      SyntheticMethodBuilder methodBuilder) {
+    EmulatedInterfaceDescriptor emulatedInterfaceDescriptor =
+        appView
+            .options()
+            .testing
+            .machineDesugaredLibrarySpecification
+            .getRewritingFlags()
+            .getEmulatedInterfaces()
+            .get(theInterface.type);
+    EmulatedDispatchMethodDescriptor descriptor =
+        emulatedInterfaceDescriptor.getEmulatedMethods().get(method.getReference());
+    DexMethod emulatedMethod = emulatedMethod(descriptor.getEmulatedDispatchMethod(), dispatchType);
+    DexMethod itfMethod = interfaceMethod(descriptor.getInterfaceMethod());
+    // TODO(b/184026720): Adapt to use the forwarding method.
+    DerivedMethod forwardingMethod = descriptor.getForwardingMethod();
+    assert forwardingMethod.getHolderKind() == SyntheticKind.COMPANION_CLASS;
+    assert forwardingMethod.getMethod() == method.getReference();
+    DexMethod companionMethod =
+        helper.ensureDefaultAsMethodOfProgramCompanionClassStub(method).getReference();
+    LinkedHashMap<DexType, DexMethod> extraDispatchCases = resolveDispatchCases(descriptor);
+    methodBuilder
+        .setName(descriptor.getEmulatedDispatchMethod().getName())
+        .setProto(descriptor.getEmulatedDispatchMethod().getProto())
+        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+        .setCode(
+            emulatedInterfaceMethod ->
+                new EmulateDispatchSyntheticCfCodeProvider(
+                        emulatedMethod.getHolderType(),
+                        companionMethod,
+                        itfMethod,
+                        extraDispatchCases,
+                        appView)
+                    .generateCfCode());
+  }
+
+  private LinkedHashMap<DexType, DexMethod> resolveDispatchCases(
+      EmulatedDispatchMethodDescriptor descriptor) {
+    LinkedHashMap<DexType, DexMethod> extraDispatchCases = new LinkedHashMap<>();
+    descriptor
+        .getDispatchCases()
+        .forEach(
+            (type, derivedMethod) -> {
+              DexMethod caseMethod;
+              if (derivedMethod.getHolderKind() == null) {
+                caseMethod = derivedMethod.getMethod();
+              } else {
+                assert derivedMethod.getHolderKind() == SyntheticKind.COMPANION_CLASS;
+                ProgramMethod resolvedProgramMethod =
+                    appView
+                        .appInfoForDesugaring()
+                        .resolveMethod(derivedMethod.getMethod(), true)
+                        .getResolvedProgramMethod();
+                caseMethod =
+                    InterfaceDesugaringSyntheticHelper.defaultAsMethodOfCompanionClass(
+                        resolvedProgramMethod.getReference(), appView.dexItemFactory());
+              }
+              extraDispatchCases.put(type, caseMethod);
+            });
+    return extraDispatchCases;
   }
 
   private LinkedHashMap<DexType, DexMethod> getDispatchCases(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 04ba47d..5dbfaba 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -14,11 +14,13 @@
 import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterators;
@@ -134,6 +136,23 @@
         if (current.isInvoke() && !current.outValue().isUsed()) {
           current.setOutValue(null);
         }
+        if (current.isStaticGet() && !current.outValue().isUsed() && appView.hasLiveness()) {
+          Box<InitClass> initClass = new Box<>();
+          if (iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
+              appView.withLiveness(),
+              code,
+              current.asStaticGet().getField().getHolderType(),
+              initClass::set)) {
+            if (initClass.isSet()) {
+              // Apply dead code remover to the new init-class instruction.
+              current = iterator.previous();
+              assert current == initClass.get();
+            } else {
+              // Instruction removed.
+              continue;
+            }
+          }
+        }
       }
       DeadInstructionResult deadInstructionResult = current.canBeDeadCode(appView, code);
       if (deadInstructionResult.isNotDead()) {
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 2c185bf..785c461 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
@@ -1099,7 +1099,7 @@
         return false;
       }
     } else if (invoke.isInvokeStatic()) {
-      if (!iterator.replaceCurrentInstructionByInitClassIfPossible(
+      if (!iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
           appView, code, resolvedMethod.getHolderType())) {
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 755f334..4807cda 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -246,7 +246,7 @@
         iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context());
       } else if (current.isStaticGet()) {
         StaticGet staticGet = current.asStaticGet();
-        iterator.replaceCurrentInstructionByInitClassIfPossible(
+        iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
             appView, code, staticGet.getField().holder);
       }
       replacement.setPosition(position);
@@ -333,7 +333,7 @@
         if (invoke.isInvokeMethodWithReceiver()) {
           iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
         } else if (invoke.isInvokeStatic() && singleTarget != null) {
-          iterator.replaceCurrentInstructionByInitClassIfPossible(
+          iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
               appView, code, singleTarget.getHolderType());
         }
 
@@ -440,7 +440,7 @@
           iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
         } else {
           assert current.isStaticGet();
-          iterator.replaceCurrentInstructionByInitClassIfPossible(
+          iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
               appView, code, target.getHolderType());
         }
 
@@ -491,7 +491,8 @@
       return;
     }
 
-    iterator.replaceCurrentInstructionByInitClassIfPossible(appView, code, field.getHolderType());
+    iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
+        appView, code, field.getHolderType());
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
index 4093846..3edb51f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
@@ -1681,7 +1681,6 @@
           builder.addNonThisArgument(i, typeLattice);
         }
       }
-      builder.flushArgumentInstructions();
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 056d9c6..0a0bed9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -181,7 +181,7 @@
     DexEncodedField field = fieldResolutionResult.getResolvedField();
     FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
     DynamicType dynamicType = optimizationInfo.getDynamicType();
-    if (!dynamicType.isExactClassType()) {
+    if (!dynamicType.isExactClassType() || !dynamicType.getNullability().isDefinitelyNotNull()) {
       return EligibilityStatus.NOT_ELIGIBLE;
     }
     eligibleClass =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
index 1b9d97b..1583873 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
@@ -8,10 +8,9 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
@@ -19,7 +18,7 @@
 import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.google.common.collect.Sets;
+import java.util.Collections;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 
@@ -59,13 +58,11 @@
   }
 
   @Override
-  public Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor) {
-    return Sets.newIdentityHashSet();
-  }
-
-  @Override
-  public void rewriteNullCheck(InstructionListIterator iterator, InvokeMethod invoke) {
-    // Intentionally empty.
+  public Set<Phi> rewriteCode(
+      IRCode code,
+      MethodProcessor methodProcessor,
+      RewrittenPrototypeDescription prototypeChanges) {
+    return Collections.emptySet();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 4179fed..8ff6293 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -8,10 +8,9 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
@@ -44,9 +43,9 @@
 
   public abstract void recordEnumState(DexProgramClass clazz, StaticFieldValues staticFieldValues);
 
-  public abstract Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor);
+  public abstract Set<Phi> rewriteCode(
+      IRCode code, MethodProcessor methodProcessor, RewrittenPrototypeDescription prototypeChanges);
 
-  public abstract void rewriteNullCheck(InstructionListIterator iterator, InvokeMethod invoke);
 
   public abstract void unboxEnums(
       AppView<AppInfoWithLiveness> appView,
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 acde184..13fc08e 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
@@ -40,8 +40,10 @@
 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.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 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;
@@ -62,7 +64,6 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
@@ -627,6 +628,7 @@
       return;
     }
 
+    GraphLens previousLens = appView.graphLens();
     ImmutableSet<DexType> enumsToUnbox = enumUnboxingCandidatesInfo.candidates();
     ImmutableSet<DexProgramClass> enumClassesToUnbox =
         enumUnboxingCandidatesInfo.candidateClasses();
@@ -646,15 +648,19 @@
         checkNotNullMethodsBuilder
             .rewrittenWithLens(appView, (enumClasses, appliedGraphLens) -> enumClasses)
             .build(appView, builder -> builder.build(appView));
+    checkNotNullMethods.removeIf(
+        (checkNotNullMethod, ignore) ->
+            !checkNotNullMethod
+                .getOptimizationInfo()
+                .getEnumUnboxerMethodClassification()
+                .isCheckNotNullClassification());
+
     EnumUnboxingTreeFixer.Result treeFixerResult =
         new EnumUnboxingTreeFixer(
                 appView, checkNotNullMethods, enumDataMap, enumClassesToUnbox, utilityClasses)
             .fixupTypeReferences(converter, executorService);
     EnumUnboxingLens enumUnboxingLens = treeFixerResult.getLens();
 
-    // Update the graph lens.
-    appView.rewriteWithLens(enumUnboxingLens);
-
     // Enqueue the (lens rewritten) methods that require reprocessing.
     //
     // Note that the reprocessing set must be rewritten to the new enum unboxing lens before pruning
@@ -674,13 +680,12 @@
                 .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods()));
     methodsDependingOnLibraryModelisation.clear();
 
-    updateOptimizationInfos(executorService, feedback, treeFixerResult);
+    updateOptimizationInfos(executorService, feedback, treeFixerResult, previousLens);
 
     enumUnboxerRewriter =
         new EnumUnboxingRewriter(
             appView,
             treeFixerResult.getCheckNotNullToCheckNotZeroMapping(),
-            converter,
             enumUnboxingLens,
             enumDataMap,
             utilityClasses);
@@ -689,8 +694,15 @@
   private void updateOptimizationInfos(
       ExecutorService executorService,
       OptimizationFeedbackDelayed feedback,
-      EnumUnboxingTreeFixer.Result treeFixerResult)
+      EnumUnboxingTreeFixer.Result treeFixerResult,
+      GraphLens previousLens)
       throws ExecutionException {
+    NonIdentityGraphLens graphLens = appView.graphLens().asNonIdentityLens();
+    assert graphLens.isEnumUnboxerLens();
+
+    GraphLens codeLens = graphLens.getPrevious();
+    assert codeLens == previousLens;
+
     feedback.fixupOptimizationInfos(
         appView,
         executorService,
@@ -698,18 +710,18 @@
           @Override
           public void fixup(DexEncodedField field, MutableFieldOptimizationInfo optimizationInfo) {
             optimizationInfo
-                .fixupClassTypeReferences(appView, appView.graphLens())
-                .fixupAbstractValue(appView, appView.graphLens());
+                .fixupClassTypeReferences(appView, graphLens)
+                .fixupAbstractValue(appView, graphLens, codeLens);
           }
 
           @Override
           public void fixup(
               DexEncodedMethod method, MutableMethodOptimizationInfo optimizationInfo) {
             optimizationInfo
-                .fixupClassTypeReferences(appView, appView.graphLens())
-                .fixupAbstractReturnValue(appView, appView.graphLens())
+                .fixupClassTypeReferences(appView, graphLens)
+                .fixupAbstractReturnValue(appView, graphLens, codeLens)
                 .fixupInstanceInitializerInfo(
-                    appView, appView.graphLens(), treeFixerResult.getPrunedItems());
+                    appView, graphLens, codeLens, treeFixerResult.getPrunedItems());
 
             // Clear the enum unboxer method classification for check-not-null methods (these
             // classifications are transferred to the synthesized check-not-zero methods by now).
@@ -1448,23 +1460,19 @@
   }
 
   @Override
-  public Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor) {
+  public Set<Phi> rewriteCode(
+      IRCode code,
+      MethodProcessor methodProcessor,
+      RewrittenPrototypeDescription prototypeChanges) {
     // This has no effect during primary processing since the enumUnboxerRewriter is set
     // in between primary and post processing.
     if (enumUnboxerRewriter != null) {
-      return enumUnboxerRewriter.rewriteCode(code, methodProcessor);
+      return enumUnboxerRewriter.rewriteCode(code, methodProcessor, prototypeChanges);
     }
     return Sets.newIdentityHashSet();
   }
 
   @Override
-  public void rewriteNullCheck(InstructionListIterator iterator, InvokeMethod invoke) {
-    if (enumUnboxerRewriter != null) {
-      enumUnboxerRewriter.rewriteNullCheck(iterator, invoke);
-    }
-  }
-
-  @Override
   public void unsetRewriter() {
     enumUnboxerRewriter = null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index e055f70..5e3b23d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -50,28 +50,34 @@
   }
 
   @Override
+  public boolean hasCustomCodeRewritings() {
+    return true;
+  }
+
+  @Override
+  public boolean isEnumUnboxerLens() {
+    return true;
+  }
+
+  @Override
   protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
       RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
     // Rewrite the single value of the given RewrittenPrototypeDescription if it is referring to an
     // unboxed enum field.
     if (prototypeChanges.hasRewrittenReturnInfo()) {
-      RewrittenTypeInfo rewrittenTypeInfo = prototypeChanges.getRewrittenReturnInfo();
-      if (rewrittenTypeInfo.hasSingleValue()) {
-        SingleValue singleValue = rewrittenTypeInfo.getSingleValue();
-        if (singleValue.isSingleFieldValue()) {
-          SingleFieldValue singleFieldValue = singleValue.asSingleFieldValue();
-          if (unboxedEnums.hasUnboxedValueFor(singleFieldValue.getField())) {
-            prototypeChanges =
-                prototypeChanges.withRewrittenReturnInfo(
-                    RewrittenTypeInfo.builder()
-                        .setCastType(rewrittenTypeInfo.getCastType())
-                        .setOldType(rewrittenTypeInfo.getOldType())
-                        .setNewType(rewrittenTypeInfo.getNewType())
-                        .setSingleValue(
-                            abstractValueFactory.createSingleNumberValue(
-                                unboxedEnums.getUnboxedValue(singleFieldValue.getField())))
-                        .build());
-          }
+      RewrittenTypeInfo rewrittenReturnInfo = prototypeChanges.getRewrittenReturnInfo();
+      if (rewrittenReturnInfo.hasSingleValue()) {
+        SingleValue singleValue = rewrittenReturnInfo.getSingleValue();
+        SingleValue rewrittenSingleValue = rewriteSingleValue(singleValue);
+        if (rewrittenSingleValue != singleValue) {
+          prototypeChanges =
+              prototypeChanges.withRewrittenReturnInfo(
+                  RewrittenTypeInfo.builder()
+                      .setCastType(rewrittenReturnInfo.getCastType())
+                      .setOldType(rewrittenReturnInfo.getOldType())
+                      .setNewType(rewrittenReturnInfo.getNewType())
+                      .setSingleValue(rewrittenSingleValue)
+                      .build());
         }
       }
     }
@@ -84,6 +90,17 @@
     return prototypeChanges.combine(enumUnboxingPrototypeChanges);
   }
 
+  private SingleValue rewriteSingleValue(SingleValue singleValue) {
+    if (singleValue.isSingleFieldValue()) {
+      SingleFieldValue singleFieldValue = singleValue.asSingleFieldValue();
+      if (unboxedEnums.hasUnboxedValueFor(singleFieldValue.getField())) {
+        return abstractValueFactory.createSingleNumberValue(
+            unboxedEnums.getUnboxedValue(singleFieldValue.getField()));
+      }
+    }
+    return singleValue;
+  }
+
   @Override
   protected Invoke.Type mapInvocationType(
       DexMethod newMethod, DexMethod originalMethod, Invoke.Type type) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 787b4da..b0a4ed5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -13,12 +13,16 @@
 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.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ArrayAccess;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -31,7 +35,6 @@
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
 import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification;
@@ -42,6 +45,7 @@
 import com.google.common.collect.Sets;
 import java.util.Collections;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -51,7 +55,6 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping;
-  private final IRConverter converter;
   private final DexItemFactory factory;
   private final InternalOptions options;
   private final EnumDataMap unboxedEnumsData;
@@ -61,13 +64,11 @@
   EnumUnboxingRewriter(
       AppView<AppInfoWithLiveness> appView,
       Map<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping,
-      IRConverter converter,
       EnumUnboxingLens enumUnboxingLens,
       EnumDataMap unboxedEnumsInstanceFieldData,
       EnumUnboxingUtilityClasses utilityClasses) {
     this.appView = appView;
     this.checkNotNullToCheckNotZeroMapping = checkNotNullToCheckNotZeroMapping;
-    this.converter = converter;
     this.factory = appView.dexItemFactory();
     this.options = appView.options();
     this.enumUnboxingLens = enumUnboxingLens;
@@ -83,7 +84,36 @@
     return utilityClasses.getSharedUtilityClass();
   }
 
-  Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor) {
+  private Map<Instruction, DexType> createInitialConvertedEnums(
+      IRCode code, RewrittenPrototypeDescription prototypeChanges) {
+    Map<Instruction, DexType> convertedEnums = new IdentityHashMap<>();
+    Iterator<Instruction> iterator = code.entryBlock().iterator();
+    int originalNumberOfArguments =
+        code.getNumberOfArguments()
+            + prototypeChanges.getArgumentInfoCollection().numberOfRemovedArguments();
+    for (int argumentIndex = 0; argumentIndex < originalNumberOfArguments; argumentIndex++) {
+      ArgumentInfo argumentInfo =
+          prototypeChanges.getArgumentInfoCollection().getArgumentInfo(argumentIndex);
+      if (argumentInfo.isRemovedArgumentInfo()) {
+        continue;
+      }
+      Instruction next = iterator.next();
+      assert next.isArgument();
+      if (argumentInfo.isRewrittenTypeInfo()) {
+        RewrittenTypeInfo rewrittenTypeInfo = argumentInfo.asRewrittenTypeInfo();
+        DexType enumType = getEnumTypeOrNull(rewrittenTypeInfo.getOldType().toBaseType(factory));
+        if (enumType != null) {
+          convertedEnums.put(next, enumType);
+        }
+      }
+    }
+    return convertedEnums;
+  }
+
+  Set<Phi> rewriteCode(
+      IRCode code,
+      MethodProcessor methodProcessor,
+      RewrittenPrototypeDescription prototypeChanges) {
     // We should not process the enum methods, they will be removed and they may contain invalid
     // rewriting rules.
     if (unboxedEnumsData.isEmpty()) {
@@ -91,7 +121,7 @@
     }
     assert code.isConsistentSSABeforeTypesAreCorrect();
     ProgramMethod context = code.context();
-    Map<Instruction, DexType> convertedEnums = new IdentityHashMap<>();
+    Map<Instruction, DexType> convertedEnums = createInitialConvertedEnums(code, prototypeChanges);
     Set<Phi> affectedPhis = Sets.newIdentityHashSet();
     ListIterator<BasicBlock> blocks = code.listIterator();
     Set<BasicBlock> seenBlocks = Sets.newIdentityHashSet();
@@ -109,6 +139,27 @@
           continue;
         }
 
+        if (instruction.isIf()) {
+          If ifInstruction = instruction.asIf();
+          if (!ifInstruction.isZeroTest()) {
+            for (int operandIndex = 0; operandIndex < 2; operandIndex++) {
+              Value operand = ifInstruction.getOperand(operandIndex);
+              DexType enumType = getEnumTypeOrNull(operand, convertedEnums);
+              if (enumType != null) {
+                int otherOperandIndex = 1 - operandIndex;
+                Value otherOperand = ifInstruction.getOperand(otherOperandIndex);
+                if (otherOperand.getType().isNullType()) {
+                  iterator.previous();
+                  ifInstruction.replaceValue(
+                      otherOperandIndex, iterator.insertConstIntInstruction(code, options, 0));
+                  iterator.next();
+                  break;
+                }
+              }
+            }
+          }
+        }
+
         // Rewrites specific enum methods, such as ordinal, into their corresponding enum unboxed
         // counterpart. The rewriting (== or match) is based on the following:
         // - name, ordinal and compareTo are final and implemented only on java.lang.Enum,
@@ -269,14 +320,14 @@
         // Rewrite array accesses from MyEnum[] (OBJECT) to int[] (INT).
         if (instruction.isArrayAccess()) {
           ArrayAccess arrayAccess = instruction.asArrayAccess();
-          DexType enumType = getEnumTypeOrNull(arrayAccess);
+          DexType enumType = getEnumTypeOrNull(arrayAccess, convertedEnums);
           if (enumType != null) {
             if (arrayAccess.hasOutValue()) {
               affectedPhis.addAll(arrayAccess.outValue().uniquePhiUsers());
             }
-            instruction = arrayAccess.withMemberType(MemberType.INT);
-            iterator.replaceCurrentInstruction(instruction);
-            convertedEnums.put(instruction, enumType);
+            arrayAccess = arrayAccess.withMemberType(MemberType.INT);
+            iterator.replaceCurrentInstruction(arrayAccess);
+            convertedEnums.put(arrayAccess, enumType);
           }
           assert validateArrayAccess(arrayAccess);
         }
@@ -526,8 +577,8 @@
 
   private DexType getEnumTypeOrNull(Value receiver, Map<Instruction, DexType> convertedEnums) {
     TypeElement type = receiver.getType();
-    if (type.isInt()) {
-      return convertedEnums.get(receiver.definition);
+    if (type.isInt() || (type.isArrayType() && type.asArrayType().getBaseType().isInt())) {
+      return receiver.isPhi() ? null : convertedEnums.get(receiver.getDefinition());
     }
     return getEnumTypeOrNull(type);
   }
@@ -536,11 +587,15 @@
     if (!type.isClassType()) {
       return null;
     }
-    DexType enumType = type.asClassType().getClassType();
-    return unboxedEnumsData.isUnboxedEnum(enumType) ? enumType : null;
+    return getEnumTypeOrNull(type.asClassType().getClassType());
   }
 
-  private DexType getEnumTypeOrNull(ArrayAccess arrayAccess) {
+  private DexType getEnumTypeOrNull(DexType type) {
+    return unboxedEnumsData.isUnboxedEnum(type) ? type : null;
+  }
+
+  private DexType getEnumTypeOrNull(
+      ArrayAccess arrayAccess, Map<Instruction, DexType> convertedEnums) {
     ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
     if (arrayType == null) {
       assert arrayAccess.array().getType().isNullType();
@@ -550,10 +605,10 @@
       return null;
     }
     TypeElement baseType = arrayType.getBaseType();
-    if (!baseType.isClassType()) {
-      return null;
+    if (baseType.isClassType()) {
+      DexType classType = baseType.asClassType().getClassType();
+      return unboxedEnumsData.isUnboxedEnum(classType) ? classType : null;
     }
-    DexType classType = baseType.asClassType().getClassType();
-    return unboxedEnumsData.isUnboxedEnum(classType) ? classType : null;
+    return getEnumTypeOrNull(arrayAccess.array(), convertedEnums);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 862840b..773dadf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -132,12 +132,18 @@
       }
     }
 
+    // Install the new graph lens before processing any checkNotZero() methods.
+    EnumUnboxingLens lens = lensBuilder.build(appView);
+    appView.rewriteWithLens(lens);
+
+    // Rewrite outliner with lens.
+    converter.outliner.rewriteWithLens();
+
     // Create mapping from checkNotNull() to checkNotZero() methods.
     BiMap<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping =
         duplicateCheckNotNullMethods(converter, executorService);
 
-    return new Result(
-        checkNotNullToCheckNotZeroMapping, lensBuilder.build(appView), prunedItemsBuilder.build());
+    return new Result(checkNotNullToCheckNotZeroMapping, lens, prunedItemsBuilder.build());
   }
 
   private BiMap<DexMethod, DexMethod> duplicateCheckNotNullMethods(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index c03d170..3bf8709 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -64,8 +64,9 @@
     this.abstractValue = abstractValue;
   }
 
-  public void fixupAbstractValue(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
-    setAbstractValue(abstractValue.rewrittenWithLens(appView, lens));
+  public void fixupAbstractValue(
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+    setAbstractValue(abstractValue.rewrittenWithLens(appView, lens, codeLens));
   }
 
   @Override
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 e07fee5..64bdb91 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
@@ -185,15 +185,18 @@
   }
 
   public MutableMethodOptimizationInfo fixupAbstractReturnValue(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens) {
-    abstractReturnValue = abstractReturnValue.rewrittenWithLens(appView, lens);
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+    abstractReturnValue = abstractReturnValue.rewrittenWithLens(appView, lens, codeLens);
     return this;
   }
 
   public MutableMethodOptimizationInfo fixupInstanceInitializerInfo(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems) {
+      AppView<AppInfoWithLiveness> appView,
+      GraphLens lens,
+      GraphLens codeLens,
+      PrunedItems prunedItems) {
     instanceInitializerInfoCollection =
-        instanceInitializerInfoCollection.rewrittenWithLens(appView, lens, prunedItems);
+        instanceInitializerInfoCollection.rewrittenWithLens(appView, lens, codeLens, prunedItems);
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java
index 16a86a4..833f0c6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java
@@ -60,7 +60,7 @@
 
   @Override
   public InstanceFieldInitializationInfoCollection rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
index 46255f1..b689d8a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
@@ -62,7 +62,7 @@
 
   @Override
   public InstanceFieldInitializationInfo rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     // We don't have the context here to determine what should happen. It is the responsibility of
     // optimizations that change the proto of instance initializers to update the argument
     // initialization info.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
index 94e5ea5..0a34194 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfo.java
@@ -51,5 +51,5 @@
       ArgumentInfoCollection argumentInfoCollection);
 
   InstanceFieldInitializationInfo rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens);
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
index c5d846e..2520561 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
@@ -48,7 +48,7 @@
       ArgumentInfoCollection argumentInfoCollection);
 
   public abstract InstanceFieldInitializationInfoCollection rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens);
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens);
 
   public static class Builder {
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
index 39a72ba..24730bb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
@@ -55,7 +55,7 @@
 
   @Override
   public InstanceFieldInitializationInfo rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     EnumDataMap enumDataMap = appView.unboxedEnums();
     if (dynamicLowerBoundType != null
         && enumDataMap.isUnboxedEnum(dynamicLowerBoundType.getClassType())) {
@@ -69,9 +69,9 @@
     }
     return new InstanceFieldTypeInitializationInfo(
         dynamicLowerBoundType != null
-            ? dynamicLowerBoundType.rewrittenWithLens(appView, lens).asClassType()
+            ? dynamicLowerBoundType.rewrittenWithLens(appView, lens, codeLens).asClassType()
             : null,
-        dynamicUpperBoundType.rewrittenWithLens(appView, lens));
+        dynamicUpperBoundType.rewrittenWithLens(appView, lens, codeLens));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
index 2e34bcd..b996542 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
@@ -80,12 +80,13 @@
 
   @Override
   public InstanceFieldInitializationInfoCollection rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     Builder builder = InstanceFieldInitializationInfoCollection.builder();
     infos.forEach(
         (field, info) ->
             builder.recordInitializationInfo(
-                lens.lookupField(field), info.rewrittenWithLens(appView, lens)));
+                lens.lookupField(field, codeLens),
+                info.rewrittenWithLens(appView, lens, codeLens)));
     return builder.build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
index f0353a4..77409cb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
@@ -37,7 +37,7 @@
 
   @Override
   public InstanceFieldInitializationInfo rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextInsensitiveInstanceInitializerInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextInsensitiveInstanceInitializerInfoCollection.java
index 1feccf6..b15e8e4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextInsensitiveInstanceInitializerInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextInsensitiveInstanceInitializerInfoCollection.java
@@ -48,9 +48,12 @@
 
   @Override
   public ContextInsensitiveInstanceInitializerInfoCollection rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems) {
+      AppView<AppInfoWithLiveness> appView,
+      GraphLens lens,
+      GraphLens codeLens,
+      PrunedItems prunedItems) {
     NonTrivialInstanceInitializerInfo rewrittenInfo =
-        info.rewrittenWithLens(appView, lens, prunedItems);
+        info.rewrittenWithLens(appView, lens, codeLens, prunedItems);
     if (rewrittenInfo != info) {
       return new ContextInsensitiveInstanceInitializerInfoCollection(rewrittenInfo);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextSensitiveInstanceInitializerInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextSensitiveInstanceInitializerInfoCollection.java
index f281892..fec6ed4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextSensitiveInstanceInitializerInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextSensitiveInstanceInitializerInfoCollection.java
@@ -62,11 +62,14 @@
 
   @Override
   public InstanceInitializerInfoCollection rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems) {
+      AppView<AppInfoWithLiveness> appView,
+      GraphLens lens,
+      GraphLens codeLens,
+      PrunedItems prunedItems) {
     Builder builder = builder();
     infos.forEach(
         (context, info) ->
-            builder.put(context, info.rewrittenWithLens(appView, lens, prunedItems)));
+            builder.put(context, info.rewrittenWithLens(appView, lens, codeLens, prunedItems)));
     return builder.build();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
index 678489b..46a3511 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
@@ -74,7 +74,10 @@
 
   @Override
   public InstanceInitializerInfo rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems) {
+      AppView<AppInfoWithLiveness> appView,
+      GraphLens lens,
+      GraphLens codeLens,
+      PrunedItems prunedItems) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/EmptyInstanceInitializerInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/EmptyInstanceInitializerInfoCollection.java
index c3742a5..9ac9762 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/EmptyInstanceInitializerInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/EmptyInstanceInitializerInfoCollection.java
@@ -45,7 +45,10 @@
 
   @Override
   public EmptyInstanceInitializerInfoCollection rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems) {
+      AppView<AppInfoWithLiveness> appView,
+      GraphLens lens,
+      GraphLens codeLens,
+      PrunedItems prunedItems) {
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
index e89b738..e07b514 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
@@ -76,5 +76,8 @@
       AppView<AppInfoWithLiveness> appView, ArgumentInfoCollection argumentInfoCollection);
 
   public abstract InstanceInitializerInfo rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems);
+      AppView<AppInfoWithLiveness> appView,
+      GraphLens lens,
+      GraphLens codeLens,
+      PrunedItems prunedItems);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfoCollection.java
index f228922..8002513 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfoCollection.java
@@ -41,7 +41,10 @@
       AppView<AppInfoWithLiveness> appView, ArgumentInfoCollection argumentInfoCollection);
 
   public abstract InstanceInitializerInfoCollection rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems);
+      AppView<AppInfoWithLiveness> appView,
+      GraphLens lens,
+      GraphLens codeLens,
+      PrunedItems prunedItems);
 
   public static class Builder {
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
index 99cb83b..58af519 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
@@ -111,12 +111,15 @@
 
   @Override
   public NonTrivialInstanceInitializerInfo rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems) {
+      AppView<AppInfoWithLiveness> appView,
+      GraphLens lens,
+      GraphLens codeLens,
+      PrunedItems prunedItems) {
     return new NonTrivialInstanceInitializerInfo(
         data,
-        fieldInitializationInfos.rewrittenWithLens(appView, lens),
-        readSet.rewrittenWithLens(appView, lens, prunedItems),
-        lens.getRenamedMethodSignature(parent));
+        fieldInitializationInfos.rewrittenWithLens(appView, lens, codeLens),
+        readSet.rewrittenWithLens(appView, lens, codeLens, prunedItems),
+        lens.getRenamedMethodSignature(parent, codeLens));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
index e356796..a82579e 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
@@ -32,7 +32,6 @@
 public class EmulateDispatchSyntheticCfCodeProvider extends SyntheticCfCodeProvider {
 
   private final DexMethod forwardingMethod;
-  private final DexType receiverType;
   private final DexMethod interfaceMethod;
   private final LinkedHashMap<DexType, DexMethod> extraDispatchCases;
 
@@ -44,13 +43,13 @@
       AppView<?> appView) {
     super(appView, holder);
     this.forwardingMethod = forwardingMethod;
-    this.receiverType = forwardingMethod.getParameter(0);
     this.interfaceMethod = interfaceMethod;
     this.extraDispatchCases = extraDispatchCases;
   }
 
   @Override
   public CfCode generateCfCode() {
+    DexType receiverType = forwardingMethod.getParameter(0);
     List<CfInstruction> instructions = new ArrayList<>();
     CfLabel[] labels = new CfLabel[extraDispatchCases.size() + 1];
     for (int i = 0; i < labels.length; i++) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
index f2a6eb4..e2a2083 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
@@ -8,39 +8,23 @@
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import com.android.tools.r8.utils.Reporter;
-import java.util.List;
 import kotlinx.metadata.KmFlexibleTypeUpperBound;
-import kotlinx.metadata.KmType;
-import kotlinx.metadata.jvm.JvmExtensionsKt;
 
-public class KotlinFlexibleTypeUpperBoundInfo extends KotlinTypeInfo {
+public class KotlinFlexibleTypeUpperBoundInfo implements EnqueuerMetadataTraceable {
 
   private static final String KOTLIN_JVM_PLATFORMTYPE = "kotlin.jvm.PlatformType";
   private static final KotlinFlexibleTypeUpperBoundInfo NO_FLEXIBLE_UPPER_BOUND =
-      new KotlinFlexibleTypeUpperBoundInfo(
-          0, null, null, null, null, null, null, KOTLIN_JVM_PLATFORMTYPE);
+      new KotlinFlexibleTypeUpperBoundInfo(KOTLIN_JVM_PLATFORMTYPE, null);
 
   private final String typeFlexibilityId;
+  private final KotlinTypeInfo kotlinTypeInfo;
 
   private KotlinFlexibleTypeUpperBoundInfo(
-      int flags,
-      KotlinClassifierInfo classifier,
-      KotlinTypeInfo abbreviatedType,
-      KotlinTypeInfo outerType,
-      List<KotlinTypeProjectionInfo> arguments,
-      List<KotlinAnnotationInfo> annotations,
-      KotlinFlexibleTypeUpperBoundInfo flexibleTypeUpperBoundInfo,
-      String typeFlexibilityId) {
-    super(
-        flags,
-        classifier,
-        abbreviatedType,
-        outerType,
-        arguments,
-        annotations,
-        flexibleTypeUpperBoundInfo);
+      String typeFlexibilityId, KotlinTypeInfo kotlinTypeInfo) {
     this.typeFlexibilityId = typeFlexibilityId;
+    this.kotlinTypeInfo = kotlinTypeInfo;
     assert KOTLIN_JVM_PLATFORMTYPE.equals(typeFlexibilityId);
   }
 
@@ -49,17 +33,9 @@
     if (flexibleTypeUpperBound == null) {
       return NO_FLEXIBLE_UPPER_BOUND;
     }
-    KmType kmType = flexibleTypeUpperBound.getType();
     return new KotlinFlexibleTypeUpperBoundInfo(
-        kmType.getFlags(),
-        KotlinClassifierInfo.create(kmType.classifier, factory, reporter),
-        KotlinTypeInfo.create(kmType.getAbbreviatedType(), factory, reporter),
-        KotlinTypeInfo.create(kmType.getOuterType(), factory, reporter),
-        getArguments(kmType.getArguments(), factory, reporter),
-        KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), factory),
-        KotlinFlexibleTypeUpperBoundInfo.create(
-            kmType.getFlexibleTypeUpperBound(), factory, reporter),
-        flexibleTypeUpperBound.getTypeFlexibilityId());
+        flexibleTypeUpperBound.getTypeFlexibilityId(),
+        KotlinTypeInfo.create(flexibleTypeUpperBound.getType(), factory, reporter));
   }
 
   boolean rewrite(
@@ -70,7 +46,11 @@
       // Nothing to do.
       return false;
     }
-    return super.rewrite(
+    if (kotlinTypeInfo == null) {
+      assert false;
+      return false;
+    }
+    return kotlinTypeInfo.rewrite(
         flags -> visitorProvider.get(flags, typeFlexibilityId), appView, namingLens);
   }
 
@@ -79,6 +59,10 @@
     if (this == NO_FLEXIBLE_UPPER_BOUND) {
       return;
     }
-    super.trace(definitionSupplier);
+    if (kotlinTypeInfo == null) {
+      assert false;
+      return;
+    }
+    kotlinTypeInfo.trace(definitionSupplier);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
index 9503404..6127754 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -34,6 +34,7 @@
   private final List<KotlinTypeProjectionInfo> arguments;
   private final List<KotlinAnnotationInfo> annotations;
   private final KotlinFlexibleTypeUpperBoundInfo flexibleTypeUpperBound;
+  private final boolean isRaw;
 
   KotlinTypeInfo(
       int flags,
@@ -42,7 +43,8 @@
       KotlinTypeInfo outerType,
       List<KotlinTypeProjectionInfo> arguments,
       List<KotlinAnnotationInfo> annotations,
-      KotlinFlexibleTypeUpperBoundInfo flexibleTypeUpperBound) {
+      KotlinFlexibleTypeUpperBoundInfo flexibleTypeUpperBound,
+      boolean isRaw) {
     this.flags = flags;
     this.classifier = classifier;
     this.abbreviatedType = abbreviatedType;
@@ -50,6 +52,7 @@
     this.arguments = arguments;
     this.annotations = annotations;
     this.flexibleTypeUpperBound = flexibleTypeUpperBound;
+    this.isRaw = isRaw;
   }
 
   static KotlinTypeInfo create(KmType kmType, DexItemFactory factory, Reporter reporter) {
@@ -64,7 +67,8 @@
         getArguments(kmType.getArguments(), factory, reporter),
         KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), factory),
         KotlinFlexibleTypeUpperBoundInfo.create(
-            kmType.getFlexibleTypeUpperBound(), factory, reporter));
+            kmType.getFlexibleTypeUpperBound(), factory, reporter),
+        JvmExtensionsKt.isRaw(kmType));
   }
 
   static List<KotlinTypeProjectionInfo> getArguments(
@@ -104,7 +108,7 @@
     rewritten |=
         flexibleTypeUpperBound.rewrite(
             kmTypeVisitor::visitFlexibleTypeUpperBound, appView, namingLens);
-    if (annotations.isEmpty()) {
+    if (annotations.isEmpty() && !isRaw) {
       return rewritten;
     }
     JvmTypeExtensionVisitor extensionVisitor =
@@ -113,6 +117,7 @@
       for (KotlinAnnotationInfo annotation : annotations) {
         rewritten |= annotation.rewrite(extensionVisitor::visitAnnotation, appView, namingLens);
       }
+      extensionVisitor.visit(isRaw);
     }
     return rewritten;
   }
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 8a8d804..b2b9e9f 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -272,6 +272,11 @@
   }
 
   private void reserveNamesInClasses() {
+    // Ensure reservation state for java.lang.Object is always created, even if the type is missing.
+    allocateReservationStateAndReserve(
+        appView.dexItemFactory().objectType,
+        appView.dexItemFactory().objectType,
+        rootReservationState);
     TopDownClassHierarchyTraversal.forAllClasses(appView)
         .visit(
             appView.appInfo().classes(),
@@ -336,6 +341,10 @@
     if (reservationState != null) {
       return reservationState;
     }
+    if (appView.definitionFor(type) == null) {
+      // Reservation states for missing definitions is always object.
+      return reservationStates.get(appView.dexItemFactory().objectType);
+    }
     // If we cannot find the reservation state, which is a result from a library class extending
     // a program class. The gap is tracked in the frontier state.
     assert frontiers.containsKey(type);
diff --git a/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java
index 4f56738..f4b80a7 100644
--- a/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java
@@ -71,7 +71,10 @@
   }
 
   @Override
-  public DexField getRenamedFieldSignature(DexField originalField) {
+  public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) {
+    if (this == codeLens) {
+      return originalField;
+    }
     return getPrevious().getRenamedFieldSignature(originalField);
   }
 
@@ -92,13 +95,17 @@
   }
 
   @Override
-  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+  public DexMethod getPreviousMethodSignature(DexMethod method) {
     return method;
   }
 
   @Override
-  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
-    return getPrevious().lookupPrototypeChangesForMethodDefinition(method);
+  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+      DexMethod method, GraphLens codeLens) {
+    if (this == codeLens) {
+      return getIdentityLens().lookupPrototypeChangesForMethodDefinition(method, codeLens);
+    }
+    return getPrevious().lookupPrototypeChangesForMethodDefinition(method, codeLens);
   }
 
   @Override
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 32bc033..25667ea 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -73,6 +73,25 @@
       return original;
     }
 
+    if (invokeType.isSuper() && options.canHaveSuperInvokeBug()) {
+      // To preserve semantics we should find the first library method on the boundary.
+      DexClass libraryHolder =
+          appView.definitionFor(
+              firstLibraryClassOrFirstInterfaceTarget(
+                  resolutionResult.getResolvedHolder(),
+                  appView,
+                  resolvedMethod.getReference(),
+                  original.getHolderType(),
+                  DexClass::lookupMethod));
+      if (libraryHolder == null) {
+        return original;
+      }
+      if (libraryHolder == resolvedMethod.getHolder()) {
+        return resolvedMethod.getReference();
+      }
+      return resolvedMethod.getReference().withHolder(libraryHolder, appView.dexItemFactory());
+    }
+
     LibraryMethod eligibleLibraryMethod = null;
     SingleResolutionResult currentResolutionResult = resolutionResult;
     while (currentResolutionResult != null) {
@@ -106,13 +125,12 @@
     }
 
     DexType newHolder =
-        resolvedMethod.getHolder().isInterface()
-            ? firstLibraryClassForInterfaceTarget(
-                appView,
-                resolvedMethod.getReference(),
-                original.getHolderType(),
-                DexClass::lookupMethod)
-            : firstLibraryClass(appView, original.getHolderType());
+        firstLibraryClassOrFirstInterfaceTarget(
+            resolvedMethod.getHolder(),
+            appView,
+            resolvedMethod.getReference(),
+            original.getHolderType(),
+            DexClass::lookupMethod);
     return newHolder != null ? original.withHolder(newHolder, appView.dexItemFactory()) : original;
   }
 
@@ -159,15 +177,28 @@
     }
     DexClass fieldHolder = field.getHolder();
     DexType newHolder =
-        fieldHolder.isInterface()
-            ? firstLibraryClassForInterfaceTarget(
-                definitions, field.getReference(), original.getHolderType(), DexClass::lookupField)
-            : firstLibraryClass(definitions, original.getHolderType());
+        firstLibraryClassOrFirstInterfaceTarget(
+            fieldHolder,
+            definitions,
+            field.getReference(),
+            original.getHolderType(),
+            DexClass::lookupField);
     return newHolder != null
         ? original.withHolder(newHolder, definitions.dexItemFactory())
         : original;
   }
 
+  private static <T> DexType firstLibraryClassOrFirstInterfaceTarget(
+      DexClass holder,
+      DexDefinitionSupplier definitions,
+      T target,
+      DexType current,
+      BiFunction<DexClass, T, ?> lookup) {
+    return holder.isInterface()
+        ? firstLibraryClassForInterfaceTarget(definitions, target, current, lookup)
+        : firstLibraryClass(definitions, current);
+  }
+
   private static <T> DexType firstLibraryClassForInterfaceTarget(
       DexDefinitionSupplier definitions,
       T target,
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
index 8d8962e..81c4ae1 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -100,7 +100,10 @@
   }
 
   @Override
-  public DexField getRenamedFieldSignature(DexField originalField) {
+  public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) {
+    if (this == codeLens) {
+      return originalField;
+    }
     return getPrevious().getRenamedFieldSignature(originalField);
   }
 
@@ -115,13 +118,17 @@
   }
 
   @Override
-  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+  public DexMethod getPreviousMethodSignature(DexMethod method) {
     return method;
   }
 
   @Override
-  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
-    return getPrevious().lookupPrototypeChangesForMethodDefinition(method);
+  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+      DexMethod method, GraphLens codeLens) {
+    if (this == codeLens) {
+      return getIdentityLens().lookupPrototypeChangesForMethodDefinition(method, codeLens);
+    }
+    return getPrevious().lookupPrototypeChangesForMethodDefinition(method, codeLens);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
index e17606f..da5a6d5 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
@@ -68,7 +68,10 @@
   }
 
   @Override
-  public DexField getRenamedFieldSignature(DexField originalField) {
+  public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) {
+    if (this == codeLens) {
+      return originalField;
+    }
     return getPrevious().getRenamedFieldSignature(originalField);
   }
 
@@ -80,8 +83,12 @@
   }
 
   @Override
-  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
-    return getPrevious().lookupPrototypeChangesForMethodDefinition(method);
+  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+      DexMethod method, GraphLens codeLens) {
+    if (this == codeLens) {
+      return getIdentityLens().lookupPrototypeChangesForMethodDefinition(method, codeLens);
+    }
+    return getPrevious().lookupPrototypeChangesForMethodDefinition(method, codeLens);
   }
 
   @Override
@@ -127,7 +134,7 @@
   }
 
   @Override
-  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+  public DexMethod getPreviousMethodSignature(DexMethod method) {
     return method;
   }
 
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 5f43dd3..4b8bff6 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
@@ -206,7 +206,6 @@
             appView,
             immediateSubtypingInfo,
             codeScannerResult,
-            reprocessingCriteriaCollection,
             stronglyConnectedProgramComponents,
             interfaceDispatchOutsideProgram)
         .populateOptimizationInfo(converter, executorService, timing);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
index d8757d1..85bef2b 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
@@ -14,6 +14,9 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.MethodCollection;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
@@ -75,8 +78,14 @@
   }
 
   private void fixupFields(DexProgramClass clazz) {
-    clazz.setInstanceFields(fixupFields(clazz.instanceFields()));
-    clazz.setStaticFields(fixupFields(clazz.staticFields()));
+    clazz.setInstanceFields(
+        fixupFields(
+            clazz.instanceFields(),
+            builder -> builder.setGenericSignature(FieldTypeSignature.noSignature())));
+    clazz.setStaticFields(
+        fixupFields(
+            clazz.staticFields(),
+            builder -> builder.setGenericSignature(FieldTypeSignature.noSignature())));
   }
 
   private void fixupMethods(DexProgramClass clazz) {
@@ -97,8 +106,9 @@
                 if (graphLens.hasPrototypeChanges(methodReferenceAfterParameterRemoval)) {
                   RewrittenPrototypeDescription prototypeChanges =
                       graphLens.getPrototypeChanges(methodReferenceAfterParameterRemoval);
-                  builder.apply(prototypeChanges.createParameterAnnotationsRemover(method));
-
+                  builder
+                      .apply(prototypeChanges.createParameterAnnotationsRemover(method))
+                      .setGenericSignature(MethodTypeSignature.noSignature());
                   if (method.isInstance()
                       && prototypeChanges.getArgumentInfoCollection().isArgumentRemoved(0)) {
                     builder
@@ -111,6 +121,7 @@
   }
 
   private void fixupOptimizationInfos(ExecutorService executorService) throws ExecutionException {
+    GraphLens codeLens = graphLens.getPrevious();
     PrunedItems prunedItems = PrunedItems.empty(appView.app());
     getSimpleFeedback()
         .fixupOptimizationInfos(
@@ -120,7 +131,7 @@
               @Override
               public void fixup(
                   DexEncodedField field, MutableFieldOptimizationInfo optimizationInfo) {
-                optimizationInfo.fixupAbstractValue(appView, graphLens);
+                optimizationInfo.fixupAbstractValue(appView, graphLens, codeLens);
               }
 
               @Override
@@ -129,8 +140,8 @@
                 // Fixup the return value in case the method returns a field that had its signature
                 // changed.
                 optimizationInfo
-                    .fixupAbstractReturnValue(appView, graphLens)
-                    .fixupInstanceInitializerInfo(appView, graphLens, prunedItems);
+                    .fixupAbstractReturnValue(appView, graphLens, codeLens)
+                    .fixupInstanceInitializerInfo(appView, graphLens, codeLens, prunedItems);
 
                 // Rewrite the optimization info to account for method signature changes.
                 if (graphLens.hasPrototypeChanges(method.getReference())) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
index 0955d0a..866fdd3 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
@@ -45,7 +45,7 @@
   }
 
   public boolean isAffected(DexMethod method) {
-    return method != internalGetPreviousMethodSignature(method) || hasPrototypeChanges(method);
+    return method != getPreviousMethodSignature(method) || hasPrototypeChanges(method);
   }
 
   @Override
@@ -70,7 +70,7 @@
   @Override
   protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
       RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
-    DexMethod previous = internalGetPreviousMethodSignature(method);
+    DexMethod previous = getPreviousMethodSignature(method);
     if (!hasPrototypeChanges(method)) {
       return prototypeChanges;
     }
@@ -83,8 +83,8 @@
   }
 
   @Override
-  public DexMethod internalGetPreviousMethodSignature(DexMethod method) {
-    return super.internalGetPreviousMethodSignature(method);
+  public DexMethod getPreviousMethodSignature(DexMethod method) {
+    return super.getPreviousMethodSignature(method);
   }
 
   @Override
@@ -177,7 +177,8 @@
       for (Entry<DexMethod, RewrittenPrototypeDescription> entry : prototypeChanges.entrySet()) {
         RewrittenPrototypeDescription prototypeChangesForMethod = entry.getValue();
         RewrittenPrototypeDescription rewrittenPrototypeChangesForMethod =
-            prototypeChangesForMethod.rewrittenWithLens(appView, argumentPropagatorGraphLens);
+            prototypeChangesForMethod.rewrittenWithLens(
+                appView, argumentPropagatorGraphLens, argumentPropagatorGraphLens.getPrevious());
         if (rewrittenPrototypeChangesForMethod != prototypeChangesForMethod) {
           entry.setValue(rewrittenPrototypeChangesForMethod);
         }
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 f9bfde1..f8d617b 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
@@ -13,19 +13,16 @@
 import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
-import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeParameterState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeParameterState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteParameterState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeParameterState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
@@ -33,14 +30,11 @@
 import com.android.tools.r8.optimize.argumentpropagation.propagation.InParameterFlowPropagator;
 import com.android.tools.r8.optimize.argumentpropagation.propagation.InterfaceMethodArgumentPropagator;
 import com.android.tools.r8.optimize.argumentpropagation.propagation.VirtualDispatchMethodArgumentPropagator;
-import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.ArgumentPropagatorReprocessingCriteriaCollection;
 import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Iterables;
-import java.util.BitSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -56,7 +50,6 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final MethodStateCollectionByReference methodStates;
-  private final ArgumentPropagatorReprocessingCriteriaCollection reprocessingCriteriaCollection;
 
   private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
   private final List<Set<DexProgramClass>> stronglyConnectedProgramComponents;
@@ -68,13 +61,11 @@
       AppView<AppInfoWithLiveness> appView,
       ImmediateProgramSubtypingInfo immediateSubtypingInfo,
       MethodStateCollectionByReference methodStates,
-      ArgumentPropagatorReprocessingCriteriaCollection reprocessingCriteriaCollection,
       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;
   }
@@ -172,7 +163,6 @@
     }
 
     methodState = getMethodStateAfterUninstantiatedParameterRemoval(method, methodState);
-    methodState = getMethodStateAfterUnusedParameterRemoval(method, methodState);
 
     if (methodState.isUnknown()) {
       // Nothing is known about the arguments to this method.
@@ -254,57 +244,6 @@
         : methodState;
   }
 
-  private MethodState getMethodStateAfterUnusedParameterRemoval(
-      ProgramMethod method, MethodState methodState) {
-    assert methodState.isMonomorphic() || methodState.isUnknown();
-    if (!method.getOptimizationInfo().hasUnusedArguments()
-        || appView.appInfo().isKeepUnusedArgumentsMethod(method)) {
-      return methodState;
-    }
-
-    int numberOfArguments = method.getDefinition().getNumberOfArguments();
-    List<ParameterState> parameterStates =
-        methodState.isMonomorphic()
-            ? methodState.asMonomorphic().getParameterStates()
-            : ListUtils.newInitializedArrayList(numberOfArguments, ParameterState.unknown());
-
-    BitSet unusedArguments = method.getOptimizationInfo().getUnusedArguments();
-    for (int argumentIndex = method.getDefinition().getFirstNonReceiverArgumentIndex();
-        argumentIndex < numberOfArguments;
-        argumentIndex++) {
-      boolean isUnused = unusedArguments.get(argumentIndex);
-      if (isUnused) {
-        DexType argumentType = method.getArgumentType(argumentIndex);
-        parameterStates.set(argumentIndex, getUnusedParameterState(argumentType));
-      }
-    }
-
-    if (methodState.isUnknown()) {
-      if (!unusedArguments.get(0) || Iterables.any(parameterStates, ParameterState::isConcrete)) {
-        assert parameterStates.stream().anyMatch(ParameterState::isConcrete);
-        return new ConcreteMonomorphicMethodState(parameterStates);
-      }
-    }
-    return methodState;
-  }
-
-  private ParameterState getUnusedParameterState(DexType argumentType) {
-    if (argumentType.isArrayType()) {
-      // Ensure argument removal by simulating that this unused parameter is the constant null.
-      return new ConcreteArrayTypeParameterState(Nullability.definitelyNull());
-    } else if (argumentType.isClassType()) {
-      // Ensure argument removal by simulating that this unused parameter is the constant null.
-      return new ConcreteClassTypeParameterState(
-          appView.abstractValueFactory().createNullValue(), DynamicType.definitelyNull());
-    } else {
-      assert argumentType.isPrimitiveType();
-      // Ensure argument removal by simulating that this unused parameter is the constant zero.
-      // Note that the same zero value is used for all primitive types.
-      return new ConcretePrimitiveTypeParameterState(
-          appView.abstractValueFactory().createZeroValue());
-    }
-  }
-
   private boolean widenDynamicTypes(
       ProgramMethod method, ConcreteMonomorphicMethodState methodState) {
     for (int argumentIndex = 0;
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 0e68d70..c2912c0 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
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 
+import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -29,22 +30,27 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
+import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorGraphLens.Builder;
 import com.android.tools.r8.optimize.argumentpropagation.utils.ParameterRemovalUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepFieldInfo;
+import com.android.tools.r8.shaking.KeepMethodInfo;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.AccessUtils;
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
 import com.android.tools.r8.utils.collections.ProgramMethodMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -160,11 +166,12 @@
       Timing timing)
       throws ExecutionException {
     timing.begin("Optimize components");
+    ProcessorContext processorContext = appView.createProcessorContext();
     Collection<Builder> partialGraphLensBuilders =
         ThreadUtils.processItemsWithResults(
             stronglyConnectedProgramComponents,
             classes ->
-                new StronglyConnectedComponentOptimizer()
+                new StronglyConnectedComponentOptimizer(processorContext)
                     .optimize(
                         classes,
                         interfaceDispatchOutsideProgram.getOrDefault(
@@ -208,8 +215,10 @@
 
   public class StronglyConnectedComponentOptimizer {
 
-    private final DexItemFactory dexItemFactory;
-    private final InternalOptions options;
+    private final DexItemFactory dexItemFactory = appView.dexItemFactory();
+    private final InternalOptions options = appView.options();
+    private final CallSiteOptimizationOptions callSiteOptimizationOptions =
+        appView.options().callSiteOptimizationOptions();
 
     private final Map<DexMethodSignature, AllowedPrototypeChanges>
         allowedPrototypeChangesForVirtualMethods = new HashMap<>();
@@ -231,9 +240,10 @@
     private final Map<DexMethodSignature, Pair<AllowedPrototypeChanges, DexMethodSignature>>
         occupiedMethodSignatures = new HashMap<>();
 
-    public StronglyConnectedComponentOptimizer() {
-      this.dexItemFactory = appView.dexItemFactory();
-      this.options = appView.options();
+    private final ProcessorContext processorContext;
+
+    public StronglyConnectedComponentOptimizer(ProcessorContext processorContext) {
+      this.processorContext = processorContext;
     }
 
     // TODO(b/190154391): Strengthen the static type of parameters.
@@ -287,7 +297,9 @@
         clazz.forEachProgramMethodMatching(
             method -> !method.isInstanceInitializer(),
             method -> {
-              if (!appView.getKeepInfo(method).isShrinkingAllowed(options)) {
+              KeepMethodInfo keepInfo = appView.getKeepInfo(method);
+              if (!keepInfo.isOptimizationAllowed(options)
+                  || !keepInfo.isShrinkingAllowed(options)) {
                 pinnedMethodSignatures.add(method.getMethodSignature());
               }
             });
@@ -463,7 +475,7 @@
         }
         ProgramMethod method = methods.getFirst();
         return method.getOptimizationInfo().hasUnusedArguments()
-            && method.getOptimizationInfo().getUnusedArguments().get(0)
+            && method.getOptimizationInfo().getUnusedArguments().get(parameterIndex)
             && ParameterRemovalUtils.canRemoveUnusedParametersFrom(appView, method)
             && ParameterRemovalUtils.canRemoveUnusedParameter(appView, method, parameterIndex);
       }
@@ -472,6 +484,13 @@
           // OK, this parameter can be removed.
           continue;
         }
+        if (method.getOptimizationInfo().hasUnusedArguments()
+            && method.getOptimizationInfo().getUnusedArguments().get(parameterIndex)
+            && ParameterRemovalUtils.canRemoveUnusedParametersFrom(appView, method)
+            && ParameterRemovalUtils.canRemoveUnusedParameter(appView, method, parameterIndex)) {
+          // OK, this parameter is unused.
+          continue;
+        }
         CallSiteOptimizationInfo optimizationInfo = method.getOptimizationInfo().getArgumentInfos();
         if (optimizationInfo.isConcreteCallSiteOptimizationInfo()) {
           ConcreteCallSiteOptimizationInfo concreteOptimizationInfo =
@@ -550,14 +569,39 @@
             }
           });
       DexMethodSignatureSet instanceInitializerSignatures = DexMethodSignatureSet.create();
-      clazz.forEachProgramInstanceInitializer(instanceInitializerSignatures::add);
+      ProgramMethodMap<RewrittenPrototypeDescription> instanceInitializerPrototypeChanges =
+          ProgramMethodMap.create();
+      clazz.forEachProgramInstanceInitializer(
+          method -> {
+            RewrittenPrototypeDescription prototypeChanges =
+                computePrototypeChangesForDirectMethod(
+                    method, interfaceDispatchOutsideProgram, null);
+            if (prototypeChanges.isEmpty()) {
+              instanceInitializerSignatures.add(method);
+            }
+            instanceInitializerPrototypeChanges.put(method, prototypeChanges);
+          });
       clazz.forEachProgramMethod(
           method -> {
             RewrittenPrototypeDescription prototypeChanges =
-                method.getDefinition().belongsToDirectPool()
-                    ? computePrototypeChangesForDirectMethod(
-                        method, interfaceDispatchOutsideProgram, instanceInitializerSignatures)
-                    : computePrototypeChangesForVirtualMethod(method);
+                instanceInitializerPrototypeChanges.getOrDefault(
+                    method,
+                    () ->
+                        method.getDefinition().belongsToDirectPool()
+                            ? computePrototypeChangesForDirectMethod(
+                                method,
+                                interfaceDispatchOutsideProgram,
+                                instanceInitializerSignatures)
+                            : computePrototypeChangesForVirtualMethod(method));
+            if (method.getDefinition().isInstanceInitializer()) {
+              if (prototypeChanges.isEmpty()) {
+                assert instanceInitializerSignatures.contains(method);
+                return;
+              }
+              prototypeChanges =
+                  selectInitArgumentTypeForInstanceInitializer(
+                      method, prototypeChanges, instanceInitializerSignatures);
+            }
             DexMethod newMethodSignature = getNewMethodSignature(method, prototypeChanges);
             if (newMethodSignature != method.getReference()) {
               partialGraphLensBuilder.recordMove(
@@ -722,31 +766,54 @@
         DexMethodSignatureSet interfaceDispatchOutsideProgram,
         DexMethodSignatureSet instanceInitializerSignatures) {
       assert method.getDefinition().belongsToDirectPool();
-      if (!isPrototypeChangesAllowed(method, interfaceDispatchOutsideProgram)) {
-        return RewrittenPrototypeDescription.none();
-      }
-      // TODO(b/199864962): Allow parameter removal from check-not-null classified methods.
-      if (method
-          .getOptimizationInfo()
-          .getEnumUnboxerMethodClassification()
-          .isCheckNotNullClassification()) {
-        return RewrittenPrototypeDescription.none();
-      }
-      RewrittenPrototypeDescription prototypeChanges = computePrototypeChangesForMethod(method);
-      if (prototypeChanges.isEmpty()) {
-        return prototypeChanges;
-      }
-      if (method.getDefinition().isInstanceInitializer()) {
-        DexMethod rewrittenMethod =
-            prototypeChanges.getArgumentInfoCollection().rewriteMethod(method, dexItemFactory);
-        assert rewrittenMethod != method.getReference();
-        if (!instanceInitializerSignatures.add(rewrittenMethod)) {
-          return RewrittenPrototypeDescription.none();
-        }
+      RewrittenPrototypeDescription prototypeChanges =
+          isPrototypeChangesAllowed(method, interfaceDispatchOutsideProgram)
+              ? computePrototypeChangesForMethod(method)
+              : RewrittenPrototypeDescription.none();
+      if (method.getDefinition().isInstanceInitializer() && instanceInitializerSignatures != null) {
+        prototypeChanges =
+            selectInitArgumentTypeForInstanceInitializer(
+                method, prototypeChanges, instanceInitializerSignatures);
       }
       return prototypeChanges;
     }
 
+    private RewrittenPrototypeDescription selectInitArgumentTypeForInstanceInitializer(
+        ProgramMethod method,
+        RewrittenPrototypeDescription prototypeChanges,
+        DexMethodSignatureSet instanceInitializerSignatures) {
+      DexMethod rewrittenMethod = prototypeChanges.rewriteMethod(method, dexItemFactory);
+      if (instanceInitializerSignatures.add(rewrittenMethod)) {
+        return prototypeChanges;
+      }
+      if (!callSiteOptimizationOptions.isForceSyntheticsForInstanceInitializersEnabled()) {
+        for (DexType extraArgumentType :
+            ImmutableList.of(dexItemFactory.intType, dexItemFactory.objectType)) {
+          RewrittenPrototypeDescription candidatePrototypeChanges =
+              prototypeChanges.withExtraParameters(new ExtraUnusedNullParameter(extraArgumentType));
+          rewrittenMethod = candidatePrototypeChanges.rewriteMethod(method, dexItemFactory);
+          if (instanceInitializerSignatures.add(rewrittenMethod)) {
+            return candidatePrototypeChanges;
+          }
+        }
+      }
+      DexType extraArgumentType =
+          appView
+              .getSyntheticItems()
+              .createClass(
+                  SyntheticKind.NON_FIXED_INIT_TYPE_ARGUMENT,
+                  processorContext.createMethodProcessingContext(method).createUniqueContext(),
+                  appView)
+              .getType();
+      RewrittenPrototypeDescription finalPrototypeChanges =
+          prototypeChanges.withExtraParameters(new ExtraUnusedNullParameter(extraArgumentType));
+      boolean added =
+          instanceInitializerSignatures.add(
+              finalPrototypeChanges.rewriteMethod(method, dexItemFactory));
+      assert added;
+      return finalPrototypeChanges;
+    }
+
     private RewrittenPrototypeDescription computePrototypeChangesForVirtualMethod(
         ProgramMethod method) {
       AllowedPrototypeChanges allowedPrototypeChanges =
@@ -933,41 +1000,54 @@
           && ParameterRemovalUtils.canRemoveUnusedParametersFrom(appView, method)
           && ParameterRemovalUtils.canRemoveUnusedParameter(appView, method, 0)) {
         parameterChangesBuilder.addArgumentInfo(
-            0, RemovedArgumentInfo.builder().setType(method.getHolderType()).build());
+            0,
+            RemovedArgumentInfo.builder()
+                .setCheckNullOrZero(true)
+                .setType(method.getHolderType())
+                .build());
       }
 
-      ConcreteCallSiteOptimizationInfo optimizationInfo =
-          method.getOptimizationInfo().getArgumentInfos().asConcreteCallSiteOptimizationInfo();
-      if (optimizationInfo != null) {
-        for (int argumentIndex = method.getDefinition().getFirstNonReceiverArgumentIndex();
-            argumentIndex < method.getDefinition().getNumberOfArguments();
-            argumentIndex++) {
-          if (removableParameterIndices.test(argumentIndex)) {
-            AbstractValue abstractValue = optimizationInfo.getAbstractArgumentValue(argumentIndex);
-            if (abstractValue.isSingleValue()
-                && abstractValue.asSingleValue().isMaterializableInContext(appView, method)) {
-              parameterChangesBuilder.addArgumentInfo(
-                  argumentIndex,
-                  RemovedArgumentInfo.builder()
-                      .setSingleValue(abstractValue.asSingleValue())
-                      .setType(method.getArgumentType(argumentIndex))
-                      .build());
-              continue;
-            }
-          }
-
-          DexType dynamicType = newParameterTypes.apply(argumentIndex);
-          if (dynamicType != null) {
-            DexType staticType = method.getArgumentType(argumentIndex);
-            assert dynamicType != staticType;
+      CallSiteOptimizationInfo optimizationInfo = method.getOptimizationInfo().getArgumentInfos();
+      for (int argumentIndex = method.getDefinition().getFirstNonReceiverArgumentIndex();
+          argumentIndex < method.getDefinition().getNumberOfArguments();
+          argumentIndex++) {
+        if (removableParameterIndices.test(argumentIndex)) {
+          if (method.getOptimizationInfo().hasUnusedArguments()
+              && method.getOptimizationInfo().getUnusedArguments().get(argumentIndex)
+              && ParameterRemovalUtils.canRemoveUnusedParametersFrom(appView, method)
+              && ParameterRemovalUtils.canRemoveUnusedParameter(appView, method, argumentIndex)) {
             parameterChangesBuilder.addArgumentInfo(
                 argumentIndex,
-                RewrittenTypeInfo.builder()
-                    .setCastType(dynamicType)
-                    .setOldType(staticType)
-                    .setNewType(dynamicType)
+                RemovedArgumentInfo.builder()
+                    .setType(method.getArgumentType(argumentIndex))
                     .build());
+            continue;
           }
+
+          AbstractValue abstractValue = optimizationInfo.getAbstractArgumentValue(argumentIndex);
+          if (abstractValue.isSingleValue()
+              && abstractValue.asSingleValue().isMaterializableInContext(appView, method)) {
+            parameterChangesBuilder.addArgumentInfo(
+                argumentIndex,
+                RemovedArgumentInfo.builder()
+                    .setSingleValue(abstractValue.asSingleValue())
+                    .setType(method.getArgumentType(argumentIndex))
+                    .build());
+            continue;
+          }
+        }
+
+        DexType dynamicType = newParameterTypes.apply(argumentIndex);
+        if (dynamicType != null) {
+          DexType staticType = method.getArgumentType(argumentIndex);
+          assert dynamicType != staticType;
+          parameterChangesBuilder.addArgumentInfo(
+              argumentIndex,
+              RewrittenTypeInfo.builder()
+                  .setCastType(dynamicType)
+                  .setOldType(staticType)
+                  .setNewType(dynamicType)
+                  .build());
         }
       }
       return parameterChangesBuilder.build();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ParameterRemovalUtils.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ParameterRemovalUtils.java
index 0d16b37..72cbfba 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ParameterRemovalUtils.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ParameterRemovalUtils.java
@@ -19,6 +19,9 @@
     if (!keepInfo.isParameterRemovalAllowed(options)) {
       return false;
     }
+    if (appView.appInfo().isKeepUnusedArgumentsMethod(method)) {
+      return false;
+    }
     return !appView.appInfoWithLiveness().isMethodTargetedByInvokeDynamic(method);
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java
index c387c1c..1ded13b 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java
@@ -33,7 +33,7 @@
   }
 
   @Override
-  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+  public DexMethod getPreviousMethodSignature(DexMethod method) {
     Set<DexMethod> bridges = bridgeToHoistedBridgeMap.getKeys(method);
     return bridges.isEmpty() ? method : bridges.iterator().next();
   }
@@ -54,13 +54,20 @@
   }
 
   @Override
-  public DexField getRenamedFieldSignature(DexField originalField) {
+  public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) {
+    if (this == codeLens) {
+      return originalField;
+    }
     return getPrevious().getRenamedFieldSignature(originalField);
   }
 
   @Override
-  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
-    return getPrevious().lookupPrototypeChangesForMethodDefinition(method);
+  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+      DexMethod method, GraphLens codeLens) {
+    if (this == codeLens) {
+      return getIdentityLens().lookupPrototypeChangesForMethodDefinition(method, codeLens);
+    }
+    return getPrevious().lookupPrototypeChangesForMethodDefinition(method, codeLens);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index d38df4c..71d4807 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -1632,7 +1632,7 @@
   }
 
   public SubtypingInfo computeSubtypingInfo() {
-    return new SubtypingInfo(this);
+    return SubtypingInfo.create(this);
   }
 
   public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(ClassTypeElement type) {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 68dba3e..33b077d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -608,16 +608,17 @@
   }
 
   private void recordTypeReference(DexType type, ProgramDefinition context) {
-    recordTypeReference(type, context, this::reportMissingClass);
+    recordTypeReference(type, context, this::recordNonProgramClass, this::reportMissingClass);
   }
 
   private void recordTypeReference(DexType type, ProgramDerivedContext context) {
-    recordTypeReference(type, context, this::reportMissingClass);
+    recordTypeReference(type, context, this::recordNonProgramClass, this::reportMissingClass);
   }
 
   private void recordTypeReference(
       DexType type,
       ProgramDerivedContext context,
+      BiConsumer<DexClass, ProgramDerivedContext> foundClassConsumer,
       BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer) {
     if (type == null) {
       return;
@@ -629,21 +630,22 @@
       return;
     }
     // Lookup the definition, ignoring the result. This populates the missing and referenced sets.
-    definitionFor(type, context, missingClassConsumer);
+    definitionFor(type, context, foundClassConsumer, missingClassConsumer);
   }
 
   private void recordMethodReference(DexMethod method, ProgramDerivedContext context) {
-    recordMethodReference(method, context, this::reportMissingClass);
+    recordMethodReference(method, context, this::recordNonProgramClass, this::reportMissingClass);
   }
 
   private void recordMethodReference(
       DexMethod method,
       ProgramDerivedContext context,
+      BiConsumer<DexClass, ProgramDerivedContext> foundClassConsumer,
       BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer) {
-    recordTypeReference(method.holder, context, missingClassConsumer);
-    recordTypeReference(method.proto.returnType, context, missingClassConsumer);
+    recordTypeReference(method.holder, context, foundClassConsumer, missingClassConsumer);
+    recordTypeReference(method.proto.returnType, context, foundClassConsumer, missingClassConsumer);
     for (DexType type : method.proto.parameters.values) {
-      recordTypeReference(type, context, missingClassConsumer);
+      recordTypeReference(type, context, foundClassConsumer, missingClassConsumer);
     }
   }
 
@@ -661,31 +663,28 @@
   }
 
   public DexClass definitionFor(DexType type, ProgramDefinition context) {
-    return definitionFor(type, context, this::reportMissingClass);
+    return definitionFor(type, context, this::recordNonProgramClass, this::reportMissingClass);
   }
 
   private DexClass definitionFor(
       DexType type,
       ProgramDerivedContext context,
+      BiConsumer<DexClass, ProgramDerivedContext> foundClassConsumer,
       BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer) {
-    return internalDefinitionFor(type, context, missingClassConsumer);
+    return internalDefinitionFor(type, context, foundClassConsumer, missingClassConsumer);
   }
 
   private DexClass internalDefinitionFor(
       DexType type,
       ProgramDerivedContext context,
+      BiConsumer<DexClass, ProgramDerivedContext> foundClassConsumer,
       BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer) {
     DexClass clazz = appInfo().definitionFor(type);
     if (clazz == null) {
       missingClassConsumer.accept(type, context);
       return null;
     }
-    if (clazz.isNotProgramClass()) {
-      addLiveNonProgramType(
-          clazz.asClasspathOrLibraryClass(),
-          (missingType, derivedContext) ->
-              reportMissingClass(missingType, derivedContext.asProgramDerivedContext(context)));
-    }
+    foundClassConsumer.accept(clazz, context);
     return clazz;
   }
 
@@ -772,7 +771,8 @@
   private DexProgramClass getProgramClassOrNullFromReflectiveAccess(
       DexType type, ProgramDefinition context) {
     // To avoid that we report reflectively accessed types as missing.
-    DexClass clazz = definitionFor(type, context, this::ignoreMissingClass);
+    DexClass clazz =
+        definitionFor(type, context, this::recordNonProgramClass, this::ignoreMissingClass);
     return clazz != null && clazz.isProgramClass() ? clazz.asProgramClass() : null;
   }
 
@@ -1805,8 +1805,16 @@
               ? this::reportMissingClass
               : this::ignoreMissingClass;
       for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
-        recordTypeReference(innerClassAttribute.getInner(), clazz, missingClassConsumer);
-        recordTypeReference(innerClassAttribute.getOuter(), clazz, missingClassConsumer);
+        recordTypeReference(
+            innerClassAttribute.getInner(),
+            clazz,
+            this::recordNonProgramClass,
+            missingClassConsumer);
+        recordTypeReference(
+            innerClassAttribute.getOuter(),
+            clazz,
+            this::recordNonProgramClass,
+            missingClassConsumer);
       }
     }
 
@@ -1828,10 +1836,12 @@
               ? this::reportMissingClass
               : this::ignoreMissingClass;
       if (enclosingMethod != null) {
-        recordMethodReference(enclosingMethod, clazz, missingClassConsumer);
+        recordMethodReference(
+            enclosingMethod, clazz, this::recordNonProgramClass, missingClassConsumer);
       } else {
         DexType enclosingClass = enclosingMethodAttribute.getEnclosingClass();
-        recordTypeReference(enclosingClass, clazz, missingClassConsumer);
+        recordTypeReference(
+            enclosingClass, clazz, this::recordNonProgramClass, missingClassConsumer);
       }
     }
 
@@ -2072,11 +2082,13 @@
   private SingleResolutionResult resolveMethod(
       DexMethod method, ProgramDefinition context, KeepReason reason) {
     // Record the references in case they are not program types.
-    recordMethodReference(method, context);
     MethodResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
     if (resolutionResult.isFailedResolution()) {
       markFailedMethodResolutionTargets(
           method, resolutionResult.asFailedResolution(), context, reason);
+      recordMethodReference(method, context, this::recordFoundClass, this::reportMissingClass);
+    } else {
+      recordMethodReference(method, context);
     }
     return resolutionResult.asSingleResolution();
   }
@@ -2092,7 +2104,7 @@
       assert resolutionResult.isFailedResolution();
       markFailedMethodResolutionTargets(
           method, resolutionResult.asFailedResolution(), context, reason);
-      recordMethodReference(method, context);
+      recordMethodReference(method, context, this::recordFoundClass, this::reportMissingClass);
     }
     return resolutionResult.asSingleResolution();
   }
@@ -2327,6 +2339,25 @@
         });
   }
 
+  private void recordFoundClass(DexClass clazz, ProgramDerivedContext context) {
+    if (clazz.isProgramClass()) {
+      if (context.isProgramContext()) {
+        markTypeAsLive(clazz, context.getContext().asProgramDefinition());
+      }
+    } else {
+      recordNonProgramClass(clazz, context);
+    }
+  }
+
+  private void recordNonProgramClass(DexClass clazz, ProgramDerivedContext context) {
+    if (!clazz.isProgramClass()) {
+      addLiveNonProgramType(
+          clazz.asClasspathOrLibraryClass(),
+          (missingType, derivedContext) ->
+              reportMissingClass(missingType, derivedContext.asProgramDerivedContext(context)));
+    }
+  }
+
   private void ignoreMissingClass(DexType clazz) {
     missingClassesBuilder.ignoreNewMissingClass(clazz);
   }
@@ -2968,19 +2999,18 @@
       return;
     }
 
-    // Note that all virtual methods derived from library methods are kept regardless of being
-    // reachable, so the following only needs to consider reachable targets in the program.
-    // TODO(b/70160030): Revise this to support tree shaking library methods on non-escaping types.
-    DexProgramClass holder = getProgramClassOrNull(method.holder, context);
-    if (holder == null) {
-      // TODO(b/139464956): clean this.
-      // Ensure that the full proto of the targeted method is referenced.
-      recordMethodReference(method, context);
+    SingleResolutionResult resolution = resolveMethod(method, context, reason, interfaceInvoke);
+    if (resolution == null) {
       return;
     }
 
-    SingleResolutionResult resolution = resolveMethod(method, context, reason, interfaceInvoke);
-    if (resolution == null) {
+    // Note that all virtual methods derived from library methods are kept regardless of being
+    // reachable, so the following only needs to consider reachable targets in the program.
+    // TODO(b/70160030): Revise this to support tree shaking library methods on non-escaping types.
+    DexProgramClass initialResolutionHolder =
+        resolution.getInitialResolutionHolder().asProgramClass();
+    if (initialResolutionHolder == null) {
+      recordMethodReference(method, context);
       return;
     }
 
@@ -2996,7 +3026,8 @@
     // If the method has already been marked, just report the new reason for the resolved target and
     // save the context to ensure correct lookup of virtual dispatch targets.
     ResolutionSearchKey resolutionSearchKey = new ResolutionSearchKey(method, interfaceInvoke);
-    ProgramMethodSet seenContexts = getReachableVirtualTargets(holder).get(resolutionSearchKey);
+    ProgramMethodSet seenContexts =
+        getReachableVirtualTargets(initialResolutionHolder).get(resolutionSearchKey);
     if (seenContexts != null) {
       seenContexts.add(context);
       graphReporter.registerMethod(resolution.getResolvedMethod(), reason);
@@ -3019,7 +3050,7 @@
 
     // The method resolved and is accessible, so currently live overrides become live.
     reachableVirtualTargets
-        .computeIfAbsent(holder, ignoreArgument(HashMap::new))
+        .computeIfAbsent(initialResolutionHolder, ignoreArgument(HashMap::new))
         .computeIfAbsent(resolutionSearchKey, ignoreArgument(ProgramMethodSet::create))
         .add(context);
 
@@ -3544,7 +3575,7 @@
     // Commit the pending synthetics and recompute subtypes.
     appInfo = appInfo.rebuildWithClassHierarchy(app -> app);
     appView.setAppInfo(appInfo);
-    subtypingInfo = new SubtypingInfo(appView);
+    subtypingInfo = SubtypingInfo.create(appView);
 
     // Finally once all synthesized items "exist" it is now safe to continue tracing. The new work
     // items are enqueued and the fixed point will continue once this subroutine returns.
@@ -4066,7 +4097,7 @@
     // Commit the pending synthetics and recompute subtypes.
     appInfo = appInfo.rebuildWithClassHierarchy(app -> app);
     appView.setAppInfo(appInfo);
-    subtypingInfo = new SubtypingInfo(appView);
+    subtypingInfo = SubtypingInfo.create(appView);
 
     syntheticAdditions.enqueueWorkItems(this);
 
@@ -4987,7 +5018,8 @@
     }
 
     public DexClass definitionFor(DexType type, ProgramDefinition context) {
-      return enqueuer.definitionFor(type, context, enqueuer::ignoreMissingClass);
+      return enqueuer.definitionFor(
+          type, context, enqueuer::recordNonProgramClass, enqueuer::ignoreMissingClass);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 98ffa2a..8596642 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -237,7 +237,6 @@
               // Fields that are javac inlined are unsound as predicates for conditional rules.
               // Ignore any such field members and record it for possible reporting later.
               if (isFieldInlinedByJavaC(f)) {
-                f.markAsInlinableByJavaC();
                 fieldsInlinedByJavaC.add(DexClassAndField.create(targetClass, f));
                 return false;
               }
@@ -317,21 +316,9 @@
     if (enqueuer.getMode().isFinalTreeShaking()) {
       // Ignore any field value in the final tree shaking pass so it remains consistent with the
       // initial pass.
-      return field.isInlinableByJavaC();
+      return field.getIsInlinableByJavaC();
     }
-    if (!field.isStatic() || !field.isFinal()) {
-      return false;
-    }
-    if (!field.hasExplicitStaticValue()) {
-      return false;
-    }
-    if (field.getType().isPrimitiveType()) {
-      return true;
-    }
-    if (field.getType() != appView.dexItemFactory().stringType) {
-      return false;
-    }
-    return field.getStaticValue().isDexValueString();
+    return field.getOrComputeIsInlinableByJavaC(appView.dexItemFactory());
   }
 
   private void materializeIfRule(ProguardIfRule rule, Set<DexReference> preconditions) {
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 9629fb2..2f7d3f4 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -62,6 +62,7 @@
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.OriginWithPosition;
 import com.android.tools.r8.utils.PredicateSet;
+import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
@@ -1299,13 +1300,7 @@
             throw new Unreachable();
         }
       } else if (context instanceof ProguardIdentifierNameStringRule) {
-        if (item.isField()) {
-          identifierNameStrings.add(item.asField().getReference());
-          context.markAsUsed();
-        } else if (item.isMethod()) {
-          identifierNameStrings.add(item.asMethod().getReference());
-          context.markAsUsed();
-        }
+        evaluateIdentifierNameStringRule(item, context, ifRule);
       } else if (context instanceof ConstantArgumentRule) {
         if (item.isMethod()) {
           keepParametersWithConstantValue.add(item.asMethod().getReference());
@@ -1559,6 +1554,36 @@
       }
     }
 
+    private void evaluateIdentifierNameStringRule(
+        Definition item, ProguardConfigurationRule context, ProguardIfRule ifRule) {
+      // Main dex rules should not contain -identifiernamestring rules.
+      assert !isMainDexRootSetBuilder();
+
+      // We don't expect -identifiernamestring rules to be used as the subsequent of -if rules.
+      assert ifRule == null;
+
+      if (item.isClass()) {
+        return;
+      }
+
+      if (item.isField()) {
+        DexClassAndField field = item.asField();
+        if (field.getDefinition().getOrComputeIsInlinableByJavaC(appView.dexItemFactory())) {
+          Reporter reporter = appView.reporter();
+          reporter.warning(
+              new StringDiagnostic(
+                  "Rule matches the static final field `"
+                      + field.toSourceString()
+                      + "`, which may have been inlined: "
+                      + context.toString(),
+                  context.getOrigin()));
+        }
+      }
+
+      identifierNameStrings.add(item.asMember().getReference());
+      context.markAsUsed();
+    }
+
     private boolean isInterfaceMethodNeedingDesugaring(ProgramDefinition item) {
       return options.isInterfaceMethodDesugaringEnabled()
           && item.isMethod()
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 a7362e5..19f0adc 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -324,9 +324,8 @@
 
     // The set of targets that must remain for proper resolution error cases should not be merged.
     // TODO(b/192821424): Can be removed if handled.
-    for (DexMethod method : appInfo.getFailedMethodResolutionTargets()) {
-      markTypeAsPinned(method.holder, AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE);
-    }
+    extractPinnedItems(
+        appInfo.getFailedMethodResolutionTargets(), AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE);
   }
 
   private <T extends DexReference> void extractPinnedItems(Iterable<T> items, AbortReason reason) {
@@ -1129,7 +1128,7 @@
         return false;
       }
 
-      // Rewrite generic signatures before we merge fields.
+      // Rewrite generic signatures before we merge a base with a generic signature.
       rewriteGenericSignatures(target, source, directMethods.values(), virtualMethods.values());
 
       // Convert out of DefaultInstanceInitializerCode, since this piece of code will require lens
@@ -1333,16 +1332,18 @@
         assert false : "Type should be present in generic signature";
         return null;
       }
+      Map<String, FieldTypeSignature> substitutionMap = new HashMap<>();
       List<FormalTypeParameter> formals = source.getClassSignature().getFormalTypeParameters();
       if (genericArgumentsToSuperType.size() != formals.size()) {
-        // TODO(b/214509535): Correctly rewrite signature when type arguments is empty.
-        assert genericArgumentsToSuperType.isEmpty() : "Invalid argument count to formals";
-        return null;
-      }
-      Map<String, FieldTypeSignature> substitutionMap = new HashMap<>();
-      for (int i = 0; i < formals.size(); i++) {
-        // It is OK to override a generic type variable so we just use put.
-        substitutionMap.put(formals.get(i).getName(), genericArgumentsToSuperType.get(i));
+        if (!genericArgumentsToSuperType.isEmpty()) {
+          assert false : "Invalid argument count to formals";
+          return null;
+        }
+      } else {
+        for (int i = 0; i < formals.size(); i++) {
+          // It is OK to override a generic type variable so we just use put.
+          substitutionMap.put(formals.get(i).getName(), genericArgumentsToSuperType.get(i));
+        }
       }
       return GenericSignaturePartialTypeArgumentApplier.build(
           appView,
@@ -1512,6 +1513,7 @@
               .setApiLevelForDefinition(method.getApiLevelForDefinition())
               .setApiLevelForCode(method.getApiLevelForDefinition())
               .setIsLibraryMethodOverride(method.isLibraryMethodOverride())
+              .setGenericSignature(method.getGenericSignature())
               .build();
       if (method.accessFlags.isPromotedToPublic()) {
         // The bridge is now the public method serving the role of the original method, and should
@@ -1966,7 +1968,7 @@
     }
 
     @Override
-    public DexField getRenamedFieldSignature(DexField originalField) {
+    public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) {
       throw new Unreachable();
     }
 
@@ -1981,7 +1983,7 @@
     }
 
     @Override
-    protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+    public DexMethod getPreviousMethodSignature(DexMethod method) {
       throw new Unreachable();
     }
 
@@ -2022,7 +2024,7 @@
 
     @Override
     public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
-        DexMethod method) {
+        DexMethod method, GraphLens codeLens) {
       throw new Unreachable();
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
index 21af6a8..37965ec 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
@@ -86,6 +86,11 @@
   }
 
   @Override
+  public boolean isVerticalClassMergerLens() {
+    return true;
+  }
+
+  @Override
   protected Iterable<DexType> internalGetOriginalTypes(DexType previous) {
     Collection<DexType> originalTypes = mergedClasses.getSourcesFor(previous);
     Iterable<DexType> currentType = IterableUtils.singleton(previous);
@@ -130,8 +135,8 @@
   }
 
   @Override
-  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
-    return super.internalGetPreviousMethodSignature(
+  public DexMethod getPreviousMethodSignature(DexMethod method) {
+    return super.getPreviousMethodSignature(
         originalMethodSignaturesForBridges.getOrDefault(method, method));
   }
 
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 36c6573..574f518 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -109,8 +109,7 @@
     }
     DexType thisType = getHolder().getType();
     DexType otherType = other.getHolder().getType();
-    if (getKind().isFixedSuffixSynthetic) {
-      // Fixed synthetics are non-shareable. Ordered by their unique type.
+    if (!getKind().isShareable()) {
       return thisType.compareTo(otherType);
     }
     if (includeContext) {
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 a29bfcf..0cb0137 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -32,6 +31,7 @@
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -419,13 +419,15 @@
 
   // Addition and creation of synthetic items.
 
-  private DexProgramClass internalEnsureDexProgramClass(
+  private DexProgramClass internalEnsureFixedProgramClass(
       SyntheticKind kind,
       Consumer<SyntheticProgramClassBuilder> classConsumer,
       Consumer<DexProgramClass> onCreationConsumer,
       SynthesizingContext outerContext,
-      DexType type,
       AppView<?> appView) {
+    Function<SynthesizingContext, DexType> contextToType =
+        c -> SyntheticNaming.createFixedType(kind, c, appView.dexItemFactory());
+    DexType type = contextToType.apply(outerContext);
     // Fast path is that the synthetic is already present. If so it must be a program class.
     DexClass dexClass = appView.definitionFor(type);
     if (dexClass != null) {
@@ -452,7 +454,8 @@
               },
               outerContext,
               type,
-              appView.dexItemFactory());
+              contextToType,
+              appView);
       onCreationConsumer.accept(dexProgramClass);
       return dexProgramClass;
     }
@@ -463,15 +466,38 @@
       Consumer<SyntheticProgramClassBuilder> fn,
       SynthesizingContext outerContext,
       DexType type,
-      DexItemFactory factory) {
+      Function<SynthesizingContext, DexType> contextToType,
+      AppView<?> appView) {
+    registerSyntheticTypeRewriting(outerContext, contextToType, appView, type);
     SyntheticProgramClassBuilder classBuilder =
-        new SyntheticProgramClassBuilder(type, kind, outerContext, factory);
+        new SyntheticProgramClassBuilder(type, kind, outerContext, appView.dexItemFactory());
     fn.accept(classBuilder);
     DexProgramClass clazz = classBuilder.build();
     addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
     return clazz;
   }
 
+  private void registerSyntheticTypeRewriting(
+      SynthesizingContext outerContext,
+      Function<SynthesizingContext, DexType> contextToType,
+      AppView<?> appView,
+      DexType type) {
+    DexType rewrittenContextType =
+        appView.rewritePrefix.rewrittenContextType(
+            outerContext.getSynthesizingContextType(), appView);
+    if (rewrittenContextType == null) {
+      return;
+    }
+    SynthesizingContext synthesizingContext = SynthesizingContext.fromType(rewrittenContextType);
+    DexType rewrittenType = contextToType.apply(synthesizingContext);
+    appView.rewritePrefix.rewriteType(type, rewrittenType);
+  }
+
+  public DexProgramClass createClass(
+      SyntheticKind kind, UniqueContext context, AppView<?> appView) {
+    return createClass(kind, context, appView, ConsumerUtils.emptyConsumer());
+  }
+
   public DexProgramClass createClass(
       SyntheticKind kind,
       UniqueContext context,
@@ -480,10 +506,12 @@
     // 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 = getSynthesizingContext(context.getClassContext(), appView);
-    DexType type =
-        SyntheticNaming.createInternalType(
-            kind, outerContext, context.getSyntheticSuffix(), appView.dexItemFactory());
-    return internalCreateProgramClass(kind, fn, outerContext, type, appView.dexItemFactory());
+    Function<SynthesizingContext, DexType> contextToType =
+        c ->
+            SyntheticNaming.createInternalType(
+                kind, c, context.getSyntheticSuffix(), appView.dexItemFactory());
+    return internalCreateProgramClass(
+        kind, fn, outerContext, contextToType.apply(outerContext), contextToType, appView);
   }
 
   // TODO(b/172194101): Make this take a unique context.
@@ -493,8 +521,10 @@
       AppView<?> appView,
       Consumer<SyntheticProgramClassBuilder> fn) {
     SynthesizingContext outerContext = internalGetOuterContext(context, appView);
-    DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
-    return internalCreateProgramClass(kind, fn, outerContext, type, appView.dexItemFactory());
+    Function<SynthesizingContext, DexType> contextToType =
+        c -> SyntheticNaming.createFixedType(kind, c, appView.dexItemFactory());
+    return internalCreateProgramClass(
+        kind, fn, outerContext, contextToType.apply(outerContext), contextToType, appView);
   }
 
   public DexProgramClass getExistingFixedClass(
@@ -530,8 +560,7 @@
       Consumer<DexProgramClass> onCreationConsumer) {
     assert kind.isFixedSuffixSynthetic;
     SynthesizingContext outerContext = internalGetOuterContext(context, appView);
-    DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
-    return internalEnsureDexProgramClass(kind, fn, onCreationConsumer, outerContext, type, appView);
+    return internalEnsureFixedProgramClass(kind, fn, onCreationConsumer, outerContext, appView);
   }
 
   public ProgramMethod ensureFixedClassMethod(
@@ -589,13 +618,15 @@
                 + " class.");
   }
 
-  private DexClasspathClass internalEnsureDexClasspathClass(
+  private DexClasspathClass internalEnsureFixedClasspathClass(
       SyntheticKind kind,
       Consumer<SyntheticClasspathClassBuilder> classConsumer,
       Consumer<DexClasspathClass> onCreationConsumer,
       SynthesizingContext outerContext,
-      DexType type,
       AppView<?> appView) {
+    Function<SynthesizingContext, DexType> contextToType =
+        (c) -> SyntheticNaming.createFixedType(kind, c, appView.dexItemFactory());
+    DexType type = contextToType.apply(outerContext);
     synchronized (type) {
       DexClass clazz = appView.definitionFor(type);
       if (clazz != null) {
@@ -604,6 +635,7 @@
         }
         return clazz.asClasspathClass();
       }
+      registerSyntheticTypeRewriting(outerContext, contextToType, appView, type);
       SyntheticClasspathClassBuilder classBuilder =
           new SyntheticClasspathClassBuilder(type, kind, outerContext, appView.dexItemFactory());
       classConsumer.accept(classBuilder);
@@ -620,9 +652,8 @@
       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);
+    return internalEnsureFixedClasspathClass(
+        kind, classConsumer, ignored -> {}, outerContext, appView);
   }
 
   public DexClasspathClass ensureFixedClasspathClass(
@@ -634,9 +665,8 @@
     // 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());
-    return internalEnsureDexClasspathClass(
-        kind, classConsumer, onCreationConsumer, outerContext, type, appView);
+    return internalEnsureFixedClasspathClass(
+        kind, classConsumer, onCreationConsumer, outerContext, appView);
   }
 
   public DexClassAndMethod ensureFixedClasspathClassMethod(
@@ -703,8 +733,7 @@
     // 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.fromType(contextType);
-    DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
-    return internalEnsureDexProgramClass(kind, fn, onCreationConsumer, outerContext, type, appView);
+    return internalEnsureFixedProgramClass(kind, fn, onCreationConsumer, outerContext, appView);
   }
 
   /** Create a single synthetic method item. */
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index c80a94b..62c7c6f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -39,6 +39,7 @@
     HORIZONTAL_INIT_TYPE_ARGUMENT_1(SYNTHETIC_CLASS_SEPARATOR + "IA$1", 6, false, true),
     HORIZONTAL_INIT_TYPE_ARGUMENT_2(SYNTHETIC_CLASS_SEPARATOR + "IA$2", 7, false, true),
     HORIZONTAL_INIT_TYPE_ARGUMENT_3(SYNTHETIC_CLASS_SEPARATOR + "IA$3", 8, false, true),
+    NON_FIXED_INIT_TYPE_ARGUMENT("$IA", 35, false),
     // Method synthetics.
     ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD("CheckNotZero", 27, true),
     RECORD_HELPER("Record", 9, true),
@@ -100,6 +101,18 @@
       return this == RECORD_TAG;
     }
 
+    public boolean isShareable() {
+      if (isFixedSuffixSynthetic) {
+        // Fixed synthetics are non-shareable. Ordered by their unique type.
+        return false;
+      }
+      if (this == NON_FIXED_INIT_TYPE_ARGUMENT) {
+        // TODO(b/214901256): Sharing of synthetic classes may lead to duplicate method errors.
+        return false;
+      }
+      return true;
+    }
+
     public static SyntheticKind fromDescriptor(String descriptor) {
       for (SyntheticKind kind : values()) {
         if (kind.descriptor.equals(descriptor)) {
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 c1dc2c9..d1264f6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -53,6 +53,8 @@
 import com.android.tools.r8.horizontalclassmerging.Policy;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
+import com.android.tools.r8.ir.desugar.PrefixRewritingMapper.MachineDesugarPrefixRewritingMapper;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.nest.Nest;
@@ -892,6 +894,15 @@
   public LegacyDesugaredLibrarySpecification desugaredLibrarySpecification =
       LegacyDesugaredLibrarySpecification.empty();
 
+  public PrefixRewritingMapper getPrefixRewritingMapper() {
+    if (testing.machineDesugaredLibrarySpecification != null) {
+      return new MachineDesugarPrefixRewritingMapper(
+          desugaredLibrarySpecification.getPrefixRewritingMapper(),
+          testing.machineDesugaredLibrarySpecification.getRewritingFlags());
+    }
+    return desugaredLibrarySpecification.getPrefixRewritingMapper();
+  }
+
   public boolean relocatorCompilation = false;
 
   // If null, no keep rules are recorded.
@@ -1264,6 +1275,8 @@
     private boolean enabled = true;
     private boolean enableMethodStaticizing = true;
 
+    private boolean forceSyntheticsForInstanceInitializers = false;
+
     public void disableOptimization() {
       enabled = false;
     }
@@ -1279,6 +1292,10 @@
       return enabled;
     }
 
+    public boolean isForceSyntheticsForInstanceInitializersEnabled() {
+      return forceSyntheticsForInstanceInitializers;
+    }
+
     public boolean isMethodStaticizingEnabled() {
       return enableMethodStaticizing;
     }
@@ -1292,6 +1309,12 @@
       return this;
     }
 
+    public CallSiteOptimizationOptions setForceSyntheticsForInstanceInitializers(
+        boolean forceSyntheticsForInstanceInitializers) {
+      this.forceSyntheticsForInstanceInitializers = forceSyntheticsForInstanceInitializers;
+      return this;
+    }
+
     public CallSiteOptimizationOptions setEnableMethodStaticizing(boolean enableMethodStaticizing) {
       this.enableMethodStaticizing = enableMethodStaticizing;
       return this;
@@ -2315,4 +2338,12 @@
   public boolean canParseNumbersWithPlusPrefix() {
     return getMinApiLevel().isGreaterThan(AndroidApiLevel.K);
   }
+
+  // Lollipop and Marshmallow devices do not correctly handle invoke-super when the static holder
+  // is higher up in the hierarchy than the method that the invoke-super should resolve to.
+  //
+  // See b/215573892.
+  public boolean canHaveSuperInvokeBug() {
+    return getMinApiLevel().isLessThan(AndroidApiLevel.N);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
index 9c1c5af..b14dd86 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
@@ -53,6 +53,11 @@
     return backing.getOrDefault(wrap(member), defaultValue);
   }
 
+  public V getOrDefault(K member, Supplier<V> defaultValue) {
+    V value = backing.get(wrap(member));
+    return value != null ? value : defaultValue.get();
+  }
+
   public boolean isEmpty() {
     return backing.isEmpty();
   }
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelper.java b/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelper.java
index 558fdf6..15efa1a 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelper.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelper.java
@@ -6,7 +6,9 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.FieldPut;
@@ -15,7 +17,6 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.Return;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
 
 /**
  * Inserts check-cast instructions after vertical class merging when this is needed for the program
@@ -41,8 +42,13 @@
   public static InterfaceTypeToClassTypeLensCodeRewriterHelper create(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       IRCode code,
-      MethodProcessor methodProcessor) {
-    if (methodProcessor.isPrimaryMethodProcessor() && appView.hasVerticallyMergedClasses()) {
+      NonIdentityGraphLens graphLens,
+      GraphLens codeLens) {
+    NonIdentityGraphLens previousLens =
+        graphLens.find(lens -> lens.isVerticalClassMergerLens() || lens == codeLens);
+    if (previousLens != null
+        && previousLens != codeLens
+        && previousLens.isVerticalClassMergerLens()) {
       return new InterfaceTypeToClassTypeLensCodeRewriterHelperImpl(appView, code);
     }
     return new EmptyInterfaceTypeToClassTypeLensCodeRewriterHelper();
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 1c74df5..cf27d33 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.D8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.benchmarks.BenchmarkResults;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -64,10 +65,13 @@
 
   @Override
   D8TestCompileResult internalCompile(
-      Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
+      Builder builder,
+      Consumer<InternalOptions> optionsConsumer,
+      Supplier<AndroidApp> app,
+      BenchmarkResults benchmarkResults)
       throws CompilationFailedException {
     libraryDesugaringTestConfiguration.configure(builder);
-    ToolHelper.runD8(builder, optionsConsumer);
+    ToolHelper.runAndBenchmarkD8(builder, optionsConsumer, benchmarkResults);
     return new D8TestCompileResult(
         getState(),
         app.get(),
diff --git a/src/test/java/com/android/tools/r8/DXTestBuilder.java b/src/test/java/com/android/tools/r8/DXTestBuilder.java
index 436f1e7..fcef696 100644
--- a/src/test/java/com/android/tools/r8/DXTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/DXTestBuilder.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.D8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.benchmarks.BenchmarkResults;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -44,8 +45,12 @@
 
   @Override
   DXTestCompileResult internalCompile(
-      Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
+      Builder builder,
+      Consumer<InternalOptions> optionsConsumer,
+      Supplier<AndroidApp> app,
+      BenchmarkResults benchmarkResults)
       throws CompilationFailedException {
+    assert benchmarkResults == null;
     assert !libraryDesugaringTestConfiguration.isEnabled();
     try {
       Path dxOutputFolder = getState().getNewTempFolder();
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index 6d34346..2808f05 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.benchmarks.BenchmarkResults;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -110,8 +111,12 @@
 
   @Override
   ExternalR8TestCompileResult internalCompile(
-      Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
+      Builder builder,
+      Consumer<InternalOptions> optionsConsumer,
+      Supplier<AndroidApp> app,
+      BenchmarkResults benchmarkResults)
       throws CompilationFailedException {
+    assert benchmarkResults == null;
     assert !libraryDesugaringTestConfiguration.isEnabled();
     try {
       Path outputFolder = getState().getNewTempFolder();
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 8d5bdea..66e5fc8 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.benchmarks.BenchmarkResults;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.utils.AndroidApp;
@@ -64,8 +65,12 @@
 
   @Override
   ProguardTestCompileResult internalCompile(
-      Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
+      Builder builder,
+      Consumer<InternalOptions> optionsConsumer,
+      Supplier<AndroidApp> app,
+      BenchmarkResults benchmarkResults)
       throws CompilationFailedException {
+    assert benchmarkResults == null;
     assert !libraryDesugaringTestConfiguration.isEnabled();
     try {
       Path proguardOutputFolder = getState().getNewTempFolder();
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 090ebb5..b3a595a 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.benchmarks.BenchmarkResults;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
@@ -71,7 +72,10 @@
 
   @Override
   R8TestCompileResult internalCompile(
-      Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
+      Builder builder,
+      Consumer<InternalOptions> optionsConsumer,
+      Supplier<AndroidApp> app,
+      BenchmarkResults benchmarkResults)
       throws CompilationFailedException {
     if (!keepRules.isEmpty()) {
       builder.addProguardConfiguration(keepRules, Origin.unknown());
@@ -118,10 +122,11 @@
     ToolHelper.addSyntheticProguardRulesConsumerForTesting(
         builder, rules -> box.syntheticProguardRules = rules);
     libraryDesugaringTestConfiguration.configure(builder);
-    ToolHelper.runR8WithoutResult(
+    ToolHelper.runAndBenchmarkR8WithoutResult(
         builder.build(),
         optionsConsumer.andThen(
-            options -> box.proguardConfiguration = options.getProguardConfiguration()));
+            options -> box.proguardConfiguration = options.getProguardConfiguration()),
+        benchmarkResults);
     R8TestCompileResult compileResult =
         new R8TestCompileResult(
             getState(),
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index d72151f..eb7769e 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -806,7 +806,7 @@
         computeAppViewWithClassHierarchy(app, keepConfig, optionsConsumer);
     // Run the tree shaker to compute an instance of AppInfoWithLiveness.
     ExecutorService executor = Executors.newSingleThreadExecutor();
-    SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
+    SubtypingInfo subtypingInfo = SubtypingInfo.create(appView);
     RootSet rootSet =
         RootSet.builder(
                 appView, subtypingInfo, appView.options().getProguardConfiguration().getRules())
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 5b6996d..efe353d 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.benchmarks.BenchmarkResults;
 import com.android.tools.r8.debug.CfDebugTestConfig;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.debug.DexDebugTestConfig;
@@ -25,11 +26,13 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.TriFunction;
+import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
@@ -658,4 +661,19 @@
     app.writeToZip(jarFile, OutputMode.DexIndexed);
     return new Dex2OatTestRunResult(app, runtime, ToolHelper.runDex2OatRaw(jarFile, oatFile, vm));
   }
+
+  public CR benchmarkCodeSize(BenchmarkResults results) throws IOException {
+    Path out = writeToZip();
+    Box<Long> size = new Box<>(0L);
+    ZipUtils.iter(
+        out,
+        (entry, stream) -> {
+          if ((getBackend().isDex() && entry.getName().endsWith(".dex"))
+              || getBackend().isCf() && entry.getName().endsWith(".class")) {
+            size.set(size.get() + entry.getSize());
+          }
+        });
+    results.addCodeSizeResult(size.get());
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index a66a4b2..be94525 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.benchmarks.BenchmarkResults;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer;
@@ -110,7 +111,10 @@
   }
 
   abstract CR internalCompile(
-      B builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
+      B builder,
+      Consumer<InternalOptions> optionsConsumer,
+      Supplier<AndroidApp> app,
+      BenchmarkResults benchmarkResults)
       throws CompilationFailedException;
 
   public T addArgumentPropagatorCodeScannerResultInspector(
@@ -183,7 +187,19 @@
                             dexItemFactory, verticallyMergedClasses))));
   }
 
+  public CR benchmarkCompile(BenchmarkResults results) throws CompilationFailedException {
+    if (System.getProperty("com.android.tools.r8.printtimes") != null) {
+      allowStdoutMessages();
+    }
+    return internalCompileAndBenchmark(results);
+  }
+
   public CR compile() throws CompilationFailedException {
+    return internalCompileAndBenchmark(null);
+  }
+
+  private CR internalCompileAndBenchmark(BenchmarkResults benchmark)
+      throws CompilationFailedException {
     AndroidAppConsumers sink = new AndroidAppConsumers();
     builder.setProgramConsumer(sink.wrapProgramConsumer(programConsumer));
     if (mainDexClassesCollector != null || mainDexListConsumer != null) {
@@ -239,7 +255,7 @@
                     () -> new AssertionError("Unexpected print to stderr"))));
       }
       cr =
-          internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build))
+          internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build), benchmark)
               .addRunClasspathFiles(additionalRunClassPath);
       if (isAndroidBuildVersionAdded) {
         cr.setSystemProperty(AndroidBuildVersion.PROPERTY, "" + builder.getMinApiLevel());
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index bd5ad83..28625eb 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -120,6 +120,13 @@
     return assertSuccessWithOutputThatMatches(is(expected));
   }
 
+  public RR assertSuccessWithOutputIf(boolean condition, String expected) {
+    if (condition) {
+      return assertSuccessWithOutput(expected);
+    }
+    return self();
+  }
+
   public RR assertSuccessWithEmptyOutput() {
     return assertSuccessWithOutput("");
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 57fabd1..1534edb 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper.DexVm.Kind;
+import com.android.tools.r8.benchmarks.BenchmarkResults;
 import com.android.tools.r8.desugar.desugaredlibrary.jdk11.DesugaredLibraryJDK11Undesugarer;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.Unreachable;
@@ -1252,9 +1253,25 @@
   public static void runR8WithoutResult(
       R8Command command, Consumer<InternalOptions> optionsConsumer)
       throws CompilationFailedException {
+    runAndBenchmarkR8WithoutResult(command, optionsConsumer, null);
+  }
+
+  public static void runAndBenchmarkR8WithoutResult(
+      R8Command command,
+      Consumer<InternalOptions> optionsConsumer,
+      BenchmarkResults benchmarkResults)
+      throws CompilationFailedException {
     InternalOptions internalOptions = command.getInternalOptions();
     optionsConsumer.accept(internalOptions);
+    long start = 0;
+    if (benchmarkResults != null) {
+      start = System.nanoTime();
+    }
     R8.runForTesting(command.getInputApp(), internalOptions);
+    if (benchmarkResults != null) {
+      long end = System.nanoTime();
+      benchmarkResults.addRuntimeRawResult(end - start);
+    }
   }
 
   public static AndroidApp runR8WithFullResult(
@@ -1324,6 +1341,14 @@
   public static AndroidApp runD8(
       D8Command.Builder builder, Consumer<InternalOptions> optionsConsumer)
       throws CompilationFailedException {
+    return runAndBenchmarkD8(builder, optionsConsumer, null);
+  }
+
+  public static AndroidApp runAndBenchmarkD8(
+      D8Command.Builder builder,
+      Consumer<InternalOptions> optionsConsumer,
+      BenchmarkResults benchmarkResults)
+      throws CompilationFailedException {
     AndroidAppConsumers compatSink = new AndroidAppConsumers(builder);
     D8Command command = builder.build();
     InternalOptions options = command.getInternalOptions();
@@ -1331,7 +1356,15 @@
       ExceptionUtils.withD8CompilationHandler(
           options.reporter, () -> optionsConsumer.accept(options));
     }
+    long start = 0;
+    if (benchmarkResults != null) {
+      start = System.nanoTime();
+    }
     D8.runForTesting(command.getInputApp(), options);
+    if (benchmarkResults != null) {
+      long end = System.nanoTime();
+      benchmarkResults.addRuntimeRawResult(end - start);
+    }
     return compatSink.build();
   }
 
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkBase.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkBase.java
new file mode 100644
index 0000000..3753d7f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkBase.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2022, 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.benchmarks;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public abstract class BenchmarkBase extends TestBase {
+
+  // Benchmarks must be configured with the "none" runtime as each config defines a singleton
+  // benchmark in golem.
+  public static List<Object[]> parametersFromConfigs(Iterable<BenchmarkConfig> configs) {
+    return buildParameters(configs, getTestParameters().withNoneRuntime().build());
+  }
+
+  private final BenchmarkConfig config;
+
+  protected BenchmarkBase(BenchmarkConfig config, TestParameters parameters) {
+    this.config = config;
+    parameters.assertNoneRuntime();
+  }
+
+  protected BenchmarkConfig getConfig() {
+    return config;
+  }
+
+  @Test
+  public void testBenchmarks() throws Exception {
+    config.run(temp);
+  }
+
+  public static BenchmarkRunner runner(BenchmarkConfig config) {
+    return BenchmarkRunner.runner(config);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
new file mode 100644
index 0000000..f75aabe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2022, 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.benchmarks;
+
+import com.android.tools.r8.benchmarks.helloworld.HelloWorldBenchmark;
+import com.android.tools.r8.errors.Unreachable;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class BenchmarkCollection {
+
+  // Actual list of all configured benchmarks.
+  private final Map<BenchmarkIdentifier, BenchmarkConfig> benchmarks = new HashMap<>();
+
+  private void addBenchmark(BenchmarkConfig benchmark) {
+    BenchmarkIdentifier id = benchmark.getIdentifier();
+    if (benchmarks.containsKey(id)) {
+      throw new Unreachable("Duplicate definition of benchmark with name and target: " + id);
+    }
+    benchmarks.put(id, benchmark);
+  }
+
+  public BenchmarkConfig getBenchmark(BenchmarkIdentifier benchmark) {
+    return benchmarks.get(benchmark);
+  }
+
+  public static BenchmarkCollection computeCollection() {
+    BenchmarkCollection collection = new BenchmarkCollection();
+    // Every benchmark that should be active on golem must be setup in this method.
+    HelloWorldBenchmark.configs().forEach(collection::addBenchmark);
+    return collection;
+  }
+
+  /** Compute and print the golem configuration. */
+  public static void main(String[] args) throws IOException {
+    new BenchmarkCollectionPrinter(System.out)
+        .printGolemConfig(computeCollection().benchmarks.values());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollectionPrinter.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollectionPrinter.java
new file mode 100644
index 0000000..e3ede15
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollectionPrinter.java
@@ -0,0 +1,213 @@
+// Copyright (c) 2022, 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.benchmarks;
+
+import static com.android.tools.r8.utils.ListUtils.map;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
+import com.google.common.base.Strings;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BenchmarkCollectionPrinter {
+
+  private interface RunnableIO {
+    void run() throws IOException;
+  }
+
+  private static final PrintStream QUIET =
+      new PrintStream(
+          new OutputStream() {
+            @Override
+            public void write(int b) {
+              // ignore
+            }
+          });
+
+  // Internal state for printing the benchmark config for golem.
+  private int currentIndent = 0;
+  private final PrintStream out;
+
+  public BenchmarkCollectionPrinter(PrintStream out) {
+    this.out = out;
+  }
+
+  private void scopeBraces(RunnableIO fn) throws IOException {
+    print("{");
+    indentScope(2, fn);
+    print("}");
+  }
+
+  private void indentScope(RunnableIO fn) throws IOException {
+    indentScope(2, fn);
+  }
+
+  private void indentScope(int spaces, RunnableIO fn) throws IOException {
+    currentIndent += spaces;
+    fn.run();
+    currentIndent -= spaces;
+  }
+
+  private static String quote(String str) {
+    return "\"" + str + "\"";
+  }
+
+  private void print(String string) {
+    printIndented(string, currentIndent);
+  }
+
+  private void printSemi(String string) {
+    print(string + ";");
+  }
+
+  private void printIndented(String string, int indent) {
+    out.print(Strings.repeat(" ", indent));
+    out.println(string);
+  }
+
+  public void printGolemConfig(Collection<BenchmarkConfig> benchmarks) throws IOException {
+    Path jdkHome = getJdkHome();
+    Map<String, List<BenchmarkConfig>> nameToTargets = new HashMap<>();
+    benchmarks.forEach(
+        b -> nameToTargets.computeIfAbsent(b.getName(), k -> new ArrayList<>()).add(b));
+    List<String> sortedNames = new ArrayList<>(nameToTargets.keySet());
+    sortedNames.sort(String::compareTo);
+    print(
+        "// AUTOGENERATED FILE from"
+            + " src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java");
+    print("");
+    printSemi("part of r8_config");
+    print("");
+    print("createTestBenchmarks() {");
+    indentScope(
+        () -> {
+          print("final cpus = [\"Lenovo M90\"];");
+          addGolemResource("openjdk", Paths.get(jdkHome + ".tar.gz"));
+          for (String benchmarkName : sortedNames) {
+            List<BenchmarkConfig> benchmarkTargets = nameToTargets.get(benchmarkName);
+            assert !benchmarkTargets.isEmpty();
+            scopeBraces(() -> printBenchmarkBlock(benchmarkName, benchmarkTargets));
+          }
+        });
+    print("}");
+  }
+
+  private void printBenchmarkBlock(String benchmarkName, List<BenchmarkConfig> benchmarkTargets)
+      throws IOException {
+    printSemi("final name = " + quote(benchmarkName));
+    printSemi("final group = new GroupBenchmark(name + \"Group\", [])");
+    // NOTE: It appears these must be consistent for each target now?
+    boolean hasWarmup = false;
+    String suite = null;
+    List<String> metrics = null;
+    for (BenchmarkConfig benchmark : benchmarkTargets) {
+      if (metrics == null) {
+        // TODO: Verify equal on other runs.
+        hasWarmup = benchmark.hasTimeWarmupRuns();
+        suite = benchmark.getSuite().getDartName();
+        metrics = map(benchmark.getMetrics(), BenchmarkMetric::getDartType);
+      }
+      scopeBraces(
+          () -> {
+            printSemi("final target = " + quote(benchmark.getTarget().getGolemName()));
+            printSemi("final options = group.addTargets(noImplementation, [target])");
+            printSemi("options.cpus = cpus");
+            printSemi("options.isScript = true");
+            printSemi("options.fromRevision = " + benchmark.getFromRevision());
+            print("options.mainFile =");
+            indentScope(
+                4,
+                () ->
+                    printSemi(
+                        quote(
+                            "tools/run_benchmark.py --golem"
+                                + " --target "
+                                + benchmark.getTarget().getIdentifierName()
+                                + " --benchmark "
+                                + benchmark.getName())));
+            printSemi("options.resources.add(openjdk)");
+          });
+    }
+
+    List<String> finalMetrics = metrics;
+    String finalSuite = suite;
+    boolean finalHasWarmup = hasWarmup;
+    scopeBraces(
+        () -> {
+          printSemi("final metrics = " + StringUtils.join(", ", finalMetrics, BraceType.SQUARE));
+          printSemi("group.addBenchmark(name, metrics)");
+          printSemi(finalSuite + ".addBenchmark(name)");
+          if (finalHasWarmup) {
+            printSemi("final warmupName = name + \"Warmup\"");
+            printSemi("group.addBenchmark(warmupName, [Metric.RunTimeRaw])");
+            printSemi(finalSuite + ".addBenchmark(warmupName)");
+          }
+        });
+  }
+
+  private void addGolemResource(String name, Path tarball) throws IOException {
+    Path shaFile = Paths.get(tarball.toString() + ".sha1");
+    downloadDependency(shaFile);
+    String sha256 = computeSha256(tarball);
+    String shaFileContent = getShaFileContent(shaFile);
+    print("final " + name + " = BenchmarkResource(" + quote("") + ",");
+    indentScope(
+        4,
+        () -> {
+          print("type: BenchmarkResourceType.Storage,");
+          print("uri: " + quote("gs://r8-deps/" + shaFileContent) + ",");
+          // Make dart formatter happy.
+          if (currentIndent > 6) {
+            print("hash:");
+            indentScope(4, () -> print(quote(sha256) + ","));
+          } else {
+            print("hash: " + quote(sha256) + ",");
+          }
+          print("extract: " + quote("gz") + ");");
+        });
+  }
+
+  private static Path getJdkHome() throws IOException {
+    ProcessBuilder builder = new ProcessBuilder("python", "tools/jdk.py");
+    ProcessResult result = ToolHelper.runProcess(builder, QUIET);
+    if (result.exitCode != 0) {
+      throw new Unreachable("Unexpected failure to determine jdk home: " + result);
+    }
+    return Paths.get(result.stdout.trim());
+  }
+
+  private static String computeSha256(Path path) throws IOException {
+    Hasher hasher = Hashing.sha256().newHasher();
+    return hasher.putBytes(Files.readAllBytes(path)).hash().toString();
+  }
+
+  private static String getShaFileContent(Path path) throws IOException {
+    return String.join("\n", Files.readAllLines(path)).trim();
+  }
+
+  private static void downloadDependency(Path path) throws IOException {
+    ProcessBuilder builder =
+        new ProcessBuilder(
+            "download_from_google_storage", "-n", "-b", "r8-deps", "-u", "-s", path.toString());
+    ProcessResult result = ToolHelper.runProcess(builder, QUIET);
+    if (result.exitCode != 0) {
+      throw new Unreachable("Unable to download dependency '" + path + "'\n" + result);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkConfig.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkConfig.java
new file mode 100644
index 0000000..09d7a6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkConfig.java
@@ -0,0 +1,168 @@
+// Copyright (c) 2022, 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.benchmarks;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.google.common.collect.ImmutableSet;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.rules.TemporaryFolder;
+
+public class BenchmarkConfig {
+
+  public static class Builder {
+
+    private String name = null;
+    private BenchmarkMethod method = null;
+    private BenchmarkTarget target = null;
+    private Set<BenchmarkMetric> metrics = new HashSet<>();
+    private BenchmarkSuite suite = BenchmarkSuite.getDefault();
+    private int fromRevision = -1;
+
+    private boolean timeWarmupRuns = false;
+
+    private Builder() {}
+
+    public BenchmarkConfig build() {
+      if (name == null) {
+        throw new Unreachable("Benchmark name must be set");
+      }
+      if (method == null) {
+        throw new Unreachable("Benchmark method must be set");
+      }
+      if (target == null) {
+        throw new Unreachable("Benchmark target must be set");
+      }
+      if (metrics.isEmpty()) {
+        throw new Unreachable("Benchmark must have at least one metric to measure");
+      }
+      if (suite == null) {
+        throw new Unreachable("Benchmark must have a suite");
+      }
+      if (fromRevision < 0) {
+        throw new Unreachable("Benchmark must specify from which golem revision it is valid");
+      }
+      if (timeWarmupRuns && !metrics.contains(BenchmarkMetric.RunTimeRaw)) {
+        throw new Unreachable("Benchmark with warmup time must measure RunTimeRaw");
+      }
+      return new BenchmarkConfig(
+          name, method, target, ImmutableSet.copyOf(metrics), suite, fromRevision, timeWarmupRuns);
+    }
+
+    public Builder setName(String name) {
+      this.name = name;
+      return this;
+    }
+
+    public Builder setTarget(BenchmarkTarget target) {
+      this.target = target;
+      return this;
+    }
+
+    public Builder setMethod(BenchmarkMethod method) {
+      this.method = method;
+      return this;
+    }
+
+    public Builder measureRunTimeRaw() {
+      metrics.add(BenchmarkMetric.RunTimeRaw);
+      return this;
+    }
+
+    public Builder measureCodeSize() {
+      metrics.add(BenchmarkMetric.CodeSize);
+      return this;
+    }
+
+    public Builder setSuite(BenchmarkSuite suite) {
+      this.suite = suite;
+      return this;
+    }
+
+    public Builder setFromRevision(int fromRevision) {
+      this.fromRevision = fromRevision;
+      return this;
+    }
+
+    public Builder timeWarmupRuns() {
+      this.timeWarmupRuns = true;
+      return this;
+    }
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  private final BenchmarkIdentifier id;
+  private final BenchmarkMethod method;
+  private final ImmutableSet<BenchmarkMetric> metrics;
+  private final BenchmarkSuite suite;
+  private final int fromRevision;
+  private final boolean timeWarmupRuns;
+
+  private BenchmarkConfig(
+      String name,
+      BenchmarkMethod benchmarkMethod,
+      BenchmarkTarget target,
+      ImmutableSet<BenchmarkMetric> metrics,
+      BenchmarkSuite suite,
+      int fromRevision,
+      boolean timeWarmupRuns) {
+    this.id = new BenchmarkIdentifier(name, target);
+    this.method = benchmarkMethod;
+    this.metrics = metrics;
+    this.suite = suite;
+    this.fromRevision = fromRevision;
+    this.timeWarmupRuns = timeWarmupRuns;
+  }
+
+  public BenchmarkIdentifier getIdentifier() {
+    return id;
+  }
+
+  public String getName() {
+    return id.getName();
+  }
+
+  public String getWarmupName() {
+    if (!timeWarmupRuns) {
+      throw new Unreachable("Invalid attempt at getting warmup benchmark name");
+    }
+    return getName() + "Warmup";
+  }
+
+  public BenchmarkTarget getTarget() {
+    return id.getTarget();
+  }
+
+  public Set<BenchmarkMetric> getMetrics() {
+    return metrics;
+  }
+
+  public boolean hasMetric(BenchmarkMetric metric) {
+    return metrics.contains(metric);
+  }
+
+  public BenchmarkSuite getSuite() {
+    return suite;
+  }
+
+  public int getFromRevision() {
+    return fromRevision;
+  }
+
+  public boolean hasTimeWarmupRuns() {
+    return timeWarmupRuns;
+  }
+
+  public void run(TemporaryFolder temp) throws Exception {
+    method.run(this, temp);
+  }
+
+  @Override
+  public String toString() {
+    return id.getName() + "/" + id.getTarget().getIdentifierName();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkIdentifier.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkIdentifier.java
new file mode 100644
index 0000000..f9ac65e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkIdentifier.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2022, 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.benchmarks;
+
+import com.android.tools.r8.utils.structural.Equatable;
+import com.android.tools.r8.utils.structural.Ordered;
+import java.util.Comparator;
+import java.util.Objects;
+
+public class BenchmarkIdentifier implements Ordered<BenchmarkIdentifier> {
+
+  private final String name;
+  private final BenchmarkTarget target;
+
+  public static BenchmarkIdentifier parse(String benchmarkName, String targetIdentifier) {
+    for (BenchmarkTarget target : BenchmarkTarget.values()) {
+      if (target.getIdentifierName().equals(targetIdentifier)) {
+        return new BenchmarkIdentifier(benchmarkName, target);
+      }
+    }
+    return null;
+  }
+
+  public BenchmarkIdentifier(String name, BenchmarkTarget target) {
+    this.name = name;
+    this.target = target;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public BenchmarkTarget getTarget() {
+    return target;
+  }
+
+  @Override
+  public int compareTo(BenchmarkIdentifier other) {
+    return Comparator.comparing(BenchmarkIdentifier::getName)
+        .thenComparing(BenchmarkIdentifier::getTarget)
+        .compare(this, other);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return Equatable.equalsImpl(this, o);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(name, target);
+  }
+
+  @Override
+  public String toString() {
+    return "BenchmarkIdentifier{" + "name='" + name + '\'' + ", target=" + target + '}';
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMainEntryRunner.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMainEntryRunner.java
new file mode 100644
index 0000000..3e5a86a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMainEntryRunner.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2022, 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.benchmarks;
+
+import org.junit.rules.TemporaryFolder;
+
+public class BenchmarkMainEntryRunner {
+
+  public static void main(String[] args) throws Exception {
+    if (args.length != 2) {
+      throw new RuntimeException("Invalid arguments. Expected exactly one benchmark and target");
+    }
+    String benchmarkName = args[0];
+    String targetIdentifier = args[1];
+    BenchmarkIdentifier identifier = BenchmarkIdentifier.parse(benchmarkName, targetIdentifier);
+    if (identifier == null) {
+      throw new RuntimeException("Invalid identifier identifier: " + benchmarkName);
+    }
+    BenchmarkCollection collection = BenchmarkCollection.computeCollection();
+    BenchmarkConfig config = collection.getBenchmark(identifier);
+    if (config == null) {
+      throw new RuntimeException("Unknown identifier: " + identifier);
+    }
+    TemporaryFolder temp = new TemporaryFolder();
+    temp.create();
+    config.run(temp);
+    temp.delete();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMethod.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMethod.java
new file mode 100644
index 0000000..8c7e372
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMethod.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, 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.benchmarks;
+
+import org.junit.rules.TemporaryFolder;
+
+@FunctionalInterface
+public interface BenchmarkMethod {
+
+  void run(BenchmarkConfig config, TemporaryFolder temp) throws Exception;
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMetric.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMetric.java
new file mode 100644
index 0000000..9483499
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMetric.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, 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.benchmarks;
+
+public enum BenchmarkMetric {
+  RunTimeRaw,
+  CodeSize;
+
+  public String getDartType() {
+    return "Metric." + name();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResults.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResults.java
new file mode 100644
index 0000000..cf55b86
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResults.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2022, 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.benchmarks;
+
+import com.android.tools.r8.benchmarks.BenchmarkRunner.ResultMode;
+import com.android.tools.r8.errors.Unreachable;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+import it.unimi.dsi.fastutil.longs.LongList;
+
+public class BenchmarkResults {
+
+  private final boolean isWarmupResults;
+  private final LongList runtimeRawResults = new LongArrayList();
+  private final LongList codeSizeResults = new LongArrayList();
+
+  public static BenchmarkResults create() {
+    return new BenchmarkResults(false);
+  }
+
+  public static BenchmarkResults createForWarmup() {
+    return new BenchmarkResults(true);
+  }
+
+  private BenchmarkResults(boolean isWarmupResults) {
+    this.isWarmupResults = isWarmupResults;
+  }
+
+  private String getName(BenchmarkConfig config) {
+    return isWarmupResults ? config.getWarmupName() : config.getName();
+  }
+
+  public void addRuntimeRawResult(long result) {
+    runtimeRawResults.add(result);
+  }
+
+  public void addCodeSizeResult(long result) {
+    codeSizeResults.add(result);
+  }
+
+  private static void verifyMetric(BenchmarkMetric metric, boolean expected, boolean actual) {
+    if (expected != actual) {
+      throw new Unreachable(
+          "Mismatched config and result for "
+              + metric.name()
+              + ". Expected by config: "
+              + expected
+              + ", but has result: "
+              + actual);
+    }
+  }
+
+  private void verifyConfigAndResults(BenchmarkConfig config) {
+    verifyMetric(
+        BenchmarkMetric.RunTimeRaw,
+        config.getMetrics().contains(BenchmarkMetric.RunTimeRaw),
+        !runtimeRawResults.isEmpty());
+    verifyMetric(
+        BenchmarkMetric.CodeSize,
+        config.getMetrics().contains(BenchmarkMetric.CodeSize),
+        !codeSizeResults.isEmpty());
+  }
+
+  public static String prettyTime(long nanoTime) {
+    return "" + (nanoTime / 1000000) + "ms";
+  }
+
+  private void printRunTimeRaw(BenchmarkConfig config, long duration) {
+    System.out.println(getName(config) + "(RunTimeRaw): " + prettyTime(duration));
+  }
+
+  private void printCodeSize(BenchmarkConfig config, long bytes) {
+    System.out.println(getName(config) + "(CodeSize): " + bytes);
+  }
+
+  public void printResults(ResultMode mode, BenchmarkConfig config) {
+    verifyConfigAndResults(config);
+    if (config.hasMetric(BenchmarkMetric.RunTimeRaw)) {
+      long sum = runtimeRawResults.stream().mapToLong(l -> l).sum();
+      if (mode == ResultMode.SUM) {
+        printRunTimeRaw(config, sum);
+      } else if (mode == ResultMode.AVERAGE) {
+        printRunTimeRaw(config, sum / runtimeRawResults.size());
+      }
+    }
+    if (!isWarmupResults && config.hasMetric(BenchmarkMetric.CodeSize)) {
+      long size = codeSizeResults.getLong(0);
+      for (int i = 1; i < codeSizeResults.size(); i++) {
+        if (size != codeSizeResults.getLong(i)) {
+          throw new Unreachable(
+              "Unexpected code size difference: " + size + " and " + codeSizeResults.getLong(i));
+        }
+      }
+      printCodeSize(config, size);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java
new file mode 100644
index 0000000..f0cb0b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2022, 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.benchmarks;
+
+public class BenchmarkRunner {
+
+  public interface BenchmarkRunnerFunction {
+    void run(BenchmarkResults results) throws Exception;
+  }
+
+  public enum ResultMode {
+    AVERAGE,
+    SUM;
+
+    @Override
+    public String toString() {
+      return name().toLowerCase();
+    }
+  }
+
+  private final BenchmarkConfig config;
+  private int warmups = 0;
+  private int iterations = 1;
+  private ResultMode resultMode = ResultMode.AVERAGE;
+
+  private BenchmarkRunner(BenchmarkConfig config) {
+    this.config = config;
+  }
+
+  public static BenchmarkRunner runner(BenchmarkConfig config) {
+    return new BenchmarkRunner(config);
+  }
+
+  public BenchmarkRunner setWarmupIterations(int iterations) {
+    this.warmups = iterations;
+    return this;
+  }
+
+  public BenchmarkRunner setBenchmarkIterations(int iterations) {
+    this.iterations = iterations;
+    return this;
+  }
+
+  public BenchmarkRunner reportResultAverage() {
+    resultMode = ResultMode.AVERAGE;
+    return this;
+  }
+
+  public BenchmarkRunner reportResultSum() {
+    resultMode = ResultMode.SUM;
+    return this;
+  }
+
+  public void run(BenchmarkRunnerFunction fn) throws Exception {
+    long warmupTotalTime = 0;
+    BenchmarkResults warmupResults = BenchmarkResults.createForWarmup();
+    if (warmups > 0) {
+      long start = System.nanoTime();
+      for (int i = 0; i < warmups; i++) {
+        fn.run(warmupResults);
+      }
+      warmupTotalTime = System.nanoTime() - start;
+    }
+    BenchmarkResults results = BenchmarkResults.create();
+    long start = System.nanoTime();
+    for (int i = 0; i < iterations; i++) {
+      fn.run(results);
+    }
+    long benchmarkTotalTime = System.nanoTime() - start;
+    System.out.println(
+        "Benchmark results for "
+            + config.getName()
+            + " on target "
+            + config.getTarget().getIdentifierName());
+    if (warmups > 0) {
+      printMetaInfo("warmup", warmups, warmupTotalTime);
+      if (config.hasTimeWarmupRuns()) {
+        warmupResults.printResults(resultMode, config);
+      }
+    }
+    printMetaInfo("benchmark", iterations, benchmarkTotalTime);
+    results.printResults(resultMode, config);
+    System.out.println();
+  }
+
+  private void printMetaInfo(String kind, int iterations, long totalTime) {
+    System.out.println("  " + kind + " reporting mode: " + resultMode);
+    System.out.println("  " + kind + " iterations: " + iterations);
+    System.out.println("  " + kind + " total time: " + BenchmarkResults.prettyTime(totalTime));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkSuite.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkSuite.java
new file mode 100644
index 0000000..1068f72
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkSuite.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2022, 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.benchmarks;
+
+/** Enumeration of the benchmark suites on Golem. */
+public enum BenchmarkSuite {
+  R8_BENCHMARKS("R8Benchmarks", "suite"),
+  D8_BENCHMARKS("D8KeyBenchmarks", "d8KeySuite"),
+  D8_INCREMENTAL_BENCHMARKS("D8IncrementalBenchmarks", "incrementalSuite"),
+  OPENSOURCE_BENCHMARKS("OpenSourceAppDumps", "dumpsSuite"),
+  MEMORY_BENCHMARKS("R8MemoryBenchmarks", "r8MemorySuite"),
+  RETRACE_BENCHMARKS("R8RetraceBenchmarks", "r8RetraceSuite");
+
+  private final String golemName;
+  private final String dartName;
+
+  public static BenchmarkSuite getDefault() {
+    return R8_BENCHMARKS;
+  }
+
+  BenchmarkSuite(String golemName, String dartName) {
+    this.golemName = golemName;
+    this.dartName = dartName;
+  }
+
+  /** The name as shown in golems listings. */
+  public String getGolemName() {
+    return golemName;
+  }
+
+  /** The variable name used for the suite in the benchmarks.dart script. */
+  public String getDartName() {
+    return dartName;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkTarget.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkTarget.java
new file mode 100644
index 0000000..cda7f6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkTarget.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2022, 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.benchmarks;
+
+public enum BenchmarkTarget {
+  // Possible dashboard targets on golem.
+  D8("d8", "D8"),
+  R8_COMPAT("r8-compat", "R8"),
+  R8_NON_COMPAT("r8", "R8-full"),
+  R8_FORCE_OPT("r8-force", "R8-full-minify-optimize-shrink");
+
+  private final String idName;
+  private final String golemName;
+
+  BenchmarkTarget(String idName, String golemName) {
+    this.idName = idName;
+    this.golemName = golemName;
+  }
+
+  public String getGolemName() {
+    return golemName;
+  }
+
+  public String getIdentifierName() {
+    return idName;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/helloworld/HelloWorldBenchmark.java b/src/test/java/com/android/tools/r8/benchmarks/helloworld/HelloWorldBenchmark.java
new file mode 100644
index 0000000..b91e5b1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/helloworld/HelloWorldBenchmark.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2022, 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.benchmarks.helloworld;
+
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.benchmarks.BenchmarkBase;
+import com.android.tools.r8.benchmarks.BenchmarkConfig;
+import com.android.tools.r8.benchmarks.BenchmarkMethod;
+import com.android.tools.r8.benchmarks.BenchmarkTarget;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import java.util.List;
+import java.util.function.Function;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Example of setting up a benchmark based on the testing infrastructure. */
+@RunWith(Parameterized.class)
+public class HelloWorldBenchmark extends BenchmarkBase {
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return parametersFromConfigs(configs());
+  }
+
+  public HelloWorldBenchmark(BenchmarkConfig config, TestParameters parameters) {
+    super(config, parameters);
+  }
+
+  /** Static method to add benchmarks to the benchmark collection. */
+  public static List<BenchmarkConfig> configs() {
+    Builder<BenchmarkConfig> benchmarks = ImmutableList.builder();
+    makeBenchmark(BenchmarkTarget.D8, HelloWorldBenchmark::benchmarkD8, benchmarks);
+    makeBenchmark(BenchmarkTarget.R8_NON_COMPAT, HelloWorldBenchmark::benchmarkR8, benchmarks);
+    return benchmarks.build();
+  }
+
+  // Options/parameter setup to define variants of the benchmark above.
+  private static class Options {
+    final BenchmarkTarget target;
+    final Backend backend;
+    final boolean includeLibrary;
+    final AndroidApiLevel minApi = AndroidApiLevel.B;
+
+    public Options(BenchmarkTarget target, Backend backend, boolean includeLibrary) {
+      this.target = target;
+      this.backend = backend;
+      this.includeLibrary = includeLibrary;
+    }
+
+    public String getName() {
+      // The name include each non-target option for the variants to ensure unique benchmarks.
+      String backendString = backend.isCf() ? "Cf" : "Dex";
+      String libraryString = includeLibrary ? "" : "NoLib";
+      return "HelloWorld" + backendString + libraryString;
+    }
+  }
+
+  private static void makeBenchmark(
+      BenchmarkTarget target,
+      Function<Options, BenchmarkMethod> method,
+      ImmutableList.Builder<BenchmarkConfig> benchmarks) {
+    for (boolean includeLibrary : BooleanUtils.values()) {
+      for (Backend backend : Backend.values()) {
+        Options options = new Options(target, backend, includeLibrary);
+        benchmarks.add(
+            BenchmarkConfig.builder()
+                // The benchmark is required to have a unique combination of name and target.
+                .setName(options.getName())
+                .setTarget(target)
+                // The benchmark is required to have at least one metric.
+                .measureRunTimeRaw()
+                .measureCodeSize()
+                // The benchmark is required to have a runner method which defines the actual
+                // execution.
+                .setMethod(method.apply(options))
+                // The benchmark is required to set a "golem from revision".
+                // Find this value by looking at the current revision on golem.
+                .setFromRevision(11900)
+                // The benchmark can optionally time the warmup. This is not needed to use a warmup
+                // in the actual run, only to include it as its own benchmark entry on golem.
+                .timeWarmupRuns()
+                .build());
+      }
+    }
+  }
+
+  public static BenchmarkMethod benchmarkD8(Options options) {
+    return (config, temp) ->
+        runner(config)
+            .setWarmupIterations(1)
+            .setBenchmarkIterations(100)
+            .reportResultSum()
+            .run(
+                results ->
+                    testForD8(temp, options.backend)
+                        .setMinApi(options.minApi)
+                        .applyIf(!options.includeLibrary, TestBuilder::addLibraryFiles)
+                        .addProgramClasses(TestClass.class)
+                        // Compile and emit RunTimeRaw measure.
+                        .benchmarkCompile(results)
+                        // Measure the output size.
+                        .benchmarkCodeSize(results));
+  }
+
+  public static BenchmarkMethod benchmarkR8(Options options) {
+    return (config, temp) ->
+        runner(config)
+            .setWarmupIterations(1)
+            .setBenchmarkIterations(4)
+            .reportResultSum()
+            .run(
+                results ->
+                    testForR8(temp, options.backend)
+                        .applyIf(!options.includeLibrary, b -> b.addLibraryFiles().addDontWarn("*"))
+                        .setMinApi(options.minApi)
+                        .addProgramClasses(TestClass.class)
+                        .addKeepMainRule(TestClass.class)
+                        // Compile and emit RunTimeRaw measure.
+                        .benchmarkCompile(results)
+                        // Measure the output size.
+                        .benchmarkCodeSize(results));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodWithSuperMethodMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodWithSuperMethodMergingTest.java
new file mode 100644
index 0000000..0f0fb29
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodWithSuperMethodMergingTest.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+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.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AbstractMethodWithSuperMethodMergingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertIsCompleteMergeGroup(B1.class, B2.class))
+        .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccess();
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      boolean unknownButAlwaysTrue = System.currentTimeMillis() > 0;
+      B1 c1 = unknownButAlwaysTrue ? new C1() : new C2();
+      C3 c3 = unknownButAlwaysTrue ? new C3() : null;
+      System.out.println(c1.foo());
+      System.out.println(c3.foo());
+    }
+  }
+
+  abstract static class A {
+
+    public int foo() {
+      return 0;
+    }
+  }
+
+  @NoVerticalClassMerging
+  abstract static class B1 extends A {
+
+    @Override
+    public abstract int foo();
+  }
+
+  @NoVerticalClassMerging
+  abstract static class B2 extends A {}
+
+  @NoHorizontalClassMerging
+  static class C1 extends B1 {
+
+    @Override
+    @NeverInline
+    public int foo() {
+      return System.identityHashCode(this);
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class C2 extends B1 {
+
+    @Override
+    @NeverInline
+    public int foo() {
+      return System.identityHashCode(this);
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class C3 extends B2 {}
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
index 055ab8d..93bfcef 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
@@ -37,30 +37,25 @@
         .assertSuccessWithOutputLines("42", "13", "7", "print a", "print b")
         .inspect(
             codeInspector -> {
-                ClassSubject aClassSubject = codeInspector.clazz(A.class);
-                assertThat(aClassSubject, isPresent());
-                FieldSubject classIdFieldSubject =
-                    aClassSubject.uniqueFieldWithName(ClassMerger.CLASS_ID_FIELD_NAME);
-                assertThat(classIdFieldSubject, isPresent());
+              ClassSubject aClassSubject = codeInspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              FieldSubject classIdFieldSubject =
+                  aClassSubject.uniqueFieldWithName(ClassMerger.CLASS_ID_FIELD_NAME);
+              assertThat(classIdFieldSubject, isPresent());
 
-                MethodSubject firstInitSubject = aClassSubject.init("int");
-                assertThat(firstInitSubject, isPresent());
-                assertThat(
-                    firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
+              MethodSubject firstInitSubject = aClassSubject.init("int");
+              assertThat(firstInitSubject, isPresent());
+              assertThat(firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
-                ClassSubject synthesizedClass = getSynthesizedArgumentClassSubject(codeInspector);
+              MethodSubject otherInitSubject = aClassSubject.init("int", "int");
+              assertThat(otherInitSubject, isPresent());
+              assertThat(otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
-                MethodSubject otherInitSubject =
-                    aClassSubject.init("int", synthesizedClass.getFinalName());
-                assertThat(otherInitSubject, isPresent());
-                assertThat(
-                    otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
+              MethodSubject printSubject = aClassSubject.method("void", "print$bridge");
+              assertThat(printSubject, isPresent());
+              assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
 
-                MethodSubject printSubject = aClassSubject.method("void", "print$bridge");
-                assertThat(printSubject, isPresent());
-                assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
-
-                assertThat(codeInspector.clazz(B.class), not(isPresent()));
+              assertThat(codeInspector.clazz(B.class), not(isPresent()));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
index 0dd1f3e..61432d2 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
@@ -37,30 +37,25 @@
         .assertSuccessWithOutputLines("7", "42", "13", "print a", "print b")
         .inspect(
             codeInspector -> {
-                ClassSubject aClassSubject = codeInspector.clazz(A.class);
-                assertThat(aClassSubject, isPresent());
-                FieldSubject classIdFieldSubject =
-                    aClassSubject.uniqueFieldWithName(ClassMerger.CLASS_ID_FIELD_NAME);
-                assertThat(classIdFieldSubject, isPresent());
+              ClassSubject aClassSubject = codeInspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              FieldSubject classIdFieldSubject =
+                  aClassSubject.uniqueFieldWithName(ClassMerger.CLASS_ID_FIELD_NAME);
+              assertThat(classIdFieldSubject, isPresent());
 
-                MethodSubject firstInitSubject = aClassSubject.init("int");
-                assertThat(firstInitSubject, isPresent());
-                assertThat(
-                    firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
+              MethodSubject firstInitSubject = aClassSubject.init("int");
+              assertThat(firstInitSubject, isPresent());
+              assertThat(firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
-                ClassSubject synthesizedClass = getSynthesizedArgumentClassSubject(codeInspector);
+              MethodSubject otherInitSubject = aClassSubject.init("int", "int");
+              assertThat(otherInitSubject, isPresent());
+              assertThat(otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
-                MethodSubject otherInitSubject =
-                    aClassSubject.init("int", synthesizedClass.getFinalName());
-                assertThat(otherInitSubject, isPresent());
-                assertThat(
-                    otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
+              MethodSubject printSubject = aClassSubject.method("void", "print$bridge");
+              assertThat(printSubject, isPresent());
+              assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
 
-                MethodSubject printSubject = aClassSubject.method("void", "print$bridge");
-                assertThat(printSubject, isPresent());
-                assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
-
-                assertThat(codeInspector.clazz(B.class), not(isPresent()));
+              assertThat(codeInspector.clazz(B.class), not(isPresent()));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java
index 6094157..d02f16a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import org.junit.Test;
@@ -53,25 +52,10 @@
               assertThat(aClassSubject, isPresent());
               assertEquals(
                   2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
-
-              ClassSubject nullArgumentClassSubject =
-                  inspector.allClasses().stream()
-                      .filter(
-                          clazz ->
-                              SyntheticItemsTestUtils.isHorizontalInitializerTypeArgument(
-                                  clazz.getOriginalReference()))
-                      .findFirst()
-                      .orElseThrow(RuntimeException::new);
-
               assertThat(
                   aClassSubject.method("void", "<init>", "java.lang.Object", "int"), isPresent());
               assertThat(
-                  aClassSubject.method(
-                      "void",
-                      "<init>",
-                      "java.lang.Object",
-                      "int",
-                      nullArgumentClassSubject.getFinalName()),
+                  aClassSubject.method("void", "<init>", "java.lang.Object", "int", "int"),
                   isPresent());
             })
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingOfInitArgumentTypesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingOfInitArgumentTypesTest.java
new file mode 100644
index 0000000..fc00578
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingOfInitArgumentTypesTest.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+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.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.BooleanUtils;
+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;
+
+@RunWith(Parameterized.class)
+public class HorizontalClassMergingOfInitArgumentTypesTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableHorizontalClassMerging;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, horizontal class merging: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> {
+              options.callSiteOptimizationOptions().setForceSyntheticsForInstanceInitializers(true);
+              options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging);
+            })
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              int expectedNumberOfSynthetics =
+                  2 - BooleanUtils.intValue(enableHorizontalClassMerging);
+              assertEquals(3 + expectedNumberOfSynthetics, inspector.allClasses().size());
+              assertThat(inspector.clazz(Main.class), isPresent());
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(inspector.clazz(B.class), isPresent());
+              assertEquals(
+                  expectedNumberOfSynthetics,
+                  inspector.allClasses().stream()
+                      .filter(
+                          clazz ->
+                              SyntheticItemsTestUtils.isExternalNonFixedInitializerTypeArgument(
+                                  clazz.getOriginalReference()))
+                      .count());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A()", "A(Object)", "B()", "B(Object)");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      String arg = args.length > 0 ? args[1] : null;
+      System.out.println(new A());
+      System.out.println(new A(arg));
+      System.out.println(new B());
+      System.out.println(new B(arg));
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class A {
+
+    boolean unused;
+
+    A() {}
+
+    // Unused argument removal will rewrite this into A(A$$ExternalSynthetic$IA0)
+    A(Object unused) {
+      this.unused = true;
+    }
+
+    @Override
+    public String toString() {
+      return unused ? "A(Object)" : "A()";
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class B {
+
+    boolean unused;
+
+    B() {}
+
+    // Unused argument removal will rewrite this into B(B$$ExternalSynthetic$IA0)
+    B(Object unused) {
+      this.unused = true;
+    }
+
+    @Override
+    public String toString() {
+      return unused ? "B(Object)" : "B()";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
index 9b45774..660dae3 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
@@ -26,6 +26,7 @@
         .addInnerClasses(getClass())
         .addProgramClasses(A.class, B.class)
         .addKeepMainRule(Main.class)
+        .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java
index a88c32b..049e3cb 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.IsNot.not;
 
+import com.android.tools.r8.KeepConstantArguments;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
@@ -24,6 +25,13 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector
+                    .assertMergedInto(C.class, A.class)
+                    .assertMergedInto(D.class, B.class)
+                    .assertNoOtherClassesMerged())
+        .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -34,14 +42,14 @@
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
               assertThat(codeInspector.clazz(B.class), isPresent());
-                // C has been merged into A.
-                assertThat(codeInspector.clazz(C.class), not(isPresent()));
-                assertThat(codeInspector.clazz(A.class).init("long"), isPresent());
+              // C has been merged into A.
+              assertThat(codeInspector.clazz(C.class), not(isPresent()));
+              assertThat(codeInspector.clazz(A.class).init("long"), isPresent());
 
-                // D has been merged into B.
-                assertThat(codeInspector.clazz(D.class), not(isPresent()));
-                ClassSubject bClassSubject = codeInspector.clazz(B.class);
-                assertThat(bClassSubject.init("boolean"), isPresent());
+              // D has been merged into B.
+              assertThat(codeInspector.clazz(D.class), not(isPresent()));
+              ClassSubject bClassSubject = codeInspector.clazz(B.class);
+              assertThat(bClassSubject.init("boolean"), isPresent());
             });
   }
 
@@ -69,6 +77,7 @@
 
   @NeverClassInline
   public static class C {
+    @KeepConstantArguments
     public C(long v) {
       System.out.println(v);
     }
@@ -76,6 +85,7 @@
 
   @NeverClassInline
   public static class D {
+    @KeepConstantArguments
     public D(boolean v) {
       System.out.println(v);
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/B.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/B.java
index 6157508..8bd1e0b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/B.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/B.java
@@ -4,11 +4,13 @@
 
 package com.android.tools.r8.classmerging.horizontal.testclasses;
 
+import com.android.tools.r8.KeepConstantArguments;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 
 @NeverClassInline
 public class B {
+  @KeepConstantArguments
   protected B(String a) {
     System.out.println(a);
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 3f95a85..b056931 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -1076,9 +1076,6 @@
             "classmerging.SimpleInterfaceAccessTest$OtherSimpleInterfaceImpl",
             "classmerging.pkg.SimpleInterfaceImplRetriever",
             "classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl");
-    if (parameters.isCfRuntime()) {
-      preservedClassNames.add("classmerging.SimpleInterfaceAccessTest$1");
-    }
     runTest(
         testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsSubstitutionTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsSubstitutionTest.java
new file mode 100644
index 0000000..27b7093
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsSubstitutionTest.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.vertical;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.KeepConstantArguments;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.lang.reflect.TypeVariable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class VerticalClassMergingWithMissingTypeArgsSubstitutionTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test()
+  public void test() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassRules(A.class)
+        .addKeepAttributeSignature()
+        .addVerticallyMergedClassesInspector(
+            inspector -> inspector.assertMergedIntoSubtype(B.class))
+        .enableInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
+        .enableConstantArgumentAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("T", "Hello World")
+        .inspect(
+            inspector -> {
+              ClassSubject classSubject = inspector.clazz(C.class);
+              assertThat(classSubject, isPresentAndRenamed());
+              assertEquals(
+                  "<T:Ljava/lang/Object;>L" + binaryName(A.class) + "<Ljava/lang/Object;>;",
+                  classSubject.getFinalSignatureAttribute());
+              MethodSubject bar = classSubject.uniqueMethodWithName("bar");
+              assertThat(bar, isPresentAndRenamed());
+              assertEquals("(TT;)V", bar.getFinalSignatureAttribute());
+              // The NeverInline is transferred to the private vertically merged method, making
+              // it hard to lookup.
+              MethodSubject movedFooSubject =
+                  classSubject.uniqueMethodThatMatches(
+                      method ->
+                          method.getMethod().getReference() != bar.getMethod().getReference()
+                              && !method.isInstanceInitializer());
+              assertThat(movedFooSubject, isPresentAndRenamed());
+              assertEquals(
+                  "(Ljava/lang/Object;)Ljava/lang/Object;",
+                  movedFooSubject.getFinalSignatureAttribute());
+            });
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      C<String> stringC = new C<>();
+      for (TypeVariable<? extends Class<? extends C>> typeParameter :
+          stringC.getClass().getTypeParameters()) {
+        System.out.println(typeParameter.getName());
+      }
+      stringC.bar("Hello World");
+    }
+  }
+
+  static class A<T> {}
+
+  static class B<T> extends A<T> {
+
+    @KeepConstantArguments
+    @NoMethodStaticizing
+    @NeverInline
+    public T foo(T t) {
+      if (System.currentTimeMillis() == 0) {
+        throw new RuntimeException("Foo");
+      }
+      return t;
+    }
+  }
+
+  static class C<T> extends B {
+
+    @NoMethodStaticizing
+    @KeepConstantArguments
+    @NeverInline
+    void bar(T t) {
+      System.out.println(foo(t));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
index b3eb86b..d1e9746 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
@@ -10,11 +10,17 @@
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.HumanToMachineSpecificationConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.LegacyToHumanSpecificationConverter;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -34,14 +40,19 @@
 
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
+  private final boolean machineSpec;
 
-  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  @Parameters(name = "machine: {0}, {2}, shrink: {1}")
   public static List<Object[]> data() {
     return buildParameters(
-        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withDexRuntimes().withAllApiLevels().build());
   }
 
-  public CustomCollectionTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+  public CustomCollectionTest(
+      boolean machineSpec, boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.machineSpec = machineSpec;
     this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
     this.parameters = parameters;
   }
@@ -49,6 +60,22 @@
   private final String EXECUTOR =
       "com.android.tools.r8.desugar.desugaredlibrary.CustomCollectionTest$Executor";
 
+  private void setMachineSpec(InternalOptions opt) {
+    if (!machineSpec) {
+      return;
+    }
+    try {
+      HumanDesugaredLibrarySpecification human =
+          new LegacyToHumanSpecificationConverter()
+              .convert(opt.desugaredLibrarySpecification, getLibraryFile(), opt);
+      MachineDesugaredLibrarySpecification machine =
+          new HumanToMachineSpecificationConverter().convert(human, getLibraryFile(), opt);
+      opt.testing.machineDesugaredLibrarySpecification = machine;
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
   @Test
   public void testCustomCollectionD8() throws Exception {
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
@@ -56,6 +83,7 @@
         testForD8()
             .addLibraryFiles(getLibraryFile())
             .addInnerClasses(CustomCollectionTest.class)
+            .addOptionsModification(this::setMachineSpec)
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
@@ -78,6 +106,7 @@
     Path jar =
         testForD8(Backend.CF)
             .addInnerClasses(CustomCollectionTest.class)
+            .addOptionsModification(this::setMachineSpec)
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
@@ -133,6 +162,7 @@
         testForR8(Backend.DEX)
             .addLibraryFiles(getLibraryFile())
             .addInnerClasses(CustomCollectionTest.class)
+            .addOptionsModification(this::setMachineSpec)
             .setMinApi(parameters.getApiLevel())
             .addKeepClassAndMembersRules(Executor.class)
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
index 606f253..2bd50b2 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
@@ -153,6 +153,15 @@
         "org/openjdk/tests/java/util/stream/FindAnyOpTest.java"
       };
 
+  private boolean streamCloseTestShouldSucceed() {
+    if (!isJDK11DesugaredLibrary()) {
+      return false;
+    }
+    // TODO(b/216047740): Investigate if this runs on Dalvik VMs.
+    // StreamCloseTest relies on suppressed exceptions which may not work on Dalvik VMs.
+    return parameters.getDexRuntimeVersion().isNewerThan(Version.V4_4_4);
+  }
+
   private Map<String, String> getSuccessfulTests() {
     Map<String, String> runnableTests = getRunnableTests(SUCCESSFUL_RUNNABLE_TESTS);
     if (isJDK11DesugaredLibrary()) {
@@ -160,9 +169,9 @@
       if (parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0)) {
         runnableTests.putAll(getRunnableTests(SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_AND_V7));
       }
-      if (!parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.K)) {
-        runnableTests.putAll(getRunnableTests(STREAM_CLOSE_TESTS));
-      }
+    }
+    if (streamCloseTestShouldSucceed()) {
+      runnableTests.putAll(getRunnableTests(STREAM_CLOSE_TESTS));
     }
     return runnableTests;
   }
@@ -172,14 +181,11 @@
     if (!isJDK11DesugaredLibrary()) {
       runnableTests.putAll(getRunnableTests(SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_ONLY));
       runnableTests.putAll(getRunnableTests(SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_AND_V7));
+    } else if (!parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0)) {
+      runnableTests.putAll(getRunnableTests(SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_AND_V7));
+    }
+    if (!streamCloseTestShouldSucceed()) {
       runnableTests.putAll(getRunnableTests(STREAM_CLOSE_TESTS));
-    } else {
-      if (parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.K)) {
-        runnableTests.putAll(getRunnableTests(STREAM_CLOSE_TESTS));
-      }
-      if (!parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0)) {
-        runnableTests.putAll(getRunnableTests(SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_AND_V7));
-      }
     }
     return runnableTests;
   }
diff --git a/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java b/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java
index 436dc43..5fe49e5 100644
--- a/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java
+++ b/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java
@@ -71,8 +71,8 @@
     String restStackTrace = fullStackTrace.substring(topStackTraceEnd);
     // Check that top stack trace always has the version marker.
     assertThat(topStackTrace, containsString("fakeStackEntry"));
-    // Check that top stack has the D8 entry (from tests the non-renamed entry is ToolHelper.runD8).
-    assertThat(topStackTrace, containsString("com.android.tools.r8.ToolHelper.runD8("));
+    // Check that top stack has the D8 entry (from tests the non-renamed entry is ToolHelper.runX).
+    assertThat(topStackTrace, containsString("com.android.tools.r8.ToolHelper.run"));
     // Check that the stack trace always has the suppressed info.
     assertThat(restStackTrace, containsString(StringUtils.LINE_SEPARATOR + "\tSuppressed:"));
     // Custom test checks.
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
index 97d8b7a..de2364e 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.internal;
 
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.startsWith;
 import static org.hamcrest.core.AnyOf.anyOf;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -122,6 +124,11 @@
         .assertAllWarningMessagesMatch(
             anyOf(
                 containsString("Expected stack map table for method with non-linear control flow."),
-                containsString("Ignoring option: -outjars")));
+                containsString("Ignoring option: -outjars"),
+                allOf(
+                    startsWith(
+                        "Rule matches the static final field "
+                            + "`java.lang.String com.google.protobuf."),
+                    containsString("which may have been inlined: -identifiernamestring"))));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index 8538449..2f98c5c 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -61,7 +61,7 @@
             .toDirect();
     appView = AppView.createForR8(program);
     appView.setAppServices(AppServices.builder(appView).build());
-    subtypingInfo = new SubtypingInfo(appView);
+    subtypingInfo = SubtypingInfo.create(appView);
   }
 
   private AppInfoWithClassHierarchy appInfo() {
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
index c0f2478..dbde5fd 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertFalse;
@@ -56,7 +55,7 @@
         .compile()
         .assertAllInfoMessagesMatch(
             containsString("Proguard configuration rule does not match anything"))
-        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .apply(this::inspectWarningMessages)
         .inspect(this::inspect)
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines(
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index 3f9e228..9ea0db9 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -82,8 +81,7 @@
             .compile()
             .assertAllInfoMessagesMatch(
                 containsString("Proguard configuration rule does not match anything"))
-            .assertAllWarningMessagesMatch(
-                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+            .apply(this::inspectWarningMessages)
             .inspect(this::inspect);
 
     for (String main : mains) {
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index fcc6f54..63dae9c 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -6,9 +6,7 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
-import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -97,8 +95,7 @@
             .compile()
             .assertAllInfoMessagesMatch(
                 containsString("Proguard configuration rule does not match anything"))
-            .assertAllWarningMessagesMatch(
-                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+            .apply(this::inspectWarningMessages)
             .inspect(
                 outputInspector -> {
                   verifyMapAndRequiredFieldsAreKept(inputInspector, outputInspector);
@@ -374,10 +371,7 @@
         .compile()
         .assertAllInfoMessagesMatch(
             containsString("Proguard configuration rule does not match anything"))
-        .assertAllWarningMessagesMatch(
-            anyOf(
-                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."),
-                containsString("required for default or static interface methods desugaring")))
+        .apply(this::inspectWarningMessages)
         .inspect(
             inspector ->
                 assertRewrittenProtoSchemasMatch(new CodeInspector(PROGRAM_FILES), inspector));
@@ -405,10 +399,7 @@
         .compile()
         .assertAllInfoMessagesMatch(
             containsString("Proguard configuration rule does not match anything"))
-        .assertAllWarningMessagesMatch(
-            anyOf(
-                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."),
-                containsString("required for default or static interface methods desugaring")))
+        .apply(this::inspectWarningMessages)
         .inspect(
             outputInspector -> {
               verifyUnusedExtensionsAreRemoved(inputInspector, outputInspector);
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
index c0ce178..23d7007 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
@@ -5,10 +5,16 @@
 package com.android.tools.r8.internal.proto;
 
 import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.getInfoValueFromMessageInfoConstructionInvoke;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.startsWith;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+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;
@@ -136,4 +142,16 @@
     }
     return result;
   }
+
+  void inspectWarningMessages(R8TestCompileResult compileResult) {
+    compileResult.assertAllWarningMessagesMatch(
+        anyOf(
+            equalTo("Resource 'META-INF/MANIFEST.MF' already exists."),
+            allOf(
+                startsWith(
+                    "Rule matches the static final field `java.lang.String com.google."
+                        + "protobuf.proto2_registryGeneratedExtensionRegistryLite."
+                        + "CONTAINING_TYPE_"),
+                containsString("`, which may have been inlined: -identifiernamestring"))));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index 6f5d826..be71b20 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -74,7 +74,7 @@
     AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(application.asDirect());
     appView.setAppServices(AppServices.builder(appView).build());
     ExecutorService executorService = ThreadUtils.getExecutorService(options);
-    SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
+    SubtypingInfo subtypingInfo = SubtypingInfo.create(appView);
     appView.setRootSet(
         RootSet.builder(
                 appView,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerNullableStaticGetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerNullableStaticGetTest.java
new file mode 100644
index 0000000..0a6e376
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerNullableStaticGetTest.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassInlinerNullableStaticGetTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Singleton.get().greet();
+      Singleton.set();
+      if (System.currentTimeMillis() < 0) {
+        System.out.println(new Singleton());
+      }
+    }
+  }
+
+  @NoVerticalClassMerging
+  static class Singleton {
+
+    static Singleton INSTANCE;
+
+    static Singleton get() {
+      return INSTANCE;
+    }
+
+    static void set() {
+      INSTANCE = new SingletonImpl();
+    }
+
+    void greet() {
+      System.out.println("Hello!");
+    }
+  }
+
+  static class SingletonImpl extends Singleton {}
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index 5987d3c..15f68fc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -183,8 +183,7 @@
         Lists.newArrayList(
             "STATIC: String SimpleWithSideEffects.bar(String)",
             "STATIC: String SimpleWithSideEffects.foo()",
-            "STATIC: String TrivialTestClass.next()",
-            "SimpleWithSideEffects SimpleWithSideEffects.INSTANCE"),
+            "STATIC: String TrivialTestClass.next()"),
         references(clazz, "testSimpleWithSideEffects", "void"));
 
     ClassSubject simpleWithSideEffects = inspector.clazz(SimpleWithSideEffects.class);
@@ -315,8 +314,7 @@
         Lists.newArrayList(
             "STATIC: String movetohost.CandidateOkSideEffects.bar(String)",
             "STATIC: String movetohost.CandidateOkSideEffects.foo()",
-            "STATIC: String movetohost.MoveToHostTestClass.next()",
-            "movetohost.CandidateOkSideEffects movetohost.HostOkSideEffects.INSTANCE"),
+            "STATIC: String movetohost.MoveToHostTestClass.next()"),
         references(clazz, "testOkSideEffects", "void"));
 
     assertThat(inspector.clazz(HostOkSideEffects.class), isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/ConstantArgumentUpdateGenericSignatureTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/ConstantArgumentUpdateGenericSignatureTest.java
new file mode 100644
index 0000000..2619ab8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/ConstantArgumentUpdateGenericSignatureTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.unusedarguments;
+
+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.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConstantArgumentUpdateGenericSignatureTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAttributeSignature()
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World")
+        .inspect(
+            inspector -> {
+              ClassSubject classA = inspector.clazz(A.class);
+              assertThat(classA, isPresentAndRenamed());
+              MethodSubject foo =
+                  classA.uniqueMethodThatMatches(method -> !method.isInstanceInitializer());
+              assertThat(foo, isPresent());
+              assertNull(foo.getFinalSignatureAttribute());
+              assertEquals("void a()", foo.getFinalSignature().toString());
+            });
+  }
+
+  @NeverClassInline
+  public static class A<T> {
+
+    @NeverInline
+    @NoMethodStaticizing
+    public void foo(T t) {
+      System.out.println(t);
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A<String>().foo("Hello World");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/ConstantArgumentWithUnusedArgumentUpdateGenericSignatureTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/ConstantArgumentWithUnusedArgumentUpdateGenericSignatureTest.java
new file mode 100644
index 0000000..ea624dc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/ConstantArgumentWithUnusedArgumentUpdateGenericSignatureTest.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.unusedarguments;
+
+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.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConstantArgumentWithUnusedArgumentUpdateGenericSignatureTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAttributeSignature()
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World", "0")
+        .inspect(
+            inspector -> {
+              ClassSubject classA = inspector.clazz(A.class);
+              assertThat(classA, isPresentAndRenamed());
+              MethodSubject foo =
+                  classA.uniqueMethodThatMatches(method -> !method.isInstanceInitializer());
+              assertThat(foo, isPresent());
+              assertNull(foo.getFinalSignatureAttribute());
+              assertEquals("void a(int)", foo.getFinalSignature().toString());
+            });
+  }
+
+  @NeverClassInline
+  public static class A<T> {
+
+    @NeverInline
+    @NoMethodStaticizing
+    public void foo(T t, int count, T unused) {
+      System.out.println(t);
+      System.out.println(count);
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A<String>().foo("Hello World", args.length, args.length > 0 ? args[0] : "Hello World");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/StaticizeUpdateGenericSignatureTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/StaticizeUpdateGenericSignatureTest.java
new file mode 100644
index 0000000..da960e7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/StaticizeUpdateGenericSignatureTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.unusedarguments;
+
+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.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.KeepConstantArguments;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StaticizeUpdateGenericSignatureTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAttributeSignature()
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
+        .enableConstantArgumentAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World")
+        .inspect(
+            inspector -> {
+              ClassSubject classA = inspector.clazz(A.class);
+              assertThat(classA, isPresentAndRenamed());
+              MethodSubject foo = classA.uniqueMethod();
+              assertThat(foo, isPresent());
+              assertNull(foo.getFinalSignatureAttribute());
+              assertEquals(
+                  "java.lang.String a(java.lang.Object)", foo.getFinalSignature().toString());
+            });
+  }
+
+  @NeverClassInline
+  public static class A<T> {
+
+    @NeverInline
+    @NeverPropagateValue
+    @KeepConstantArguments
+    public String foo(T t) {
+      return "Hello " + t;
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new A<String>().foo("World"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentUpdateGenericSignatureTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentUpdateGenericSignatureTest.java
new file mode 100644
index 0000000..4ebb10e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentUpdateGenericSignatureTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.unusedarguments;
+
+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.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoMethodStaticizing;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class UnusedArgumentUpdateGenericSignatureTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAttributeSignature()
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
+        .enableNoMethodStaticizingAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World")
+        .inspect(
+            inspector -> {
+              ClassSubject classA = inspector.clazz(A.class);
+              assertThat(classA, isPresentAndRenamed());
+              MethodSubject foo =
+                  classA.uniqueMethodThatMatches(method -> !method.isInstanceInitializer());
+              assertThat(foo, isPresent());
+              assertNull(foo.getFinalSignatureAttribute());
+              assertEquals("java.lang.String a()", foo.getFinalSignature().toString());
+            });
+  }
+
+  @NeverClassInline
+  public static class A<T> {
+
+    @NeverInline
+    @NeverPropagateValue
+    @NoMethodStaticizing
+    public String foo(T t) {
+      return "Hello World";
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new A<String>().foo(args.length > 0 ? args[0] : "Hello World!"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index b05928d..24f1672 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -34,6 +35,7 @@
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Set;
+import java.util.function.Consumer;
 import org.junit.Test;
 
 public class RegisterMoveSchedulerTest {
@@ -84,8 +86,11 @@
     }
 
     @Override
-    public boolean replaceCurrentInstructionByInitClassIfPossible(
-        AppView<AppInfoWithLiveness> appView, IRCode code, DexType type) {
+    public boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
+        AppView<AppInfoWithLiveness> appView,
+        IRCode code,
+        DexType type,
+        Consumer<InitClass> consumer) {
       throw new Unimplemented();
     }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRawTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRawTest.java
new file mode 100644
index 0000000..7686ac3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRawTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2022, 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.MIN_SUPPORTED_VERSION;
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
+
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.kotlin.metadata.type_raw_lib.JavaLibraryClass;
+import com.android.tools.r8.kotlin.metadata.type_raw_lib.JavaLibraryClass.GenericClass;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteRawTest extends KotlinMetadataTestBase {
+
+  private final String EXPECTED = StringUtils.lines("Hello World");
+  private static final String PKG_LIB = PKG + ".type_raw_lib";
+  private static final String PKG_APP = PKG + ".type_raw_app";
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(),
+        getKotlinTestParameters()
+            .withCompilersStartingFromIncluding(MIN_SUPPORTED_VERSION)
+            .withAllTargetVersions()
+            .build());
+  }
+
+  public MetadataRewriteRawTest(TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
+    this.parameters = parameters;
+  }
+
+  private static Path javaLibZip;
+
+  @BeforeClass
+  public static void before() throws Exception {
+    Path zipFile = getStaticTemp().newFolder().toPath().resolve("out.jar");
+    javaLibZip =
+        ZipBuilder.builder(zipFile)
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(JavaLibraryClass.class),
+                ToolHelper.getClassFileForTestClass(GenericClass.class))
+            .build();
+  }
+
+  private static final KotlinCompileMemoizer libJars =
+      getCompileMemoizer(getKotlinFileInTest(getBinaryNameFromJavaType(PKG_LIB), "lib"))
+          .configure(kotlinCompilerTool -> kotlinCompilerTool.addClasspathFiles(javaLibZip));
+  private final TestParameters parameters;
+
+  @Test
+  public void smokeTest() throws Exception {
+    Path libJar = libJars.getForConfiguration(kotlinc, targetVersion);
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+            .addClasspathFiles(libJar, javaLibZip)
+            .addSourceFiles(getKotlinFileInTest(getBinaryNameFromJavaType(PKG_APP), "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar, javaLibZip)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testMetadataForLib() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addClasspathFiles(
+                kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar(), javaLibZip)
+            .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
+            .addKeepRules("-keep class " + PKG_LIB + ".ClassWithRawType { *; }")
+            .addKeepAttributes(
+                ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+                ProguardKeepAttributes.SIGNATURE,
+                ProguardKeepAttributes.INNER_CLASSES,
+                ProguardKeepAttributes.ENCLOSING_METHOD)
+            .compile()
+            .inspect(
+                inspector ->
+                    assertEqualMetadata(
+                        new CodeInspector(libJars.getForConfiguration(kotlinc, targetVersion)),
+                        inspector,
+                        (addedStrings, addedNonInitStrings) -> {}))
+            .writeToZip();
+    Path main =
+        kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+            .addClasspathFiles(libJar, javaLibZip)
+            .addSourceFiles(getKotlinFileInTest(getBinaryNameFromJavaType(PKG_APP), "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(
+            kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar(), libJar, javaLibZip)
+        .addClasspath(main)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/type_raw_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/type_raw_app/main.kt
new file mode 100644
index 0000000..7cb36c2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/type_raw_app/main.kt
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, 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.kotlin.metadata.type_raw_app
+
+import com.android.tools.r8.kotlin.metadata.type_raw_lib.ClassWithRawType
+
+fun main() {
+  println(ClassWithRawType().returnsRaw())
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/type_raw_lib/JavaLibraryClass.java b/src/test/java/com/android/tools/r8/kotlin/metadata/type_raw_lib/JavaLibraryClass.java
new file mode 100644
index 0000000..a46422e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/type_raw_lib/JavaLibraryClass.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2022, 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.kotlin.metadata.type_raw_lib;
+
+public abstract class JavaLibraryClass {
+
+  public static GenericClass getGeneric() {
+    return new GenericClass();
+  }
+
+  public static class GenericClass<T> {
+
+    @Override
+    public String toString() {
+      return "Hello World";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/type_raw_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/type_raw_lib/lib.kt
new file mode 100644
index 0000000..df9a05a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/type_raw_lib/lib.kt
@@ -0,0 +1,10 @@
+// Copyright (c) 2020, 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.kotlin.metadata.type_raw_lib
+
+class ClassWithRawType {
+
+  fun returnsRaw() = JavaLibraryClass.getGeneric();
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingSuperTest.java b/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingSuperTest.java
new file mode 100644
index 0000000..1fade81
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingSuperTest.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2022, 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.memberrebinding;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.utils.AndroidApiLevel.B;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.apimodel.ApiModelingTestHelper;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/215573892.
+@RunWith(Parameterized.class)
+public class LibraryMemberRebindingSuperTest extends TestBase {
+
+  private final String EXPECTED =
+      StringUtils.lines("ProgramClass::foo", "LibrarySub::foo", "LibraryBase::foo");
+  private final String R8_INVALID = StringUtils.lines("ProgramClass::foo", "LibraryBase::foo");
+
+  private final Class<?>[] LIBRARY_CLASSES =
+      new Class<?>[] {LibraryBase.class, LibrarySub.class, LibrarySubSub.class};
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addLibraryClasses(LIBRARY_CLASSES)
+        .addDefaultRuntimeLibrary(parameters)
+        .addProgramClasses(ProgramClass.class, Main.class)
+        .applyIf(
+            parameters.isDexRuntime(),
+            builder -> builder.addRunClasspathFiles(buildOnDexRuntime(parameters, LIBRARY_CLASSES)))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryClasses(LIBRARY_CLASSES)
+        .addDefaultRuntimeLibrary(parameters)
+        .addProgramClasses(ProgramClass.class, Main.class)
+        .apply(setMockApiLevelForMethod(LibraryBase.class.getDeclaredMethod("foo"), B))
+        .apply(setMockApiLevelForMethod(LibrarySub.class.getDeclaredMethod("foo"), B))
+        .apply(setMockApiLevelForClass(LibraryBase.class, B))
+        .apply(setMockApiLevelForClass(LibrarySub.class, B))
+        .apply(setMockApiLevelForClass(LibrarySubSub.class, B))
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
+        .addKeepAllClassesRule()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathClasses(LIBRARY_CLASSES)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  public static class LibraryBase {
+
+    public void foo() {
+      System.out.println("LibraryBase::foo");
+    }
+  }
+
+  public static class LibrarySub extends LibraryBase {
+
+    @Override
+    public void foo() {
+      System.out.println("LibrarySub::foo");
+      super.foo();
+    }
+  }
+
+  public static class LibrarySubSub extends LibrarySub {}
+
+  public static class ProgramClass extends LibrarySubSub {
+
+    @Override
+    public void foo() {
+      System.out.println("ProgramClass::foo");
+      super.foo();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new ProgramClass().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java
index 24c31c2..a86f95a 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java
@@ -72,7 +72,12 @@
               assertThat(
                   getSystemService,
                   CodeMatchers.invokesMethodWithHolderAndName(
-                      typeName(LibrarySub.class), "getSystemService"));
+                      typeName(
+                          parameters.isCfRuntime()
+                                  || parameters.getApiLevel().isLessThan(AndroidApiLevel.N)
+                              ? LibrarySubSub.class
+                              : LibrarySub.class),
+                      "getSystemService"));
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("LibrarySub::getSystemService");
diff --git a/src/test/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringStaticFinalFieldTest.java b/src/test/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringStaticFinalFieldTest.java
new file mode 100644
index 0000000..6d0c21d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringStaticFinalFieldTest.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2022, 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.identifiernamestring;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IdentifierNameStringStaticFinalFieldTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(Backend.DEX)
+        .addInnerClasses(getClass())
+        .addKeepClassAndMembersRules(Main.class)
+        .addKeepRules("-identifiernamestring class * { java.lang.String CLASS_NAME; }")
+        .allowDiagnosticWarningMessages()
+        .setMinApi(AndroidApiLevel.B)
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics.assertWarningsMatch(
+                    diagnosticMessage(
+                        equalTo(
+                            StringUtils.joinLines(
+                                "Rule matches the static final field `java.lang.String "
+                                    + Main.class.getTypeName()
+                                    + ".CLASS_NAME`, which may have been inlined:"
+                                    + " -identifiernamestring class * {",
+                                "  java.lang.String CLASS_NAME;",
+                                "}")))));
+  }
+
+  static class Main {
+
+    // @IdentifierNameString
+    public static final String CLASS_NAME = "Foo";
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineConditionTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineConditionTest.java
new file mode 100644
index 0000000..15466f7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineConditionTest.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2022, 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.retrace;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceInlineConditionTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Throwable {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(Main.class)
+            .compile()
+            .inspectProguardMap(
+                map -> {
+                  // TODO(b/215339687): We should not have a rewriteFrame in the mapping file since
+                  //  an explicit null check should be inserted.
+                  assertThat(map, CoreMatchers.containsString("com.android.tools.r8.rewriteFrame"));
+                });
+  }
+
+  static class Foo {
+
+    void inlinable(boolean loop) {
+      while (loop) {}
+      String string = toString();
+      System.out.println(string);
+    }
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      Foo foo = (args.length == 0 ? null : new Foo());
+      foo.inlinable(args.length == 0);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckInlinedTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckInlinedTest.java
new file mode 100644
index 0000000..6143522
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckInlinedTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2022, 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.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceInlineeWithNullCheckInlinedTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean throwReceiverNpe;
+
+  @Parameters(name = "{0}, throwReceiverNpe: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        BooleanUtils.values());
+  }
+
+  public StackTrace expectedStackTrace;
+
+  @Before
+  public void setup() throws Exception {
+    // Get the expected stack trace by running on the JVM.
+    expectedStackTrace =
+        testForRuntime(parameters)
+            .addProgramClasses(Caller.class, Foo.class)
+            .run(parameters.getRuntime(), Caller.class, getArgs())
+            .getStackTrace();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Caller.class)
+        .addKeepAttributeLineNumberTable()
+        .addKeepAttributeSourceFile()
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
+        .run(parameters.getRuntime(), Caller.class, getArgs())
+        .assertFailureWithErrorThatThrows(NullPointerException.class)
+        .inspectStackTrace(
+            (stackTrace, inspector) -> {
+              ClassSubject callerClass = inspector.clazz(Caller.class);
+              assertThat(callerClass, isPresent());
+              MethodSubject staticized = callerClass.uniqueMethodWithName("outerCaller");
+              assertThat(staticized, isPresentAndRenamed());
+              // TODO(b/214377135): The stack traces should be the same (when 206427323) is
+              // resolved.
+              assertThat(stackTrace, notIf(isSame(expectedStackTrace), throwReceiverNpe));
+            });
+  }
+
+  private String[] getArgs() {
+    return throwReceiverNpe ? new String[] {"Foo"} : new String[0];
+  }
+
+  static class Foo {
+    @NeverInline
+    @NoMethodStaticizing
+    Object notInlinable() {
+      System.out.println("Hello, world!");
+      throw new NullPointerException("Foo");
+    }
+
+    Object inlinable() {
+      return notInlinable();
+    }
+  }
+
+  static class Caller {
+
+    static void caller(Foo f) {
+      Object inlinable = f.inlinable();
+      System.out.println(inlinable);
+    }
+
+    @NeverInline
+    public static void outerCaller(Foo f) {
+      // caller should be inlined here as is. When inlinable is inlined into caller, it is done so
+      // without synthesizing a null check since the call notInlineable will throw an NPE if not
+      // staticized. We have this outer caller to check that stack traces still work correctly
+      // when the bit on throwing NPE is inlined.
+      caller(f);
+    }
+
+    public static void main(String[] args) {
+      outerCaller(args.length == 0 ? new Foo() : null);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CheckNotZeroMethodWithArgumentRemovalTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CheckNotZeroMethodWithArgumentRemovalTest.java
index be25553..3685b5c 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CheckNotZeroMethodWithArgumentRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CheckNotZeroMethodWithArgumentRemovalTest.java
@@ -50,8 +50,7 @@
               MethodSubject checkNotNullSubject =
                   mainClassSubject.uniqueMethodWithName("checkNotNull");
               assertThat(checkNotNullSubject, isPresent());
-              // TODO(b/199864962): Allow parameter removal from check-not-null classified methods.
-              assertEquals(2, checkNotNullSubject.getProgramMethod().getReference().getArity());
+              assertEquals(1, checkNotNullSubject.getProgramMethod().getReference().getArity());
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithEmptyOutput();
diff --git a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
index 9a81a6a..eb1e0bf 100644
--- a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
@@ -4,9 +4,10 @@
 
 package com.android.tools.r8.regress.b69825683;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -62,19 +63,16 @@
             .inspector();
 
     List<FoundClassSubject> classes = inspector.allClasses();
-
-    // Check that the synthetic class is still present when generating class files.
-    assertEquals(parameters.isCfRuntime() ? 3 : 2, classes.size());
-    assertEquals(
-        parameters.isCfRuntime(),
-        classes.stream()
-            .map(FoundClassSubject::getOriginalName)
-            .anyMatch(name -> name.endsWith("$1")));
+    assertEquals(2, classes.size());
+    assertThat(inspector.clazz(inner), isPresent());
+    assertThat(inspector.clazz(outer), isPresent());
   }
 
   @Test
   public void innerConstructsOuter() throws Exception {
     Class<?> clazz = com.android.tools.r8.regress.b69825683.innerconstructsouter.Outer.class;
+    Class<?> innerClass =
+        com.android.tools.r8.regress.b69825683.innerconstructsouter.Outer.Inner.class;
     CodeInspector inspector =
         testForR8(parameters.getBackend())
             .addProgramFiles(ToolHelper.getClassFilesForTestPackage(clazz.getPackage()))
@@ -94,9 +92,7 @@
 
     List<FoundClassSubject> classes = inspector.allClasses();
     assertEquals(2, classes.size());
-    assertTrue(
-        classes.stream()
-            .map(FoundClassSubject::getOriginalName)
-            .noneMatch(name -> name.endsWith("$1")));
+    assertThat(inspector.clazz(clazz), isPresent());
+    assertThat(inspector.clazz(innerClass), isPresent());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeSuperChainTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeSuperChainTest.java
new file mode 100644
index 0000000..3bd714f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeSuperChainTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2022, 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.resolution;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import org.junit.Assert;
+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;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class InvokeSuperChainTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    // This tests shows that invoke super behaves differently on V5_1_1 and V6_0_1 based on the
+    // static receiver on invoke-super, skipping the direct resolution target.
+    boolean hasIncorrectSuperInvokeSemantics =
+        parameters.isDexRuntimeVersion(Version.V5_1_1)
+            || parameters.isDexRuntimeVersion(Version.V6_0_1);
+    testForRuntime(parameters)
+        .addProgramClasses(A.class, Main.class)
+        .addProgramClassFileData(
+            rewriteBarSuperInvokeToA(B.class), rewriteBarSuperInvokeToA(C.class))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLinesIf(
+            !hasIncorrectSuperInvokeSemantics,
+            "C::foo",
+            "B::foo",
+            "A::foo",
+            "C::bar",
+            "B::bar",
+            "A::bar")
+        .assertSuccessWithOutputLinesIf(
+            hasIncorrectSuperInvokeSemantics, "C::foo", "B::foo", "A::foo", "C::bar", "A::bar");
+  }
+
+  private byte[] rewriteBarSuperInvokeToA(Class<?> clazz) throws Exception {
+    return transformer(clazz)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (!name.equals("println")) {
+                Assert.assertEquals(Opcodes.INVOKESPECIAL, opcode);
+                Assert.assertEquals("bar", name);
+                visitor.visitMethodInsn(opcode, binaryName(A.class), name, descriptor, isInterface);
+              } else {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
+  }
+
+  public static class A {
+
+    public void foo() {
+      System.out.println("A::foo");
+    }
+
+    public void bar() {
+      System.out.println("A::bar");
+    }
+  }
+
+  public static class B extends A {
+
+    @Override
+    public void foo() {
+      System.out.println("B::foo");
+      super.foo();
+    }
+
+    @Override
+    public void bar() {
+      System.out.println("B::bar");
+      super.bar();
+    }
+  }
+
+  public static class C extends B {
+
+    @Override
+    public void foo() {
+      System.out.println("C::foo");
+      super.foo();
+    }
+
+    @Override
+    public void bar() {
+      System.out.println("C::bar");
+      super.bar();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new C().foo();
+      new C().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index 6a95824..f53c113 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -11,6 +11,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8FullTestBuilder;
@@ -19,6 +20,9 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -28,7 +32,6 @@
 import com.android.tools.r8.utils.graphinspector.GraphInspector;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import com.google.common.collect.Sets.SetView;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
@@ -269,7 +272,8 @@
             .apply(this::configureHorizontalClassMerging)
             .compile()
             .graphInspector();
-    assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector);
+    assertRetainedClassesEqual(
+        referenceInspector, ifHasMemberThenKeepClassInspector, true, true, true, true);
   }
 
   private void configureHorizontalClassMerging(R8FullTestBuilder testBuilder) {
@@ -289,14 +293,16 @@
 
   private void assertRetainedClassesEqual(
       GraphInspector referenceResult, GraphInspector conditionalResult) {
-    assertRetainedClassesEqual(referenceResult, conditionalResult, false, false);
+    assertRetainedClassesEqual(referenceResult, conditionalResult, false, false, false, false);
   }
 
   private void assertRetainedClassesEqual(
       GraphInspector referenceResult,
       GraphInspector conditionalResult,
       boolean expectReferenceIsLarger,
-      boolean expectConditionalIsLarger) {
+      boolean expectReferenceIsLargerOnlyBySynthetics,
+      boolean expectConditionalIsLarger,
+      boolean expectConditionalIsLargerOnlyBySynthetics) {
     Set<String> referenceClasses =
         new TreeSet<>(
             referenceResult.codeInspector().allClasses().stream()
@@ -308,9 +314,13 @@
             .map(FoundClassSubject::getOriginalName)
             .collect(Collectors.toSet());
     {
-      SetView<String> notInReference = Sets.difference(conditionalClasses, referenceClasses);
+      Set<String> notInReference =
+          new TreeSet<>(Sets.difference(conditionalClasses, referenceClasses));
       if (expectConditionalIsLarger) {
         assertFalse("Expected classes in -if rule to retain more.", notInReference.isEmpty());
+        if (expectConditionalIsLargerOnlyBySynthetics) {
+          assertAllClassesAreSynthetics(notInReference);
+        }
       } else {
         assertEquals(
             "Classes in -if rule that are not in -keepclassmembers rule",
@@ -319,17 +329,28 @@
       }
     }
     {
-      SetView<String> notInConditional = Sets.difference(referenceClasses, conditionalClasses);
+      Set<String> notInConditional =
+          new TreeSet<>(Sets.difference(referenceClasses, conditionalClasses));
       if (expectReferenceIsLarger) {
         assertFalse(
             "Expected classes in -keepclassmembers rule to retain more.",
             notInConditional.isEmpty());
+        if (expectReferenceIsLargerOnlyBySynthetics) {
+          assertAllClassesAreSynthetics(notInConditional);
+        }
       } else {
         assertEquals(
             "Classes in -keepclassmembers rule that are not in -if rule",
             Collections.emptySet(),
-            new TreeSet<>(notInConditional));
+            notInConditional);
       }
     }
   }
+
+  private void assertAllClassesAreSynthetics(Set<String> classNames) {
+    for (String className : classNames) {
+      ClassReference classReference = Reference.classFromTypeName(className);
+      assertTrue(className, SyntheticItemsTestUtils.isExternalSynthetic(classReference));
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
index 0040143..63e5bc9 100644
--- a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
@@ -34,7 +34,7 @@
     dexItemFactory = program.dexItemFactory;
     AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(program);
     appInfo = appView.appInfo();
-    subtypingInfo = new SubtypingInfo(appView);
+    subtypingInfo = SubtypingInfo.create(appView);
   }
 
   private void validateSubtype(DexType super_type, DexType sub_type) {
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
index fdea6ab..84b88e6 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.KeepConstantArguments;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoFieldTypeStrengthening;
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -84,6 +85,7 @@
         .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoFieldTypeStrengtheningAnnotations()
         .run(parameters.getRuntime(), KeptClass.class)
         .assertSuccessWithOutputLines(EXPECTED)
         .inspect(inspector -> inspect(inspector, keptForNotKept));
@@ -137,7 +139,7 @@
   @NeverClassInline
   public static class NotKeptClass<P> {
 
-    public List<P> notKeptField;
+    @NoFieldTypeStrengthening public List<P> notKeptField;
 
     @KeepConstantArguments
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitInlineForGetterTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitInlineForGetterTest.java
new file mode 100644
index 0000000..b1e3efd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitInlineForGetterTest.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2022, 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.shaking.clinit;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassInitInlineForGetterTest extends TestBase {
+
+  private static final String EXPECTED = "Hello World";
+  private static final String R8_RESULT = "Goodbye World";
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addKeepRules("-keep class " + typeName(B.class) + " { <fields>; }")
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(R8_RESULT);
+  }
+
+  @NeverClassInline
+  public static class B {
+
+    public static boolean TEST = System.currentTimeMillis() == 0;
+
+    @NeverInline
+    public boolean getTest() {
+      return TEST;
+    }
+  }
+
+  public static class A {
+
+    static {
+      B.TEST = true;
+    }
+
+    @NeverInline
+    public static void triggerClassAInit(boolean b) {
+      if (b) {
+        System.out.println("Hello World");
+      } else {
+        System.out.println("Goodbye World");
+      }
+    }
+
+    public static void inlinable(B b) {
+      if (b == null) {
+        triggerClassAInit(false);
+        return;
+      }
+      triggerClassAInit(b.getTest());
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A.inlinable(new B());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index bb46834..8cc71c8 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -122,6 +122,11 @@
     return SyntheticNaming.isSynthetic(reference, null, SyntheticKind.INIT_TYPE_ARGUMENT);
   }
 
+  public static boolean isExternalNonFixedInitializerTypeArgument(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(
+        reference, Phase.EXTERNAL, SyntheticKind.NON_FIXED_INIT_TYPE_ARGUMENT);
+  }
+
   public static boolean isHorizontalInitializerTypeArgument(ClassReference reference) {
     return SyntheticNaming.isSynthetic(
             reference, null, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_1)
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
index bd9081e..8a0bab6 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -12,6 +12,7 @@
 
   public abstract Signature getFinalSignature();
 
+  @Override
   public String getOriginalName() {
     return getOriginalName(true);
   }
diff --git a/tools/as_utils.py b/tools/as_utils.py
index 43153c6..4087137 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -3,11 +3,11 @@
 # 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.
 
-import utils
-
-from distutils.version import LooseVersion
 import os
 import shutil
+from distutils.version import LooseVersion
+
+import utils
 
 if utils.is_python3():
   from html.parser import HTMLParser
@@ -174,7 +174,7 @@
     shutil.rmtree(dst)
   elif os.path.isfile(dst):
     os.remove(dst)
-  os.rename(src, dst)
+  shutil.move(src, dst)
 
 def MoveDir(src, dst, quiet=False):
   assert os.path.isdir(src)
diff --git a/tools/golem_build.py b/tools/golem_build.py
index 24a1906..c691293 100755
--- a/tools/golem_build.py
+++ b/tools/golem_build.py
@@ -5,15 +5,33 @@
 
 # Utility script to make it easier to update what golem builds.
 
-import gradle
 import sys
 
+import gradle
+import retrace_benchmark
+import run_benchmark
+import run_on_app_dump
+
 GRADLE_ARGS = ['--no-daemon', '-Pno_internal']
-BUILD_TARGETS = ['R8', 'D8', 'R8Lib', 'buildExampleJars',
-                 'downloadAndroidCts', 'downloadDx']
+
+LEGACY_BUILD_TARGETS = [
+  'R8',
+  'D8',
+  'buildExampleJars',
+  'downloadAndroidCts',
+  'downloadDx']
+
+def lower(items):
+  return [ item.lower() for item in items ]
 
 def Main():
-  gradle.RunGradle(GRADLE_ARGS + BUILD_TARGETS)
+  targets = set()
+  targets.update(lower(LEGACY_BUILD_TARGETS))
+  targets.update(lower(retrace_benchmark.GOLEM_BUILD_TARGETS))
+  targets.update(lower(run_benchmark.GOLEM_BUILD_TARGETS))
+  targets.update(lower(run_on_app_dump.GOLEM_BUILD_TARGETS))
+  cmd = GRADLE_ARGS + [target for target in targets]
+  gradle.RunGradle(cmd)
 
 if __name__ == '__main__':
   sys.exit(Main())
diff --git a/tools/r8_release.py b/tools/r8_release.py
index d60baa0..53a7dc9 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -324,8 +324,9 @@
 
 
 def download_file(version, file, dst):
+  dir = 'raw' if len(version) != 40 else 'raw/main'
   urllib.request.urlretrieve(
-      ('https://storage.googleapis.com/r8-releases/raw/%s/%s' % (version, file)),
+      ('https://storage.googleapis.com/r8-releases/%s/%s/%s' % (dir, version, file)),
       dst)
 
 def download_gfile(gfile, dst):
@@ -842,8 +843,7 @@
     sys.exit(1)
 
   if args.version and not 'dev' in args.version and args.google3:
-    print("You should not roll a release version into google 3")
-    sys.exit(1)
+    print("WARNING: You should not roll a release version into google 3")
 
   return args
 
diff --git a/tools/retrace_benchmark.py b/tools/retrace_benchmark.py
index e29bb86..42ed792 100755
--- a/tools/retrace_benchmark.py
+++ b/tools/retrace_benchmark.py
@@ -14,6 +14,7 @@
 import proguard
 import utils
 
+GOLEM_BUILD_TARGETS = ['R8Lib']
 RETRACERS = ['r8', 'proguard', 'remapper']
 
 def parse_arguments(argv):
diff --git a/tools/run_benchmark.py b/tools/run_benchmark.py
new file mode 100755
index 0000000..2ad5200
--- /dev/null
+++ b/tools/run_benchmark.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022, 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.
+
+import argparse
+import os
+import subprocess
+import sys
+
+import gradle
+import jdk
+import utils
+
+NONLIB_BUILD_TARGET = 'R8WithRelocatedDeps'
+NONLIB_TEST_BUILD_TARGETS = [utils.R8_TESTS_TARGET, utils.R8_TESTS_DEPS_TARGET]
+
+R8LIB_BUILD_TARGET = utils.R8LIB
+R8LIB_TEST_BUILD_TARGETS = [utils.R8LIB_TESTS_TARGET, utils.R8LIB_TESTS_DEPS_TARGET]
+
+# The r8lib target is always the golem target.
+GOLEM_BUILD_TARGETS = [R8LIB_BUILD_TARGET] + R8LIB_TEST_BUILD_TARGETS
+
+def get_jdk_home(options, benchmark):
+  if options.golem:
+    return os.path.join('benchmarks', benchmark, 'linux')
+  return None
+
+def parse_options(argv):
+  result = argparse.ArgumentParser(description = 'Run test-based benchmarks.')
+  result.add_argument('--golem',
+                      help='Indicate this as a run on golem',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--benchmark',
+                      help='The test benchmark to run',
+                      required=True)
+  result.add_argument('--target',
+                      help='The test target to run',
+                      required=True,
+                      # These should 1:1 with BenchmarkTarget.java
+                      choices=['d8', 'r8', 'r8-force', 'r8-compat'])
+  result.add_argument('--nolib', '--no-lib', '--no-r8lib',
+                      help='Run the non-lib R8 build (default false)',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--no-build', '--no_build',
+                      help='Run without building first (default false)',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--enable-assertions', '--enable_assertions', '-ea',
+                      help='Enable assertions when running',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--print-times',
+                      help='Print timing information from r8',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--temp',
+                      help='A directory to use for temporaries and outputs.',
+                      default=None)
+  return result.parse_known_args(argv)
+
+def main(argv):
+  (options, args) = parse_options(argv)
+
+  if options.golem:
+    options.no_build = True
+    if options.nolib:
+      print("Error: golem should always run r8lib")
+      return 1
+
+  if options.nolib:
+    buildTargets = [NONLIB_BUILD_TARGET] + NONLIB_TEST_BUILD_TARGETS
+    r8jar = utils.R8_WITH_RELOCATED_DEPS_JAR
+    testjars = [utils.R8_TESTS_DEPS_JAR, utils.R8_TESTS_JAR]
+  else:
+    buildTargets = GOLEM_BUILD_TARGETS
+    r8jar = utils.R8LIB_JAR
+    testjars = [utils.R8LIB_TESTS_DEPS_JAR, utils.R8LIB_TESTS_JAR]
+
+  if not options.no_build:
+    gradle.RunGradle(buildTargets + ['-Pno_internal'])
+
+  return run(options, r8jar, testjars)
+
+def run(options, r8jar, testjars):
+  jdkhome = get_jdk_home(options, options.benchmark)
+  cmd = [jdk.GetJavaExecutable(jdkhome)]
+  if options.enable_assertions:
+    cmd.append('-ea')
+  if options.print_times:
+    cmd.append('-Dcom.android.tools.r8.printtimes=1')
+  cmd.extend(['-cp', ':'.join([r8jar] + testjars)])
+  cmd.extend([
+    'com.android.tools.r8.benchmarks.BenchmarkMainEntryRunner',
+    options.benchmark,
+    options.target,
+    ])
+  return subprocess.check_call(cmd)
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index c55a6f7..6035752 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -3,24 +3,25 @@
 # 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.
 
+import argparse
+import hashlib
+import os
+import shutil
+import sys
+import time
+import zipfile
+from datetime import datetime
+
 import adb
 import apk_masseur
 import as_utils
 import compiledump
 import gradle
-import hashlib
 import jdk
-import argparse
-import os
-import shutil
-import sys
-import time
 import update_prebuilds_in_android
 import utils
-import zipfile
 
-from datetime import datetime
-
+GOLEM_BUILD_TARGETS = ['R8Lib', 'R8Retrace']
 SHRINKERS = ['r8', 'r8-full', 'r8-nolib', 'r8-nolib-full']
 
 class AttrDict(dict):
@@ -1060,9 +1061,7 @@
   print_indented('final targetsFull = ["R8-full-minify-optimize-shrink"];', 2)
   # Avoid calculating this for every app
   jdk_gz = jdk.GetJdkHome() + '.tar.gz'
-  download_sha(jdk_gz + '.sha1', False, quiet=True)
-  jdk_sha256 = get_sha256(jdk_gz)
-  add_golem_resource(2, jdk_gz, 'openjdk', sha256=jdk_sha256)
+  add_golem_resource(2, jdk_gz, 'openjdk')
   for app in options.apps:
     if app.folder and not app.internal:
       indentation = 2;
@@ -1171,7 +1170,7 @@
           os.path.join(temp_dir, target), os.path.join(temp_dir, 'r8lib.jar'),
           quiet=options.quiet)
     elif options.version == 'main':
-      if not (options.no_build or options.golem):
+      if not options.no_build:
         gradle.RunGradle(['R8Retrace', 'r8', '-Pno_internal'])
         build_r8lib = False
         for shrinker in options.shrinker:
diff --git a/tools/test.py b/tools/test.py
index 041f17b..9d5e99f 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -118,7 +118,7 @@
       help='Use a custom java version to run tests.')
   result.add_option('--java-max-memory-size', '--java_max_memory_size',
       help='Set memory for running tests, default 4G',
-      default='4G')
+      default=os.environ.get('R8_JAVA_MAX_MEMORY_SIZE', '4G'))
   result.add_option('--test-namespace', '--test_namespace',
       help='Only run tests in  this namespace. The namespace is relative to '
           'com/android/tools/r8, e.g., desugar/desugaredlibrary',
diff --git a/tools/utils.py b/tools/utils.py
index 5d574ac..eb7849b 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -45,10 +45,15 @@
 R8RETRACE_NO_DEPS = 'R8RetraceNoDeps'
 R8_SRC = 'sourceJar'
 LIBRARY_DESUGAR_CONVERSIONS = 'buildLibraryDesugarConversions'
+R8_TESTS_TARGET = 'TestJar'
+R8_TESTS_DEPS_TARGET = 'RepackageTestDeps'
+R8LIB_TESTS_TARGET = 'configureTestForR8Lib'
+R8LIB_TESTS_DEPS_TARGET = R8_TESTS_DEPS_TARGET
 
 ALL_DEPS_JAR = os.path.join(LIBS, 'deps_all.jar')
 D8_JAR = os.path.join(LIBS, 'd8.jar')
 R8_JAR = os.path.join(LIBS, 'r8.jar')
+R8_WITH_RELOCATED_DEPS_JAR = os.path.join(LIBS, 'r8_with_relocated_deps.jar')
 R8LIB_JAR = os.path.join(LIBS, 'r8lib.jar')
 R8LIB_MAP = os.path.join(LIBS, 'r8lib.jar.map')
 R8_SRC_JAR = os.path.join(LIBS, 'r8-src.jar')
@@ -56,6 +61,10 @@
 R8_FULL_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8-full-exclude-deps.jar')
 R8RETRACE_JAR = os.path.join(LIBS, 'r8retrace.jar')
 R8RETRACE_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8retrace-exclude-deps.jar')
+R8_TESTS_JAR = os.path.join(LIBS, 'r8tests.jar')
+R8LIB_TESTS_JAR = os.path.join(LIBS, 'r8libtestdeps-cf.jar')
+R8_TESTS_DEPS_JAR = os.path.join(LIBS, 'test_deps_all.jar')
+R8LIB_TESTS_DEPS_JAR = R8_TESTS_DEPS_JAR
 MAVEN_ZIP = os.path.join(LIBS, 'r8.zip')
 MAVEN_ZIP_LIB = os.path.join(LIBS, 'r8lib.zip')
 LIBRARY_DESUGAR_CONVERSIONS_ZIP = os.path.join(LIBS, 'library_desugar_conversions.zip')
@@ -395,7 +404,9 @@
     tar.extractall(path=dirname)
 
 def check_gcert():
-  subprocess.check_call(['gcert'])
+  status = subprocess.call(['gcertstatus'])
+  if status != 0:
+    subprocess.check_call(['gcert'])
 
 # Note that gcs is eventually consistent with regards to list operations.
 # This is not a problem in our case, but don't ever use this method