Merge commit '753744fdb2eadce122036738e2d5cad6f41c5386' into dev-release
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 9c6d6c3..3eab5e6 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -52,6 +52,7 @@
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
+import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.NestReducer;
 import com.android.tools.r8.ir.optimize.SwitchMapCollector;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxingCfMethods;
@@ -621,8 +622,7 @@
 
             new BridgeHoisting(appViewWithLiveness).run();
 
-            // TODO(b/130721661): Enable this assert.
-            // assert Inliner.verifyNoMethodsInlinedDueToSingleCallSite(appView);
+            assert Inliner.verifyAllSingleCallerMethodsHaveBeenPruned(appViewWithLiveness);
 
             assert appView.allMergedClasses().verifyAllSourcesPruned(appViewWithLiveness);
             assert appView.validateUnboxedEnumsHaveBeenPruned();
@@ -778,7 +778,7 @@
 
       // Overwrite SourceFile if specified. This step should be done after IR conversion.
       timing.begin("Rename SourceFile");
-      new SourceFileRewriter(appView, appView.appInfo().app()).run();
+      new SourceFileRewriter(appView).run();
       timing.end();
 
       // If a method filter is present don't produce output since the application is likely partial.
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 5441a9e..dab9181 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -321,7 +321,7 @@
     }
   }
 
-  private SourceFileEnvironment createSourceFileEnvironment(ProguardMapId proguardMapId) {
+  public static SourceFileEnvironment createSourceFileEnvironment(ProguardMapId proguardMapId) {
     if (proguardMapId == null) {
       return new SourceFileEnvironment() {
         @Override
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 492a4ec..0e165f7 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.naming.SeedMapper;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepClassInfo;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.KeepMethodInfo;
 import com.android.tools.r8.shaking.LibraryModeledPredicate;
@@ -537,6 +538,10 @@
     return keepInfo;
   }
 
+  public KeepClassInfo getKeepInfo(DexProgramClass clazz) {
+    return getKeepInfo().getClassInfo(clazz);
+  }
+
   public KeepMethodInfo getKeepInfo(ProgramMethod method) {
     return getKeepInfo().getMethodInfo(method);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 450bc51..209e51c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -733,6 +733,11 @@
     setCode(builder.build(), appView);
   }
 
+  public void unsetCode() {
+    checkIfObsolete();
+    code = null;
+  }
+
   public boolean keepLocals(InternalOptions options) {
     if (options.testing.noLocalsTableOnInput) {
       return false;
@@ -908,24 +913,6 @@
     return getReference().toSourceString();
   }
 
-  public DexEncodedMethod toAbstractMethod() {
-    checkIfObsolete();
-    // 'final' wants this to be *not* overridden, while 'abstract' wants this to be implemented in
-    // a subtype, i.e., self contradict.
-    assert !accessFlags.isFinal();
-    // static abstract is an invalid access combination and we should never create that.
-    assert !accessFlags.isStatic();
-    return builder(this)
-        .modifyAccessFlags(MethodAccessFlags::setAbstract)
-        .setIsLibraryMethodOverrideIf(
-            isNonPrivateVirtualMethod() && !isLibraryMethodOverride().isUnknown(),
-            isLibraryMethodOverride())
-        .unsetCode()
-        .addBuildConsumer(
-            method -> OptimizationFeedbackSimple.getInstance().unsetBridgeInfo(method))
-        .build();
-  }
-
   /**
    * Generates a {@link DexCode} object for the given instructions.
    */
@@ -1369,6 +1356,10 @@
     return apiLevelForCode;
   }
 
+  public void clearApiLevelForCode(AppView<?> appView) {
+    this.apiLevelForCode = AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView);
+  }
+
   public void setApiLevelForCode(AndroidApiLevel apiLevel) {
     assert apiLevel != null;
     this.apiLevelForCode = apiLevel;
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 fdee405..22eaec8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -643,6 +643,10 @@
   public final DexProto deserializeLambdaMethodProto =
       createProto(objectType, serializedLambdaType);
 
+  public final String defaultSourceFileAttributeString = "SourceFile";
+  public final DexString defaultSourceFileAttribute =
+      createString(defaultSourceFileAttributeString);
+
   // Dex system annotations.
   // See https://source.android.com/devices/tech/dalvik/dex-format.html#system-annotation
   public final DexType annotationDefault =
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index 23bd9f7..cd5e8de 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -127,6 +127,10 @@
     set(Constants.ACC_SYNCHRONIZED);
   }
 
+  public void demoteFromSynchronized() {
+    demote(Constants.ACC_SYNCHRONIZED);
+  }
+
   public void unsetSynchronized() {
     unset(Constants.ACC_SYNCHRONIZED);
   }
@@ -179,6 +183,14 @@
     set(Constants.ACC_ABSTRACT);
   }
 
+  public void demoteFromAbstract() {
+    demote(Constants.ACC_ABSTRACT);
+  }
+
+  public void promoteToAbstract() {
+    promote(Constants.ACC_ABSTRACT);
+  }
+
   public void unsetAbstract() {
     unset(Constants.ACC_ABSTRACT);
   }
@@ -191,6 +203,10 @@
     set(Constants.ACC_STRICT);
   }
 
+  public void demoteFromStrict() {
+    demote(Constants.ACC_STRICT);
+  }
+
   public void unsetStrict() {
     unset(Constants.ACC_STRICT);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index c03a7b8..98c8bd2 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -3,16 +3,21 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback;
+
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevel;
 
 /** Type representing a method definition in the programs compilation unit and its holder. */
 public final class ProgramMethod extends DexClassAndMethod
@@ -66,6 +71,47 @@
     definition.parameterAnnotationsList.collectIndexedItems(indexedItems);
   }
 
+  public boolean canBeConvertedToAbstractMethod(AppView<AppInfoWithLiveness> appView) {
+    return (appView.options().canUseAbstractMethodOnNonAbstractClass()
+            || getHolder().isAbstract()
+            || getHolder().isInterface())
+        && !getAccessFlags().isNative()
+        && !getAccessFlags().isPrivate()
+        && !getAccessFlags().isStatic()
+        && !appView.appInfo().isFailedResolutionTarget(getReference());
+  }
+
+  public void convertToAbstractOrThrowNullMethod(AppView<AppInfoWithLiveness> appView) {
+    if (!convertToAbstractMethodIfPossible(appView)) {
+      convertToThrowNullMethod(appView);
+    }
+  }
+
+  private boolean convertToAbstractMethodIfPossible(AppView<AppInfoWithLiveness> appView) {
+    boolean canBeAbstract = canBeConvertedToAbstractMethod(appView);
+    if (canBeAbstract) {
+      MethodAccessFlags accessFlags = getAccessFlags();
+      accessFlags.demoteFromFinal();
+      accessFlags.demoteFromStrict();
+      accessFlags.demoteFromSynchronized();
+      accessFlags.promoteToAbstract();
+      getDefinition().clearApiLevelForCode(appView);
+      getDefinition().unsetCode();
+      getSimpleFeedback().unsetOptimizationInfoForAbstractMethod(this);
+    }
+    return canBeAbstract;
+  }
+
+  public void convertToThrowNullMethod(AppView<?> appView) {
+    MethodAccessFlags accessFlags = getAccessFlags();
+    accessFlags.demoteFromAbstract();
+    Code emptyThrowingCode = getDefinition().buildEmptyThrowingCode(appView.options());
+    getDefinition().setApiLevelForCode(AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView));
+    getDefinition().setCode(emptyThrowingCode, appView);
+    getSimpleFeedback().markProcessed(getDefinition(), ConstraintWithTarget.ALWAYS);
+    getSimpleFeedback().unsetOptimizationInfoForThrowNullMethod(this);
+  }
+
   public void registerCodeReferences(UseRegistry<?> registry) {
     Code code = getDefinition().getCode();
     if (code != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
index 92a127d..4899a50 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
@@ -82,6 +82,12 @@
           continue;
         }
 
+        if (method.getDefinition().isDefaultInitializer()
+            && appView.hasProguardCompatibilityActions()
+            && appView.getProguardCompatibilityActions().isCompatInstantiated(method.getHolder())) {
+          continue;
+        }
+
         int numberOfCallSites = node.getNumberOfCallSites();
         if (numberOfCallSites == 1) {
           singleCallSite.add(reference);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 1d1a698..4fde21a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -270,7 +270,7 @@
               : null;
       this.enumUnboxer = EnumUnboxer.create(appViewWithLiveness);
       this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, enumUnboxer);
-      this.inliner = new Inliner(appViewWithLiveness, lensCodeRewriter);
+      this.inliner = new Inliner(appViewWithLiveness, this, lensCodeRewriter);
       this.outliner = Outliner.create(appViewWithLiveness);
       this.memberValuePropagation =
           options.enableValuePropagation ? new MemberValuePropagation(appViewWithLiveness) : null;
@@ -360,8 +360,7 @@
   public void convert(AppView<AppInfo> appView, ExecutorService executor)
       throws ExecutionException {
     LambdaDeserializationMethodRemover.run(appView);
-    workaroundAbstractMethodOnNonAbstractClassVerificationBug(
-        executor, OptimizationFeedbackIgnore.getInstance());
+    workaroundAbstractMethodOnNonAbstractClassVerificationBug(executor);
     DexApplication application = appView.appInfo().app();
     D8MethodProcessor methodProcessor = new D8MethodProcessor(this, executor);
     InterfaceProcessor interfaceProcessor =
@@ -603,7 +602,7 @@
   }
 
   private void workaroundAbstractMethodOnNonAbstractClassVerificationBug(
-      ExecutorService executorService, OptimizationFeedback feedback) throws ExecutionException {
+      ExecutorService executorService) throws ExecutionException {
     if (!options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()) {
       return;
     }
@@ -612,13 +611,8 @@
         appView.appInfo().classes(),
         clazz -> {
           if (!clazz.isAbstract()) {
-            clazz.forEachMethod(
-                method -> {
-                  if (method.isAbstract()) {
-                    method.accessFlags.unsetAbstract();
-                    finalizeEmptyThrowingCode(method, feedback);
-                  }
-                });
+            clazz.forEachProgramMethodMatching(
+                DexEncodedMethod::isAbstract, method -> method.convertToThrowNullMethod(appView));
           }
         },
         executorService);
@@ -633,8 +627,7 @@
     DexApplication application = appView.appInfo().app();
 
     computeReachabilitySensitivity(application);
-    workaroundAbstractMethodOnNonAbstractClassVerificationBug(
-        executorService, simpleOptimizationFeedback);
+    workaroundAbstractMethodOnNonAbstractClassVerificationBug(executorService);
 
     // The process is in two phases in general.
     // 1) Subject all DexEncodedMethods to optimization, except some optimizations that require
@@ -975,16 +968,6 @@
     processMethodsConcurrently(methods, executorService);
   }
 
-  public void optimizeSynthesizedClasses(
-      Collection<DexProgramClass> classes, ExecutorService executorService)
-      throws ExecutionException {
-    SortedProgramMethodSet methods = SortedProgramMethodSet.create();
-    for (DexProgramClass clazz : classes) {
-      clazz.forEachProgramMethod(methods::add);
-    }
-    processMethodsConcurrently(methods, executorService);
-  }
-
   public void optimizeSynthesizedMethod(ProgramMethod synthesizedMethod) {
     if (!synthesizedMethod.getDefinition().isProcessed()) {
       // Process the generated method, but don't apply any outlining.
@@ -1188,8 +1171,8 @@
             + ExceptionUtils.getMainStackTrace();
     assert !method.isProcessed()
             || !appView.enableWholeProgramOptimizations()
-            || !appView.appInfo().withLiveness().isNeverReprocessMethod(method.getReference())
-        : "Illegal reprocessing due to -neverreprocess rule: " + context.toSourceString();
+            || !appView.appInfo().withLiveness().isNeverReprocessMethod(context)
+        : "Unexpected reprocessing of method: " + context.toSourceString();
 
     if (typeChecker != null && !typeChecker.check(code)) {
       assert appView.enableWholeProgramOptimizations();
@@ -1200,14 +1183,14 @@
                   + method.toSourceString()
                   + "` does not type check and will be assumed to be unreachable.");
       options.reporter.warning(warning);
-      finalizeEmptyThrowingCode(method, feedback);
+      context.convertToThrowNullMethod(appView);
       return timing;
     }
 
     // This is the first point in time where we can assert that the types are sound. If this
     // assert fails, then the types that we have inferred are unsound, or the method does not type
     // check. In the latter case, the type checker should be extended to detect the issue such that
-    // we will return with finalizeEmptyThrowingCode() above.
+    // we will return with a throw-null method above.
     assert code.verifyTypes(appView);
     assert code.isConsistentSSA();
 
@@ -1653,13 +1636,6 @@
     }
   }
 
