Merge commit '3cd0e1c825fad39d4cc17189182ae0f46ad43621' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index e7f27d5..35d22bb 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -702,7 +702,9 @@
         .getArtProfileOptions()
         .setArtProfilesForRewriting(getArtProfilesForRewriting())
         .setPassthrough(true);
-    internal.getStartupOptions().setStartupProfileProviders(getStartupProfileProviders());
+    if (!getStartupProfileProviders().isEmpty()) {
+      internal.getStartupOptions().setStartupProfileProviders(getStartupProfileProviders());
+    }
 
     internal.programClassConflictResolver =
         ProgramClassCollection.wrappedConflictResolver(
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d4e7f3a..d57d450 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -68,6 +68,7 @@
 import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
 import com.android.tools.r8.optimize.AccessModifier;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
+import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
 import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory;
 import com.android.tools.r8.optimize.RedundantBridgeRemover;
 import com.android.tools.r8.optimize.bridgehoisting.BridgeHoisting;
@@ -460,7 +461,7 @@
           // We can now remove redundant bridges. Note that we do not need to update the
           // invoke-targets here, as the existing invokes will simply dispatch to the now
           // visible super-method. MemberRebinding, if run, will then dispatch it correctly.
-          new RedundantBridgeRemover(appView.withLiveness()).run(executorService);
+          new RedundantBridgeRemover(appView.withLiveness()).run(null, executorService);
         }
       }
 
@@ -688,10 +689,17 @@
         }
       }
 
+      // Insert a member rebinding oracle in the graph to ensure that all subsequent rewritings of
+      // the application has an applied oracle for looking up non-rebound references.
+      MemberRebindingIdentityLens memberRebindingIdentityLens =
+          MemberRebindingIdentityLensFactory.create(appView, executorService);
+      appView.setGraphLens(memberRebindingIdentityLens);
+
       // Remove redundant bridges that have been inserted for member rebinding.
       // This can only be done if we have AppInfoWithLiveness.
       if (appView.appInfo().hasLiveness()) {
-        new RedundantBridgeRemover(appView.withLiveness()).run(executorService);
+        new RedundantBridgeRemover(appView.withLiveness())
+            .run(memberRebindingIdentityLens, executorService);
       } else {
         // If we don't have AppInfoWithLiveness here, it must be because we are not shrinking. When
         // we are not shrinking, we can't move visibility bridges. In principle, though, it would be
@@ -700,10 +708,6 @@
         assert !options.isShrinking();
       }
 
-      // Insert a member rebinding oracle in the graph to ensure that all subsequent rewritings of
-      // the application has an applied oracle for looking up non-rebound references.
-      appView.setGraphLens(MemberRebindingIdentityLensFactory.create(appView, executorService));
-
       if (appView.appInfo().hasLiveness()) {
         SyntheticFinalization.finalizeWithLiveness(appView.withLiveness(), executorService, timing);
       } else {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 81efb90..e579889 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1077,7 +1077,9 @@
     internal.configureAndroidPlatformBuild(getAndroidPlatformBuild());
 
     internal.getArtProfileOptions().setArtProfilesForRewriting(getArtProfilesForRewriting());
-    internal.getStartupOptions().setStartupProfileProviders(getStartupProfileProviders());
+    if (!getStartupProfileProviders().isEmpty()) {
+      internal.getStartupOptions().setStartupProfileProviders(getStartupProfileProviders());
+    }
 
     internal.programClassConflictResolver =
         ProgramClassCollection.wrappedConflictResolver(
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
index 3b5b940..0eef997 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
@@ -9,10 +9,14 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
+import com.android.tools.r8.profile.art.ArtProfileClassRuleInfo;
+import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfo;
+import com.android.tools.r8.profile.art.ArtProfileRulePredicate;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.startup.StartupProfileBuilder;
 import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.startup.diagnostic.MissingStartupProfileItemsDiagnostic;
-import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.UTF8TextInputStream;
 import java.io.IOException;
@@ -28,7 +32,23 @@
       public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
         try {
           startupProfileBuilder.addHumanReadableArtProfile(
-              new UTF8TextInputStream(path), ConsumerUtils.emptyConsumer());
+              new UTF8TextInputStream(path),
+              profileParserBuilder ->
+                  profileParserBuilder.setRulePredicate(
+                      new ArtProfileRulePredicate() {
+                        @Override
+                        public boolean testClassRule(
+                            ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo) {
+                          return false;
+                        }
+
+                        @Override
+                        public boolean testMethodRule(
+                            MethodReference methodReference,
+                            ArtProfileMethodRuleInfo methodRuleInfo) {
+                          return methodRuleInfo.isStartup();
+                        }
+                      }));
         } catch (IOException e) {
           throw new UncheckedIOException(e);
         }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index b891c89..42f5d96 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -688,6 +688,13 @@
     return resolveMethodOnLegacy(invokedMethod.getHolderType(), invokedMethod, isInterface);
   }
 
+  public MethodResolutionResult resolveMethodOn(DexClass clazz, DexMethod method) {
+    assert checkIfObsolete();
+    return clazz.isInterface()
+        ? resolveMethodOnInterface(clazz, method)
+        : resolveMethodOnClass(clazz, method);
+  }
+
   public MethodResolutionResult resolveMethodOnLegacy(DexClass clazz, DexMethod method) {
     assert checkIfObsolete();
     return clazz.isInterface()
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
index 5569e34..425c5d5 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.dex.code.DexReturnVoid;
 import com.android.tools.r8.graph.DexCode.Try;
 import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumberGenerator;
@@ -178,8 +179,10 @@
       IndexedItemCollection indexedItems,
       ProgramMethod context,
       LensCodeRewriterUtils rewriter) {
-    getParentConstructor(context, rewriter.dexItemFactory())
-        .collectIndexedItems(appView, indexedItems);
+    DexMethod parentConstructor = getParentConstructor(context, rewriter.dexItemFactory());
+    MethodLookupResult lookupResult =
+        appView.graphLens().lookupInvokeDirect(parentConstructor, context);
+    lookupResult.getReference().collectIndexedItems(appView, indexedItems);
   }
 
   @Override
@@ -370,7 +373,9 @@
       GraphLens graphLens,
       LensCodeRewriterUtils lensCodeRewriter,
       ObjectToOffsetMapping mapping) {
-    new DexInvokeDirect(1, getParentConstructor(context, mapping.dexItemFactory()), 0, 0, 0, 0, 0)
+    DexMethod parentConstructor = getParentConstructor(context, mapping.dexItemFactory());
+    MethodLookupResult lookupResult = graphLens.lookupInvokeDirect(parentConstructor, context);
+    new DexInvokeDirect(1, lookupResult.getReference(), 0, 0, 0, 0, 0)
         .write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
     new DexReturnVoid().write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
   }
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 3069a60..0564b9c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -893,7 +893,7 @@
   }
 
   public final boolean classInitializationMayHaveSideEffectsInContext(
-      AppView<?> appView, ProgramDefinition context) {
+      AppView<?> appView, Definition context) {
     // Types that are a super type of the current context are guaranteed to be initialized already.
     return classInitializationMayHaveSideEffects(
         appView, type -> appView.isSubtype(context.getContextType(), type).isTrue());
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 93011d5..47fabfa 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.kotlin.KotlinFieldLevelInfo;
+import com.android.tools.r8.kotlin.KotlinMetadataUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.structural.StructuralItem;
@@ -397,6 +398,7 @@
     private DexAnnotationSet annotations = DexAnnotationSet.empty();
     private FieldAccessFlags accessFlags;
     private FieldTypeSignature genericSignature = FieldTypeSignature.noSignature();
+    private KotlinFieldLevelInfo kotlinInfo = KotlinMetadataUtils.getNoKotlinInfo();
     private DexValue staticValue = null;
     private ComputedApiLevel apiLevel = ComputedApiLevel.notSet();
     private FieldOptimizationInfo optimizationInfo = DefaultFieldOptimizationInfo.getInstance();
@@ -416,8 +418,8 @@
       // Copy all the mutable state of a DexEncodedField here.
       field = from.getReference();
       accessFlags = from.accessFlags.copy();
-      // TODO(b/169923358): Consider removing the fieldSignature here.
       genericSignature = from.getGenericSignature();
+      kotlinInfo = from.getKotlinInfo();
       annotations = from.annotations();
       staticValue = from.staticValue;
       apiLevel = from.getApiLevel();
@@ -518,6 +520,7 @@
               apiLevel,
               deprecated,
               d8R8Synthesized);
+      dexEncodedField.setKotlinMemberInfo(kotlinInfo);
       dexEncodedField.optimizationInfo = optimizationInfo;
       buildConsumer.accept(dexEncodedField);
       return dexEncodedField;
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 9620ee4..3ee7c50 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -291,6 +291,7 @@
   public final DexString optionalLongDescriptor = createString("Ljava/util/OptionalLong;");
   public final DexString streamDescriptor = createString("Ljava/util/stream/Stream;");
   public final DexString arraysDescriptor = createString("Ljava/util/Arrays;");
+  public final DexString threadLocalDescriptor = createString("Ljava/lang/ThreadLocal;");
 
   public final DexString throwableDescriptor = createString(throwableDescriptorString);
   public final DexString illegalAccessErrorDescriptor =
@@ -446,6 +447,7 @@
   public final DexType optionalIntType = createStaticallyKnownType(optionalIntDescriptor);
   public final DexType optionalLongType = createStaticallyKnownType(optionalLongDescriptor);
   public final DexType streamType = createStaticallyKnownType(streamDescriptor);
+  public final DexType threadLocalType = createStaticallyKnownType(threadLocalDescriptor);
 
   public final DexType bufferType = createStaticallyKnownType(bufferDescriptor);
   public final DexType byteBufferType = createStaticallyKnownType(byteBufferDescriptor);
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 f303854..e239b5e 100644
--- a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
@@ -3,15 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.graph.DexApplication.classesWithDeterministicOrder;
+
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 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;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -32,15 +36,17 @@
 
   private final Map<DexType, TypeInfo> typeInfo;
 
+  private final DexDefinitionSupplier definitionSupplier;
   private final DexItemFactory factory;
 
   private SubtypingInfo(
       Map<DexType, TypeInfo> typeInfo,
       Map<DexType, Set<DexType>> subtypeMap,
-      DexItemFactory factory) {
+      DexDefinitionSupplier definitionSupplier) {
     this.typeInfo = typeInfo;
     this.subtypeMap = subtypeMap;
-    this.factory = factory;
+    this.definitionSupplier = definitionSupplier;
+    factory = definitionSupplier.dexItemFactory();
   }
 
   public static SubtypingInfo create(AppView<? extends AppInfoWithClassHierarchy> appView) {
@@ -60,7 +66,7 @@
     Map<DexType, TypeInfo> typeInfo = new ConcurrentHashMap<>();
     Map<DexType, Set<DexType>> subtypeMap = new IdentityHashMap<>();
     populateSubtypeMap(classes, subtypeMap, typeInfo, definitions);
-    return new SubtypingInfo(typeInfo, subtypeMap, definitions.dexItemFactory());
+    return new SubtypingInfo(typeInfo, subtypeMap, definitions);
   }
 
   private static void populateSuperType(
@@ -245,6 +251,16 @@
         .forEach(fn);
   }
 
+  public List<DexClass> computeReachableInterfacesWithDeterministicOrder() {
+    List<DexClass> interfaces = new ArrayList<>();
+    forAllInterfaceRoots(
+        type ->
+            definitionSupplier
+                .contextIndependentDefinitionForWithResolutionResult(type)
+                .forEachClassResolutionResult(interfaces::add));
+    return classesWithDeterministicOrder(interfaces);
+  }
+
   private static class TypeInfo {
 
     private final DexType type;
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 e6faadc..a93e87f 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
@@ -5,10 +5,19 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
@@ -17,13 +26,19 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethodDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
 import com.android.tools.r8.ir.desugar.backports.BooleanMethodRewrites;
@@ -39,6 +54,7 @@
 import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
 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.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -186,6 +202,9 @@
       }
       if (options.getMinApiLevel().isLessThan(AndroidApiLevel.O)) {
         initializeAndroidOMethodProviders(factory);
+        if (typeIsPresent(factory.supplierType)) {
+          initializeAndroidOThreadLocalMethodProviderWithSupplier(factory);
+        }
       }
       if (options.getMinApiLevel().isLessThan(AndroidApiLevel.R)) {
         if (typeIsPresentWithoutBackportsFrom(factory.setType, AndroidApiLevel.R)) {
@@ -1617,6 +1636,17 @@
           new MethodGenerator(method, BackportedMethods::ObjectsMethods_requireNonNullSupplier));
     }
 
+    private void initializeAndroidOThreadLocalMethodProviderWithSupplier(DexItemFactory factory) {
+      // ThreadLocal
+      DexType type = factory.threadLocalType;
+
+      // ThreadLocal.withInitial(Supplier)
+      DexString name = factory.createString("withInitial");
+      DexProto proto = factory.createProto(type, factory.supplierType);
+      DexMethod method = factory.createMethod(type, proto, name);
+      addProvider(new ThreadLocalWithInitialWithSupplierGenerator(method));
+    }
+
     private void addProvider(MethodProvider generator) {
       MethodProvider replaced = rewritable.put(generator.method, generator);
       assert replaced == null;
@@ -1754,6 +1784,174 @@
     }
   }
 