-  private void finalizeEmptyThrowingCode(DexEncodedMethod method, OptimizationFeedback feedback) {
-    assert options.isGeneratingClassFiles() || options.isGeneratingDex();
-    Code emptyThrowingCode = method.buildEmptyThrowingCode(options);
-    method.setCode(emptyThrowingCode, appView);
-    feedback.markProcessed(method, ConstraintWithTarget.ALWAYS);
-  }
-
   private void finalizeToCf(
       IRCode code, OptimizationFeedback feedback, MethodConversionOptions conversionOptions) {
     DexEncodedMethod method = code.method();
@@ -1984,6 +1960,7 @@
   public void pruneMethod(ProgramMethod method) {
     assert appView.enableWholeProgramOptimizations();
     assert method.getHolder().lookupMethod(method.getReference()) == null;
+    appView.withArgumentPropagator(argumentPropagator -> argumentPropagator.pruneMethod(method));
     if (inliner != null) {
       inliner.pruneMethod(method);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 80b9cbd..6cf694a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.BitSetUtils;
 import java.util.BitSet;
 import java.util.Set;
 
@@ -37,8 +38,6 @@
   void methodReturnsAbstractValue(
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue);
 
-  void unsetAbstractReturnValue(ProgramMethod method);
-
   void methodReturnsObjectWithUpperBoundType(
       DexEncodedMethod method, AppView<?> appView, TypeElement type);
 
@@ -48,7 +47,7 @@
 
   void methodReturnValueOnlyDependsOnArguments(DexEncodedMethod method);
 
-  void methodNeverReturnsNormally(DexEncodedMethod method);
+  void methodNeverReturnsNormally(ProgramMethod method);
 
   void markProcessed(DexEncodedMethod method, ConstraintWithTarget state);
 
@@ -66,8 +65,6 @@
   void setEnumUnboxerMethodClassification(
       ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification);
 
-  void unsetEnumUnboxerMethodClassification(ProgramMethod method);
-
   void setInstanceInitializerInfoCollection(
       DexEncodedMethod method, InstanceInitializerInfoCollection instanceInitializerInfoCollection);
 
@@ -82,4 +79,89 @@
   void classInitializerMayBePostponed(DexEncodedMethod method);
 
   void setUnusedArguments(ProgramMethod method, BitSet unusedArguments);
+
+  // Unset methods.
+
+  void unsetAbstractReturnValue(ProgramMethod method);
+
+  void unsetBridgeInfo(DexEncodedMethod method);
+
+  void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method);
+
+  void unsetClassInitializerMayBePostponed(ProgramMethod method);
+
+  void unsetClassInlinerMethodConstraint(ProgramMethod method);
+
+  void unsetDynamicLowerBoundReturnType(ProgramMethod method);
+
+  void unsetDynamicUpperBoundReturnType(ProgramMethod method);
+
+  void unsetEnumUnboxerMethodClassification(ProgramMethod method);
+
+  void unsetForceInline(ProgramMethod method);
+
+  void unsetInitializedClassesOnNormalExit(ProgramMethod method);
+
+  void unsetInitializerEnablingJavaVmAssertions(ProgramMethod method);
+
+  void unsetInlinedIntoSingleCallSite(ProgramMethod method);
+
+  void unsetInstanceInitializerInfoCollection(ProgramMethod method);
+
+  void unsetMayNotHaveSideEffects(ProgramMethod method);
+
+  void unsetNeverReturnsNormally(ProgramMethod method);
+
+  void unsetNonNullParamOnNormalExits(ProgramMethod method);
+
+  void unsetNonNullParamOrThrow(ProgramMethod method);
+
+  void unsetReachabilitySensitive(ProgramMethod method);
+
+  void unsetReturnedArgument(ProgramMethod method);
+
+  void unsetReturnValueOnlyDependsOnArguments(ProgramMethod method);
+
+  void unsetSimpleInliningConstraint(ProgramMethod method);
+
+  void unsetTriggerClassInitBeforeAnySideEffect(ProgramMethod method);
+
+  void unsetUnusedArguments(ProgramMethod method);
+
+  default void unsetOptimizationInfoForAbstractMethod(ProgramMethod method) {
+    if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
+      unsetAbstractReturnValue(method);
+      unsetBridgeInfo(method.getDefinition());
+      unsetCheckNullReceiverBeforeAnySideEffect(method);
+      unsetClassInitializerMayBePostponed(method);
+      unsetClassInlinerMethodConstraint(method);
+      unsetDynamicLowerBoundReturnType(method);
+      unsetDynamicUpperBoundReturnType(method);
+      unsetEnumUnboxerMethodClassification(method);
+      unsetForceInline(method);
+      unsetInitializedClassesOnNormalExit(method);
+      unsetInitializerEnablingJavaVmAssertions(method);
+      unsetInstanceInitializerInfoCollection(method);
+      unsetMayNotHaveSideEffects(method);
+      unsetNeverReturnsNormally(method);
+      unsetNonNullParamOnNormalExits(method);
+      unsetNonNullParamOrThrow(method);
+      unsetReachabilitySensitive(method);
+      unsetReturnedArgument(method);
+      unsetReturnValueOnlyDependsOnArguments(method);
+      unsetSimpleInliningConstraint(method);
+      unsetTriggerClassInitBeforeAnySideEffect(method);
+      unsetUnusedArguments(method);
+    }
+  }
+
+  default void unsetOptimizationInfoForThrowNullMethod(ProgramMethod method) {
+    unsetOptimizationInfoForAbstractMethod(method);
+    methodNeverReturnsNormally(method);
+    setUnusedArguments(
+        method, BitSetUtils.createFilled(true, method.getDefinition().getNumberOfArguments()));
+    if (method.getDefinition().isInstance()) {
+      markCheckNullReceiverBeforeAnySideEffect(method.getDefinition(), true);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index a54b717..6435f37 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -82,10 +82,6 @@
       methodsToRevisit.forEach(this::add);
     }
 
-    public void put(PostOptimization postOptimization) {
-      put(postOptimization.methodsToRevisit());
-    }
-
     // Some optimizations may change methods, creating new instances of the encoded methods with a
     // new signature. The compiler needs to update the set of methods that must be reprocessed
     // according to the graph lens.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java b/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
deleted file mode 100644
index 30c873d..0000000
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.conversion;
-
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
-
-/**
- * An abstraction of optimizations that require post processing of methods.
- */
-public interface PostOptimization {
-
-  /** @return a set of methods that need post processing. */
-  ProgramMethodSet methodsToRevisit();
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java
index 84e3558..326b3c9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java
@@ -57,6 +57,7 @@
   private final Map<DexType, DexType> backportCoreLibraryMember;
   private final Map<DexType, DexType> customConversions;
   private final List<Pair<DexType, DexString>> dontRewriteInvocation;
+  private final Set<DexType> dontRetargetLibMember;
   private final List<String> extraKeepRules;
   private final Set<DexType> wrapperConversions;
   private final PrefixRewritingMapper prefixRewritingMapper;
@@ -81,6 +82,7 @@
         ImmutableMap.of(),
         ImmutableSet.of(),
         ImmutableList.of(),
+        ImmutableSet.of(),
         ImmutableList.of(),
         new DesugarPrefixRewritingMapper(prefix, options.itemFactory, true));
   }
@@ -100,6 +102,7 @@
         ImmutableMap.of(),
         ImmutableSet.of(),
         ImmutableList.of(),
+        ImmutableSet.of(),
         ImmutableList.of(),
         PrefixRewritingMapper.empty()) {
 
@@ -129,6 +132,7 @@
       Map<DexType, DexType> customConversions,
       Set<DexType> wrapperConversions,
       List<Pair<DexType, DexString>> dontRewriteInvocation,
+      Set<DexType> dontRetargetLibMember,
       List<String> extraKeepRules,
       PrefixRewritingMapper prefixRewritingMapper) {
     this.requiredCompilationAPILevel = requiredCompilationAPILevel;
@@ -144,6 +148,7 @@
     this.customConversions = customConversions;
     this.wrapperConversions = wrapperConversions;
     this.dontRewriteInvocation = dontRewriteInvocation;
+    this.dontRetargetLibMember = dontRetargetLibMember;
     this.extraKeepRules = extraKeepRules;
     this.prefixRewritingMapper = prefixRewritingMapper;
   }
@@ -233,6 +238,10 @@
     return dontRewriteInvocation;
   }
 
+  public Set<DexType> getDontRetargetLibMember() {
+    return dontRetargetLibMember;
+  }
+
   public List<String> getExtraKeepRules() {
     return extraKeepRules;
   }
@@ -262,6 +271,8 @@
     private Map<DexType, DexType> customConversions = new IdentityHashMap<>();
     private Set<DexType> wrapperConversions = Sets.newIdentityHashSet();
     private List<Pair<DexType, DexString>> dontRewriteInvocation = new ArrayList<>();
+    private Set<DexType> dontRetargetLibMember = Sets.newIdentityHashSet();
+    ;
     private List<String> extraKeepRules = Collections.emptyList();
     private boolean supportAllCallbacksFromLibrary = FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY;
 
@@ -395,6 +406,11 @@
       return this;
     }
 
+    public Builder addDontRetargetLibMember(String dontRetargetLibMember) {
+      this.dontRetargetLibMember.add(stringClassToDexType(dontRetargetLibMember));
+      return this;
+    }
+
     private int sharpIndex(String typeAndSelector, String descr) {
       int index = typeAndSelector.lastIndexOf('#');
       if (index <= 0 || index >= typeAndSelector.length() - 1) {
@@ -428,6 +444,7 @@
           ImmutableMap.copyOf(customConversions),
           ImmutableSet.copyOf(wrapperConversions),
           ImmutableList.copyOf(dontRewriteInvocation),
+          ImmutableSet.copyOf(dontRetargetLibMember),
           ImmutableList.copyOf(extraKeepRules),
           rewritePrefix.isEmpty()
               ? PrefixRewritingMapper.empty()
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfigurationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfigurationParser.java
index 9b0862e..f8b69f6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfigurationParser.java
@@ -45,6 +45,7 @@
   static final String RETARGET_LIB_MEMBER_KEY = "retarget_lib_member";
   static final String EMULATE_INTERFACE_KEY = "emulate_interface";
   static final String DONT_REWRITE_KEY = "dont_rewrite";
+  static final String DONT_RETARGET_LIB_MEMBER_KEY = "dont_retarget_lib_member";
   static final String BACKPORT_KEY = "backport";
   static final String SHRINKER_CONFIG_KEY = "shrinker_config";
   static final String SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY = "support_all_callbacks_from_library";
@@ -222,5 +223,11 @@
         configurationBuilder.addDontRewriteInvocation(rewrite.getAsString());
       }
     }
+    if (jsonFlagSet.has(DONT_RETARGET_LIB_MEMBER_KEY)) {
+      JsonArray dontRetarget = jsonFlagSet.get(DONT_RETARGET_LIB_MEMBER_KEY).getAsJsonArray();
+      for (JsonElement rewrite : dontRetarget) {
+        configurationBuilder.addDontRetargetLibMember(rewrite.getAsString());
+      }
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
index c268bfd..1a2adfb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
@@ -121,6 +121,13 @@
         // The class has already been desugared.
         continue;
       }
+      if (appView
+          .options()
+          .desugaredLibraryConfiguration
+          .getDontRetargetLibMember()
+          .contains(clazz.getType())) {
+        continue;
+      }
       clazz.addExtraInterfaces(
           Collections.singletonList(new ClassTypeSignature(newInterface.type)));
       eventConsumer.acceptInterfaceInjection(clazz, newInterface);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfToCfRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfToCfRewriter.java
new file mode 100644
index 0000000..76c468a6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfToCfRewriter.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.records;
+
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeDynamicOnRecord;
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.parseInvokeDynamicOnRecord;
+
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.RecordInvokeDynamic;
+import com.android.tools.r8.naming.NamingLens;
+import java.util.ArrayList;
+
+/** Used to shrink records in Cf to Cf compilations */
+public class RecordCfToCfRewriter {
+
+  private final AppView<?> appView;
+
+  public static RecordCfToCfRewriter create(AppView<?> appView) {
+    if (appView.enableWholeProgramOptimizations()
+        && appView.options().isGeneratingClassFiles()
+        && appView.options().testing.enableRecordModeling) {
+      return new RecordCfToCfRewriter(appView);
+    }
+    return null;
+  }
+
+  private RecordCfToCfRewriter(AppView<?> appView) {
+    this.appView = appView;
+  }
+
+  // Called after final tree shaking, prune and minify field names and field values.
+  public CfInvokeDynamic rewriteRecordInvokeDynamic(
+      CfInvokeDynamic invokeDynamic, ProgramMethod context, NamingLens namingLens) {
+    if (!isInvokeDynamicOnRecord(invokeDynamic, appView, context)) {
+      return invokeDynamic;
+    }
+    RecordInvokeDynamic recordInvokeDynamic =
+        parseInvokeDynamicOnRecord(invokeDynamic, appView, context);
+    DexString newFieldNames =
+        recordInvokeDynamic
+            .computeRecordFieldNamesComputationInfo()
+            .internalComputeNameFor(
+                recordInvokeDynamic.getRecordClass().type,
+                appView,
+                appView.graphLens(),
+                namingLens);
+    DexField[] newFields = computePresentFields(appView.graphLens(), recordInvokeDynamic);
+    return writeRecordInvokeDynamic(
+        recordInvokeDynamic.withFieldNamesAndFields(newFieldNames, newFields));
+  }
+
+  private DexField[] computePresentFields(
+      GraphLens graphLens, RecordInvokeDynamic recordInvokeDynamic) {
+    ArrayList<DexField> finalFields = new ArrayList<>();
+    for (DexField field : recordInvokeDynamic.getFields()) {
+      DexEncodedField dexEncodedField =
+          recordInvokeDynamic
+              .getRecordClass()
+              .lookupInstanceField(graphLens.getRenamedFieldSignature(field));
+      if (dexEncodedField != null) {
+        finalFields.add(field);
+      }
+    }
+    DexField[] newFields = new DexField[finalFields.size()];
+    for (int i = 0; i < finalFields.size(); i++) {
+      newFields[i] = finalFields.get(i);
+    }
+    return newFields;
+  }
+
+  private CfInvokeDynamic writeRecordInvokeDynamic(RecordInvokeDynamic recordInvokeDynamic) {
+    DexItemFactory factory = appView.dexItemFactory();
+    DexMethodHandle bootstrapMethod =
+        new DexMethodHandle(
+            MethodHandleType.INVOKE_STATIC, factory.objectMethodsMembers.bootstrap, false, null);
+    ArrayList<DexValue> bootstrapArgs = new ArrayList<>();
+    bootstrapArgs.add(new DexValueType(recordInvokeDynamic.getRecordClass().type));
+    bootstrapArgs.add(new DexValueString(recordInvokeDynamic.getFieldNames()));
+    for (DexField field : recordInvokeDynamic.getFields()) {
+      bootstrapArgs.add(
+          new DexValueMethodHandle(
+              new DexMethodHandle(MethodHandleType.INSTANCE_GET, field, false, null)));
+    }
+    return new CfInvokeDynamic(
+        factory.createCallSite(
+            recordInvokeDynamic.getMethodName(),
+            recordInvokeDynamic.getMethodProto(),
+            bootstrapMethod,
+            bootstrapArgs));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
index 0c1ee6e..3829aa0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
@@ -6,9 +6,12 @@
 
 import static com.android.tools.r8.cf.code.CfStackInstruction.Opcode.Dup;
 import static com.android.tools.r8.cf.code.CfStackInstruction.Opcode.Swap;
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeDynamicOnRecord;
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.parseInvokeDynamicOnRecord;
 
 import com.android.tools.r8.cf.code.CfConstClass;
 import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
@@ -21,20 +24,14 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
 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.DexValue.DexValueMethodHandle;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring;
@@ -48,6 +45,7 @@
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.desugar.ProgramAdditions;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.RecordInvokeDynamic;
 import com.android.tools.r8.ir.synthetic.CallObjectInitCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordEqualsCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordGetFieldsAsObjectsCfCodeProvider;
@@ -100,14 +98,15 @@
     CfCode cfCode = method.getDefinition().getCode().asCfCode();
     for (CfInstruction instruction : cfCode.getInstructions()) {
       if (instruction.isInvokeDynamic() && needsDesugaring(instruction, method)) {
-        prepareInvokeDynamicOnRecord(instruction.asInvokeDynamic(), method, programAdditions);
+        prepareInvokeDynamicOnRecord(instruction.asInvokeDynamic(), programAdditions, method);
       }
     }
   }
 
   private void prepareInvokeDynamicOnRecord(
-      CfInvokeDynamic invokeDynamic, ProgramMethod context, ProgramAdditions programAdditions) {
-    RecordInvokeDynamic recordInvokeDynamic = parseInvokeDynamicOnRecord(invokeDynamic, context);
+      CfInvokeDynamic invokeDynamic, ProgramAdditions programAdditions, ProgramMethod context) {
+    RecordInvokeDynamic recordInvokeDynamic =
+        parseInvokeDynamicOnRecord(invokeDynamic, appView, context);
     if (recordInvokeDynamic.getMethodName() == factory.toStringMethodName
         || recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) {
       ensureGetFieldsAsObjects(recordInvokeDynamic, programAdditions);
@@ -178,8 +177,8 @@
       return desugarInvokeDynamicOnRecord(
           instruction.asInvokeDynamic(),
           localStackAllocator,
-          context,
           eventConsumer,
+          context,
           methodProcessingContext);
     }
     assert instruction.isInvoke();
@@ -191,71 +190,17 @@
         new CfInvoke(cfInvoke.getOpcode(), newMethod, cfInvoke.isInterface()));
   }
 
-  static class RecordInvokeDynamic {
-
-    private final DexString methodName;
-    private final DexString fieldNames;
-    private final DexField[] fields;
-    private final DexProgramClass recordClass;
-
-    private RecordInvokeDynamic(
-        DexString methodName,
-        DexString fieldNames,
-        DexField[] fields,
-        DexProgramClass recordClass) {
-      this.methodName = methodName;
-      this.fieldNames = fieldNames;
-      this.fields = fields;
-      this.recordClass = recordClass;
-    }
-
-    DexField[] getFields() {
-      return fields;
-    }
-
-    DexProgramClass getRecordClass() {
-      return recordClass;
-    }
-
-    DexString getFieldNames() {
-      return fieldNames;
-    }
-
-    DexString getMethodName() {
-      return methodName;
-    }
-  }
-
-  private RecordInvokeDynamic parseInvokeDynamicOnRecord(
-      CfInvokeDynamic invokeDynamic, ProgramMethod context) {
-    assert needsDesugaring(invokeDynamic, context);
-    DexCallSite callSite = invokeDynamic.getCallSite();
-    DexValueType recordValueType = callSite.bootstrapArgs.get(0).asDexValueType();
-    DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
-    DexString fieldNames = valueString.getValue();
-    DexField[] fields = new DexField[callSite.bootstrapArgs.size() - 2];
-    for (int i = 2; i < callSite.bootstrapArgs.size(); i++) {
-      DexValueMethodHandle handle = callSite.bootstrapArgs.get(i).asDexValueMethodHandle();
-      fields[i - 2] = handle.value.member.asDexField();
-    }
-    DexProgramClass recordClass =
-        appView.definitionFor(recordValueType.getValue()).asProgramClass();
-    return new RecordInvokeDynamic(callSite.methodName, fieldNames, fields, recordClass);
-  }
-
   private List<CfInstruction> desugarInvokeDynamicOnRecord(
       CfInvokeDynamic invokeDynamic,
       LocalStackAllocator localStackAllocator,
-      ProgramMethod context,
       CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
       MethodProcessingContext methodProcessingContext) {
-    RecordInvokeDynamic recordInvokeDynamic = parseInvokeDynamicOnRecord(invokeDynamic, context);
+    RecordInvokeDynamic recordInvokeDynamic =
+        parseInvokeDynamicOnRecord(invokeDynamic, appView, context);
     if (recordInvokeDynamic.getMethodName() == factory.toStringMethodName) {
       return desugarInvokeRecordToString(
-          recordInvokeDynamic,
-          localStackAllocator,
-          eventConsumer,
-          methodProcessingContext);
+          recordInvokeDynamic, localStackAllocator, eventConsumer, methodProcessingContext);
     }
     if (recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) {
       return desugarInvokeRecordHashCode(
@@ -392,7 +337,15 @@
     ArrayList<CfInstruction> instructions = new ArrayList<>();
     instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false));
     instructions.add(new CfConstClass(recordInvokeDynamic.getRecordClass().type, true));
-    instructions.add(new CfConstString(recordInvokeDynamic.getFieldNames()));
+    if (appView.options().testing.enableRecordModeling
+        && appView.enableWholeProgramOptimizations()) {
+      instructions.add(
+          new CfDexItemBasedConstString(
+              recordInvokeDynamic.getRecordClass().type,
+              recordInvokeDynamic.computeRecordFieldNamesComputationInfo()));
+    } else {
+      instructions.add(new CfConstString(recordInvokeDynamic.getFieldNames()));
+    }
     ProgramMethod programMethod =
         synthesizeRecordHelper(
             recordToStringHelperProto,
@@ -426,9 +379,7 @@
             appView,
             builder -> {
               DexEncodedMethod init = synthesizeRecordInitMethod();
-              builder
-                  .setAbstract()
-                  .setDirectMethods(ImmutableList.of(init));
+              builder.setAbstract().setDirectMethods(ImmutableList.of(init));
             },
             eventConsumer::acceptRecordClass);
   }
@@ -484,75 +435,7 @@
   }
 
   private boolean needsDesugaring(CfInvokeDynamic invokeDynamic, ProgramMethod context) {
-    DexCallSite callSite = invokeDynamic.getCallSite();
-    // 1. Validates this is an invoke-static to ObjectMethods#bootstrap.
-    DexMethodHandle bootstrapMethod = callSite.bootstrapMethod;
-    if (!bootstrapMethod.type.isInvokeStatic()) {
-      return false;
-    }
-    if (bootstrapMethod.member != factory.objectMethodsMembers.bootstrap) {
-      return false;
-    }
-    // From there on we assume in the assertions that the invoke to the library method is
-    // well-formed. If the invoke is not well formed assertions will fail but the execution is
-    // correct.
-    if (bootstrapMethod.isInterface) {
-      assert false
-          : "Invoke-dynamic invoking non interface method ObjectMethods#bootstrap as an interface"
-              + " method.";
-      return false;
-    }
-    // 2. Validate the bootstrapArgs include the record type, the instance field names and
-    // the corresponding instance getters.
-    if (callSite.bootstrapArgs.size() < 2) {
-      assert false
-          : "Invoke-dynamic invoking method ObjectMethods#bootstrap with less than 2 parameters.";
-      return false;
-    }
-    DexValueType recordType = callSite.bootstrapArgs.get(0).asDexValueType();
-    if (recordType == null) {
-      assert false : "Invoke-dynamic invoking method ObjectMethods#bootstrap with an invalid type.";
-      return false;
-    }
-    DexClass recordClass = appView.definitionFor(recordType.getValue());
-    if (recordClass == null || recordClass.isNotProgramClass()) {
-      return false;
-    }
-    DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
-    if (valueString == null) {
-      assert false
-          : "Invoke-dynamic invoking method ObjectMethods#bootstrap with invalid field names.";
-      return false;
-    }
-    DexString fieldNames = valueString.getValue();
-    assert fieldNames.toString().isEmpty()
-        || (fieldNames.toString().split(";").length == callSite.bootstrapArgs.size() - 2);
-    assert recordClass.instanceFields().size() == callSite.bootstrapArgs.size() - 2;
-    for (int i = 2; i < callSite.bootstrapArgs.size(); i++) {
-      DexValueMethodHandle handle = callSite.bootstrapArgs.get(i).asDexValueMethodHandle();
-      if (handle == null
-          || !handle.value.type.isInstanceGet()
-          || !handle.value.member.isDexField()) {
-        assert false
-            : "Invoke-dynamic invoking method ObjectMethods#bootstrap with invalid getters.";
-        return false;
-      }
-    }
-    // 3. Create the invoke-record instruction.
-    if (callSite.methodName == factory.toStringMethodName) {
-      assert callSite.methodProto == factory.createProto(factory.stringType, recordClass.getType());
-      return true;
-    }
-    if (callSite.methodName == factory.hashCodeMethodName) {
-      assert callSite.methodProto == factory.createProto(factory.intType, recordClass.getType());
-      return true;
-    }
-    if (callSite.methodName == factory.equalsMethodName) {
-      assert callSite.methodProto
-          == factory.createProto(factory.booleanType, recordClass.getType(), factory.objectType);
-      return true;
-    }
-    return false;
+    return isInvokeDynamicOnRecord(invokeDynamic, appView, context);
   }
 
   @SuppressWarnings("ConstantConditions")
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java
new file mode 100644
index 0000000..dc589e2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java
@@ -0,0 +1,166 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.records;
+
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethodHandle;
+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.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.naming.dexitembasedstring.RecordFieldNamesComputationInfo;
+
+public class RecordRewriterHelper {
+
+  public static boolean isInvokeDynamicOnRecord(
+      CfInvokeDynamic invokeDynamic, AppView<?> appView, ProgramMethod context) {
+    DexItemFactory factory = appView.dexItemFactory();
+    DexCallSite callSite = invokeDynamic.getCallSite();
+    // 1. Validates this is an invoke-static to ObjectMethods#bootstrap.
+    DexMethodHandle bootstrapMethod = callSite.bootstrapMethod;
+    if (!bootstrapMethod.type.isInvokeStatic()) {
+      return false;
+    }
+    if (bootstrapMethod.member != factory.objectMethodsMembers.bootstrap) {
+      return false;
+    }
+    // From there on we assume in the assertions that the invoke to the library method is
+    // well-formed. If the invoke is not well formed assertions will fail but the execution is
+    // correct.
+    if (bootstrapMethod.isInterface) {
+      assert false
+          : "Invoke-dynamic invoking non interface method ObjectMethods#bootstrap as an interface"
+              + " method.";
+      return false;
+    }
+    // 2. Validate the bootstrapArgs include the record type, the instance field names and
+    // the corresponding instance getters.
+    if (callSite.bootstrapArgs.size() < 2) {
+      assert false
+          : "Invoke-dynamic invoking method ObjectMethods#bootstrap with less than 2 parameters.";
+      return false;
+    }
+    DexValueType recordType = callSite.bootstrapArgs.get(0).asDexValueType();
+    if (recordType == null) {
+      assert false : "Invoke-dynamic invoking method ObjectMethods#bootstrap with an invalid type.";
+      return false;
+    }
+    DexClass recordClass = appView.definitionFor(recordType.getValue(), context);
+    if (recordClass == null || recordClass.isNotProgramClass()) {
+      return false;
+    }
+    DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
+    if (valueString == null) {
+      assert false
+          : "Invoke-dynamic invoking method ObjectMethods#bootstrap with invalid field names.";
+      return false;
+    }
+    DexString fieldNames = valueString.getValue();
+    assert fieldNames.toString().isEmpty()
+        || (fieldNames.toString().split(";").length == callSite.bootstrapArgs.size() - 2);
+    assert recordClass.instanceFields().size() == callSite.bootstrapArgs.size() - 2;
+    for (int i = 2; i < callSite.bootstrapArgs.size(); i++) {
+      DexValueMethodHandle handle = callSite.bootstrapArgs.get(i).asDexValueMethodHandle();
+      if (handle == null
+          || !handle.value.type.isInstanceGet()
+          || !handle.value.member.isDexField()) {
+        assert false
+            : "Invoke-dynamic invoking method ObjectMethods#bootstrap with invalid getters.";
+        return false;
+      }
+    }
+    // 3. Check it matches one of the 3 invokeDynamicOnRecord instruction.
+    if (callSite.methodName == factory.toStringMethodName) {
+      assert callSite.methodProto == factory.createProto(factory.stringType, recordClass.getType());
+      return true;
+    }
+    if (callSite.methodName == factory.hashCodeMethodName) {
+      assert callSite.methodProto == factory.createProto(factory.intType, recordClass.getType());
+      return true;
+    }
+    if (callSite.methodName == factory.equalsMethodName) {
+      assert callSite.methodProto
+          == factory.createProto(factory.booleanType, recordClass.getType(), factory.objectType);
+      return true;
+    }
+    return false;
+  }
+
+  public static RecordInvokeDynamic parseInvokeDynamicOnRecord(
+      CfInvokeDynamic invokeDynamic, AppView<?> appView, ProgramMethod context) {
+    assert isInvokeDynamicOnRecord(invokeDynamic, appView, context);
+    DexCallSite callSite = invokeDynamic.getCallSite();
+    DexValueType recordValueType = callSite.bootstrapArgs.get(0).asDexValueType();
+    DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
+    DexString fieldNames = valueString.getValue();
+    DexField[] fields = new DexField[callSite.bootstrapArgs.size() - 2];
+    for (int i = 2; i < callSite.bootstrapArgs.size(); i++) {
+      DexValueMethodHandle handle = callSite.bootstrapArgs.get(i).asDexValueMethodHandle();
+      fields[i - 2] = handle.value.member.asDexField();
+    }
+    DexProgramClass recordClass =
+        appView.definitionFor(recordValueType.getValue()).asProgramClass();
+    return new RecordInvokeDynamic(
+        callSite.methodName, callSite.methodProto, fieldNames, fields, recordClass);
+  }
+
+  static class RecordInvokeDynamic {
+
+    private final DexString methodName;
+    private final DexProto methodProto;
+    private final DexString fieldNames;
+    private final DexField[] fields;
+    private final DexProgramClass recordClass;
+
+    private RecordInvokeDynamic(
+        DexString methodName,
+        DexProto methodProto,
+        DexString fieldNames,
+        DexField[] fields,
+        DexProgramClass recordClass) {
+      this.methodName = methodName;
+      this.methodProto = methodProto;
+      this.fieldNames = fieldNames;
+      this.fields = fields;
+      this.recordClass = recordClass;
+    }
+
+    RecordInvokeDynamic withFieldNamesAndFields(DexString fieldNames, DexField[] fields) {
+      return new RecordInvokeDynamic(methodName, methodProto, fieldNames, fields, recordClass);
+    }
+
+    DexField[] getFields() {
+      return fields;
+    }
+
+    DexProgramClass getRecordClass() {
+      return recordClass;
+    }
+
+    DexString getFieldNames() {
+      return fieldNames;
+    }
+
+    DexString getMethodName() {
+      return methodName;
+    }
+
+    DexProto getMethodProto() {
+      return methodProto;
+    }
+
+    RecordFieldNamesComputationInfo computeRecordFieldNamesComputationInfo() {
+      return RecordFieldNamesComputationInfo.forFieldNamesAndFields(getFieldNames(), getFields());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index abf7239..ecf8f25 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.optimize;
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 import static com.google.common.base.Predicates.not;
 
 import com.android.tools.r8.androidapi.AvailableApiExceptions;
@@ -47,6 +48,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.LensCodeRewriter;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
@@ -78,10 +80,12 @@
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 public class Inliner {
 
   protected final AppView<AppInfoWithLiveness> appView;
+  private final IRConverter converter;
   private final Set<DexMethod> extraNeverInlineMethods;
   private final LensCodeRewriter lensCodeRewriter;
   final MainDexInfo mainDexInfo;
@@ -96,13 +100,18 @@
   private final Map<DexEncodedMethod, ProgramMethod> doubleInlineeCandidates =
       new IdentityHashMap<>();
 
+  private final Map<DexProgramClass, ProgramMethodSet> singleCallerInlinedMethods =
+      new ConcurrentHashMap<>();
+
   private final AvailableApiExceptions availableApiExceptions;
 
   public Inliner(
       AppView<AppInfoWithLiveness> appView,
+      IRConverter converter,
       LensCodeRewriter lensCodeRewriter) {
     Kotlin.Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
     this.appView = appView;
+    this.converter = converter;
     this.extraNeverInlineMethods =
         appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations
             ? ImmutableSet.of()
@@ -1130,7 +1139,15 @@
               appView, code, inlinee.code, blockIterator, blocksToRemove, downcastClass);
 
           if (inlinee.reason == Reason.SINGLE_CALLER) {
+            assert converter.isInWave();
             feedback.markInlinedIntoSingleCallSite(singleTargetMethod);
+            if (singleCallerInlinedMethods.isEmpty()) {
+              converter.addWaveDoneAction(this::onWaveDone);
+            }
+            singleCallerInlinedMethods
+                .computeIfAbsent(
+                    singleTarget.getHolder(), ignoreKey(ProgramMethodSet::createConcurrent))
+                .add(singleTarget);
           }
 
           classInitializationAnalysis.notifyCodeHasChanged();
@@ -1304,11 +1321,41 @@
     singleInlineCallers.remove(method.getReference(), appView.graphLens());
   }
 
-  public static boolean verifyNoMethodsInlinedDueToSingleCallSite(AppView<?> appView) {
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      for (DexEncodedMethod method : clazz.methods()) {
-        assert !method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite();
-      }
+  private void onWaveDone() {
+    singleCallerInlinedMethods.forEach(
+        (clazz, singleCallerInlinedMethodsForClass) -> {
+          // Convert and remove virtual single caller inlined methods to abstract or throw null.
+          singleCallerInlinedMethodsForClass.removeIf(
+              singleCallerInlinedMethod -> {
+                if (singleCallerInlinedMethod.getDefinition().belongsToVirtualPool() || true) {
+                  singleCallerInlinedMethod.convertToAbstractOrThrowNullMethod(appView);
+                  return true;
+                }
+                return false;
+              });
+
+          // Remove direct single caller inlined methods from the application.
+          if (!singleCallerInlinedMethodsForClass.isEmpty()) {
+            clazz
+                .getMethodCollection()
+                .removeMethods(
+                    singleCallerInlinedMethodsForClass.toDefinitionSet(
+                        SetUtils::newIdentityHashSet));
+            singleCallerInlinedMethodsForClass.forEach(converter::pruneMethod);
+          }
+        });
+    singleCallerInlinedMethods.clear();
+  }
+
+  public static boolean verifyAllSingleCallerMethodsHaveBeenPruned(
+      AppView<AppInfoWithLiveness> appView) {
+    for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
+      clazz.forEachProgramMethodMatching(
+          method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite(),
+          method -> {
+            assert !method.getDefinition().hasCode()
+                || !method.canBeConvertedToAbstractMethod(appView);
+          });
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 26430a4..854a4e0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -638,9 +638,21 @@
     postMethodProcessorBuilder
         .getMethodsToReprocessBuilder()
         .rewrittenWithLens(appView)
-        .merge(dependencies)
-        .merge(methodsDependingOnLibraryModelisation)
-        .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods());
+        .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods())
+        .merge(
+            dependencies
+                .rewrittenWithLens(appView)
+                .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods())
+                .removeIf(
+                    appView,
+                    method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()))
+        .merge(
+            methodsDependingOnLibraryModelisation
+                .rewrittenWithLens(appView)
+                .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods())
+                .removeIf(
+                    appView,
+                    method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()));
     methodsDependingOnLibraryModelisation.clear();
 
     updateOptimizationInfos(executorService, feedback, treeFixerResult);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index a9c52e4..4959de0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -179,7 +179,7 @@
     DexEncodedMethod method = context.getDefinition();
     List<BasicBlock> normalExits = code.computeNormalExitBlocks();
     if (normalExits.isEmpty()) {
-      feedback.methodNeverReturnsNormally(method);
+      feedback.methodNeverReturnsNormally(context);
       return;
     }
     Return firstExit = normalExits.get(0).exit().asReturn();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 9f1ea2d..06a25cf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.BitSet;
+import java.util.Collections;
 import java.util.Set;
 
 public class MutableMethodOptimizationInfo extends MethodOptimizationInfo
@@ -251,6 +252,10 @@
     setFlag(CLASS_INITIALIZER_MAY_BE_POSTPONED_FLAG);
   }
 