+  /*
+    Generate code for the SynthesizedThreadLocalSubClass class below, and rewrite
+
+      ThreadLocal.withInitial(someSupplier);
+
+    to
+
+      new SynthesizedThreadLocalSubClass(someSupplier);
+
+    class SynthesizedThreadLocalSubClass extends ThreadLocal<Object> {
+      private Supplier<Object> supplier;
+
+      SynthesizedThreadLocalSubClass(Supplier<Object> supplier) {
+        this.supplier = supplier;
+      }
+
+      protected Object initialValue() {
+        return supplier.get();
+      }
+    }
+  */
+  private static class ThreadLocalWithInitialWithSupplierGenerator extends MethodGenerator {
+
+    ThreadLocalWithInitialWithSupplierGenerator(DexMethod method) {
+      super(method, null, method.name.toString());
+    }
+
+    @Override
+    protected SyntheticKind getSyntheticKind(SyntheticNaming naming) {
+      return naming.THREAD_LOCAL;
+    }
+
+    @Override
+    public Collection<CfInstruction> rewriteInvoke(
+        CfInvoke invoke,
+        AppView<?> appView,
+        BackportedMethodDesugaringEventConsumer eventConsumer,
+        MethodProcessingContext methodProcessingContext,
+        LocalStackAllocator localStackAllocator) {
+      DexItemFactory factory = appView.dexItemFactory();
+      DexProgramClass threadLocalSubclass =
+          appView
+              .getSyntheticItems()
+              .createClass(
+                  kinds -> kinds.THREAD_LOCAL,
+                  methodProcessingContext.createUniqueContext(),
+                  appView,
+                  builder -> new ThreadLocalSubclassGenerator(builder, appView));
+      eventConsumer.acceptBackportedClass(
+          threadLocalSubclass, methodProcessingContext.getMethodContext());
+      return ImmutableList.of(
+          new CfNew(threadLocalSubclass.type),
+          // Massage the stack with dup_x1 and swap:
+          //   ..., SomeSupplier, ThreadLocalSubClass ->
+          //      ..., ThreadLocalSubClass, ThreadLocalSubClass, SomeSupplier
+          new CfStackInstruction(Opcode.DupX1),
+          new CfStackInstruction(Opcode.Swap),
+          new CfInvoke(
+              Opcodes.INVOKESPECIAL,
+              factory.createMethod(
+                  threadLocalSubclass.type,
+                  factory.createProto(factory.voidType, factory.supplierType),
+                  factory.constructorMethodName),
+              false));
+    }
+  }
+
+  private static class ThreadLocalSubclassGenerator {
+    private final DexItemFactory factory;
+    private final DexType type;
+    private final DexField supplierField;
+    private final DexMethod constructor;
+    private final DexMethod initialValueMethod;
+
+    ThreadLocalSubclassGenerator(SyntheticProgramClassBuilder builder, AppView<?> appView) {
+      this.factory = appView.dexItemFactory();
+      this.type = builder.getType();
+      this.supplierField =
+          factory.createField(
+              builder.getType(),
+              factory.supplierType,
+              factory.createString("initialValueSupplier"));
+      this.constructor =
+          factory.createMethod(
+              type,
+              factory.createProto(factory.voidType, factory.supplierType),
+              factory.constructorMethodName);
+      this.initialValueMethod =
+          factory.createMethod(
+              type, factory.createProto(factory.objectType), factory.createString("initialValue"));
+
+      builder.setSuperType(factory.threadLocalType);
+      synthesizeInstanceFields(builder);
+      synthesizeDirectMethods(builder);
+      synthesizeVirtualMethods(builder);
+    }
+
+    private void synthesizeInstanceFields(SyntheticProgramClassBuilder builder) {
+      // Instance field:
+      //
+      // private Supplier<Object> supplier
+      builder.setInstanceFields(
+          ImmutableList.of(
+              DexEncodedField.syntheticBuilder()
+                  .setField(supplierField)
+                  .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+                  .disableAndroidApiLevelCheck()
+                  .build()));
+    }
+
+    private void synthesizeDirectMethods(SyntheticProgramClassBuilder builder) {
+      // Code for:
+      //
+      //  SynthesizedThreadLocalSubClass(Supplier<Object> supplier) {
+      //    this.supplier = supplier;
+      //  }
+      List<CfInstruction> instructions = new ArrayList<>();
+      instructions.add(new CfLoad(ValueType.OBJECT, 0));
+      instructions.add(new CfStackInstruction(Opcode.Dup));
+      instructions.add(
+          new CfInvoke(
+              Opcodes.INVOKESPECIAL,
+              factory.createMethod(
+                  factory.threadLocalType,
+                  factory.createProto(factory.voidType),
+                  factory.constructorMethodName),
+              false));
+      instructions.add(new CfLoad(ValueType.fromDexType(factory.supplierType), 1));
+      instructions.add(new CfInstanceFieldWrite(supplierField));
+      instructions.add(new CfReturnVoid());
+
+      builder.setDirectMethods(
+          ImmutableList.of(
+              DexEncodedMethod.syntheticBuilder()
+                  .setMethod(constructor)
+                  .setAccessFlags(
+                      MethodAccessFlags.fromSharedAccessFlags(
+                          Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true))
+                  .setCode(new CfCode(type, 2, 2, instructions))
+                  .disableAndroidApiLevelCheck()
+                  .build()));
+    }
+
+    private void synthesizeVirtualMethods(SyntheticProgramClassBuilder builder) {
+      // Code for:
+      //
+      // protected Object initialValue() {
+      //   return supplier.get();
+      // }
+      List<CfInstruction> instructions = new ArrayList<>();
+      instructions.add(new CfLoad(ValueType.OBJECT, 0));
+      instructions.add(new CfInstanceFieldRead(supplierField));
+      instructions.add(new CfInvoke(Opcodes.INVOKEINTERFACE, factory.supplierMembers.get, true));
+      instructions.add(new CfReturn(ValueType.OBJECT));
+
+      builder.setVirtualMethods(
+          ImmutableList.of(
+              DexEncodedMethod.syntheticBuilder()
+                  .setMethod(initialValueMethod)
+                  .setAccessFlags(
+                      MethodAccessFlags.fromSharedAccessFlags(
+                          Constants.ACC_PROTECTED | Constants.ACC_SYNTHETIC, false))
+                  .setCode(new CfCode(type, 1, 1, instructions))
+                  .disableAndroidApiLevelCheck()
+                  .build()));
+    }
+  }
+
   private interface TemplateMethodFactory {
 
     CfCode create(DexItemFactory factory, DexMethod method);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 6a9fb76..e44467e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -127,6 +127,13 @@
     }
 
     @Override
+    public void acceptBackportedClass(DexProgramClass backportedClass, ProgramMethod context) {
+      backportedClass
+          .programMethods()
+          .forEach(method -> methodProcessor.scheduleMethodForProcessing(method, this));
+    }
+
+    @Override
     public void acceptRecordMethod(ProgramMethod method) {
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
     }
@@ -398,6 +405,11 @@
     }
 
     @Override