+  void unsetClassInitializerMayBePostponed() {
+    clearFlag(CLASS_INITIALIZER_MAY_BE_POSTPONED_FLAG);
+  }
+
   @Override
   public ClassInlinerMethodConstraint getClassInlinerMethodConstraint() {
     return classInlinerConstraint;
@@ -341,6 +346,10 @@
     this.nonNullParamOrThrow = facts;
   }
 
+  void unsetNonNullParamOrThrow() {
+    this.nonNullParamOrThrow = null;
+  }
+
   @Override
   public BitSet getNonNullParamOnNormalExits() {
     return nonNullParamOnNormalExits;
@@ -356,19 +365,23 @@
     this.nonNullParamOnNormalExits = facts;
   }
 
+  void unsetNonNullParamOnNormalExits() {
+    nonNullParamOnNormalExits = null;
+  }
+
   @Override
   public boolean hasBeenInlinedIntoSingleCallSite() {
     return isFlagSet(HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG);
   }
 
-  void unsetInlinedIntoSingleCallSite() {
-    clearFlag(HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG);
-  }
-
   void markInlinedIntoSingleCallSite() {
     setFlag(HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG);
   }
 
+  void unsetInlinedIntoSingleCallSite() {
+    clearFlag(HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG);
+  }
+
   @Override
   public boolean isReachabilitySensitive() {
     return isFlagSet(REACHABILITY_SENSITIVE_FLAG);
@@ -449,6 +462,10 @@
         unusedArguments != null && !unusedArguments.isEmpty() ? unusedArguments : null;
   }
 
+  void unsetUnusedArguments() {
+    unusedArguments = null;
+  }
+
   @Override
   public boolean isInitializerEnablingJavaVmAssertions() {
     return isFlagSet(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG);
@@ -494,10 +511,18 @@
     setFlag(REACHABILITY_SENSITIVE_FLAG, reachabilitySensitive);
   }
 
+  void unsetReachabilitySensitive() {
+    clearFlag(REACHABILITY_SENSITIVE_FLAG);
+  }
+
   void setSimpleInliningConstraint(SimpleInliningConstraint constraint) {
     this.simpleInliningConstraint = constraint;
   }
 
+  void unsetSimpleInliningConstraint() {
+    simpleInliningConstraint = NeverSimpleInliningConstraint.getInstance();
+  }
+
   public MutableMethodOptimizationInfo fixupSimpleInliningConstraint(
       AppView<AppInfoWithLiveness> appView, MethodOptimizationInfoFixer fixer) {
     simpleInliningConstraint =
@@ -511,20 +536,36 @@
     this.instanceInitializerInfoCollection = instanceInitializerInfoCollection;
   }
 
+  void unsetInstanceInitializerInfoCollection() {
+    instanceInitializerInfoCollection = InstanceInitializerInfoCollection.empty();
+  }
+
   void setInitializerEnablingJavaAssertions() {
     setFlag(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG);
   }
 
+  void unsetInitializerEnablingJavaVmAssertions() {
+    clearFlag(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG);
+  }
+
   void markInitializesClassesOnNormalExit(Set<DexType> initializedClassesOnNormalExit) {
     this.initializedClassesOnNormalExit = initializedClassesOnNormalExit;
   }
 
+  void unsetInitializedClassesOnNormalExit() {
+    initializedClassesOnNormalExit = Collections.emptySet();
+  }
+
   void markReturnsArgument(int returnedArgumentIndex) {
     assert returnedArgumentIndex >= 0;
     assert returnedArgument == -1 || returnedArgument == returnedArgumentIndex;
     returnedArgument = returnedArgumentIndex;
   }
 
+  void unsetReturnedArgument() {
+    returnedArgument = -1;
+  }
+
   public MutableMethodOptimizationInfo fixupReturnedArgumentIndex(
       MethodOptimizationInfoFixer fixer) {
     returnedArgument = fixer.fixupReturnedArgumentIndex(returnedArgument);
@@ -535,14 +576,26 @@
     clearFlag(MAY_HAVE_SIDE_EFFECT_FLAG);
   }
 
+  void unsetMayNotHaveSideEffects() {
+    setFlag(MAY_HAVE_SIDE_EFFECT_FLAG);
+  }
+
   void markReturnValueOnlyDependsOnArguments() {
     setFlag(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG);
   }
 
+  void unsetReturnValueOnlyDependsOnArguments() {
+    clearFlag(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG);
+  }
+
   void markNeverReturnsNormally() {
     setFlag(NEVER_RETURNS_NORMALLY_FLAG);
   }
 
+  void unsetNeverReturnsNormally() {
+    clearFlag(NEVER_RETURNS_NORMALLY_FLAG);
+  }
+
   void markReturnsAbstractValue(AbstractValue value) {
     assert !abstractReturnValue.isSingleValue() || abstractReturnValue.equals(value)
         : "return single value changed from " + abstractReturnValue + " to " + value;
@@ -571,6 +624,10 @@
     }
   }
 
+  void unsetDynamicUpperBoundReturnType() {
+    returnsObjectWithUpperBoundType = null;
+  }
+
   void markReturnsObjectWithLowerBoundType(ClassTypeElement type) {
     assert type != null;
     // Currently, we only have a lower bound type when we have _exact_ runtime type information.
@@ -581,6 +638,10 @@
     returnsObjectWithLowerBoundType = type;
   }
 
+  void unsetDynamicLowerBoundReturnType() {
+    returnsObjectWithLowerBoundType = null;
+  }
+
   // TODO(b/140214568): Should be package-private.
   public void markForceInline() {
     // For concurrent scenarios we should allow the flag to be already set
@@ -599,10 +660,18 @@
     setFlag(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG, mark);
   }
 
+  void unsetCheckNullReceiverBeforeAnySideEffect() {
+    clearFlag(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG);
+  }
+
   void markTriggerClassInitBeforeAnySideEffect(boolean mark) {
     setFlag(TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG, mark);
   }
 
+  void unsetTriggerClassInitBeforeAnySideEffect() {
+    clearFlag(TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG);
+  }
+
   // TODO(b/140214568): Should be package-private.
   public void markAsPropagated() {
     setFlag(RETURN_VALUE_HAS_BEEN_PROPAGATED_FLAG);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
index b38f436..6e7bf18 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
@@ -36,10 +36,18 @@
     }
   }
 
+  public static OptimizationFeedbackIgnore getIgnoreFeedback() {
+    return OptimizationFeedbackIgnore.getInstance();
+  }
+
   public static OptimizationFeedbackSimple getSimple() {
     return OptimizationFeedbackSimple.getInstance();
   }
 
+  public static OptimizationFeedbackSimple getSimpleFeedback() {
+    return getSimple();
+  }
+
   public void fixupOptimizationInfos(
       AppView<?> appView, ExecutorService executorService, OptimizationInfoFixer fixer)
       throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 1b4acdd..23bedd0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -199,11 +199,6 @@
   }
 
   @Override
-  public synchronized void unsetAbstractReturnValue(ProgramMethod method) {
-    getMethodOptimizationInfoForUpdating(method).unsetAbstractReturnValue();
-  }
-
-  @Override
   public synchronized void methodReturnsObjectWithUpperBoundType(
       DexEncodedMethod method, AppView<?> appView, TypeElement type) {
     getMethodOptimizationInfoForUpdating(method).markReturnsObjectWithUpperBoundType(appView, type);
@@ -216,7 +211,7 @@
   }
 
   @Override
-  public synchronized void methodNeverReturnsNormally(DexEncodedMethod method) {
+  public synchronized void methodNeverReturnsNormally(ProgramMethod method) {
     getMethodOptimizationInfoForUpdating(method).markNeverReturnsNormally();
   }
 
@@ -272,11 +267,6 @@
   }
 
   @Override
-  public void unsetEnumUnboxerMethodClassification(ProgramMethod method) {
-    getMethodOptimizationInfoForUpdating(method).unsetEnumUnboxerMethodClassification();
-  }
-
-  @Override
   public synchronized void setInstanceInitializerInfoCollection(
       DexEncodedMethod method,
       InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
@@ -314,4 +304,121 @@
   public synchronized void setUnusedArguments(ProgramMethod method, BitSet unusedArguments) {
     getMethodOptimizationInfoForUpdating(method).setUnusedArguments(unusedArguments);
   }
+
+  // Unset methods.
+
+  @Override
+  public synchronized void unsetAbstractReturnValue(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetAbstractReturnValue();
+  }
+
+  @Override
+  public synchronized void unsetBridgeInfo(DexEncodedMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetBridgeInfo();
+  }
+
+  @Override
+  public synchronized void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetCheckNullReceiverBeforeAnySideEffect();
+  }
+
+  @Override
+  public synchronized void unsetClassInitializerMayBePostponed(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetClassInitializerMayBePostponed();
+  }
+
+  @Override
+  public synchronized void unsetClassInlinerMethodConstraint(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetClassInlinerMethodConstraint();
+  }
+
+  @Override
+  public synchronized void unsetDynamicLowerBoundReturnType(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetDynamicLowerBoundReturnType();
+  }
+
+  @Override
+  public synchronized void unsetDynamicUpperBoundReturnType(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetDynamicUpperBoundReturnType();
+  }
+
+  @Override
+  public synchronized void unsetEnumUnboxerMethodClassification(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetEnumUnboxerMethodClassification();
+  }
+
+  @Override
+  public synchronized void unsetForceInline(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetForceInline();
+  }
+
+  @Override
+  public synchronized void unsetInitializedClassesOnNormalExit(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetInitializedClassesOnNormalExit();
+  }
+
+  @Override
+  public synchronized void unsetInitializerEnablingJavaVmAssertions(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetInitializerEnablingJavaVmAssertions();
+  }
+
+  @Override
+  public synchronized void unsetInlinedIntoSingleCallSite(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetInlinedIntoSingleCallSite();
+  }
+
+  @Override
+  public synchronized void unsetInstanceInitializerInfoCollection(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetInstanceInitializerInfoCollection();
+  }
+
+  @Override
+  public synchronized void unsetMayNotHaveSideEffects(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetMayNotHaveSideEffects();
+  }
+
+  @Override
+  public synchronized void unsetNeverReturnsNormally(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetNeverReturnsNormally();
+  }
+
+  @Override
+  public synchronized void unsetNonNullParamOnNormalExits(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetNonNullParamOnNormalExits();
+  }
+
+  @Override
+  public synchronized void unsetNonNullParamOrThrow(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetNonNullParamOrThrow();
+  }
+
+  @Override
+  public synchronized void unsetReachabilitySensitive(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetReachabilitySensitive();
+  }
+
+  @Override
+  public synchronized void unsetReturnedArgument(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetReturnedArgument();
+  }
+
+  @Override
+  public synchronized void unsetReturnValueOnlyDependsOnArguments(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetReturnValueOnlyDependsOnArguments();
+  }
+
+  @Override
+  public synchronized void unsetSimpleInliningConstraint(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetSimpleInliningConstraint();
+  }
+
+  @Override
+  public synchronized void unsetTriggerClassInitBeforeAnySideEffect(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetTriggerClassInitBeforeAnySideEffect();
+  }
+
+  @Override
+  public synchronized void unsetUnusedArguments(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetUnusedArguments();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 8b13771..3bcd3fc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -79,9 +79,6 @@
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {}
 
   @Override
-  public void unsetAbstractReturnValue(ProgramMethod method) {}
-
-  @Override
   public void methodReturnsObjectWithUpperBoundType(
       DexEncodedMethod method, AppView<?> appView, TypeElement type) {}
 
@@ -96,7 +93,7 @@
   public void methodReturnValueOnlyDependsOnArguments(DexEncodedMethod method) {}
 
   @Override
-  public void methodNeverReturnsNormally(DexEncodedMethod method) {}
+  public void methodNeverReturnsNormally(ProgramMethod method) {}
 
   @Override
   public void markAsPropagated(DexEncodedMethod method) {}
@@ -122,9 +119,6 @@
       ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {}
 
   @Override
-  public void unsetEnumUnboxerMethodClassification(ProgramMethod method) {}
-
-  @Override
   public void setInstanceInitializerInfoCollection(
       DexEncodedMethod method,
       InstanceInitializerInfoCollection instanceInitializerInfoCollection) {}
@@ -147,4 +141,75 @@
 
   @Override
   public void setUnusedArguments(ProgramMethod method, BitSet unusedArguments) {}
+
+  // Unset methods.
+
+  @Override
+  public void unsetAbstractReturnValue(ProgramMethod method) {}
+
+  @Override
+  public void unsetBridgeInfo(DexEncodedMethod method) {}
+
+  @Override
+  public void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method) {}
+
+  @Override
+  public void unsetClassInitializerMayBePostponed(ProgramMethod method) {}
+
+  @Override
+  public void unsetClassInlinerMethodConstraint(ProgramMethod method) {}
+
+  @Override
+  public void unsetDynamicLowerBoundReturnType(ProgramMethod method) {}
+
+  @Override
+  public void unsetDynamicUpperBoundReturnType(ProgramMethod method) {}
+
+  @Override
+  public void unsetEnumUnboxerMethodClassification(ProgramMethod method) {}
+
+  @Override
+  public void unsetForceInline(ProgramMethod method) {}
+
+  @Override
+  public void unsetInitializedClassesOnNormalExit(ProgramMethod method) {}
+
+  @Override
+  public void unsetInitializerEnablingJavaVmAssertions(ProgramMethod method) {}
+
+  @Override
+  public void unsetInlinedIntoSingleCallSite(ProgramMethod method) {}
+
+  @Override
+  public void unsetInstanceInitializerInfoCollection(ProgramMethod method) {}
+
+  @Override
+  public void unsetMayNotHaveSideEffects(ProgramMethod method) {}
+
+  @Override
+  public void unsetNeverReturnsNormally(ProgramMethod method) {}
+
+  @Override
+  public void unsetNonNullParamOnNormalExits(ProgramMethod method) {}
+
+  @Override
+  public void unsetNonNullParamOrThrow(ProgramMethod method) {}
+
+  @Override
+  public void unsetReachabilitySensitive(ProgramMethod method) {}
+
+  @Override
+  public void unsetReturnedArgument(ProgramMethod method) {}
+
+  @Override
+  public void unsetReturnValueOnlyDependsOnArguments(ProgramMethod method) {}
+
+  @Override
+  public void unsetSimpleInliningConstraint(ProgramMethod method) {}
+
+  @Override
+  public void unsetTriggerClassInitBeforeAnySideEffect(ProgramMethod method) {}
+
+  @Override
+  public void unsetUnusedArguments(ProgramMethod method) {}
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 86ea927..77615e2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
 import java.util.Set;
+import java.util.function.Consumer;
 
 public class OptimizationFeedbackSimple extends OptimizationFeedback {
 
@@ -80,7 +81,7 @@
   }
 
   @Override
-  public synchronized void markInlinedIntoSingleCallSite(DexEncodedMethod method) {
+  public void markInlinedIntoSingleCallSite(DexEncodedMethod method) {
     method.getMutableOptimizationInfo().markInlinedIntoSingleCallSite();
   }
 
@@ -107,13 +108,6 @@
   }
 
   @Override
-  public void unsetAbstractReturnValue(ProgramMethod method) {
-    if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
-      method.getDefinition().getMutableOptimizationInfo().unsetAbstractReturnValue();
-    }
-  }
-
-  @Override
   public void methodReturnsObjectWithUpperBoundType(
       DexEncodedMethod method, AppView<?> appView, TypeElement type) {
     method.getMutableOptimizationInfo().markReturnsObjectWithUpperBoundType(appView, type);
@@ -136,8 +130,8 @@
   }
 
   @Override
-  public void methodNeverReturnsNormally(DexEncodedMethod method) {
-    // Ignored.
+  public void methodNeverReturnsNormally(ProgramMethod method) {
+    method.getDefinition().getMutableOptimizationInfo().markNeverReturnsNormally();
   }
 
   @Override
@@ -152,7 +146,7 @@
 
   @Override
   public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
-    // Ignored.
+    method.getMutableOptimizationInfo().markCheckNullReceiverBeforeAnySideEffect(mark);
   }
 
   @Override
@@ -165,27 +159,12 @@
     method.getMutableOptimizationInfo().setBridgeInfo(bridgeInfo);
   }
 
-  public void unsetBridgeInfo(DexEncodedMethod method) {
-    if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
-      method.getOptimizationInfo().asMutableMethodOptimizationInfo().unsetBridgeInfo();
-    }
-  }
-
   @Override
   public void setClassInlinerMethodConstraint(
       ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint) {
     // Ignored.
   }
 