+    public void acceptBackportedClass(DexProgramClass backportedClass, ProgramMethod context) {
+      // Intentionally empty. The method will be hit by tracing if required.
+    }
+
+    @Override
     public void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info) {
       synchronized (pendingInvokeSpecialBridges) {
         pendingInvokeSpecialBridges.add(info);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethodDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethodDesugaringEventConsumer.java
index c62501f..11f9d56 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethodDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethodDesugaringEventConsumer.java
@@ -4,9 +4,12 @@
 
 package com.android.tools.r8.ir.desugar.backports;
 
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 
 public interface BackportedMethodDesugaringEventConsumer {
 
   void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context);
+
+  void acceptBackportedClass(DexProgramClass backportedClass, ProgramMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
index 44876ed..4f3a62e 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
@@ -75,7 +75,7 @@
   // Mapping from SSA value definition to the local name index in the constant pool.
   private final Int2ReferenceMap<DebugLocalInfo> debugLocals = new Int2ReferenceOpenHashMap<>();
   // Mapping from instruction to the end usage of SSA values with debug local info.
-  private final Int2ReferenceMap<int[]> debugLocalEnds = new Int2ReferenceOpenHashMap();
+  private final Int2ReferenceMap<int[]> debugLocalEnds = new Int2ReferenceOpenHashMap<>();
 
   // TODO(b/225838009): Reconsider this fixed space as the operand count for phis is much larger.
   // Pre-allocated space for caching value indexes when writing instructions.
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index ef6142f..00c0d9f 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
-import static com.android.tools.r8.graph.DexApplication.classesWithDeterministicOrder;
 import static com.android.tools.r8.utils.StringUtils.EMPTY_CHAR_ARRAY;
 import static com.android.tools.r8.utils.SymbolGenerationUtils.RESERVED_NAMES;
 
@@ -26,7 +25,6 @@
 import com.android.tools.r8.utils.SymbolGenerationUtils;
 import com.android.tools.r8.utils.SymbolGenerationUtils.MixedCasing;
 import com.android.tools.r8.utils.Timing;
-import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -45,9 +43,9 @@
 
   public NamingLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
     assert appView.options().isMinifying();
-    SubtypingInfo subtypingInfo = appView.appInfo().computeSubtypingInfo();
+    SubtypingInfo subtypingInfo = MinifierUtils.createSubtypingInfo(appView);
     timing.begin("ComputeInterfaces");
-    List<DexClass> interfaces = computeReachableInterfacesWithDeterministicOrder(subtypingInfo);
+    List<DexClass> interfaces = subtypingInfo.computeReachableInterfacesWithDeterministicOrder();
     timing.end();
     timing.begin("MinifyClasses");
     ClassNameMinifier classNameMinifier =
@@ -99,19 +97,6 @@
     timing.end();
   }
 
-  private List<DexClass> computeReachableInterfacesWithDeterministicOrder(
-      SubtypingInfo subtypingInfo) {
-    List<DexClass> interfaces = new ArrayList<>();
-    subtypingInfo.forAllInterfaceRoots(
-        type -> {
-          DexClass clazz = appView.contextIndependentDefinitionFor(type);
-          if (clazz != null) {
-            interfaces.add(clazz);
-          }
-        });
-    return classesWithDeterministicOrder(interfaces);
-  }
-
   abstract static class BaseMinificationNamingStrategy {
 
     // We have to ensure that the names proposed by the minifier is not used in the obfuscation
diff --git a/src/main/java/com/android/tools/r8/naming/MinifierUtils.java b/src/main/java/com/android/tools/r8/naming/MinifierUtils.java
new file mode 100644
index 0000000..e259a67
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/MinifierUtils.java
@@ -0,0 +1,32 @@
+// 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;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.SetUtils;
+import java.util.Set;
+
+public class MinifierUtils {
+
+  public static SubtypingInfo createSubtypingInfo(AppView<AppInfoWithLiveness> appView) {
+    Set<DexClass> classesToBuildSubtypeInformationFor =
+        SetUtils.newIdentityHashSet(appView.app().classes());
+    appView
+        .appInfo()
+        .getObjectAllocationInfoCollection()
+        .forEachInstantiatedLambdaInterfaces(
+            type -> {
+              DexClass lambdaInterface = appView.contextIndependentDefinitionFor(type);
+              if (lambdaInterface != null) {
+                classesToBuildSubtypeInformationFor.add(lambdaInterface);
+              }
+            });
+    appView.appInfo().forEachReferencedClasspathClass(classesToBuildSubtypeInformationFor::add);
+    return SubtypingInfo.create(classesToBuildSubtypeInformationFor, appView);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 174614b..d297031 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -35,14 +35,12 @@
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
-import java.util.Comparator;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -50,7 +48,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeSet;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiPredicate;
@@ -96,37 +93,15 @@
   public NamingLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
     ArrayDeque<Map<DexReference, MemberNaming>> nonPrivateMembers = new ArrayDeque<>();
     Set<DexReference> notMappedReferences = new HashSet<>();
+    SubtypingInfo subtypingInfo = MinifierUtils.createSubtypingInfo(appView);
     timing.begin("MappingInterfaces");
-    Set<DexClass> classesToBuildSubtypeInformationFor =
-        SetUtils.newIdentityHashSet(appView.app().classes());
-    appView
-        .appInfo()
-        .getObjectAllocationInfoCollection()
-        .forEachInstantiatedLambdaInterfaces(
-            type -> {
-              DexClass lambdaInterface = appView.contextIndependentDefinitionFor(type);
-              if (lambdaInterface != null) {
-                classesToBuildSubtypeInformationFor.add(lambdaInterface);
-              }
-            });
-    appView.appInfo().forEachReferencedClasspathClass(classesToBuildSubtypeInformationFor::add);
-    SubtypingInfo subtypingInfo =
-        SubtypingInfo.create(classesToBuildSubtypeInformationFor, appView);
-    timing.begin("MappingInterfaces");
-    Set<DexClass> interfaces = new TreeSet<>(Comparator.comparing(DexClass::getType));
-    subtypingInfo.forAllInterfaceRoots(
-        iFace -> {
-          DexClass iFaceDefinition = appView.definitionFor(iFace);
-          if (iFaceDefinition != null) {
-            interfaces.add(iFaceDefinition);
-            computeMapping(iFace, nonPrivateMembers, notMappedReferences, subtypingInfo);
-          }
-        });
-
-    assert nonPrivateMembers.isEmpty();
+    List<DexClass> interfaces = subtypingInfo.computeReachableInterfacesWithDeterministicOrder();
+    interfaces.forEach(
+        iface ->
+            computeMapping(iface.getType(), nonPrivateMembers, notMappedReferences, subtypingInfo));
     timing.end();
-
     timing.begin("MappingClasses");
+    mappedClasses.addAll(appView.appInfo().classes());
     subtypingInfo.forAllImmediateExtendsSubtypes(
         factory.objectType,
         subType -> {
@@ -193,13 +168,9 @@
     ClassNamingForMapApplier classNaming = seedMapper.getClassNaming(type);
     DexClass clazz = appView.definitionFor(type);
 
-    // Keep track of classes that needs to get renamed.
-    if (clazz != null) {
-      if (clazz.isClasspathClass() && classNaming != null) {
-        mappedClasses.add(clazz.asClasspathClass());
-      } else if (clazz.isProgramClass()) {
-        mappedClasses.add(clazz.asProgramClass());
-      }
+    // Keep track of classpath classes that needs to get renamed.
+    if (clazz != null && clazz.isClasspathClass() && classNaming != null) {
+      mappedClasses.add(clazz.asClasspathClass());
     }
 
     Map<DexReference, MemberNaming> nonPrivateMembers = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
index c7d53f9..ea14e07 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
@@ -46,6 +46,10 @@
     return false;
   }
 
+  public boolean isResidualSignatureMappingInformation() {
+    return false;
+  }
+
   public MapVersionMappingInformation asMapVersionMappingInformation() {
     return null;
   }
@@ -74,6 +78,10 @@
     return null;
   }
 
+  public ResidualSignatureMappingInformation asResidualSignatureMappingInformation() {
+    return null;
+  }
+
   public boolean isGlobalMappingInformation() {
     return false;
   }
@@ -138,6 +146,9 @@
       case OutlineCallsiteMappingInformation.ID:
         OutlineCallsiteMappingInformation.deserialize(version, object, onMappingInfo);
         return;
+      case ResidualSignatureMappingInformation.ID:
+        ResidualSignatureMappingInformation.deserialize(version, object, onMappingInfo);
+        return;
       default:
         diagnosticsHandler.info(MappingInformationDiagnostics.noHandlerFor(lineNumber, id));
         UnknownJsonMappingInformation.deserialize(id, object, onMappingInfo);
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java
index efdb788..4bcfe95 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java
@@ -88,12 +88,12 @@
   public static void deserialize(
       MapVersion version, JsonObject object, Consumer<MappingInformation> onMappingInfo) {
     if (isSupported(version)) {
-      JsonObject postionsMapObject = object.getAsJsonObject(POSITIONS_KEY);
-      if (postionsMapObject == null) {
+      JsonObject positionsMapObject = object.getAsJsonObject(POSITIONS_KEY);
+      if (positionsMapObject == null) {
         throw new CompilationError("Expected '" + POSITIONS_KEY + "' to be present: " + object);
       }
       Int2IntSortedMap positionsMap = new Int2IntLinkedOpenHashMap();
-      postionsMapObject
+      positionsMapObject
           .entrySet()
           .forEach(
               entry -> {
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/ResidualSignatureMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/ResidualSignatureMappingInformation.java
new file mode 100644
index 0000000..5808b25
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/ResidualSignatureMappingInformation.java
@@ -0,0 +1,125 @@
+// 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.mappinginformation;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+public abstract class ResidualSignatureMappingInformation extends MappingInformation {
+
+  public static final MapVersion SUPPORTED_VERSION = MapVersion.MAP_VERSION_EXPERIMENTAL;
+  public static final String ID = "com.android.tools.r8.residualsignature";
+  public static final String SIGNATURE_KEY = "signature";
+
+  @Override
+  public String getId() {
+    return ID;
+  }
+
+  @Override
+  public String serialize() {
+    JsonObject object = new JsonObject();
+    object.add(SIGNATURE_KEY, new JsonPrimitive(serializeInternal()));
+    return object.toString();
+  }
+
+  protected abstract String serializeInternal();
+
+  @Override
+  public boolean allowOther(MappingInformation information) {
+    return !information.isResidualSignatureMappingInformation();
+  }
+
+  public static boolean isSupported(MapVersion version) {
+    return version.isGreaterThanOrEqualTo(SUPPORTED_VERSION);
+  }
+
+  public static void deserialize(
+      MapVersion version, JsonObject object, Consumer<MappingInformation> onMappingInfo) {
+    if (isSupported(version)) {
+      JsonObject signatureObject = object.getAsJsonObject(SIGNATURE_KEY);
+      if (signatureObject == null) {
+        throw new CompilationError("Expected '" + SIGNATURE_KEY + "' to be present: " + object);
+      }
+      String signatureString = signatureObject.getAsString();
+      if (signatureString.contains("(")) {
+        onMappingInfo.accept(
+            ResidualMethodSignatureMappingInformation.deserialize(signatureString));
+      } else {
+        onMappingInfo.accept(ResidualFieldSignatureMappingInformation.deserialize(signatureString));
+      }
+    }
+  }
+
+  public static class ResidualMethodSignatureMappingInformation
+      extends ResidualSignatureMappingInformation {
+
+    private static final String[] EMPTY_ARRAY = new String[0];
+
+    private final String returnType;
+    private final String[] parameters;
+
+    private ResidualMethodSignatureMappingInformation(String[] parameters, String returnType) {
+      assert DescriptorUtils.isDescriptor(returnType)
+          || DescriptorUtils.isVoidDescriptor(returnType);
+      this.returnType = returnType;
+      this.parameters = parameters;
+    }
+
+    public static ResidualMethodSignatureMappingInformation fromDexMethod(DexMethod method) {
+      String[] parameters =
+          ArrayUtils.mapAll(
+              method.getParameters().values, DexType::toDescriptorString, EMPTY_ARRAY);
+      return new ResidualMethodSignatureMappingInformation(
+          parameters, method.getReturnType().toDescriptorString());
+    }
+
+    @Override
+    protected String serializeInternal() {
+      return StringUtils.join("", Arrays.asList(parameters), BraceType.PARENS) + returnType;
+    }
+
+    public static ResidualMethodSignatureMappingInformation deserialize(String signature) {
+      return new ResidualMethodSignatureMappingInformation(
+          DescriptorUtils.getArgumentTypeDescriptors(signature),
+          DescriptorUtils.getReturnTypeDescriptor(signature));
+    }
+  }
+
+  public static class ResidualFieldSignatureMappingInformation
+      extends ResidualSignatureMappingInformation {
+
+    private final String type;
+
+    private ResidualFieldSignatureMappingInformation(String type) {
+      assert DescriptorUtils.isDescriptor(type);
+      this.type = type;
+    }
+
+    public static ResidualFieldSignatureMappingInformation fromDexField(DexField field) {
+      return new ResidualFieldSignatureMappingInformation(field.getType().toDescriptorString());
+    }
+
+    @Override
+    protected String serializeInternal() {
+      return type;
+    }
+
+    public static ResidualFieldSignatureMappingInformation deserialize(String signature) {
+      return new ResidualFieldSignatureMappingInformation(signature);
+    }
+  }
+}
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 e5b23f5..0cc861f 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -49,6 +49,11 @@
     return new Builder(appView, previousLens);
   }
 
+  public void addNonReboundMethodReference(
+      DexMethod nonReboundMethodReference, DexMethod reboundMethodReference) {
+    nonReboundMethodReferenceToDefinitionMap.put(nonReboundMethodReference, reboundMethodReference);
+  }
+
   @Override
   public boolean hasCodeRewritings() {
     return false;
diff --git a/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
index 6c2af55..d9ab527 100644
--- a/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.utils.ThreadUtils.processItems;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -13,6 +14,7 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
+import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemovalLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.Map;
@@ -28,17 +30,17 @@
     this.appView = appView;
   }
 
-  private boolean isRedundantBridge(ProgramMethod method) {
+  private DexClassAndMethod getTargetForRedundantBridge(ProgramMethod method) {
     // Clean-up the predicate check.
     if (appView.appInfo().isPinned(method.getReference())) {
-      return false;
+      return null;
     }
     DexEncodedMethod definition = method.getDefinition();
     // TODO(b/197490164): Remove if method is abstract.
     BridgeInfo bridgeInfo = definition.getOptimizationInfo().getBridgeInfo();
     boolean isBridge = definition.isBridge() || bridgeInfo != null;
     if (!isBridge || definition.isAbstract()) {
-      return false;
+      return null;
     }
     InvokeSingleTargetExtractor targetExtractor = new InvokeSingleTargetExtractor(appView, method);
     method.registerCodeReferences(targetExtractor);
@@ -46,31 +48,28 @@
     // javac-generated visibility forward bridge method has same descriptor (name, signature and
     // return type).
     if (target == null || !target.match(method.getReference())) {
-      return false;
+      return null;
     }
     if (!isTargetingSuperMethod(method, targetExtractor.getKind(), target)) {
-      return false;
+      return null;
     }
     // This is a visibility forward, so check for the direct target.
-    ProgramMethod targetMethod =
-        appView
-            .appInfo()
-            .unsafeResolveMethodDueToDexFormatLegacy(target)
-            .getResolvedProgramMethod();
+    DexClassAndMethod targetMethod =
+        appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(target).getResolutionPair();
     if (targetMethod == null) {
-      return false;
+      return null;
     }
     if (method.getAccessFlags().isPublic()) {
       if (!targetMethod.getAccessFlags().isPublic()) {
-        return false;
+        return null;
       }
     } else {
       if (targetMethod.getAccessFlags().isProtected()
           && !targetMethod.getHolderType().isSamePackage(method.getHolderType())) {
-        return false;
+        return null;
       }
       if (targetMethod.getAccessFlags().isPrivate()) {
-        return false;
+        return null;
       }
     }
     if (definition.isStatic()
@@ -78,9 +77,9 @@
         && method
             .getHolder()
             .classInitializationMayHaveSideEffectsInContext(appView, targetMethod)) {
-      return false;
+      return null;
     }
-    return true;
+    return targetMethod;
   }
 
   private boolean isTargetingSuperMethod(ProgramMethod method, InvokeKind kind, DexMethod target) {
@@ -90,7 +89,6 @@
     if (kind == InvokeKind.DIRECT) {
       return method.getDefinition().isInstanceInitializer()
           && appView.options().canHaveNonReboundConstructorInvoke()
-          && appView.testing().enableRedundantConstructorBridgeRemoval
           && appView.appInfo().isStrictSubtypeOf(method.getHolderType(), target.getHolderType());
     }
     assert !method.getAccessFlags().isPrivate();
@@ -105,15 +103,44 @@
     return false;
   }
 
-  public void run(ExecutorService executorService) throws ExecutionException {
+  public void run(
+      MemberRebindingIdentityLens memberRebindingIdentityLens, ExecutorService executorService)
+      throws ExecutionException {
+    assert memberRebindingIdentityLens == null
+        || memberRebindingIdentityLens == appView.graphLens();
+
     // Collect all redundant bridges to remove.
+    RedundantBridgeRemovalLens.Builder lensBuilder = new RedundantBridgeRemovalLens.Builder();
     Map<DexProgramClass, ProgramMethodSet> bridgesToRemove =
-        computeBridgesToRemove(executorService);
+        computeBridgesToRemove(lensBuilder, executorService);
+    if (bridgesToRemove.isEmpty()) {
+      return;
+    }
+
     pruneApp(bridgesToRemove, executorService);
+
+    if (!lensBuilder.isEmpty()) {
+      appView.setGraphLens(lensBuilder.build(appView));
+    }
+
+    if (memberRebindingIdentityLens != null) {
+      for (ProgramMethodSet bridgesToRemoveFromClass : bridgesToRemove.values()) {
+        for (ProgramMethod bridgeToRemove : bridgesToRemoveFromClass) {
+          DexClassAndMethod resolvedMethod =
+              appView
+                  .appInfo()
+                  .resolveMethodOn(bridgeToRemove.getHolder(), bridgeToRemove.getReference())
+                  .getResolutionPair();
+          memberRebindingIdentityLens.addNonReboundMethodReference(
+              bridgeToRemove.getReference(), resolvedMethod.getReference());
+        }
+      }
+    }
   }
 
   private Map<DexProgramClass, ProgramMethodSet> computeBridgesToRemove(
-      ExecutorService executorService) throws ExecutionException {
+      RedundantBridgeRemovalLens.Builder lensBuilder, ExecutorService executorService)
+      throws ExecutionException {
     Map<DexProgramClass, ProgramMethodSet> bridgesToRemove = new ConcurrentHashMap<>();
     processItems(
         appView.appInfo().classes(),
@@ -121,8 +148,20 @@
           ProgramMethodSet bridgesToRemoveForClass = ProgramMethodSet.create();
           clazz.forEachProgramMethod(
               method -> {
-                if (isRedundantBridge(method)) {
+                DexClassAndMethod target = getTargetForRedundantBridge(method);
+                if (target != null) {
+                  // Record that the redundant bridge should be removed.
                   bridgesToRemoveForClass.add(method);
+
+                  // Rewrite invokes to the bridge to the target if it is accessible.
+                  // TODO(b/173751869): Consider enabling this for constructors as well.
+                  // TODO(b/245882297): Refine these visibility checks so that we also rewrite when
+                  //  the target is not public, but still accessible to call sites.
+                  if (!method.getDefinition().isInstanceInitializer()
+                      && target.getAccessFlags().isPublic()
+                      && target.getHolder().isPublic()) {
+                    lensBuilder.map(method, target);
+                  }
                 }
               });
           if (!bridgesToRemoveForClass.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
index 1ce0703..6f7f61f 100644
--- a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
@@ -105,7 +105,8 @@
     if (methodSignature == newMethodSignature) {
       return previous;
     }
-    assert !previous.hasReboundReference();
+    assert !previous.hasReboundReference()
+        || previous.getReference() == previous.getReboundReference();
     return MethodLookupResult.builder(this)
         .setPrototypeChanges(
             previous
diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java
new file mode 100644
index 0000000..a680b36
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java
@@ -0,0 +1,150 @@
+// 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.optimize.redundantbridgeremoval;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+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.proto.RewrittenPrototypeDescription;
+import com.google.common.collect.Sets;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class RedundantBridgeRemovalLens extends NonIdentityGraphLens {
+
+  private final Set<DexType> interfaces;
+  private final Map<DexMethod, DexMethod> methodMap;
+
+  public RedundantBridgeRemovalLens(
+      AppView<?> appView, Set<DexType> interfaces, Map<DexMethod, DexMethod> methodMap) {
+    super(appView);
+    this.interfaces = interfaces;
+    this.methodMap = methodMap;
+  }
+
+  // Fields.
+
+  @Override
+  public DexField getOriginalFieldSignature(DexField field) {
+    return getPrevious().getOriginalFieldSignature(field);
+  }
+
+  @Override
+  public DexField getRenamedFieldSignature(DexField originalField, GraphLens codeLens) {
+    if (this == codeLens) {
+      return originalField;
+    }
+    return getPrevious().getRenamedFieldSignature(originalField, codeLens);
+  }
+
+  @Override
+  protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+    return previous;
+  }
+
+  // Methods.
+
+  @Override
+  public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
+    if (this == applied) {
+      return originalMethod;
+    }
+    DexMethod previousMethodSignature =
+        getPrevious().getRenamedMethodSignature(originalMethod, applied);
+    return methodMap.getOrDefault(previousMethodSignature, previousMethodSignature);
+  }
+
+  @Override
+  public DexMethod getNextMethodSignature(DexMethod method) {
+    return method;
+  }
+
+  @Override
+  public DexMethod getPreviousMethodSignature(DexMethod method) {
+    return method;
+  }
+
+  @Override
+  protected MethodLookupResult internalDescribeLookupMethod(
+      MethodLookupResult previous, DexMethod context) {
+    if (methodMap.containsKey(previous.getReference())) {
+      DexMethod newReference = previous.getReference();
+      do {
+        newReference = methodMap.get(newReference);
+      } while (methodMap.containsKey(newReference));
+      if (previous.getType().isSuper() && interfaces.contains(newReference.getHolderType())) {
+        return previous;
+      }
+      return MethodLookupResult.builder(this)
+          .setReference(newReference)
+          .setReboundReference(newReference)
+          .setPrototypeChanges(previous.getPrototypeChanges())
+          .setType(previous.getType())
+          .build();
+    }
+    return previous;
+  }
+
+  @Override
+  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+      DexMethod method, GraphLens codeLens) {
+    if (this == codeLens) {
+      return RewrittenPrototypeDescription.none();
+    }
+    return getPrevious().lookupPrototypeChangesForMethodDefinition(method, codeLens);
+  }
+
+  // Types.
+
+  @Override
+  public DexType getOriginalType(DexType type) {
+    return getPrevious().getOriginalType(type);
+  }
+
+  @Override
+  public Iterable<DexType> getOriginalTypes(DexType type) {
+    return getPrevious().getOriginalTypes(type);
+  }
+
+  @Override
+  protected DexType internalDescribeLookupClassType(DexType previous) {
+    return previous;
+  }
+
+  // Misc.
+
+  @Override
+  public boolean isContextFreeForMethods() {
+    return getPrevious().isContextFreeForMethods();
+  }
+
+  public static class Builder {
+
+    private final Set<DexType> interfaces = Sets.newIdentityHashSet();
+    private final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
+
+    public synchronized Builder map(ProgramMethod from, DexClassAndMethod to) {
+      methodMap.put(from.getReference(), to.getReference());
+      if (to.getHolder().isInterface()) {
+        interfaces.add(to.getHolderType());
+      }
+      return this;
+    }
+
+    public boolean isEmpty() {
+      return methodMap.isEmpty();
+    }
+
+    public RedundantBridgeRemovalLens build(AppView<?> appView) {
+      return new RedundantBridgeRemovalLens(appView, interfaces, methodMap);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParser.java b/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParser.java
index 3abbbca..cc39eb9 100644
--- a/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParser.java
+++ b/src/main/java/com/android/tools/r8/profile/art/HumanReadableArtProfileParser.java
@@ -9,8 +9,9 @@
 import com.android.tools.r8.profile.art.diagnostic.HumanReadableArtProfileParserErrorDiagnostic;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.utils.Action;
-import com.android.tools.r8.utils.ClassReferenceUtils;
 import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.android.tools.r8.utils.Reporter;
 import java.io.BufferedReader;
@@ -65,12 +66,16 @@
   }
 
   public boolean parseRule(String rule) {
-    ArtProfileMethodRuleInfoImpl.Builder methodRuleInfoBuilder =
-        ArtProfileMethodRuleInfoImpl.builder();
-    rule = parseFlag(rule, 'H', methodRuleInfoBuilder::setIsHot);
-    rule = parseFlag(rule, 'S', methodRuleInfoBuilder::setIsStartup);
-    rule = parseFlag(rule, 'P', methodRuleInfoBuilder::setIsPostStartup);
-    return parseClassOrMethodDescriptor(rule, methodRuleInfoBuilder.build());
+    try {
+      ArtProfileMethodRuleInfoImpl.Builder methodRuleInfoBuilder =
+          ArtProfileMethodRuleInfoImpl.builder();
+      rule = parseFlag(rule, 'H', methodRuleInfoBuilder::setIsHot);
+      rule = parseFlag(rule, 'S', methodRuleInfoBuilder::setIsStartup);
+      rule = parseFlag(rule, 'P', methodRuleInfoBuilder::setIsPostStartup);
+      return parseClassOrMethodRule(rule, methodRuleInfoBuilder.build());
+    } catch (Throwable t) {
+      return false;
+    }
   }
 
   private static String parseFlag(String rule, char c, Action action) {
@@ -81,23 +86,30 @@
     return rule;
   }
 
-  private boolean parseClassOrMethodDescriptor(
-      String descriptor, ArtProfileMethodRuleInfoImpl methodRuleInfo) {
-    int arrowStartIndex = descriptor.indexOf("->");
+  private boolean parseClassOrMethodRule(String rule, ArtProfileMethodRuleInfoImpl methodRuleInfo) {
+    int arrowStartIndex = rule.indexOf("->");
     if (arrowStartIndex >= 0) {
-      return parseMethodRule(descriptor, methodRuleInfo, arrowStartIndex);
+      return parseMethodRule(rule, methodRuleInfo, arrowStartIndex);
     } else if (methodRuleInfo.isEmpty()) {
-      return parseClassRule(descriptor);
+      return parseClassRule(rule);
     } else {
       return false;
     }
   }
 
   private boolean parseClassRule(String descriptor) {
-    ClassReference classReference = ClassReferenceUtils.parseClassDescriptor(descriptor);
-    if (classReference == null) {
+    TypeReference typeReference = Reference.typeFromDescriptor(descriptor);
+    if (typeReference == null) {
       return false;
     }
+    if (typeReference.isArray()) {
+      typeReference = typeReference.asArray().getBaseType();
+    }
+    if (typeReference.isPrimitive()) {
+      return false;
+    }
+    assert typeReference.isClass();
+    ClassReference classReference = typeReference.asClass();
     if (rulePredicate.testClassRule(classReference, ArtProfileClassRuleInfoImpl.empty())) {
       profileBuilder.addClassRule(
           classRuleBuilder -> classRuleBuilder.setClassReference(classReference));
@@ -106,7 +118,9 @@
   }
 
   private boolean parseMethodRule(
-      String descriptor, ArtProfileMethodRuleInfoImpl methodRuleInfo, int arrowStartIndex) {
+      String rule, ArtProfileMethodRuleInfoImpl methodRuleInfo, int arrowStartIndex) {
+    int inlineCacheStartIndex = rule.indexOf('+', arrowStartIndex + 2);
+    String descriptor = inlineCacheStartIndex > 0 ? rule.substring(0, inlineCacheStartIndex) : rule;
     MethodReference methodReference =
         MethodReferenceUtils.parseSmaliString(descriptor, arrowStartIndex);
     if (methodReference == null) {
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 e1ac8ba..59c8d20 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -57,6 +57,7 @@
 
   // Locally generated synthetic classes.
   public final SyntheticKind LAMBDA = generator.forInstanceClass("Lambda");
+  public final SyntheticKind THREAD_LOCAL = generator.forInstanceClass("ThreadLocal");
 
   // TODO(b/214901256): Sharing of synthetic classes may lead to duplicate method errors.
   public final SyntheticKind NON_FIXED_INIT_TYPE_ARGUMENT =
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 13f630e..216bd47 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -98,6 +98,15 @@
     return results != null ? results.toArray(emptyArray) : original;
   }
 
+  /** Rewrites the input array to the output array unconditionally. */
+  public static <T, S> S[] mapAll(T[] original, Function<T, S> mapper, S[] emptyArray) {
+    ArrayList<S> results = new ArrayList<>(original.length);
+    for (T t : original) {
+      results.add(mapper.apply(t));
+    }
+    return results.toArray(emptyArray);
+  }
+
   public static <T> T[] filter(T[] original, Predicate<T> predicate, T[] emptyArray) {
     return map(original, e -> predicate.test(e) ? e : null, emptyArray);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/BoxBase.java b/src/main/java/com/android/tools/r8/utils/BoxBase.java
index d38d92b..93f70da 100644
--- a/src/main/java/com/android/tools/r8/utils/BoxBase.java
+++ b/src/main/java/com/android/tools/r8/utils/BoxBase.java
@@ -6,6 +6,7 @@
 
 import java.util.Comparator;
 import java.util.Objects;
+import java.util.function.Function;
 import java.util.function.Supplier;
 
 public abstract class BoxBase<T> {
@@ -39,6 +40,12 @@
     return oldValue;
   }
 
+  public T getAndCompute(Function<T, T> newValue) {
+    T t = get();
+    set(newValue.apply(t));
+    return t;
+  }
+
   void set(T value) {
     this.value = value;
   }
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 82d7e77..33a2afb 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -124,7 +124,8 @@
 
   // Set to true to run compilation in a single thread and without randomly shuffling the input.
   // This makes life easier when running R8 in a debugger.
-  public static final boolean DETERMINISTIC_DEBUGGING = false;
+  public static final boolean DETERMINISTIC_DEBUGGING =
+      System.getProperty("com.android.tools.r8.deterministicdebugging") != null;
 
   // Use a MethodCollection where most interleavings between reading and mutating is caught.
   public static final boolean USE_METHOD_COLLECTION_CONCURRENCY_CHECKED = false;
@@ -1972,7 +1973,6 @@
     public boolean enableDeadSwitchCaseElimination = true;
     public boolean enableInvokeSuperToInvokeVirtualRewriting = true;
     public boolean enableMultiANewArrayDesugaringForClassFiles = false;
-    public boolean enableRedundantConstructorBridgeRemoval = false;
     public boolean enableSwitchToIfRewriting = true;
     public boolean enableEnumUnboxingDebugLogs =
         System.getProperty("com.android.tools.r8.enableEnumUnboxingDebugLogs") != null;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 90d26b4..d71f6b5 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -54,6 +54,7 @@
 import com.android.tools.r8.naming.ClassNaming;
 import com.android.tools.r8.naming.ClassNaming.Builder;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
+import com.android.tools.r8.naming.MapVersion;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -69,6 +70,7 @@
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.naming.mappinginformation.OutlineCallsiteMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.OutlineMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.ResidualSignatureMappingInformation.ResidualMethodSignatureMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.RemoveInnerFramesAction;
 import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.ThrowsCondition;
@@ -640,16 +642,26 @@
             methodMappingInfo.add(CompilerSynthesizedMappingInformation.builder().build());
           }
 
-          // Don't emit pure identity mappings.
-          if (mappedPositions.isEmpty()
-              && methodMappingInfo.isEmpty()
-              && obfuscatedNameDexString == originalMethod.name
-              && originalMethod.holder == originalType) {
+          MapVersion mapFileVersion = appView.options().getMapFileVersion();
+          if (isIdentityMapping(
+              mapFileVersion,
+              mappedPositions,
+              methodMappingInfo,
+              method,
+              obfuscatedNameDexString,
+              originalMethod,
+              originalType)) {
             assert appView.options().lineNumberOptimization == LineNumberOptimization.OFF
                 || hasAtMostOnePosition(definition, appView.options())
                 || appView.isCfByteCodePassThrough(definition);
             continue;
           }
+          // TODO(b/169953605): Ensure we emit the residual signature information.
+          if (mapFileVersion.isGreaterThan(MapVersion.MAP_VERSION_2_1)
+              && originalMethod != method.getReference()) {
+            methodMappingInfo.add(
+                ResidualMethodSignatureMappingInformation.fromDexMethod(method.getReference()));
+          }
 
           MemberNaming memberNaming = new MemberNaming(originalSignature, obfuscatedName);
           onDemandClassNamingBuilder.computeIfAbsent().addMemberEntry(memberNaming);
@@ -689,26 +701,16 @@
             MappedPosition firstPosition = mappedPositions.get(i);
             int j = i + 1;
             MappedPosition lastPosition = firstPosition;
+            MappedPositionRange mappedPositionRange = MappedPositionRange.SINGLE_LINE;
             for (; j < mappedPositions.size(); j++) {
               // Break if this position cannot be merged with lastPosition.
               MappedPosition currentPosition = mappedPositions.get(j);
-              // We allow for ranges being mapped to the same line but not to other ranges:
-              //   1:10:void foo():42:42 -> a
-              // is OK since retrace(a(:7)) = 42, however, the following is not OK:
-              //   1:10:void foo():42:43 -> a
-              // since retrace(a(:7)) = 49, which is not correct.
-              boolean isSingleLine = currentPosition.originalLine == firstPosition.originalLine;
-              boolean differentDelta =
-                  currentPosition.originalLine - lastPosition.originalLine
-                      != currentPosition.obfuscatedLine - lastPosition.obfuscatedLine;
-              boolean isMappingRangeToSingleLine =
-                  firstPosition.obfuscatedLine != lastPosition.obfuscatedLine
-                      && firstPosition.originalLine == lastPosition.originalLine;
+              mappedPositionRange =
+                  mappedPositionRange.canAddNextMappingToRange(lastPosition, currentPosition);
               // Note that currentPosition.caller and lastPosition.class must be deep-compared since
               // multiple inlining passes lose the canonical property of the positions.
               if (currentPosition.method != lastPosition.method
-                  || (!isSingleLine && differentDelta)
-                  || (!isSingleLine && isMappingRangeToSingleLine)
+                  || mappedPositionRange.isOutOfRange()
                   || !Objects.equals(currentPosition.caller, lastPosition.caller)
                   // Break when we see a mapped outline
                   || currentPosition.outlineCallee != null
@@ -822,6 +824,28 @@
     return classNameMapperBuilder.build();
   }
 
+  private static boolean isIdentityMapping(
+      MapVersion mapFileVersion,
+      List<MappedPosition> mappedPositions,
+      List<MappingInformation> methodMappingInfo,
+      ProgramMethod method,
+      DexString obfuscatedNameDexString,
+      DexMethod originalMethod,
+      DexType originalType) {
+    if (mapFileVersion.isGreaterThan(MapVersion.MAP_VERSION_2_1)) {
+      // Don't emit pure identity mappings.
+      return mappedPositions.isEmpty()
+          && methodMappingInfo.isEmpty()
+          && originalMethod == method.getReference();
+    } else {
+      // Don't emit pure identity mappings.
+      return mappedPositions.isEmpty()
+          && methodMappingInfo.isEmpty()
+          && obfuscatedNameDexString == originalMethod.name
+          && originalMethod.holder == originalType;
+    }
+  }
+
   private static boolean hasAtMostOnePosition(
       DexEncodedMethod definition, InternalOptions options) {
     if (!mustHaveResidualDebugInfo(definition, options)) {
@@ -1414,6 +1438,55 @@
     }
   }
 
+  private enum MappedPositionRange {
+    // Single line represent a mapping on the form X:X:<method>:Y:Y.
+    SINGLE_LINE,
+    // Range to single line allows for a range on the left hand side, X:X':<method>:Y:Y
+    RANGE_TO_SINGLE,
+    // Same delta is when we have a range on both sides and the delta (line mapping between them)
+    // is the same: X:X':<method>:Y:Y' and delta(X,X') = delta(Y,Y')
+    SAME_DELTA,
+    // Out of range encodes a mapping range that cannot be encoded.
+    OUT_OF_RANGE;
+
+    private boolean isSingleLine() {
+      return this == SINGLE_LINE;
+    }
+
+    private boolean isRangeToSingle() {
+      return this == RANGE_TO_SINGLE;
+    }
+
+    private boolean isOutOfRange() {
+      return this == OUT_OF_RANGE;
+    }
+
+    public MappedPositionRange canAddNextMappingToRange(
+        MappedPosition lastPosition, MappedPosition currentPosition) {
+      if (isOutOfRange()) {
+        return this;
+      }
+      // We allow for ranges being mapped to the same line but not to other ranges:
+      //   1:10:void foo():42:42 -> a
+      // is OK since retrace(a(:7)) = 42, however, the following is not OK:
+      //   1:10:void foo():42:43 -> a
+      // since retrace(a(:7)) = 49, which is not correct.
+      boolean hasSameRightHandSide = lastPosition.originalLine == currentPosition.originalLine;
+      if (hasSameRightHandSide) {
+        boolean hasSameLeftHandSide = lastPosition.obfuscatedLine == currentPosition.obfuscatedLine;
+        return hasSameLeftHandSide ? SINGLE_LINE : RANGE_TO_SINGLE;
+      }
+      if (isRangeToSingle()) {
+        // We cannot recover a delta encoding if we have had range to single encoding.
+        return OUT_OF_RANGE;
+      }
+      boolean sameDelta =
+          currentPosition.originalLine - lastPosition.originalLine
+              == currentPosition.obfuscatedLine - lastPosition.obfuscatedLine;
+      return sameDelta ? SAME_DELTA : OUT_OF_RANGE;
+    }
+  }
+
   private static class OutlineFixupBuilder {
 
     private static final int MINIFIED_POSITION_REMOVED = -1;
diff --git a/src/main/java/com/android/tools/r8/utils/StackTraceUtils.java b/src/main/java/com/android/tools/r8/utils/StackTraceUtils.java
new file mode 100644
index 0000000..009c490
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/StackTraceUtils.java
@@ -0,0 +1,57 @@
+// 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.utils;
+
+import com.android.tools.r8.Keep;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+
+@Keep
+public class StackTraceUtils {
+
+  private static final String pathToWriteStacktrace =
+      System.getProperty("com.android.tools.r8.internalPathToStacktraces");
+
+  private static final String SEPARATOR = "@@@@";
+
+  private static final PrintStream printStream = getStacktracePrintStream();
+
+  private static final int samplingInterval = getSamplingInterval();
+
+  private static int getSamplingInterval() {
+    String setSamplingInterval =
+        System.getProperty("com.android.tools.r8.internalStackTraceSamplingInterval");
+    if (setSamplingInterval == null) {
+      return 1000;
+    }
+    return Integer.parseInt(setSamplingInterval);
+  }
+
+  private static int counter = 0;
+
+  private static PrintStream getStacktracePrintStream() {
+    if (pathToWriteStacktrace == null) {
+      throw new RuntimeException("pathToWriteStacktrace is null");
+    }
+    try {
+      return new PrintStream(pathToWriteStacktrace, StandardCharsets.UTF_8.name());
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * Utility function with the only purpose of being able to print stack traces from various
+   * inserted points in R8. See RetraceStackTraceFunctionalCompositionTest.
+   */
+  public static void printCurrentStack(long identifier) {
+    if (counter++ < samplingInterval) {
+      new RuntimeException("------(" + identifier + "," + counter + ")------")
+          .printStackTrace(printStream);
+      printStream.println(SEPARATOR);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 40a27ee..6c616b5 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -39,6 +39,19 @@
     return builder().withNoneRuntime().build();
   }
 
+  /**
+   * Returns true if the runtime uses resolution to lookup the constructor targeted by a given
+   * invoke, so that it is valid to have non-rebound constructor invokes.
+   *
+   * <p>Example: If value `v` is an uninitialized instanceof type `T`, then calling `T.<init>()`
+   * succeeds on ART even if `T.<init>()` does not exists, as ART will resolve the constructor, find
+   * `Object.<init>()`, and use this method to initialize `v`. On the JVM and on Dalvik, this is a
+   * runtime error.
+   */
+  public boolean canHaveNonReboundConstructorInvoke() {
+    return isDexRuntime() && getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L);
+  }
+
   public boolean canUseDefaultAndStaticInterfaceMethods() {
     assert isCfRuntime() || isDexRuntime();
     assert !isCfRuntime() || apiLevel == null
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 8d78785..726d550 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -173,10 +173,6 @@
     return addDontWarn("javax.annotation.Nullable");
   }
 
-  public T addDontWarnJavaLangReflectAnnotatedType() {
-    return addDontWarn("java.lang.reflect.AnnotatedType");
-  }
-
   public T addDontWarnJavaLangInvokeLambdaMetadataFactory() {
     return addDontWarn("java.lang.invoke.LambdaMetafactory");
   }
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
index 7c3bb9d..66dbfec 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
@@ -63,9 +63,6 @@
         .setMode(CompilationMode.DEBUG)
         .setMinApi(parameters.getApiLevel())
         .applyIf(
-            kotlinTestParameters.isKotlinDev(),
-            TestShrinkerBuilder::addDontWarnJavaLangReflectAnnotatedType)
-        .applyIf(
             parameters.isCfRuntime() && kotlinTestParameters.isKotlinDev(),
             TestShrinkerBuilder::addDontWarnJavaLangInvokeLambdaMetadataFactory)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
index 7d13c36..ccff561 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
@@ -36,6 +36,7 @@
 import com.google.common.collect.Sets.SetView;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -237,7 +238,7 @@
 
   public ProcessResult runDoublePerFileCompilation(Backend firstRoundOutput, boolean intermediate)
       throws Exception {
-    List<byte[]> outputsRoundOne = new ArrayList<>();
+    List<byte[]> outputsRoundOne = Collections.synchronizedList(new ArrayList<>());
     testForD8(firstRoundOutput)
         .addProgramClasses(CLASSES)
         .setMinApi(parameters.getApiLevel())
@@ -248,7 +249,9 @@
                   @Override
                   public void accept(
                       ByteDataView data, String descriptor, DiagnosticsHandler handler) {
-                    outputsRoundOne.add(data.copyByteData());
+                    byte[] bytes = data.copyByteData();
+                    assert bytes != null;
+                    outputsRoundOne.add(bytes);
                   }
                 }
                 : new DexFilePerClassFileConsumer.ForwardingConsumer(null) {
@@ -258,7 +261,9 @@
                       ByteDataView data,
                       Set<String> descriptors,
                       DiagnosticsHandler handler) {
-                    outputsRoundOne.add(data.copyByteData());
+                    byte[] bytes = data.copyByteData();
+                    assert bytes != null;
+                    outputsRoundOne.add(bytes);
                   }
 
                   @Override
@@ -270,6 +275,7 @@
 
     List<Path> outputsRoundTwo = new ArrayList<>();
     for (byte[] bytes : outputsRoundOne) {
+      assert bytes != null;
       outputsRoundTwo.add(
           testForD8(parameters.getBackend())
               .applyIf(
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportTest.java
new file mode 100644
index 0000000..929a3fb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportTest.java
@@ -0,0 +1,88 @@
+// 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.desugar.backports;
+
+import static com.android.tools.r8.utils.AndroidApiLevel.LATEST;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+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 ThreadLocalBackportTest extends DesugaredLibraryTestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkExpected);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(
+            parameters.isCfRuntime()
+                ? ToolHelper.getJava8RuntimeJar()
+                : ToolHelper.getAndroidJar(LATEST))
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkExpected);
+  }
+
+  private void checkExpected(TestRunResult<?> result) {
+    result.applyIf(
+        parameters.getRuntime().isCf()
+            || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
+            || parameters
+                .getRuntime()
+                .asDex()
+                .getMinApiLevel()
+                .isGreaterThanOrEqualTo(AndroidApiLevel.O),
+        r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+        parameters.getRuntime().isDex()
+            && parameters
+                .getRuntime()
+                .asDex()
+                .getMinApiLevel()
+                .isGreaterThanOrEqualTo(AndroidApiLevel.N)
+            && parameters.getRuntime().asDex().getMinApiLevel().isLessThan(AndroidApiLevel.O)
+            && parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+        r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+        r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Hello, world!");
+      System.out.println(threadLocal.get());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportWithDesugaredLibraryTest.java
new file mode 100644
index 0000000..67ee012
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ThreadLocalBackportWithDesugaredLibraryTest.java
@@ -0,0 +1,65 @@
+// 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.desugar.backports;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.SPECIFICATIONS_WITH_CF2CF;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_LEGACY;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_MINIMAL;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ThreadLocalBackportWithDesugaredLibraryTest extends DesugaredLibraryTestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+  @Parameter(2)
+  public CompilationSpecification compilationSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        ImmutableList.of(JDK8, JDK11_LEGACY, JDK11_MINIMAL, JDK11, JDK11_PATH),
+        SPECIFICATIONS_WITH_CF2CF);
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
+
+  @Test
+  public void testWithDesugaredLibrary() throws Exception {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Hello, world!");
+      System.out.println(threadLocal.get());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
index be94723..8c572cf 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
@@ -99,12 +99,6 @@
         .allowDiagnosticMessages()
         .allowUnusedDontWarnKotlinReflectJvmInternal(
             kotlinParameters.getCompiler().isNot(KOTLINC_1_3_72))
-        .applyIfR8TestBuilder(
-            b -> {
-              if (kotlinParameters.isKotlinDev()) {
-                b.addDontWarnJavaLangReflectAnnotatedType();
-              }
-            })
         .compile()
         .inspect(
             i -> {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineMappingInformationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineMappingInformationTest.java
index 8a540ba..2d5abb5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineMappingInformationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineMappingInformationTest.java
@@ -74,7 +74,6 @@
             HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .enableExperimentalMapFileVersion()
         .compile()
         .run(
             parameters.getRuntime(),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineWithInlineMappingInformationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineWithInlineMappingInformationTest.java
index fddf01c..a177c41 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineWithInlineMappingInformationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineWithInlineMappingInformationTest.java
@@ -73,7 +73,6 @@
             HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .enableExperimentalMapFileVersion()
         .compile()
         .run(
             parameters.getRuntime(),
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 f265abe..4032c83 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
@@ -9,7 +9,6 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -228,7 +227,9 @@
         references(clazz, "testSimpleWithLazyInit", "void"));
 
     ClassSubject simpleWithLazyInit = inspector.clazz(SimpleWithLazyInit.class);
-    assertFalse(instanceMethods(simpleWithLazyInit).isEmpty());
+    assertEquals(
+        parameters.canHaveNonReboundConstructorInvoke(),
+        instanceMethods(simpleWithLazyInit).isEmpty());
     assertThat(simpleWithLazyInit.clinit(), isPresent());
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
index 5497d76..718a3e0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
@@ -136,7 +136,8 @@
 
   @Override
   public void inspectTestClass(ClassSubject clazz) {
-    assertEquals(11, clazz.allMethods().size());
+    assertEquals(
+        parameters.canHaveNonReboundConstructorInvoke() ? 10 : 11, clazz.allMethods().size());
     clazz.forAllMethods(
         method ->
             Assert.assertTrue(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
index 822f9f9..4a06340 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
@@ -24,8 +24,8 @@
 @RunWith(Parameterized.class)
 public abstract class UnusedArgumentsTestBase extends TestBase {
 
-  private final TestParameters parameters;
-  private final boolean minification;
+  protected final TestParameters parameters;
+  protected final boolean minification;
 
   public UnusedArgumentsTestBase(TestParameters parameters, boolean minification) {
     this.parameters = parameters;
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
index acd52f2..214b507 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
@@ -41,9 +41,6 @@
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST))
         .addKeepRules(rules)
         .applyIf(
-            notShrinking && kotlinParameters.isKotlinDev(),
-            TestShrinkerBuilder::addDontWarnJavaLangReflectAnnotatedType)
-        .applyIf(
             notShrinking && kotlinParameters.isKotlinDev() && parameters.isCfRuntime(),
             TestShrinkerBuilder::addDontWarnJavaLangInvokeLambdaMetadataFactory)
         .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
index 79af7bd..5272541 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
@@ -105,10 +105,7 @@
     String renamedSuperTypeName = "com.android.tools.r8.kotlin.metadata.typealias_lib.FooBar";
     Path libJar =
         testForR8(parameters.getBackend())
-            .addClasspathFiles(
-                kotlinc.getKotlinStdlibJar(),
-                kotlinc.getKotlinReflectJar(),
-                kotlinc.getKotlinAnnotationJar())
+            .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
             .addProgramFiles(typeAliasLibJarMap.getForConfiguration(kotlinc, targetVersion))
             // Keep non-private members of Impl
             .addKeepRules("-keep class **.Impl { !private *; }")
@@ -126,8 +123,8 @@
             .addKeepRules("-keep class " + PKG + ".typealias_lib.*Tester$Companion { *; }")
             .addKeepRules("-keep class " + PKG + ".typealias_lib.SubTypeOfAlias { *; }")
             .addApplyMapping(superTypeName + " -> " + renamedSuperTypeName + ":")
+            .addKeepKotlinMetadata()
             .addKeepAttributes(
-                ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
                 ProguardKeepAttributes.SIGNATURE,
                 ProguardKeepAttributes.INNER_CLASSES,
                 ProguardKeepAttributes.ENCLOSING_METHOD)
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
index a5400d6..6078cef 100644
--- a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
@@ -102,9 +102,6 @@
         .applyIf(
             parameters.isCfRuntime() && kotlinParameters.isKotlinDev(),
             TestShrinkerBuilder::addDontWarnJavaLangInvokeLambdaMetadataFactory)
-        .applyIf(
-            kotlinParameters.isKotlinDev(),
-            TestShrinkerBuilder::addDontWarnJavaLangReflectAnnotatedType)
         .compile()
         .assertNoErrorMessages()
         // -keepattributes Signature is added in kotlin-reflect from version 1.4.20.
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
index 0855291..1a73766 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
@@ -59,7 +59,6 @@
         .addKeepAttributeSourceFile()
         .setMinApi(parameters.getApiLevel())
         .enableInliningAnnotations()
-        .enableExperimentalMapFileVersion()
         .run(parameters.getRuntime(), Caller.class, getArgs())
         .assertFailureWithErrorThatThrows(NullPointerException.class)
         .inspectStackTrace(
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 e0a469c..57761fe 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
@@ -5,6 +5,7 @@
 package com.android.tools.r8.regress.b69825683;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.startsWith;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -19,20 +20,20 @@
 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 Regress69825683Test extends TestBase {
-  private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}")
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public Regress69825683Test(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   @Test
   public void outerConstructsInner() throws Exception {
     Class<?> inner = com.android.tools.r8.regress.b69825683.outerconstructsinner.Outer.Inner.class;
@@ -52,7 +53,7 @@
                 "  synthetic void <init>(...);",
                 "}",
                 "-keepunusedarguments class " + inner.getName() + " {",
-                "  synthetic void <init>(...);",
+                "  void <init>(...);",
                 "}")
             .addOptionsModification(options -> options.enableClassInlining = false)
             .addDontObfuscate()
@@ -87,7 +88,10 @@
             .setMinApi(parameters.getApiLevel())
             // Run code to check that the constructor with synthetic class as argument is present.
             .run(parameters.getRuntime(), clazz)
-            .assertSuccessWithOutputThatMatches(startsWith(clazz.getName()))
+            .assertSuccessWithOutputThatMatches(
+                parameters.canHaveNonReboundConstructorInvoke()
+                    ? equalTo("")
+                    : startsWith(clazz.getName()))
             .inspector();
 
     List<FoundClassSubject> classes = inspector.allClasses();
diff --git a/src/test/java/com/android/tools/r8/regress/b79498926/B79498926.java b/src/test/java/com/android/tools/r8/regress/b79498926/B79498926.java
index 2cf6b4f..d10454d 100644
--- a/src/test/java/com/android/tools/r8/regress/b79498926/B79498926.java
+++ b/src/test/java/com/android/tools/r8/regress/b79498926/B79498926.java
@@ -8,6 +8,9 @@
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.Origin;
 import java.io.IOException;
@@ -15,8 +18,20 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
-public class B79498926 {
+@RunWith(Parameterized.class)
+public class B79498926 extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().build();
+  }
 
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
@@ -33,6 +48,6 @@
             .setProgramConsumer(consumer)
             .setDisableDesugaring(true)
             .build());
-    ToolHelper.runDex2Oat(outDex, outOat);
+    ToolHelper.runDex2Oat(outDex, outOat, parameters.getRuntime().asDex().getVm());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceStackTraceFunctionalCompositionTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceStackTraceFunctionalCompositionTest.java
new file mode 100644
index 0000000..09a05ed
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceStackTraceFunctionalCompositionTest.java
@@ -0,0 +1,263 @@
+// 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.retrace;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.StackTraceUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Supplier;
+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.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class RetraceStackTraceFunctionalCompositionTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  private Path rewrittenR8Jar;
+  private static final int SAMPLING_SIZE = 50000;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    assumeTrue(ToolHelper.isLinux());
+    return getTestParameters().withDefaultCfRuntime().build();
+  }
+
+  private void insertPrintingOfStacktraces(Path path, Path outputPath) throws Exception {
+    String entryNameForClass = ZipUtils.zipEntryNameForClass(StackTraceUtils.class);
+    Box<Long> idBox = new Box<>(0L);
+    ZipUtils.map(
+        path,
+        outputPath,
+        (zipEntry, bytes) -> {
+          String entryName = zipEntry.getName();
+          // Only insert into the R8 namespace, this will provide a better sampling than inserting
+          // into all calls due to more inlining/outlining could have happened.
+          if (ZipUtils.isClassFile(entryName)
+              && !entryNameForClass.equals(entryName)
+              && entryName.contains("com/android/tools/r8/")) {
+            return transformer(bytes, null)
+                .addClassTransformer(
+                    new ClassTransformer() {
+                      @Override
+                      public MethodVisitor visitMethod(
+                          int access,
+                          String name,
+                          String descriptor,
+                          String signature,
+                          String[] exceptions) {
+                        MethodVisitor sub =
+                            super.visitMethod(access, name, descriptor, signature, exceptions);
+                        if (name.equals("<clinit>")) {
+                          return sub;
+                        } else {
+                          return new InsertStackTraceCallTransformer(
+                              sub, () -> idBox.getAndCompute(x -> x + 1));
+                        }
+                      }
+                      ;
+                    })
+                .transform();
+          }
+          return bytes;
+        });
+  }
+
+  private static class InsertStackTraceCallTransformer extends MethodTransformer {
+
+    private boolean insertedStackTraceCall = false;
+    private final Supplier<Long> idGenerator;
+
+    private InsertStackTraceCallTransformer(MethodVisitor visitor, Supplier<Long> idGenerator) {
+      this.mv = visitor;
+      this.idGenerator = idGenerator;
+    }
+
+    @Override
+    public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
+      insertPrintIfFirstInstruction();
+      super.visitFieldInsn(opcode, owner, name, descriptor);
+    }
+
+    @Override
+    public void visitInsn(int opcode) {
+      insertPrintIfFirstInstruction();
+      super.visitInsn(opcode);
+    }
+
+    @Override
+    public void visitLdcInsn(Object value) {
+      insertPrintIfFirstInstruction();
+      super.visitLdcInsn(value);
+    }
+
+    @Override
+    public void visitMethodInsn(
+        int opcode, String owner, String name, String descriptor, boolean isInterface) {
+      insertPrintIfFirstInstruction();
+      super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+    }
+
+    private void insertPrintIfFirstInstruction() {
+      if (!insertedStackTraceCall) {
+        super.visitLdcInsn(idGenerator.get());
+        super.visitMethodInsn(
+            INVOKESTATIC, binaryName(StackTraceUtils.class), "printCurrentStack", "(J)V", false);
+        insertedStackTraceCall = true;
+      }
+    }
+
+    @Override
+    public void visitMaxs(int maxStack, int maxLocals) {
+      super.visitMaxs(maxStack + 2, maxLocals);
+    }
+  }
+
+  private Path getRewrittenR8Jar() throws Exception {
+    if (rewrittenR8Jar != null) {
+      return rewrittenR8Jar;
+    }
+    rewrittenR8Jar = temp.newFolder().toPath().resolve("r8_with_deps_with_prints.jar");
+    insertPrintingOfStacktraces(ToolHelper.R8_WITH_DEPS_JAR, rewrittenR8Jar);
+    return rewrittenR8Jar;
+  }
+
+  @Test
+  public void testR8RetraceAndComposition() throws Exception {
+    Path rewrittenR8Jar = getRewrittenR8Jar();
+    List<String> originalStackTraces = generateStackTraces(rewrittenR8Jar);
+    Map<String, List<String>> originalPartitions = partitionStacktraces(originalStackTraces);
+
+    List<String> originalStackTracesDeterministicCheck = generateStackTraces(rewrittenR8Jar);
+    Map<String, List<String>> deterministicPartitions =
+        partitionStacktraces(originalStackTracesDeterministicCheck);
+    comparePartitionedStackTraces(originalPartitions, deterministicPartitions);
+
+    // Compile rewritten R8 with R8 to obtain first level
+    Pair<Path, Path> r8OfR8 = compileR8WithR8(rewrittenR8Jar);
+    List<String> firstLevelStackTraces = generateStackTraces(r8OfR8.getFirst());
+
+    // If we retrace the entire file we should get the same result as the original.
+    List<String> retracedFirstLevelStackTraces = new ArrayList<>();
+    Retrace.run(
+        RetraceCommand.builder()
+            .setRetracedStackTraceConsumer(retracedFirstLevelStackTraces::addAll)
+            .setStackTrace(firstLevelStackTraces)
+            .setMappingSupplier(
+                ProguardMappingSupplier.builder()
+                    .setProguardMapProducer(ProguardMapProducer.fromPath(r8OfR8.getSecond()))
+                    .build())
+            .build());
+    Map<String, List<String>> firstRoundPartitions =
+        partitionStacktraces(retracedFirstLevelStackTraces);
+    comparePartitionedStackTraces(originalPartitions, firstRoundPartitions);
+  }
+
+  private void comparePartitionedStackTraces(
+      Map<String, List<String>> one, Map<String, List<String>> other) {
+    for (Entry<String, List<String>> keyStackTraceEntry : one.entrySet()) {
+      String oneAsString = StringUtils.lines(keyStackTraceEntry.getValue());
+      String otherAsString = StringUtils.lines(other.get(keyStackTraceEntry.getKey()));
+      assertEquals(oneAsString, otherAsString);
+    }
+    assertEquals(one.keySet(), other.keySet());
+  }
+
+  private Map<String, List<String>> partitionStacktraces(List<String> allStacktraces) {
+    Map<String, List<String>> partitions = new LinkedHashMap<>();
+    int lastIndex = 0;
+    for (int i = 0; i < allStacktraces.size(); i++) {
+      if (allStacktraces.get(i).contains("@@@@")) {
+        List<String> stackTrace = allStacktraces.subList(lastIndex, i);
+        String keyForStackTrace = getKeyForStackTrace(stackTrace);
+        List<String> existing = partitions.put(keyForStackTrace, stackTrace);
+        assertNull(existing);
+        lastIndex = i + 1;
+        i++;
+      }
+    }
+    return partitions;
+  }
+
+  private String getKeyForStackTrace(List<String> stackTrace) {
+    String identifier = "java.lang.RuntimeException: ------(";
+    String firstLine = stackTrace.get(0);
+    int index = firstLine.indexOf(identifier);
+    assertEquals(0, index);
+    String endIdentifier = ")------";
+    int endIndex = firstLine.indexOf(endIdentifier);
+    assertTrue(endIndex > 0);
+    return firstLine.substring(index + identifier.length(), endIndex);
+  }
+
+  private Pair<Path, Path> compileR8WithR8(Path r8Input) throws Exception {
+    Path MAIN_KEEP = Paths.get("src/main/keep.txt");
+    Path jar = temp.newFolder().toPath().resolve("out.jar");
+    Path map = temp.newFolder().toPath().resolve("out.map");
+    testForR8(Backend.CF)
+        .setMode(CompilationMode.RELEASE)
+        .addProgramFiles(r8Input)
+        .addKeepRuleFiles(MAIN_KEEP)
+        .allowUnusedProguardConfigurationRules()
+        .addDontObfuscate()
+        .compile()
+        .apply(c -> FileUtils.writeTextFile(map, c.getProguardMap()))
+        .writeToZip(jar);
+    return Pair.create(jar, map);
+  }
+
+  private List<String> generateStackTraces(Path r8Jar) throws Exception {
+    File stacktraceOutput = new File(temp.newFolder(), "stacktraces.txt");
+    stacktraceOutput.createNewFile();
+    testForExternalR8(Backend.DEX, parameters.getRuntime())
+        .useProvidedR8(r8Jar)
+        .addProgramClasses(HelloWorld.class)
+        .addKeepMainRule(HelloWorld.class)
+        .setMinApi(AndroidApiLevel.B)
+        .addJvmFlag("-Dcom.android.tools.r8.deterministicdebugging=true")
+        .addJvmFlag("-Dcom.android.tools.r8.internalStackTraceSamplingInterval=" + SAMPLING_SIZE)
+        .addJvmFlag("-Dcom.android.tools.r8.internalPathToStacktraces=" + stacktraceOutput.toPath())
+        .compile();
+    return Files.readAllLines(stacktraceOutput.toPath(), StandardCharsets.UTF_8);
+  }
+
+  public static class HelloWorld {
+
+    public static void main(String[] args) {
+      System.out.println("Hello World");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiResidualSignatureTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiResidualSignatureTest.java
new file mode 100644
index 0000000..21e1905
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiResidualSignatureTest.java
@@ -0,0 +1,85 @@
+// 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.retrace.api;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceFieldElement;
+import com.android.tools.r8.retrace.RetraceMethodElement;
+import com.android.tools.r8.retrace.Retracer;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetraceApiResidualSignatureTest extends RetraceApiTestBase {
+
+  public RetraceApiResidualSignatureTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final String mapping =
+        "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }\n"
+            + "some.Class -> a:\n"
+            + "  some.SuperType field -> a\n"
+            + "  # { id: 'com.android.tools.r8.residualsignature', signature: 'Ljava/lang/Object;'"
+            + " }\n"
+            + "  some.SubType field -> a\n"
+            + "  # { id: 'com.android.tools.r8.residualsignature', signature: 'Lsome/SuperType;'"
+            + " }\n"
+            + "  void foo(int, int) -> x\n"
+            + "  # { id: 'com.android.tools.r8.residualsignature', signature: '(I)V' }\n"
+            + "  void foo(int) -> x\n"
+            + "  # { id: 'com.android.tools.r8.residualsignature', signature: '()V' }\n";
+
+    @Test
+    public void testResidualSignature() {
+      Retracer retracer =
+          Retracer.createDefault(
+              ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+      ClassReference aClass = Reference.classFromTypeName("a");
+      List<RetraceMethodElement> fooWithTwoArgs =
+          retracer.retraceMethod(Reference.methodFromDescriptor(aClass, "x", "(I)V")).stream()
+              .collect(Collectors.toList());
+      // TODO(b/169953605): Use the residual signature information to prune the result.
+      assertEquals(2, fooWithTwoArgs.size());
+      List<RetraceMethodElement> fooWithOneArg =
+          retracer.retraceMethod(Reference.methodFromDescriptor(aClass, "x", "()V")).stream()
+              .collect(Collectors.toList());
+      // TODO(b/169953605): Use the residual signature information to prune the result.
+      assertEquals(2, fooWithOneArg.size());
+      List<RetraceFieldElement> fieldWithSuperType =
+          retracer
+              .retraceField(
+                  Reference.field(aClass, "a", Reference.typeFromTypeName("java.lang.Object")))
+              .stream()
+              .collect(Collectors.toList());
+      // TODO(b/169953605): Use the residual signature information to prune the result.
+      assertEquals(2, fieldWithSuperType.size());
+      List<RetraceFieldElement> fieldWithSubType =
+          retracer
+              .retraceField(
+                  Reference.field(aClass, "a", Reference.typeFromTypeName("some.SuperType")))
+              .stream()
+              .collect(Collectors.toList());
+      // TODO(b/169953605): Use the residual signature information to prune the result.
+      assertEquals(2, fieldWithSubType.size());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
index e1e73b8..327be4d 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
@@ -46,7 +46,7 @@
           RetracePartitionRoundTripInlineTest.ApiTest.class);
 
   public static List<Class<? extends RetraceApiBinaryTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
-      ImmutableList.of();
+      ImmutableList.of(RetraceApiResidualSignatureTest.ApiTest.class);
 
   private final TemporaryFolder temp;
 
diff --git a/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexTest.java b/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexTest.java
index 6ff7000..85f698d 100644
--- a/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexTest.java
@@ -14,7 +14,6 @@
 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.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
@@ -40,8 +39,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options -> options.testing.enableRedundantConstructorBridgeRemoval = true)
         .enableConstantArgumentAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
@@ -53,10 +50,6 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    boolean canHaveNonReboundConstructorInvoke =
-        parameters.isDexRuntime()
-            && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L);
-
     ClassSubject aClassSubject = inspector.clazz(A.class);
     assertThat(aClassSubject, isPresent());
     assertEquals(2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
@@ -64,13 +57,13 @@
     ClassSubject bClassSubject = inspector.clazz(B.class);
     assertThat(bClassSubject, isPresent());
     assertEquals(
-        canHaveNonReboundConstructorInvoke ? 0 : 2,
+        parameters.canHaveNonReboundConstructorInvoke() ? 0 : 2,
         bClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
 
     ClassSubject cClassSubject = inspector.clazz(C.class);
     assertThat(cClassSubject, isPresent());
     assertEquals(
-        canHaveNonReboundConstructorInvoke ? 0 : 2,
+        parameters.canHaveNonReboundConstructorInvoke() ? 0 : 2,
         cClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexWithClassMergingTest.java b/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexWithClassMergingTest.java
index c7f9a92..436e50e 100644
--- a/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexWithClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ForwardingConstructorShakingOnDexWithClassMergingTest.java
@@ -41,14 +41,12 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addOptionsModification(
-            options -> {
-              options.testing.enableRedundantConstructorBridgeRemoval = true;
-              options.testing.horizontalClassMergingTarget =
-                  (appView, candidates, target) ->
-                      Iterables.find(
-                          candidates,
-                          candidate -> candidate.getTypeName().equals(B.class.getTypeName()));
-            })
+            options ->
+                options.testing.horizontalClassMergingTarget =
+                    (appView, candidates, target) ->
+                        Iterables.find(
+                            candidates,
+                            candidate -> candidate.getTypeName().equals(B.class.getTypeName())))
         .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertMergedInto(A.class, B.class).assertNoOtherClassesMerged())
         .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/shaking/RetainIndirectlyReferencedConstructorShakingOnDexTest.java b/src/test/java/com/android/tools/r8/shaking/RetainIndirectlyReferencedConstructorShakingOnDexTest.java
index f914a87..4221794 100644
--- a/src/test/java/com/android/tools/r8/shaking/RetainIndirectlyReferencedConstructorShakingOnDexTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/RetainIndirectlyReferencedConstructorShakingOnDexTest.java
@@ -38,8 +38,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options -> options.testing.enableRedundantConstructorBridgeRemoval = true)
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
index a48c5e9..265e46a 100644
--- a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
@@ -9,47 +9,43 @@
 
 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 com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.lang.reflect.Method;
 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;
 
-@NoVerticalClassMerging
-interface I {
-  default void m() {
-    System.out.println("I::m");
-  }
-}
-
-class C implements I {
-}
-
-class BridgeInliningTestRunner {
-  public static void main(String[] args) throws Exception {
-    C obj = new C();
-    for (Method m : obj.getClass().getDeclaredMethods()) {
-      m.invoke(obj);
-    }
-  }
-}
-
+@RunWith(Parameterized.class)
 public class BridgeInliningTest extends TestBase {
-  private static final Class<?> MAIN = BridgeInliningTestRunner.class;
-  private static final String EXPECTED_OUTPUT = StringUtils.lines("I::m");
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(AndroidApiLevel.N)
+        .build();
+  }
 
   @Test
   public void test() throws Exception {
-    testForR8(Backend.DEX)
-        .addProgramClasses(I.class, C.class, MAIN)
-        .setMinApi(AndroidApiLevel.L)
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules("-keep interface " + I.class.getTypeName() + " { m(); }")
         .enableNoVerticalClassMergingAnnotations()
-        .addKeepMainRule(MAIN)
-        .addKeepRules("-keep interface **.I { m(); }")
-        .run(MAIN)
-        .assertSuccessWithOutput(EXPECTED_OUTPUT)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("I::m")
         .inspect(this::inspect);
   }
 
@@ -67,4 +63,21 @@
     //});
   }
 
+  static class Main {
+    public static void main(String[] args) throws Exception {
+      C obj = new C();
+      for (Method m : obj.getClass().getDeclaredMethods()) {
+        m.invoke(obj);
+      }
+    }
+  }
+
+  @NoVerticalClassMerging
+  interface I {
+    default void m() {
+      System.out.println("I::m");
+    }
+  }
+
+  static class C implements I {}
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/MethodsTestBase.java b/src/test/java/com/android/tools/r8/shaking/methods/MethodsTestBase.java
index 07be6e7..1cff714 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/MethodsTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/MethodsTestBase.java
@@ -6,6 +6,8 @@
 
 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 com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
@@ -13,13 +15,34 @@
 import java.util.List;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
+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 abstract class MethodsTestBase extends TestBase {
 
+  @Parameter(0)
+  public static TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimesAndAllApiLevels().build();
+  }
+
   public enum Shrinker {
     Proguard,
     R8Full,
-    R8Compat
+    R8Compat;
+
+    public boolean isProguard() {
+      return this == Proguard;
+    }
+
+    public boolean isR8Full() {
+      return this == R8Full;
+    }
   }
 
   public abstract Collection<Class<?>> getClasses();
@@ -29,7 +52,8 @@
   public void testOnR8(
       List<String> keepRules, BiConsumer<CodeInspector, Shrinker> inspector, String expected)
       throws Throwable {
-    testForR8(Backend.DEX)
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
         .enableNoVerticalClassMergingAnnotations()
         .addProgramClasses(getClasses())
         .addKeepRules(keepRules)
@@ -42,7 +66,7 @@
   public void testOnR8Compat(
       List<String> keepRules, BiConsumer<CodeInspector, Shrinker> inspector, String expected)
       throws Throwable {
-    testForR8Compat(Backend.DEX)
+    testForR8Compat(parameters.getBackend())
         .enableNoVerticalClassMergingAnnotations()
         .addProgramClasses(getClasses())
         .addKeepRules(keepRules)
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/pblc/PublicMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/pblc/PublicMethodsTest.java
index 2ce8046..477a310 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/pblc/PublicMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/pblc/PublicMethodsTest.java
@@ -10,12 +10,15 @@
 
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.shaking.methods.MethodsTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import java.util.Collection;
 import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Predicate;
 import org.junit.Test;
 
 @NoVerticalClassMerging
@@ -63,6 +66,24 @@
     return Main.class;
   }
 
+  private boolean willShrinkConstructors(Shrinker shrinker) {
+    return shrinker.isR8Full()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L);
+  }
+
+  private static BiConsumer<CodeInspector, Shrinker> applyInspectorIf(
+      Predicate<Shrinker> predicate,
+      BiConsumer<CodeInspector, Shrinker> thenInspector,
+      BiConsumer<CodeInspector, Shrinker> elseInspector) {
+    return (inspector, shrinker) -> {
+      if (predicate.test(shrinker)) {
+        thenInspector.accept(inspector, shrinker);
+      } else {
+        elseInspector.accept(inspector, shrinker);
+      }
+    };
+  }
+
   private void checkConstructors(CodeInspector inspector, Set<String> expected) {
     ClassSubject superSubject = inspector.clazz(Super.class);
     assertThat(superSubject, isPresent());
@@ -100,6 +121,11 @@
     checkConstructors(inspector, ImmutableSet.of("Super", "Sub", "SubSub"));
   }
 
+  private void checkAllMethodsAndSubSubConstructor(CodeInspector inspector, Shrinker shrinker) {
+    checkMethods(inspector, ImmutableSet.of("m1", "m2", "m3"));
+    checkConstructors(inspector, ImmutableSet.of("SubSub"));
+  }
+
   private void checkAllMethodsNoConstructorsInFullMode(CodeInspector inspector, Shrinker shrinker) {
     checkMethods(inspector, ImmutableSet.of("m1", "m2", "m3"));
     checkNoConstructorsInFullMode(inspector, shrinker);
@@ -115,6 +141,11 @@
     checkConstructors(inspector, ImmutableSet.of("Super", "Sub", "SubSub"));
   }
 
+  private void checkM1AndSubSubConstructor(CodeInspector inspector, Shrinker shrinker) {
+    checkMethods(inspector, ImmutableSet.of("m1"));
+    checkConstructors(inspector, ImmutableSet.of("SubSub"));
+  }
+
   private void checkOnlyM2(CodeInspector inspector, Shrinker shrinker) {
     checkMethods(inspector, ImmutableSet.of("m2"));
     checkNoConstructorsInFullMode(inspector, shrinker);
@@ -125,6 +156,11 @@
     checkConstructors(inspector, ImmutableSet.of("Super", "Sub", "SubSub"));
   }
 
+  private void checkM2AndSubSubConstructor(CodeInspector inspector, Shrinker shrinker) {
+    checkMethods(inspector, ImmutableSet.of("m2"));
+    checkConstructors(inspector, ImmutableSet.of("SubSub"));
+  }
+
   private void checkOnlyM3(CodeInspector inspector, Shrinker shrinker) {
     checkMethods(inspector, ImmutableSet.of("m3"));
     checkNoConstructorsInFullMode(inspector, shrinker);
@@ -135,11 +171,19 @@
     checkConstructors(inspector, ImmutableSet.of("Super", "Sub", "SubSub"));
   }
 
+  private void checkM3AndSubSubConstructor(CodeInspector inspector, Shrinker shrinker) {
+    checkMethods(inspector, ImmutableSet.of("m3"));
+    checkConstructors(inspector, ImmutableSet.of("SubSub"));
+  }
+
   @Test
   public void testKeepAllMethodsWithWildcard() throws Throwable {
     runTest(
         "-keep class **.SubSub { *; }",
-        this::checkAllMethodsAndAllConstructors,
+        applyInspectorIf(
+            this::willShrinkConstructors,
+            this::checkAllMethodsAndSubSubConstructor,
+            this::checkAllMethodsAndAllConstructors),
         allMethodsOutput());
   }
 
@@ -147,7 +191,10 @@
   public void testKeepAllMethodsWithMethods() throws Throwable {
     runTest(
         "-keep class **.SubSub { <methods>; }",
-        this::checkAllMethodsAndAllConstructors,
+        applyInspectorIf(
+            this::willShrinkConstructors,
+            this::checkAllMethodsAndSubSubConstructor,
+            this::checkAllMethodsAndAllConstructors),
         allMethodsOutput());
   }
 
@@ -163,7 +210,10 @@
   public void testKeepDefaultConstructorAndAllMethodsWithNameWildcard() throws Throwable {
     runTest(
         "-keep class **.SubSub { <init>(); void m*(); }",
-        this::checkAllMethodsAndAllConstructors,
+        applyInspectorIf(
+            this::willShrinkConstructors,
+            this::checkAllMethodsAndSubSubConstructor,
+            this::checkAllMethodsAndAllConstructors),
         allMethodsOutput());
   }
 
@@ -176,7 +226,10 @@
   public void testKeepDefaultConstructorAndKeepM1() throws Throwable {
     runTest(
         "-keep class **.SubSub { <init>(); void m1(); }",
-        this::checkM1AndAllConstructors,
+        applyInspectorIf(
+            this::willShrinkConstructors,
+            this::checkM1AndSubSubConstructor,
+            this::checkM1AndAllConstructors),
         onlyM1Output());
   }
 
@@ -189,7 +242,10 @@
   public void testKeepDefaultConstructorAndKeepM2() throws Throwable {
     runTest(
         "-keep class **.SubSub { <init>(); void m2(); }",
-        this::checkM2AndAllConstructors,
+        applyInspectorIf(
+            this::willShrinkConstructors,
+            this::checkM2AndSubSubConstructor,
+            this::checkM2AndAllConstructors),
         onlyM2Output());
   }
 
@@ -202,7 +258,10 @@
   public void testKeepDefaultConstructorAndKeepM3() throws Throwable {
     runTest(
         "-keep class **.SubSub { <init>(); void m3(); }",
-        this::checkM3AndAllConstructors,
+        applyInspectorIf(
+            this::willShrinkConstructors,
+            this::checkM3AndSubSubConstructor,
+            this::checkM3AndAllConstructors),
         onlyM3Output());
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/staticinterfacemethods/defaultmethods/StaticInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/staticinterfacemethods/defaultmethods/StaticInterfaceMethodsTest.java
index e9af866..ad6b6ea 100644
--- a/src/test/java/com/android/tools/r8/shaking/staticinterfacemethods/defaultmethods/StaticInterfaceMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/staticinterfacemethods/defaultmethods/StaticInterfaceMethodsTest.java
@@ -180,7 +180,7 @@
     if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
       // TODO(160142903): @NeverInline does not seem to work on static interface methods.
       // TODO(160144053): Using -keepclassmembers for this cause InterfaceWithStaticMethods to
-      // be renamed.
+      //  be renamed.
       if (allowObfuscation) {
         builder.add(
             "-if class " + InterfaceWithStaticMethods.class.getTypeName(),
diff --git a/tools/cherry-pick.py b/tools/cherry-pick.py
index a51ed00..6a7109c 100755
--- a/tools/cherry-pick.py
+++ b/tools/cherry-pick.py
@@ -53,15 +53,7 @@
       subprocess.run(['git', 'new-branch', branch, '--upstream-current'])
 
     subprocess.run(['git', 'cherry-pick', hash])
-
-    commit_message = subprocess.check_output(['git', 'log', '--format=%B', '-n', '1', 'HEAD'])
-    commit_lines = [l.strip() for l in commit_message.decode('UTF-8').split('\n')]
-    for line in commit_lines:
-      if line.startswith('Bug: '):
-        normalized = line.replace('Bug: ', '').replace('b/', '')
-        if len(normalized) > 0:
-          bugs.add(normalized)
-    confirm_and_upload(branch, args)
+    confirm_and_upload(branch, args, bugs)
     count = count + 1
 
   branch = 'cherry-%d' % count
@@ -96,18 +88,44 @@
     message += 'Bug: b/%s\n' % bug
 
   subprocess.run(['git', 'commit', '-a', '-m', message])
-  confirm_and_upload(branch, args)
+  confirm_and_upload(branch, args, None)
   if (not args.current_checkout):
-    answer = input('Done, press enter to delete checkout in %s' % os.getcwd())
+    while True:
+      try:
+        answer = input("Type 'delete' to finish and delete checkout in %s: " % os.getcwd())
+        if answer == 'delete':
+          break
+      except KeyboardInterrupt:
+        pass
 
-def confirm_and_upload(branch, args):
+def confirm_and_upload(branch, args, bugs):
   question = ('Ready to continue (cwd %s, will not upload to Gerrit)' % os.getcwd()
     if args.no_upload else
         'Ready to upload %s (cwd %s)' % (branch, os.getcwd()))
-  answer = input(question + ' [y/N]?')
-  if answer != 'y':
-    print('Aborting new branch for %s' % branch_version)
-    sys.exit(1)
+
+  while True:
+    try:
+      answer = input(question + ' [yes/abort]? ')
+      if answer == 'yes':
+        break
+      if answer == 'abort':
+        print('Aborting new branch for %s' % branch)
+        sys.exit(1)
+    except KeyboardInterrupt:
+      pass
+
+  # Compute the set of bug refs from the commit message after confirmation.
+  # If done before a conflicting cherry-pick status will potentially include
+  # references that are orthogonal to the pick.
+  if bugs != None:
+    commit_message = subprocess.check_output(['git', 'log', '--format=%B', '-n', '1', 'HEAD'])
+    commit_lines = [l.strip() for l in commit_message.decode('UTF-8').split('\n')]
+    for line in commit_lines:
+      if line.startswith('Bug: '):
+        normalized = line.replace('Bug: ', '').replace('b/', '')
+        if len(normalized) > 0:
+          bugs.add(normalized)
+
   if (not args.no_upload):
     subprocess.run(['git', 'cl', 'upload', '--bypass-hooks'])
 
diff --git a/tools/extractmarker.py b/tools/extractmarker.py
index b50a2fd..d8348dd 100755
--- a/tools/extractmarker.py
+++ b/tools/extractmarker.py
@@ -3,8 +3,30 @@
 # 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 sys
 import toolhelper
 
+def extractmarker(apk_or_dex, build=True):
+  stdout = toolhelper.run('extractmarker', [apk_or_dex], build=build, return_stdout=True)
+  return stdout
+
+def parse_options(argv):
+  result = argparse.ArgumentParser(
+      description='Relayout a given APK using a startup profile.')
+  result.add_argument('--no-build',
+                      action='store_true',
+                      default=False,
+                      help='To disable building using gradle')
+  options, args = result.parse_known_args(argv)
+  return options, args
+
+def main(argv):
+  options, args = parse_options(argv)
+  build = not options.no_build
+  for apk_or_dex in args:
+    print(extractmarker(apk_or_dex, build=build))
+    build = False
+
 if __name__ == '__main__':
-  sys.exit(toolhelper.run('extractmarker', sys.argv[1:]))
+  sys.exit(main(sys.argv[1:]))
diff --git a/tools/startup/relayout.py b/tools/startup/relayout.py
index 6220336..0ace441 100755
--- a/tools/startup/relayout.py
+++ b/tools/startup/relayout.py
@@ -11,8 +11,10 @@
 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 
 import apk_masseur
+import extractmarker
 import toolhelper
 import utils
+import zip_utils
 
 def parse_options(argv):
   result = argparse.ArgumentParser(
@@ -20,6 +22,15 @@
   result.add_argument('--apk',
                       help='Path to the .apk',
                       required=True)
+  result.add_argument('--desugared-library',
+                      choices=['auto', 'true', 'false'],
+                      default='auto',
+                      help='Whether the last dex file of the app is desugared '
+                           'library')
+  result.add_argument('--no-build',
+                      action='store_true',
+                      default=False,
+                      help='To disable building using gradle')
   result.add_argument('--out',
                       help='Destination of resulting apk',
                       required=True)
@@ -29,6 +40,29 @@
   options, args = result.parse_known_args(argv)
   return options, args
 
+def get_dex_to_relayout(options, temp):
+  marker = extractmarker.extractmarker(options.apk, build=not options.no_build)
+  if '~~L8' not in marker:
+    return [options.apk], None
+  dex_dir = os.path.join(temp, 'dex')
+  dex_predicate = \
+      lambda name : name.startswith('classes') and name.endswith('.dex')
+  extracted_dex_files = \
+      zip_utils.extract_all_that_matches(options.apk, dex_dir, dex_predicate)
+  desugared_library_dex = 'classes%s.dex' % len(extracted_dex_files)
+  assert desugared_library_dex in extracted_dex_files
+  return [
+      os.path.join(dex_dir, name) \
+          for name in extracted_dex_files if name != desugared_library_dex], \
+      os.path.join(dex_dir, desugared_library_dex)
+
+def has_desugared_library_dex(options):
+  if options.desugared_library == 'auto':
+    marker = extractmarker.extractmarker(
+        options.apk, build=not options.no_build)
+    return '~~L8' in marker
+  return options.desugared_library == 'true'
+
 def get_min_api(apk):
   aapt = os.path.join(utils.getAndroidBuildTools(), 'aapt')
   cmd = [aapt, 'dump', 'badging', apk]
@@ -46,14 +80,21 @@
         '--min-api', str(get_min_api(options.apk)),
         '--output', dex,
         '--no-desugaring',
-        '--release',
-        options.apk]
+        '--release']
+    dex_to_relayout, desugared_library_dex = get_dex_to_relayout(options, temp)
+    d8_args.extend(dex_to_relayout)
     extra_args = ['-Dcom.android.tools.r8.startup.profile=%s' % options.profile]
     toolhelper.run(
         'd8',
         d8_args,
+        build=not options.no_build,
         extra_args=extra_args,
         main='com.android.tools.r8.D8')
+    if desugared_library_dex is not None:
+      dex_files = [name for name in \
+          zip_utils.get_names_that_matches(dex, lambda x : True)]
+      zip_utils.add_file_to_zip(
+          desugared_library_dex, 'classes%s.dex' % str(len(dex_files) + 1), dex)
     apk_masseur.masseur(options.apk, dex=dex, out=options.out)
 
 if __name__ == '__main__':
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index c728953..881f6dc 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -63,10 +63,10 @@
       stdout, stderr = proc.communicate()
     finally:
       timer.cancel()
-    result = stdout if return_stdout else proc.returncode
+    result = stdout.decode('utf-8') if return_stdout else proc.returncode
   else:
     result = (
-        subprocess.check_output(cmd)
+        subprocess.check_output(cmd).decode('utf-8')
         if return_stdout
         else subprocess.call(cmd, stdout=stdout, stderr=stderr))
   duration = int((time.time() - start) * 1000)
diff --git a/tools/zip_utils.py b/tools/zip_utils.py
index b0571f2..795119b 100644
--- a/tools/zip_utils.py
+++ b/tools/zip_utils.py
@@ -8,3 +8,13 @@
 def add_file_to_zip(file, destination, zip_file):
   with zipfile.ZipFile(zip_file, 'a') as zip:
     zip.write(file, destination)
+
+def extract_all_that_matches(zip_file, destination, predicate):
+  with zipfile.ZipFile(zip_file) as zip:
+    names_to_extract = [name for name in zip.namelist() if predicate(name)]
+    zip.extractall(path=destination, members=names_to_extract)
+    return names_to_extract
+
+def get_names_that_matches(zip_file, predicate):
+  with zipfile.ZipFile(zip_file) as zip:
+    return [name for name in zip.namelist() if predicate(name)]