-  public void unsetClassInlinerMethodConstraint(ProgramMethod method) {
-    if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
-      method
-          .getOptimizationInfo()
-          .asMutableMethodOptimizationInfo()
-          .unsetClassInlinerMethodConstraint();
-    }
-  }
-
   @Override
   public void setEnumUnboxerMethodClassification(
       ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
@@ -196,16 +175,6 @@
   }
 
   @Override
-  public void unsetEnumUnboxerMethodClassification(ProgramMethod method) {
-    if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
-      method
-          .getOptimizationInfo()
-          .asMutableMethodOptimizationInfo()
-          .unsetEnumUnboxerMethodClassification();
-    }
-  }
-
-  @Override
   public void setInstanceInitializerInfoCollection(
       DexEncodedMethod method,
       InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
@@ -245,12 +214,154 @@
     method.getDefinition().getMutableOptimizationInfo().setUnusedArguments(unusedArguments);
   }
 
+  // Unset methods.
+
+  @Override
+  public void unsetAbstractReturnValue(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetAbstractReturnValue);
+  }
+
+  @Override
+  public void unsetBridgeInfo(DexEncodedMethod method) {
+    withMutableMethodOptimizationInfo(method, MutableMethodOptimizationInfo::unsetBridgeInfo);
+  }
+
+  @Override
+  public void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetCheckNullReceiverBeforeAnySideEffect);
+  }
+
+  @Override
+  public void unsetClassInitializerMayBePostponed(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetClassInitializerMayBePostponed);
+  }
+
+  @Override
+  public void unsetClassInlinerMethodConstraint(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetClassInlinerMethodConstraint);
+  }
+
+  @Override
+  public void unsetDynamicLowerBoundReturnType(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetDynamicLowerBoundReturnType);
+  }
+
+  @Override
+  public void unsetDynamicUpperBoundReturnType(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetDynamicUpperBoundReturnType);
+  }
+
+  @Override
+  public void unsetEnumUnboxerMethodClassification(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetEnumUnboxerMethodClassification);
+  }
+
+  @Override
+  public void unsetForceInline(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(method, MutableMethodOptimizationInfo::unsetForceInline);
+  }
+
+  @Override
+  public void unsetInitializedClassesOnNormalExit(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetInitializedClassesOnNormalExit);
+  }
+
+  @Override
+  public void unsetInitializerEnablingJavaVmAssertions(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetInitializerEnablingJavaVmAssertions);
+  }
+
+  @Override
   public void unsetInlinedIntoSingleCallSite(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetInlinedIntoSingleCallSite);
+  }
+
+  @Override
+  public void unsetInstanceInitializerInfoCollection(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetInstanceInitializerInfoCollection);
+  }
+
+  @Override
+  public void unsetMayNotHaveSideEffects(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetMayNotHaveSideEffects);
+  }
+
+  @Override
+  public void unsetNeverReturnsNormally(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetNeverReturnsNormally);
+  }
+
+  @Override
+  public void unsetNonNullParamOnNormalExits(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetNonNullParamOnNormalExits);
+  }
+
+  @Override
+  public void unsetNonNullParamOrThrow(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetNonNullParamOrThrow);
+  }
+
+  @Override
+  public void unsetReachabilitySensitive(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetReachabilitySensitive);
+  }
+
+  @Override
+  public void unsetReturnedArgument(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(method, MutableMethodOptimizationInfo::unsetReturnedArgument);
+  }
+
+  @Override
+  public void unsetReturnValueOnlyDependsOnArguments(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetReturnValueOnlyDependsOnArguments);
+  }
+
+  @Override
+  public void unsetSimpleInliningConstraint(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetSimpleInliningConstraint);
+  }
+
+  @Override
+  public void unsetTriggerClassInitBeforeAnySideEffect(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetTriggerClassInitBeforeAnySideEffect);
+  }
+
+  @Override
+  public void unsetUnusedArguments(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(method, MutableMethodOptimizationInfo::unsetUnusedArguments);
+  }
+
+  private void withMutableMethodOptimizationInfo(
+      ProgramMethod method, Consumer<MutableMethodOptimizationInfo> consumer) {
     if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
-      method
-          .getOptimizationInfo()
-          .asMutableMethodOptimizationInfo()
-          .unsetInlinedIntoSingleCallSite();
+      consumer.accept(method.getOptimizationInfo().asMutableMethodOptimizationInfo());
+    }
+  }
+
+  @Deprecated
+  private void withMutableMethodOptimizationInfo(
+      DexEncodedMethod method, Consumer<MutableMethodOptimizationInfo> consumer) {
+    if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
+      consumer.accept(method.getOptimizationInfo().asMutableMethodOptimizationInfo());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java
index 16e4dab..78dc248 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java
@@ -97,12 +97,15 @@
               DexProgramClass.asProgramClassOrNull(
                   appView.definitionFor(rewrittenReference.getHolderType()));
           ProgramMethod method = rewrittenReference.lookupOnProgramClass(holder);
-          if (method != null) {
-            for (Outline outline : outlinesForMethod) {
-              methodsPerOutline.computeIfAbsent(outline, ignoreKey(ArrayList::new)).add(method);
-            }
-          } else {
+          if (method == null) {
             assert false;
+            return;
+          }
+          if (method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()) {
+            return;
+          }
+          for (Outline outline : outlinesForMethod) {
+            methodsPerOutline.computeIfAbsent(outline, ignoreKey(ArrayList::new)).add(method);
           }
         });
     return methodsPerOutline;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 2fbaa44..c73132f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -234,7 +234,13 @@
         LongLivedProgramMethodSetBuilder<?> referencedFromBuilder =
             classStaticizer.referencedFrom.remove(info);
         assert referencedFromBuilder != null;
-        referencedFrom = referencedFromBuilder.build(appView);
+        referencedFrom =
+            referencedFromBuilder
+                .rewrittenWithLens(appView)
+                .removeIf(
+                    appView,
+                    method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite())
+                .build(appView);
         materializedReferencedFromCollections.put(info, referencedFrom);
       } else {
         referencedFrom = ProgramMethodSet.empty();
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index c3677ba..7b8da09 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.SourceFileEnvironment;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Marker;
@@ -40,6 +41,7 @@
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.ProguardMapSupplier;
+import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.AsmUtils;
@@ -131,16 +133,24 @@
   }
 
   private void writeApplication(ClassFileConsumer consumer) {
-    if (proguardMapSupplier != null && options.proguardMapConsumer != null) {
-      marker.setPgMapId(proguardMapSupplier.writeProguardMap().getId());
+    ProguardMapId proguardMapId =
+        (proguardMapSupplier != null && options.proguardMapConsumer != null)
+            ? proguardMapSupplier.writeProguardMap()
+            : null;
+    if (proguardMapId != null) {
+      marker.setPgMapId(proguardMapId.getId());
     }
     Optional<String> markerString =
         includeMarker(marker) ? Optional.of(marker.toString()) : Optional.empty();
+    SourceFileEnvironment sourceFileEnvironment = null;
+    if (options.sourceFileProvider != null) {
+      sourceFileEnvironment = ApplicationWriter.createSourceFileEnvironment(proguardMapId);
+    }
     LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView);
     for (DexProgramClass clazz : application.classes()) {
       assert SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
       try {
-        writeClass(clazz, consumer, rewriter, markerString);
+        writeClass(clazz, consumer, rewriter, markerString, sourceFileEnvironment);
       } catch (ClassTooLargeException e) {
         throw appView
             .options()
@@ -172,14 +182,21 @@
       DexProgramClass clazz,
       ClassFileConsumer consumer,
       LensCodeRewriterUtils rewriter,
-      Optional<String> markerString) {
+      Optional<String> markerString,
+      SourceFileEnvironment sourceFileEnvironment) {
     ClassWriter writer = new ClassWriter(0);
     if (markerString.isPresent()) {
       int markerStringPoolIndex = writer.newConst(markerString.get());
       assert markerStringPoolIndex == MARKER_STRING_CONSTANT_POOL_INDEX;
     }
+    String sourceFile;
+    if (options.sourceFileProvider == null) {
+      sourceFile = clazz.sourceFile != null ? clazz.sourceFile.toString() : null;
+    } else {
+      sourceFile = options.sourceFileProvider.get(sourceFileEnvironment);
+    }
     String sourceDebug = getSourceDebugExtension(clazz.annotations());
-    writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, sourceDebug);
+    writer.visitSource(sourceFile, sourceDebug);
     CfVersion version = getClassFileVersion(clazz);
     if (version.isGreaterThanOrEqualTo(CfVersion.V1_8)) {
       // JDK8 and after ignore ACC_SUPER so unset it.
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index a775a72..a22a766 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -19,10 +19,12 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexItemBasedValueString;
 import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.records.RecordCfToCfRewriter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardClassFilter;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -35,11 +37,13 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final ProguardClassFilter adaptClassStrings;
+  private final RecordCfToCfRewriter recordCfToCfRewriter;
   private final NamingLens lens;
 
   IdentifierMinifier(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
     this.appView = appView;
     this.adaptClassStrings = appView.options().getProguardConfiguration().getAdaptClassStrings();
+    this.recordCfToCfRewriter = RecordCfToCfRewriter.create(appView);
     this.lens = lens;
   }
 
@@ -117,12 +121,10 @@
           for (DexEncodedField field : clazz.staticFields()) {
             replaceDexItemBasedConstStringInStaticField(field);
           }
-          clazz
-              .methods(DexEncodedMethod::hasCode)
-              .forEach(this::replaceDexItemBasedConstStringInMethod);
+          clazz.forEachProgramMethodMatching(
+              DexEncodedMethod::hasCode, this::replaceDexItemBasedConstStringInMethod);
         },
-        executorService
-    );
+        executorService);
   }
 
   private void replaceDexItemBasedConstStringInStaticField(DexEncodedField encodedField) {
@@ -137,8 +139,8 @@
     }
   }
 
-  private void replaceDexItemBasedConstStringInMethod(DexEncodedMethod encodedMethod) {
-    Code code = encodedMethod.getCode();
+  private void replaceDexItemBasedConstStringInMethod(ProgramMethod programMethod) {
+    Code code = programMethod.getDefinition().getCode();
     assert code != null;
     if (code.isDexCode()) {
       Instruction[] instructions = code.asDexCode().instructions;
@@ -157,23 +159,23 @@
     } else {
       assert code.isCfCode();
       List<CfInstruction> instructions = code.asCfCode().getInstructions();
-      List<CfInstruction> newInstructions = null;
-      for (int i = 0; i < instructions.size(); ++i) {
-        CfInstruction instruction = instructions.get(i);
-        if (instruction.isDexItemBasedConstString()) {
-          CfDexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
-          DexString replacement =
-              cnst.getNameComputationInfo()
-                  .computeNameFor(cnst.getItem(), appView, appView.graphLens(), lens);
-          if (newInstructions == null) {
-            newInstructions = new ArrayList<>(instructions);
-          }
-          newInstructions.set(i, new CfConstString(replacement));
-        }
-      }
-      if (newInstructions != null) {
-        code.asCfCode().setInstructions(newInstructions);
-      }
+      List<CfInstruction> newInstructions =
+          ListUtils.mapOrElse(
+              instructions,
+              (int i, CfInstruction instruction) -> {
+                if (instruction.isDexItemBasedConstString()) {
+                  CfDexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
+                  return new CfConstString(
+                      cnst.getNameComputationInfo()
+                          .computeNameFor(cnst.getItem(), appView, appView.graphLens(), lens));
+                } else if (recordCfToCfRewriter != null && instruction.isInvokeDynamic()) {
+                  return recordCfToCfRewriter.rewriteRecordInvokeDynamic(
+                      instruction.asInvokeDynamic(), programMethod, lens);
+                }
+                return instruction;
+              },
+              instructions);
+      code.asCfCode().setInstructions(newInstructions);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
index 8b650cd..696bce1 100644
--- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -3,74 +3,73 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.SourceFileEnvironment;
+import com.android.tools.r8.SourceFileProvider;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.shaking.ProguardConfiguration;
 
-/**
- * Visit program {@link DexClass}es and replace their sourceFile with the given string.
- *
- * If -keepattribute SourceFile is not set, we rather remove that attribute.
- */
+/** Computes the source file provider based on the proguard configuration if none is set. */
 public class SourceFileRewriter {
 
   private final AppView<?> appView;
-  private final DexApplication application;
 
-  public SourceFileRewriter(AppView<?> appView, DexApplication application) {
+  public SourceFileRewriter(AppView<?> appView) {
     this.appView = appView;
-    this.application = application;
   }
 
   public void run() {
-    boolean isMinifying = appView.options().isMinifying();
-    boolean isCompatR8 = appView.options().forceProguardCompatibility;
-    if (!isMinifying && isCompatR8) {
-      // Compatibility mode will only apply -renamesourcefileattribute when minifying names.
+    if (appView.options().sourceFileProvider != null) {
       return;
     }
-    ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration();
-    boolean hasKeptNonRenamedSourceFile =
-        proguardConfiguration.getRenameSourceFileAttribute() == null
-            && proguardConfiguration.getKeepAttributes().sourceFile;
-    // If source file is kept without a rewrite, it is only modified in a minifing full-mode.
-    if (hasKeptNonRenamedSourceFile && (!isMinifying || isCompatR8)) {
-      return;
-    }
-    assert !isMinifying || appView.appInfo().hasLiveness();
-    DexString defaultRenaming = getSourceFileRenaming(proguardConfiguration);
-    for (DexClass clazz : application.classes()) {
-      clazz.sourceFile = defaultRenaming;
-    }
+    appView.options().sourceFileProvider = computeSourceFileProvider();
   }
 
-  private DexString getSourceFileRenaming(ProguardConfiguration proguardConfiguration) {
-    // If we should not be keeping the source file, null it out.
-    if (!proguardConfiguration.getKeepAttributes().sourceFile) {
-      // For class files, we always remove the attribute
-      if (appView.options().isGeneratingClassFiles()) {
-        return null;
-      }
-      assert appView.options().isGeneratingDex();
-      // When generating DEX we only remove the attribute for full-mode to ensure that we get
-      // line-numbers printed in stack traces.
-      if (!appView.options().forceProguardCompatibility) {
-        return null;
+  public SourceFileProvider computeSourceFileProvider() {
+    if (!appView.options().getProguardConfiguration().getKeepAttributes().sourceFile) {
+      return rewriteToDefaultSourceFile();
+    }
+    if (appView.options().forceProguardCompatibility) {
+      return computeCompatProvider();
+    }
+    return computeNonCompatProvider();
+  }
+
+  private SourceFileProvider computeCompatProvider() {
+    // Compatibility mode will only apply -renamesourcefileattribute when minifying names.
+    if (appView.options().isMinifying()) {
+      String renaming = getRenameSourceFileAttribute();
+      if (renaming != null) {
+        return rewriteTo(renaming);
       }
     }
+    return null;
+  }
 
-    String renamedSourceFileAttribute = proguardConfiguration.getRenameSourceFileAttribute();
-    if (renamedSourceFileAttribute != null) {
-      return appView.dexItemFactory().createString(renamedSourceFileAttribute);
+  private SourceFileProvider computeNonCompatProvider() {
+    String renaming = getRenameSourceFileAttribute();
+    if (renaming != null) {
+      return rewriteTo(renaming);
     }
+    if (appView.options().isMinifying()) {
+      // TODO(b/202367773): This should also apply if optimizing.
+      return rewriteToDefaultSourceFile();
+    }
+    return null;
+  }
 
-    // Otherwise, take the smallest size depending on platform. We cannot use NULL since the jvm
-    // and art will write at foo.bar.baz(Unknown Source) without a line-number. Newer version of ART
-    // will report the DEX PC.
-    return appView
-        .dexItemFactory()
-        .createString(appView.options().isGeneratingClassFiles() ? "SourceFile" : "");
+  private String getRenameSourceFileAttribute() {
+    return appView.options().getProguardConfiguration().getRenameSourceFileAttribute();
+  }
+
+  private SourceFileProvider rewriteToDefaultSourceFile() {
+    return rewriteTo(appView.dexItemFactory().defaultSourceFileAttributeString);
+  }
+
+  private SourceFileProvider rewriteTo(String renaming) {
+    return new SourceFileProvider() {
+      @Override
+      public String get(SourceFileEnvironment environment) {
+        return renaming;
+      }
+    };
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java
index 67e574c..8a16fb0 100644
--- a/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java
+++ b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java
@@ -27,6 +27,10 @@
         return asClassNameComputationInfo()
             .internalComputeNameFor(rewritten.asDexType(), definitions, namingLens);
       }
+      if (isRecordFieldNamesComputationInfo()) {
+        return asRecordFieldNamesComputationInfo()
+            .internalComputeNameFor(rewritten.asDexType(), definitions, graphLens, namingLens);
+      }
     }
     return namingLens.lookupName(rewritten, definitions.dexItemFactory());
   }
@@ -53,4 +57,12 @@
   public ClassNameComputationInfo asClassNameComputationInfo() {
     return null;
   }
+
+  public boolean isRecordFieldNamesComputationInfo() {
+    return false;
+  }
+
+  public RecordFieldNamesComputationInfo asRecordFieldNamesComputationInfo() {
+    return null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/dexitembasedstring/RecordFieldNamesComputationInfo.java b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/RecordFieldNamesComputationInfo.java
new file mode 100644
index 0000000..17ac83a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/RecordFieldNamesComputationInfo.java
@@ -0,0 +1,168 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.dexitembasedstring;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.IntFunction;
+
+/**
+ * Specific subclass for Record field names. Record field names are specific DexString instances
+ * used at the Java class file level for Records which encode the record field names in a single
+ * String separated by semi-colon. The computation is able to minify and prune such names.
+ *
+ * <p>Example: record Person(String name, int age) {} The string is: name;age
+ *
+ * <p>The JVM normally generates the Record field names string such as it includes each field name
+ * in the string. However, bytecode manipulation tools may rewrite the field name and not the
+ * string. For this reason we have two subclasses. - MatchingRecordFieldNamesComputationInfo is used
+ * when the name in the string matches the field names, and the R8 compilation is able to minify and
+ * prune the string based on the fields. - MissMatchingRecordFieldNamesComputationInfo is used when
+ * the name in the string does not match the field names, and the R8 compilation is then only able
+ * to prune the fields while maintaining the non-matching field names.
+ */
+public abstract class RecordFieldNamesComputationInfo extends NameComputationInfo<DexType> {
+
+  final DexField[] fields;
+
+  protected RecordFieldNamesComputationInfo(DexField[] fields) {
+    this.fields = fields;
+  }
+
+  private static class MissMatchingRecordFieldNamesComputationInfo
+      extends RecordFieldNamesComputationInfo {
+
+    private final String[] fieldNames;
+
+    private MissMatchingRecordFieldNamesComputationInfo(String[] fieldNames, DexField[] fields) {
+      super(fields);
+      this.fieldNames = fieldNames;
+    }
+
+    @Override
+    public DexString internalComputeNameFor(
+        DexType type,
+        DexDefinitionSupplier definitions,
+        GraphLens graphLens,
+        NamingLens namingLens) {
+      return internalComputeNameFor(type, definitions, graphLens, i -> fieldNames[i]);
+    }
+  }
+
+  private static class MatchingRecordFieldNamesComputationInfo
+      extends RecordFieldNamesComputationInfo {
+
+    public MatchingRecordFieldNamesComputationInfo(DexField[] fields) {
+      super(fields);
+    }
+
+    @Override
+    public DexString internalComputeNameFor(
+        DexType type,
+        DexDefinitionSupplier definitions,
+        GraphLens graphLens,
+        NamingLens namingLens) {
+      return internalComputeNameFor(
+          type,
+          definitions,
+          graphLens,
+          i ->
+              namingLens
+                  .lookupField(
+                      graphLens.getRenamedFieldSignature(fields[i]), definitions.dexItemFactory())
+                  .name
+                  .toString());
+    }
+  }
+
+  static DexString dexStringFromFieldNames(List<String> fieldNames, DexItemFactory factory) {
+    return factory.createString(StringUtils.join(";", fieldNames));
+  }
+
+  public static RecordFieldNamesComputationInfo forFieldNamesAndFields(
+      DexString fieldNames, DexField[] fields) {
+    String fieldNamesString = fieldNames.toString();
+    String[] fieldNamesSplit =
+        fieldNamesString.isEmpty() ? new String[0] : fieldNamesString.split(";");
+    assert fieldNamesSplit.length == fields.length;
+    if (fieldsMatchNames(fieldNamesSplit, fields)) {
+      return new MatchingRecordFieldNamesComputationInfo(fields);
+    }
+    return new MissMatchingRecordFieldNamesComputationInfo(fieldNamesSplit, fields);
+  }
+
+  private static boolean fieldsMatchNames(String[] fieldNames, DexField[] fields) {
+    for (int i = 0; i < fieldNames.length; i++) {
+      if (!(fields[i].name.toString().equals(fieldNames[i]))) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  public DexString internalComputeNameFor(
+      DexType type,
+      DexDefinitionSupplier definitions,
+      GraphLens graphLens,
+      IntFunction<String> nameSupplier) {
+    assert Arrays.stream(fields).allMatch(f -> f.holder == type);
+    DexClass recordClass = definitions.contextIndependentDefinitionFor(type);
+    assert recordClass != null;
+    List<String> names = new ArrayList<>(fields.length);
+    for (int i = 0; i < fields.length; i++) {
+      DexEncodedField recordField =
+          recordClass.lookupInstanceField(graphLens.getRenamedFieldSignature(fields[i]));
+      if (recordField != null) {
+        names.add(nameSupplier.apply(i));
+      } else {
+        // TODO(b/201277582): Temporarily work-around as long as the field values are not also
+        //  shrunk in dex.
+        names.add("<pruned>");
+      }
+    }
+    return dexStringFromFieldNames(names, definitions.dexItemFactory());
+  }
+
+  @Override
+  DexString internalComputeNameFor(
+      DexType reference, DexDefinitionSupplier definitions, NamingLens namingLens) {
+    throw new Unreachable();
+  }
+
+  public abstract DexString internalComputeNameFor(
+      DexType type, DexDefinitionSupplier definitions, GraphLens graphLens, NamingLens namingLens);
+
+  @Override
+  public boolean needsToComputeName() {
+    return true;
+  }
+
+  @Override
+  public boolean needsToRegisterReference() {
+    return false;
+  }
+
+  @Override
+  public boolean isRecordFieldNamesComputationInfo() {
+    return true;
+  }
+
+  @Override
+  public RecordFieldNamesComputationInfo asRecordFieldNamesComputationInfo() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 080e691..922e198 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -213,4 +213,20 @@
     reprocessingCriteriaCollection = null;
     timing.end();
   }
+
+  /**
+   * Called by {@link IRConverter} at the end of a wave if a method is pruned by an optimization.
+   *
+   * <p>We only prune (1) direct single caller methods and (2) isX()/asX() virtual method overrides.
+   * For (2), we always transfer the argument information for the isX()/asX() method to its parent
+   * method using {@link #transferArgumentInformation(ProgramMethod, ProgramMethod)}, which unsets
+   * the argument information for the override.
+   *
+   * <p>Therefore, we assert that we only find a method state for direct methods.
+   */
+  public void pruneMethod(ProgramMethod method) {
+    assert codeScanner != null;
+    MethodState methodState = codeScanner.getMethodStates().removeOrElse(method, null);
+    assert methodState == null || method.getDefinition().belongsToDirectPool();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
index c01508c..efa1985 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
@@ -81,7 +81,7 @@
             CallSiteOptimizationInfo callSiteOptimizationInfo =
                 method.getDefinition().getCallSiteOptimizationInfo();
             if (callSiteOptimizationInfo.isConcreteCallSiteOptimizationInfo()
-                && !appView.appInfo().isNeverReprocessMethod(method.getReference())) {
+                && !appView.appInfo().isNeverReprocessMethod(method)) {
               methodsToReprocessBuilder.add(method, currentGraphLens);
               appView.testing().callSiteOptimizationInfoInspector.accept(method);
             }
@@ -107,11 +107,14 @@
                   method -> {
                     if (graphLens.internalGetNextMethodSignature(method.getReference())
                         != method.getReference()) {
-                      methodsToReprocessInClass.add(method);
+                      if (!method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()) {
+                        methodsToReprocessInClass.add(method);
+                      }
                     } else {
                       AffectedMethodUseRegistry registry =
                           new AffectedMethodUseRegistry(appView, method, graphLens);
                       if (method.registerCodeReferencesWithResult(registry)) {
+                        assert !method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite();
                         methodsToReprocessInClass.add(method);
                       }
                     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
index 6e90810..cd1e030 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
@@ -20,10 +20,18 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
 
+/**
+ * Computes the set of virtual methods for which we can use a monomorphic method state as well as
+ * the mapping from virtual methods to their representative root methods.
+ *
+ * <p>The analysis can be used to easily mark effectively final classes and methods as final, and
+ * therefore does this as a side effect.
+ */
 public class VirtualRootMethodsAnalysis extends DepthFirstTopDownClassHierarchyTraversal {
 
   static class VirtualRootMethod {
@@ -118,6 +126,16 @@
   }
 
   @Override
+  public void forEachSubClass(DexProgramClass clazz, Consumer<DexProgramClass> consumer) {
+    List<DexProgramClass> subclasses = immediateSubtypingInfo.getSubclasses(clazz);
+    if (subclasses.isEmpty()) {
+      promoteToFinalIfPossible(clazz);
+    } else {
+      subclasses.forEach(consumer);
+    }
+  }
+
+  @Override
   public void visit(DexProgramClass clazz) {
     Map<DexMethodSignature, VirtualRootMethod> state = computeVirtualRootMethodsState(clazz);
     virtualRootMethodsPerClass.put(clazz, state);
@@ -157,12 +175,7 @@
         rootCandidate -> {
           VirtualRootMethod virtualRootMethod =
               virtualRootMethodsForClass.remove(rootCandidate.getMethodSignature());
-          if (!clazz.isInterface()
-              && !rootCandidate.getAccessFlags().isAbstract()
-              && !virtualRootMethod.hasOverrides()
-              && appView.getKeepInfo(rootCandidate).isOptimizationAllowed(appView.options())) {
-            rootCandidate.getAccessFlags().promoteToFinal();
-          }
+          promoteToFinalIfPossible(rootCandidate, virtualRootMethod);
           if (!rootCandidate.isStructurallyEqualTo(virtualRootMethod.getRoot())) {
             return;
           }
@@ -193,4 +206,21 @@
           }
         });
   }
+
+  private void promoteToFinalIfPossible(DexProgramClass clazz) {
+    if (!clazz.isAbstract()
+        && !clazz.isInterface()
+        && appView.getKeepInfo(clazz).isOptimizationAllowed(appView.options())) {
+      clazz.getAccessFlags().promoteToFinal();
+    }
+  }
+
+  private void promoteToFinalIfPossible(ProgramMethod method, VirtualRootMethod virtualRootMethod) {
+    if (!method.getHolder().isInterface()
+        && !method.getAccessFlags().isAbstract()
+        && !virtualRootMethod.hasOverrides()
+        && appView.getKeepInfo(method).isOptimizationAllowed(appView.options())) {
+      method.getAccessFlags().promoteToFinal();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 91289b7..45630fa 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -657,8 +657,9 @@
     return keepUnusedArguments.contains(method);
   }
 
-  public boolean isNeverReprocessMethod(DexMethod method) {
-    return neverReprocess.contains(method);
+  public boolean isNeverReprocessMethod(ProgramMethod method) {
+    return neverReprocess.contains(method.getReference())
+        || method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite();
   }
 
   public Set<DexMethod> getReprocessMethods() {
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 43d2d16..f837095 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -17,13 +17,13 @@
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MutableMethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
@@ -283,11 +283,13 @@
     return -1;
   }
 
-  private DexEncodedMethod[] reachableMethods(Iterable<DexEncodedMethod> methods, DexClass clazz) {
+  private DexEncodedMethod[] reachableMethods(
+      Iterable<DexEncodedMethod> methods, DexProgramClass clazz) {
     return reachableMethods(IterableUtils.ensureUnmodifiableList(methods), clazz);
   }
 
-  private DexEncodedMethod[] reachableMethods(List<DexEncodedMethod> methods, DexClass clazz) {
+  private DexEncodedMethod[] reachableMethods(
+      List<DexEncodedMethod> methods, DexProgramClass clazz) {
     AppInfoWithLiveness appInfo = appView.appInfo();
     InternalOptions options = appView.options();
     int firstUnreachable =
@@ -320,27 +322,11 @@
         if (Log.ENABLED) {
           Log.debug(getClass(), "Making method %s abstract.", method.getReference());
         }
-        // Final classes cannot be abstract, so we have to keep the method in that case.
-        // Also some other kinds of methods cannot be abstract, so keep them around.
-        boolean allowAbstract =
-            (options.canUseAbstractMethodOnNonAbstractClass() || clazz.isAbstract())
-                && !method.isFinal()
-                && !method.accessFlags.isNative()
-                && !method.accessFlags.isStrict()
-                && !method.isSynchronized()
-                && !method.accessFlags.isPrivate()
-                && !method.isStatic()
-                && !appInfo.isFailedResolutionTarget(method.getReference());
         // Private methods and static methods can only be targeted yet non-live as the result of
         // an invalid invoke. They will not actually be called at runtime but we have to keep them
         // as non-abstract (see above) to produce the same failure mode.
-        if (!allowAbstract) {
-          // If the method was not marked as live and we cannot make it abstract, set the api level
-          // to be min or unknown.
-          method.setApiLevelForCode(AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView));
-        }
-        reachableMethods.add(
-            allowAbstract ? method.toAbstractMethod() : method.toEmptyThrowingMethod(options));
+        new ProgramMethod(clazz, method).convertToAbstractOrThrowNullMethod(appView);
+        reachableMethods.add(method);
       } else {
         if (Log.ENABLED) {
           Log.debug(getClass(), "Removing method %s.", method.getReference());
diff --git a/src/main/java/com/android/tools/r8/utils/BitSetUtils.java b/src/main/java/com/android/tools/r8/utils/BitSetUtils.java
index 483eb67..085a368 100644
--- a/src/main/java/com/android/tools/r8/utils/BitSetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/BitSetUtils.java
@@ -8,6 +8,14 @@
 
 public class BitSetUtils {
 
+  public static BitSet createFilled(boolean value, int length) {
+    BitSet bitSet = new BitSet(length);
+    for (int i = 0; i < length; i++) {
+      bitSet.set(i, value);
+    }
+    return bitSet;
+  }
+
   @SuppressWarnings("unchecked")
   public static BitSet or(BitSet bitSet, BitSet other) {
     BitSet newBitSet = (BitSet) bitSet.clone();
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 ff3878e..24e9259 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1590,6 +1590,10 @@
     public boolean testEnableTestAssertions = false;
     public boolean keepMetadataInR8IfNotRewritten = true;
 
+    // If set, pruned record fields are not used in hashCode/equals/toString and toString prints
+    // minified field names instead of original field names.
+    public boolean enableRecordModeling = false;
+
     public boolean allowConflictingSyntheticTypes = false;
 
     // Flag to allow processing of resources in D8. A data resource consumer still needs to be
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
index 5fe5cf1..00749bb 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
@@ -7,11 +7,13 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
-import com.google.common.collect.Sets;
+import com.android.tools.r8.utils.SetUtils;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.IntFunction;
+import java.util.function.Predicate;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
 
@@ -78,6 +80,10 @@
     return remove(method.getReference());
   }
 
+  public boolean removeIf(Predicate<? super T> predicate) {
+    return backing.values().removeIf(predicate);
+  }
+
   public int size() {
     return backing.size();
   }
@@ -88,7 +94,11 @@
 
   public Set<DexEncodedMethod> toDefinitionSet() {
     assert backing instanceof IdentityHashMap;
-    Set<DexEncodedMethod> definitions = Sets.newIdentityHashSet();
+    return toDefinitionSet(SetUtils::newIdentityHashSet);
+  }
+
+  public Set<DexEncodedMethod> toDefinitionSet(IntFunction<Set<DexEncodedMethod>> factory) {
+    Set<DexEncodedMethod> definitions = factory.apply(size());
     forEach(method -> definitions.add(method.getDefinition()));
     return definitions;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
index 911290b..ce4399b 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
@@ -68,7 +68,7 @@
 
   public void addAll(Iterable<ProgramMethod> methodsToAdd, GraphLens currentGraphLens) {
     assert verifyIsRewrittenWithLens(currentGraphLens);
-    methodsToAdd.forEach(method -> methods.add(method.getReference()));
+    methodsToAdd.forEach(method -> add(method, currentGraphLens));
   }
 
   public void clear() {
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
index 7b653ed..df6be44 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
@@ -29,6 +29,13 @@
   private static final String EXPECTED_RESULT_D8 =
       String.format(EXPECTED_RESULT, "Person", "Person");
   private static final String EXPECTED_RESULT_R8 = String.format(EXPECTED_RESULT, "a", "a");
+  // TODO(b/201277582): These results are temporary while we transition into pruned minified record
+  //  fields.
+  private static final String EXPECTED_RESULT_R8_ADVANCED_DEX =
+      StringUtils.lines(
+          "a[a=Jane Doe, <pruned>=42, <pruned>=-1]", "a[a=Bob, <pruned>=42, <pruned>=-1]");
+  private static final String EXPECTED_RESULT_R8_ADVANCED_CF =
+      StringUtils.lines("a[a=Jane Doe, b=42, c=-1]", "a[a=Bob, b=42, c=-1]");
 
   private final TestParameters parameters;
 
@@ -83,6 +90,43 @@
   }
 
   @Test
+  public void testR8AdvancedShrinking() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(PROGRAM_DATA)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN_TYPE)
+        .addOptionsModification(opt -> opt.testing.enableRecordModeling = true)
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .compile()
+        .inspect(this::assertSingleField)
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT_R8_ADVANCED_DEX);
+  }
+
+  @Test
+  public void testR8CfThenDexAdvancedShrinking() throws Exception {
+    Path desugared =
+        testForR8(Backend.CF)
+            .addProgramClassFileData(PROGRAM_DATA)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(MAIN_TYPE)
+            .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+            .addOptionsModification(opt -> opt.testing.enableRecordModeling = true)
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .compile()
+            .writeToZip();
+    testForR8(parameters.getBackend())
+        .addProgramFiles(desugared)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN_TYPE)
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .compile()
+        .inspect(this::assertSingleField)
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT_R8_ADVANCED_CF);
+  }
+
+  @Test
   public void testR8CfThenDex() throws Exception {
     Path desugared =
         testForR8(Backend.CF)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/LibraryOverrideInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/LibraryOverrideInliningTest.java
index 0b58101..3882a7b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/LibraryOverrideInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/LibraryOverrideInliningTest.java
@@ -6,16 +6,14 @@
 
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -24,17 +22,14 @@
 @RunWith(Parameterized.class)
 public class LibraryOverrideInliningTest extends TestBase {
 
-  private final boolean disableInliningOfLibraryMethodOverrides;
   private final TestParameters parameters;
 
-  @Parameters(name = "{1}, disableInliningOfLibraryMethodOverrides: {0}")
-  public static List<Object[]> data() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public LibraryOverrideInliningTest(
-      boolean disableInliningOfLibraryMethodOverrides, TestParameters parameters) {
-    this.disableInliningOfLibraryMethodOverrides = disableInliningOfLibraryMethodOverrides;
+  public LibraryOverrideInliningTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -43,12 +38,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(LibraryOverrideInliningTest.class)
         .addKeepMainRule(TestClass.class)
-        .addOptionsModification(
-            options ->
-                options.disableInliningOfLibraryMethodOverrides =
-                    disableInliningOfLibraryMethodOverrides)
         .enableNeverClassInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
             inspector -> {
@@ -64,12 +55,7 @@
 
               MethodSubject mainMethodSubject = testClassSubject.mainMethod();
               assertThat(mainMethodSubject, isPresent());
-
-              if (disableInliningOfLibraryMethodOverrides) {
-                assertThat(mainMethodSubject, invokesMethod(toStringMethodSubject));
-              } else {
-                assertThat(mainMethodSubject, not(invokesMethod(toStringMethodSubject)));
-              }
+              assertThat(mainMethodSubject, invokesMethod(toStringMethodSubject));
             })
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello world!");
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 5bae6d3..105d097 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -6,8 +6,10 @@
 
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -120,7 +122,7 @@
                 assertThat(
                     inspector.clazz(
                         "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda2"),
-                    isPresent());
+                    isAbsent());
               } else {
                 assertThat(
                     inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateless$1"),
@@ -164,7 +166,7 @@
                       !hasKotlinCGeneratedLambdaClasses
                           ? "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda2"
                           : "class_inliner_lambda_j_style.MainKt$testStateful$1"),
-                  isPresent());
+                  onlyIf(hasKotlinCGeneratedLambdaClasses, isPresent()));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
index b7b1f1b..89e517a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
@@ -60,8 +60,9 @@
 
   @Test
   public void test_dex() {
-    // TODO(b/179860027): Make it run on all tests.
-    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
+    assumeTrue(
+        "kotlinc > 1.3.72 will no longer compile kotlin files with duplicate annotations",
+        kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     assumeTrue("test DEX", parameters.isDexRuntime());
     try {
       testForR8(parameters.getBackend())
@@ -80,8 +81,9 @@
 
   @Test
   public void test_cf() throws Exception {
-    // TODO(b/179860027): Make it run on all tests.
-    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
+    assumeTrue(
+        "kotlinc > 1.3.72 will no longer compile kotlin files with duplicate annotations",
+        kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     assumeTrue("test CF", parameters.isCfRuntime());
     testForR8(parameters.getBackend())
         .addProgramFiles(compiledJars.getForConfiguration(kotlinc, targetVersion))
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index 0f534e0..282d8d7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -10,7 +10,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinTargetVersion;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestParameters;
@@ -91,6 +90,11 @@
     runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+                return;
+              }
+
               ClassSubject outerClass =
                   checkClassIsKept(inspector, testedClass.getOuterClassName());
               String propertyName = "primitiveProp";
@@ -124,6 +128,11 @@
     runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+                return;
+              }
+
               ClassSubject outerClass =
                   checkClassIsKept(inspector, testedClass.getOuterClassName());
               String propertyName = "privateProp";
@@ -159,6 +168,11 @@
     runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+                return;
+              }
+
               ClassSubject outerClass =
                   checkClassIsKept(inspector, testedClass.getOuterClassName());
               String propertyName = "internalProp";
@@ -193,6 +207,11 @@
     runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+                return;
+              }
+
               ClassSubject outerClass =
                   checkClassIsKept(inspector, testedClass.getOuterClassName());
               String propertyName = "publicProp";
@@ -227,6 +246,11 @@
     runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+                return;
+              }
+
               ClassSubject outerClass =
                   checkClassIsKept(inspector, testedClass.getOuterClassName());
               String propertyName = "privateLateInitProp";
@@ -260,6 +284,11 @@
     runTest(PROPERTIES_PACKAGE_NAME, mainClass)
         .inspect(
             inspector -> {
+              if (true) {
+                checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+                return;
+              }
+
               ClassSubject outerClass =
                   checkClassIsKept(inspector, testedClass.getOuterClassName());
               String propertyName = "internalLateInitProp";
@@ -293,6 +322,11 @@
     runTest(PROPERTIES_PACKAGE_NAME, mainClass)
         .inspect(
             inspector -> {
+              if (true) {
+                checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+                return;
+              }
+
               ClassSubject outerClass =
                   checkClassIsKept(inspector, testedClass.getOuterClassName());
               String propertyName = "publicLateInitProp";
@@ -375,9 +409,7 @@
     runTest("accessors", mainClass)
         .inspect(
             inspector -> {
-              if (allowAccessModification
-                  && (testParameters.isCfRuntime()
-                      || !kotlinParameters.is(KOTLINC_1_5_0, KotlinTargetVersion.JAVA_8))) {
+              if (allowAccessModification) {
                 checkClassIsRemoved(inspector, testedClass.getClassName());
                 return;
               }
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index c605951..5a60483 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -87,7 +87,7 @@
           .addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
           .addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
 
-  private Consumer<InternalOptions> disableAggressiveClassOptimizations =
+  private final Consumer<InternalOptions> disableAggressiveClassOptimizations =
       o -> {
         o.enableClassInlining = false;
         o.enableVerticalClassMerging = false;
@@ -116,13 +116,9 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
-            inspector -> {
-              checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
-            });
+            inspector -> checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName()));
   }
 
   @Test
@@ -132,21 +128,20 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
+                return;
+              }
+
               ClassSubject classSubject =
                   checkClassIsKept(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
               String propertyName = "privateProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName);
-              if (!allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
 
               // Private property has no getter or setter.
               checkMethodIsAbsent(
@@ -163,11 +158,14 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
+                return;
+              }
+
               ClassSubject classSubject =
                   checkClassIsKept(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
               String propertyName = "protectedProp";
@@ -176,13 +174,8 @@
 
               // Protected property has private field.
               MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-                checkMethodIsRemoved(classSubject, getter);
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-                checkMethodIsKept(classSubject, getter);
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
+              checkMethodIsKept(classSubject, getter);
             });
   }
 
@@ -193,11 +186,14 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
+                return;
+              }
+
               ClassSubject classSubject =
                   checkClassIsKept(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
               String propertyName = "internalProp";
@@ -206,13 +202,8 @@
 
               // Internal property has private field
               MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-                checkMethodIsRemoved(classSubject, getter);
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-                checkMethodIsKept(classSubject, getter);
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
+              checkMethodIsKept(classSubject, getter);
             });
   }
 
@@ -223,11 +214,14 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
+                return;
+              }
+
               ClassSubject classSubject =
                   checkClassIsKept(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
               String propertyName = "publicProp";
@@ -236,13 +230,8 @@
 
               // Public property has private field
               MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-                checkMethodIsRemoved(classSubject, getter);
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-                checkMethodIsKept(classSubject, getter);
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
+              checkMethodIsKept(classSubject, getter);
             });
   }
 
@@ -256,6 +245,11 @@
             testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
+                return;
+              }
+
               ClassSubject classSubject =
                   checkClassIsKept(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
               String propertyName = "primitiveProp";
@@ -263,15 +257,9 @@
 
               MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(propertyName);
               MethodSignature setter = MUTABLE_PROPERTY_CLASS.getSetterForProperty(propertyName);
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-                checkMethodIsRemoved(classSubject, getter);
-                checkMethodIsRemoved(classSubject, setter);
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-                checkMethodIsKept(classSubject, getter);
-                checkMethodIsRemoved(classSubject, setter);
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
+              checkMethodIsKept(classSubject, getter);
+              checkMethodIsRemoved(classSubject, setter);
             });
   }
 
@@ -284,9 +272,7 @@
             mainClass,
             testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
-            inspector -> {
-              checkClassIsRemoved(inspector, LATE_INIT_PROPERTY_CLASS.getClassName());
-            });
+            inspector -> checkClassIsRemoved(inspector, LATE_INIT_PROPERTY_CLASS.getClassName()));
   }
 
   @Test
@@ -296,19 +282,20 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, LATE_INIT_PROPERTY_CLASS.getClassName());
+                return;
+              }
+
               ClassSubject classSubject =
                   checkClassIsKept(inspector, LATE_INIT_PROPERTY_CLASS.getClassName());
               String propertyName = "privateLateInitProp";
               FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
               assertTrue("Field is absent", fieldSubject.isPresent());
-              if (!allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
 
               // Private late init property have no getter or setter.
               checkMethodIsAbsent(
@@ -325,24 +312,9 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
-            inspector -> {
-              ClassSubject classSubject =
-                  checkClassIsKept(inspector, LATE_INIT_PROPERTY_CLASS.getClassName());
-              String propertyName = "protectedLateInitProp";
-              FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
-              assertTrue("Field is absent", fieldSubject.isPresent());
-              if (!allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isProtected());
-              }
-
-              // Protected late init property have protected getter
-              checkMethodIsRemoved(
-                  classSubject, LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
-            });
+            inspector -> checkClassIsRemoved(inspector, LATE_INIT_PROPERTY_CLASS.getClassName()));
   }
 
   @Test
@@ -352,22 +324,9 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
-            inspector -> {
-              ClassSubject classSubject =
-                  checkClassIsKept(inspector, LATE_INIT_PROPERTY_CLASS.getClassName());
-              String propertyName = "internalLateInitProp";
-              FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
-              assertTrue("Field is absent", fieldSubject.isPresent());
-              assertTrue(fieldSubject.getField().accessFlags.isPublic());
-
-              // Internal late init property have protected getter
-              checkMethodIsRemoved(
-                  classSubject, LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
-            });
+            inspector -> checkClassIsRemoved(inspector, LATE_INIT_PROPERTY_CLASS.getClassName()));
   }
 
   @Test
@@ -377,22 +336,9 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
-            inspector -> {
-              ClassSubject classSubject =
-                  checkClassIsKept(inspector, LATE_INIT_PROPERTY_CLASS.getClassName());
-              String propertyName = "publicLateInitProp";
-              FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
-              assertTrue("Field is absent", fieldSubject.isPresent());
-              assertTrue(fieldSubject.getField().accessFlags.isPublic());
-
-              // Internal late init property have protected getter
-              checkMethodIsRemoved(
-                  classSubject, LATE_INIT_PROPERTY_CLASS.getGetterForProperty(propertyName));
-            });
+            inspector -> checkClassIsRemoved(inspector, LATE_INIT_PROPERTY_CLASS.getClassName()));
   }
 
   @Test
@@ -404,9 +350,8 @@
             mainClass,
             testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
-            inspector -> {
-              checkClassIsRemoved(inspector, USER_DEFINED_PROPERTY_CLASS.getClassName());
-            });
+            inspector ->
+                checkClassIsRemoved(inspector, USER_DEFINED_PROPERTY_CLASS.getClassName()));
   }
 
   @Test
@@ -419,6 +364,11 @@
             testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, USER_DEFINED_PROPERTY_CLASS.getClassName());
+                return;
+              }
+
               ClassSubject classSubject =
                   checkClassIsKept(inspector, USER_DEFINED_PROPERTY_CLASS.getClassName());
               String propertyName = "durationInSeconds";
@@ -430,13 +380,8 @@
                   checkFieldIsKept(classSubject, "int", "durationInMilliSeconds");
               MethodSignature getter =
                   USER_DEFINED_PROPERTY_CLASS.getGetterForProperty(propertyName);
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-                checkMethodIsRemoved(classSubject, getter);
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-                checkMethodIsKept(classSubject, getter);
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
+              checkMethodIsKept(classSubject, getter);
             });
   }
 
@@ -447,22 +392,22 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
+
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, "properties.CompanionProperties");
+                return;
+              }
+
               ClassSubject outerClass =
                   checkClassIsKept(inspector, "properties.CompanionProperties");
-              checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
               String propertyName = "primitiveProp";
               FieldSubject fieldSubject = checkFieldIsKept(outerClass, "int", propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-              }
+              assertTrue(fieldSubject.getField().isStatic());
+              assertTrue(fieldSubject.getField().isPrivate());
             });
   }
 
@@ -473,34 +418,29 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
+
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, "properties.CompanionProperties");
+                return;
+              }
+
               ClassSubject outerClass =
                   checkClassIsKept(inspector, "properties.CompanionProperties");
-              checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
               String propertyName = "privateProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
-
-              MemberNaming.MethodSignature getter =
-                  COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
-              MemberNaming.MethodSignature setter =
-                  COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
+              assertTrue(fieldSubject.getField().isStatic());
 
               // Because the getter/setter are private, they can only be called from another method
               // in the class. If this is an instance method, they will be called on 'this' which is
               // known to be non-null, thus the getter/setter can be inlined if their code is small
               // enough. Because the backing field is private, they will call into an accessor
-              // (static) method. If access relaxation is enabled, this accessor can be removed.
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-              }
+              // (static) method.
+              assertTrue(fieldSubject.getField().isPrivate());
             });
   }
 
@@ -511,23 +451,22 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, "properties.CompanionProperties");
+                return;
+              }
+
               ClassSubject outerClass =
                   checkClassIsKept(inspector, "properties.CompanionProperties");
               checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
               String propertyName = "internalProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-              }
+              assertTrue(fieldSubject.getField().isStatic());
+              assertTrue(fieldSubject.getField().isPrivate());
             });
   }
 
@@ -538,23 +477,23 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
+
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, "properties.CompanionProperties");
+                return;
+              }
+
               ClassSubject outerClass =
                   checkClassIsKept(inspector, "properties.CompanionProperties");
-              checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
               String propertyName = "publicProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-              }
+              assertTrue(fieldSubject.getField().isStatic());
+              assertTrue(fieldSubject.getField().isPrivate());
             });
   }
 
@@ -566,29 +505,29 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              checkClassIsRemoved(inspector, testedClass.getClassName());
+
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getOuterClassName());
+                return;
+              }
+
               ClassSubject outerClass =
                   checkClassIsKept(inspector, testedClass.getOuterClassName());
-              checkClassIsRemoved(inspector, testedClass.getClassName());
               String propertyName = "privateLateInitProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
+              assertTrue(fieldSubject.getField().isStatic());
 
               // Because the getter/setter are private, they can only be called from another method
               // in the class. If this is an instance method, they will be called on 'this' which is
               // known to be non-null, thus the getter/setter can be inlined if their code is small
               // enough. Because the backing field is private, they will call into an accessor
               // (static) method. If access relaxation is enabled, this accessor can be removed.
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().isPublic());
-              } else {
-                assertTrue(fieldSubject.getField().isPrivate());
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
             });
   }
 
@@ -600,19 +539,11 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
-              ClassSubject outerClass =
-                  checkClassIsKept(inspector, testedClass.getOuterClassName());
               checkClassIsRemoved(inspector, testedClass.getClassName());
-              String propertyName = "internalLateInitProp";
-              FieldSubject fieldSubject =
-                  checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
-              assertTrue(fieldSubject.getField().accessFlags.isPublic());
+              checkClassIsRemoved(inspector, testedClass.getOuterClassName());
             });
   }
 
@@ -624,19 +555,11 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
-              ClassSubject outerClass =
-                  checkClassIsKept(inspector, testedClass.getOuterClassName());
               checkClassIsRemoved(inspector, testedClass.getClassName());
-              String propertyName = "publicLateInitProp";
-              FieldSubject fieldSubject =
-                  checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
-              assertTrue(fieldSubject.getField().accessFlags.isPublic());
+              checkClassIsRemoved(inspector, testedClass.getClassName());
             });
   }
 
@@ -651,30 +574,21 @@
             testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getClassName());
+                return;
+              }
+
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
               String propertyName = "primitiveProp";
               FieldSubject fieldSubject = checkFieldIsKept(objectClass, "int", propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
+              assertTrue(fieldSubject.getField().isStatic());
 
               MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
               MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
-
-              if (allowAccessModification) {
-                // Getter and setter is inlined because the constructor of ObjectProperties is
-                // considered trivial, which implies that the member value propagation marks the
-                // INSTANCE field as being non-null.
-                checkMethodIsRemoved(objectClass, getter);
-                checkMethodIsRemoved(objectClass, setter);
-              } else {
-                checkMethodIsKept(objectClass, getter);
-                checkMethodIsRemoved(objectClass, setter);
-              }
-
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-              }
+              checkMethodIsKept(objectClass, getter);
+              checkMethodIsRemoved(objectClass, setter);
+              assertTrue(fieldSubject.getField().isPrivate());
             });
   }
 
@@ -686,16 +600,19 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getClassName());
+                return;
+              }
+
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
               String propertyName = "privateProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
+              assertTrue(fieldSubject.getField().isStatic());
 
               MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
               MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
@@ -704,11 +621,7 @@
               checkMethodIsAbsent(objectClass, getter);
               checkMethodIsAbsent(objectClass, setter);
 
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
             });
   }
 
@@ -723,31 +636,24 @@
             testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getClassName());
+                return;
+              }
+
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
               String propertyName = "internalProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
+              assertTrue(fieldSubject.getField().isStatic());
 
               MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
               MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
 
-              if (allowAccessModification) {
-                // Getter and setter is inlined because the constructor of ObjectProperties is
-                // considered trivial, which implies that the member value propagation marks the
-                // INSTANCE field as being non-null.
-                checkMethodIsRemoved(objectClass, getter);
-                checkMethodIsRemoved(objectClass, setter);
-              } else {
-                checkMethodIsKept(objectClass, getter);
-                checkMethodIsRemoved(objectClass, setter);
-              }
+              checkMethodIsKept(objectClass, getter);
+              checkMethodIsRemoved(objectClass, setter);
 
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
             });
   }
 
@@ -762,31 +668,24 @@
             testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getClassName());
+                return;
+              }
+
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
               String propertyName = "publicProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
+              assertTrue(fieldSubject.getField().isStatic());
 
               MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
               MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
 
-              if (allowAccessModification) {
-                // Getter and setter is inlined because the constructor of ObjectProperties is
-                // considered trivial, which implies that the member value propagation marks the
-                // INSTANCE field as being non-null.
-                checkMethodIsRemoved(objectClass, getter);
-                checkMethodIsRemoved(objectClass, setter);
-              } else {
-                checkMethodIsKept(objectClass, getter);
-                checkMethodIsRemoved(objectClass, setter);
-              }
+              checkMethodIsKept(objectClass, getter);
+              checkMethodIsRemoved(objectClass, setter);
 
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
             });
   }
 
@@ -798,16 +697,19 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getClassName());
+                return;
+              }
+
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
               String propertyName = "privateLateInitProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
+              assertTrue(fieldSubject.getField().isStatic());
 
               MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
               MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
@@ -816,11 +718,7 @@
               checkMethodIsAbsent(objectClass, getter);
               checkMethodIsAbsent(objectClass, setter);
 
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
             });
   }
 
@@ -832,24 +730,8 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
-        .inspect(
-            inspector -> {
-              ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
-              String propertyName = "internalLateInitProp";
-              FieldSubject fieldSubject =
-                  checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
-
-              MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
-              MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
-
-              checkMethodIsRemoved(objectClass, getter);
-              checkMethodIsRemoved(objectClass, setter);
-              assertTrue(fieldSubject.getField().accessFlags.isPublic());
-            });
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+        .inspect(inspector -> checkClassIsRemoved(inspector, testedClass.getClassName()));
   }
 
   @Test
@@ -860,24 +742,8 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
-        .inspect(
-            inspector -> {
-              ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
-              String propertyName = "publicLateInitProp";
-              FieldSubject fieldSubject =
-                  checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
-
-              MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
-              MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
-
-              checkMethodIsRemoved(objectClass, getter);
-              checkMethodIsRemoved(objectClass, setter);
-              assertTrue(fieldSubject.getField().accessFlags.isPublic());
-            });
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+        .inspect(inspector -> checkClassIsRemoved(inspector, testedClass.getClassName()));
   }
 
   @Test
@@ -891,23 +757,22 @@
             testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getClassName());
+                return;
+              }
+
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
               String propertyName = "primitiveProp";
               FieldSubject fieldSubject = checkFieldIsKept(objectClass, "int", propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
+              assertTrue(fieldSubject.getField().isStatic());
 
               MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
               MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
 
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-                checkMethodIsRemoved(objectClass, getter);
-                checkMethodIsRemoved(objectClass, setter);
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-                checkMethodIsKept(objectClass, getter);
-                checkMethodIsRemoved(objectClass, setter);
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
+              checkMethodIsKept(objectClass, getter);
+              checkMethodIsRemoved(objectClass, setter);
             });
   }
 
@@ -919,16 +784,19 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getClassName());
+                return;
+              }
+
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
               String propertyName = "privateProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
+              assertTrue(fieldSubject.getField().isStatic());
 
               MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
               MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
@@ -936,11 +804,7 @@
               // A private property has no getter/setter.
               checkMethodIsAbsent(objectClass, getter);
               checkMethodIsAbsent(objectClass, setter);
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
             });
   }
 
@@ -952,16 +816,19 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getClassName());
+                return;
+              }
+
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
               String propertyName = "internalProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
+              assertTrue(fieldSubject.getField().isStatic());
 
               // We expect getter to be inlined when access (of the backing field) is relaxed to
               // public.
@@ -969,13 +836,8 @@
               // Note: the setter is considered as a regular method (because of KotlinC adding extra
               // null checks), thus we cannot say if the setter would be inlined or not by R8.
               MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-                checkMethodIsRemoved(objectClass, getter);
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-                checkMethodIsKept(objectClass, getter);
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
+              checkMethodIsKept(objectClass, getter);
             });
   }
 
@@ -987,29 +849,27 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getClassName());
+                return;
+              }
+
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
               String propertyName = "publicProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
+              assertTrue(fieldSubject.getField().isStatic());
 
               // We expect getter to be inlined when access (of the backing field) is relaxed to
               // public. On the other hand, the setter is considered as a regular method (because of
               // null checks), thus we cannot say if it can be inlined or not.
               MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
 
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-                checkMethodIsRemoved(objectClass, getter);
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-                checkMethodIsKept(objectClass, getter);
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
+              checkMethodIsKept(objectClass, getter);
             });
   }
 
@@ -1021,16 +881,19 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
+              if (allowAccessModification) {
+                checkClassIsRemoved(inspector, testedClass.getClassName());
+                return;
+              }
+
               ClassSubject fileClass = checkClassIsKept(inspector, testedClass.getClassName());
               String propertyName = "privateLateInitProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(fileClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
+              assertTrue(fieldSubject.getField().isStatic());
 
               MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
               MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
@@ -1038,11 +901,7 @@
               // A private property has no getter/setter.
               checkMethodIsAbsent(fileClass, getter);
               checkMethodIsAbsent(fileClass, setter);
-              if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
-              } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
-              }
+              assertTrue(fieldSubject.getField().isPrivate());
             });
   }
 
@@ -1054,26 +913,8 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
-        .inspect(
-            inspector -> {
-              ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
-              String propertyName = "internalLateInitProp";
-              FieldSubject fieldSubject =
-                  checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
-              assertTrue(fieldSubject.getField().accessFlags.isPublic());
-
-              MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
-              MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
-
-              // Field is public and getter/setter is only called from one place so we expect to
-              // always inline it.
-              checkMethodIsRemoved(objectClass, getter);
-              checkMethodIsRemoved(objectClass, setter);
-            });
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+        .inspect(inspector -> checkClassIsRemoved(inspector, testedClass.getClassName()));
   }
 
   @Test
@@ -1084,24 +925,8 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder ->
-                testBuilder
-                    .addOptionsModification(disableAggressiveClassOptimizations))
-        .inspect(
-            inspector -> {
-              ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
-              String propertyName = "publicLateInitProp";
-              FieldSubject fieldSubject =
-                  checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
-              assertTrue(fieldSubject.getField().accessFlags.isStatic());
-
-              MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
-              MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
-
-              checkMethodIsRemoved(objectClass, getter);
-              checkMethodIsRemoved(objectClass, setter);
-              assertTrue(fieldSubject.getField().accessFlags.isPublic());
-            });
+            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+        .inspect(inspector -> checkClassIsRemoved(inspector, testedClass.getClassName()));
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
index 1652222..1d25958 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -9,7 +9,6 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
@@ -70,9 +69,6 @@
     ClassSubject enumClass = inspector.clazz(ENUM_CLASS_NAME);
     assertThat(enumClass, isPresent());
     assertEquals(minify, enumClass.isRenamed());
-    // TODO(b/179994975): Kotlin enum changed in 1.5.
-    assertThat(
-        enumClass.clinit(),
-        kotlinc.is(KotlinCompilerVersion.KOTLINC_1_5_0) ? isPresent() : isAbsent());
+    assertThat(enumClass.clinit(), isAbsent());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
index 257e189..e62b1e0 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
@@ -105,14 +105,12 @@
   }
 
   private String getDefaultExpectedName() {
-    return getDefaultExpectedName(parameters.getBackend() == Backend.CF ? "SourceFile" : "");
+    return "SourceFile";
   }
 
   private String getDefaultExpectedName(String name) {
     if (!keepSourceFile) {
-      if (parameters.getBackend() == Backend.CF || !isCompat) {
-        return null;
-      }
+      return getDefaultExpectedName();
     }
     return name;
   }
diff --git a/src/test/java/com/android/tools/r8/naming/sourcefile/RenameSourceFileAttributeCompatTest.java b/src/test/java/com/android/tools/r8/naming/sourcefile/RenameSourceFileAttributeCompatTest.java
index 55784ce..ae03cae 100644
--- a/src/test/java/com/android/tools/r8/naming/sourcefile/RenameSourceFileAttributeCompatTest.java
+++ b/src/test/java/com/android/tools/r8/naming/sourcefile/RenameSourceFileAttributeCompatTest.java
@@ -46,8 +46,8 @@
   }
 
   private void checkSourceFileIsRemoved(SingleTestRunResult<?> result) throws Exception {
-    // TODO(b/202368282): We should likely emit a "default" source file attribute rather than strip.
-    checkSourceFile(result, null, null, null);
+    String removalValue = result.isR8TestRunResult() ? "SourceFile" : null;
+    checkSourceFile(result, removalValue, removalValue, removalValue);
   }
 
   private void checkSourceFileIsOriginal(SingleTestRunResult<?> result) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileAttributeCompatTest.java b/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileAttributeCompatTest.java
index 8793045..c4b0920 100644
--- a/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileAttributeCompatTest.java
+++ b/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileAttributeCompatTest.java
@@ -45,8 +45,13 @@
   }
 
   private void checkSourceFileIsRemoved(SingleTestRunResult<?> result) throws Exception {
-    // TODO(b/202368282): We should likely emit a "default" source file attribute rather than strip.
-    checkSourceFile(result, null, null, null);
+    if (result.isR8TestRunResult()) {
+      // R8 and R8/compat differ from PG in that at least the default source file attribute is
+      // retained. This ensures better stack traces on various VMs and has next to no size overhead.
+      checkSourceFileIsReplacedByDefault(result);
+    } else {
+      checkSourceFile(result, null, null, null);
+    }
   }
 
   private void checkSourceFileIsOriginal(SingleTestRunResult<?> result) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeKeptClassTest.java b/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeKeptClassTest.java
new file mode 100644
index 0000000..b20a64b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeKeptClassTest.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.finalize;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isFinal;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.AllOf.allOf;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import 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 FinalizeKeptClassTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, allOf(isPresent(), not(isFinal())));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  // Should not become final.
+  static class Main {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeSubclassTest.java b/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeSubclassTest.java
new file mode 100644
index 0000000..f71d961
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeSubclassTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.finalize;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isFinal;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.AllOf.allOf;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+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.codeinspector.ClassSubject;
+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 FinalizeSubclassTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, allOf(isPresent(), not(isFinal())));
+
+              ClassSubject bClassSubject = inspector.clazz(B.class);
+              assertThat(bClassSubject, allOf(isPresent(), not(isFinal())));
+
+              ClassSubject cClassSubject = inspector.clazz(C.class);
+              assertThat(cClassSubject, allOf(isPresent(), isFinal()));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A.m()", "B.m()", "C.m()");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A().m();
+      new B().m();
+      new C().m();
+    }
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  static class A {
+
+    @NeverInline
+    void m() {
+      System.out.println("A.m()");
+    }
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  static class B extends A {
+
+    @NeverInline
+    void m() {
+      System.out.println("B.m()");
+    }
+  }
+
+  // Should become final.
+  @NeverClassInline
+  static class C extends B {
+
+    @NeverInline
+    void m() {
+      System.out.println("C.m()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
index a913463..5c7a755 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
@@ -29,7 +29,6 @@
 @RunWith(Parameterized.class)
 public class KeepAttributesDotsTest extends TestBase {
 
-  private final TestParameters parameters;
   private final String keepAttributes;
 
   @Parameterized.Parameters(name = "-keepattributes {1}")
@@ -40,7 +39,7 @@
   }
 
   public KeepAttributesDotsTest(TestParameters parameters, String keepAttributes) {
-    this.parameters = parameters;
+    parameters.assertNoneRuntime();
     this.keepAttributes = keepAttributes;
   }
 
@@ -76,7 +75,7 @@
     assertTrue(field.getField().annotations().isEmpty());
     assertTrue(
         clazz.getDexProgramClass().sourceFile == null
-            || clazz.getDexProgramClass().sourceFile.size == 0);
+            || clazz.getDexProgramClass().sourceFile.toString().equals("SourceFile"));
     assertNull(main.getLineNumberTable());
     assertTrue(main.getLocalVariableTable().isEmpty());
   }