Merge commit 'de5e2c1192ff73d5936b36a8fc46127cf17de185' into dev-release
diff --git a/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java b/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java
index c856d87..107efb4 100644
--- a/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java
+++ b/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java
@@ -4,8 +4,8 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfigurationParser;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 69c1ec8..c292f33 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -7,8 +7,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.inspector.Inspector;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfigurationParser;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 1ef4a6e..c23b48c 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.inspector.Inspector;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
diff --git a/src/main/java/com/android/tools/r8/DumpOptions.java b/src/main/java/com/android/tools/r8/DumpOptions.java
index f923ffa..3b502b5 100644
--- a/src/main/java/com/android/tools/r8/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/DumpOptions.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index a47844f..19d4a98 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -35,8 +35,8 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfigurationParser;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 9ec502c..7cd95e5 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.inspector.Inspector;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 6edb405..3bd0653 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -46,9 +46,9 @@
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
-import com.android.tools.r8.ir.desugar.RecordRewriter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterLibraryTypeSynthesizor;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.records.RecordRewriter;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
 import com.android.tools.r8.ir.optimize.NestReducer;
@@ -307,8 +307,8 @@
         MainDexListBuilder.checkForAssumedLibraryTypes(appView.appInfo());
       }
       if (!options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
-        DesugaredLibraryRetargeter.checkForAssumedLibraryTypes(appView);
-        DesugaredLibraryRetargeter.amendLibraryWithRetargetedMembers(appView);
+        DesugaredLibraryRetargeterLibraryTypeSynthesizor.checkForAssumedLibraryTypes(appView);
+        DesugaredLibraryRetargeterLibraryTypeSynthesizor.amendLibraryWithRetargetedMembers(appView);
       }
       InterfaceMethodRewriter.checkForAssumedLibraryTypes(appView.appInfo(), options);
       BackportedMethodRewriter.registerAssumedLibraryTypes(options);
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 844d01a..af8aa34 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.inspector.Inspector;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.shaking.ProguardConfiguration;
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 64dd9a5..c6ba520 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -37,6 +37,7 @@
 import com.android.tools.r8.cf.code.CfNeg;
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfNewUnboxedEnum;
 import com.android.tools.r8.cf.code.CfNop;
 import com.android.tools.r8.cf.code.CfNumberConversion;
 import com.android.tools.r8.cf.code.CfPosition;
@@ -503,6 +504,12 @@
     }
   }
 
+  public void print(CfNewUnboxedEnum newInstance) {
+    indent();
+    builder.append("newunboxedenum ");
+    appendClass(newInstance.getType());
+  }
+
   public void print(CfMultiANewArray multiANewArray) {
     indent();
     builder.append("multianewarray ");
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
new file mode 100644
index 0000000..89f8686
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+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.CfCompareHelper;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
+import java.util.ListIterator;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/** The class-file representation of {@link com.android.tools.r8.ir.code.NewUnboxedEnumInstance}. */
+public class CfNewUnboxedEnum extends CfInstruction implements CfTypeInstruction {
+
+  private final DexType type;
+  private final int ordinal;
+
+  public CfNewUnboxedEnum(DexType type, int ordinal) {
+    this.type = type;
+    this.ordinal = ordinal;
+  }
+
+  @Override
+  public CfTypeInstruction asTypeInstruction() {
+    return this;
+  }
+
+  @Override
+  public boolean isTypeInstruction() {
+    return true;
+  }
+
+  @Override
+  public DexType getType() {
+    return type;
+  }
+
+  @Override
+  public CfInstruction withType(DexType newType) {
+    return new CfNewUnboxedEnum(newType, ordinal);
+  }
+
+  @Override
+  public int getCompareToId() {
+    return Opcodes.NEW;
+  }
+
+  @Override
+  public int internalAcceptCompareTo(
+      CfInstruction other, CompareToVisitor visitor, CfCompareHelper helper) {
+    return type.acceptCompareTo(((CfNewUnboxedEnum) other).type, visitor);
+  }
+
+  @Override
+  public void write(
+      AppView<?> appView,
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  @Override
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    registry.registerNewUnboxedEnumInstance(type);
+  }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    builder.addNewUnboxedEnumInstance(state.push(type).register, type, ordinal);
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
+    return inliningConstraints.forNewUnboxedEnumInstance(type, context);
+  }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ... →
+    // ..., objectref
+    frameBuilder.push(FrameType.initialized(type));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/code/DexNewUnboxedEnumInstance.java b/src/main/java/com/android/tools/r8/code/DexNewUnboxedEnumInstance.java
new file mode 100644
index 0000000..f9c26e6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/DexNewUnboxedEnumInstance.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2016, 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.code;
+
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
+import java.nio.ShortBuffer;
+
+/** The dex representation of {@link com.android.tools.r8.ir.code.NewUnboxedEnumInstance}. */
+public class DexNewUnboxedEnumInstance extends Format21c<DexType> {
+
+  public static final int OPCODE = 0x22;
+  public static final String NAME = "NewUnboxedEnumInstance";
+  public static final String SMALI_NAME = "new-unboxed-enum-instance";
+
+  private final int ordinal;
+
+  public DexNewUnboxedEnumInstance(int AA, DexType BBBB, int ordinal) {
+    super(AA, BBBB);
+    this.ordinal = ordinal;
+  }
+
+  @Override
+  public String getName() {
+    return NAME;
+  }
+
+  @Override
+  public String getSmaliName() {
+    return SMALI_NAME;
+  }
+
+  @Override
+  public int getOpcode() {
+    throw new Unreachable();
+  }
+
+  @Override
+  void internalSubSpecify(StructuralSpecification<Format21c<DexType>, ?> spec) {
+    spec.withItem(i -> i.BBBB);
+  }
+
+  @Override
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void registerUse(UseRegistry registry) {
+    registry.registerNewUnboxedEnumInstance(getType());
+  }
+
+  public DexType getType() {
+    return BBBB;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder) {
+    builder.addNewUnboxedEnumInstance(AA, getType(), ordinal);
+  }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryKeepRuleGenerator.java b/src/main/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryKeepRuleGenerator.java
index 6386b61..c7c920b 100644
--- a/src/main/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryKeepRuleGenerator.java
+++ b/src/main/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryKeepRuleGenerator.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.references.ArrayReference;
 import com.android.tools.r8.references.ClassReference;
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 3d1a246..49cab56 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -5,7 +5,6 @@
 
 import static com.android.tools.r8.graph.DexCode.FAKE_THIS_PREFIX;
 import static com.android.tools.r8.graph.DexCode.FAKE_THIS_SUFFIX;
-import static org.objectweb.asm.Opcodes.ACC_STATIC;
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.CfVersion;
@@ -896,15 +895,11 @@
 
   private Int2ReferenceSortedMap<FrameType> computeInitialLocals(
       DexType context, DexEncodedMethod method, RewrittenPrototypeDescription protoTypeChanges) {
-    int accessFlags =
-        protoTypeChanges.isEmpty()
-            ? method.accessFlags.modifiedFlags
-            : method.accessFlags.originalFlags;
     Int2ReferenceSortedMap<FrameType> initialLocals = new Int2ReferenceAVLTreeMap<>();
     int index = 0;
     if (method.isInstanceInitializer()) {
       initialLocals.put(index++, FrameType.uninitializedThis());
-    } else if (!MethodAccessFlags.isSet(ACC_STATIC, accessFlags)) {
+    } else if (!method.getAccessFlags().isStatic()) {
       initialLocals.put(index++, FrameType.initialized(context));
     }
     ArgumentInfoCollection argumentsInfo = protoTypeChanges.getArgumentInfoCollection();
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 41dd2c1..40b630c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -237,7 +237,7 @@
     return methodCollection.removeMethod(method);
   }
 
-  public void setDirectMethods(List<DexEncodedMethod> methods) {
+  public void setDirectMethods(Collection<DexEncodedMethod> methods) {
     setDirectMethods(methods.toArray(DexEncodedMethod.EMPTY_ARRAY));
   }
 
@@ -323,6 +323,15 @@
     instanceFields(predicate).forEach(consumer);
   }
 
+  public void forEachStaticField(Consumer<DexEncodedField> consumer) {
+    forEachStaticFieldMatching(alwaysTrue(), consumer);
+  }
+
+  public void forEachStaticFieldMatching(
+      Predicate<DexEncodedField> predicate, Consumer<DexEncodedField> consumer) {
+    staticFields(predicate).forEach(consumer);
+  }
+
   public TraversalContinuation traverseFields(Function<DexEncodedField, TraversalContinuation> fn) {
     for (DexEncodedField field : fields()) {
       if (fn.apply(field).shouldBreak()) {
@@ -366,6 +375,12 @@
     assert verifyNoDuplicateFields();
   }
 
+  public DexEncodedField[] clearStaticFields() {
+    DexEncodedField[] previousFields = staticFields;
+    setStaticFields(DexEncodedField.EMPTY_ARRAY);
+    return previousFields;
+  }
+
   public void removeStaticField(int index) {
     DexEncodedField[] newFields = new DexEncodedField[staticFields.length - 1];
     System.arraycopy(staticFields, 0, newFields, 0, index);
@@ -385,6 +400,10 @@
     assert verifyNoDuplicateFields();
   }
 
+  public void setStaticFields(Collection<DexEncodedField> fields) {
+    setStaticFields(fields.toArray(DexEncodedField.EMPTY_ARRAY));
+  }
+
   public boolean definesStaticField(DexField field) {
     for (DexEncodedField encodedField : staticFields()) {
       if (encodedField.getReference() == field) {
@@ -448,8 +467,10 @@
     assert verifyNoDuplicateFields();
   }
 
-  public void clearInstanceFields() {
+  public DexEncodedField[] clearInstanceFields() {
+    DexEncodedField[] previousFields = instanceFields;
     instanceFields = DexEncodedField.EMPTY_ARRAY;
+    return previousFields;
   }
 
   private boolean verifyCorrectnessOfFieldHolder(DexEncodedField field) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index da8323c..9e1b306 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -401,6 +401,11 @@
       return this;
     }
 
+    public Builder modifyAccessFlags(Consumer<FieldAccessFlags> consumer) {
+      consumer.accept(accessFlags);
+      return this;
+    }
+
     public Builder setAbstractValue(
         AbstractValue abstractValue, AppView<AppInfoWithLiveness> appView) {
       return addBuildConsumer(
@@ -409,6 +414,15 @@
                   .recordFieldHasAbstractValue(fixedUpField, appView, abstractValue));
     }
 
+    public Builder clearAnnotations() {
+      return setAnnotations(DexAnnotationSet.empty());
+    }
+
+    public Builder setAnnotations(DexAnnotationSet annotations) {
+      this.annotations = annotations;
+      return this;
+    }
+
     private Builder addBuildConsumer(Consumer<DexEncodedField> consumer) {
       this.buildConsumer = this.buildConsumer.andThen(consumer);
       return this;
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 36cb33c..0dc90ad 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -222,7 +222,8 @@
     assert defaultInterfaceMethodImplementation == null;
     assert implementation != null;
     assert code != null;
-    assert code == implementation.getCode();
+    // TODO(b/183998768): Once R8 desugars in the enqueuer this should always be invalid code.
+    assert InvalidCode.isInvalidCode(code) || code == implementation.getCode();
     accessFlags.setAbstract();
     removeCode();
     defaultInterfaceMethodImplementation = implementation;
@@ -1588,6 +1589,28 @@
           !isLibraryMethodOverride.isUnknown(), isLibraryMethodOverride);
     }
 
+    public Builder unsetIsLibraryMethodOverride() {
+      this.isLibraryMethodOverride = OptionalBool.UNKNOWN;
+      return this;
+    }
+
+    public Builder clearAnnotations() {
+      return setAnnotations(DexAnnotationSet.empty());
+    }
+
+    public Builder clearParameterAnnotations() {
+      return setParameterAnnotations(ParameterAnnotationsList.empty());
+    }
+
+    public Builder clearAllAnnotations() {
+      return clearAnnotations().clearParameterAnnotations();
+    }
+
+    public Builder setAnnotations(DexAnnotationSet annotations) {
+      this.annotations = annotations;
+      return this;
+    }
+
     public Builder setParameterAnnotations(ParameterAnnotationsList parameterAnnotations) {
       this.parameterAnnotations = parameterAnnotations;
       return this;
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 d7a48c1..25cf31b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -393,6 +393,8 @@
   public final DexType stringBuilderType = createStaticallyKnownType(stringBuilderDescriptor);
   public final DexType stringBufferType = createStaticallyKnownType(stringBufferDescriptor);
 
+  public final DexType javaLangAnnotationRetentionPolicyType =
+      createStaticallyKnownType("Ljava/lang/annotation/RetentionPolicy;");
   public final DexType javaLangReflectArrayType =
       createStaticallyKnownType("Ljava/lang/reflect/Array;");
   public final DexType javaLangSystemType = createStaticallyKnownType(javaLangSystemDescriptor);
@@ -569,6 +571,8 @@
   public final EnumMembers enumMembers = new EnumMembers();
   public final JavaLangReflectArrayMembers javaLangReflectArrayMembers =
       new JavaLangReflectArrayMembers();
+  public final JavaLangAnnotationRetentionPolicyMembers javaLangAnnotationRetentionPolicyMembers =
+      new JavaLangAnnotationRetentionPolicyMembers();
   public final JavaLangSystemMethods javaLangSystemMethods = new JavaLangSystemMethods();
   public final NullPointerExceptionMethods npeMethods = new NullPointerExceptionMethods();
   public final IllegalArgumentExceptionMethods illegalArgumentExceptionMethods =
@@ -1499,6 +1503,15 @@
     }
   }
 
+  public class JavaLangAnnotationRetentionPolicyMembers {
+
+    public final DexField CLASS =
+        createField(
+            javaLangAnnotationRetentionPolicyType, javaLangAnnotationRetentionPolicyType, "CLASS");
+
+    private JavaLangAnnotationRetentionPolicyMembers() {}
+  }
+
   public class JavaLangReflectArrayMembers {
 
     public final DexMethod newInstanceMethodWithDimensions =
@@ -1533,6 +1546,8 @@
     public final DexMethod nameMethod;
     public final DexMethod toString;
     public final DexMethod compareTo;
+    public final DexMethod compareToWithObject =
+        createMethod(enumType, createProto(intType, objectType), "compareTo");
     public final DexMethod equals;
     public final DexMethod hashCode;
 
@@ -2208,33 +2223,28 @@
   }
 
   /**
-   * Tries to find a method name for insertion into the class {@code target} of the form baseName$n,
+   * Tries to find a method name for insertion into the class {@code holder} of the form baseName$n,
    * where {@code baseName} is supplied by the user, and {@code n} is picked to be the first number
    * so that {@code isFresh.apply(method)} returns {@code true}.
    */
-  public DexField createFreshFieldName(DexField template, Predicate<DexField> isFresh) {
-    return internalCreateFreshFieldName(template, null, isFresh);
-  }
-
-  /**
-   * Tries to find a method name for insertion into the class {@code target} of the form
-   * baseName$holder$n, where {@code baseName} and {@code holder} are supplied by the user, and
-   * {@code n} is picked to be the first number so that {@code isFresh.apply(method)} returns {@code
-   * true}.
-   *
-   * @param holder indicates where the method originates from.
-   */
-  public DexField createFreshFieldNameWithHolderSuffix(
-      DexField template, DexType holder, Predicate<DexField> isFresh) {
-    return internalCreateFreshFieldName(template, holder, isFresh);
+  public DexField createFreshFieldNameWithoutHolder(
+      DexType holder, DexType type, String baseName, Predicate<DexField> isFresh) {
+    return internalCreateFreshFieldName(null, holder, type, baseName, isFresh);
   }
 
   private DexField internalCreateFreshFieldName(
-      DexField template, DexType holder, Predicate<DexField> isFresh) {
+      DexType originalHolder,
+      DexType newHolder,
+      DexType type,
+      String baseName,
+      Predicate<DexField> isFresh) {
     return createFreshMember(
-        name -> Optional.of(template.withName(name, this)).filter(isFresh),
-        template.name.toSourceString(),
-        holder);
+        name -> {
+          DexField candidate = createField(newHolder, type, name);
+          return isFresh.test(candidate) ? Optional.of(candidate) : Optional.empty();
+        },
+        baseName,
+        originalHolder);
   }
 
   public DexMethod createClassInitializer(DexType holder) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index d46c103..5b3456c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -206,6 +206,10 @@
     forEachInstanceField(field -> consumer.accept(new ProgramField(this, field)));
   }
 
+  public void forEachProgramStaticField(Consumer<? super ProgramField> consumer) {
+    forEachStaticField(field -> consumer.accept(new ProgramField(this, field)));
+  }
+
   public void forEachProgramMember(Consumer<? super ProgramMember<?, ?>> consumer) {
     forEachProgramField(consumer);
     forEachProgramMethod(consumer);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
index dbc2119..554cadc 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -30,6 +30,10 @@
     return null;
   }
 
+  public ProgramField getProgramField() {
+    return null;
+  }
+
   public boolean isSuccessfulResolution() {
     return false;
   }
@@ -102,6 +106,13 @@
     }
 
     @Override
+    public ProgramField getProgramField() {
+      return resolvedHolder.isProgramClass()
+          ? new ProgramField(resolvedHolder.asProgramClass(), resolvedField)
+          : null;
+    }
+
+    @Override
     public OptionalBool isAccessibleFrom(
         ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
       return AccessControl.isMemberAccessible(this, context, appInfo);
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 457ef38..254f5a4 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -375,6 +375,13 @@
       }
     }
 
+    public void injectInterfaces(
+        DexDefinitionSupplier definitions, DexProgramClass clazz, List<DexClass> newInterfaces) {
+      for (DexClass newInterface : newInterfaces) {
+        populateInstantiatedHierarchy(definitions, newInterface.type, clazz);
+      }
+    }
+
     private void populateInstantiatedHierarchy(DexDefinitionSupplier definitions, DexClass clazz) {
       if (clazz.superType != null) {
         populateInstantiatedHierarchy(definitions, clazz.superType, clazz);
diff --git a/src/main/java/com/android/tools/r8/graph/PrunedItems.java b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
index 7441412..1f8aa68 100644
--- a/src/main/java/com/android/tools/r8/graph/PrunedItems.java
+++ b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
@@ -14,16 +14,22 @@
   private final Set<DexReference> additionalPinnedItems;
   private final Set<DexType> noLongerSyntheticItems;
   private final Set<DexType> removedClasses;
+  private final Set<DexField> removedFields;
+  private final Set<DexMethod> removedMethods;
 
   private PrunedItems(
       DexApplication prunedApp,
       Set<DexReference> additionalPinnedItems,
       Set<DexType> noLongerSyntheticItems,
-      Set<DexType> removedClasses) {
+      Set<DexType> removedClasses,
+      Set<DexField> removedFields,
+      Set<DexMethod> removedMethods) {
     this.prunedApp = prunedApp;
     this.additionalPinnedItems = additionalPinnedItems;
     this.noLongerSyntheticItems = noLongerSyntheticItems;
     this.removedClasses = removedClasses;
+    this.removedFields = removedFields;
+    this.removedMethods = removedMethods;
   }
 
   public static Builder builder() {
@@ -38,6 +44,14 @@
     return removedClasses.isEmpty() && additionalPinnedItems.isEmpty();
   }
 
+  public boolean isRemoved(DexField field) {
+    return removedFields.contains(field);
+  }
+
+  public boolean isRemoved(DexMethod method) {
+    return removedMethods.contains(method);
+  }
+
   public boolean isRemoved(DexType type) {
     return removedClasses.contains(type);
   }
@@ -62,6 +76,10 @@
     return removedClasses;
   }
 
+  public Set<DexMethod> getRemovedMethods() {
+    return removedMethods;
+  }
+
   public static class Builder {
 
     private DexApplication prunedApp;
@@ -69,6 +87,8 @@
     private final Set<DexReference> additionalPinnedItems = Sets.newIdentityHashSet();
     private final Set<DexType> noLongerSyntheticItems = Sets.newIdentityHashSet();
     private final Set<DexType> removedClasses = Sets.newIdentityHashSet();
+    private final Set<DexField> removedFields = Sets.newIdentityHashSet();
+    private final Set<DexMethod> removedMethods = Sets.newIdentityHashSet();
 
     public Builder setPrunedApp(DexApplication prunedApp) {
       this.prunedApp = prunedApp;
@@ -92,9 +112,24 @@
       return this;
     }
 
+    public Builder addRemovedField(DexField removedField) {
+      removedFields.add(removedField);
+      return this;
+    }
+
+    public Builder addRemovedMethod(DexMethod removedMethod) {
+      removedMethods.add(removedMethod);
+      return this;
+    }
+
     public PrunedItems build() {
       return new PrunedItems(
-          prunedApp, additionalPinnedItems, noLongerSyntheticItems, removedClasses);
+          prunedApp,
+          additionalPinnedItems,
+          noLongerSyntheticItems,
+          removedClasses,
+          removedFields,
+          removedMethods);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 708a02f..c9c0bc9 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -55,6 +55,10 @@
     registerTypeReference(type);
   }
 
+  public void registerNewUnboxedEnumInstance(DexType type) {
+    registerTypeReference(type);
+  }
+
   public abstract void registerStaticFieldRead(DexField field);
 
   public void registerStaticFieldReadFromMethodHandle(DexField field) {
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
index ee41edb..28d6be7 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
@@ -8,8 +8,8 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter.Mode;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.function.Consumer;
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index d1e98c7..db0178c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -312,8 +312,8 @@
 
   void mergeStaticFields() {
     group.forEachSource(classStaticFieldsMerger::addFields);
-    classStaticFieldsMerger.merge(group.getTarget());
-    group.forEachSource(clazz -> clazz.setStaticFields(null));
+    classStaticFieldsMerger.merge();
+    group.forEachSource(DexClass::clearStaticFields);
   }
 
   public void mergeGroup(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java
index 80180f6..f94d407 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java
@@ -14,36 +14,41 @@
 import java.util.Map;
 
 public class ClassStaticFieldsMerger {
-  private final Builder lensBuilder;
-  private final MergeGroup group;
-  private final Map<DexField, DexEncodedField> targetFields = new LinkedHashMap<>();
+
   private final DexItemFactory dexItemFactory;
+  private final MergeGroup group;
+  private final Builder lensBuilder;
+
+  private final Map<DexField, DexEncodedField> targetFields = new LinkedHashMap<>();
 
   public ClassStaticFieldsMerger(
       AppView<?> appView, HorizontalClassMergerGraphLens.Builder lensBuilder, MergeGroup group) {
-    this.lensBuilder = lensBuilder;
-
-    this.group = group;
-    // Add mappings for all target fields.
-    group
-        .getTarget()
-        .staticFields()
-        .forEach(field -> targetFields.put(field.getReference(), field));
-
     this.dexItemFactory = appView.dexItemFactory();
+    this.group = group;
+    this.lensBuilder = lensBuilder;
   }
 
   private boolean isFresh(DexField fieldReference) {
-    return !targetFields.containsKey(fieldReference);
+    if (group.getTarget().lookupField(fieldReference) != null) {
+      // The target class has an instance or static field with the given reference.
+      return false;
+    }
+    if (targetFields.containsKey(fieldReference)) {
+      // We have already committed another static field from a source class in the merge group to
+      // the given field reference (but the field is not yet added to the target class).
+      return false;
+    }
+    return true;
   }
 
   private void addField(DexEncodedField field) {
     DexField oldFieldReference = field.getReference();
-    DexField templateReference =
-        field.getReference().withHolder(group.getTarget().getType(), dexItemFactory);
     DexField newFieldReference =
-        dexItemFactory.createFreshFieldNameWithHolderSuffix(
-            templateReference, field.getHolderType(), this::isFresh);
+        dexItemFactory.createFreshFieldNameWithoutHolder(
+            group.getTarget().getType(),
+            field.getType(),
+            field.getName().toString(),
+            this::isFresh);
 
     field = field.toTypeSubstitutedField(newFieldReference);
     targetFields.put(newFieldReference, field);
@@ -52,10 +57,10 @@
   }
 
   public void addFields(DexProgramClass toMerge) {
-    toMerge.staticFields().forEach(this::addField);
+    toMerge.forEachStaticField(this::addField);
   }
 
-  public void merge(DexProgramClass clazz) {
-    clazz.setStaticFields(targetFields.values().toArray(DexEncodedField.EMPTY_ARRAY));
+  public void merge() {
+    group.getTarget().appendStaticFields(targetFields.values());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
index 20eb5a3..2b12d6d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -5,7 +5,9 @@
 package com.android.tools.r8.horizontalclassmerging;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.NestedGraphLens;
@@ -15,6 +17,7 @@
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Streams;
@@ -83,18 +86,21 @@
 
   public static class Builder {
 
-    private final MutableBidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap =
-        BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
-    private final BidirectionalManyToOneHashMap<DexMethod, DexMethod> methodMap =
+    private final MutableBidirectionalManyToOneRepresentativeMap<DexField, DexField>
+        newFieldSignatures = BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
+    private final MutableBidirectionalManyToOneMap<DexMethod, DexMethod> methodMap =
         BidirectionalManyToOneHashMap.newIdentityHashMap();
-    private final BidirectionalManyToOneRepresentativeHashMap<DexMethod, DexMethod>
+    private final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod>
         newMethodSignatures = BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
     private final Map<DexMethod, List<ExtraParameter>> methodExtraParameters =
         new IdentityHashMap<>();
 
-    private final BidirectionalManyToOneHashMap<DexMethod, DexMethod> pendingMethodMapUpdates =
+    private final MutableBidirectionalManyToOneMap<DexMethod, DexMethod> pendingMethodMapUpdates =
         BidirectionalManyToOneHashMap.newIdentityHashMap();
-    private final BidirectionalManyToOneRepresentativeHashMap<DexMethod, DexMethod>
+    private final MutableBidirectionalManyToOneRepresentativeMap<DexField, DexField>
+        pendingNewFieldSignatureUpdates =
+            BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
+    private final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod>
         pendingNewMethodSignatureUpdates =
             BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
 
@@ -103,6 +109,7 @@
     HorizontalClassMergerGraphLens build(
         AppView<?> appView, HorizontallyMergedClasses mergedClasses) {
       assert pendingMethodMapUpdates.isEmpty();
+      assert pendingNewFieldSignatureUpdates.isEmpty();
       assert pendingNewMethodSignatureUpdates.isEmpty();
       assert newMethodSignatures.values().stream()
           .allMatch(
@@ -115,7 +122,7 @@
           appView,
           mergedClasses,
           methodExtraParameters,
-          fieldMap,
+          newFieldSignatures,
           methodMap.getForwardMap(),
           newMethodSignatures);
     }
@@ -126,7 +133,7 @@
     }
 
     void recordNewFieldSignature(DexField oldFieldSignature, DexField newFieldSignature) {
-      fieldMap.put(oldFieldSignature, newFieldSignature);
+      newFieldSignatures.put(oldFieldSignature, newFieldSignature);
     }
 
     void recordNewFieldSignature(
@@ -135,26 +142,20 @@
         DexField representative) {
       assert Streams.stream(oldFieldSignatures)
           .anyMatch(oldFieldSignature -> oldFieldSignature != newFieldSignature);
-      assert Streams.stream(oldFieldSignatures).noneMatch(fieldMap::containsValue);
+      assert Streams.stream(oldFieldSignatures).noneMatch(newFieldSignatures::containsValue);
       assert Iterables.contains(oldFieldSignatures, representative);
       for (DexField oldFieldSignature : oldFieldSignatures) {
         recordNewFieldSignature(oldFieldSignature, newFieldSignature);
       }
-      fieldMap.setRepresentative(newFieldSignature, representative);
+      newFieldSignatures.setRepresentative(newFieldSignature, representative);
     }
 
     void fixupField(DexField oldFieldSignature, DexField newFieldSignature) {
-      DexField representative = fieldMap.removeRepresentativeFor(oldFieldSignature);
-      Set<DexField> originalFieldSignatures = fieldMap.removeValue(oldFieldSignature);
-      if (originalFieldSignatures.isEmpty()) {
-        fieldMap.put(oldFieldSignature, newFieldSignature);
-      } else if (originalFieldSignatures.size() == 1) {
-        fieldMap.put(originalFieldSignatures, newFieldSignature);
-      } else {
-        assert representative != null;
-        fieldMap.put(originalFieldSignatures, newFieldSignature);
-        fieldMap.setRepresentative(newFieldSignature, representative);
-      }
+      fixupOriginalMemberSignatures(
+          oldFieldSignature,
+          newFieldSignature,
+          newFieldSignatures,
+          pendingNewFieldSignatureUpdates);
     }
 
     void mapMethod(DexMethod oldMethodSignature, DexMethod newMethodSignature) {
@@ -195,7 +196,11 @@
 
     void fixupMethod(DexMethod oldMethodSignature, DexMethod newMethodSignature) {
       fixupMethodMap(oldMethodSignature, newMethodSignature);
-      fixupOriginalMethodSignatures(oldMethodSignature, newMethodSignature);
+      fixupOriginalMemberSignatures(
+          oldMethodSignature,
+          newMethodSignature,
+          newMethodSignatures,
+          pendingNewMethodSignatureUpdates);
     }
 
     private void fixupMethodMap(DexMethod oldMethodSignature, DexMethod newMethodSignature) {
@@ -209,18 +214,22 @@
       }
     }
 
-    private void fixupOriginalMethodSignatures(
-        DexMethod oldMethodSignature, DexMethod newMethodSignature) {
-      Set<DexMethod> oldMethodSignatures = newMethodSignatures.getKeys(oldMethodSignature);
-      if (oldMethodSignatures.isEmpty()) {
-        pendingNewMethodSignatureUpdates.put(oldMethodSignature, newMethodSignature);
+    private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+        void fixupOriginalMemberSignatures(
+            R oldMemberSignature,
+            R newMemberSignature,
+            MutableBidirectionalManyToOneRepresentativeMap<R, R> newMemberSignatures,
+            MutableBidirectionalManyToOneRepresentativeMap<R, R> pendingNewMemberSignatureUpdates) {
+      Set<R> oldMemberSignatures = newMemberSignatures.getKeys(oldMemberSignature);
+      if (oldMemberSignatures.isEmpty()) {
+        pendingNewMemberSignatureUpdates.put(oldMemberSignature, newMemberSignature);
       } else {
-        for (DexMethod originalMethodSignature : oldMethodSignatures) {
-          pendingNewMethodSignatureUpdates.put(originalMethodSignature, newMethodSignature);
+        for (R originalMethodSignature : oldMemberSignatures) {
+          pendingNewMemberSignatureUpdates.put(originalMethodSignature, newMemberSignature);
         }
-        DexMethod representative = newMethodSignatures.getRepresentativeKey(oldMethodSignature);
+        R representative = newMemberSignatures.getRepresentativeKey(oldMemberSignature);
         if (representative != null) {
-          pendingNewMethodSignatureUpdates.setRepresentative(newMethodSignature, representative);
+          pendingNewMemberSignatureUpdates.setRepresentative(newMemberSignature, representative);
         }
       }
     }
@@ -231,16 +240,24 @@
       pendingMethodMapUpdates.forEachManyToOneMapping(methodMap::put);
       pendingMethodMapUpdates.clear();
 
-      // Commit pending original method signatures updates.
-      newMethodSignatures.removeAll(pendingNewMethodSignatureUpdates.keySet());
-      pendingNewMethodSignatureUpdates.forEachManyToOneMapping(
+      // Commit pending original field and method signatures updates.
+      commitPendingNewMemberSignatureUpdates(newFieldSignatures, pendingNewFieldSignatureUpdates);
+      commitPendingNewMemberSignatureUpdates(newMethodSignatures, pendingNewMethodSignatureUpdates);
+    }
+
+    private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+        void commitPendingNewMemberSignatureUpdates(
+            MutableBidirectionalManyToOneRepresentativeMap<R, R> newMemberSignatures,
+            MutableBidirectionalManyToOneRepresentativeMap<R, R> pendingNewMemberSignatureUpdates) {
+      newMemberSignatures.removeAll(pendingNewMemberSignatureUpdates.keySet());
+      pendingNewMemberSignatureUpdates.forEachManyToOneMapping(
           (keys, value, representative) -> {
-            newMethodSignatures.put(keys, value);
+            newMemberSignatures.put(keys, value);
             if (keys.size() > 1) {
-              newMethodSignatures.setRepresentative(value, representative);
+              newMemberSignatures.setRepresentative(value, representative);
             }
           });
-      pendingNewMethodSignatureUpdates.clear();
+      pendingNewMemberSignatureUpdates.clear();
     }
 
     /**
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index 4abddc6..89dcd0a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -150,12 +150,11 @@
       RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
     ImmutableList.Builder<Policy> builder = ImmutableList.builder();
 
-    addRequiredMultiClassPolicies(appView, mode, builder);
+    addRequiredMultiClassPolicies(appView, mode, runtimeTypeCheckInfo, builder);
 
     if (!appView.options().horizontalClassMergerOptions().isRestrictedToSynthetics()) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      addMultiClassPoliciesForMergingNonSyntheticClasses(
-          appViewWithLiveness, runtimeTypeCheckInfo, builder);
+      addMultiClassPoliciesForMergingNonSyntheticClasses(appViewWithLiveness, builder);
     }
 
     if (mode.isInitial()) {
@@ -189,6 +188,7 @@
   private static void addRequiredMultiClassPolicies(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       Mode mode,
+      RuntimeTypeCheckInfo runtimeTypeCheckInfo,
       ImmutableList.Builder<Policy> builder) {
     builder.add(
         new CheckAbstractClasses(appView),
@@ -201,15 +201,14 @@
         new SyntheticItemsPolicy(appView, mode),
         new RespectPackageBoundaries(appView),
         new NoDifferentApiReferenceLevel(appView),
+        new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
         new PreventClassMethodAndDefaultMethodCollisions(appView));
   }
 
   private static void addMultiClassPoliciesForMergingNonSyntheticClasses(
       AppView<AppInfoWithLiveness> appView,
-      RuntimeTypeCheckInfo runtimeTypeCheckInfo,
       ImmutableList.Builder<Policy> builder) {
-    builder.add(
-        new NoDeadLocks(appView), new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo));
+    builder.add(new NoDeadLocks(appView));
   }
 
   private static void addMultiClassPoliciesForInterfaceMerging(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index 442489d..b85eab3 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass.FieldSetter;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -22,6 +21,7 @@
 import com.android.tools.r8.graph.TreeFixerBase;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.shaking.AnnotationFixer;
+import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.OptionalBool;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
@@ -168,8 +168,11 @@
         .getMethodCollection()
         .replaceAllDirectMethods(method -> fixupDirectMethod(newMethodReferences, method));
 
-    fixupFields(clazz.staticFields(), clazz::setStaticField);
-    fixupFields(clazz.instanceFields(), clazz::setInstanceField);
+    Set<DexField> newFieldReferences = Sets.newIdentityHashSet();
+    DexEncodedField[] instanceFields = clazz.clearInstanceFields();
+    DexEncodedField[] staticFields = clazz.clearStaticFields();
+    clazz.setInstanceFields(fixupFields(instanceFields, newFieldReferences));
+    clazz.setStaticFields(fixupFields(staticFields, newFieldReferences));
 
     lensBuilder.commitPendingUpdates();
 
@@ -219,8 +222,13 @@
         .getMethodCollection()
         .replaceDirectMethods(method -> fixupDirectMethod(newDirectMethods, method));
     iface.getMethodCollection().replaceVirtualMethods(this::fixupVirtualInterfaceMethod);
-    fixupFields(iface.staticFields(), iface::setStaticField);
-    fixupFields(iface.instanceFields(), iface::setInstanceField);
+
+    assert !iface.hasInstanceFields();
+
+    Set<DexField> newFieldReferences = Sets.newIdentityHashSet();
+    DexEncodedField[] staticFields = iface.clearStaticFields();
+    iface.setStaticFields(fixupFields(staticFields, newFieldReferences));
+
     lensBuilder.commitPendingUpdates();
   }
 
@@ -371,35 +379,40 @@
     return fixupProgramMethod(newMethodReference, method);
   }
 
-  private void fixupFields(List<DexEncodedField> fields, FieldSetter setter) {
-    if (fields == null) {
-      return;
+  private DexEncodedField[] fixupFields(
+      DexEncodedField[] fields, Set<DexField> newFieldReferences) {
+    if (fields == null || ArrayUtils.isEmpty(fields)) {
+      return DexEncodedField.EMPTY_ARRAY;
     }
-    Set<DexField> existingFields = Sets.newIdentityHashSet();
 
-    for (int i = 0; i < fields.size(); i++) {
-      DexEncodedField oldField = fields.get(i);
+    DexEncodedField[] newFields = new DexEncodedField[fields.length];
+    for (int i = 0; i < fields.length; i++) {
+      DexEncodedField oldField = fields[i];
       DexField oldFieldReference = oldField.getReference();
       DexField newFieldReference = fixupFieldReference(oldFieldReference);
 
       // Rename the field if it already exists.
-      if (!existingFields.add(newFieldReference)) {
+      if (!newFieldReferences.add(newFieldReference)) {
         DexField template = newFieldReference;
         newFieldReference =
             dexItemFactory.createFreshMember(
                 tryName ->
                     Optional.of(template.withName(tryName, dexItemFactory))
-                        .filter(tryMethod -> !existingFields.contains(tryMethod)),
+                        .filter(tryMethod -> !newFieldReferences.contains(tryMethod)),
                 newFieldReference.name.toSourceString());
-        boolean added = existingFields.add(newFieldReference);
+        boolean added = newFieldReferences.add(newFieldReference);
         assert added;
       }
 
       if (newFieldReference != oldFieldReference) {
         lensBuilder.fixupField(oldFieldReference, newFieldReference);
-        setter.setField(i, oldField.toTypeSubstitutedField(newFieldReference));
+        newFields[i] = oldField.toTypeSubstitutedField(newFieldReference);
+      } else {
+        newFields[i] = oldField;
       }
     }
+
+    return newFields;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java
index eb26afa..c791889 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.NewUnboxedEnumInstance;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -153,5 +154,11 @@
       markInitializedOnNormalExit(instruction.clazz);
       return null;
     }
+
+    @Override
+    public Void visit(NewUnboxedEnumInstance instruction) {
+      assert false;
+      return null;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index 2cf0214..d870dc7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -23,6 +24,8 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeNewArray;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.LogicalBinop;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.NewInstance;
@@ -77,10 +80,12 @@
 
   private final AppView<?> appView;
   private final ProgramMethod context;
+  private final DexItemFactory dexItemFactory;
 
   public ValueMayDependOnEnvironmentAnalysis(AppView<?> appView, IRCode code) {
     this.appView = appView;
     this.context = code.context();
+    this.dexItemFactory = appView.dexItemFactory();
   }
 
   public boolean anyValueMayDependOnEnvironment(Iterable<Value> values) {
@@ -146,6 +151,10 @@
     return addConstantValueToValueGraph(value)
         || addArrayValueToValueGraph(
             value, node, graph, consumedInstructions, mutableValues, worklist)
+        || addInvokeVirtualValueToValueGraph(
+            value, node, graph, consumedInstructions, mutableValues, worklist)
+        || addLogicalBinopValueToValueGraph(
+            value, node, graph, consumedInstructions, mutableValues, worklist)
         || addNewInstanceValueToValueGraph(
             value, node, graph, consumedInstructions, mutableValues, worklist);
   }
@@ -237,6 +246,54 @@
     return true;
   }
 
+  private boolean addInvokeVirtualValueToValueGraph(
+      Value value,
+      Node node,
+      ValueGraph graph,
+      Set<Instruction> consumedInstructions,
+      Set<Value> mutableValues,
+      WorkList<Value> worklist) {
+    if (!value.isDefinedByInstructionSatisfying(Instruction::isInvokeVirtual)) {
+      return false;
+    }
+
+    InvokeVirtual invoke = value.getDefinition().asInvokeVirtual();
+    if (invoke.getInvokedMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
+      // We treat the value from calling MyClass.class.desiredAssertionStatus() as being independent
+      // of the environment if MyClass is not pinned.
+      return isNonPinnedClassConstant(invoke.getReceiver());
+    }
+
+    return false;
+  }
+
+  private boolean isNonPinnedClassConstant(Value value) {
+    Value root = value.getAliasedValue();
+    return root.isDefinedByInstructionSatisfying(Instruction::isConstClass)
+        && !appView.getKeepInfo().isPinned(root.getDefinition().asConstClass().getType(), appView);
+  }
+
+  private boolean addLogicalBinopValueToValueGraph(
+      Value value,
+      Node node,
+      ValueGraph graph,
+      Set<Instruction> consumedInstructions,
+      Set<Value> mutableValues,
+      WorkList<Value> worklist) {
+    if (!value.isDefinedByInstructionSatisfying(Instruction::isLogicalBinop)) {
+      return false;
+    }
+
+    // The result of a logical binop depends on the environment if any of the operands does.
+    LogicalBinop logicalBinop = value.getDefinition().asLogicalBinop();
+    for (Value inValue : logicalBinop.inValues()) {
+      graph.addDirectedEdge(node, graph.createNodeIfAbsent(inValue));
+      worklist.addIfNotSeen(inValue);
+    }
+
+    return true;
+  }
+
   private boolean addNewInstanceValueToValueGraph(
       Value value,
       Node node,
@@ -255,8 +312,7 @@
     }
 
     // Find the single constructor invocation.
-    InvokeDirect constructorInvoke =
-        newInstance.getUniqueConstructorInvoke(appView.dexItemFactory());
+    InvokeDirect constructorInvoke = newInstance.getUniqueConstructorInvoke(dexItemFactory);
     if (constructorInvoke == null || constructorInvoke.getInvokedMethod().holder != clazz.type) {
       // Didn't find a (valid) constructor invocation, give up.
       return false;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java
index 7d13544..a5977df 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
 
 /**
  * Implements a lifted subset lattice for fields.
@@ -70,5 +71,6 @@
     return lessThanOrEqual(other) && !equals(other);
   }
 
-  public abstract AbstractFieldSet rewrittenWithLens(AppView<?> appView, GraphLens lens);
+  public abstract AbstractFieldSet rewrittenWithLens(
+      AppView<?> appView, GraphLens lens, PrunedItems prunedItems);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
index f84e13d..cdb9eb9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
@@ -69,10 +70,14 @@
   }
 
   @Override
-  public AbstractFieldSet rewrittenWithLens(AppView<?> appView, GraphLens lens) {
+  public AbstractFieldSet rewrittenWithLens(
+      AppView<?> appView, GraphLens lens, PrunedItems prunedItems) {
     assert !isEmpty();
     ConcreteMutableFieldSet rewrittenSet = new ConcreteMutableFieldSet();
     for (DexEncodedField field : fields) {
+      if (prunedItems.isRemoved(field.getReference())) {
+        continue;
+      }
       DexField rewrittenFieldReference = lens.lookupField(field.getReference());
       DexClass holder = appView.definitionForHolder(rewrittenFieldReference);
       DexEncodedField rewrittenField = rewrittenFieldReference.lookupOnClass(holder);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java
index 9a7c7aa..d5d694f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
 
 public class EmptyFieldSet extends AbstractFieldSet implements KnownFieldSet {
 
@@ -39,7 +40,8 @@
   }
 
   @Override
-  public AbstractFieldSet rewrittenWithLens(AppView<?> appView, GraphLens lens) {
+  public AbstractFieldSet rewrittenWithLens(
+      AppView<?> appView, GraphLens lens, PrunedItems prunedItems) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java
index 75a1bab..32ddba9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
 
 public class UnknownFieldSet extends AbstractFieldSet {
 
@@ -34,7 +35,8 @@
   }
 
   @Override
-  public AbstractFieldSet rewrittenWithLens(AppView<?> appView, GraphLens lens) {
+  public AbstractFieldSet rewrittenWithLens(
+      AppView<?> appView, GraphLens lens, PrunedItems prunedItems) {
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/AlwaysSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/AlwaysSimpleInliningConstraint.java
index a164607..93cdb16 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/AlwaysSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/AlwaysSimpleInliningConstraint.java
@@ -36,7 +36,8 @@
   }
 
   @Override
-  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
+      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java
index 351d51d..2c88a60 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java
@@ -41,7 +41,8 @@
   }
 
   @Override
-  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
+      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java
index bf3fde3..46c29b0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java
@@ -41,7 +41,8 @@
   }
 
   @Override
-  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
+      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToNumberSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToNumberSimpleInliningConstraint.java
new file mode 100644
index 0000000..a321670
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/EqualToNumberSimpleInliningConstraint.java
@@ -0,0 +1,46 @@
+// 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.analysis.inlining;
+
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+public class EqualToNumberSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
+
+  private final long rawValue;
+
+  private EqualToNumberSimpleInliningConstraint(int argumentIndex, long rawValue) {
+    super(argumentIndex);
+    this.rawValue = rawValue;
+  }
+
+  static EqualToNumberSimpleInliningConstraint create(
+      int argumentIndex, long rawValue, SimpleInliningConstraintFactory witness) {
+    assert witness != null;
+    return new EqualToNumberSimpleInliningConstraint(argumentIndex, rawValue);
+  }
+
+  @Override
+  public boolean isSatisfied(InvokeMethod invoke) {
+    Value argumentRoot = getArgument(invoke).getAliasedValue();
+    return argumentRoot.isDefinedByInstructionSatisfying(Instruction::isConstNumber)
+        && argumentRoot.getDefinition().asConstNumber().getRawValue() == rawValue;
+  }
+
+  @Override
+  public SimpleInliningConstraint fixupAfterRemovingThisParameter(
+      SimpleInliningConstraintFactory factory) {
+    assert getArgumentIndex() > 0;
+    return factory.createNumberConstraint(getArgumentIndex() - 1, rawValue);
+  }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
+      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NeverSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NeverSimpleInliningConstraint.java
index 4568e5f..809dbb9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NeverSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NeverSimpleInliningConstraint.java
@@ -35,7 +35,8 @@
   }
 
   @Override
-  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
+      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotEqualToNumberSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotEqualToNumberSimpleInliningConstraint.java
new file mode 100644
index 0000000..4739231
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotEqualToNumberSimpleInliningConstraint.java
@@ -0,0 +1,46 @@
+// 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.analysis.inlining;
+
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+public class NotEqualToNumberSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
+
+  private final long rawValue;
+
+  private NotEqualToNumberSimpleInliningConstraint(int argumentIndex, long rawValue) {
+    super(argumentIndex);
+    this.rawValue = rawValue;
+  }
+
+  static NotEqualToNumberSimpleInliningConstraint create(
+      int argumentIndex, long rawValue, SimpleInliningConstraintFactory witness) {
+    assert witness != null;
+    return new NotEqualToNumberSimpleInliningConstraint(argumentIndex, rawValue);
+  }
+
+  @Override
+  public boolean isSatisfied(InvokeMethod invoke) {
+    Value argumentRoot = getArgument(invoke).getAliasedValue();
+    return argumentRoot.isDefinedByInstructionSatisfying(Instruction::isConstNumber)
+        && argumentRoot.getDefinition().asConstNumber().getRawValue() != rawValue;
+  }
+
+  @Override
+  public SimpleInliningConstraint fixupAfterRemovingThisParameter(
+      SimpleInliningConstraintFactory factory) {
+    assert getArgumentIndex() > 0;
+    return factory.createNotNumberConstraint(getArgumentIndex() - 1, rawValue);
+  }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
+      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java
index 0834cba..00288f8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java
@@ -41,10 +41,10 @@
   }
 
   @Override
-  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
+      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
     if (unboxedArgumentIndices.contains(getArgumentIndex())) {
-      // TODO(b/176067541): Could be refined to an argument-equals-int constraint.
-      return NeverSimpleInliningConstraint.getInstance();
+      return factory.createNotNumberConstraint(getArgumentIndex(), 0);
     }
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java
index 19607ee..5a0d3cf 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java
@@ -45,10 +45,10 @@
   }
 
   @Override
-  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
+      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
     if (unboxedArgumentIndices.contains(getArgumentIndex())) {
-      // TODO(b/176067541): Could be refined to an argument-equals-int constraint.
-      return NeverSimpleInliningConstraint.getInstance();
+      return factory.createNumberConstraint(getArgumentIndex(), 0);
     }
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
index df108ac..82c3f7d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
@@ -110,5 +110,5 @@
       SimpleInliningConstraintFactory factory);
 
   public abstract SimpleInliningConstraint rewrittenWithUnboxedArguments(
-      IntList unboxedArgumentIndices);
+      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
index 1a7e997..6d79b82 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
+import java.util.OptionalLong;
 import java.util.Set;
 
 /**
@@ -105,43 +106,46 @@
     switch (instruction.opcode()) {
       case IF:
         If ifInstruction = instruction.asIf();
-        if (ifInstruction.isZeroTest()) {
-          Value lhs = ifInstruction.lhs().getAliasedValue();
-          if (lhs.isArgument() && !lhs.isThis()) {
-            int argumentIndex = lhs.getDefinition().asArgument().getIndex();
-            DexType argumentType = method.getDefinition().getArgumentType(argumentIndex);
-            int currentDepth = instructionDepth;
-
-            // Compute the constraint for which paths through the true target are guaranteed to exit
-            // early.
-            SimpleInliningConstraint trueTargetConstraint =
-                computeConstraintFromIfZeroTest(
-                        argumentIndex, argumentType, ifInstruction.getType())
-                    // Only recurse into the true target if the constraint from the if-instruction
-                    // is not 'never'.
-                    .lazyMeet(
-                        () ->
-                            analyzeInstructionsInBlock(
-                                ifInstruction.getTrueTarget(), currentDepth));
-
-            // Compute the constraint for which paths through the false target are guaranteed to
-            // exit early.
-            SimpleInliningConstraint fallthroughTargetConstraint =
-                computeConstraintFromIfZeroTest(
-                        argumentIndex, argumentType, ifInstruction.getType().inverted())
-                    // Only recurse into the false target if the constraint from the if-instruction
-                    // is not 'never'.
-                    .lazyMeet(
-                        () ->
-                            analyzeInstructionsInBlock(
-                                ifInstruction.fallthroughBlock(), currentDepth));
-
-            // Paths going through this basic block are guaranteed to exit early if the true target
-            // is guaranteed to exit early or the false target is.
-            return trueTargetConstraint.join(fallthroughTargetConstraint);
-          }
+        Value singleArgumentOperand = getSingleArgumentOperand(ifInstruction);
+        if (singleArgumentOperand == null || singleArgumentOperand.isThis()) {
+          break;
         }
-        break;
+
+        Value otherOperand =
+            ifInstruction.isZeroTest()
+                ? null
+                : ifInstruction.getOperand(
+                    1 - ifInstruction.inValues().indexOf(singleArgumentOperand));
+
+        int argumentIndex =
+            singleArgumentOperand.getAliasedValue().getDefinition().asArgument().getIndex();
+        DexType argumentType = method.getDefinition().getArgumentType(argumentIndex);
+        int currentDepth = instructionDepth;
+
+        // Compute the constraint for which paths through the true target are guaranteed to exit
+        // early.
+        SimpleInliningConstraint trueTargetConstraint =
+            computeConstraintFromIfTest(
+                    argumentIndex, argumentType, otherOperand, ifInstruction.getType())
+                // Only recurse into the true target if the constraint from the if-instruction
+                // is not 'never'.
+                .lazyMeet(
+                    () -> analyzeInstructionsInBlock(ifInstruction.getTrueTarget(), currentDepth));
+
+        // Compute the constraint for which paths through the false target are guaranteed to
+        // exit early.
+        SimpleInliningConstraint fallthroughTargetConstraint =
+            computeConstraintFromIfTest(
+                    argumentIndex, argumentType, otherOperand, ifInstruction.getType().inverted())
+                // Only recurse into the false target if the constraint from the if-instruction
+                // is not 'never'.
+                .lazyMeet(
+                    () ->
+                        analyzeInstructionsInBlock(ifInstruction.fallthroughBlock(), currentDepth));
+
+        // Paths going through this basic block are guaranteed to exit early if the true target
+        // is guaranteed to exit early or the false target is.
+        return trueTargetConstraint.join(fallthroughTargetConstraint);
 
       case GOTO:
         return analyzeInstructionsInBlock(instruction.asGoto().getTarget(), instructionDepth);
@@ -162,24 +166,39 @@
     return NeverSimpleInliningConstraint.getInstance();
   }
 
-  private SimpleInliningConstraint computeConstraintFromIfZeroTest(
-      int argumentIndex, DexType argumentType, If.Type type) {
+  private SimpleInliningConstraint computeConstraintFromIfTest(
+      int argumentIndex, DexType argumentType, Value otherOperand, If.Type type) {
+    boolean isZeroTest = otherOperand == null;
     switch (type) {
       case EQ:
-        if (argumentType.isReferenceType()) {
-          return factory.createNullConstraint(argumentIndex);
-        }
-        if (argumentType.isBooleanType()) {
-          return factory.createBooleanFalseConstraint(argumentIndex);
+        if (isZeroTest) {
+          if (argumentType.isReferenceType()) {
+            return factory.createNullConstraint(argumentIndex);
+          }
+          if (argumentType.isBooleanType()) {
+            return factory.createBooleanFalseConstraint(argumentIndex);
+          }
+        } else if (argumentType.isPrimitiveType()) {
+          OptionalLong rawValue = getRawNumberValue(otherOperand);
+          if (rawValue.isPresent()) {
+            return factory.createNumberConstraint(argumentIndex, rawValue.getAsLong());
+          }
         }
         return NeverSimpleInliningConstraint.getInstance();
 
       case NE:
-        if (argumentType.isReferenceType()) {
-          return factory.createNotNullConstraint(argumentIndex);
-        }
-        if (argumentType.isBooleanType()) {
-          return factory.createBooleanTrueConstraint(argumentIndex);
+        if (isZeroTest) {
+          if (argumentType.isReferenceType()) {
+            return factory.createNotNullConstraint(argumentIndex);
+          }
+          if (argumentType.isBooleanType()) {
+            return factory.createBooleanTrueConstraint(argumentIndex);
+          }
+        } else if (argumentType.isPrimitiveType()) {
+          OptionalLong rawValue = getRawNumberValue(otherOperand);
+          if (rawValue.isPresent()) {
+            return factory.createNotNumberConstraint(argumentIndex, rawValue.getAsLong());
+          }
         }
         return NeverSimpleInliningConstraint.getInstance();
 
@@ -187,4 +206,33 @@
         return NeverSimpleInliningConstraint.getInstance();
     }
   }
+
+  private OptionalLong getRawNumberValue(Value value) {
+    Value root = value.getAliasedValue();
+    if (root.isDefinedByInstructionSatisfying(Instruction::isConstNumber)) {
+      return OptionalLong.of(root.getDefinition().asConstNumber().getRawValue());
+    }
+    return OptionalLong.empty();
+  }
+
+  private Value getSingleArgumentOperand(If ifInstruction) {
+    Value singleArgumentOperand = null;
+
+    Value lhs = ifInstruction.lhs();
+    if (lhs.getAliasedValue().isArgument()) {
+      singleArgumentOperand = lhs;
+    }
+
+    if (!ifInstruction.isZeroTest()) {
+      Value rhs = ifInstruction.rhs();
+      if (rhs.getAliasedValue().isArgument()) {
+        if (singleArgumentOperand != null) {
+          return null;
+        }
+        singleArgumentOperand = rhs;
+      }
+    }
+
+    return singleArgumentOperand;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintConjunction.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintConjunction.java
index c993b12..ae2e07b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintConjunction.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintConjunction.java
@@ -77,11 +77,12 @@
   }
 
   @Override
-  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
+      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
     List<SimpleInliningConstraint> rewrittenConstraints =
         ListUtils.mapOrElse(
             constraints,
-            constraint -> constraint.rewrittenWithUnboxedArguments(unboxedArgumentIndices),
+            constraint -> constraint.rewrittenWithUnboxedArguments(unboxedArgumentIndices, factory),
             null);
     return rewrittenConstraints != null
         ? new SimpleInliningConstraintConjunction(rewrittenConstraints)
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintDisjunction.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintDisjunction.java
index a6b418d..42bcaf6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintDisjunction.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintDisjunction.java
@@ -77,11 +77,12 @@
   }
 
   @Override
-  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(
+      IntList unboxedArgumentIndices, SimpleInliningConstraintFactory factory) {
     List<SimpleInliningConstraint> rewrittenConstraints =
         ListUtils.mapOrElse(
             constraints,
-            constraint -> constraint.rewrittenWithUnboxedArguments(unboxedArgumentIndices),
+            constraint -> constraint.rewrittenWithUnboxedArguments(unboxedArgumentIndices, factory),
             null);
     return rewrittenConstraints != null
         ? new SimpleInliningConstraintDisjunction(rewrittenConstraints)
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java
index 99603e8..ed3cd55 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java
@@ -77,6 +77,16 @@
         () -> NullSimpleInliningConstraint.create(argumentIndex, this));
   }
 
+  public NotEqualToNumberSimpleInliningConstraint createNotNumberConstraint(
+      int argumentIndex, long rawValue) {
+    return NotEqualToNumberSimpleInliningConstraint.create(argumentIndex, rawValue, this);
+  }
+
+  public EqualToNumberSimpleInliningConstraint createNumberConstraint(
+      int argumentIndex, long rawValue) {
+    return EqualToNumberSimpleInliningConstraint.create(argumentIndex, rawValue, this);
+  }
+
   private <T extends SimpleInliningArgumentConstraint> T createArgumentConstraint(
       int argumentIndex, T[] lowConstraints, Map<Integer, T> highConstraints, Supplier<T> fn) {
     return argumentIndex < lowConstraints.length
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index 9c70c92..3a51eec 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -246,7 +246,7 @@
 
   @Override
   public boolean throwsOnNullInput() {
-    return true;
+    return hasNonNullAssumption();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 7f80abf..0fa4236 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -32,6 +32,14 @@
     this.clazz = clazz;
   }
 
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public DexType getType() {
+    return clazz;
+  }
+
   @Override
   public int opcode() {
     return Opcodes.CONST_CLASS;
@@ -189,4 +197,24 @@
     }
     return UnknownValue.getInstance();
   }
+
+  public static class Builder extends BuilderBase<Builder, ConstClass> {
+
+    private DexType type;
+
+    public Builder setType(DexType type) {
+      this.type = type;
+      return this;
+    }
+
+    @Override
+    public ConstClass build() {
+      return amend(new ConstClass(outValue, type));
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 708ffcf..5517fb8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -98,6 +98,10 @@
     return position == null ? "???" : position.toString();
   }
 
+  public Value getOperand(int index) {
+    return inValues().get(index);
+  }
+
   public List<Value> inValues() {
     return inValues;
   }
@@ -1011,6 +1015,14 @@
     return null;
   }
 
+  public boolean isNewUnboxedEnumInstance() {
+    return false;
+  }
+
+  public NewUnboxedEnumInstance asNewUnboxedEnumInstance() {
+    return null;
+  }
+
   public boolean isNot() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
index a374439..8aa631d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
@@ -108,6 +108,8 @@
 
   T visit(NewInstance instruction);
 
+  T visit(NewUnboxedEnumInstance instruction);
+
   T visit(Not instruction);
 
   T visit(NumberConversion instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index ae02e8b..50899ca 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -38,6 +38,10 @@
     this.clazz = clazz;
   }
 
+  public DexType getType() {
+    return clazz;
+  }
+
   public InvokeDirect getUniqueConstructorInvoke(DexItemFactory dexItemFactory) {
     return IRCodeUtils.getUniqueConstructorInvoke(outValue(), dexItemFactory);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
new file mode 100644
index 0000000..8230766
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
@@ -0,0 +1,161 @@
+// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.code;
+
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.cf.code.CfNewUnboxedEnum;
+import com.android.tools.r8.code.DexNewUnboxedEnumInstance;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+
+/**
+ * Special instruction used by {@link com.android.tools.r8.ir.optimize.enums.EnumUnboxer}.
+ *
+ * <p>When applying the enum unboxer to the application, we move the class initializer of each
+ * unboxed enum to its utility class, and change each {@link NewInstance} instruction that
+ * instantiates the unboxed enum into a {@link NewUnboxedEnumInstance} that holds the ordinal of the
+ * enum instance.
+ *
+ * <p>The {@link NewUnboxedEnumInstance} is an instruction that produces an (initialized) instance
+ * of the unboxed enum, i.e., the out-type is a non-nullable class type. This is important for the
+ * code to type check until lens code rewriting, which replaces the {@link NewUnboxedEnumInstance}
+ * instructions by {@link ConstNumber} instructions.
+ *
+ * <p>Note: The {@link NewUnboxedEnumInstance} is only used from {@link
+ * com.android.tools.r8.ir.optimize.enums.EnumUnboxer#unboxEnums} until the execution of the {@link
+ * com.android.tools.r8.ir.conversion.PostMethodProcessor}. There should be no instances of {@link
+ * NewUnboxedEnumInstance} (nor {@link CfNewUnboxedEnum}, {@link DexNewUnboxedEnumInstance}) after
+ * IR processing has finished.
+ */
+public class NewUnboxedEnumInstance extends Instruction {
+
+  public final DexType clazz;
+  private final int ordinal;
+
+  public NewUnboxedEnumInstance(DexType clazz, int ordinal, Value dest) {
+    super(dest);
+    assert clazz != null;
+    this.clazz = clazz;
+    this.ordinal = ordinal;
+  }
+
+  public int getOrdinal() {
+    return ordinal;
+  }
+
+  public DexType getType() {
+    return clazz;
+  }
+
+  @Override
+  public int opcode() {
+    return Opcodes.NEW_UNBOXED_ENUM_INSTANCE;
+  }
+
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
+  public void buildDex(DexBuilder builder) {
+    int dest = builder.allocatedRegister(outValue(), getNumber());
+    builder.add(this, new DexNewUnboxedEnumInstance(dest, clazz, ordinal));
+  }
+
+  @Override
+  public String toString() {
+    return super.toString() + " " + clazz;
+  }
+
+  @Override
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
+    return other.isNewUnboxedEnumInstance() && other.asNewUnboxedEnumInstance().clazz == clazz;
+  }
+
+  @Override
+  public int maxInValueRegister() {
+    assert false : "NewUnboxedEnumInstance has no register arguments";
+    return 0;
+  }
+
+  @Override
+  public int maxOutValueRegister() {
+    return Constants.U8BIT_MAX;
+  }
+
+  @Override
+  public boolean instructionTypeCanThrow() {
+    // Depending on how this instruction is lowered to CF/DEX the instruction type may throw. If we
+    // lower the instruction to a const-number, then it can't throw, but if we lower it to something
+    // that triggers the class initialization of the enum utility class, then it could throw.
+    return true;
+  }
+
+  @Override
+  public boolean isNewUnboxedEnumInstance() {
+    return true;
+  }
+
+  @Override
+  public NewUnboxedEnumInstance asNewUnboxedEnumInstance() {
+    return this;
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, ProgramMethod context) {
+    return inliningConstraints.forNewUnboxedEnumInstance(clazz, context);
+  }
+
+  @Override
+  public boolean hasInvariantOutType() {
+    return true;
+  }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    helper.storeOutValue(this, it);
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfNewUnboxedEnum(clazz, ordinal));
+  }
+
+  @Override
+  public DexType computeVerificationType(AppView<?> appView, TypeVerificationHelper helper) {
+    return clazz;
+  }
+
+  @Override
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(clazz, Nullability.definitelyNotNull(), appView);
+  }
+
+  @Override
+  public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
+    // Conservatively return true.
+    return true;
+  }
+
+  @Override
+  public boolean verifyTypes(AppView<?> appView, VerifyTypesHelper verifyTypesHelper) {
+    TypeElement type = getOutType();
+    assert type.isClassType();
+    assert type.asClassType().getClassType() == clazz || appView.options().testing.allowTypeErrors;
+    assert type.isDefinitelyNotNull();
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
index 51b19d8e..d2c79d4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
@@ -56,22 +56,23 @@
   int NEW_ARRAY_EMPTY = 47;
   int NEW_ARRAY_FILLED_DATA = 48;
   int NEW_INSTANCE = 49;
-  int NOT = 50;
-  int NUMBER_CONVERSION = 51;
-  int OR = 52;
-  int POP = 53;
-  int REM = 54;
-  int RETURN = 55;
-  int SHL = 56;
-  int SHR = 57;
-  int STATIC_GET = 58;
-  int STATIC_PUT = 59;
-  int STORE = 60;
-  int STRING_SWITCH = 61;
-  int SUB = 62;
-  int SWAP = 63;
-  int THROW = 64;
-  int USHR = 65;
-  int XOR = 66;
-  int UNINITIALIZED_THIS_LOCAL_READ = 67;
+  int NEW_UNBOXED_ENUM_INSTANCE = 50;
+  int NOT = 51;
+  int NUMBER_CONVERSION = 52;
+  int OR = 53;
+  int POP = 54;
+  int REM = 55;
+  int RETURN = 56;
+  int SHL = 57;
+  int SHR = 58;
+  int STATIC_GET = 59;
+  int STATIC_PUT = 60;
+  int STORE = 61;
+  int STRING_SWITCH = 62;
+  int SUB = 63;
+  int SWAP = 64;
+  int THROW = 65;
+  int USHR = 66;
+  int XOR = 67;
+  int UNINITIALIZED_THIS_LOCAL_READ = 68;
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java
index 9b0e28a..dc41e4a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java
@@ -5,11 +5,14 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
 
 public interface StaticFieldInstruction {
 
+  DexField getField();
+
   boolean hasOutValue();
 
   Value outValue();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 32be662..48656ec 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
@@ -20,6 +19,7 @@
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
@@ -44,6 +44,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -185,7 +186,7 @@
           assert !context.getDefinition().isBridge()
               || singleTarget.getDefinition() != context.getDefinition();
           // For static invokes, the class could be initialized.
-          if (type == Invoke.Type.STATIC) {
+          if (type.isStatic()) {
             addClassInitializerTarget(singleTarget.getHolder());
           }
           addCallEdge(singleTarget, false);
@@ -252,28 +253,22 @@
       }
     }
 
-    private void processFieldRead(DexField field) {
-      if (!field.holder.isClassType()) {
+    private void processFieldRead(DexField reference) {
+      if (!reference.holder.isClassType()) {
         return;
       }
 
-      DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
-      if (encodedField == null || appView.appInfo().isPinned(encodedField.getReference())) {
-        return;
-      }
-
-      DexProgramClass clazz =
-          asProgramClassOrNull(appView.definitionFor(encodedField.getHolderType()));
-      if (clazz == null) {
+      ProgramField field = appView.appInfo().resolveField(reference).getProgramField();
+      if (field == null || appView.appInfo().isPinned(field)) {
         return;
       }
 
       // Each static field access implicitly triggers the class initializer.
-      if (encodedField.isStatic()) {
-        addClassInitializerTarget(clazz);
+      if (field.getAccessFlags().isStatic()) {
+        addClassInitializerTarget(field.getHolder());
       }
 
-      FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(encodedField.getReference());
+      FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.getReference());
       if (fieldAccessInfo != null && fieldAccessInfo.hasKnownWriteContexts()) {
         if (fieldAccessInfo.getNumberOfWriteContexts() == 1) {
           fieldAccessInfo.forEachWriteContext(this::addFieldReadEdge);
@@ -281,12 +276,12 @@
       }
     }
 
-    private void processFieldWrite(DexField field) {
-      if (field.holder.isClassType()) {
-        DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
-        if (encodedField != null && encodedField.isStatic()) {
+    private void processFieldWrite(DexField reference) {
+      if (reference.getHolderType().isClassType()) {
+        ProgramField field = appView.appInfo().resolveField(reference).getProgramField();
+        if (field != null && field.getAccessFlags().isStatic()) {
           // Each static field access implicitly triggers the class initializer.
-          addClassInitializerTarget(field.holder);
+          addClassInitializerTarget(field.getHolder());
         }
       }
     }
@@ -426,6 +421,11 @@
     // Nodes on the DFS stack.
     private Map<Node, StackEntryInfo> stackEntryInfo = new IdentityHashMap<>();
 
+    // Subset of the DFS stack, where the nodes on the stack are class initializers.
+    //
+    // This stack is used to efficiently compute if there is a class initializer on the stack.
+    private Deque<Node> clinitCallStack = new ArrayDeque<>();
+
     // Subset of the DFS stack, where the nodes on the stack satisfy that the edge from the
     // predecessor to the node itself is a field read edge.
     //
@@ -471,6 +471,7 @@
 
     private void prepareForNewTraversal() {
       assert calleesToBeRemoved.isEmpty();
+      assert clinitCallStack.isEmpty();
       assert stack.isEmpty();
       assert stackEntryInfo.isEmpty();
       assert writersToBeRemoved.isEmpty();
@@ -480,6 +481,7 @@
     }
 
     private void reset() {
+      assert clinitCallStack.isEmpty();
       assert marked.isEmpty();
       assert revisit.isEmpty();
       assert stack.isEmpty();
@@ -624,28 +626,36 @@
 
         // Otherwise, it is a call edge. Check if there is a field read edge in the cycle, and if
         // so, remove that edge.
-        if (!writerStack.isEmpty()) {
-          Node lastKnownWriter = writerStack.peek();
-          StackEntryInfo lastKnownWriterStackEntryInfo = stackEntryInfo.get(lastKnownWriter);
-          boolean cycleContainsLastKnownWriter =
-              lastKnownWriterStackEntryInfo.index > calleeOrWriterStackEntryInfo.index;
-          if (cycleContainsLastKnownWriter) {
-            assert verifyCycleSatisfies(
+        if (!writerStack.isEmpty()
+            && removeIncomingEdgeOnStack(
+                writerStack.peek(),
                 calleeOrWriter,
-                cycle ->
-                    cycle.contains(lastKnownWriter)
-                        && cycle.contains(lastKnownWriterStackEntryInfo.predecessor));
-            if (!lastKnownWriterStackEntryInfo.processed) {
-              removeFieldReadEdge(lastKnownWriterStackEntryInfo.predecessor, lastKnownWriter);
-              revisit.add(lastKnownWriter);
-              lastKnownWriterStackEntryInfo.processed = true;
-            }
-            continue;
-          }
+                calleeOrWriterStackEntryInfo,
+                this::removeFieldReadEdge)) {
+          continue;
         }
 
-        // It is a call edge, and the cycle does not contain any field read edges. In this case, we
-        // remove the call edge if it is safe according to force inlining.
+        // It is a call edge and the cycle does not contain any field read edges.
+        // If it is a call edge to a <clinit>, then remove it.
+        if (calleeOrWriter.getMethod().isClassInitializer()) {
+          // Calls to class initializers are always safe to remove.
+          assert callEdgeRemovalIsSafe(callerOrReader, calleeOrWriter);
+          removeCallEdge(callerOrReader, calleeOrWriter);
+          continue;
+        }
+
+        // Otherwise, check if there is a call edge to a <clinit> method in the cycle, and if so,
+        // remove that edge.
+        if (!clinitCallStack.isEmpty()
+            && removeIncomingEdgeOnStack(
+                clinitCallStack.peek(),
+                calleeOrWriter,
+                calleeOrWriterStackEntryInfo,
+                this::removeCallEdge)) {
+          continue;
+        }
+
+        // Otherwise, we remove the call edge if it is safe according to force inlining.
         if (callEdgeRemovalIsSafe(callerOrReader, calleeOrWriter)) {
           // Break the cycle by removing the edge node->calleeOrWriter.
           // Need to remove `calleeOrWriter` from `node.callees` using the iterator to prevent a
@@ -669,6 +679,7 @@
 
           // Break the cycle by removing the edge caller->callee.
           removeCallEdge(edge.caller, edge.callee);
+          revisit.add(edge.callee);
         }
 
         // Recover the stack.
@@ -681,8 +692,12 @@
       stack.push(node);
       assert !stackEntryInfo.containsKey(node);
       stackEntryInfo.put(node, new StackEntryInfo(stack.size() - 1, predecessor));
-      if (predecessor != null && predecessor.getWritersWithDeterministicOrder().contains(node)) {
-        writerStack.push(node);
+      if (predecessor != null) {
+        if (node.getMethod().isClassInitializer() && node.hasCaller(predecessor)) {
+          clinitCallStack.push(node);
+        } else if (predecessor.getWritersWithDeterministicOrder().contains(node)) {
+          writerStack.push(node);
+        }
       }
     }
 
@@ -691,7 +706,10 @@
       assert popped == node;
       assert stackEntryInfo.containsKey(node);
       stackEntryInfo.remove(node);
-      if (writerStack.peek() == popped) {
+      if (clinitCallStack.peek() == popped) {
+        assert writerStack.peek() != popped;
+        clinitCallStack.pop();
+      } else if (writerStack.peek() == popped) {
         writerStack.pop();
       }
     }
@@ -704,6 +722,28 @@
       writersToBeRemoved.computeIfAbsent(reader, ignore -> Sets.newIdentityHashSet()).add(writer);
     }
 
+    private boolean removeIncomingEdgeOnStack(
+        Node target,
+        Node currentCalleeOrWriter,
+        StackEntryInfo currentCalleeOrWriterStackEntryInfo,
+        BiConsumer<Node, Node> edgeRemover) {
+      StackEntryInfo targetStackEntryInfo = stackEntryInfo.get(target);
+      boolean cycleContainsTarget =
+          targetStackEntryInfo.index > currentCalleeOrWriterStackEntryInfo.index;
+      if (cycleContainsTarget) {
+        assert verifyCycleSatisfies(
+            currentCalleeOrWriter,
+            cycle -> cycle.contains(target) && cycle.contains(targetStackEntryInfo.predecessor));
+        if (!targetStackEntryInfo.processed) {
+          edgeRemover.accept(targetStackEntryInfo.predecessor, target);
+          revisit.add(target);
+          targetStackEntryInfo.processed = true;
+        }
+        return true;
+      }
+      return false;
+    }
+
     private LinkedList<Node> extractCycle(Node entry) {
       LinkedList<Node> cycle = new LinkedList<>();
       do {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
index 83e0ff5..0266f4b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer.D8CfClassDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer.D8CfPostProcessingDesugaringEventConsumer;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -107,11 +109,15 @@
         assert instructionDesugaringEventConsumer.verifyNothingToFinalize();
       }
 
-      converter.finalizeDesugaredLibraryRetargeting(instructionDesugaringEventConsumer);
-      assert instructionDesugaringEventConsumer.verifyNothingToFinalize();
-
       classes = deferred;
     }
+
+    D8CfPostProcessingDesugaringEventConsumer eventConsumer =
+        CfPostProcessingDesugaringEventConsumer.createForD8(methodProcessor, appView);
+    methodProcessor.newWave();
+    converter.postProcessDesugaring(eventConsumer);
+    methodProcessor.awaitMethodProcessing();
+    eventConsumer.finalizeDesugaring();
   }
 
   abstract void convertClass(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
index 4ba4555..9bcee66 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -55,6 +56,11 @@
   }
 
   @Override
+  public MethodProcessingContext createMethodProcessingContext(ProgramMethod method) {
+    return processorContext.createMethodProcessingContext(method);
+  }
+
+  @Override
   public boolean isProcessedConcurrently(ProgramMethod method) {
     // In D8 all methods are considered independently compiled.
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index ba1a15c..2563fa8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -91,6 +91,7 @@
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.NewUnboxedEnumInstance;
 import com.android.tools.r8.ir.code.Not;
 import com.android.tools.r8.ir.code.NumberConversion;
 import com.android.tools.r8.ir.code.NumberGenerator;
@@ -1813,6 +1814,14 @@
     addInstruction(instruction);
   }
 
+  public void addNewUnboxedEnumInstance(int dest, DexType type, int ordinal) {
+    TypeElement instanceType = TypeElement.fromDexType(type, definitelyNotNull(), appView);
+    Value out = writeRegister(dest, instanceType, ThrowingInfo.CAN_THROW);
+    NewUnboxedEnumInstance instruction = new NewUnboxedEnumInstance(type, ordinal, out);
+    assert instruction.instructionTypeCanThrow();
+    addInstruction(instruction);
+  }
+
   public void addReturn(int value) {
     DexType returnType = method.getDefinition().returnType();
     if (returnType.isVoidType()) {
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 a9e34e2..b73d9d7 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
@@ -49,10 +49,12 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringCollection;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter.Mode;
+import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceProcessor;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.ir.desugar.lambda.LambdaDeserializationMethodRemover;
@@ -139,7 +141,6 @@
   private final StringBuilderOptimizer stringBuilderOptimizer;
   private final IdempotentFunctionCallCanonicalizer idempotentFunctionCallCanonicalizer;
   private final InterfaceMethodRewriter interfaceMethodRewriter;
-  private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
   private final ClassInliner classInliner;
   private final ClassStaticizer classStaticizer;
   private final InternalOptions options;
@@ -225,7 +226,6 @@
       assert options.desugarState.isOn();
       this.instructionDesugaring = CfInstructionDesugaringCollection.create(appView);
       this.classDesugaring = instructionDesugaring.createClassDesugaringCollection();
-      this.desugaredLibraryRetargeter = null; // Managed cf to cf.
       this.interfaceMethodRewriter =
           options.desugaredLibraryConfiguration.getEmulateLibraryInterface().isEmpty()
               ? null
@@ -258,11 +258,6 @@
             ? CfInstructionDesugaringCollection.empty()
             : CfInstructionDesugaringCollection.create(appView);
     this.classDesugaring = instructionDesugaring.createClassDesugaringCollection();
-    this.desugaredLibraryRetargeter =
-        options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
-                || !appView.enableWholeProgramOptimizations()
-            ? null
-            : new DesugaredLibraryRetargeter(appView);
     this.interfaceMethodRewriter =
         options.isInterfaceMethodDesugaringEnabled()
             ? new InterfaceMethodRewriter(appView, this)
@@ -372,10 +367,9 @@
         D8NestBasedAccessDesugaring::clearNestAttributes);
   }
 
-  public void finalizeDesugaredLibraryRetargeting(
-      D8CfInstructionDesugaringEventConsumer instructionDesugaringEventConsumer) {
-    instructionDesugaring.withDesugaredLibraryRetargeter(
-        retargeter -> retargeter.finalizeDesugaring(instructionDesugaringEventConsumer));
+  void postProcessDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer) {
+    CfPostProcessingDesugaringCollection.create(appView, instructionDesugaring.getRetargetingInfo())
+        .postProcessingDesugaring(eventConsumer);
   }
 
   private void staticizeClasses(
@@ -392,25 +386,23 @@
     }
   }
 
-  private void desugarInterfaceMethods(
-      Builder<?> builder,
-      Flavor includeAllResources,
-      ExecutorService executorService)
+  private void finalizeInterfaceMethodRewritingThroughIR(ExecutorService executorService)
       throws ExecutionException {
     assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
     if (interfaceMethodRewriter != null) {
-      interfaceMethodRewriter.desugarInterfaceMethods(
-          builder, includeAllResources, executorService);
+      interfaceMethodRewriter.finalizeInterfaceMethodRewritingThroughIR(this, executorService);
     }
   }
 
-  private void synthesizeRetargetClass(ExecutorService executorService) throws ExecutionException {
-    if (desugaredLibraryRetargeter != null) {
-      desugaredLibraryRetargeter.synthesizeRetargetClasses(this, executorService);
+  private void runInterfaceDesugaringProcessors(
+      Flavor includeAllResources, ExecutorService executorService) throws ExecutionException {
+    assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
+    if (interfaceMethodRewriter != null) {
+      interfaceMethodRewriter.runInterfaceDesugaringProcessors(
+          this, includeAllResources, executorService);
     }
   }
 
-
   private void synthesizeEnumUnboxingUtilityMethods(ExecutorService executorService)
       throws ExecutionException {
     if (enumUnboxer != null) {
@@ -448,7 +440,10 @@
     // Build a new application with jumbo string info,
     Builder<?> builder = application.builder().setHighestSortingString(highestSortingString);
 
-    desugarInterfaceMethods(builder, ExcludeDexResources, executor);
+    runInterfaceDesugaringProcessors(ExcludeDexResources, executor);
+    if (appView.options().isDesugaredLibraryCompilation()) {
+      EmulatedInterfaceProcessor.filterEmulatedInterfaceSubInterfaces(appView, builder);
+    }
     processCovariantReturnTypeAnnotations(builder);
     generateDesugaredLibraryAPIWrappers(builder, executor);
 
@@ -743,7 +738,7 @@
           .run(executorService, feedback, timing);
     }
     if (enumUnboxer != null) {
-      enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
+      enumUnboxer.unboxEnums(this, postMethodProcessorBuilder, executorService, feedback);
     } else {
       appView.setUnboxedEnums(EnumDataMap.empty());
     }
@@ -764,6 +759,11 @@
     }
     timing.end();
 
+    if (enumUnboxer != null) {
+      // TODO(b/190098858): Uncomment when methods are synthesized on-the-fly.
+      // enumUnboxer.unsetRewriter();
+    }
+
     // All the code that should be impacted by the lenses inserted between phase 1 and phase 2
     // have now been processed and rewritten, we clear code lens rewriting so that the class
     // staticizer and phase 3 does not perform again the rewriting.
@@ -793,11 +793,11 @@
     builder.setHighestSortingString(highestSortingString);
 
     printPhase("Interface method desugaring");
-    desugarInterfaceMethods(builder, IncludeAllResources, executorService);
+    finalizeInterfaceMethodRewritingThroughIR(executorService);
+    runInterfaceDesugaringProcessors(IncludeAllResources, executorService);
     feedback.updateVisibleOptimizationInfo();
 
     printPhase("Utility classes synthesis");
-    synthesizeRetargetClass(executorService);
     synthesizeEnumUnboxingUtilityMethods(executorService);
 
     printPhase("Desugared library API Conversion finalization");
@@ -1221,7 +1221,7 @@
     if (appView.graphLens().hasCodeRewritings()) {
       assert lensCodeRewriter != null;
       timing.begin("Lens rewrite");
-      lensCodeRewriter.rewrite(code, context);
+      lensCodeRewriter.rewrite(code, context, methodProcessor);
       timing.end();
     }
 
@@ -1461,14 +1461,6 @@
       timing.end();
     }
 
-    if (desugaredLibraryRetargeter != null) {
-      // The desugaredLibraryRetargeter should run before backportedMethodRewriter to be able to
-      // perform backport rewriting before the methods can be retargeted.
-      timing.begin("Retarget library methods");
-      desugaredLibraryRetargeter.desugar(code);
-      timing.end();
-    }
-
     previous = printMethod(code, "IR after lambda desugaring (SSA)", previous);
 
     assert code.verifyTypes(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index f44ec67..0cf836d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -26,6 +26,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.MOVE_EXCEPTION;
 import static com.android.tools.r8.ir.code.Opcodes.NEW_ARRAY_EMPTY;
 import static com.android.tools.r8.ir.code.Opcodes.NEW_INSTANCE;
+import static com.android.tools.r8.ir.code.Opcodes.NEW_UNBOXED_ENUM_INSTANCE;
 import static com.android.tools.r8.ir.code.Opcodes.RETURN;
 import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
 import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
@@ -139,9 +140,11 @@
   }
 
   /** Replace type appearances, invoke targets and field accesses with actual definitions. */
-  public void rewrite(IRCode code, ProgramMethod method) {
+  public void rewrite(IRCode code, ProgramMethod method, MethodProcessor methodProcessor) {
     Set<Phi> affectedPhis =
-        enumUnboxer != null ? enumUnboxer.rewriteCode(code) : Sets.newIdentityHashSet();
+        enumUnboxer != null
+            ? enumUnboxer.rewriteCode(code, methodProcessor)
+            : Sets.newIdentityHashSet();
     GraphLens graphLens = appView.graphLens();
     DexItemFactory factory = appView.dexItemFactory();
     // Rewriting types that affects phi can cause us to compute TOP for cyclic phi's. To solve this
@@ -590,6 +593,9 @@
             }
             break;
 
+          case NEW_UNBOXED_ENUM_INSTANCE:
+            break;
+
           case RETURN:
             {
               Return ret = current.asReturn();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index 140e0ce..9857098 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.ProgramMethod;
 
 public abstract class MethodProcessor {
@@ -11,6 +12,8 @@
     return false;
   }
 
+  public abstract MethodProcessingContext createMethodProcessingContext(ProgramMethod method);
+
   public abstract boolean isProcessedConcurrently(ProgramMethod method);
 
   public abstract boolean shouldApplyCodeRewritings(ProgramMethod method);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
index cb8eeab..0d1c587 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 
 class NeedsIRDesugarUseRegistry extends UseRegistry {
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index f637225..e40cae1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -45,6 +45,11 @@
   }
 
   @Override
+  public MethodProcessingContext createMethodProcessingContext(ProgramMethod method) {
+    return processorContext.createMethodProcessingContext(method);
+  }
+
+  @Override
   public boolean shouldApplyCodeRewritings(ProgramMethod method) {
     return 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 9d9b9f1..a8e5508 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
@@ -43,6 +43,11 @@
   }
 
   @Override
+  public MethodProcessingContext createMethodProcessingContext(ProgramMethod method) {
+    return processorContext.createMethodProcessingContext(method);
+  }
+
+  @Override
   public boolean shouldApplyCodeRewritings(ProgramMethod method) {
     assert !wave.contains(method);
     return !processed.contains(method);
@@ -67,6 +72,10 @@
       put(postOptimization.methodsToRevisit());
     }
 
+    public void removePrunedMethods(Iterable<DexMethod> prunedMethod) {
+      methodsToReprocessBuilder.removeAll(prunedMethod);
+    }
+
     // 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.
@@ -128,8 +137,7 @@
         assert feedback.noUpdatesLeft();
         ThreadUtils.processItems(
             wave,
-            method ->
-                consumer.accept(method, processorContext.createMethodProcessingContext(method)),
+            method -> consumer.accept(method, createMethodProcessingContext(method)),
             executorService);
         feedback.updateVisibleOptimizationInfo();
         processed.addAll(wave);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index 8969cba..9bd75fe 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -41,6 +41,8 @@
   private final PostMethodProcessor.Builder postMethodProcessorBuilder;
   private final Deque<SortedProgramMethodSet> waves;
 
+  private ProcessorContext processorContext;
+
   private PrimaryMethodProcessor(
       AppView<AppInfoWithLiveness> appView,
       PostMethodProcessor.Builder postMethodProcessorBuilder,
@@ -62,6 +64,11 @@
   }
 
   @Override
+  public MethodProcessingContext createMethodProcessingContext(ProgramMethod method) {
+    return processorContext.createMethodProcessingContext(method);
+  }
+
+  @Override
   public boolean isPrimaryMethodProcessor() {
     return true;
   }
@@ -125,7 +132,7 @@
     TimingMerger merger =
         timing.beginMerger("primary-processor", ThreadUtils.getNumberOfThreads(executorService));
     while (!waves.isEmpty()) {
-      ProcessorContext processorContext = appView.createProcessorContext();
+      processorContext = appView.createProcessorContext();
       wave = waves.removeFirst();
       assert !wave.isEmpty();
       assert waveExtension.isEmpty();
@@ -135,9 +142,7 @@
             ThreadUtils.processItemsWithResults(
                 wave,
                 method -> {
-                  Timing time =
-                      consumer.apply(
-                          method, processorContext.createMethodProcessingContext(method));
+                  Timing time = consumer.apply(method, createMethodProcessingContext(method));
                   time.end();
                   return time;
                 },
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index b287221..197ab34 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.ir.desugar.backports.ObjectsMethodRewrites;
 import com.android.tools.r8.ir.desugar.backports.OptionalMethodRewrites;
 import com.android.tools.r8.ir.desugar.backports.SparseArrayMethodRewrites;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeter;
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java
index 9b7c709..70d5123 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.ir.desugar.records.RecordRewriter;
 
 /** Interface for desugaring a class. */
 public abstract class CfClassDesugaringCollection {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java
index 9b02ab5..ef09aba 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
+import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer;
 
 public abstract class CfClassDesugaringEventConsumer implements RecordDesugaringEventConsumer {
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
index 148ea56..2d84083 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -7,9 +7,9 @@
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.utils.ThrowingConsumer;
-import java.util.function.Consumer;
 
 /**
  * Abstracts a collection of low-level desugarings (i.e., mappings from class-file instructions to
@@ -56,8 +56,5 @@
   public abstract <T extends Throwable> void withD8NestBasedAccessDesugaring(
       ThrowingConsumer<D8NestBasedAccessDesugaring, T> consumer) throws T;
 
-  public abstract void withDesugaredLibraryRetargeter(
-      Consumer<DesugaredLibraryRetargeter> consumer);
-
-  public abstract void withRecordRewriter(Consumer<RecordRewriter> consumer);
+  public abstract RetargetingInfo getRetargetingInfo();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 1c6b337..f05c16b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
@@ -14,13 +15,16 @@
 import com.android.tools.r8.ir.conversion.ClassConverterResult;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethodDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterInstructionEventConsumer;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.lambda.LambdaDeserializationMethodRemover;
 import com.android.tools.r8.ir.desugar.lambda.LambdaDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.twr.TwrCloseResourceDesugaringEventConsumer;
+import com.android.tools.r8.shaking.Enqueuer.SyntheticAdditions;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -44,7 +48,7 @@
         RecordDesugaringEventConsumer,
         TwrCloseResourceDesugaringEventConsumer,
         InterfaceMethodDesugaringEventConsumer,
-        DesugaredLibraryRetargeterEventConsumer {
+        DesugaredLibraryRetargeterInstructionEventConsumer {
 
   public static D8CfInstructionDesugaringEventConsumer createForD8(
       D8MethodProcessor methodProcessor) {
@@ -54,9 +58,10 @@
   public static R8CfInstructionDesugaringEventConsumer createForR8(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer,
-      BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer) {
+      BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer,
+      SyntheticAdditions additions) {
     return new R8CfInstructionDesugaringEventConsumer(
-        appView, lambdaClassConsumer, twrCloseResourceMethodConsumer);
+        appView, lambdaClassConsumer, twrCloseResourceMethodConsumer, additions);
   }
 
   public static CfInstructionDesugaringEventConsumer createForDesugaredCode() {
@@ -73,7 +78,7 @@
       }
 
       @Override
-      public void acceptForwardingMethod(ProgramMethod method) {
+      public void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
         assert false;
       }
 
@@ -155,12 +160,12 @@
 
     @Override
     public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
-      // Intentionnaly empty.
+      // Intentionally empty.
     }
 
     @Override
-    public void acceptForwardingMethod(ProgramMethod method) {
-      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+    public void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
+      // Intentionally empty.
     }
 
     @Override
@@ -287,6 +292,7 @@
     //  synthetic items.
     private final BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer;
     private final BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer;
+    private final SyntheticAdditions additions;
 
     private final Map<LambdaClass, ProgramMethod> synthesizedLambdaClasses =
         new IdentityHashMap<>();
@@ -295,10 +301,12 @@
     public R8CfInstructionDesugaringEventConsumer(
         AppView<? extends AppInfoWithClassHierarchy> appView,
         BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer,
-        BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer) {
+        BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer,
+        SyntheticAdditions additions) {
       this.appView = appView;
       this.lambdaClassConsumer = lambdaClassConsumer;
       this.twrCloseResourceMethodConsumer = twrCloseResourceMethodConsumer;
+      this.additions = additions;
     }
 
     @Override
@@ -308,15 +316,13 @@
     }
 
     @Override
-    public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
-      // TODO(b/188767735): R8 currently relies on IR desugaring.
-      // The classpath class should be marked as liveNonProgramType.
+    public void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
+      additions.injectInterface(clazz, newInterface);
     }
 
     @Override
-    public void acceptForwardingMethod(ProgramMethod method) {
-      // TODO(b/188767735): R8 currently relies on IR desugaring.
-      // The method should be marked live, and assert everything it references is traced.
+    public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
+      additions.addLiveClasspathClass(clazz);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaring.java
new file mode 100644
index 0000000..2a2de5f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaring.java
@@ -0,0 +1,9 @@
+// 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;
+
+public interface CfPostProcessingDesugaring {
+
+  void postProcessingDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
new file mode 100644
index 0000000..f659ad8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
@@ -0,0 +1,74 @@
+// 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;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterPostProcessor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class CfPostProcessingDesugaringCollection {
+
+  public static CfPostProcessingDesugaringCollection create(
+      AppView<?> appView, RetargetingInfo retargetingInfo) {
+    if (appView.options().desugarState.isOn()) {
+      return NonEmptyCfPostProcessingDesugaringCollection.create(appView, retargetingInfo);
+    }
+    return empty();
+  }
+
+  static CfPostProcessingDesugaringCollection empty() {
+    return EmptyCfPostProcessingDesugaringCollection.getInstance();
+  }
+
+  public abstract void postProcessingDesugaring(
+      CfPostProcessingDesugaringEventConsumer eventConsumer);
+
+  public static class NonEmptyCfPostProcessingDesugaringCollection
+      extends CfPostProcessingDesugaringCollection {
+
+    private final List<CfPostProcessingDesugaring> desugarings;
+
+    public NonEmptyCfPostProcessingDesugaringCollection(
+        List<CfPostProcessingDesugaring> desugarings) {
+      this.desugarings = desugarings;
+    }
+
+    public static CfPostProcessingDesugaringCollection create(
+        AppView<?> appView, RetargetingInfo retargetingInfo) {
+      if (appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
+        return empty();
+      }
+      return new NonEmptyCfPostProcessingDesugaringCollection(
+          Collections.singletonList(
+              new DesugaredLibraryRetargeterPostProcessor(appView, retargetingInfo)));
+    }
+
+    @Override
+    public void postProcessingDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer) {
+      for (CfPostProcessingDesugaring desugaring : desugarings) {
+        desugaring.postProcessingDesugaring(eventConsumer);
+      }
+    }
+  }
+
+  public static class EmptyCfPostProcessingDesugaringCollection
+      extends CfPostProcessingDesugaringCollection {
+
+    private static final EmptyCfPostProcessingDesugaringCollection INSTANCE =
+        new EmptyCfPostProcessingDesugaringCollection();
+
+    private EmptyCfPostProcessingDesugaringCollection() {}
+
+    private static EmptyCfPostProcessingDesugaringCollection getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    public void postProcessingDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer) {
+      // Intentionally empty.
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
new file mode 100644
index 0000000..ec4f197
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
@@ -0,0 +1,113 @@
+// 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;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.D8MethodProcessor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterInstructionEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer;
+import com.android.tools.r8.shaking.Enqueuer.SyntheticAdditions;
+import java.util.function.Consumer;
+
+/**
+ * Specialized Event consumer for desugaring finalization. During finalization, it is not possible
+ * to run any more instruction desugaring. If there are dependencies in between various desugaring,
+ * explicit calls must be done here.
+ */
+public abstract class CfPostProcessingDesugaringEventConsumer
+    implements DesugaredLibraryRetargeterPostProcessingEventConsumer {
+  protected DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
+
+  protected CfPostProcessingDesugaringEventConsumer(AppView<?> appView) {
+    this.desugaredLibraryAPIConverter = new DesugaredLibraryAPIConverter(appView, null);
+  }
+
+  public static D8CfPostProcessingDesugaringEventConsumer createForD8(
+      D8MethodProcessor methodProcessor, AppView<?> appView) {
+    return new D8CfPostProcessingDesugaringEventConsumer(methodProcessor, appView);
+  }
+
+  public static R8PostProcessingDesugaringEventConsumer createForR8(
+      AppView<?> appView, Consumer<ProgramMethod> methodConsumer, SyntheticAdditions additions) {
+    return new R8PostProcessingDesugaringEventConsumer(appView, methodConsumer, additions);
+  }
+
+  public void finalizeDesugaring() {
+    desugaredLibraryAPIConverter.generateTrackingWarnings();
+  }
+
+  public static class D8CfPostProcessingDesugaringEventConsumer
+      extends CfPostProcessingDesugaringEventConsumer {
+    private final D8MethodProcessor methodProcessor;
+
+    private D8CfPostProcessingDesugaringEventConsumer(
+        D8MethodProcessor methodProcessor, AppView<?> appView) {
+      super(appView);
+      this.methodProcessor = methodProcessor;
+    }
+
+    @Override
+    public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
+      methodProcessor.scheduleDesugaredMethodsForProcessing(clazz.programMethods());
+    }
+
+    @Override
+    public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptForwardingMethod(ProgramMethod method) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+      // TODO(b/189912077): Uncomment when API conversion is performed cf to cf in D8.
+      // desugaredLibraryAPIConverter.generateCallbackIfRequired(method);
+    }
+  }
+
+  public static class R8PostProcessingDesugaringEventConsumer
+      extends CfPostProcessingDesugaringEventConsumer {
+    private final Consumer<ProgramMethod> methodConsumer;
+    private final SyntheticAdditions additions;
+
+    protected R8PostProcessingDesugaringEventConsumer(
+        AppView<?> appView, Consumer<ProgramMethod> methodConsumer, SyntheticAdditions additions) {
+      super(appView);
+      this.methodConsumer = methodConsumer;
+      this.additions = additions;
+    }
+
+    @Override
+    public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
+      clazz.programMethods().forEach(methodConsumer);
+    }
+
+    @Override
+    public void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
+      additions.injectInterface(clazz, newInterface);
+    }
+
+    @Override
+    public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
+      additions.addLiveClasspathClass(clazz);
+    }
+
+    @Override
+    public void acceptForwardingMethod(ProgramMethod method) {
+      methodConsumer.accept(method);
+      ProgramMethod callback = desugaredLibraryAPIConverter.generateCallbackIfRequired(method);
+      if (callback != null) {
+        methodConsumer.accept(callback);
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
deleted file mode 100644
index eb354b0..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ /dev/null
@@ -1,812 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-//  for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.desugar;
-
-import static com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter.InvokeRetargetingResult.NO_REWRITING;
-
-import com.android.tools.r8.ProgramResource.Kind;
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.ClasspathOrLibraryClass;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexLibraryClass;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.EnclosingMethodAttribute;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.NestHostClassAttribute;
-import com.android.tools.r8.graph.NestMemberClassAttribute;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.synthetic.EmulateInterfaceSyntheticCfCodeProvider;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.synthesis.SyntheticClassBuilder;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.WorkList;
-import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
-import com.google.common.collect.Maps;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.IdentityHashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import org.objectweb.asm.Opcodes;
-
-public class DesugaredLibraryRetargeter implements CfInstructionDesugaring {
-
-  private final AppView<?> appView;
-  private final Map<DexMethod, DexMethod> retargetLibraryMember = new IdentityHashMap<>();
-  // Map nonFinalRewrite hold a methodName -> method mapping for methods which are rewritten while
-  // the holder is non final. In this case d8 needs to force resolution of given methods to see if
-  // the invoke needs to be rewritten.
-  private final Map<DexString, List<DexMethod>> nonFinalHolderRewrites = new IdentityHashMap<>();
-  // Non final virtual library methods requiring generation of emulated dispatch.
-  private final DexClassAndMethodSet emulatedDispatchMethods = DexClassAndMethodSet.create();
-
-  private final SortedProgramMethodSet forwardingMethods = SortedProgramMethodSet.create();
-
-  public DesugaredLibraryRetargeter(AppView<?> appView) {
-    this.appView = appView;
-    if (appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
-      return;
-    }
-    new RetargetingSetup().setUpRetargeting();
-  }
-
-  public static void checkForAssumedLibraryTypes(AppView<?> appView) {
-    Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
-        appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
-    for (DexString methodName : retargetCoreLibMember.keySet()) {
-      for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
-        DexClass typeClass = appView.definitionFor(inType);
-        if (typeClass == null) {
-          warnMissingRetargetCoreLibraryMember(inType, appView);
-        }
-      }
-    }
-  }
-
-  public static void amendLibraryWithRetargetedMembers(AppView<AppInfoWithClassHierarchy> appView) {
-    Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
-        appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
-    Map<DexType, DexLibraryClass> synthesizedLibraryClasses =
-        synthesizeLibraryClassesForRetargetedMembers(appView, retargetCoreLibMember);
-    Map<DexLibraryClass, Set<DexEncodedMethod>> synthesizedLibraryMethods =
-        synthesizedMembersForRetargetClasses(
-            appView, retargetCoreLibMember, synthesizedLibraryClasses);
-    synthesizedLibraryMethods.forEach(DexLibraryClass::addDirectMethods);
-    DirectMappedDexApplication newApplication =
-        appView
-            .appInfo()
-            .app()
-            .asDirect()
-            .builder()
-            .addLibraryClasses(synthesizedLibraryClasses.values())
-            .build();
-    appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(app -> newApplication));
-  }
-
-  private static Map<DexType, DexLibraryClass> synthesizeLibraryClassesForRetargetedMembers(
-      AppView<AppInfoWithClassHierarchy> appView,
-      Map<DexString, Map<DexType, DexType>> retargetCoreLibMember) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    Map<DexType, DexLibraryClass> synthesizedLibraryClasses = new LinkedHashMap<>();
-    for (Map<DexType, DexType> oldToNewTypeMap : retargetCoreLibMember.values()) {
-      for (DexType newType : oldToNewTypeMap.values()) {
-        if (appView.definitionFor(newType) == null) {
-          synthesizedLibraryClasses.computeIfAbsent(
-              newType,
-              type ->
-                  // Synthesize a library class with the given name. Note that this is assuming that
-                  // the library class inherits directly from java.lang.Object, does not implement
-                  // any interfaces, etc.
-                  new DexLibraryClass(
-                      type,
-                      Kind.CF,
-                      new SynthesizedOrigin(
-                          "Desugared library retargeter", DesugaredLibraryRetargeter.class),
-                      ClassAccessFlags.fromCfAccessFlags(Constants.ACC_PUBLIC),
-                      dexItemFactory.objectType,
-                      DexTypeList.empty(),
-                      dexItemFactory.createString("DesugaredLibraryRetargeter"),
-                      NestHostClassAttribute.none(),
-                      NestMemberClassAttribute.emptyList(),
-                      EnclosingMethodAttribute.none(),
-                      InnerClassAttribute.emptyList(),
-                      ClassSignature.noSignature(),
-                      DexAnnotationSet.empty(),
-                      DexEncodedField.EMPTY_ARRAY,
-                      DexEncodedField.EMPTY_ARRAY,
-                      DexEncodedMethod.EMPTY_ARRAY,
-                      DexEncodedMethod.EMPTY_ARRAY,
-                      dexItemFactory.getSkipNameValidationForTesting()));
-        }
-      }
-    }
-    return synthesizedLibraryClasses;
-  }
-
-  private static Map<DexLibraryClass, Set<DexEncodedMethod>> synthesizedMembersForRetargetClasses(
-      AppView<AppInfoWithClassHierarchy> appView,
-      Map<DexString, Map<DexType, DexType>> retargetCoreLibMember,
-      Map<DexType, DexLibraryClass> synthesizedLibraryClasses) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    Map<DexLibraryClass, Set<DexEncodedMethod>> synthesizedMembers = new IdentityHashMap<>();
-    for (Entry<DexString, Map<DexType, DexType>> entry : retargetCoreLibMember.entrySet()) {
-      DexString methodName = entry.getKey();
-      Map<DexType, DexType> types = entry.getValue();
-      types.forEach(
-          (oldType, newType) -> {
-            DexClass oldClass = appView.definitionFor(oldType);
-            DexLibraryClass newClass = synthesizedLibraryClasses.get(newType);
-            if (oldClass == null || newClass == null) {
-              return;
-            }
-            for (DexEncodedMethod method :
-                oldClass.methods(method -> method.getName() == methodName)) {
-              DexMethod retargetMethod = method.getReference().withHolder(newType, dexItemFactory);
-              if (!method.isStatic()) {
-                retargetMethod = retargetMethod.withExtraArgumentPrepended(oldType, dexItemFactory);
-              }
-              synthesizedMembers
-                  .computeIfAbsent(
-                      newClass,
-                      ignore -> new TreeSet<>(Comparator.comparing(DexEncodedMethod::getReference)))
-                  .add(
-                      new DexEncodedMethod(
-                          retargetMethod,
-                          MethodAccessFlags.fromCfAccessFlags(
-                              Constants.ACC_PUBLIC | Constants.ACC_STATIC, false),
-                          MethodTypeSignature.noSignature(),
-                          DexAnnotationSet.empty(),
-                          ParameterAnnotationsList.empty(),
-                          null,
-                          true));
-            }
-          });
-    }
-    return synthesizedMembers;
-  }
-
-  private static void warnMissingRetargetCoreLibraryMember(DexType type, AppView<?> appView) {
-    StringDiagnostic warning =
-        new StringDiagnostic(
-            "Cannot retarget core library member "
-                + type.getName()
-                + " because the class is missing.");
-    appView.options().reporter.warning(warning);
-  }
-
-  // Used by the ListOfBackportedMethods utility.
-  void visit(Consumer<DexMethod> consumer) {
-    retargetLibraryMember.keySet().forEach(consumer);
-  }
-
-  @Override
-  public Collection<CfInstruction> desugarInstruction(
-      CfInstruction instruction,
-      FreshLocalProvider freshLocalProvider,
-      LocalStackAllocator localStackAllocator,
-      CfInstructionDesugaringEventConsumer eventConsumer,
-      ProgramMethod context,
-      MethodProcessingContext methodProcessingContext,
-      DexItemFactory dexItemFactory) {
-    InvokeRetargetingResult invokeRetargetingResult = computeNewInvokeTarget(instruction, context);
-
-    if (!invokeRetargetingResult.hasNewInvokeTarget()) {
-      return null;
-    }
-
-    DexMethod newInvokeTarget = invokeRetargetingResult.getNewInvokeTarget(eventConsumer);
-    return Collections.singletonList(
-        new CfInvoke(Opcodes.INVOKESTATIC, newInvokeTarget, instruction.asInvoke().isInterface()));
-  }
-
-  @Override
-  public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
-    return computeNewInvokeTarget(instruction, context).hasNewInvokeTarget();
-  }
-
-  @Deprecated // Use Cf to Cf desugaring instead.
-  public void desugar(IRCode code) {
-    if (retargetLibraryMember.isEmpty()) {
-      return;
-    }
-
-    InstructionListIterator iterator = code.instructionListIterator();
-    while (iterator.hasNext()) {
-      Instruction instruction = iterator.next();
-      if (!instruction.isInvokeMethod()) {
-        continue;
-      }
-
-      InvokeMethod invoke = instruction.asInvokeMethod();
-      DexMethod invokedMethod = invoke.getInvokedMethod();
-      boolean isInterface = invoke.getInterfaceBit();
-
-      InvokeRetargetingResult invokeRetargetingResult =
-          computeNewInvokeTarget(
-              invokedMethod, isInterface, invoke.isInvokeSuper(), code.context());
-      if (invokeRetargetingResult.hasNewInvokeTarget()) {
-        DexMethod newInvokeTarget = invokeRetargetingResult.getNewInvokeTarget(null);
-        iterator.replaceCurrentInstruction(
-            new InvokeStatic(newInvokeTarget, invoke.outValue(), invoke.inValues()));
-      }
-    }
-  }
-
-  static class InvokeRetargetingResult {
-
-    static InvokeRetargetingResult NO_REWRITING =
-        new InvokeRetargetingResult(false, ignored -> null);
-
-    private final boolean hasNewInvokeTarget;
-    private final Function<DesugaredLibraryRetargeterEventConsumer, DexMethod>
-        newInvokeTargetSupplier;
-
-    static InvokeRetargetingResult createInvokeRetargetingResult(DexMethod retarget) {
-      if (retarget == null) {
-        return NO_REWRITING;
-      }
-      return new InvokeRetargetingResult(true, ignored -> retarget);
-    }
-
-    private InvokeRetargetingResult(
-        boolean hasNewInvokeTarget,
-        Function<DesugaredLibraryRetargeterEventConsumer, DexMethod> newInvokeTargetSupplier) {
-      this.hasNewInvokeTarget = hasNewInvokeTarget;
-      this.newInvokeTargetSupplier = newInvokeTargetSupplier;
-    }
-
-    public boolean hasNewInvokeTarget() {
-      return hasNewInvokeTarget;
-    }
-
-    public DexMethod getNewInvokeTarget(DesugaredLibraryRetargeterEventConsumer eventConsumer) {
-      assert hasNewInvokeTarget();
-      return newInvokeTargetSupplier.apply(eventConsumer);
-    }
-  }
-
-  public boolean hasNewInvokeTarget(
-      DexMethod invokedMethod, boolean isInterface, boolean isInvokeSuper, ProgramMethod context) {
-    return computeNewInvokeTarget(invokedMethod, isInterface, isInvokeSuper, context)
-        .hasNewInvokeTarget();
-  }
-
-  private InvokeRetargetingResult computeNewInvokeTarget(
-      CfInstruction instruction, ProgramMethod context) {
-    if (retargetLibraryMember.isEmpty() || !instruction.isInvoke()) {
-      return NO_REWRITING;
-    }
-    CfInvoke cfInvoke = instruction.asInvoke();
-    return computeNewInvokeTarget(
-        cfInvoke.getMethod(),
-        cfInvoke.isInterface(),
-        cfInvoke.isInvokeSuper(context.getHolderType()),
-        context);
-  }
-
-  private InvokeRetargetingResult computeNewInvokeTarget(
-      DexMethod invokedMethod, boolean isInterface, boolean isInvokeSuper, ProgramMethod context) {
-    InvokeRetargetingResult retarget = computeRetargetedMethod(invokedMethod, isInterface);
-    if (!retarget.hasNewInvokeTarget()) {
-      return NO_REWRITING;
-    }
-    if (isInvokeSuper && matchesNonFinalHolderRewrite(invokedMethod)) {
-      DexClassAndMethod superTarget =
-          appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
-      // Final methods can be rewritten as a normal invoke.
-      if (superTarget != null && !superTarget.getAccessFlags().isFinal()) {
-        return InvokeRetargetingResult.createInvokeRetargetingResult(
-            appView.options().desugaredLibraryConfiguration.retargetMethod(superTarget, appView));
-      }
-    }
-    return retarget;
-  }
-
-  private InvokeRetargetingResult computeRetargetedMethod(
-      DexMethod invokedMethod, boolean isInterface) {
-    InvokeRetargetingResult invokeRetargetingResult = computeRetargetLibraryMember(invokedMethod);
-    if (!invokeRetargetingResult.hasNewInvokeTarget()) {
-      if (!matchesNonFinalHolderRewrite(invokedMethod)) {
-        return NO_REWRITING;
-      }
-      // We need to force resolution, even on d8, to know if the invoke has to be rewritten.
-      ResolutionResult resolutionResult =
-          appView.appInfoForDesugaring().resolveMethod(invokedMethod, isInterface);
-      if (resolutionResult.isFailedResolution()) {
-        return NO_REWRITING;
-      }
-      DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
-      assert singleTarget != null;
-      invokeRetargetingResult = computeRetargetLibraryMember(singleTarget.getReference());
-    }
-    return invokeRetargetingResult;
-  }
-
-  private InvokeRetargetingResult computeRetargetLibraryMember(DexMethod method) {
-    DexClassAndMethod emulatedMethod = emulatedDispatchMethods.get(method);
-    if (emulatedMethod != null) {
-      assert !emulatedMethod.getAccessFlags().isStatic();
-      return new InvokeRetargetingResult(
-          true,
-          eventConsumer -> {
-            DexType newHolder =
-                ensureEmulatedHolderDispatchMethod(emulatedMethod, eventConsumer).type;
-            return computeRetargetMethod(
-                method, emulatedMethod.getAccessFlags().isStatic(), newHolder);
-          });
-    }
-    return InvokeRetargetingResult.createInvokeRetargetingResult(retargetLibraryMember.get(method));
-  }
-
-  private boolean matchesNonFinalHolderRewrite(DexMethod method) {
-    List<DexMethod> dexMethods = nonFinalHolderRewrites.get(method.name);
-    if (dexMethods == null) {
-      return false;
-    }
-    for (DexMethod dexMethod : dexMethods) {
-      if (method.match(dexMethod)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  DexMethod computeRetargetMethod(DexMethod method, boolean isStatic, DexType newHolder) {
-    DexItemFactory factory = appView.dexItemFactory();
-    DexProto newProto = isStatic ? method.getProto() : factory.prependHolderToProto(method);
-    return factory.createMethod(newHolder, newProto, method.getName());
-  }
-
-  private class RetargetingSetup {
-
-    private void setUpRetargeting() {
-      DesugaredLibraryConfiguration desugaredLibraryConfiguration =
-          appView.options().desugaredLibraryConfiguration;
-      Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
-          desugaredLibraryConfiguration.getRetargetCoreLibMember();
-      for (DexString methodName : retargetCoreLibMember.keySet()) {
-        for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
-          DexClass typeClass = appView.definitionFor(inType);
-          if (typeClass != null) {
-            DexType newHolder = retargetCoreLibMember.get(methodName).get(inType);
-            List<DexClassAndMethod> found = findMethodsWithName(methodName, typeClass);
-            for (DexClassAndMethod method : found) {
-              boolean emulatedDispatch = false;
-              DexMethod methodReference = method.getReference();
-              if (!typeClass.isFinal()) {
-                nonFinalHolderRewrites.putIfAbsent(method.getName(), new ArrayList<>());
-                nonFinalHolderRewrites.get(method.getName()).add(methodReference);
-                if (!method.getAccessFlags().isStatic()) {
-                  if (isEmulatedInterfaceDispatch(method)) {
-                    // In this case interface method rewriter takes care of it.
-                    continue;
-                  } else if (!method.getAccessFlags().isFinal()) {
-                    // Virtual rewrites require emulated dispatch for inheritance.
-                    // The call is rewritten to the dispatch holder class instead.
-                    emulatedDispatchMethods.add(method);
-                    emulatedDispatch = true;
-                  }
-                }
-              }
-              if (!emulatedDispatch) {
-                retargetLibraryMember.put(
-                    methodReference,
-                    computeRetargetMethod(
-                        methodReference, method.getAccessFlags().isStatic(), newHolder));
-              }
-            }
-          }
-        }
-      }
-      if (desugaredLibraryConfiguration.isLibraryCompilation()) {
-        // TODO(b/177977763): This is only a workaround rewriting invokes of j.u.Arrays.deepEquals0
-        // to j.u.DesugarArrays.deepEquals0.
-        DexItemFactory itemFactory = appView.options().dexItemFactory();
-        DexString name = itemFactory.createString("deepEquals0");
-        DexProto proto =
-            itemFactory.createProto(
-                itemFactory.booleanType, itemFactory.objectType, itemFactory.objectType);
-        DexMethod source =
-            itemFactory.createMethod(
-                itemFactory.createType(itemFactory.arraysDescriptor), proto, name);
-        DexMethod target =
-            computeRetargetMethod(
-                source, true, itemFactory.createType("Ljava/util/DesugarArrays;"));
-        retargetLibraryMember.put(source, target);
-
-        // TODO(b/181629049): This is only a workaround rewriting invokes of
-        //  j.u.TimeZone.getTimeZone taking a java.time.ZoneId.
-        name = itemFactory.createString("getTimeZone");
-        proto =
-            itemFactory.createProto(
-                itemFactory.createType("Ljava/util/TimeZone;"),
-                itemFactory.createType("Ljava/time/ZoneId;"));
-        source =
-            itemFactory.createMethod(itemFactory.createType("Ljava/util/TimeZone;"), proto, name);
-        target =
-            computeRetargetMethod(
-                source, true, itemFactory.createType("Ljava/util/DesugarTimeZone;"));
-        retargetLibraryMember.put(source, target);
-      }
-    }
-
-    private boolean isEmulatedInterfaceDispatch(DexClassAndMethod method) {
-      // Answers true if this method is already managed through emulated interface dispatch.
-      Map<DexType, DexType> emulateLibraryInterface =
-          appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
-      if (emulateLibraryInterface.isEmpty()) {
-        return false;
-      }
-      DexMethod methodToFind = method.getReference();
-
-      // Look-up all superclass and interfaces, if an emulated interface is found, and it implements
-      // the method, answers true.
-      WorkList<DexClass> worklist = WorkList.newIdentityWorkList(method.getHolder());
-      while (worklist.hasNext()) {
-        DexClass clazz = worklist.next();
-        if (clazz.isInterface()
-            && emulateLibraryInterface.containsKey(clazz.getType())
-            && clazz.lookupMethod(methodToFind) != null) {
-          return true;
-        }
-        // All super types are library class, or we are doing L8 compilation.
-        clazz.forEachImmediateSupertype(
-            superType -> {
-              DexClass superClass = appView.definitionFor(superType);
-              if (superClass != null) {
-                worklist.addIfNotSeen(superClass);
-              }
-            });
-      }
-      return false;
-    }
-
-    private List<DexClassAndMethod> findMethodsWithName(DexString methodName, DexClass clazz) {
-      List<DexClassAndMethod> found = new ArrayList<>();
-      clazz.forEachClassMethodMatching(
-          definition -> definition.getName() == methodName, found::add);
-      assert !found.isEmpty()
-          : "Should have found a method (library specifications) for "
-              + clazz.toSourceString()
-              + "."
-              + methodName
-              + ". Maybe the library used for the compilation should be newer.";
-      return found;
-    }
-  }
-
-  public void finalizeDesugaring(DesugaredLibraryRetargeterEventConsumer eventConsumer) {
-    new EmulatedDispatchTreeFixer().fixApp(eventConsumer);
-  }
-
-  private void rewriteType(DexType type) {
-    String newName =
-        appView.options().desugaredLibraryConfiguration.convertJavaNameToDesugaredLibrary(type);
-    DexType newType =
-        appView.dexItemFactory().createType(DescriptorUtils.javaTypeToDescriptor(newName));
-    appView.rewritePrefix.rewriteType(type, newType);
-  }
-
-  public DexClass ensureEmulatedHolderDispatchMethod(
-      DexClassAndMethod emulatedDispatchMethod,
-      DesugaredLibraryRetargeterEventConsumer eventConsumer) {
-    assert eventConsumer != null || appView.enableWholeProgramOptimizations();
-    DexClass interfaceClass =
-        ensureEmulatedInterfaceDispatchMethod(emulatedDispatchMethod, eventConsumer);
-    DexMethod itfMethod =
-        interfaceClass.lookupMethod(emulatedDispatchMethod.getReference()).getReference();
-    DexClass holderDispatch;
-    if (appView.options().isDesugaredLibraryCompilation()) {
-      holderDispatch =
-          appView
-              .getSyntheticItems()
-              .ensureFixedClass(
-                  SyntheticKind.RETARGET_CLASS,
-                  emulatedDispatchMethod.getHolder(),
-                  appView,
-                  classBuilder ->
-                      buildHolderDispatchMethod(classBuilder, emulatedDispatchMethod, itfMethod),
-                  clazz -> {
-                    if (eventConsumer != null) {
-                      eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz);
-                    }
-                  });
-    } else {
-      ClasspathOrLibraryClass context =
-          emulatedDispatchMethod.getHolder().asClasspathOrLibraryClass();
-      assert context != null;
-      holderDispatch =
-          appView
-              .getSyntheticItems()
-              .ensureFixedClasspathClass(
-                  SyntheticKind.RETARGET_CLASS,
-                  context,
-                  appView,
-                  classBuilder ->
-                      buildHolderDispatchMethod(classBuilder, emulatedDispatchMethod, itfMethod),
-                  clazz -> {
-                    if (eventConsumer != null) {
-                      eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz);
-                    }
-                  });
-    }
-    rewriteType(holderDispatch.type);
-    return holderDispatch;
-  }
-
-  public DexClass ensureEmulatedInterfaceDispatchMethod(
-      DexClassAndMethod emulatedDispatchMethod,
-      DesugaredLibraryRetargeterEventConsumer eventConsumer) {
-    assert eventConsumer != null || appView.enableWholeProgramOptimizations();
-    DexClass interfaceDispatch;
-    if (appView.options().isDesugaredLibraryCompilation()) {
-      interfaceDispatch =
-          appView
-              .getSyntheticItems()
-              .ensureFixedClass(
-                  SyntheticKind.RETARGET_INTERFACE,
-                  emulatedDispatchMethod.getHolder(),
-                  appView,
-                  classBuilder ->
-                      buildInterfaceDispatchMethod(classBuilder, emulatedDispatchMethod),
-                  clazz -> {
-                    if (eventConsumer != null) {
-                      eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz);
-                    }
-                  });
-    } else {
-      ClasspathOrLibraryClass context =
-          emulatedDispatchMethod.getHolder().asClasspathOrLibraryClass();
-      assert context != null;
-      interfaceDispatch =
-          appView
-              .getSyntheticItems()
-              .ensureFixedClasspathClass(
-                  SyntheticKind.RETARGET_INTERFACE,
-                  context,
-                  appView,
-                  classBuilder ->
-                      buildInterfaceDispatchMethod(classBuilder, emulatedDispatchMethod),
-                  clazz -> {
-                    if (eventConsumer != null) {
-                      eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz);
-                    }
-                  });
-    }
-    rewriteType(interfaceDispatch.type);
-    return interfaceDispatch;
-  }
-
-  private void buildInterfaceDispatchMethod(
-      SyntheticClassBuilder<?, ?> classBuilder, DexClassAndMethod emulatedDispatchMethod) {
-    classBuilder
-        .setInterface()
-        .addMethod(
-            methodBuilder -> {
-              MethodAccessFlags flags =
-                  MethodAccessFlags.fromSharedAccessFlags(
-                      Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC,
-                      false);
-              methodBuilder
-                  .setName(emulatedDispatchMethod.getName())
-                  .setProto(emulatedDispatchMethod.getProto())
-                  .setAccessFlags(flags);
-            });
-  }
-
-  private <SCB extends SyntheticClassBuilder<?, ?>> void buildHolderDispatchMethod(
-      SCB classBuilder, DexClassAndMethod emulatedDispatchMethod, DexMethod itfMethod) {
-    classBuilder.addMethod(
-        methodBuilder -> {
-          DexMethod desugarMethod =
-              appView
-                  .options()
-                  .desugaredLibraryConfiguration
-                  .retargetMethod(emulatedDispatchMethod, appView);
-          assert desugarMethod
-              != null; // This method is reached only for retarget core lib members.
-          methodBuilder
-              .setName(emulatedDispatchMethod.getName())
-              .setProto(desugarMethod.proto)
-              .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
-              .setCode(
-                  methodSig ->
-                      new EmulateInterfaceSyntheticCfCodeProvider(
-                              emulatedDispatchMethod.getHolderType(),
-                              desugarMethod,
-                              itfMethod,
-                              Collections.emptyList(),
-                              appView)
-                          .generateCfCode());
-        });
-  }
-
-  @Deprecated // Use Cf to Cf desugaring.
-  public void synthesizeRetargetClasses(IRConverter converter, ExecutorService executorService)
-      throws ExecutionException {
-    assert appView.enableWholeProgramOptimizations();
-    new EmulatedDispatchTreeFixer().fixApp(null);
-    converter.processMethodsConcurrently(forwardingMethods, executorService);
-  }
-
-  // The rewrite of virtual calls requires to go through emulate dispatch. This class is responsible
-  // for inserting interfaces on library boundaries and forwarding methods in the program, and to
-  // synthesize the interfaces and emulated dispatch classes in the desugared library.
-  class EmulatedDispatchTreeFixer {
-
-    void fixApp(DesugaredLibraryRetargeterEventConsumer eventConsumer) {
-      if (appView.options().isDesugaredLibraryCompilation()) {
-        synthesizeEmulatedDispatchMethods(eventConsumer);
-      } else {
-        addInterfacesAndForwardingMethods(eventConsumer);
-      }
-    }
-
-    private void addInterfacesAndForwardingMethods(
-        DesugaredLibraryRetargeterEventConsumer eventConsumer) {
-      assert !appView.options().isDesugaredLibraryCompilation();
-      Map<DexType, List<DexClassAndMethod>> map = Maps.newIdentityHashMap();
-      for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) {
-        map.putIfAbsent(emulatedDispatchMethod.getHolderType(), new ArrayList<>(1));
-        map.get(emulatedDispatchMethod.getHolderType()).add(emulatedDispatchMethod);
-      }
-      for (DexProgramClass clazz : appView.appInfo().classes()) {
-        if (clazz.superType == null) {
-          assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
-          continue;
-        }
-        DexClass superclass = appView.definitionFor(clazz.superType);
-        // Only performs computation if superclass is a library class, but not object to filter out
-        // the most common case.
-        if (superclass != null
-            && superclass.isLibraryClass()
-            && superclass.type != appView.dexItemFactory().objectType) {
-          map.forEach(
-              (type, methods) -> {
-                if (inherit(superclass.asLibraryClass(), type, emulatedDispatchMethods)) {
-                  addInterfacesAndForwardingMethods(eventConsumer, clazz, methods);
-                }
-              });
-        }
-      }
-    }
-
-    private boolean inherit(
-        DexLibraryClass clazz, DexType typeToInherit, DexClassAndMethodSet retarget) {
-      DexLibraryClass current = clazz;
-      while (current.type != appView.dexItemFactory().objectType) {
-        if (current.type == typeToInherit) {
-          return true;
-        }
-        DexClass dexClass = appView.definitionFor(current.superType);
-        if (dexClass == null || dexClass.isClasspathClass()) {
-          reportInvalidLibrarySupertype(current, retarget);
-          return false;
-        } else if (dexClass.isProgramClass()) {
-          // If dexClass is a program class, then it is already correctly desugared.
-          return false;
-        }
-        current = dexClass.asLibraryClass();
-      }
-      return false;
-    }
-
-    private void addInterfacesAndForwardingMethods(
-        DesugaredLibraryRetargeterEventConsumer eventConsumer,
-        DexProgramClass clazz,
-        List<DexClassAndMethod> methods) {
-      // DesugaredLibraryRetargeter emulate dispatch: insertion of a marker interface & forwarding
-      // methods.
-      // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
-      // applies up to 24.
-      for (DexClassAndMethod method : methods) {
-        DexClass dexClass = ensureEmulatedInterfaceDispatchMethod(method, eventConsumer);
-        if (clazz.interfaces.contains(dexClass.type)) {
-          // The class has already been desugared.
-          continue;
-        }
-        clazz.addExtraInterfaces(Collections.singletonList(new ClassTypeSignature(dexClass.type)));
-        if (clazz.lookupVirtualMethod(method.getReference()) == null) {
-          DexEncodedMethod newMethod = createForwardingMethod(method, clazz);
-          clazz.addVirtualMethod(newMethod);
-          if (eventConsumer != null) {
-            eventConsumer.acceptForwardingMethod(new ProgramMethod(clazz, newMethod));
-          } else {
-            assert appView.enableWholeProgramOptimizations();
-            forwardingMethods.add(new ProgramMethod(clazz, newMethod));
-          }
-        }
-      }
-    }
-
-    private DexEncodedMethod createForwardingMethod(DexClassAndMethod target, DexClass clazz) {
-      // NOTE: Never add a forwarding method to methods of classes unknown or coming from
-      // android.jar
-      // even if this results in invalid code, these classes are never desugared.
-      // In desugared library, emulated interface methods can be overridden by retarget lib members.
-      DexMethod forwardMethod =
-          appView.options().desugaredLibraryConfiguration.retargetMethod(target, appView);
-      assert forwardMethod != null && forwardMethod != target.getReference();
-      return DexEncodedMethod.createDesugaringForwardingMethod(
-          target, clazz, forwardMethod, appView.dexItemFactory());
-    }
-
-    private void synthesizeEmulatedDispatchMethods(
-        DesugaredLibraryRetargeterEventConsumer eventConsumer) {
-      assert appView.options().isDesugaredLibraryCompilation();
-      if (emulatedDispatchMethods.isEmpty()) {
-        return;
-      }
-      for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) {
-        ensureEmulatedHolderDispatchMethod(emulatedDispatchMethod, eventConsumer);
-      }
-    }
-
-    private void reportInvalidLibrarySupertype(
-        DexLibraryClass libraryClass, DexClassAndMethodSet retarget) {
-      DexClass dexClass = appView.definitionFor(libraryClass.superType);
-      String message;
-      if (dexClass == null) {
-        message = "missing";
-      } else if (dexClass.isClasspathClass()) {
-        message = "a classpath class";
-      } else {
-        message = "INVALID";
-        assert false;
-      }
-      appView
-          .options()
-          .warningInvalidLibrarySuperclassForDesugar(
-              dexClass == null ? libraryClass.getOrigin() : dexClass.getOrigin(),
-              libraryClass.type,
-              libraryClass.superType,
-              message,
-              retarget);
-    }
-
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeterEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeterEventConsumer.java
deleted file mode 100644
index 3806aa6..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeterEventConsumer.java
+++ /dev/null
@@ -1,18 +0,0 @@
-// 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;
-
-import com.android.tools.r8.graph.DexClasspathClass;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ProgramMethod;
-
-public interface DesugaredLibraryRetargeterEventConsumer {
-
-  void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz);
-
-  void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz);
-
-  void acceptForwardingMethod(ProgramMethod method);
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
index 5b25c84..6cd9be6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
@@ -7,9 +7,9 @@
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.utils.ThrowingConsumer;
-import java.util.function.Consumer;
 
 public class EmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
 
@@ -58,12 +58,7 @@
   }
 
   @Override
-  public void withDesugaredLibraryRetargeter(Consumer<DesugaredLibraryRetargeter> consumer) {
-    // Intentionally empty.
-  }
-
-  @Override
-  public void withRecordRewriter(Consumer<RecordRewriter> consumer) {
-    // Intentionally empty.
+  public RetargetingInfo getRetargetingInfo() {
+    return null;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 723e8b1..8437128 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -14,11 +14,14 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.NonEmptyCfClassDesugaringCollection;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.desugar.records.RecordRewriter;
 import com.android.tools.r8.ir.desugar.stringconcat.StringConcatInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.twr.TwrCloseResourceInstructionDesugaring;
 import com.android.tools.r8.utils.IntBox;
@@ -30,7 +33,6 @@
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
-import java.util.function.Consumer;
 
 public class NonEmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
 
@@ -53,7 +55,6 @@
     BackportedMethodRewriter backportedMethodRewriter = null;
     desugaredLibraryRetargeter =
         appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
-                || appView.enableWholeProgramOptimizations()
             ? null
             : new DesugaredLibraryRetargeter(appView);
     if (desugaredLibraryRetargeter != null) {
@@ -305,16 +306,10 @@
   }
 
   @Override
-  public void withDesugaredLibraryRetargeter(Consumer<DesugaredLibraryRetargeter> consumer) {
+  public RetargetingInfo getRetargetingInfo() {
     if (desugaredLibraryRetargeter != null) {
-      consumer.accept(desugaredLibraryRetargeter);
+      return desugaredLibraryRetargeter.getRetargetingInfo();
     }
-  }
-
-  @Override
-  public void withRecordRewriter(Consumer<RecordRewriter> consumer) {
-    if (recordRewriter != null) {
-      consumer.accept(recordRewriter);
-    }
+    return null;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
similarity index 97%
rename from src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
index 5fc1537..42db4ed 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// 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;
+package com.android.tools.r8.ir.desugar.desugaredlibrary;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -164,6 +164,18 @@
     }
   }
 
+  public ProgramMethod generateCallbackIfRequired(ProgramMethod method) {
+    if (!shouldRegisterCallback(method)) {
+      return null;
+    }
+    if (trackedCallBackAPIs != null) {
+      trackedCallBackAPIs.add(method.getReference());
+    }
+    ProgramMethod callback = generateCallbackMethod(method.getDefinition(), method.getHolder());
+    method.getHolder().addVirtualMethod(callback.getDefinition());
+    return callback;
+  }
+
   public boolean shouldRegisterCallback(ProgramMethod method) {
     // Any override of a library method can be called by the library.
     // We duplicate the method to have a vivified type version callable by the library and
@@ -186,6 +198,19 @@
             .containsKey(method.getHolderType())) {
       return false;
     }
+    // In R8 we should be in the enqueuer, therefore we can duplicate a default method and both
+    // methods will be desugared.
+    // In D8, this happens after interface method desugaring, we cannot introduce new default
+    // methods, but we do not need to since this is a library override (invokes will resolve) and
+    // all implementors have been enhanced with a forwarding method which will be duplicated.
+    if (!appView.enableWholeProgramOptimizations()) {
+      if (method.getHolder().isInterface()
+          && method.getDefinition().isDefaultMethod()
+          && (!appView.options().canUseDefaultAndStaticInterfaceMethods()
+              || appView.options().isDesugaredLibraryCompilation())) {
+        return false;
+      }
+    }
     if (!appView.options().desugaredLibraryConfiguration.supportAllCallbacksFromLibrary
         && appView.options().isDesugaredLibraryCompilation()) {
       return false;
@@ -247,19 +272,6 @@
   }
 
   private synchronized void registerCallback(ProgramMethod method) {
-    // In R8 we should be in the enqueuer, therefore we can duplicate a default method and both
-    // methods will be desugared.
-    // In D8, this happens after interface method desugaring, we cannot introduce new default
-    // methods, but we do not need to since this is a library override (invokes will resolve) and
-    // all implementors have been enhanced with a forwarding method which will be duplicated.
-    if (!appView.enableWholeProgramOptimizations()) {
-      if (method.getHolder().isInterface()
-          && method.getDefinition().isDefaultMethod()
-          && (!appView.options().canUseDefaultAndStaticInterfaceMethods()
-              || appView.options().isDesugaredLibraryCompilation())) {
-        return;
-      }
-    }
     if (trackedCallBackAPIs != null) {
       trackedCallBackAPIs.add(method.getReference());
     }
@@ -315,12 +327,7 @@
   }
 
   public SortedProgramMethodSet generateCallbackMethods() {
-    if (appView.options().testing.trackDesugaredAPIConversions) {
-      generateTrackDesugaredAPIWarnings(trackedAPIs, "");
-      generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ");
-      trackedAPIs.clear();
-      trackedCallBackAPIs.clear();
-    }
+    generateTrackingWarnings();
     SortedProgramMethodSet allCallbackMethods = SortedProgramMethodSet.create();
     pendingCallBackMethods.forEach(
         (clazz, callbacks) -> {
@@ -337,6 +344,15 @@
     return allCallbackMethods;
   }
 
+  public void generateTrackingWarnings() {
+    if (appView.options().testing.trackDesugaredAPIConversions) {
+      generateTrackDesugaredAPIWarnings(trackedAPIs, "");
+      generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ");
+      trackedAPIs.clear();
+      trackedCallBackAPIs.clear();
+    }
+  }
+
   public void synthesizeWrappers(Consumer<DexClasspathClass> synthesizedCallback) {
     wrapperSynthesizor.synthesizeWrappersForClasspath(synthesizedCallback);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java
similarity index 98%
rename from src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java
index a069091..cf81745 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfiguration.java
@@ -1,7 +1,7 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// 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;
+package com.android.tools.r8.ir.desugar.desugaredlibrary;
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper.DesugarPrefixRewritingMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfigurationParser.java
similarity index 98%
rename from src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfigurationParser.java
index 9b0da2d..9b0862e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryConfigurationParser.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// 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;
+package com.android.tools.r8.ir.desugar.desugaredlibrary;
 
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.graph.DexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java
new file mode 100644
index 0000000..43b26e1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java
@@ -0,0 +1,248 @@
+// 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.desugaredlibrary;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeter.InvokeRetargetingResult.NO_REWRITING;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+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.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import org.objectweb.asm.Opcodes;
+
+public class DesugaredLibraryRetargeter implements CfInstructionDesugaring {
+
+  private final AppView<?> appView;
+  private final DesugaredLibraryRetargeterSyntheticHelper syntheticHelper;
+
+  private final RetargetingInfo retargetingInfo;
+  private final Map<DexMethod, DexMethod> retargetLibraryMember;
+  private final Map<DexString, List<DexMethod>> nonFinalHolderRewrites;
+  private final DexClassAndMethodSet emulatedDispatchMethods;
+
+  public DesugaredLibraryRetargeter(AppView<?> appView) {
+    this.appView = appView;
+    this.syntheticHelper = new DesugaredLibraryRetargeterSyntheticHelper(appView);
+    retargetingInfo = RetargetingInfo.get(appView);
+    retargetLibraryMember = retargetingInfo.getRetargetLibraryMember();
+    nonFinalHolderRewrites = retargetingInfo.getNonFinalHolderRewrites();
+    emulatedDispatchMethods = retargetingInfo.getEmulatedDispatchMethods();
+  }
+
+  // Used by the ListOfBackportedMethods utility.
+  public void visit(Consumer<DexMethod> consumer) {
+    retargetLibraryMember.keySet().forEach(consumer);
+  }
+
+  public RetargetingInfo getRetargetingInfo() {
+    return retargetingInfo;
+  }
+
+  @Override
+  public Collection<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
+    InvokeRetargetingResult invokeRetargetingResult = computeNewInvokeTarget(instruction, context);
+
+    if (!invokeRetargetingResult.hasNewInvokeTarget()) {
+      return null;
+    }
+
+    DexMethod newInvokeTarget = invokeRetargetingResult.getNewInvokeTarget(eventConsumer);
+    return Collections.singletonList(
+        new CfInvoke(Opcodes.INVOKESTATIC, newInvokeTarget, instruction.asInvoke().isInterface()));
+  }
+
+  @Override
+  public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+    return computeNewInvokeTarget(instruction, context).hasNewInvokeTarget();
+  }
+
+  @Deprecated // Use Cf to Cf desugaring instead.
+  public void desugar(IRCode code) {
+    if (retargetLibraryMember.isEmpty()) {
+      return;
+    }
+
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      if (!instruction.isInvokeMethod()) {
+        continue;
+      }
+
+      InvokeMethod invoke = instruction.asInvokeMethod();
+      DexMethod invokedMethod = invoke.getInvokedMethod();
+      boolean isInterface = invoke.getInterfaceBit();
+
+      InvokeRetargetingResult invokeRetargetingResult =
+          computeNewInvokeTarget(
+              invokedMethod, isInterface, invoke.isInvokeSuper(), code.context());
+      if (invokeRetargetingResult.hasNewInvokeTarget()) {
+        DexMethod newInvokeTarget = invokeRetargetingResult.getNewInvokeTarget(null);
+        iterator.replaceCurrentInstruction(
+            new InvokeStatic(newInvokeTarget, invoke.outValue(), invoke.inValues()));
+      }
+    }
+  }
+
+  static class InvokeRetargetingResult {
+
+    static InvokeRetargetingResult NO_REWRITING =
+        new InvokeRetargetingResult(false, ignored -> null);
+
+    private final boolean hasNewInvokeTarget;
+    private final Function<DesugaredLibraryRetargeterInstructionEventConsumer, DexMethod>
+        newInvokeTargetSupplier;
+
+    static InvokeRetargetingResult createInvokeRetargetingResult(DexMethod retarget) {
+      if (retarget == null) {
+        return NO_REWRITING;
+      }
+      return new InvokeRetargetingResult(true, ignored -> retarget);
+    }
+
+    private InvokeRetargetingResult(
+        boolean hasNewInvokeTarget,
+        Function<DesugaredLibraryRetargeterInstructionEventConsumer, DexMethod>
+            newInvokeTargetSupplier) {
+      this.hasNewInvokeTarget = hasNewInvokeTarget;
+      this.newInvokeTargetSupplier = newInvokeTargetSupplier;
+    }
+
+    public boolean hasNewInvokeTarget() {
+      return hasNewInvokeTarget;
+    }
+
+    public DexMethod getNewInvokeTarget(
+        DesugaredLibraryRetargeterInstructionEventConsumer eventConsumer) {
+      assert hasNewInvokeTarget();
+      return newInvokeTargetSupplier.apply(eventConsumer);
+    }
+  }
+
+  public boolean hasNewInvokeTarget(
+      DexMethod invokedMethod, boolean isInterface, boolean isInvokeSuper, ProgramMethod context) {
+    return computeNewInvokeTarget(invokedMethod, isInterface, isInvokeSuper, context)
+        .hasNewInvokeTarget();
+  }
+
+  private InvokeRetargetingResult computeNewInvokeTarget(
+      CfInstruction instruction, ProgramMethod context) {
+    if (retargetLibraryMember.isEmpty() || !instruction.isInvoke()) {
+      return NO_REWRITING;
+    }
+    CfInvoke cfInvoke = instruction.asInvoke();
+    return computeNewInvokeTarget(
+        cfInvoke.getMethod(),
+        cfInvoke.isInterface(),
+        cfInvoke.isInvokeSuper(context.getHolderType()),
+        context);
+  }
+
+  private InvokeRetargetingResult computeNewInvokeTarget(
+      DexMethod invokedMethod, boolean isInterface, boolean isInvokeSuper, ProgramMethod context) {
+    InvokeRetargetingResult retarget = computeRetargetedMethod(invokedMethod, isInterface);
+    if (!retarget.hasNewInvokeTarget()) {
+      return NO_REWRITING;
+    }
+    if (isInvokeSuper && matchesNonFinalHolderRewrite(invokedMethod)) {
+      DexClassAndMethod superTarget =
+          appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
+      // Final methods can be rewritten as a normal invoke.
+      if (superTarget != null && !superTarget.getAccessFlags().isFinal()) {
+        return InvokeRetargetingResult.createInvokeRetargetingResult(
+            appView.options().desugaredLibraryConfiguration.retargetMethod(superTarget, appView));
+      }
+    }
+    return retarget;
+  }
+
+  private InvokeRetargetingResult computeRetargetedMethod(
+      DexMethod invokedMethod, boolean isInterface) {
+    InvokeRetargetingResult invokeRetargetingResult = computeRetargetLibraryMember(invokedMethod);
+    if (!invokeRetargetingResult.hasNewInvokeTarget()) {
+      if (!matchesNonFinalHolderRewrite(invokedMethod)) {
+        return NO_REWRITING;
+      }
+      // We need to force resolution, even on d8, to know if the invoke has to be rewritten.
+      ResolutionResult resolutionResult =
+          appView.appInfoForDesugaring().resolveMethod(invokedMethod, isInterface);
+      if (resolutionResult.isFailedResolution()) {
+        return NO_REWRITING;
+      }
+      DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
+      assert singleTarget != null;
+      invokeRetargetingResult = computeRetargetLibraryMember(singleTarget.getReference());
+    }
+    return invokeRetargetingResult;
+  }
+
+  private InvokeRetargetingResult computeRetargetLibraryMember(DexMethod method) {
+    DexClassAndMethod emulatedMethod = emulatedDispatchMethods.get(method);
+    if (emulatedMethod != null) {
+      assert !emulatedMethod.getAccessFlags().isStatic();
+      return new InvokeRetargetingResult(
+          true,
+          eventConsumer -> {
+            DexType newHolder =
+                syntheticHelper.ensureEmulatedHolderDispatchMethod(emulatedMethod, eventConsumer)
+                    .type;
+            return computeRetargetMethod(
+                method, emulatedMethod.getAccessFlags().isStatic(), newHolder);
+          });
+    }
+    return InvokeRetargetingResult.createInvokeRetargetingResult(retargetLibraryMember.get(method));
+  }
+
+  private boolean matchesNonFinalHolderRewrite(DexMethod method) {
+    List<DexMethod> dexMethods = nonFinalHolderRewrites.get(method.name);
+    if (dexMethods == null) {
+      return false;
+    }
+    for (DexMethod dexMethod : dexMethods) {
+      if (method.match(dexMethod)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  DexMethod computeRetargetMethod(DexMethod method, boolean isStatic, DexType newHolder) {
+    DexItemFactory factory = appView.dexItemFactory();
+    DexProto newProto = isStatic ? method.getProto() : factory.prependHolderToProto(method);
+    return factory.createMethod(newHolder, newProto, method.getName());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterInstructionEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterInstructionEventConsumer.java
new file mode 100644
index 0000000..ae7630d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterInstructionEventConsumer.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface DesugaredLibraryRetargeterInstructionEventConsumer {
+
+  void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz);
+
+  void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz);
+
+  void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface);
+
+  interface DesugaredLibraryRetargeterPostProcessingEventConsumer
+      extends DesugaredLibraryRetargeterInstructionEventConsumer {
+
+    void acceptForwardingMethod(ProgramMethod method);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterLibraryTypeSynthesizor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterLibraryTypeSynthesizor.java
new file mode 100644
index 0000000..8c5a747
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterLibraryTypeSynthesizor.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.desugaredlibrary;
+
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.NestHostClassAttribute;
+import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeSet;
+
+public class DesugaredLibraryRetargeterLibraryTypeSynthesizor {
+
+  public static void checkForAssumedLibraryTypes(AppView<?> appView) {
+    Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+        appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+    for (DexString methodName : retargetCoreLibMember.keySet()) {
+      for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
+        DexClass typeClass = appView.definitionFor(inType);
+        if (typeClass == null) {
+          warnMissingRetargetCoreLibraryMember(inType, appView);
+        }
+      }
+    }
+  }
+
+  public static void amendLibraryWithRetargetedMembers(AppView<AppInfoWithClassHierarchy> appView) {
+    Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+        appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+    Map<DexType, DexLibraryClass> synthesizedLibraryClasses =
+        synthesizeLibraryClassesForRetargetedMembers(appView, retargetCoreLibMember);
+    Map<DexLibraryClass, Set<DexEncodedMethod>> synthesizedLibraryMethods =
+        synthesizedMembersForRetargetClasses(
+            appView, retargetCoreLibMember, synthesizedLibraryClasses);
+    synthesizedLibraryMethods.forEach(DexLibraryClass::addDirectMethods);
+    DirectMappedDexApplication newApplication =
+        appView
+            .appInfo()
+            .app()
+            .asDirect()
+            .builder()
+            .addLibraryClasses(synthesizedLibraryClasses.values())
+            .build();
+    appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(app -> newApplication));
+  }
+
+  private static Map<DexType, DexLibraryClass> synthesizeLibraryClassesForRetargetedMembers(
+      AppView<AppInfoWithClassHierarchy> appView,
+      Map<DexString, Map<DexType, DexType>> retargetCoreLibMember) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    Map<DexType, DexLibraryClass> synthesizedLibraryClasses = new LinkedHashMap<>();
+    for (Map<DexType, DexType> oldToNewTypeMap : retargetCoreLibMember.values()) {
+      for (DexType newType : oldToNewTypeMap.values()) {
+        if (appView.definitionFor(newType) == null) {
+          synthesizedLibraryClasses.computeIfAbsent(
+              newType,
+              type ->
+                  // Synthesize a library class with the given name. Note that this is assuming that
+                  // the library class inherits directly from java.lang.Object, does not implement
+                  // any interfaces, etc.
+                  new DexLibraryClass(
+                      type,
+                      Kind.CF,
+                      new SynthesizedOrigin(
+                          "Desugared library retargeter", DesugaredLibraryRetargeter.class),
+                      ClassAccessFlags.fromCfAccessFlags(Constants.ACC_PUBLIC),
+                      dexItemFactory.objectType,
+                      DexTypeList.empty(),
+                      dexItemFactory.createString("DesugaredLibraryRetargeter"),
+                      NestHostClassAttribute.none(),
+                      NestMemberClassAttribute.emptyList(),
+                      EnclosingMethodAttribute.none(),
+                      InnerClassAttribute.emptyList(),
+                      ClassSignature.noSignature(),
+                      DexAnnotationSet.empty(),
+                      DexEncodedField.EMPTY_ARRAY,
+                      DexEncodedField.EMPTY_ARRAY,
+                      DexEncodedMethod.EMPTY_ARRAY,
+                      DexEncodedMethod.EMPTY_ARRAY,
+                      dexItemFactory.getSkipNameValidationForTesting()));
+        }
+      }
+    }
+    return synthesizedLibraryClasses;
+  }
+
+  private static Map<DexLibraryClass, Set<DexEncodedMethod>> synthesizedMembersForRetargetClasses(
+      AppView<AppInfoWithClassHierarchy> appView,
+      Map<DexString, Map<DexType, DexType>> retargetCoreLibMember,
+      Map<DexType, DexLibraryClass> synthesizedLibraryClasses) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    Map<DexLibraryClass, Set<DexEncodedMethod>> synthesizedMembers = new IdentityHashMap<>();
+    for (Entry<DexString, Map<DexType, DexType>> entry : retargetCoreLibMember.entrySet()) {
+      DexString methodName = entry.getKey();
+      Map<DexType, DexType> types = entry.getValue();
+      types.forEach(
+          (oldType, newType) -> {
+            DexClass oldClass = appView.definitionFor(oldType);
+            DexLibraryClass newClass = synthesizedLibraryClasses.get(newType);
+            if (oldClass == null || newClass == null) {
+              return;
+            }
+            for (DexEncodedMethod method :
+                oldClass.methods(method -> method.getName() == methodName)) {
+              DexMethod retargetMethod = method.getReference().withHolder(newType, dexItemFactory);
+              if (!method.isStatic()) {
+                retargetMethod = retargetMethod.withExtraArgumentPrepended(oldType, dexItemFactory);
+              }
+              synthesizedMembers
+                  .computeIfAbsent(
+                      newClass,
+                      ignore -> new TreeSet<>(Comparator.comparing(DexEncodedMethod::getReference)))
+                  .add(
+                      new DexEncodedMethod(
+                          retargetMethod,
+                          MethodAccessFlags.fromCfAccessFlags(
+                              Constants.ACC_PUBLIC | Constants.ACC_STATIC, false),
+                          MethodTypeSignature.noSignature(),
+                          DexAnnotationSet.empty(),
+                          ParameterAnnotationsList.empty(),
+                          null,
+                          true));
+            }
+          });
+    }
+    return synthesizedMembers;
+  }
+
+  private static void warnMissingRetargetCoreLibraryMember(DexType type, AppView<?> appView) {
+    StringDiagnostic warning =
+        new StringDiagnostic(
+            "Cannot retarget core library member "
+                + type.getName()
+                + " because the class is missing.");
+    appView.options().reporter.warning(warning);
+  }
+}
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
new file mode 100644
index 0000000..0b0039e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
@@ -0,0 +1,174 @@
+// 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.desugaredlibrary;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterInstructionEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer;
+import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
+import com.google.common.collect.Maps;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+// The rewrite of virtual calls requires to go through emulate dispatch. This class is responsible
+// for inserting interfaces on library boundaries and forwarding methods in the program, and to
+// synthesize the interfaces and emulated dispatch classes in the desugared library.
+public class DesugaredLibraryRetargeterPostProcessor implements CfPostProcessingDesugaring {
+
+  private final AppView<?> appView;
+  private final DesugaredLibraryRetargeterSyntheticHelper syntheticHelper;
+  private final DexClassAndMethodSet emulatedDispatchMethods;
+
+  public DesugaredLibraryRetargeterPostProcessor(
+      AppView<?> appView, RetargetingInfo retargetingInfo) {
+    this.appView = appView;
+    this.syntheticHelper = new DesugaredLibraryRetargeterSyntheticHelper(appView);
+    emulatedDispatchMethods = retargetingInfo.getEmulatedDispatchMethods();
+  }
+
+  @Override
+  public void postProcessingDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer) {
+    if (appView.options().isDesugaredLibraryCompilation()) {
+      ensureEmulatedDispatchMethodsSynthesized(eventConsumer);
+    } else {
+      ensureInterfacesAndForwardingMethodsSynthesized(eventConsumer);
+    }
+  }
+
+  private void ensureInterfacesAndForwardingMethodsSynthesized(
+      DesugaredLibraryRetargeterPostProcessingEventConsumer eventConsumer) {
+    assert !appView.options().isDesugaredLibraryCompilation();
+    Map<DexType, List<DexClassAndMethod>> map = Maps.newIdentityHashMap();
+    for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) {
+      map.putIfAbsent(emulatedDispatchMethod.getHolderType(), new ArrayList<>(1));
+      map.get(emulatedDispatchMethod.getHolderType()).add(emulatedDispatchMethod);
+    }
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (clazz.superType == null) {
+        assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
+        continue;
+      }
+      DexClass superclass = appView.definitionFor(clazz.superType);
+      // Only performs computation if superclass is a library class, but not object to filter out
+      // the most common case.
+      if (superclass != null
+          && superclass.isLibraryClass()
+          && superclass.type != appView.dexItemFactory().objectType) {
+        map.forEach(
+            (type, methods) -> {
+              if (inherit(superclass.asLibraryClass(), type, emulatedDispatchMethods)) {
+                ensureInterfacesAndForwardingMethodsSynthesized(eventConsumer, clazz, methods);
+              }
+            });
+      }
+    }
+  }
+
+  private boolean inherit(
+      DexLibraryClass clazz, DexType typeToInherit, DexClassAndMethodSet retarget) {
+    DexLibraryClass current = clazz;
+    while (current.type != appView.dexItemFactory().objectType) {
+      if (current.type == typeToInherit) {
+        return true;
+      }
+      DexClass dexClass = appView.definitionFor(current.superType);
+      if (dexClass == null || dexClass.isClasspathClass()) {
+        reportInvalidLibrarySupertype(current, retarget);
+        return false;
+      } else if (dexClass.isProgramClass()) {
+        // If dexClass is a program class, then it is already correctly desugared.
+        return false;
+      }
+      current = dexClass.asLibraryClass();
+    }
+    return false;
+  }
+
+  private void ensureInterfacesAndForwardingMethodsSynthesized(
+      DesugaredLibraryRetargeterPostProcessingEventConsumer eventConsumer,
+      DexProgramClass clazz,
+      List<DexClassAndMethod> methods) {
+    // DesugaredLibraryRetargeter emulate dispatch: insertion of a marker interface & forwarding
+    // methods.
+    // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
+    // applies up to 24.
+    for (DexClassAndMethod method : methods) {
+      DexClass newInterface =
+          syntheticHelper.ensureEmulatedInterfaceDispatchMethod(method, eventConsumer);
+      if (clazz.interfaces.contains(newInterface.type)) {
+        // The class has already been desugared.
+        continue;
+      }
+      clazz.addExtraInterfaces(
+          Collections.singletonList(new ClassTypeSignature(newInterface.type)));
+      eventConsumer.acceptInterfaceInjection(clazz, newInterface);
+      if (clazz.lookupVirtualMethod(method.getReference()) == null) {
+        DexEncodedMethod newMethod = createForwardingMethod(method, clazz);
+        clazz.addVirtualMethod(newMethod);
+        eventConsumer.acceptForwardingMethod(new ProgramMethod(clazz, newMethod));
+      }
+    }
+  }
+
+  private DexEncodedMethod createForwardingMethod(DexClassAndMethod target, DexClass clazz) {
+    // NOTE: Never add a forwarding method to methods of classes unknown or coming from
+    // android.jar
+    // even if this results in invalid code, these classes are never desugared.
+    // In desugared library, emulated interface methods can be overridden by retarget lib members.
+    DexMethod forwardMethod =
+        appView.options().desugaredLibraryConfiguration.retargetMethod(target, appView);
+    assert forwardMethod != null && forwardMethod != target.getReference();
+    DexEncodedMethod desugaringForwardingMethod =
+        DexEncodedMethod.createDesugaringForwardingMethod(
+            target, clazz, forwardMethod, appView.dexItemFactory());
+    desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
+    return desugaringForwardingMethod;
+  }
+
+  private void ensureEmulatedDispatchMethodsSynthesized(
+      DesugaredLibraryRetargeterInstructionEventConsumer eventConsumer) {
+    assert appView.options().isDesugaredLibraryCompilation();
+    if (emulatedDispatchMethods.isEmpty()) {
+      return;
+    }
+    for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) {
+      syntheticHelper.ensureEmulatedHolderDispatchMethod(emulatedDispatchMethod, eventConsumer);
+    }
+  }
+
+  private void reportInvalidLibrarySupertype(
+      DexLibraryClass libraryClass, DexClassAndMethodSet retarget) {
+    DexClass dexClass = appView.definitionFor(libraryClass.superType);
+    String message;
+    if (dexClass == null) {
+      message = "missing";
+    } else if (dexClass.isClasspathClass()) {
+      message = "a classpath class";
+    } else {
+      message = "INVALID";
+      assert false;
+    }
+    appView
+        .options()
+        .warningInvalidLibrarySuperclassForDesugar(
+            dexClass == null ? libraryClass.getOrigin() : dexClass.getOrigin(),
+            libraryClass.type,
+            libraryClass.superType,
+            message,
+            retarget);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java
new file mode 100644
index 0000000..1f8322f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java
@@ -0,0 +1,169 @@
+// 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.desugaredlibrary;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathOrLibraryClass;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.synthetic.EmulateInterfaceSyntheticCfCodeProvider;
+import com.android.tools.r8.synthesis.SyntheticClassBuilder;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.Collections;
+
+public class DesugaredLibraryRetargeterSyntheticHelper {
+
+  private final AppView<?> appView;
+
+  public DesugaredLibraryRetargeterSyntheticHelper(AppView<?> appView) {
+    this.appView = appView;
+  }
+
+  public DexClass ensureEmulatedHolderDispatchMethod(
+      DexClassAndMethod emulatedDispatchMethod,
+      DesugaredLibraryRetargeterInstructionEventConsumer eventConsumer) {
+    assert eventConsumer != null || appView.enableWholeProgramOptimizations();
+    DexClass interfaceClass =
+        ensureEmulatedInterfaceDispatchMethod(emulatedDispatchMethod, eventConsumer);
+    DexMethod itfMethod =
+        interfaceClass.lookupMethod(emulatedDispatchMethod.getReference()).getReference();
+    DexClass holderDispatch;
+    if (appView.options().isDesugaredLibraryCompilation()) {
+      holderDispatch =
+          appView
+              .getSyntheticItems()
+              .ensureFixedClass(
+                  SyntheticKind.RETARGET_CLASS,
+                  emulatedDispatchMethod.getHolder(),
+                  appView,
+                  classBuilder ->
+                      buildHolderDispatchMethod(classBuilder, emulatedDispatchMethod, itfMethod),
+                  clazz -> {
+                    if (eventConsumer != null) {
+                      eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz);
+                    }
+                  });
+    } else {
+      ClasspathOrLibraryClass context =
+          emulatedDispatchMethod.getHolder().asClasspathOrLibraryClass();
+      assert context != null;
+      holderDispatch =
+          appView
+              .getSyntheticItems()
+              .ensureFixedClasspathClass(
+                  SyntheticKind.RETARGET_CLASS,
+                  context,
+                  appView,
+                  classBuilder ->
+                      buildHolderDispatchMethod(classBuilder, emulatedDispatchMethod, itfMethod),
+                  clazz -> {
+                    if (eventConsumer != null) {
+                      eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz);
+                    }
+                  });
+    }
+    rewriteType(holderDispatch.type);
+    return holderDispatch;
+  }
+
+  public DexClass ensureEmulatedInterfaceDispatchMethod(
+      DexClassAndMethod emulatedDispatchMethod,
+      DesugaredLibraryRetargeterInstructionEventConsumer eventConsumer) {
+    assert eventConsumer != null || appView.enableWholeProgramOptimizations();
+    DexClass interfaceDispatch;
+    if (appView.options().isDesugaredLibraryCompilation()) {
+      interfaceDispatch =
+          appView
+              .getSyntheticItems()
+              .ensureFixedClass(
+                  SyntheticKind.RETARGET_INTERFACE,
+                  emulatedDispatchMethod.getHolder(),
+                  appView,
+                  classBuilder ->
+                      buildInterfaceDispatchMethod(classBuilder, emulatedDispatchMethod),
+                  clazz -> {
+                    if (eventConsumer != null) {
+                      eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz);
+                    }
+                  });
+    } else {
+      ClasspathOrLibraryClass context =
+          emulatedDispatchMethod.getHolder().asClasspathOrLibraryClass();
+      assert context != null;
+      interfaceDispatch =
+          appView
+              .getSyntheticItems()
+              .ensureFixedClasspathClass(
+                  SyntheticKind.RETARGET_INTERFACE,
+                  context,
+                  appView,
+                  classBuilder ->
+                      buildInterfaceDispatchMethod(classBuilder, emulatedDispatchMethod),
+                  clazz -> {
+                    if (eventConsumer != null) {
+                      eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz);
+                    }
+                  });
+    }
+    rewriteType(interfaceDispatch.type);
+    return interfaceDispatch;
+  }
+
+  private void buildInterfaceDispatchMethod(
+      SyntheticClassBuilder<?, ?> classBuilder, DexClassAndMethod emulatedDispatchMethod) {
+    classBuilder
+        .setInterface()
+        .addMethod(
+            methodBuilder -> {
+              MethodAccessFlags flags =
+                  MethodAccessFlags.fromSharedAccessFlags(
+                      Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC,
+                      false);
+              methodBuilder
+                  .setName(emulatedDispatchMethod.getName())
+                  .setProto(emulatedDispatchMethod.getProto())
+                  .setAccessFlags(flags);
+            });
+  }
+
+  private <SCB extends SyntheticClassBuilder<?, ?>> void buildHolderDispatchMethod(
+      SCB classBuilder, DexClassAndMethod emulatedDispatchMethod, DexMethod itfMethod) {
+    classBuilder.addMethod(
+        methodBuilder -> {
+          DexMethod desugarMethod =
+              appView
+                  .options()
+                  .desugaredLibraryConfiguration
+                  .retargetMethod(emulatedDispatchMethod, appView);
+          assert desugarMethod
+              != null; // This method is reached only for retarget core lib members.
+          methodBuilder
+              .setName(emulatedDispatchMethod.getName())
+              .setProto(desugarMethod.proto)
+              .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+              .setCode(
+                  methodSig ->
+                      new EmulateInterfaceSyntheticCfCodeProvider(
+                              emulatedDispatchMethod.getHolderType(),
+                              desugarMethod,
+                              itfMethod,
+                              Collections.emptyList(),
+                              appView)
+                          .generateCfCode());
+        });
+  }
+
+  private void rewriteType(DexType type) {
+    String newName =
+        appView.options().desugaredLibraryConfiguration.convertJavaNameToDesugaredLibrary(type);
+    DexType newType =
+        appView.dexItemFactory().createType(DescriptorUtils.javaTypeToDescriptor(newName));
+    appView.rewritePrefix.rewriteType(type, newType);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
similarity index 99%
rename from src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
index 00c8bff..6c1e889 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// 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;
+package com.android.tools.r8.ir.desugar.desugaredlibrary;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/RetargetingInfo.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/RetargetingInfo.java
new file mode 100644
index 0000000..525801f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/RetargetingInfo.java
@@ -0,0 +1,195 @@
+// 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.desugaredlibrary;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+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.utils.WorkList;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
+import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class RetargetingInfo {
+
+  private final Map<DexMethod, DexMethod> retargetLibraryMember;
+  // Map nonFinalRewrite hold a methodName -> method mapping for methods which are rewritten while
+  // the holder is non final. In this case d8 needs to force resolution of given methods to see if
+  // the invoke needs to be rewritten.
+  private final Map<DexString, List<DexMethod>> nonFinalHolderRewrites;
+  // Non final virtual library methods requiring generation of emulated dispatch.
+  private final DexClassAndMethodSet emulatedDispatchMethods;
+
+  RetargetingInfo(
+      Map<DexMethod, DexMethod> retargetLibraryMember,
+      Map<DexString, List<DexMethod>> nonFinalHolderRewrites,
+      DexClassAndMethodSet emulatedDispatchMethods) {
+    this.retargetLibraryMember = retargetLibraryMember;
+    this.nonFinalHolderRewrites = nonFinalHolderRewrites;
+    this.emulatedDispatchMethods = emulatedDispatchMethods;
+  }
+
+  public static synchronized RetargetingInfo get(AppView<?> appView) {
+    return new RetargetingInfoBuilder(appView).computeRetargetingInfo();
+  }
+
+  public Map<DexMethod, DexMethod> getRetargetLibraryMember() {
+    return retargetLibraryMember;
+  }
+
+  public Map<DexString, List<DexMethod>> getNonFinalHolderRewrites() {
+    return nonFinalHolderRewrites;
+  }
+
+  public DexClassAndMethodSet getEmulatedDispatchMethods() {
+    return emulatedDispatchMethods;
+  }
+
+  private static class RetargetingInfoBuilder {
+
+    private final AppView<?> appView;
+    private final Map<DexMethod, DexMethod> retargetLibraryMember = new IdentityHashMap<>();
+    private final Map<DexString, List<DexMethod>> nonFinalHolderRewrites = new IdentityHashMap<>();
+    private final DexClassAndMethodSet emulatedDispatchMethods = DexClassAndMethodSet.create();
+
+    public RetargetingInfoBuilder(AppView<?> appView) {
+      this.appView = appView;
+    }
+
+    private RetargetingInfo computeRetargetingInfo() {
+      DesugaredLibraryConfiguration desugaredLibraryConfiguration =
+          appView.options().desugaredLibraryConfiguration;
+      Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+          desugaredLibraryConfiguration.getRetargetCoreLibMember();
+      if (retargetCoreLibMember.isEmpty()) {
+        return new RetargetingInfo(
+            ImmutableMap.of(), ImmutableMap.of(), DexClassAndMethodSet.empty());
+      }
+      for (DexString methodName : retargetCoreLibMember.keySet()) {
+        for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
+          DexClass typeClass = appView.definitionFor(inType);
+          if (typeClass != null) {
+            DexType newHolder = retargetCoreLibMember.get(methodName).get(inType);
+            List<DexClassAndMethod> found = findMethodsWithName(methodName, typeClass);
+            for (DexClassAndMethod method : found) {
+              boolean emulatedDispatch = false;
+              DexMethod methodReference = method.getReference();
+              if (!typeClass.isFinal()) {
+                nonFinalHolderRewrites.putIfAbsent(method.getName(), new ArrayList<>());
+                nonFinalHolderRewrites.get(method.getName()).add(methodReference);
+                if (!method.getAccessFlags().isStatic()) {
+                  if (isEmulatedInterfaceDispatch(method)) {
+                    // In this case interface method rewriter takes care of it.
+                    continue;
+                  } else if (!method.getAccessFlags().isFinal()) {
+                    // Virtual rewrites require emulated dispatch for inheritance.
+                    // The call is rewritten to the dispatch holder class instead.
+                    emulatedDispatchMethods.add(method);
+                    emulatedDispatch = true;
+                  }
+                }
+              }
+              if (!emulatedDispatch) {
+                retargetLibraryMember.put(
+                    methodReference,
+                    computeRetargetMethod(
+                        methodReference, method.getAccessFlags().isStatic(), newHolder));
+              }
+            }
+          }
+        }
+      }
+      if (desugaredLibraryConfiguration.isLibraryCompilation()) {
+        // TODO(b/177977763): This is only a workaround rewriting invokes of j.u.Arrays.deepEquals0
+        // to j.u.DesugarArrays.deepEquals0.
+        DexItemFactory itemFactory = appView.options().dexItemFactory();
+        DexString name = itemFactory.createString("deepEquals0");
+        DexProto proto =
+            itemFactory.createProto(
+                itemFactory.booleanType, itemFactory.objectType, itemFactory.objectType);
+        DexMethod source =
+            itemFactory.createMethod(
+                itemFactory.createType(itemFactory.arraysDescriptor), proto, name);
+        DexMethod target =
+            computeRetargetMethod(
+                source, true, itemFactory.createType("Ljava/util/DesugarArrays;"));
+        retargetLibraryMember.put(source, target);
+        // TODO(b/181629049): This is only a workaround rewriting invokes of
+        //  j.u.TimeZone.getTimeZone taking a java.time.ZoneId.
+        name = itemFactory.createString("getTimeZone");
+        proto =
+            itemFactory.createProto(
+                itemFactory.createType("Ljava/util/TimeZone;"),
+                itemFactory.createType("Ljava/time/ZoneId;"));
+        source =
+            itemFactory.createMethod(itemFactory.createType("Ljava/util/TimeZone;"), proto, name);
+        target =
+            computeRetargetMethod(
+                source, true, itemFactory.createType("Ljava/util/DesugarTimeZone;"));
+        retargetLibraryMember.put(source, target);
+      }
+      return new RetargetingInfo(
+          ImmutableMap.copyOf(retargetLibraryMember),
+          ImmutableMap.copyOf(nonFinalHolderRewrites),
+          emulatedDispatchMethods);
+    }
+
+    DexMethod computeRetargetMethod(DexMethod method, boolean isStatic, DexType newHolder) {
+      DexItemFactory factory = appView.dexItemFactory();
+      DexProto newProto = isStatic ? method.getProto() : factory.prependHolderToProto(method);
+      return factory.createMethod(newHolder, newProto, method.getName());
+    }
+
+    private boolean isEmulatedInterfaceDispatch(DexClassAndMethod method) {
+      // Answers true if this method is already managed through emulated interface dispatch.
+      Map<DexType, DexType> emulateLibraryInterface =
+          appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
+      if (emulateLibraryInterface.isEmpty()) {
+        return false;
+      }
+      DexMethod methodToFind = method.getReference();
+      // Look-up all superclass and interfaces, if an emulated interface is found, and it implements
+      // the method, answers true.
+      WorkList<DexClass> worklist = WorkList.newIdentityWorkList(method.getHolder());
+      while (worklist.hasNext()) {
+        DexClass clazz = worklist.next();
+        if (clazz.isInterface()
+            && emulateLibraryInterface.containsKey(clazz.getType())
+            && clazz.lookupMethod(methodToFind) != null) {
+          return true;
+        }
+        // All super types are library class, or we are doing L8 compilation.
+        clazz.forEachImmediateSupertype(
+            superType -> {
+              DexClass superClass = appView.definitionFor(superType);
+              if (superClass != null) {
+                worklist.addIfNotSeen(superClass);
+              }
+            });
+      }
+      return false;
+    }
+
+    private List<DexClassAndMethod> findMethodsWithName(DexString methodName, DexClass clazz) {
+      List<DexClassAndMethod> found = new ArrayList<>();
+      clazz.forEachClassMethodMatching(
+          definition -> definition.getName() == methodName, found::add);
+      assert !found.isEmpty()
+          : "Should have found a method (library specifications) for "
+              + clazz.toSourceString()
+              + "."
+              + methodName
+              + ". Maybe the library used for the compilation should be newer.";
+      return found;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index 3381ece..d86d22c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -390,8 +389,7 @@
   // We introduce forwarding methods only once all desugaring has been performed to avoid
   // confusing the look-up with inserted forwarding methods.
   @Override
-  public final void finalizeProcessing(
-      DexApplication.Builder<?> builder, ProgramMethodSet synthesizedMethods) {
+  public final void finalizeProcessing(ProgramMethodSet synthesizedMethods) {
     newSyntheticMethods.forEach(
         (clazz, newForwardingMethods) -> {
           clazz.addVirtualMethods(newForwardingMethods.toDefinitionSet());
@@ -819,7 +817,7 @@
     // In desugared library, emulated interface methods can be overridden by retarget lib members.
     DexMethod forwardMethod =
         target.getHolder().isInterface()
-            ? rewriter.defaultAsMethodOfCompanionClass(target)
+            ? rewriter.ensureDefaultAsMethodOfCompanionClassStub(target).getReference()
             : appView.options().desugaredLibraryConfiguration.retargetMethod(target, appView);
     DexEncodedMethod desugaringForwardingMethod =
         DexEncodedMethod.createDesugaringForwardingMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
index 30fc05c..b9e9889 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
@@ -5,9 +5,8 @@
 
 import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.emulateInterfaceLibraryMethod;
 
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication.Builder;
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -37,6 +36,9 @@
 import java.util.Set;
 
 public final class EmulatedInterfaceProcessor implements InterfaceDesugaringProcessor {
+
+  private static final String JUNK_SUFFIX = "$JUNK";
+
   private final AppView<?> appView;
   private final InterfaceMethodRewriter rewriter;
   private final Map<DexType, DexType> emulatedInterfaces;
@@ -163,20 +165,20 @@
 
   private void synthesizeEmulatedInterfaceMethod(
       ProgramMethod method, DexProgramClass theInterface, SyntheticMethodBuilder methodBuilder) {
+    assert !method.getDefinition().isStatic();
     DexMethod libraryMethod =
         method
             .getReference()
             .withHolder(emulatedInterfaces.get(theInterface.type), appView.dexItemFactory());
     DexMethod companionMethod =
-        method.getAccessFlags().isStatic()
-            ? rewriter.staticAsMethodOfCompanionClass(method)
-            : rewriter.defaultAsMethodOfCompanionClass(method);
+        rewriter.ensureDefaultAsMethodOfProgramCompanionClassStub(method).getReference();
     List<Pair<DexType, DexMethod>> extraDispatchCases =
         getDispatchCases(method, theInterface, companionMethod);
     DexMethod emulatedMethod = emulateInterfaceLibraryMethod(method, appView.dexItemFactory());
     methodBuilder
         .setName(emulatedMethod.getName())
         .setProto(emulatedMethod.getProto())
+        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
         .setCode(
             theMethod ->
                 new EmulateInterfaceSyntheticCfCodeProvider(
@@ -185,10 +187,7 @@
                         libraryMethod,
                         extraDispatchCases,
                         appView)
-                    .generateCfCode())
-        .setAccessFlags(
-            MethodAccessFlags.fromSharedAccessFlags(
-                Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC, false));
+                    .generateCfCode());
   }
 
   private List<Pair<DexType, DexMethod>> getDispatchCases(
@@ -283,7 +282,7 @@
   }
 
   @Override
-  public void finalizeProcessing(Builder<?> builder, ProgramMethodSet synthesizedMethods) {
+  public void finalizeProcessing(ProgramMethodSet synthesizedMethods) {
     warnMissingEmulatedInterfaces();
     if (!appView.options().isDesugaredLibraryCompilation()) {
       return;
@@ -298,24 +297,29 @@
         }
       }
     }
-    // TODO(b/183918843): Investigate what to do for the filtering, the minimum would be to make
-    // the rewriting rule explicit instead of using the synthesized class prefix.
-    filterEmulatedInterfaceSubInterfaces(builder);
   }
 
-  private void filterEmulatedInterfaceSubInterfaces(Builder<?> builder) {
+  // TODO(b/183918843): Investigate what to do. The whole method is trying to fill a hole in the
+  //  desugaring library specifications by patching types and classes through questionable renaming.
+  public static void filterEmulatedInterfaceSubInterfaces(
+      AppView<?> appView, DexApplication.Builder<?> builder) {
+    assert appView.options().isDesugaredLibraryCompilation();
     ArrayList<DexProgramClass> filteredProgramClasses = new ArrayList<>();
-    for (DexProgramClass clazz : builder.getProgramClasses()) {
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
       if (clazz.isInterface()
-          && !rewriter.isEmulatedInterface(clazz.type)
+          && !appView
+              .options()
+              .desugaredLibraryConfiguration
+              .getEmulateLibraryInterface()
+              .containsKey(clazz.type)
           && !appView.rewritePrefix.hasRewrittenType(clazz.type, appView)
-          && isEmulatedInterfaceSubInterface(clazz)) {
+          && isEmulatedInterfaceSubInterface(clazz, appView)) {
         String newName =
             appView
                 .options()
                 .desugaredLibraryConfiguration
                 .convertJavaNameToDesugaredLibrary(clazz.type);
-        rewriter.addCompanionClassRewriteRule(clazz.type, newName);
+        InterfaceMethodRewriter.addCompanionClassRewriteRule(clazz.type, newName, appView);
       } else {
         filteredProgramClasses.add(clazz);
       }
@@ -323,12 +327,21 @@
     builder.replaceProgramClasses(filteredProgramClasses);
   }
 
-  private boolean isEmulatedInterfaceSubInterface(DexClass subInterface) {
-    assert !rewriter.isEmulatedInterface(subInterface.type);
+  private static boolean isEmulatedInterfaceSubInterface(
+      DexClass subInterface, AppView<?> appView) {
+    assert !appView
+        .options()
+        .desugaredLibraryConfiguration
+        .getEmulateLibraryInterface()
+        .containsKey(subInterface.type);
     LinkedList<DexType> workList = new LinkedList<>(Arrays.asList(subInterface.interfaces.values));
     while (!workList.isEmpty()) {
       DexType next = workList.removeFirst();
-      if (rewriter.isEmulatedInterface(next)) {
+      if (appView
+          .options()
+          .desugaredLibraryConfiguration
+          .getEmulateLibraryInterface()
+          .containsKey(next)) {
         return true;
       }
       DexClass nextClass = appView.definitionFor(next);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java
index c429fc7..7f3d047 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringProcessor.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.desugar.itf;
 
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 
@@ -21,5 +20,5 @@
   // All finalization phases of all desugaring processors are performed sequentially.
   // Complex computations should be avoided if possible here and be moved to the concurrent phase.
   // Classes may be mutated here (new methods can be inserted, etc.).
-  void finalizeProcessing(DexApplication.Builder<?> builder, ProgramMethodSet synthesizedMethods);
+  void finalizeProcessing(ProgramMethodSet synthesizedMethods);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
new file mode 100644
index 0000000..731316f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.itf;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+class InterfaceMethodProcessorFacade {
+
+  private final AppView<?> appView;
+
+  InterfaceMethodProcessorFacade(AppView<?> appView) {
+    this.appView = appView;
+  }
+
+  /** Runs the interfaceProcessor, the class processor and the emulated interface processor. */
+  void runInterfaceDesugaringProcessors(
+      InterfaceMethodRewriter rewriter,
+      IRConverter converter,
+      Flavor flavour,
+      ExecutorService executorService)
+      throws ExecutionException {
+    // During L8 compilation, emulated interfaces are processed to be renamed, to have
+    // their interfaces fixed-up and to generate the emulated dispatch code.
+    EmulatedInterfaceProcessor emulatedInterfaceProcessor =
+        new EmulatedInterfaceProcessor(appView, rewriter);
+
+    // Process all classes first. Add missing forwarding methods to
+    // replace desugared default interface methods.
+    ClassProcessor classProcessor = new ClassProcessor(appView, rewriter);
+
+    // Process interfaces, create companion or dispatch class if needed, move static
+    // methods to companion class, copy default interface methods to companion classes,
+    // make original default methods abstract, remove bridge methods, create dispatch
+    // classes if needed.
+    InterfaceProcessor interfaceProcessor = new InterfaceProcessor(appView, rewriter);
+
+    // The interface processors must be ordered so that finalization of the processing is performed
+    // in that order. The emulatedInterfaceProcessor has to be last at this point to avoid renaming
+    // emulated interfaces before the other processing.
+    ImmutableList<InterfaceDesugaringProcessor> orderedInterfaceDesugaringProcessors =
+        ImmutableList.of(classProcessor, interfaceProcessor, emulatedInterfaceProcessor);
+
+    SortedProgramMethodSet sortedSynthesizedMethods = SortedProgramMethodSet.createConcurrent();
+    processClassesConcurrently(
+        orderedInterfaceDesugaringProcessors, sortedSynthesizedMethods, flavour, executorService);
+    assert converter != null;
+    converter.processMethodsConcurrently(sortedSynthesizedMethods, executorService);
+  }
+
+  private boolean shouldProcess(DexProgramClass clazz, Flavor flavour) {
+    if (appView.isAlreadyLibraryDesugared(clazz)) {
+      return false;
+    }
+    return (!clazz.originatesFromDexResource() || flavour == Flavor.IncludeAllResources);
+  }
+
+  private void processClassesConcurrently(
+      List<InterfaceDesugaringProcessor> processors,
+      SortedProgramMethodSet sortedSynthesizedMethods,
+      Flavor flavour,
+      ExecutorService executorService)
+      throws ExecutionException {
+    ThreadUtils.processItems(
+        Iterables.filter(
+            appView.appInfo().classes(), (DexProgramClass clazz) -> shouldProcess(clazz, flavour)),
+        clazz -> {
+          for (InterfaceDesugaringProcessor processor : processors) {
+            processor.process(clazz, sortedSynthesizedMethods);
+          }
+        },
+        executorService);
+    for (InterfaceDesugaringProcessor processor : processors) {
+      processor.finalizeProcessing(sortedSynthesizedMethods);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 55598d1..413f803 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -25,7 +25,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.ClasspathOrLibraryClass;
-import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -33,11 +32,11 @@
 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.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.InvalidCode;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -57,10 +56,10 @@
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.stringconcat.StringConcatInstructionDesugaring;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
@@ -75,17 +74,13 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.Pair;
-import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.android.tools.r8.utils.structural.Ordered;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
@@ -132,7 +127,6 @@
   public static final String PRIVATE_METHOD_PREFIX = "$private$";
 
   private final AppView<?> appView;
-  private final IRConverter converter;
   private final InternalOptions options;
   final DexItemFactory factory;
   private final Map<DexType, DexType> emulatedInterfaces;
@@ -165,7 +159,6 @@
       BackportedMethodRewriter rewriter,
       DesugaredLibraryRetargeter desugaredLibraryRetargeter) {
     this.appView = appView;
-    this.converter = null;
     this.backportedMethodRewriter = rewriter;
     this.desugaredLibraryRetargeter = desugaredLibraryRetargeter;
     this.options = appView.options();
@@ -179,7 +172,6 @@
   public InterfaceMethodRewriter(AppView<?> appView, IRConverter converter) {
     assert converter != null;
     this.appView = appView;
-    this.converter = converter;
     this.backportedMethodRewriter = null;
     this.desugaredLibraryRetargeter = null;
     this.options = appView.options();
@@ -249,10 +241,17 @@
   }
 
   void addCompanionClassRewriteRule(DexType interfaceType, String rewrittenType) {
+    addCompanionClassRewriteRule(interfaceType, rewrittenType, appView);
+  }
+
+  static void addCompanionClassRewriteRule(
+      DexType interfaceType, String rewrittenType, AppView<?> appView) {
     appView.rewritePrefix.rewriteType(
-        getCompanionClassType(interfaceType),
-        factory.createType(
-            DescriptorUtils.javaTypeToDescriptor(rewrittenType + COMPANION_CLASS_NAME_SUFFIX)));
+        getCompanionClassType(interfaceType, appView.dexItemFactory()),
+        appView
+            .dexItemFactory()
+            .createType(
+                DescriptorUtils.javaTypeToDescriptor(rewrittenType + COMPANION_CLASS_NAME_SUFFIX)));
   }
 
   boolean isEmulatedInterface(DexType itf) {
@@ -744,7 +743,7 @@
                 // TODO(b/183998768): Why does this not create a stub on the class path?
                 : privateAsMethodOfCompanionClass(directTarget);
       } else {
-        companionMethod = defaultAsMethodOfCompanionClass(directTarget);
+        companionMethod = ensureDefaultAsMethodOfCompanionClassStub(directTarget).getReference();
       }
       return rewriteInvoke.apply(companionMethod);
     } else {
@@ -754,7 +753,8 @@
       if (virtualTarget != null) {
         // This is a invoke-direct call to a virtual method.
         assert invokeNeedsRewriting(invokedMethod, DIRECT);
-        return rewriteInvoke.apply(defaultAsMethodOfCompanionClass(virtualTarget));
+        return rewriteInvoke.apply(
+            ensureDefaultAsMethodOfCompanionClassStub(virtualTarget).getReference());
       } else {
         // The below assert is here because a well-type program should have a target, but we
         // cannot throw a compilation error, since we have no knowledge about the input.
@@ -925,7 +925,8 @@
           DexClass holder = target.getHolder();
           if (holder.isLibraryClass() && holder.isInterface()) {
             assert invokeNeedsRewriting(invokedMethod, SUPER);
-            return rewriteInvoke.apply(defaultAsMethodOfCompanionClass(target));
+            return rewriteInvoke.apply(
+                ensureDefaultAsMethodOfCompanionClassStub(target).getReference());
           }
         }
       }
@@ -938,25 +939,23 @@
     DexClassAndMethod superTarget =
         appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
     if (superTarget != null && superTarget.isLibraryMethod()) {
+      assert invokeNeedsRewriting(invokedMethod, SUPER);
       // Rewriting is required because the super invoke resolves into a missing
       // method (method is on desugared library). Find out if it needs to be
-      // retarget or if it just calls a companion class method and rewrite.
+      // retargeted or if it just calls a companion class method and rewrite.
       DexMethod retargetMethod =
           options.desugaredLibraryConfiguration.retargetMethod(superTarget, appView);
-      if (retargetMethod == null) {
-        DexMethod originalCompanionMethod = defaultAsMethodOfCompanionClass(superTarget);
-        DexMethod companionMethod =
-            factory.createMethod(
-                getCompanionClassType(emulatedItf),
-                factory.protoWithDifferentFirstParameter(
-                    originalCompanionMethod.proto, emulatedItf),
-                originalCompanionMethod.name);
-        assert invokeNeedsRewriting(invokedMethod, SUPER);
-        return rewriteInvoke.apply(companionMethod);
-      } else {
-        assert invokeNeedsRewriting(invokedMethod, SUPER);
+      if (retargetMethod != null) {
         return rewriteInvoke.apply(retargetMethod);
       }
+      DexClassAndMethod emulatedMethod =
+          superTarget.getReference().lookupMemberOnClass(appView.definitionFor(emulatedItf));
+      if (emulatedMethod == null) {
+        assert false;
+        return null;
+      }
+      DexClassAndMethod companionMethod = ensureDefaultAsMethodOfCompanionClassStub(emulatedMethod);
+      return rewriteInvoke.apply(companionMethod.getReference());
     }
     return null;
   }
@@ -1194,6 +1193,16 @@
     return factory.createType(interfaceTypeDescriptor);
   }
 
+  DexClassAndMethod ensureDefaultAsMethodOfCompanionClassStub(DexClassAndMethod method) {
+    if (method.isProgramMethod()) {
+      return ensureDefaultAsMethodOfProgramCompanionClassStub(method.asProgramMethod());
+    }
+    ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
+    DexMethod companionMethodReference =
+        defaultAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory());
+    return ensureMethodOfClasspathCompanionClassStub(companionMethodReference, context, appView);
+  }
+
   DexClassAndMethod ensureStaticAsMethodOfCompanionClassStub(DexClassAndMethod method) {
     if (method.isProgramMethod()) {
       return ensureStaticAsMethodOfProgramCompanionClassStub(method.asProgramMethod());
@@ -1204,6 +1213,37 @@
     }
   }
 
+  ProgramMethod ensureDefaultAsMethodOfProgramCompanionClassStub(ProgramMethod method) {
+    DexEncodedMethod virtual = method.getDefinition();
+    DexMethod companionMethod =
+        defaultAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory());
+    return InterfaceProcessor.ensureCompanionMethod(
+        method.getHolder(),
+        companionMethod.getName(),
+        companionMethod.getProto(),
+        appView,
+        methodBuilder -> {
+          MethodAccessFlags newFlags = method.getAccessFlags().copy();
+          newFlags.promoteToStatic();
+          methodBuilder
+              .setAccessFlags(newFlags)
+              .setGenericSignature(MethodTypeSignature.noSignature())
+              .setAnnotations(
+                  virtual
+                      .annotations()
+                      .methodParametersWithFakeThisArguments(appView.dexItemFactory()))
+              .setParameterAnnotationsList(
+                  virtual.getParameterAnnotations().withFakeThisParameter())
+              // TODO(b/183998768): Once R8 desugars in the enqueuer this should set an invalid
+              //  code to ensure it is never used before desugared and installed.
+              .setCode(
+                  ignored ->
+                      appView.enableWholeProgramOptimizations()
+                          ? virtual.getCode()
+                          : InvalidCode.getInstance());
+        });
+  }
+
   ProgramMethod ensurePrivateAsMethodOfProgramCompanionClassStub(ProgramMethod method) {
     DexMethod companionMethod =
         privateAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory());
@@ -1222,6 +1262,7 @@
               .setAccessFlags(newFlags)
               .setGenericSignature(definition.getGenericSignature())
               .setAnnotations(definition.annotations())
+              // TODO(b/183998768): Should this not also be updating with a fake 'this'
               .setParameterAnnotationsList(definition.getParameterAnnotations())
               // TODO(b/183998768): Once R8 desugars in the enqueuer this should set an invalid
               //  code to ensure it is never used before desugared and installed.
@@ -1272,13 +1313,6 @@
     return instanceAsMethodOfCompanionClass(method, DEFAULT_METHOD_PREFIX, factory);
   }
 
-  public final DexMethod defaultAsMethodOfCompanionClass(DexClassAndMethod method) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexMethod rewritten = defaultAsMethodOfCompanionClass(method.getReference(), dexItemFactory);
-    recordCompanionClassReference(appView, method, rewritten);
-    return rewritten;
-  }
-
   // Represent a private instance interface method as a method of companion class.
   static DexMethod privateAsMethodOfCompanionClass(DexMethod method, DexItemFactory factory) {
     // Add an implicit argument to represent the receiver.
@@ -1289,17 +1323,6 @@
     return privateAsMethodOfCompanionClass(method.getReference(), factory);
   }
 
-  private static DexClassAndMethod recordCompanionClassReference(
-      AppView<?> appView, DexClassAndMethod method, DexMethod rewritten) {
-    ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
-    // If the interface class is a program class, we shouldn't need to synthesize the companion
-    // class on the classpath.
-    if (context == null) {
-      return null;
-    }
-    return ensureMethodOfClasspathCompanionClassStub(rewritten, context, appView);
-  }
-
   private static DexClassAndMethod ensureMethodOfClasspathCompanionClassStub(
       DexMethod companionMethodReference, ClasspathOrLibraryClass context, AppView<?> appView) {
     return appView
@@ -1343,74 +1366,22 @@
         });
   }
 
-  /**
-   * Move static and default interface methods to companion classes, add missing methods to forward
-   * to moved default methods implementation.
-   */
-  public void desugarInterfaceMethods(
-      Builder<?> builder, Flavor flavour, ExecutorService executorService)
-      throws ExecutionException {
-    // During L8 compilation, emulated interfaces are processed to be renamed, to have
-    // their interfaces fixed-up and to generate the emulated dispatch code.
-    EmulatedInterfaceProcessor emulatedInterfaceProcessor =
-        new EmulatedInterfaceProcessor(appView, this);
-
-    // Process all classes first. Add missing forwarding methods to
-    // replace desugared default interface methods.
-    ClassProcessor classProcessor = new ClassProcessor(appView, this);
-
-    // Process interfaces, create companion or dispatch class if needed, move static
-    // methods to companion class, copy default interface methods to companion classes,
-    // make original default methods abstract, remove bridge methods, create dispatch
-    // classes if needed.
-    InterfaceProcessor interfaceProcessor = new InterfaceProcessor(appView, this);
-
-    // The interface processors must be ordered so that finalization of the processing is performed
-    // in that order. The emulatedInterfaceProcessor has to be last at this point to avoid renaming
-    // emulated interfaces before the other processing.
-    ImmutableList<InterfaceDesugaringProcessor> orderedInterfaceDesugaringProcessors =
-        ImmutableList.of(classProcessor, interfaceProcessor, emulatedInterfaceProcessor);
-    processClassesConcurrently(
-        orderedInterfaceDesugaringProcessors, builder, flavour, executorService);
-
+  public void finalizeInterfaceMethodRewritingThroughIR(
+      IRConverter converter, ExecutorService executorService) throws ExecutionException {
     SortedProgramMethodSet sortedSynthesizedMethods = SortedProgramMethodSet.create();
     sortedSynthesizedMethods.addAll(synthesizedMethods);
     converter.processMethodsConcurrently(sortedSynthesizedMethods, executorService);
 
     // Cached data is not needed any more.
-    clear();
-  }
-
-  private void clear() {
     this.cache.clear();
     this.synthesizedMethods.clear();
   }
 
-  private boolean shouldProcess(DexProgramClass clazz, Flavor flavour) {
-    if (appView.isAlreadyLibraryDesugared(clazz)) {
-      return false;
-    }
-    return (!clazz.originatesFromDexResource() || flavour == Flavor.IncludeAllResources);
-  }
-
-  private void processClassesConcurrently(
-      List<InterfaceDesugaringProcessor> processors,
-      Builder<?> builder,
-      Flavor flavour,
-      ExecutorService executorService)
+  public void runInterfaceDesugaringProcessors(
+      IRConverter converter, Flavor flavour, ExecutorService executorService)
       throws ExecutionException {
-    ThreadUtils.processItems(
-        Iterables.filter(
-            builder.getProgramClasses(), (DexProgramClass clazz) -> shouldProcess(clazz, flavour)),
-        clazz -> {
-          for (InterfaceDesugaringProcessor processor : processors) {
-            processor.process(clazz, synthesizedMethods);
-          }
-        },
-        executorService);
-    for (InterfaceDesugaringProcessor processor : processors) {
-      processor.finalizeProcessing(builder, synthesizedMethods);
-    }
+    new InterfaceMethodProcessorFacade(appView)
+        .runInterfaceDesugaringProcessors(this, converter, flavour, executorService);
   }
 
   final boolean isDefaultMethod(DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index 799b584..cb752ad 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -34,7 +33,6 @@
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InvalidCode;
 import com.android.tools.r8.graph.MethodAccessFlags;
@@ -183,11 +181,12 @@
   private DexEncodedField createStaticClinitFieldToTriggerInterfaceInitialization(
       DexProgramClass iface) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexField clinitFieldTemplateReference =
-        dexItemFactory.createField(iface.getType(), dexItemFactory.intType, "$desugar$clinit");
     DexField clinitFieldReference =
-        dexItemFactory.createFreshFieldName(
-            clinitFieldTemplateReference, candidate -> iface.lookupField(candidate) == null);
+        dexItemFactory.createFreshFieldNameWithoutHolder(
+            iface.getType(),
+            dexItemFactory.intType,
+            "$desugar$clinit",
+            candidate -> iface.lookupField(candidate) == null);
     return new DexEncodedField(
         clinitFieldReference,
         FieldAccessFlags.builder().setPackagePrivate().setStatic().setSynthetic().build(),
@@ -231,44 +230,19 @@
                   + method.toSourceString(),
               iface.origin);
         }
-
-        // Create a new method in a companion class to represent default method implementation.
-        DexMethod companionMethod = rewriter.defaultAsMethodOfCompanionClass(method);
-
         Code code = virtual.getCode();
         if (code == null) {
           throw new CompilationError(
               "Code is missing for default " + "interface method: " + method.toSourceString(),
               iface.origin);
         }
-
-        MethodAccessFlags newFlags = method.getAccessFlags().copy();
-        newFlags.promoteToStatic();
+        // Create a new method in a companion class to represent default method implementation.
+        ProgramMethod companion = rewriter.ensureDefaultAsMethodOfProgramCompanionClassStub(method);
         DexEncodedMethod.setDebugInfoWithFakeThisParameter(
-            code, companionMethod.getArity(), appView);
-
-        ensureCompanionMethod(
-            iface,
-            companionMethod.getName(),
-            companionMethod.getProto(),
-            appView,
-            methodBuilder ->
-                methodBuilder
-                    .setAccessFlags(newFlags)
-                    .setGenericSignature(MethodTypeSignature.noSignature())
-                    .setAnnotations(
-                        virtual
-                            .annotations()
-                            .methodParametersWithFakeThisArguments(appView.dexItemFactory()))
-                    .setParameterAnnotationsList(
-                        virtual.getParameterAnnotations().withFakeThisParameter())
-                    .setCode(ignored -> virtual.getCode())
-                    .setOnBuildConsumer(
-                        implMethod -> {
-                          implMethod.copyMetadata(virtual);
-                          getPostProcessingInterfaceInfo(iface)
-                              .mapDefaultMethodToCompanionMethod(virtual, implMethod);
-                        }));
+            code, companion.getReference().getArity(), appView);
+        finalizeMoveToCompanionMethod(method, companion);
+        getPostProcessingInterfaceInfo(iface)
+            .mapDefaultMethodToCompanionMethod(virtual, companion.getDefinition());
       }
     }
   }
@@ -299,7 +273,6 @@
         companion = rewriter.ensureStaticAsMethodOfProgramCompanionClassStub(method);
       } else {
         assert definition.isPrivate();
-        companion = rewriter.ensurePrivateAsMethodOfProgramCompanionClassStub(method);
         Code code = definition.getCode();
         if (code == null) {
           throw new CompilationError(
@@ -308,24 +281,31 @@
                   + method.getReference().toSourceString(),
               iface.origin);
         }
+        companion = rewriter.ensurePrivateAsMethodOfProgramCompanionClassStub(method);
         DexEncodedMethod.setDebugInfoWithFakeThisParameter(
             code, companion.getReference().getArity(), appView);
       }
 
-      // TODO(b/183998768): R8 should also install an "invalid code" object until the actual code
-      //  moves.
-      assert appView.enableWholeProgramOptimizations()
-          || InvalidCode.isInvalidCode(companion.getDefinition().getCode());
-      if (definition.hasClassFileVersion()) {
-        companion.getDefinition().downgradeClassFileVersion(definition.getClassFileVersion());
-      }
-      companion.getDefinition().setCode(definition.getCode(), appView);
+      finalizeMoveToCompanionMethod(method, companion);
       getPostProcessingInterfaceInfo(iface)
           .moveMethod(method.getReference(), companion.getReference());
-      definition.setCode(InvalidCode.getInstance(), appView);
     }
   }
 
+  private void finalizeMoveToCompanionMethod(ProgramMethod method, ProgramMethod companion) {
+    // TODO(b/183998768): R8 should also install an "invalid code" object until the actual code
+    //  moves.
+    assert appView.enableWholeProgramOptimizations()
+        || InvalidCode.isInvalidCode(companion.getDefinition().getCode());
+    DexProgramClass iface = method.getHolder();
+    DexEncodedMethod definition = method.getDefinition();
+    if (definition.hasClassFileVersion()) {
+      companion.getDefinition().downgradeClassFileVersion(definition.getClassFileVersion());
+    }
+    companion.getDefinition().setCode(definition.getCode(), appView);
+    definition.setCode(InvalidCode.getInstance(), appView);
+  }
+
   private void clearDirectMethods(DexProgramClass iface) {
     DexEncodedMethod clinit = iface.getClassInitializer();
     MethodCollection methodCollection = iface.getMethodCollection();
@@ -461,7 +441,7 @@
   }
 
   @Override
-  public void finalizeProcessing(Builder<?> builder, ProgramMethodSet synthesizedMethods) {
+  public void finalizeProcessing(ProgramMethodSet synthesizedMethods) {
     InterfaceProcessorNestedGraphLens graphLens = postProcessInterfaces();
     if (appView.enableWholeProgramOptimizations() && graphLens != null) {
       appView.setGraphLens(graphLens);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordCfMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
similarity index 99%
rename from src/main/java/com/android/tools/r8/ir/desugar/RecordCfMethods.java
rename to src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
index 47bc98b..0de85b5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/RecordCfMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
@@ -6,7 +6,7 @@
 // GENERATED FILE. DO NOT EDIT! See GenerateRecordMethods.java.
 // ***********************************************************************************
 
-package com.android.tools.r8.ir.desugar;
+package com.android.tools.r8.ir.desugar.records;
 
 import com.android.tools.r8.cf.code.CfArithmeticBinop;
 import com.android.tools.r8.cf.code.CfArrayLength;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaringEventConsumer.java
similarity index 90%
rename from src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java
rename to src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaringEventConsumer.java
index 61eb249..5affc2b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaringEventConsumer.java
@@ -2,7 +2,7 @@
 // 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;
+package com.android.tools.r8.ir.desugar.records;
 
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
similarity index 97%
rename from src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
rename to src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
index 2e71826..5333665 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
@@ -2,7 +2,7 @@
 // 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;
+package com.android.tools.r8.ir.desugar.records;
 
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
@@ -35,6 +35,12 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfClassDesugaring;
+import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.synthetic.CallObjectInitCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.RecordGetFieldsAsObjectsCfCodeProvider;
 import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo;
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 9e82361..e4d045d 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
@@ -732,7 +732,7 @@
 
       if (inliningIRProvider.shouldApplyCodeRewritings(target)) {
         assert lensCodeRewriter != null;
-        lensCodeRewriter.rewrite(code, target);
+        lensCodeRewriter.rewrite(code, target, inliningIRProvider.getMethodProcessor());
       }
       if (options.testing.inlineeIrModifier != null) {
         options.testing.inlineeIrModifier.accept(code);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index e00c267..17db09c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -292,6 +292,10 @@
     return ConstraintWithTarget.classIsVisible(context, type, appView);
   }
 
+  public ConstraintWithTarget forNewUnboxedEnumInstance(DexType type, ProgramMethod context) {
+    return ConstraintWithTarget.ALWAYS;
+  }
+
   public ConstraintWithTarget forAssume() {
     return ConstraintWithTarget.ALWAYS;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
index b47ec05..264fc7b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -23,6 +24,10 @@
     this.map = map;
   }
 
+  public boolean isUnboxedEnum(DexProgramClass clazz) {
+    return isUnboxedEnum(clazz.getType());
+  }
+
   public boolean isUnboxedEnum(DexType type) {
     return map.containsKey(type);
   }
@@ -110,8 +115,16 @@
       return unboxedValues.get(field);
     }
 
+    public boolean hasUnboxedValueFor(ProgramField field) {
+      return hasUnboxedValueFor(field.getReference());
+    }
+
     public boolean hasUnboxedValueFor(DexField field) {
-      return unboxedValues.get(field) != null;
+      return unboxedValues.containsKey(field);
+    }
+
+    public boolean matchesValuesField(ProgramField field) {
+      return matchesValuesField(field.getReference());
     }
 
     public boolean matchesValuesField(DexField field) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index e9b1e25..07ce458 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -38,6 +38,7 @@
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
@@ -62,6 +63,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Opcodes;
 import com.android.tools.r8.ir.code.Phi;
@@ -69,6 +71,7 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
@@ -251,7 +254,8 @@
           case Opcodes.INSTANCE_GET:
           case Opcodes.STATIC_PUT:
           case INSTANCE_PUT:
-            analyzeFieldInstruction(instruction.asFieldInstruction(), code);
+            analyzeFieldInstruction(
+                instruction.asFieldInstruction(), eligibleEnums, code.context());
             break;
           default: // Nothing to do for other instructions.
         }
@@ -279,13 +283,15 @@
     }
   }
 
-  private void analyzeFieldInstruction(FieldInstruction fieldInstruction, IRCode code) {
+  private void analyzeFieldInstruction(
+      FieldInstruction fieldInstruction, Set<DexType> eligibleEnums, ProgramMethod context) {
     DexField field = fieldInstruction.getField();
     DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(field.holder);
     if (enumClass != null) {
-      FieldResolutionResult resolutionResult =
-          appView.appInfo().resolveField(field, code.context());
-      if (resolutionResult.isFailedOrUnknownResolution()) {
+      FieldResolutionResult resolutionResult = appView.appInfo().resolveField(field, context);
+      if (resolutionResult.isSuccessfulResolution()) {
+        eligibleEnums.add(enumClass.getType());
+      } else {
         markEnumAsUnboxable(Reason.UNRESOLVABLE_FIELD, enumClass);
       }
     }
@@ -366,36 +372,58 @@
       return;
     }
     for (Instruction user : constClass.outValue().aliasedUsers()) {
-      if (user.isAssume()) {
-        continue;
+      if (!isLegitimateConstClassUser(user, context, enumClass)) {
+        markEnumAsUnboxable(Reason.CONST_CLASS, enumClass);
+        return;
       }
-      if (user.isInvokeVirtual()
-          && isUnboxableNameMethod(user.asInvokeVirtual().getInvokedMethod())) {
-        continue;
-      }
-      if (user.isInvokeStatic()) {
-        DexClassAndMethod singleTarget = user.asInvokeStatic().lookupSingleTarget(appView, context);
-        if (singleTarget != null) {
-          if (singleTarget.getReference() == factory.enumMembers.valueOf) {
-            // The name data is required for the correct mapping from the enum name to the ordinal
-            // in the valueOf utility method.
-            addRequiredNameData(enumClass);
-            markMethodDependsOnLibraryModelisation(context);
-            continue;
-          }
-          if (singleTarget.getReference()
-              == factory.javaLangReflectArrayMembers.newInstanceMethodWithDimensions) {
-            markMethodDependsOnLibraryModelisation(context);
-            continue;
-          }
-        }
-      }
-      markEnumAsUnboxable(Reason.CONST_CLASS, enumClass);
-      return;
     }
     eligibleEnums.add(enumType);
   }
 
+  private boolean isLegitimateConstClassUser(
+      Instruction user, ProgramMethod context, DexProgramClass enumClass) {
+    if (user.isAssume()) {
+      if (user.outValue().hasPhiUsers()) {
+        return false;
+      }
+      return true;
+    }
+
+    if (user.isInvokeStatic()) {
+      DexClassAndMethod singleTarget = user.asInvokeStatic().lookupSingleTarget(appView, context);
+      if (singleTarget == null) {
+        return false;
+      }
+      if (singleTarget.getReference() == factory.enumMembers.valueOf) {
+        // The name data is required for the correct mapping from the enum name to the ordinal
+        // in the valueOf utility method.
+        addRequiredNameData(enumClass);
+        markMethodDependsOnLibraryModelisation(context);
+        return true;
+      }
+      if (singleTarget.getReference()
+          == factory.javaLangReflectArrayMembers.newInstanceMethodWithDimensions) {
+        markMethodDependsOnLibraryModelisation(context);
+        return true;
+      }
+    }
+
+    if (user.isInvokeVirtual()) {
+      InvokeVirtual invoke = user.asInvokeVirtual();
+      DexMethod invokedMethod = invoke.getInvokedMethod();
+      if (invokedMethod == factory.classMethods.desiredAssertionStatus) {
+        // Only valid in the enum's class initializer, since the class constant must be rewritten
+        // to LocalEnumUtility.class instead of int.class.
+        return context.getDefinition().isClassInitializer() && context.getHolder() == enumClass;
+      }
+      if (isUnboxableNameMethod(invokedMethod)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
   private void addRequiredNameData(DexProgramClass enumClass) {
     enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(
         enumClass, factory.enumMembers.nameField);
@@ -465,6 +493,7 @@
   }
 
   public void unboxEnums(
+      IRConverter converter,
       PostMethodProcessor.Builder postBuilder,
       ExecutorService executorService,
       OptimizationFeedbackDelayed feedback)
@@ -472,12 +501,16 @@
     assert candidatesToRemoveInWave.isEmpty();
     EnumDataMap enumDataMap = finishAnalysis();
     assert candidatesToRemoveInWave.isEmpty();
+
     // At this point the enum unboxing candidates are no longer candidates, they will all be
     // unboxed. We extract the now immutable enums to unbox information and clear the candidate
     // info.
     if (enumUnboxingCandidatesInfo.isEmpty()) {
+      assert enumDataMap.isEmpty();
+      appView.setUnboxedEnums(enumDataMap);
       return;
     }
+
     ImmutableSet<DexType> enumsToUnbox = enumUnboxingCandidatesInfo.candidates();
     ImmutableSet<DexProgramClass> enumClassesToUnbox =
         enumUnboxingCandidatesInfo.candidateClasses();
@@ -501,25 +534,29 @@
         utilityClass -> utilityClass.getDefinition().forEachProgramMethod(postBuilder::add));
 
     fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
-    enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, utilityClasses);
-    EnumUnboxingLens enumUnboxingLens =
-        new EnumUnboxingTreeFixer(appView, enumsToUnbox, utilityClasses, enumUnboxerRewriter)
-            .fixupTypeReferences();
-    enumUnboxerRewriter.setEnumUnboxingLens(enumUnboxingLens);
+    EnumUnboxingTreeFixer.Result treeFixerResult =
+        new EnumUnboxingTreeFixer(appView, enumDataMap, enumClassesToUnbox, utilityClasses)
+            .fixupTypeReferences(converter, executorService);
+    EnumUnboxingLens enumUnboxingLens = treeFixerResult.getLens();
+    enumUnboxerRewriter =
+        new EnumUnboxingRewriter(appView, converter, enumUnboxingLens, enumDataMap, utilityClasses);
     appView.setUnboxedEnums(enumDataMap);
     GraphLens previousLens = appView.graphLens();
     appView.rewriteWithLensAndApplication(enumUnboxingLens, appBuilder.build());
-    updateOptimizationInfos(executorService, feedback);
+    updateOptimizationInfos(executorService, feedback, treeFixerResult.getPrunedItems());
     postBuilder.put(dependencies);
     // Methods depending on library modelisation need to be reprocessed so they are peephole
     // optimized.
     postBuilder.put(methodsDependingOnLibraryModelisation);
     methodsDependingOnLibraryModelisation.clear();
+    postBuilder.removePrunedMethods(treeFixerResult.getPrunedItems().getRemovedMethods());
     postBuilder.rewrittenWithLens(appView, previousLens);
   }
 
   private void updateOptimizationInfos(
-      ExecutorService executorService, OptimizationFeedbackDelayed feedback)
+      ExecutorService executorService,
+      OptimizationFeedbackDelayed feedback,
+      PrunedItems prunedItems)
       throws ExecutionException {
     feedback.fixupOptimizationInfos(
         appView,
@@ -538,7 +575,7 @@
             optimizationInfo
                 .fixupClassTypeReferences(appView, appView.graphLens())
                 .fixupAbstractReturnValue(appView, appView.graphLens())
-                .fixupInstanceInitializerInfo(appView, appView.graphLens());
+                .fixupInstanceInitializerInfo(appView, appView.graphLens(), prunedItems);
           }
         });
   }
@@ -1223,53 +1260,93 @@
       Value enumValue,
       DexMethod singleTargetReference,
       DexClass targetHolder) {
-    if (targetHolder.getType() != factory.enumType) {
-      // System.identityHashCode(Object) is supported for proto enums.
-      // Object#getClass without outValue and Objects.requireNonNull are supported since R8
-      // rewrites explicit null checks to such instructions.
-      if (singleTargetReference == factory.javaLangSystemMethods.identityHashCode) {
+    // Calls to java.lang.Enum.
+    if (targetHolder.getType() == factory.enumType) {
+      // TODO(b/147860220): EnumSet and EnumMap may be interesting to model.
+      if (singleTargetReference == factory.enumMembers.compareTo
+          || singleTargetReference == factory.enumMembers.compareToWithObject) {
+        DexProgramClass otherEnumClass =
+            getEnumUnboxingCandidateOrNull(invoke.getLastArgument().getType());
+        if (otherEnumClass == enumClass || invoke.getLastArgument().getType().isNullType()) {
+          return Reason.ELIGIBLE;
+        }
+      } else if (singleTargetReference == factory.enumMembers.equals) {
         return Reason.ELIGIBLE;
-      }
-      if (singleTargetReference == factory.stringMembers.valueOf) {
+      } else if (singleTargetReference == factory.enumMembers.nameMethod
+          || singleTargetReference == factory.enumMembers.toString) {
+        assert invoke.asInvokeMethodWithReceiver().getReceiver() == enumValue;
         addRequiredNameData(enumClass);
         return Reason.ELIGIBLE;
-      }
-      if (singleTargetReference == factory.stringBuilderMethods.appendObject
-          || singleTargetReference == factory.stringBufferMethods.appendObject) {
-        addRequiredNameData(enumClass);
+      } else if (singleTargetReference == factory.enumMembers.ordinalMethod) {
         return Reason.ELIGIBLE;
+      } else if (singleTargetReference == factory.enumMembers.hashCode) {
+        return Reason.ELIGIBLE;
+      } else if (singleTargetReference == factory.enumMembers.constructor) {
+        // Enum constructor call is allowed only if called from an enum initializer.
+        if (code.method().isInstanceInitializer() && code.context().getHolder() == enumClass) {
+          return Reason.ELIGIBLE;
+        }
       }
-      if (singleTargetReference == factory.objectMembers.getClass
-          && (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers())) {
+      return new UnsupportedLibraryInvokeReason(singleTargetReference);
+    }
+
+    // Calls to java.lang.Object.
+    if (targetHolder.getType() == factory.objectType) {
+      // Object#getClass without outValue is important since R8 rewrites explicit null checks to
+      // such instructions.
+      if (singleTargetReference == factory.objectMembers.getClass && invoke.hasUnusedOutValue()) {
         // This is a hidden null check.
         return Reason.ELIGIBLE;
       }
+      return new UnsupportedLibraryInvokeReason(singleTargetReference);
+    }
+
+    // Calls to java.lang.Objects.
+    if (targetHolder.getType() == factory.objectsType) {
+      // Objects#requireNonNull is important since R8 rewrites explicit null checks to such
+      // instructions.
       if (singleTargetReference == factory.objectsMethods.requireNonNull
           || singleTargetReference == factory.objectsMethods.requireNonNullWithMessage) {
         return Reason.ELIGIBLE;
       }
       return new UnsupportedLibraryInvokeReason(singleTargetReference);
     }
-    // TODO(b/147860220): EnumSet and EnumMap may be interesting to model.
-    if (singleTargetReference == factory.enumMembers.compareTo) {
-      return Reason.ELIGIBLE;
-    } else if (singleTargetReference == factory.enumMembers.equals) {
-      return Reason.ELIGIBLE;
-    } else if (singleTargetReference == factory.enumMembers.nameMethod
-        || singleTargetReference == factory.enumMembers.toString) {
-      assert invoke.asInvokeMethodWithReceiver().getReceiver() == enumValue;
-      addRequiredNameData(enumClass);
-      return Reason.ELIGIBLE;
-    } else if (singleTargetReference == factory.enumMembers.ordinalMethod) {
-      return Reason.ELIGIBLE;
-    } else if (singleTargetReference == factory.enumMembers.hashCode) {
-      return Reason.ELIGIBLE;
-    } else if (singleTargetReference == factory.enumMembers.constructor) {
-      // Enum constructor call is allowed only if called from an enum initializer.
-      if (code.method().isInstanceInitializer() && code.context().getHolder() == enumClass) {
+
+    // Calls to java.lang.String.
+    if (targetHolder.getType() == factory.stringType) {
+      if (singleTargetReference == factory.stringMembers.valueOf) {
+        addRequiredNameData(enumClass);
         return Reason.ELIGIBLE;
       }
+      return new UnsupportedLibraryInvokeReason(singleTargetReference);
     }
+
+    // Calls to java.lang.StringBuilder and java.lang.StringBuffer.
+    if (targetHolder.getType() == factory.stringBuilderType
+        || targetHolder.getType() == factory.stringBufferType) {
+      if (singleTargetReference == factory.stringBuilderMethods.appendObject
+          || singleTargetReference == factory.stringBufferMethods.appendObject) {
+        addRequiredNameData(enumClass);
+        return Reason.ELIGIBLE;
+      }
+      return new UnsupportedLibraryInvokeReason(singleTargetReference);
+    }
+
+    // Calls to java.lang.System.
+    if (targetHolder.getType() == factory.javaLangSystemType) {
+      if (singleTargetReference == factory.javaLangSystemMethods.arraycopy) {
+        // Important for Kotlin 1.5 enums, which use arraycopy to create a copy of $VALUES instead
+        // of int[].clone().
+        return Reason.ELIGIBLE;
+      }
+      if (singleTargetReference == factory.javaLangSystemMethods.identityHashCode) {
+        // Important for proto enum unboxing.
+        return Reason.ELIGIBLE;
+      }
+      return new UnsupportedLibraryInvokeReason(singleTargetReference);
+    }
+
+    // Unsupported holder.
     return new UnsupportedLibraryInvokeReason(singleTargetReference);
   }
 
@@ -1387,11 +1464,11 @@
     return false;
   }
 
-  public Set<Phi> rewriteCode(IRCode code) {
+  public Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor) {
     // This has no effect during primary processing since the enumUnboxerRewriter is set
     // in between primary and post processing.
     if (enumUnboxerRewriter != null) {
-      return enumUnboxerRewriter.rewriteCode(code);
+      return enumUnboxerRewriter.rewriteCode(code, methodProcessor);
     }
     return Sets.newIdentityHashSet();
   }
@@ -1402,4 +1479,8 @@
       enumUnboxerRewriter.synthesizeEnumUnboxingUtilityMethods(converter, executorService);
     }
   }
+
+  public void unsetRewriter() {
+    enumUnboxerRewriter = null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 62b05a9..8530a9d3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.optimize.enums;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMember;
@@ -13,7 +12,6 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.optimize.enums.eligibility.Reason;
-import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.UnsupportedStaticFieldReason;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 
@@ -70,48 +68,9 @@
       }
       result = false;
     }
-    if (!enumHasBasicStaticFields(clazz)) {
-      result = false;
-    }
     return result;
   }
 
-  // The enum should have the $VALUES static field and only fields directly referencing the enum
-  // instances.
-  private boolean enumHasBasicStaticFields(DexProgramClass clazz) {
-    boolean result = true;
-    for (DexEncodedField staticField : clazz.staticFields()) {
-      if (isEnumField(staticField, clazz)) {
-        // Enum field, valid, do nothing.
-      } else if (matchesValuesField(staticField, clazz, factory)) {
-        // Field $VALUES, valid, do nothing.
-      } else if (appView.appInfo().isFieldRead(staticField)) {
-        // Only non read static fields are valid, and they are assumed unused.
-        if (!enumUnboxer.reportFailure(
-            clazz, new UnsupportedStaticFieldReason(staticField.getReference()))) {
-          return false;
-        }
-        result = false;
-      }
-    }
-    return result;
-  }
-
-  static boolean isEnumField(DexEncodedField staticField, DexProgramClass enumClass) {
-    return staticField.getType() == enumClass.getType()
-        && staticField.isEnum()
-        && staticField.isFinal();
-  }
-
-  static boolean matchesValuesField(
-      DexEncodedField staticField, DexProgramClass enumClass, DexItemFactory factory) {
-    return staticField.getType().isArrayType()
-        && staticField.getType().toArrayElementType(factory) == enumClass.getType()
-        && staticField.isSynthetic()
-        && staticField.isFinal()
-        && staticField.getName() == factory.enumValuesFieldName;
-  }
-
   private void removeEnumsInAnnotations() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       if (clazz.isAnnotation()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index 1e27370..58cb128 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -6,11 +6,13 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
@@ -18,6 +20,7 @@
 import com.google.common.collect.ImmutableMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.Set;
 
 class EnumUnboxingLens extends NestedGraphLens {
 
@@ -54,26 +57,31 @@
     return type;
   }
 
-  public static Builder enumUnboxingLensBuilder() {
-    return new Builder();
+  public static Builder enumUnboxingLensBuilder(AppView<AppInfoWithLiveness> appView) {
+    return new Builder(appView);
   }
 
   static class Builder {
 
-    protected final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
-    protected final MutableBidirectionalOneToOneMap<DexField, DexField> newFieldSignatures =
+    private final DexItemFactory dexItemFactory;
+    private final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
+    private final MutableBidirectionalOneToOneMap<DexField, DexField> newFieldSignatures =
         new BidirectionalOneToOneHashMap<>();
-    protected final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures =
+    private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures =
         new BidirectionalOneToOneHashMap<>();
 
     private Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
         new IdentityHashMap<>();
 
-    public void map(DexType from, DexType to) {
-      if (from == to) {
-        return;
+    Builder(AppView<AppInfoWithLiveness> appView) {
+      this.dexItemFactory = appView.dexItemFactory();
+    }
+
+    public Builder mapUnboxedEnums(Set<DexType> enumsToUnbox) {
+      for (DexType enumToUnbox : enumsToUnbox) {
+        typeMap.put(enumToUnbox, dexItemFactory.intType);
       }
-      typeMap.put(from, to);
+      return this;
     }
 
     public void move(DexField from, DexField to) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 3bbd636..fe3049f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -37,10 +37,12 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.NewUnboxedEnumInstance;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
 import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -68,10 +70,11 @@
   private static final CfVersion REQUIRED_CLASS_FILE_VERSION = CfVersion.V1_8;
 
   private final AppView<AppInfoWithLiveness> appView;
+  private final IRConverter converter;
   private final DexItemFactory factory;
   private final InternalOptions options;
   private final EnumDataMap unboxedEnumsData;
-  private EnumUnboxingLens enumUnboxingLens;
+  private final EnumUnboxingLens enumUnboxingLens;
   private final EnumUnboxingUtilityClasses utilityClasses;
 
   private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
@@ -84,11 +87,15 @@
 
   EnumUnboxingRewriter(
       AppView<AppInfoWithLiveness> appView,
+      IRConverter converter,
+      EnumUnboxingLens enumUnboxingLens,
       EnumDataMap unboxedEnumsInstanceFieldData,
       EnumUnboxingUtilityClasses utilityClasses) {
     this.appView = appView;
+    this.converter = converter;
     this.factory = appView.dexItemFactory();
     this.options = appView.options();
+    this.enumUnboxingLens = enumUnboxingLens;
     this.unboxedEnumsData = unboxedEnumsInstanceFieldData;
     this.utilityClasses = utilityClasses;
 
@@ -130,11 +137,7 @@
     return utilityClasses.getSharedUtilityClass();
   }
 
-  public void setEnumUnboxingLens(EnumUnboxingLens enumUnboxingLens) {
-    this.enumUnboxingLens = enumUnboxingLens;
-  }
-
-  Set<Phi> rewriteCode(IRCode code) {
+  Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor) {
     // We should not process the enum methods, they will be removed and they may contain invalid
     // rewriting rules.
     if (unboxedEnumsData.isEmpty()) {
@@ -181,9 +184,13 @@
               replaceEnumInvoke(
                   iterator, invokeMethod, equalsUtilityMethod, m -> synthesizeEqualsMethod());
               continue;
-            } else if (invokedMethod == factory.enumMembers.compareTo) {
+            } else if (invokedMethod == factory.enumMembers.compareTo
+                || invokedMethod == factory.enumMembers.compareToWithObject) {
               replaceEnumInvoke(
-                  iterator, invokeMethod, compareToUtilityMethod, m -> synthesizeCompareToMethod());
+                  iterator,
+                  invokeMethod,
+                  getSharedUtilityClass()
+                      .ensureCompareToMethod(appView, converter, methodProcessor));
               continue;
             } else if (invokedMethod == factory.enumMembers.nameMethod) {
               rewriteNameMethod(iterator, invokeMethod, enumType);
@@ -240,72 +247,8 @@
             }
           }
         } else if (instruction.isInvokeStatic()) {
-          InvokeStatic invokeStatic = instruction.asInvokeStatic();
-          DexClassAndMethod singleTarget = invokeStatic.lookupSingleTarget(appView, context);
-          if (singleTarget == null) {
-            continue;
-          }
-          DexMethod invokedMethod = singleTarget.getReference();
-          if (invokedMethod == factory.enumMembers.valueOf
-              && invokeStatic.getArgument(0).isConstClass()) {
-            DexType enumType =
-                invokeStatic.getArgument(0).getConstInstruction().asConstClass().getValue();
-            if (unboxedEnumsData.isUnboxedEnum(enumType)) {
-              DexMethod valueOfMethod = computeValueOfUtilityMethod(enumType);
-              Value outValue = invokeStatic.outValue();
-              Value rewrittenOutValue = null;
-              if (outValue != null) {
-                rewrittenOutValue = code.createValue(TypeElement.getInt());
-                affectedPhis.addAll(outValue.uniquePhiUsers());
-              }
-              InvokeStatic invoke =
-                  new InvokeStatic(
-                      valueOfMethod,
-                      rewrittenOutValue,
-                      Collections.singletonList(invokeStatic.inValues().get(1)));
-              iterator.replaceCurrentInstruction(invoke);
-              convertedEnums.put(invoke, enumType);
-              continue;
-            }
-          } else if (invokedMethod == factory.javaLangSystemMethods.identityHashCode) {
-            assert invokeStatic.arguments().size() == 1;
-            Value argument = invokeStatic.getArgument(0);
-            DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
-            if (enumType != null) {
-              invokeStatic.outValue().replaceUsers(argument);
-              iterator.removeOrReplaceByDebugLocalRead();
-            }
-          } else if (invokedMethod == factory.stringMembers.valueOf) {
-            assert invokeStatic.arguments().size() == 1;
-            Value argument = invokeStatic.getArgument(0);
-            DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
-            if (enumType != null) {
-              DexMethod stringValueOfMethod = computeStringValueOfUtilityMethod(enumType);
-              iterator.replaceCurrentInstruction(
-                  new InvokeStatic(
-                      stringValueOfMethod, invokeStatic.outValue(), invokeStatic.arguments()));
-              continue;
-            }
-          } else if (invokedMethod == factory.objectsMethods.requireNonNull) {
-            assert invokeStatic.arguments().size() == 1;
-            Value argument = invokeStatic.getArgument(0);
-            DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
-            if (enumType != null) {
-              replaceEnumInvoke(
-                  iterator, invokeStatic, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
-            }
-          } else if (invokedMethod == factory.objectsMethods.requireNonNullWithMessage) {
-            assert invokeStatic.arguments().size() == 2;
-            Value argument = invokeStatic.getArgument(0);
-            DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
-            if (enumType != null) {
-              replaceEnumInvoke(
-                  iterator,
-                  invokeStatic,
-                  zeroCheckMessageMethod,
-                  m -> synthesizeZeroCheckMessageMethod());
-            }
-          }
+          rewriteInvokeStatic(
+              instruction.asInvokeStatic(), code, context, convertedEnums, iterator, affectedPhis);
         }
         if (instruction.isStaticGet()) {
           StaticGet staticGet = instruction.asStaticGet();
@@ -343,14 +286,14 @@
             // array. This is needed because the javac generated implementation of MyEnum.values()
             // is implemented as `return $VALUES.clone()`.
             removeRedundantValuesArrayCloning(invoke, instructionsToRemove, seenBlocks);
-          } else {
+          } else if (unboxedEnumsData.hasUnboxedValueFor(field)) {
             // Replace by ordinal + 1 for null check (null is 0).
-            assert unboxedEnumsData.hasUnboxedValueFor(field)
-                : "Invalid read to " + field.name + ", error during enum analysis";
             ConstNumber intConstant =
                 code.createIntConstant(unboxedEnumsData.getUnboxedValue(field));
             iterator.replaceCurrentInstruction(intConstant);
             convertedEnums.put(intConstant, holder);
+          } else {
+            // Nothing to do, handled by lens code rewriting.
           }
         }
 
@@ -387,12 +330,119 @@
           }
           assert validateArrayAccess(arrayAccess);
         }
+
+        if (instruction.isNewUnboxedEnumInstance()) {
+          NewUnboxedEnumInstance newUnboxedEnumInstance = instruction.asNewUnboxedEnumInstance();
+          assert unboxedEnumsData.isUnboxedEnum(newUnboxedEnumInstance.getType());
+          iterator.replaceCurrentInstruction(
+              code.createIntConstant(
+                  EnumUnboxer.ordinalToUnboxedInt(newUnboxedEnumInstance.getOrdinal())));
+        }
       }
     }
     assert code.isConsistentSSABeforeTypesAreCorrect();
     return affectedPhis;
   }
 
+  private void rewriteInvokeStatic(
+      InvokeStatic invoke,
+      IRCode code,
+      ProgramMethod context,
+      Map<Instruction, DexType> convertedEnums,
+      InstructionListIterator instructionIterator,
+      Set<Phi> affectedPhis) {
+    DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
+    if (singleTarget == null) {
+      return;
+    }
+    DexMethod invokedMethod = singleTarget.getReference();
+
+    // Calls to java.lang.Enum.
+    if (invokedMethod.getHolderType() == factory.enumType) {
+      if (invokedMethod == factory.enumMembers.valueOf) {
+        if (!invoke.getFirstArgument().isConstClass()) {
+          return;
+        }
+        DexType enumType =
+            invoke.getFirstArgument().getConstInstruction().asConstClass().getValue();
+        if (!unboxedEnumsData.isUnboxedEnum(enumType)) {
+          return;
+        }
+        DexMethod valueOfMethod = computeValueOfUtilityMethod(enumType);
+        Value outValue = invoke.outValue();
+        Value rewrittenOutValue = null;
+        if (outValue != null) {
+          rewrittenOutValue = code.createValue(TypeElement.getInt());
+          affectedPhis.addAll(outValue.uniquePhiUsers());
+        }
+        InvokeStatic replacement =
+            new InvokeStatic(
+                valueOfMethod,
+                rewrittenOutValue,
+                Collections.singletonList(invoke.inValues().get(1)));
+        instructionIterator.replaceCurrentInstruction(replacement);
+        convertedEnums.put(replacement, enumType);
+      }
+      return;
+    }
+
+    // Calls to java.lang.Objects.
+    if (invokedMethod.getHolderType() == factory.objectsType) {
+      if (invokedMethod == factory.objectsMethods.requireNonNull) {
+        assert invoke.arguments().size() == 1;
+        Value argument = invoke.getFirstArgument();
+        DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
+        if (enumType != null) {
+          replaceEnumInvoke(
+              instructionIterator, invoke, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
+        }
+      } else if (invokedMethod == factory.objectsMethods.requireNonNullWithMessage) {
+        assert invoke.arguments().size() == 2;
+        Value argument = invoke.getFirstArgument();
+        DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
+        if (enumType != null) {
+          replaceEnumInvoke(
+              instructionIterator,
+              invoke,
+              zeroCheckMessageMethod,
+              m -> synthesizeZeroCheckMessageMethod());
+        }
+      }
+      return;
+    }
+
+    // Calls to java.lang.String.
+    if (invokedMethod.getHolderType() == factory.stringType) {
+      if (invokedMethod == factory.stringMembers.valueOf) {
+        assert invoke.arguments().size() == 1;
+        Value argument = invoke.getFirstArgument();
+        DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
+        if (enumType != null) {
+          DexMethod stringValueOfMethod = computeStringValueOfUtilityMethod(enumType);
+          instructionIterator.replaceCurrentInstruction(
+              new InvokeStatic(stringValueOfMethod, invoke.outValue(), invoke.arguments()));
+        }
+      }
+      return;
+    }
+
+    // Calls to java.lang.System.
+    if (invokedMethod.getHolderType() == factory.javaLangSystemType) {
+      if (invokedMethod == factory.javaLangSystemMethods.arraycopy) {
+        // Intentionally empty.
+      } else if (invokedMethod == factory.javaLangSystemMethods.identityHashCode) {
+        assert invoke.arguments().size() == 1;
+        Value argument = invoke.getFirstArgument();
+        DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
+        if (enumType != null) {
+          invoke.outValue().replaceUsers(argument);
+          instructionIterator.removeOrReplaceByDebugLocalRead();
+        }
+      }
+      return;
+    }
+  }
+
   private void removeRedundantValuesArrayCloning(
       InvokeStatic invoke, Set<Instruction> instructionsToRemove, Set<BasicBlock> seenBlocks) {
     for (Instruction user : invoke.outValue().aliasedUsers()) {
@@ -457,11 +507,18 @@
   }
 
   private void replaceEnumInvoke(
+      InstructionListIterator iterator, InvokeMethod invoke, ProgramMethod method) {
+    replaceEnumInvoke(iterator, invoke, method.getReference(), null);
+  }
+
+  private void replaceEnumInvoke(
       InstructionListIterator iterator,
       InvokeMethod invoke,
       DexMethod method,
       Function<DexMethod, DexEncodedMethod> synthesizor) {
-    utilityMethods.computeIfAbsent(method, synthesizor);
+    if (synthesizor != null) {
+      utilityMethods.computeIfAbsent(method, synthesizor);
+    }
     InvokeStatic replacement =
         new InvokeStatic(
             method, invoke.hasUnusedOutValue() ? null : invoke.outValue(), invoke.arguments());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 8bab979..243e721 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -4,126 +4,409 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstClass;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.NewUnboxedEnumInstance;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.IdentityHashMap;
+import java.util.Collection;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Predicate;
 
 class EnumUnboxingTreeFixer {
 
-  private final Map<DexType, List<DexEncodedMethod>> unboxedEnumsMethods = new IdentityHashMap<>();
-  private final EnumUnboxingLens.Builder lensBuilder = EnumUnboxingLens.enumUnboxingLensBuilder();
+  private final EnumUnboxingLens.Builder lensBuilder;
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory factory;
-  private final Set<DexType> enumsToUnbox;
+  private final EnumDataMap enumDataMap;
+  private final Set<DexProgramClass> unboxedEnums;
   private final EnumUnboxingUtilityClasses utilityClasses;
-  private final EnumUnboxingRewriter enumUnboxerRewriter;
 
   EnumUnboxingTreeFixer(
       AppView<AppInfoWithLiveness> appView,
-      Set<DexType> enumsToUnbox,
-      EnumUnboxingUtilityClasses utilityClasses,
-      EnumUnboxingRewriter enumUnboxerRewriter) {
+      EnumDataMap enumDataMap,
+      Set<DexProgramClass> unboxedEnums,
+      EnumUnboxingUtilityClasses utilityClasses) {
     this.appView = appView;
+    this.enumDataMap = enumDataMap;
     this.factory = appView.dexItemFactory();
-    this.enumsToUnbox = enumsToUnbox;
+    this.lensBuilder =
+        EnumUnboxingLens.enumUnboxingLensBuilder(appView)
+            .mapUnboxedEnums(enumDataMap.getUnboxedEnums());
+    this.unboxedEnums = unboxedEnums;
     this.utilityClasses = utilityClasses;
-    this.enumUnboxerRewriter = enumUnboxerRewriter;
   }
 
-  EnumUnboxingLens fixupTypeReferences() {
-    assert enumUnboxerRewriter != null;
+  Result fixupTypeReferences(IRConverter converter, ExecutorService executorService)
+      throws ExecutionException {
+    PrunedItems.Builder prunedItemsBuilder = PrunedItems.builder();
+
+    // We do this before so that we can still perform lookup of definitions.
+    fixupEnumClassInitializers(converter, executorService);
+
     // Fix all methods and fields using enums to unbox.
+    // TODO(b/191617665): Parallelize this fixup.
     for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (enumsToUnbox.contains(clazz.getType())) {
-        // Clear the initializers and move the static methods to the new location.
-        Set<DexEncodedMethod> methodsToRemove = Sets.newIdentityHashSet();
-        clazz
-            .methods()
-            .forEach(
-                m -> {
-                  if (m.isInitializer()) {
-                    clearEnumToUnboxMethod(m);
-                  } else {
-                    DexType newHolder = utilityClasses.getLocalUtilityClass(clazz).getType();
-                    List<DexEncodedMethod> movedMethods =
-                        unboxedEnumsMethods.computeIfAbsent(newHolder, k -> new ArrayList<>());
-                    movedMethods.add(fixupEncodedMethodToUtility(m, newHolder));
-                    methodsToRemove.add(m);
-                  }
-                });
-        clazz.getMethodCollection().removeMethods(methodsToRemove);
+      if (enumDataMap.isUnboxedEnum(clazz)) {
+        // Clear the initializers and move the other methods to the new location.
+        LocalEnumUnboxingUtilityClass localUtilityClass =
+            utilityClasses.getLocalUtilityClass(clazz);
+        Collection<DexEncodedField> localUtilityFields =
+            createLocalUtilityFields(clazz, localUtilityClass, prunedItemsBuilder);
+        Collection<DexEncodedMethod> localUtilityMethods =
+            createLocalUtilityMethods(clazz, localUtilityClass, prunedItemsBuilder);
+
+        // Cleanup old class.
+        clazz.clearInstanceFields();
+        clazz.clearStaticFields();
+        clazz.getMethodCollection().clearDirectMethods();
+        clazz.getMethodCollection().clearVirtualMethods();
+
+        // Update members on the local utility class.
+        localUtilityClass.getDefinition().setDirectMethods(localUtilityMethods);
+        localUtilityClass.getDefinition().setStaticFields(localUtilityFields);
       } else {
-        clazz
-            .getMethodCollection()
-            .replaceMethods(method -> this.fixupEncodedMethod(clazz, method));
+        clazz.getMethodCollection().replaceMethods(method -> fixupEncodedMethod(clazz, method));
         fixupFields(clazz.staticFields(), clazz::setStaticField);
         fixupFields(clazz.instanceFields(), clazz::setInstanceField);
       }
     }
-    for (DexType toUnbox : enumsToUnbox) {
-      lensBuilder.map(toUnbox, factory.intType);
+
+    return new Result(lensBuilder.build(appView), prunedItemsBuilder.build());
+  }
+
+  private void fixupEnumClassInitializers(IRConverter converter, ExecutorService executorService)
+      throws ExecutionException {
+    DexEncodedField ordinalField =
+        appView
+            .appInfo()
+            .resolveField(appView.dexItemFactory().enumMembers.ordinalField)
+            .getResolvedField();
+    ThreadUtils.processItems(
+        unboxedEnums,
+        unboxedEnum -> fixupEnumClassInitializer(converter, unboxedEnum, ordinalField),
+        executorService);
+  }
+
+  private void fixupEnumClassInitializer(
+      IRConverter converter, DexProgramClass unboxedEnum, DexEncodedField ordinalField) {
+    if (!unboxedEnum.hasClassInitializer()) {
+      assert unboxedEnum.staticFields().isEmpty();
+      return;
     }
-    unboxedEnumsMethods.forEach(
-        (newHolderType, movedMethods) -> {
-          DexProgramClass newHolderClass = appView.definitionFor(newHolderType).asProgramClass();
-          movedMethods.sort(Comparator.comparing(DexEncodedMember::getReference));
-          newHolderClass.addDirectMethods(movedMethods);
+
+    ProgramMethod classInitializer = unboxedEnum.getProgramClassInitializer();
+    EnumData enumData = enumDataMap.get(unboxedEnum);
+    LocalEnumUnboxingUtilityClass localUtilityClass =
+        utilityClasses.getLocalUtilityClass(unboxedEnum);
+
+    // Rewrite enum instantiations + remove static-puts to pruned fields.
+    IRCode code = classInitializer.buildIR(appView);
+    ListIterator<BasicBlock> blockIterator = code.listIterator();
+    Set<Instruction> instructionsToRemove = Sets.newIdentityHashSet();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      InstructionListIterator instructionIterator = block.listIterator(code);
+      while (instructionIterator.hasNext()) {
+        Instruction instruction = instructionIterator.next();
+        if (instructionsToRemove.remove(instruction)) {
+          instructionIterator.removeOrReplaceByDebugLocalRead();
+          continue;
+        }
+
+        if (instruction.isConstClass()) {
+          // Rewrite MyEnum.class.desiredAssertionStatus() to
+          // LocalEnumUtility.class.desiredAssertionStatus() instead of
+          // int.class.desiredAssertionStatus().
+          ConstClass constClass = instruction.asConstClass();
+          if (constClass.getType() != unboxedEnum.getType()) {
+            continue;
+          }
+
+          List<InvokeVirtual> desiredAssertionStatusUsers = new ArrayList<>();
+          for (Instruction user : constClass.outValue().aliasedUsers()) {
+            if (user.isInvokeVirtual()) {
+              InvokeVirtual invoke = user.asInvokeVirtual();
+              if (invoke.getInvokedMethod()
+                  == appView.dexItemFactory().classMethods.desiredAssertionStatus) {
+                desiredAssertionStatusUsers.add(invoke);
+              }
+            }
+          }
+
+          if (!desiredAssertionStatusUsers.isEmpty()) {
+            ConstClass newConstClass =
+                ConstClass.builder()
+                    .setType(localUtilityClass.getType())
+                    .setFreshOutValue(
+                        code, TypeElement.classClassType(appView, definitelyNotNull()))
+                    .setPosition(constClass.getPosition())
+                    .build();
+            instructionIterator.add(newConstClass);
+            constClass
+                .outValue()
+                .replaceSelectiveInstructionUsers(
+                    newConstClass.outValue(), desiredAssertionStatusUsers::contains);
+          }
+        } else if (instruction.isNewInstance()) {
+          NewInstance newInstance = instruction.asNewInstance();
+          DexType rewrittenType = appView.graphLens().lookupType(newInstance.getType());
+          if (rewrittenType == unboxedEnum.getType()) {
+            InvokeDirect constructorInvoke =
+                newInstance.getUniqueConstructorInvoke(appView.dexItemFactory());
+            assert constructorInvoke != null;
+
+            ProgramMethod constructor =
+                unboxedEnum.lookupProgramMethod(constructorInvoke.getInvokedMethod());
+            assert constructor != null;
+
+            InstanceFieldInitializationInfo ordinalInitializationInfo =
+                constructor
+                    .getDefinition()
+                    .getOptimizationInfo()
+                    .getInstanceInitializerInfo(constructorInvoke)
+                    .fieldInitializationInfos()
+                    .get(ordinalField);
+
+            int ordinal;
+            if (ordinalInitializationInfo.isArgumentInitializationInfo()) {
+              Value ordinalValue =
+                  constructorInvoke
+                      .getArgument(
+                          ordinalInitializationInfo
+                              .asArgumentInitializationInfo()
+                              .getArgumentIndex())
+                      .getAliasedValue();
+              assert ordinalValue.isDefinedByInstructionSatisfying(Instruction::isConstNumber);
+              ordinal = ordinalValue.getDefinition().asConstNumber().getIntValue();
+            } else {
+              assert ordinalInitializationInfo.isSingleValue();
+              assert ordinalInitializationInfo.asSingleValue().isSingleNumberValue();
+              ordinal =
+                  ordinalInitializationInfo.asSingleValue().asSingleNumberValue().getIntValue();
+            }
+
+            // Replace by an instruction that produces a value of class type UnboxedEnum (for the
+            // code to type check), which can easily be rewritten to a const-number instruction in
+            // the enum unboxing rewriter.
+            instructionIterator.replaceCurrentInstruction(
+                new NewUnboxedEnumInstance(
+                    unboxedEnum.getType(),
+                    ordinal,
+                    code.createValue(
+                        ClassTypeElement.create(
+                            unboxedEnum.getType(), definitelyNotNull(), appView))));
+
+            instructionsToRemove.add(constructorInvoke);
+          }
+        } else if (instruction.isStaticPut()) {
+          StaticPut staticPut = instruction.asStaticPut();
+          DexField rewrittenField = appView.graphLens().lookupField(staticPut.getField());
+          if (rewrittenField.getHolderType() != unboxedEnum.getType()) {
+            continue;
+          }
+
+          SuccessfulFieldResolutionResult resolutionResult =
+              appView.appInfo().resolveField(rewrittenField).asSuccessfulResolution();
+          if (resolutionResult != null
+              && resolutionResult.getResolvedHolder().isProgramClass()
+              && isPrunedAfterEnumUnboxing(resolutionResult.getProgramField(), enumData)) {
+            instructionIterator.removeOrReplaceByDebugLocalRead();
+          }
+        }
+      }
+    }
+
+    if (!instructionsToRemove.isEmpty()) {
+      InstructionListIterator instructionIterator = code.instructionListIterator();
+      while (instructionIterator.hasNext()) {
+        if (instructionsToRemove.remove(instructionIterator.next())) {
+          instructionIterator.removeOrReplaceByDebugLocalRead();
+        }
+      }
+    }
+
+    converter.removeDeadCodeAndFinalizeIR(
+        code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
+  }
+
+  private Collection<DexEncodedField> createLocalUtilityFields(
+      DexProgramClass unboxedEnum,
+      LocalEnumUnboxingUtilityClass localUtilityClass,
+      PrunedItems.Builder prunedItemsBuilder) {
+    EnumData enumData = enumDataMap.get(unboxedEnum);
+    Map<DexField, DexEncodedField> localUtilityFields =
+        new LinkedHashMap<>(unboxedEnum.staticFields().size());
+    assert localUtilityClass.getDefinition().staticFields().isEmpty();
+
+    unboxedEnum.forEachProgramField(
+        field -> {
+          if (isPrunedAfterEnumUnboxing(field, enumData)) {
+            prunedItemsBuilder.addRemovedField(field.getReference());
+            return;
+          }
+
+          DexEncodedField newLocalUtilityField =
+              createLocalUtilityField(
+                  field,
+                  localUtilityClass,
+                  newFieldSignature -> !localUtilityFields.containsKey(newFieldSignature));
+          assert !localUtilityFields.containsKey(newLocalUtilityField.getReference());
+          localUtilityFields.put(newLocalUtilityField.getReference(), newLocalUtilityField);
         });
-    return lensBuilder.build(appView);
+    return localUtilityFields.values();
   }
 
-  private void clearEnumToUnboxMethod(DexEncodedMethod enumMethod) {
-    // The compiler may have references to the enum methods, but such methods will be removed
-    // and they cannot be reprocessed since their rewriting through the lensCodeRewriter/
-    // enumUnboxerRewriter will generate invalid code.
-    // To work around this problem we clear such methods, i.e., we replace the code object by
-    // an empty throwing code object, so reprocessing won't take time and will be valid.
-    enumMethod.setCode(enumMethod.buildEmptyThrowingCode(appView.options()), appView);
+  private DexEncodedField createLocalUtilityField(
+      ProgramField field,
+      LocalEnumUnboxingUtilityClass localUtilityClass,
+      Predicate<DexField> availableFieldSignatures) {
+    // Create a new, fresh field signature on the local utility class.
+    DexField newFieldSignature =
+        factory.createFreshFieldNameWithoutHolder(
+            localUtilityClass.getType(),
+            fixupType(field.getType()),
+            field.getName().toString(),
+            availableFieldSignatures);
+
+    // Record the move.
+    lensBuilder.move(field.getReference(), newFieldSignature);
+
+    // Clear annotations and publicize.
+    return field
+        .getDefinition()
+        .toTypeSubstitutedField(
+            newFieldSignature,
+            builder ->
+                builder
+                    .clearAnnotations()
+                    .modifyAccessFlags(
+                        accessFlags -> {
+                          assert accessFlags.isStatic();
+                          accessFlags.promoteToPublic();
+                        }));
   }
 
-  private DexEncodedMethod fixupEncodedMethodToUtility(
-      DexEncodedMethod encodedMethod, DexType newHolder) {
-    DexMethod method = encodedMethod.getReference();
-    DexString newMethodName =
-        factory.createString(
-            enumUnboxerRewriter.compatibleName(method.holder)
-                + "$"
-                + (encodedMethod.isStatic() ? "s" : "v")
-                + "$"
-                + method.name.toString());
-    DexProto proto = encodedMethod.isStatic() ? method.proto : factory.prependHolderToProto(method);
-    DexMethod newMethod = factory.createMethod(newHolder, fixupProto(proto), newMethodName);
-    assert appView.definitionFor(encodedMethod.getHolderType()).lookupMethod(newMethod) == null;
-    lensBuilder.move(method, newMethod, encodedMethod.isStatic(), true);
-    encodedMethod.accessFlags.promoteToPublic();
-    encodedMethod.accessFlags.promoteToStatic();
-    encodedMethod.clearAnnotations();
-    encodedMethod.clearParameterAnnotations();
-    return encodedMethod.toTypeSubstitutedMethod(
-        newMethod, builder -> builder.setCompilationState(encodedMethod.getCompilationState()));
+  private Collection<DexEncodedMethod> createLocalUtilityMethods(
+      DexProgramClass unboxedEnum,
+      LocalEnumUnboxingUtilityClass localUtilityClass,
+      PrunedItems.Builder prunedItemsBuilder) {
+    Map<DexMethod, DexEncodedMethod> localUtilityMethods =
+        new LinkedHashMap<>(
+            localUtilityClass.getDefinition().getMethodCollection().size()
+                + unboxedEnum.getMethodCollection().size());
+    localUtilityClass
+        .getDefinition()
+        .forEachMethod(method -> localUtilityMethods.put(method.getReference(), method));
+
+    unboxedEnum.forEachProgramMethod(
+        method -> {
+          if (method.getDefinition().isInstanceInitializer()) {
+            prunedItemsBuilder.addRemovedMethod(method.getReference());
+          } else {
+            DexEncodedMethod newLocalUtilityMethod =
+                createLocalUtilityMethod(
+                    method,
+                    localUtilityClass,
+                    newMethodSignature -> !localUtilityMethods.containsKey(newMethodSignature));
+            assert !localUtilityMethods.containsKey(newLocalUtilityMethod.getReference());
+            localUtilityMethods.put(newLocalUtilityMethod.getReference(), newLocalUtilityMethod);
+          }
+        });
+    return localUtilityMethods.values();
+  }
+
+  private DexEncodedMethod createLocalUtilityMethod(
+      ProgramMethod method,
+      LocalEnumUnboxingUtilityClass localUtilityClass,
+      Predicate<DexMethod> availableMethodSignatures) {
+    DexMethod methodReference = method.getReference();
+
+    // Create a new, fresh method signature on the local utility class.
+    DexMethod newMethod =
+        method.getDefinition().isClassInitializer()
+            ? factory.createClassInitializer(localUtilityClass.getType())
+            : factory.createFreshMethodNameWithoutHolder(
+                method.getName().toString(),
+                fixupProto(
+                    method.getAccessFlags().isStatic()
+                        ? method.getProto()
+                        : factory.prependHolderToProto(methodReference)),
+                localUtilityClass.getType(),
+                availableMethodSignatures);
+
+    // Record the move.
+    lensBuilder.move(methodReference, newMethod, method.getDefinition().isStatic(), true);
+
+    return method
+        .getDefinition()
+        .toTypeSubstitutedMethod(
+            newMethod,
+            builder ->
+                builder
+                    .clearAllAnnotations()
+                    .modifyAccessFlags(
+                        accessFlags -> {
+                          if (method.getDefinition().isClassInitializer()) {
+                            assert accessFlags.isStatic();
+                          } else {
+                            accessFlags.promoteToPublic();
+                            accessFlags.promoteToStatic();
+                          }
+                        })
+                    .setCompilationState(method.getDefinition().getCompilationState())
+                    .unsetIsLibraryMethodOverride());
+  }
+
+  private boolean isPrunedAfterEnumUnboxing(ProgramField field, EnumData enumData) {
+    return !field.getAccessFlags().isStatic()
+        || ((enumData.hasUnboxedValueFor(field) || enumData.matchesValuesField(field))
+            && !field.getDefinition().getOptimizationInfo().isDead());
   }
 
   private DexEncodedMethod fixupEncodedMethod(DexProgramClass holder, DexEncodedMethod method) {
@@ -168,7 +451,8 @@
     return method
         .getOptimizationInfo()
         .getSimpleInliningConstraint()
-        .rewrittenWithUnboxedArguments(unboxedArgumentIndices);
+        .rewrittenWithUnboxedArguments(
+            unboxedArgumentIndices, appView.simpleInliningConstraintFactory());
   }
 
   private DexMethod ensureUniqueMethod(DexEncodedMethod encodedMethod, DexMethod newMethod) {
@@ -234,12 +518,7 @@
       }
       return type.replaceBaseType(fixed, factory);
     }
-    if (type.isClassType() && enumsToUnbox.contains(type)) {
-      DexType intType = factory.intType;
-      lensBuilder.map(type, intType);
-      return intType;
-    }
-    return type;
+    return type.isClassType() && enumDataMap.isUnboxedEnum(type) ? factory.intType : type;
   }
 
   private DexType[] fixupTypes(DexType[] types) {
@@ -249,4 +528,23 @@
     }
     return result;
   }
+
+  public static class Result {
+
+    private final EnumUnboxingLens lens;
+    private final PrunedItems prunedItems;
+
+    Result(EnumUnboxingLens lens, PrunedItems prunedItems) {
+      this.lens = lens;
+      this.prunedItems = prunedItems;
+    }
+
+    EnumUnboxingLens getLens() {
+      return lens;
+    }
+
+    PrunedItems getPrunedItems() {
+      return prunedItems;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
index 1753375..05d4955 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
@@ -73,7 +73,7 @@
       SharedEnumUnboxingUtilityClass sharedUtilityClass =
           SharedEnumUnboxingUtilityClass.builder(
                   appView, enumDataMap, enumsToUnbox, fieldAccessInfoCollectionModifierBuilder)
-              .build(appBuilder);
+              .build();
       ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses =
           createLocalUtilityClasses(enumsToUnbox, appBuilder);
       this.localUtilityClasses = localUtilityClasses;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
index 19adc9f..3e556e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -26,21 +26,21 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.FieldAccessFlags;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.ConsumerUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -50,17 +50,16 @@
 
 public class SharedEnumUnboxingUtilityClass extends EnumUnboxingUtilityClass {
 
-  public static final String ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX =
-      "$r8$EnumUnboxingSharedUtility";
-
   private final DexProgramClass sharedUtilityClass;
-  private final ProgramField valuesField;
+  private final DexProgramClass synthesizingContext;
   private final ProgramMethod valuesMethod;
 
   public SharedEnumUnboxingUtilityClass(
-      DexProgramClass sharedUtilityClass, ProgramField valuesField, ProgramMethod valuesMethod) {
+      DexProgramClass sharedUtilityClass,
+      DexProgramClass synthesizingContext,
+      ProgramMethod valuesMethod) {
     this.sharedUtilityClass = sharedUtilityClass;
-    this.valuesField = valuesField;
+    this.synthesizingContext = synthesizingContext;
     this.valuesMethod = valuesMethod;
   }
 
@@ -73,15 +72,46 @@
         appView, enumDataMap, enumsToUnbox, fieldAccessInfoCollectionModifierBuilder);
   }
 
+  public ProgramMethod ensureCompareToMethod(
+      AppView<AppInfoWithLiveness> appView,
+      IRConverter converter,
+      MethodProcessor methodProcessor) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    // TODO(b/191957637): Consider creating free flowing static methods instead. The synthetic
+    //  infrastructure needs to be augmented with a new method ensureFixedMethod() or
+    //  ensureFixedFreeFlowingMethod() for this, if we want to create only one utility method (and
+    //  not one per use context).
+    return appView
+        .getSyntheticItems()
+        .ensureFixedClassMethod(
+            dexItemFactory.enumMembers.compareTo.getName(),
+            dexItemFactory.createProto(
+                dexItemFactory.intType, dexItemFactory.intType, dexItemFactory.intType),
+            SyntheticKind.ENUM_UNBOXING_SHARED_UTILITY_CLASS,
+            synthesizingContext,
+            appView,
+            ConsumerUtils.emptyConsumer(),
+            methodBuilder ->
+                methodBuilder
+                    .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                    .setCode(
+                        method ->
+                            EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(
+                                appView.options(), method))
+                    .setClassFileVersion(CfVersion.V1_6),
+            newMethod ->
+                converter.processDesugaredMethod(
+                    newMethod,
+                    OptimizationFeedbackSimple.getInstance(),
+                    methodProcessor,
+                    methodProcessor.createMethodProcessingContext(newMethod)));
+  }
+
   @Override
   public DexProgramClass getDefinition() {
     return sharedUtilityClass;
   }
 
-  public ProgramField getValuesField() {
-    return valuesField;
-  }
-
   public ProgramMethod getValuesMethod() {
     return valuesMethod;
   }
@@ -95,12 +125,10 @@
     private final AppView<AppInfoWithLiveness> appView;
     private final DexItemFactory dexItemFactory;
     private final EnumDataMap enumDataMap;
-    private final Set<DexProgramClass> enumsToUnbox;
     private final FieldAccessInfoCollectionModifier.Builder
         fieldAccessInfoCollectionModifierBuilder;
-    private final DexType sharedUtilityClassType;
+    private final DexProgramClass synthesizingContext;
 
-    private DexEncodedField valuesField;
     private DexEncodedMethod valuesMethod;
 
     private Builder(
@@ -108,52 +136,40 @@
         EnumDataMap enumDataMap,
         Set<DexProgramClass> enumsToUnbox,
         FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+      DexProgramClass synthesizingContext = findDeterministicContextType(enumsToUnbox);
       this.appView = appView;
       this.dexItemFactory = appView.dexItemFactory();
       this.enumDataMap = enumDataMap;
-      this.enumsToUnbox = enumsToUnbox;
       this.fieldAccessInfoCollectionModifierBuilder = fieldAccessInfoCollectionModifierBuilder;
-      this.sharedUtilityClassType =
-          EnumUnboxingUtilityClasses.Builder.getUtilityClassType(
-              findDeterministicContextType(enumsToUnbox),
-              ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX,
-              dexItemFactory);
-
-      assert appView.appInfo().definitionForWithoutExistenceAssert(sharedUtilityClassType) == null;
+      this.synthesizingContext = synthesizingContext;
     }
 
-    SharedEnumUnboxingUtilityClass build(DirectMappedDexApplication.Builder appBuilder) {
+    SharedEnumUnboxingUtilityClass build() {
       DexProgramClass clazz = createClass();
-      appBuilder.addSynthesizedClass(clazz);
-      appView.appInfo().addSynthesizedClassToBase(clazz, enumsToUnbox);
       return new SharedEnumUnboxingUtilityClass(
-          clazz, new ProgramField(clazz, valuesField), new ProgramMethod(clazz, valuesMethod));
+          clazz, synthesizingContext, new ProgramMethod(clazz, valuesMethod));
     }
 
     private DexProgramClass createClass() {
-      DexEncodedField valuesField = createValuesField(sharedUtilityClassType);
-      return new DexProgramClass(
-          sharedUtilityClassType,
-          null,
-          new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
-          ClassAccessFlags.createPublicFinalSynthetic(),
-          dexItemFactory.objectType,
-          DexTypeList.empty(),
-          null,
-          null,
-          Collections.emptyList(),
-          null,
-          Collections.emptyList(),
-          ClassSignature.noSignature(),
-          DexAnnotationSet.empty(),
-          new DexEncodedField[] {valuesField},
-          DexEncodedField.EMPTY_ARRAY,
-          new DexEncodedMethod[] {
-            createClassInitializer(valuesField), createValuesMethod(valuesField)
-          },
-          DexEncodedMethod.EMPTY_ARRAY,
-          dexItemFactory.getSkipNameValidationForTesting(),
-          DexProgramClass::checksumFromType);
+      DexProgramClass clazz =
+          appView
+              .getSyntheticItems()
+              .createFixedClass(
+                  SyntheticKind.ENUM_UNBOXING_SHARED_UTILITY_CLASS,
+                  synthesizingContext,
+                  appView,
+                  classBuilder -> {
+                    DexType sharedUtilityClassType = classBuilder.getType();
+                    DexEncodedField valuesField = createValuesField(sharedUtilityClassType);
+                    classBuilder
+                        .setDirectMethods(
+                            ImmutableList.of(
+                                createClassInitializer(sharedUtilityClassType, valuesField),
+                                createValuesMethod(sharedUtilityClassType, valuesField)))
+                        .setStaticFields(ImmutableList.of(valuesField));
+                  });
+      assert clazz.getAccessFlags().equals(ClassAccessFlags.createPublicFinalSynthetic());
+      return clazz;
     }
 
     // Fields.
@@ -172,25 +188,26 @@
       fieldAccessInfoCollectionModifierBuilder
           .recordFieldReadInUnknownContext(valuesField.getReference())
           .recordFieldWriteInUnknownContext(valuesField.getReference());
-      this.valuesField = valuesField;
       return valuesField;
     }
 
     // Methods.
 
-    private DexEncodedMethod createClassInitializer(DexEncodedField valuesField) {
+    private DexEncodedMethod createClassInitializer(
+        DexType sharedUtilityClassType, DexEncodedField valuesField) {
       return new DexEncodedMethod(
           dexItemFactory.createClassInitializer(sharedUtilityClassType),
           MethodAccessFlags.createForClassInitializer(),
           MethodTypeSignature.noSignature(),
           DexAnnotationSet.empty(),
           ParameterAnnotationsList.empty(),
-          createClassInitializerCode(valuesField),
+          createClassInitializerCode(sharedUtilityClassType, valuesField),
           DexEncodedMethod.D8_R8_SYNTHESIZED,
           CfVersion.V1_6);
     }
 
-    private CfCode createClassInitializerCode(DexEncodedField valuesField) {
+    private CfCode createClassInitializerCode(
+        DexType sharedUtilityClassType, DexEncodedField valuesField) {
       int maxValuesArraySize = enumDataMap.getMaxValuesSize();
       int numberOfInstructions = 4 + maxValuesArraySize * 4;
       List<CfInstruction> instructions = new ArrayList<>(numberOfInstructions);
@@ -217,7 +234,8 @@
           Collections.emptyList());
     }
 
-    private DexEncodedMethod createValuesMethod(DexEncodedField valuesField) {
+    private DexEncodedMethod createValuesMethod(
+        DexType sharedUtilityClassType, DexEncodedField valuesField) {
       DexEncodedMethod valuesMethod =
           new DexEncodedMethod(
               dexItemFactory.createMethod(
@@ -228,14 +246,15 @@
               MethodTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
-              createValuesMethodCode(valuesField),
+              createValuesMethodCode(sharedUtilityClassType, valuesField),
               DexEncodedMethod.D8_R8_SYNTHESIZED,
               CfVersion.V1_6);
       this.valuesMethod = valuesMethod;
       return valuesMethod;
     }
 
-    private CfCode createValuesMethodCode(DexEncodedField valuesField) {
+    private CfCode createValuesMethodCode(
+        DexType sharedUtilityClassType, DexEncodedField valuesField) {
       int maxStack = 5;
       int maxLocals = 2;
       int argumentLocalSlot = 0;
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 5b99126..7a0dae1 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
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.analysis.inlining.NeverSimpleInliningConstraint;
 import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -190,9 +191,9 @@
   }
 
   public MutableMethodOptimizationInfo fixupInstanceInitializerInfo(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems) {
     instanceInitializerInfoCollection =
-        instanceInitializerInfoCollection.rewrittenWithLens(appView, lens);
+        instanceInitializerInfoCollection.rewrittenWithLens(appView, lens, prunedItems);
     return this;
   }
 
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 ede372a..89674f5 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
@@ -30,9 +30,7 @@
       MemberOptimizationInfo<?> optimizationInfo = member.getOptimizationInfo();
       if (optimizationInfo.isMutableOptimizationInfo()) {
         member.accept(
-            field -> {
-              fixup(field, optimizationInfo.asMutableFieldOptimizationInfo());
-            },
+            field -> fixup(field, optimizationInfo.asMutableFieldOptimizationInfo()),
             method -> fixup(method, optimizationInfo.asMutableMethodOptimizationInfo()));
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextInsensitiveInstanceInitializerInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextInsensitiveInstanceInitializerInfoCollection.java
index 6cf070d..537bda3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextInsensitiveInstanceInitializerInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextInsensitiveInstanceInitializerInfoCollection.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
@@ -35,8 +36,9 @@
 
   @Override
   public ContextInsensitiveInstanceInitializerInfoCollection rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens) {
-    NonTrivialInstanceInitializerInfo rewrittenInfo = info.rewrittenWithLens(appView, lens);
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems) {
+    NonTrivialInstanceInitializerInfo rewrittenInfo =
+        info.rewrittenWithLens(appView, lens, prunedItems);
     if (rewrittenInfo != info) {
       return new ContextInsensitiveInstanceInitializerInfoCollection(rewrittenInfo);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextSensitiveInstanceInitializerInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextSensitiveInstanceInitializerInfoCollection.java
index 3347587..acaeef9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextSensitiveInstanceInitializerInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ContextSensitiveInstanceInitializerInfoCollection.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableMap;
@@ -49,9 +50,11 @@
 
   @Override
   public InstanceInitializerInfoCollection rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems) {
     Builder builder = builder();
-    infos.forEach((context, info) -> builder.put(context, info.rewrittenWithLens(appView, lens)));
+    infos.forEach(
+        (context, info) ->
+            builder.put(context, info.rewrittenWithLens(appView, lens, prunedItems)));
     return builder.build();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
index e976680..8391de8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
 import com.android.tools.r8.ir.optimize.info.field.EmptyInstanceFieldInitializationInfoCollection;
@@ -66,7 +67,7 @@
 
   @Override
   public InstanceInitializerInfo rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/EmptyInstanceInitializerInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/EmptyInstanceInitializerInfoCollection.java
index d7d3560..7100e35 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/EmptyInstanceInitializerInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/EmptyInstanceInitializerInfoCollection.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
@@ -37,7 +38,7 @@
 
   @Override
   public EmptyInstanceInitializerInfoCollection rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems) {
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
index 27576e9..edd78e5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -71,5 +72,5 @@
   }
 
   public abstract InstanceInitializerInfo rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens);
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfoCollection.java
index ce53a6a..0139cf0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfoCollection.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.MapUtils;
@@ -36,7 +37,7 @@
   public abstract boolean isEmpty();
 
   public abstract InstanceInitializerInfoCollection rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens);
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems);
 
   public static class Builder {
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
index 9dd2750..7066531 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
@@ -99,11 +100,11 @@
 
   @Override
   public NonTrivialInstanceInitializerInfo rewrittenWithLens(
-      AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+      AppView<AppInfoWithLiveness> appView, GraphLens lens, PrunedItems prunedItems) {
     return new NonTrivialInstanceInitializerInfo(
         data,
         fieldInitializationInfos.rewrittenWithLens(appView, lens),
-        readSet.rewrittenWithLens(appView, lens),
+        readSet.rewrittenWithLens(appView, lens, prunedItems),
         lens.getRenamedMethodSignature(parent));
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
index dba0cde..35afed1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -60,6 +60,10 @@
     assert existing == null;
   }
 
+  public MethodProcessor getMethodProcessor() {
+    return methodProcessor;
+  }
+
   public boolean verifyIRCacheIsEmpty() {
     assert cache.isEmpty();
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
index 3703148..0e8d33c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
@@ -68,6 +68,7 @@
     return ImmutableSet.<DexMethod>builder()
         .add(dexItemFactory.booleanMembers.toString)
         .add(dexItemFactory.byteMembers.toString)
+        .add(dexItemFactory.classMethods.desiredAssertionStatus)
         .add(dexItemFactory.charMembers.toString)
         .add(dexItemFactory.doubleMembers.toString)
         .add(dexItemFactory.enumMembers.constructor)
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index 254511e..db93ced 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -31,7 +31,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.utils.collections.ImmutableDeque;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
 import java.util.ArrayList;
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index ac1ad75..6a4089d 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
@@ -272,12 +273,46 @@
     } else {
       definition.rewriteAllAnnotations(
           (annotation, isParameterAnnotation) ->
-              DexAnnotation.isJavaLangRetentionAnnotation(annotation, appView.dexItemFactory())
-                  ? annotation
-                  : null);
+              shouldRetainAnnotationOnAnnotationClass(annotation) ? annotation : null);
     }
   }
 
+  private boolean shouldRetainAnnotationOnAnnotationClass(DexAnnotation annotation) {
+    if (DexAnnotation.isAnnotationDefaultAnnotation(annotation, appView.dexItemFactory())) {
+      return shouldRetainAnnotationDefaultAnnotationOnAnnotationClass(annotation);
+    }
+    if (DexAnnotation.isJavaLangRetentionAnnotation(annotation, appView.dexItemFactory())) {
+      return shouldRetainRetentionAnnotationOnAnnotationClass(annotation);
+    }
+    return false;
+  }
+
+  private boolean shouldRetainAnnotationDefaultAnnotationOnAnnotationClass(
+      DexAnnotation annotation) {
+    // We currently always retain the @AnnotationDefault annotations for annotation classes. In full
+    // mode we could consider only retaining @AnnotationDefault annotations for pinned annotations,
+    // as this is consistent with removing all annotations for non-kept items.
+    return true;
+  }
+
+  private boolean shouldRetainRetentionAnnotationOnAnnotationClass(DexAnnotation annotation) {
+    // Retain @Retention annotations that are different from @Retention(RetentionPolicy.CLASS).
+    if (annotation.annotation.getNumberOfElements() != 1) {
+      return true;
+    }
+    DexAnnotationElement element = annotation.annotation.getElement(0);
+    if (element.name != appView.dexItemFactory().valueString) {
+      return true;
+    }
+    DexValue value = element.getValue();
+    if (!value.isDexValueEnum()
+        || value.asDexValueEnum().getValue()
+            != appView.dexItemFactory().javaLangAnnotationRetentionPolicyMembers.CLASS) {
+      return true;
+    }
+    return false;
+  }
+
   private void stripAttributes(DexProgramClass clazz, KeepClassInfo keepInfo) {
     // If [clazz] is mentioned by a keep rule, it could be used for reflection, and we therefore
     // need to keep the enclosing method and inner classes attributes, if requested. In Proguard
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 a4478d6..2ff9008 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -46,8 +46,8 @@
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.utils.CollectionUtils;
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index ea9b2d7..9537206 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -100,9 +100,12 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.R8CfInstructionDesugaringEventConsumer;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringCollection;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer.R8PostProcessingDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.LambdaClass;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
@@ -360,7 +363,7 @@
   private final LiveFieldsSet liveFields;
 
   /** A queue of items that need processing. Different items trigger different actions. */
-  private final EnqueuerWorklist workList;
+  private EnqueuerWorklist workList;
 
   private final ProguardCompatibilityActions.Builder proguardCompatibilityActionsBuilder;
 
@@ -1742,6 +1745,8 @@
             || appView.appInfo().getMainDexInfo().isTracedRoot(clazz, appView.getSyntheticItems())
         : "Class " + clazz.toSourceString() + " was not a main dex root in the first round";
 
+    assert !appView.unboxedEnums().isUnboxedEnum(clazz);
+
     // Mark types in inner-class attributes referenced.
     {
       BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer =
@@ -3118,7 +3123,7 @@
     }
   }
 
-  private static class SyntheticAdditions {
+  public static class SyntheticAdditions {
 
     private final ProcessorContext processorContext;
     private Map<DexMethod, MethodProcessingContext> methodProcessingContexts =
@@ -3130,6 +3135,8 @@
 
     Map<DexType, DexClasspathClass> syntheticClasspathClasses = new IdentityHashMap<>();
 
+    Map<DexProgramClass, List<DexClass>> injectedInterfaces = new IdentityHashMap<>();
+
     // Subset of live methods that need have keep requirements.
     List<Pair<ProgramMethod, Consumer<KeepMethodInfo.Joiner>>> liveMethodsWithKeepActions =
         new ArrayList<>();
@@ -3152,25 +3159,36 @@
       return empty;
     }
 
-    void addLiveClasspathClass(DexClasspathClass clazz) {
+    public void addLiveClasspathClass(DexClasspathClass clazz) {
       DexClasspathClass old = syntheticClasspathClasses.put(clazz.type, clazz);
       assert old == null;
     }
 
-    void addLiveMethod(ProgramMethod method) {
+    public void addLiveMethod(ProgramMethod method) {
       DexMethod signature = method.getDefinition().getReference();
       assert !liveMethods.containsKey(signature);
       liveMethods.put(signature, method);
     }
 
+    public void injectInterface(DexProgramClass clazz, DexClass newInterface) {
+      List<DexClass> newInterfaces =
+          injectedInterfaces.computeIfAbsent(clazz, ignored -> new ArrayList<>());
+      newInterfaces.add(newInterface);
+    }
+
     void addLiveMethodWithKeepAction(
         ProgramMethod method, Consumer<KeepMethodInfo.Joiner> keepAction) {
       addLiveMethod(method);
       liveMethodsWithKeepActions.add(new Pair<>(method, keepAction));
     }
 
+    public ProgramMethodSet getLiveMethods() {
+      ProgramMethodSet set = ProgramMethodSet.create();
+      liveMethods.values().forEach(set::add);
+      return set;
+    }
+
     void enqueueWorkItems(Enqueuer enqueuer) {
-      assert !isEmpty();
       assert enqueuer.mode.isInitialTreeShaking();
 
       // All synthetic additions are initial tree shaking only. No need to track keep reasons.
@@ -3188,6 +3206,11 @@
         enqueuer.workList.enqueueMarkMethodLiveAction(liveMethod, liveMethod, fakeReason);
       }
       enqueuer.liveNonProgramTypes.addAll(syntheticClasspathClasses.values());
+      injectedInterfaces.forEach(
+          (clazz, itfs) -> {
+            enqueuer.objectAllocationInfoCollection.injectInterfaces(
+                enqueuer.appInfo(), clazz, itfs);
+          });
     }
   }
 
@@ -3224,7 +3247,8 @@
         CfInstructionDesugaringEventConsumer.createForR8(
             appView,
             this::recordLambdaSynthesizingContext,
-            this::recordTwrCloseResourceMethodSynthesizingContext);
+            this::recordTwrCloseResourceMethodSynthesizingContext,
+            additions);
     ThreadUtils.processItems(
         pendingDesugaring,
         method ->
@@ -3612,6 +3636,10 @@
         break;
       }
 
+      if (mode.isInitialTreeShaking()) {
+        postProcessingDesugaring();
+      }
+
       if (Log.ENABLED) {
         Set<DexEncodedMethod> allLive = Sets.newIdentityHashSet();
         Set<DexEncodedMethod> reachableNotLive = Sets.difference(allLive, liveMethods.getItems());
@@ -3627,6 +3655,37 @@
     }
   }
 
+  private void postProcessingDesugaring() {
+    SyntheticAdditions syntheticAdditions =
+        new SyntheticAdditions(appView.createProcessorContext());
+
+    R8PostProcessingDesugaringEventConsumer eventConsumer =
+        CfPostProcessingDesugaringEventConsumer.createForR8(
+            appView, syntheticAdditions::addLiveMethod, syntheticAdditions);
+    CfPostProcessingDesugaringCollection.create(appView, desugaring.getRetargetingInfo())
+        .postProcessingDesugaring(eventConsumer);
+
+    if (syntheticAdditions.isEmpty()) {
+      return;
+    }
+
+    // Commit the pending synthetics and recompute subtypes.
+    appInfo = appInfo.rebuildWithClassHierarchy(app -> app);
+    appView.setAppInfo(appInfo);
+    subtypingInfo = new SubtypingInfo(appView);
+
+    syntheticAdditions.enqueueWorkItems(this);
+
+    workList = workList.nonPushable(syntheticAdditions.getLiveMethods());
+
+    while (!workList.isEmpty()) {
+      EnqueuerAction action = workList.poll();
+      action.run(this);
+    }
+
+    eventConsumer.finalizeDesugaring();
+  }
+
   private long getNumberOfLiveItems() {
     long result = liveTypes.items.size();
     result += liveMethods.items.size();
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 65081d5..38e40dd 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -14,10 +15,11 @@
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.ArrayDeque;
 import java.util.Queue;
 
-public class EnqueuerWorklist {
+public abstract class EnqueuerWorklist {
 
   public abstract static class EnqueuerAction {
     public abstract void run(Enqueuer enqueuer);
@@ -279,15 +281,16 @@
     }
   }
 
-  private final Enqueuer enqueuer;
-  private final Queue<EnqueuerAction> queue = new ArrayDeque<>();
-
-  private EnqueuerWorklist(Enqueuer enqueuer) {
-    this.enqueuer = enqueuer;
-  }
+  final Enqueuer enqueuer;
+  final Queue<EnqueuerAction> queue;
 
   public static EnqueuerWorklist createWorklist(Enqueuer enqueuer) {
-    return new EnqueuerWorklist(enqueuer);
+    return new PushableEnqueuerWorkList(enqueuer);
+  }
+
+  private EnqueuerWorklist(Enqueuer enqueuer, Queue<EnqueuerAction> queue) {
+    this.enqueuer = enqueuer;
+    this.queue = queue;
   }
 
   public boolean isEmpty() {
@@ -298,88 +301,275 @@
     return queue.poll();
   }
 
-  boolean enqueueAssertAction(Action assertion) {
-    if (InternalOptions.assertionsEnabled()) {
-      queue.add(new AssertAction(assertion));
-    }
-    return true;
-  }
+  abstract EnqueuerWorklist nonPushable(ProgramMethodSet enqueuedMarkMethodLive);
 
-  void enqueueMarkReachableDirectAction(
-      DexMethod method, ProgramDefinition context, KeepReason reason) {
-    queue.add(new MarkReachableDirectAction(method, context, reason));
-  }
+  abstract boolean enqueueAssertAction(Action assertion);
 
-  void enqueueMarkReachableSuperAction(DexMethod method, ProgramMethod from) {
-    queue.add(new MarkReachableSuperAction(method, from));
-  }
+  abstract void enqueueMarkReachableDirectAction(
+      DexMethod method, ProgramDefinition context, KeepReason reason);
 
-  public void enqueueMarkFieldAsReachableAction(
-      ProgramField field, ProgramDefinition context, KeepReason reason) {
-    queue.add(new MarkFieldAsReachableAction(field, context, reason));
-  }
+  abstract void enqueueMarkReachableSuperAction(DexMethod method, ProgramMethod from);
 
-  // TODO(b/142378367): Context is the containing method that is cause of the instantiation.
-  // Consider updating call sites with the context information to increase precision where possible.
-  public void enqueueMarkInstantiatedAction(
+  public abstract void enqueueMarkFieldAsReachableAction(
+      ProgramField field, ProgramDefinition context, KeepReason reason);
+
+  public abstract void enqueueMarkInstantiatedAction(
       DexProgramClass clazz,
       ProgramMethod context,
       InstantiationReason instantiationReason,
-      KeepReason keepReason) {
-    assert !clazz.isAnnotation();
-    assert !clazz.isInterface();
-    queue.add(new MarkInstantiatedAction(clazz, context, instantiationReason, keepReason));
-  }
+      KeepReason keepReason);
 
-  void enqueueMarkAnnotationInstantiatedAction(DexProgramClass clazz, KeepReasonWitness reason) {
-    assert clazz.isAnnotation();
-    assert clazz.isInterface();
-    queue.add(new MarkAnnotationInstantiatedAction(clazz, reason));
-  }
+  abstract void enqueueMarkAnnotationInstantiatedAction(
+      DexProgramClass clazz, KeepReasonWitness reason);
 
-  void enqueueMarkInterfaceInstantiatedAction(DexProgramClass clazz, KeepReasonWitness reason) {
-    assert !clazz.isAnnotation();
-    assert clazz.isInterface();
-    queue.add(new MarkInterfaceInstantiatedAction(clazz, reason));
-  }
+  abstract void enqueueMarkInterfaceInstantiatedAction(
+      DexProgramClass clazz, KeepReasonWitness reason);
 
-  boolean enqueueMarkMethodLiveAction(
-      ProgramMethod method, ProgramDefinition context, KeepReason reason) {
-    if (enqueuer.addLiveMethod(method, reason)) {
-      queue.add(new MarkMethodLiveAction(method, context));
-      if (!enqueuer.isMethodTargeted(method)) {
-        queue.add(new TraceMethodDefinitionExcludingCodeAction(method));
+  abstract boolean enqueueMarkMethodLiveAction(
+      ProgramMethod method, ProgramDefinition context, KeepReason reason);
+
+  abstract void enqueueMarkMethodKeptAction(ProgramMethod method, KeepReason reason);
+
+  abstract void enqueueMarkFieldKeptAction(ProgramField field, KeepReasonWitness witness);
+
+  public abstract void enqueueTraceCodeAction(ProgramMethod method);
+
+  public abstract void enqueueTraceConstClassAction(DexType type, ProgramMethod context);
+
+  public abstract void enqueueTraceInvokeDirectAction(
+      DexMethod invokedMethod, ProgramMethod context);
+
+  public abstract void enqueueTraceNewInstanceAction(DexType type, ProgramMethod context);
+
+  public abstract void enqueueTraceStaticFieldRead(DexField field, ProgramMethod context);
+
+  static class PushableEnqueuerWorkList extends EnqueuerWorklist {
+
+    PushableEnqueuerWorkList(Enqueuer enqueuer) {
+      super(enqueuer, new ArrayDeque<>());
+    }
+
+    @Override
+    EnqueuerWorklist nonPushable(ProgramMethodSet enqueuedMarkMethodLive) {
+      return new NonPushableEnqueuerWorklist(this, enqueuedMarkMethodLive);
+    }
+
+    @Override
+    boolean enqueueAssertAction(Action assertion) {
+      if (InternalOptions.assertionsEnabled()) {
+        queue.add(new AssertAction(assertion));
       }
       return true;
     }
-    return false;
+
+    @Override
+    void enqueueMarkReachableDirectAction(
+        DexMethod method, ProgramDefinition context, KeepReason reason) {
+      queue.add(new MarkReachableDirectAction(method, context, reason));
+    }
+
+    @Override
+    void enqueueMarkReachableSuperAction(DexMethod method, ProgramMethod from) {
+      queue.add(new MarkReachableSuperAction(method, from));
+    }
+
+    @Override
+    public void enqueueMarkFieldAsReachableAction(
+        ProgramField field, ProgramDefinition context, KeepReason reason) {
+      queue.add(new MarkFieldAsReachableAction(field, context, reason));
+    }
+
+    // TODO(b/142378367): Context is the containing method that is cause of the instantiation.
+    // Consider updating call sites with the context information to increase precision where
+    // possible.
+    @Override
+    public void enqueueMarkInstantiatedAction(
+        DexProgramClass clazz,
+        ProgramMethod context,
+        InstantiationReason instantiationReason,
+        KeepReason keepReason) {
+      assert !clazz.isAnnotation();
+      assert !clazz.isInterface();
+      queue.add(new MarkInstantiatedAction(clazz, context, instantiationReason, keepReason));
+    }
+
+    @Override
+    void enqueueMarkAnnotationInstantiatedAction(DexProgramClass clazz, KeepReasonWitness reason) {
+      assert clazz.isAnnotation();
+      assert clazz.isInterface();
+      queue.add(new MarkAnnotationInstantiatedAction(clazz, reason));
+    }
+
+    @Override
+    void enqueueMarkInterfaceInstantiatedAction(DexProgramClass clazz, KeepReasonWitness reason) {
+      assert !clazz.isAnnotation();
+      assert clazz.isInterface();
+      queue.add(new MarkInterfaceInstantiatedAction(clazz, reason));
+    }
+
+    @Override
+    boolean enqueueMarkMethodLiveAction(
+        ProgramMethod method, ProgramDefinition context, KeepReason reason) {
+      if (enqueuer.addLiveMethod(method, reason)) {
+        queue.add(new MarkMethodLiveAction(method, context));
+        if (!enqueuer.isMethodTargeted(method)) {
+          queue.add(new TraceMethodDefinitionExcludingCodeAction(method));
+        }
+        return true;
+      }
+      return false;
+    }
+
+    @Override
+    void enqueueMarkMethodKeptAction(ProgramMethod method, KeepReason reason) {
+      queue.add(new MarkMethodKeptAction(method, reason));
+    }
+
+    @Override
+    void enqueueMarkFieldKeptAction(ProgramField field, KeepReasonWitness witness) {
+      queue.add(new MarkFieldKeptAction(field, witness));
+    }
+
+    @Override
+    public void enqueueTraceCodeAction(ProgramMethod method) {
+      queue.add(new TraceCodeAction(method));
+    }
+
+    @Override
+    public void enqueueTraceConstClassAction(DexType type, ProgramMethod context) {
+      queue.add(new TraceConstClassAction(type, context));
+    }
+
+    @Override
+    public void enqueueTraceInvokeDirectAction(DexMethod invokedMethod, ProgramMethod context) {
+      queue.add(new TraceInvokeDirectAction(invokedMethod, context));
+    }
+
+    @Override
+    public void enqueueTraceNewInstanceAction(DexType type, ProgramMethod context) {
+      queue.add(new TraceNewInstanceAction(type, context));
+    }
+
+    @Override
+    public void enqueueTraceStaticFieldRead(DexField field, ProgramMethod context) {
+      queue.add(new TraceStaticFieldReadAction(field, context));
+    }
   }
 
-  void enqueueMarkMethodKeptAction(ProgramMethod method, KeepReason reason) {
-    queue.add(new MarkMethodKeptAction(method, reason));
-  }
+  public static class NonPushableEnqueuerWorklist extends EnqueuerWorklist {
 
-  void enqueueMarkFieldKeptAction(ProgramField field, KeepReasonWitness witness) {
-    queue.add(new MarkFieldKeptAction(field, witness));
-  }
+    private ProgramMethodSet enqueuedMarkMethodLive;
 
-  public void enqueueTraceCodeAction(ProgramMethod method) {
-    queue.add(new TraceCodeAction(method));
-  }
+    private NonPushableEnqueuerWorklist(
+        PushableEnqueuerWorkList workList, ProgramMethodSet enqueuedMarkMethodLive) {
+      super(workList.enqueuer, workList.queue);
+      this.enqueuedMarkMethodLive = enqueuedMarkMethodLive;
+    }
 
-  public void enqueueTraceConstClassAction(DexType type, ProgramMethod context) {
-    queue.add(new TraceConstClassAction(type, context));
-  }
+    @Override
+    EnqueuerWorklist nonPushable(ProgramMethodSet enqueuedMarkMethodLive) {
+      return this;
+    }
 
-  public void enqueueTraceInvokeDirectAction(DexMethod invokedMethod, ProgramMethod context) {
-    queue.add(new TraceInvokeDirectAction(invokedMethod, context));
-  }
+    private Unreachable attemptToEnqueue() {
+      throw new Unreachable("Attempt to enqueue an action in a non pushable enqueuer work list.");
+    }
 
-  public void enqueueTraceNewInstanceAction(DexType type, ProgramMethod context) {
-    queue.add(new TraceNewInstanceAction(type, context));
-  }
+    @Override
+    boolean enqueueAssertAction(Action assertion) {
+      throw attemptToEnqueue();
+    }
 
-  public void enqueueTraceStaticFieldRead(DexField field, ProgramMethod context) {
-    queue.add(new TraceStaticFieldReadAction(field, context));
+    @Override
+    void enqueueMarkReachableDirectAction(
+        DexMethod method, ProgramDefinition context, KeepReason reason) {
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    void enqueueMarkReachableSuperAction(DexMethod method, ProgramMethod from) {
+
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    public void enqueueMarkFieldAsReachableAction(
+        ProgramField field, ProgramDefinition context, KeepReason reason) {
+
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    public void enqueueMarkInstantiatedAction(
+        DexProgramClass clazz,
+        ProgramMethod context,
+        InstantiationReason instantiationReason,
+        KeepReason keepReason) {
+
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    void enqueueMarkAnnotationInstantiatedAction(DexProgramClass clazz, KeepReasonWitness reason) {
+
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    void enqueueMarkInterfaceInstantiatedAction(DexProgramClass clazz, KeepReasonWitness reason) {
+
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    boolean enqueueMarkMethodLiveAction(
+        ProgramMethod method, ProgramDefinition context, KeepReason reason) {
+      if (enqueuedMarkMethodLive.contains(method)) {
+        return false;
+      }
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    void enqueueMarkMethodKeptAction(ProgramMethod method, KeepReason reason) {
+
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    void enqueueMarkFieldKeptAction(ProgramField field, KeepReasonWitness witness) {
+
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    public void enqueueTraceCodeAction(ProgramMethod method) {
+
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    public void enqueueTraceConstClassAction(DexType type, ProgramMethod context) {
+
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    public void enqueueTraceInvokeDirectAction(DexMethod invokedMethod, ProgramMethod context) {
+
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    public void enqueueTraceNewInstanceAction(DexType type, ProgramMethod context) {
+
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    public void enqueueTraceStaticFieldRead(DexField field, ProgramMethod context) {
+
+      throw attemptToEnqueue();
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
index 2cc626d..5ffc72b 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.shaking;
 
-import static com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.DESCRIPTOR_VIVIFIED_PREFIX;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter.DESCRIPTOR_VIVIFIED_PREFIX;
 import static com.android.tools.r8.utils.collections.IdentityHashSetFromMap.newProgramDerivedContextSet;
 
 import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 230af5f..6f390ea 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1133,8 +1133,8 @@
       // Step 3: Clear the members of the source class since they have now been moved to the target.
       source.getMethodCollection().clearDirectMethods();
       source.getMethodCollection().clearVirtualMethods();
-      source.setInstanceFields(null);
-      source.setStaticFields(null);
+      source.clearInstanceFields();
+      source.clearStaticFields();
       // Step 4: Record merging.
       mergedClasses.put(source.type, target.type);
       assert !abortMerge;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index f7e235f..f9bdee5 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
@@ -568,15 +570,37 @@
       DexString name,
       DexProto proto,
       SyntheticKind kind,
-      DexProgramClass context,
+      ProgramDefinition context,
       AppView<?> appView,
       Consumer<SyntheticProgramClassBuilder> buildClassCallback,
       Consumer<SyntheticMethodBuilder> buildMethodCallback) {
+    return ensureFixedClassMethod(
+        name,
+        proto,
+        kind,
+        context,
+        appView,
+        buildClassCallback,
+        buildMethodCallback,
+        emptyConsumer());
+  }
+
+  public ProgramMethod ensureFixedClassMethod(
+      DexString name,
+      DexProto proto,
+      SyntheticKind kind,
+      ProgramDefinition context,
+      AppView<?> appView,
+      Consumer<SyntheticProgramClassBuilder> buildClassCallback,
+      Consumer<SyntheticMethodBuilder> buildMethodCallback,
+      Consumer<ProgramMethod> newMethodCallback) {
     DexProgramClass clazz =
-        ensureFixedClass(kind, context, appView, buildClassCallback, ignored -> {});
+        ensureFixedClass(
+            kind, context.getContextClass(), appView, buildClassCallback, emptyConsumer());
     DexMethod methodReference = appView.dexItemFactory().createMethod(clazz.getType(), proto, name);
     DexEncodedMethod methodDefinition =
-        internalEnsureMethod(methodReference, clazz, kind, appView, buildMethodCallback);
+        internalEnsureMethod(
+            methodReference, clazz, kind, appView, buildMethodCallback, newMethodCallback);
     return new ProgramMethod(clazz, methodDefinition);
   }
 
@@ -637,16 +661,18 @@
     DexMethod methodReference =
         appView.dexItemFactory().createMethod(clazz.getType(), methodProto, methodName);
     DexEncodedMethod methodDefinition =
-        internalEnsureMethod(methodReference, clazz, kind, appView, buildMethodCallback);
+        internalEnsureMethod(
+            methodReference, clazz, kind, appView, buildMethodCallback, emptyConsumer());
     return DexClassAndMethod.create(clazz, methodDefinition);
   }
 
-  private DexEncodedMethod internalEnsureMethod(
+  private <T extends DexClassAndMethod> DexEncodedMethod internalEnsureMethod(
       DexMethod methodReference,
       DexClass clazz,
       SyntheticKind kind,
       AppView<?> appView,
-      Consumer<SyntheticMethodBuilder> buildMethodCallback) {
+      Consumer<SyntheticMethodBuilder> buildMethodCallback,
+      Consumer<T> newMethodCallback) {
     MethodCollection methodCollection = clazz.getMethodCollection();
     DexEncodedMethod methodDefinition = methodCollection.getMethod(methodReference);
     if (methodDefinition != null) {
@@ -667,6 +693,7 @@
       //  and the creation of the method code. The code can then be constructed outside the lock.
       methodDefinition = builder.build();
       methodCollection.addMethod(methodDefinition);
+      newMethodCallback.accept((T) DexClassAndMethod.create(clazz, methodDefinition));
       return methodDefinition;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index c142281..edee559 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -9,6 +9,8 @@
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.DescriptorUtils;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 
 public class SyntheticNaming {
 
@@ -23,6 +25,7 @@
    */
   public enum SyntheticKind {
     // Class synthetics.
+    ENUM_UNBOXING_SHARED_UTILITY_CLASS("$EnumUnboxingSharedUtility", 24, false, true),
     RECORD_TAG("", 1, false, true, true),
     COMPANION_CLASS("$-CC", 2, false, true),
     EMULATED_INTERFACE_CLASS("$-EL", 3, false, true),
@@ -48,6 +51,10 @@
     SERVICE_LOADER("ServiceLoad", 18, true),
     OUTLINE("Outline", 19, true);
 
+    static {
+      assert verifyNoOverlappingIds();
+    }
+
     public final String descriptor;
     public final int id;
     public final boolean isSingleSyntheticMethod;
@@ -100,6 +107,16 @@
       }
       return null;
     }
+
+    private static boolean verifyNoOverlappingIds() {
+      Int2ReferenceMap<SyntheticKind> idToKind = new Int2ReferenceOpenHashMap<>();
+      for (SyntheticKind kind : values()) {
+        SyntheticKind kindWithSameId = idToKind.put(kind.id, kind);
+        assert kindWithSameId == null
+            : "Synthetic kind " + idToKind + " has same id as " + kindWithSameId;
+      }
+      return true;
+    }
   }
 
   private static final String SYNTHETIC_CLASS_SEPARATOR = "$$";
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 7509f2a..e82e04d 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -45,7 +45,7 @@
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
 import com.android.tools.r8.ir.desugar.nest.Nest;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
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 f4115a4..5152dde 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
@@ -47,6 +47,14 @@
     methods.forEach(this::add);
   }
 
+  public void remove(DexMethod method) {
+    methods.remove(method);
+  }
+
+  public void removeAll(Iterable<DexMethod> methods) {
+    methods.forEach(this::remove);
+  }
+
   public void rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens applied) {
     Set<DexMethod> newMethods = Sets.newIdentityHashSet();
     for (DexMethod method : methods) {
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 830047f..8791b27 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -104,9 +104,6 @@
       if (!enableTreeShaking) {
         command.add("-dontshrink");
       }
-      if (!enableOptimization) {
-        command.add("-dontoptimize");
-      }
       if (!enableMinification) {
         command.add("-dontobfuscate");
       }
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 73abcee..88738f0 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -30,7 +30,6 @@
     extends TestCompilerBuilder<C, B, CR, RR, T> {
 
   protected boolean enableTreeShaking = true;
-  protected boolean enableOptimization = true;
   protected boolean enableMinification = true;
 
   private final Set<Class<? extends Annotation>> addedTestingAnnotations =
@@ -74,15 +73,6 @@
     return treeShaking(false);
   }
 
-  public T optimization(boolean enable) {
-    enableOptimization = enable;
-    return self();
-  }
-
-  public T noOptimization() {
-    return optimization(false);
-  }
-
   public T minification(boolean enable) {
     enableMinification = enable;
     return self();
@@ -112,6 +102,10 @@
     return addKeepRules(Arrays.asList(rules));
   }
 
+  public T addDontOptimize() {
+    return addKeepRules("-dontoptimize");
+  }
+
   public T addDontWarn(Class<?>... classes) {
     for (Class<?> clazz : classes) {
       addDontWarn(clazz.getTypeName());
@@ -330,6 +324,10 @@
     return addKeepRules("-keepattributes " + String.join(",", attributes));
   }
 
+  public T addKeepAttributeAnnotationDefault() {
+    return addKeepAttributes(ProguardKeepAttributes.ANNOTATION_DEFAULT);
+  }
+
   public T addKeepAttributeExceptions() {
     return addKeepAttributes(ProguardKeepAttributes.EXCEPTIONS);
   }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelDesugaredLibraryReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelDesugaredLibraryReferenceTest.java
new file mode 100644
index 0000000..38f7b45
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelDesugaredLibraryReferenceTest.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.apimodel;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.lang.reflect.Method;
+import java.time.Clock;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelDesugaredLibraryReferenceTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+  }
+
+  public ApiModelDesugaredLibraryReferenceTest(
+      TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.parameters = parameters;
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+  }
+
+  @Test
+  public void testClockR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    Method printZone = CustomLibClass.class.getDeclaredMethod("printZone");
+    Method main = Executor.class.getDeclaredMethod("main", String[].class);
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Executor.class, CustomLibClass.class)
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .addKeepMainRule(Executor.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(
+            ApiModelingTestHelper.addTracedApiReferenceLevelCallBack(
+                (reference, apiLevel) -> {
+                  if (reference.equals(Reference.methodFromMethod(printZone))) {
+                    // TODO(b/191617445): This should probably always be parameters.getApiLevel()
+                    assertEquals(AndroidApiLevel.O.max(parameters.getApiLevel()), apiLevel);
+                  }
+                }))
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutputLines("Z")
+        // TODO(b/191617445): We should always be able to inline
+        .inspect(
+            ApiModelingTestHelper.verifyThat(parameters, printZone)
+                .inlinedIntoFromApiLevel(main, AndroidApiLevel.O));
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      CustomLibClass.printZone();
+    }
+  }
+
+  static class CustomLibClass {
+
+    public static void printZone() {
+      System.out.println(Clock.systemUTC().getZone());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/SyntheticLambdaWithMissingInterfaceMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/SyntheticLambdaWithMissingInterfaceMergingTest.java
new file mode 100644
index 0000000..6f9a788
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/SyntheticLambdaWithMissingInterfaceMergingTest.java
@@ -0,0 +1,69 @@
+// 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.classmerging.horizontal;
+
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SyntheticLambdaWithMissingInterfaceMergingTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableOptimization;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, optimize: {0}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, I.class)
+        .addDontWarn(J.class)
+        .addKeepClassAndMembersRules(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .applyIf(!enableOptimization, TestShrinkerBuilder::addDontOptimize)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("I");
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      I i = () -> System.out.println("I");
+      i.m1();
+    }
+
+    static void dead() {
+      J j = () -> System.out.println("J");
+      j.m2();
+    }
+  }
+
+  interface I {
+    void m1();
+  }
+
+  interface J {
+    void m2();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
index 9976c33..4b72ec5 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
@@ -42,7 +42,7 @@
                     .addKeepMainRule(CLASS_NAME)
                     // Add main dex rule to disable Class.forName() optimization.
                     .addMainDexRules("-keep class " + CLASS_NAME)
-                    .noOptimization()
+                    .addDontOptimize()
                     .noTreeShaking()
                     .setMinApi(AndroidApiLevel.B));
 
@@ -78,7 +78,7 @@
                     .addKeepMainRule(CLASS_NAME)
                     // Add main dex rule to disable Class.forName() optimization.
                     .addMainDexRules("-keep class " + CLASS_NAME)
-                    .noOptimization()
+                    .addDontOptimize()
                     .noMinification()
                     .noTreeShaking()
                     .setMinApi(AndroidApiLevel.B));
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaContextDuplicateInLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaContextDuplicateInLibraryTest.java
new file mode 100644
index 0000000..98839fa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaContextDuplicateInLibraryTest.java
@@ -0,0 +1,127 @@
+// 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.desugar;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugarLambdaContextDuplicateInLibraryTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("library string", "Hello", "world!");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DesugarLambdaContextDuplicateInLibraryTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static final Class<?> MAIN = TestClass.class;
+  private static final Class<?> PROGRAM_CONTEXT = MAIN;
+  private static final Class<?> LIBRARY_CONTEXT = A.class;
+  private static final List<Class<?>> PROGRAM = ImmutableList.of(MAIN);
+  private static final List<Class<?>> LIBRARY =
+      ImmutableList.of(LibraryInterface.class, LIBRARY_CONTEXT);
+
+  private static final MethodReference pinnedPrintLn() throws Exception {
+    return Reference.methodFromMethod(
+        TestClass.class.getDeclaredMethod("println", LibraryInterface.class));
+  }
+
+  @Test
+  public void testOnlyProgram() throws Exception {
+    testForR8(parameters.getBackend())
+        .noMinification()
+        .addProgramClasses(PROGRAM)
+        .addProgramClasses(LIBRARY)
+        .addKeepMainRule(MAIN)
+        .addKeepMethodRules(pinnedPrintLn())
+        .setMinApi(parameters.getApiLevel())
+        .addHorizontallyMergedClassesInspector(
+            inspector -> {
+              // The regression test relies on the library type being the target. A change to
+              // synthetic sorting could change this order. If so, update this test by finding a
+              // new name to recover the order.
+              assertTrue(
+                  inspector.getSources().stream()
+                      .allMatch(t -> t.toDescriptorString().contains(binaryName(PROGRAM_CONTEXT))));
+              assertTrue(
+                  inspector.getTargets().stream()
+                      .allMatch(t -> t.toDescriptorString().contains(binaryName(LIBRARY_CONTEXT))));
+            })
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testDuplicate() throws Exception {
+    testForR8(parameters.getBackend())
+        .noMinification() // Don't minify so the name collision will happen.
+        .addProgramClasses(PROGRAM)
+        .addProgramClasses(LIBRARY)
+        .addLibraryClasses(LIBRARY)
+        .addDefaultRuntimeLibrary(parameters)
+        .addKeepMainRule(MAIN)
+        .addKeepMethodRules(pinnedPrintLn())
+        .setMinApi(parameters.getApiLevel())
+        // Use a checksum filter to simulate the classes being found on bootclasspath by removing
+        // then from the program output.
+        .setIncludeClassesChecksum(true)
+        .apply(
+            b ->
+                b.getBuilder()
+                    .setDexClassChecksumFilter(
+                        (desc, checksum) -> !desc.contains(binaryName(LIBRARY_CONTEXT))))
+        .compile()
+        .addRunClasspathClasses(LIBRARY)
+        .run(parameters.getRuntime(), MAIN)
+        .applyIf(
+            parameters.isCfRuntime(),
+            r -> r.assertSuccessWithOutput(EXPECTED),
+            // TODO(b/191747442): A library class and its derivatives should be pinned.
+            r -> r.assertFailure());
+  }
+
+  interface LibraryInterface {
+    String str();
+  }
+
+  // Library class with a synthetic. Named A to help it be ordered as the primary for merging.
+  static class A {
+
+    public static LibraryInterface getStrFn() {
+      // Ensure a static lambda is created in the library.
+      return () -> "library string";
+    }
+  }
+
+  static class TestClass {
+
+    static void println(LibraryInterface fn) {
+      System.out.println(fn.str());
+    }
+
+    public static void main(String[] args) {
+      println(A.getStrFn());
+      println(() -> "Hello");
+      println(() -> "world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
index e0f9136..ddebca5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
@@ -10,8 +10,8 @@
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfigurationParser;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryConfigurationParsingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryConfigurationParsingTest.java
index 348351b..2ef985e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryConfigurationParsingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryConfigurationParsingTest.java
@@ -20,8 +20,8 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfigurationParser;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.AndroidApiLevel;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
index 888436d..6eb1418 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.errors.DesugaredLibraryMismatchDiagnostic;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.file.Path;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index 2414cf7..6453792 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -22,8 +22,8 @@
 import com.android.tools.r8.TestState;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfigurationParser;
 import com.android.tools.r8.tracereferences.TraceReferences;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
index 9cb9cea..5c42b95 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -16,8 +16,8 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfigurationParser;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java
index 84684da..43056e6 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import java.nio.file.Path;
 import java.util.HashMap;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
index f5f2275..f253398 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -15,8 +15,8 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfigurationParser;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
index 213324c..b2973f5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
@@ -15,8 +15,8 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfigurationParser;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java
index 40d658d..05fdd28 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java
@@ -8,7 +8,7 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/AllTimeConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/AllTimeConversionTest.java
index 1d79e26..143f18b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/AllTimeConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/AllTimeConversionTest.java
@@ -5,8 +5,9 @@
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
 import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.fail;
 
+import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
@@ -114,22 +115,20 @@
   }
 
   private void assertTrackedAPIS(TestDiagnosticMessages diagnosticMessages) {
-    assertTrue(
-        diagnosticMessages
-            .getWarnings()
-            .get(0)
-            .getDiagnosticMessage()
-            .startsWith("Tracked desugared API conversions:"));
-    assertEquals(
-        9, diagnosticMessages.getWarnings().get(0).getDiagnosticMessage().split("\n").length);
-    assertTrue(
-        diagnosticMessages
-            .getWarnings()
-            .get(1)
-            .getDiagnosticMessage()
-            .startsWith("Tracked callback desugared API conversions:"));
-    assertEquals(
-        1, diagnosticMessages.getWarnings().get(1).getDiagnosticMessage().split("\n").length);
+    int trackedAPI = 0;
+    int trackedCallbackAPI = 0;
+    for (Diagnostic warning : diagnosticMessages.getWarnings()) {
+      String message = warning.getDiagnosticMessage();
+      if (message.startsWith("Tracked desugared API conversions:")) {
+        trackedAPI += message.split("\n").length - 1;
+      } else if (message.startsWith("Tracked callback desugared API conversions:")) {
+        trackedCallbackAPI += message.split("\n").length - 1;
+      } else {
+        fail();
+      }
+    }
+    assertEquals(8, trackedAPI);
+    assertEquals(0, trackedCallbackAPI);
   }
 
 
diff --git a/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java b/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java
index e3078a2..cbc7ef2 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.desugar.RecordRewriter;
+import com.android.tools.r8.ir.desugar.records.RecordRewriter;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.google.common.collect.ImmutableList;
@@ -41,7 +41,7 @@
 @RunWith(Parameterized.class)
 public class GenerateRecordMethods extends MethodGenerationBase {
   private final DexType GENERATED_TYPE =
-      factory.createType("Lcom/android/tools/r8/ir/desugar/RecordCfMethods;");
+      factory.createType("Lcom/android/tools/r8/ir/desugar/records/RecordCfMethods;");
   private final DexType RECORD_STUB_TYPE =
       factory.createType(DescriptorUtils.javaTypeToDescriptor(RecordStub.class.getTypeName()));
   private final List<Class<?>> METHOD_TEMPLATE_CLASSES = ImmutableList.of(RecordMethods.class);
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
index e7b5dce..e77f42a 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
@@ -9,8 +9,8 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.enumunboxing.examplelib1.JavaLibrary1;
-import com.android.tools.r8.ir.optimize.enums.SharedEnumUnboxingUtilityClass;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
@@ -87,11 +87,9 @@
     assertTrue(
         codeInspector.allClasses().stream()
             .anyMatch(
-                c ->
-                    c.getOriginalName()
-                        .contains(
-                            SharedEnumUnboxingUtilityClass
-                                .ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX)));
+                clazz ->
+                    SyntheticItemsTestUtils.isEnumUnboxingSharedUtilityClass(
+                        clazz.getOriginalReference())));
   }
 
   static class App {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
index 4ef092d..e7651fb 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
@@ -10,8 +10,8 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.enumunboxing.examplelib1.JavaLibrary1;
 import com.android.tools.r8.enumunboxing.examplelib2.JavaLibrary2;
-import com.android.tools.r8.ir.optimize.enums.SharedEnumUnboxingUtilityClass;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
@@ -92,11 +92,9 @@
     assertTrue(
         codeInspector.allClasses().stream()
             .anyMatch(
-                c ->
-                    c.getOriginalName()
-                        .contains(
-                            SharedEnumUnboxingUtilityClass
-                                .ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX)));
+                clazz ->
+                    SyntheticItemsTestUtils.isEnumUnboxingSharedUtilityClass(
+                        clazz.getOriginalReference())));
   }
 
   static class App {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
index 2a8105f..19b4a26 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
@@ -52,7 +52,10 @@
             .addEnumUnboxingInspector(
                 inspector ->
                     inspector
-                        .assertUnboxed(Lib.LibEnum.class)
+                        // Without the studio keep rules, LibEnum.valueOf() is removed, which is
+                        // used in this compilation, causing LibEnum to be ineligible for unboxing.
+                        .assertUnboxedIf(
+                            !missingStaticMethods || enumKeepRules.isStudio(), Lib.LibEnum.class)
                         .assertUnboxedIf(!missingStaticMethods, Lib.LibEnumStaticMethod.class))
             .allowDiagnosticMessages()
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumWithAssertionsDisabledStaticFieldTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumWithAssertionsDisabledStaticFieldTest.java
new file mode 100644
index 0000000..2f2dbc3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumWithAssertionsDisabledStaticFieldTest.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnumWithAssertionsDisabledStaticFieldTest extends TestBase {
+
+  @Parameter(0)
+  public AssertionTransformation assertionTransformation;
+
+  @Parameter(1)
+  public boolean enableRuntimeAssertions;
+
+  @Parameter(2)
+  public TestParameters parameters;
+
+  @Parameters(name = "{2}, transformation: {0}, -ea: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        AssertionTransformation.values(),
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    assumeTrue(parameters.isCfRuntime() || !enableRuntimeAssertions);
+    testForR8(parameters.getBackend())
+        .addProgramClasses(
+            EnumWithAssertionsDisabledStaticFieldMainClass.class,
+            EnumWithAssertionsDisabledStaticFieldEnumClass.class)
+        .addKeepMainRule(EnumWithAssertionsDisabledStaticFieldMainClass.class)
+        .addEnumUnboxingInspector(
+            inspector ->
+                inspector.assertUnboxed(EnumWithAssertionsDisabledStaticFieldEnumClass.class))
+        .addAssertionsConfiguration(
+            builder -> builder.setTransformation(assertionTransformation).setScopeAll().build())
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .enableRuntimeAssertions(enableRuntimeAssertions)
+        .run(parameters.getRuntime(), EnumWithAssertionsDisabledStaticFieldMainClass.class)
+        .applyIf(
+            assertionTransformation == AssertionTransformation.ENABLE
+                || (assertionTransformation == AssertionTransformation.PASSTHROUGH
+                    && enableRuntimeAssertions),
+            result -> result.assertFailureWithErrorThatThrows(AssertionError.class),
+            TestRunResult::assertSuccessWithEmptyOutput);
+  }
+}
+
+// Intentionally added as a top-level class because the $assertionsDisabled field is always
+// synthesized on the outer-most class.
+class EnumWithAssertionsDisabledStaticFieldMainClass {
+
+  public static void main(String[] args) {
+    EnumWithAssertionsDisabledStaticFieldEnumClass.A.fail();
+  }
+}
+
+@NeverClassInline
+enum EnumWithAssertionsDisabledStaticFieldEnumClass {
+  A;
+
+  @NeverInline
+  public void fail() {
+    assert false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumWithStaticFieldUnboxingTest.java
similarity index 87%
rename from src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/EnumWithStaticFieldUnboxingTest.java
index e3df746..9e8e577 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumWithStaticFieldUnboxingTest.java
@@ -13,7 +13,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class FailingEnumUnboxingTest extends EnumUnboxingTestBase {
+public class EnumWithStaticFieldUnboxingTest extends EnumUnboxingTestBase {
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
@@ -24,7 +24,7 @@
     return enumUnboxingTestParameters();
   }
 
-  public FailingEnumUnboxingTest(
+  public EnumWithStaticFieldUnboxingTest(
       TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
@@ -34,13 +34,13 @@
   @Test
   public void testEnumUnboxingFailure() throws Exception {
     testForR8(parameters.getBackend())
-        .addInnerClasses(FailingEnumUnboxingTest.class)
+        .addInnerClasses(EnumWithStaticFieldUnboxingTest.class)
         .addKeepMainRule(EnumStaticFieldMain.class)
         .enableNeverClassInliningAnnotations()
         .addKeepRules(enumKeepRules.getKeepRules())
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .addEnumUnboxingInspector(
-            inspector -> inspector.assertNotUnboxed(EnumStaticFieldMain.EnumStaticField.class))
+            inspector -> inspector.assertUnboxed(EnumStaticFieldMain.EnumStaticField.class))
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), EnumStaticFieldMain.class)
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index ec665d3..252b11d 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -212,6 +213,11 @@
   static class PrimaryMethodProcessorMock extends MethodProcessorWithWave {
 
     @Override
+    public MethodProcessingContext createMethodProcessingContext(ProgramMethod method) {
+      throw new Unreachable();
+    }
+
+    @Override
     public boolean shouldApplyCodeRewritings(ProgramMethod method) {
       return false;
     }
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
index 548ce4d..f8dcce6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
@@ -55,12 +55,12 @@
 
   @Test
   public void testAsIs() throws Exception {
-    test(builder -> builder.noMinification().noOptimization().noTreeShaking());
+    test(builder -> builder.noMinification().addDontOptimize().noTreeShaking());
   }
 
   @Test
   public void testDontShrinkAndDontOptimize() throws Exception {
-    test(builder -> builder.noOptimization().noTreeShaking());
+    test(builder -> builder.addDontOptimize().noTreeShaking());
   }
 
   @Test
@@ -100,7 +100,7 @@
 
   @Test
   public void testDontOptimize() throws Exception {
-    test(TestShrinkerBuilder::noOptimization);
+    test(TestShrinkerBuilder::addDontOptimize);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
index 598087a..c990029 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
@@ -98,7 +98,7 @@
             .addDontWarn(PKG + ".**")
             .allowDiagnosticWarningMessages()
             // -dontoptimize so that basic code structure is kept.
-            .noOptimization()
+            .addDontOptimize()
             .compile()
             .inspect(this::inspect)
             .assertAllWarningMessagesMatch(
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java b/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
index 3883528..06a87e4 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
 import com.android.tools.r8.memberrebinding.b135627418.library.Drawable;
 import com.android.tools.r8.memberrebinding.b135627418.library.DrawableWrapper;
 import com.android.tools.r8.memberrebinding.b135627418.library.InsetDrawable;
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 220bea7..b2380c3 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -36,6 +36,7 @@
 import com.android.tools.r8.retrace.stacktraces.MemberFieldOverlapStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MultipleDotsInFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NamedModuleStackTrace;
+import com.android.tools.r8.retrace.stacktraces.NoObfuscatedLineNumberWithOverrideTest;
 import com.android.tools.r8.retrace.stacktraces.NoObfuscationRangeMappingWithStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NullStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ObfucatedExceptionClassStackTrace;
@@ -258,6 +259,11 @@
     runExperimentalRetraceTest(new SyntheticLambdaMethodWithInliningStackTrace());
   }
 
+  @Test
+  public void testNoObfuscatedLineNumberWithOverrideTest() throws Exception {
+    runRetraceTest(new NoObfuscatedLineNumberWithOverrideTest());
+  }
+
   private void inspectRetraceTest(
       StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) {
     inspection.accept(
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscatedLineNumberWithOverrideTest.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscatedLineNumberWithOverrideTest.java
new file mode 100644
index 0000000..3f11112
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscatedLineNumberWithOverrideTest.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class NoObfuscatedLineNumberWithOverrideTest implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Unknown Source)",
+        "\tat com.android.tools.r8.naming.retrace.Main.overload(Unknown Source)",
+        "\tat com.android.tools.r8.naming.retrace.Main.definedOverload(Unknown Source)",
+        "\tat com.android.tools.r8.naming.retrace.Main.mainPC(:3)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.naming.retrace.Main -> com.android.tools.r8.naming.retrace.Main:",
+        "    void main(java.lang.String):3 -> main",
+        "    void definedOverload():7 -> definedOverload",
+        "    void definedOverload(java.lang.String):11 -> definedOverload",
+        "    void overload1():7 -> overload",
+        "    void overload2(java.lang.String):11 -> overload",
+        "    void mainPC(java.lang.String[]):42 -> mainPC");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        // TODO(b/191513686): Could be retrace to ...Main.main(Main.java:3)
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java)",
+        "\tat com.android.tools.r8.naming.retrace.Main.overload1(Main.java)",
+        "\t<OR> at com.android.tools.r8.naming.retrace.Main.overload2(Main.java)",
+        "\tat com.android.tools.r8.naming.retrace.Main.definedOverload(Main.java)",
+        "\tat com.android.tools.r8.naming.retrace.Main.mainPC(Main.java:42)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainNonDefaultRetentionAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainNonDefaultRetentionAnnotationTest.java
new file mode 100644
index 0000000..cfeb5c1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainNonDefaultRetentionAnnotationTest.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.annotations;
+
+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.onlyIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AlwaysRetainNonDefaultRetentionAnnotationTest extends TestBase {
+
+  private final boolean enableProguardCompatibilityMode;
+  private final boolean keepAllowShrinking;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{2}, compat: {0}, keep: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public AlwaysRetainNonDefaultRetentionAnnotationTest(
+      boolean enableProguardCompatibilityMode,
+      boolean keepAllowShrinking,
+      TestParameters parameters) {
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
+    this.keepAllowShrinking = keepAllowShrinking;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    assumeTrue(!enableProguardCompatibilityMode || !keepAllowShrinking);
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepRuntimeInvisibleAnnotations()
+        .addKeepRuntimeVisibleAnnotations()
+        .applyIf(
+            keepAllowShrinking,
+            builder -> {
+              assertFalse(enableProguardCompatibilityMode);
+              builder.addKeepRules(
+                  "-keep,allowshrinking,allowobfuscation class "
+                      + MyClassAnnotation.class.getTypeName());
+              builder.addKeepRules(
+                  "-keep,allowshrinking,allowobfuscation class "
+                      + MyRuntimeAnnotation.class.getTypeName());
+            })
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject classAnnotationClassSubject = inspector.clazz(MyClassAnnotation.class);
+              assertThat(classAnnotationClassSubject, isPresent());
+              assertThat(
+                  classAnnotationClassSubject.annotation(Retention.class.getTypeName()),
+                  onlyIf(isFullModeWithoutKeepRule(), isAbsent()));
+              assertThat(
+                  classAnnotationClassSubject.annotation(Target.class.getTypeName()),
+                  onlyIf(isFullModeWithoutKeepRule(), isAbsent()));
+              assertThat(
+                  classAnnotationClassSubject.annotation(MyClassAnnotation.class.getTypeName()),
+                  onlyIf(isFullModeWithoutKeepRule(), isAbsent()));
+
+              ClassSubject runtimeAnnotationClassSubject =
+                  inspector.clazz(MyRuntimeAnnotation.class);
+              assertThat(runtimeAnnotationClassSubject, isPresent());
+              assertThat(
+                  runtimeAnnotationClassSubject.annotation(Retention.class.getTypeName()),
+                  isPresent());
+              assertThat(
+                  runtimeAnnotationClassSubject.annotation(Target.class.getTypeName()),
+                  onlyIf(isFullModeWithoutKeepRule(), isAbsent()));
+              assertThat(
+                  runtimeAnnotationClassSubject.annotation(MyRuntimeAnnotation.class.getTypeName()),
+                  onlyIf(isFullModeWithoutKeepRule(), isAbsent()));
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines(
+            isFullModeWithoutKeepRule() ? ImmutableList.of("0", "1") : ImmutableList.of("2", "3"));
+  }
+
+  private boolean isFullModeWithoutKeepRule() {
+    return !enableProguardCompatibilityMode && !keepAllowShrinking;
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(MyClassAnnotation.class.getAnnotations().length);
+      System.out.println(MyRuntimeAnnotation.class.getAnnotations().length);
+    }
+  }
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.TYPE})
+  @MyClassAnnotation
+  @interface MyClassAnnotation {}
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.TYPE})
+  @MyRuntimeAnnotation
+  @interface MyRuntimeAnnotation {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainRetentionAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainRetentionAnnotationTest.java
deleted file mode 100644
index 6e25e4ae..0000000
--- a/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainRetentionAnnotationTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.shaking.annotations;
-
-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.onlyIf;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.shaking.enums.EnumInAnnotationTest.MyAnnotation;
-import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.util.List;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class AlwaysRetainRetentionAnnotationTest extends TestBase {
-
-  private final boolean enableProguardCompatibilityMode;
-  private final boolean keepAllowShrinking;
-  private final TestParameters parameters;
-
-  @Parameters(name = "{2}, compat: {0}, keep: {1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        BooleanUtils.values(),
-        BooleanUtils.values(),
-        getTestParameters().withAllRuntimesAndApiLevels().build());
-  }
-
-  public AlwaysRetainRetentionAnnotationTest(
-      boolean enableProguardCompatibilityMode,
-      boolean keepAllowShrinking,
-      TestParameters parameters) {
-    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
-    this.keepAllowShrinking = keepAllowShrinking;
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void test() throws Exception {
-    assumeTrue(!enableProguardCompatibilityMode || !keepAllowShrinking);
-    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
-        .addInnerClasses(getClass())
-        .addKeepMainRule(TestClass.class)
-        .addKeepRuntimeVisibleAnnotations()
-        .applyIf(
-            keepAllowShrinking,
-            builder -> {
-              assertFalse(enableProguardCompatibilityMode);
-              builder.addKeepRules(
-                  "-keep,allowshrinking,allowobfuscation class "
-                      + MyAnnotation.class.getTypeName());
-            })
-        .setMinApi(parameters.getApiLevel())
-        .compile()
-        .inspect(
-            inspector -> {
-              ClassSubject annotationClassSubject = inspector.clazz(MyAnnotation.class);
-              assertThat(annotationClassSubject, isPresent());
-
-              AnnotationSubject retentionAnnotationSubject =
-                  annotationClassSubject.annotation(Retention.class.getTypeName());
-              assertThat(retentionAnnotationSubject, isPresent());
-
-              AnnotationSubject targetAnnotationSubject =
-                  annotationClassSubject.annotation(Target.class.getTypeName());
-              assertThat(targetAnnotationSubject, onlyIf(shouldOnlyRetainRetention(), isAbsent()));
-
-              AnnotationSubject myAnnotationAnnotationSubject =
-                  annotationClassSubject.annotation(MyAnnotation.class.getTypeName());
-              assertThat(
-                  myAnnotationAnnotationSubject, onlyIf(shouldOnlyRetainRetention(), isAbsent()));
-            })
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines(shouldOnlyRetainRetention() ? "1" : "3");
-  }
-
-  private boolean shouldOnlyRetainRetention() {
-    return !enableProguardCompatibilityMode && !keepAllowShrinking;
-  }
-
-  static class TestClass {
-
-    public static void main(String[] args) {
-      System.out.println(MyAnnotation.class.getAnnotations().length);
-    }
-  }
-
-  @Retention(RetentionPolicy.RUNTIME)
-  @Target({ElementType.TYPE})
-  @MyAnnotation
-  @interface MyAnnotation {}
-}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/annotationdefault/DefaultValueForAnnotationWithExplicitValueShrinkingTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/annotationdefault/DefaultValueForAnnotationWithExplicitValueShrinkingTest.java
new file mode 100644
index 0000000..fecefb9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/annotationdefault/DefaultValueForAnnotationWithExplicitValueShrinkingTest.java
@@ -0,0 +1,83 @@
+// 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.shaking.annotations.annotationdefault;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DefaultValueForAnnotationWithExplicitValueShrinkingTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableProguardCompatibilityMode;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, compat: {0}")
+  public static List<Object[]> params() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepAttributeAnnotationDefault()
+        .addKeepRuntimeVisibleAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .apply(
+            compileResult -> {
+              CodeInspector inspector = compileResult.inspector();
+
+              // MyAnnotation has a @Retention annotation and an @AnnotationDefault annotation.
+              // TODO(b/191741002): The default value for MyAnnotation.value() is unused, thus there
+              // is no need to retain the @AnnotationDefault annotation.
+              ClassSubject annotationClassSubject = inspector.clazz(MyAnnotation.class);
+              assertThat(annotationClassSubject, isPresent());
+              assertThat(
+                  annotationClassSubject.annotation(Retention.class.getTypeName()), isPresent());
+              assertThat(
+                  annotationClassSubject.annotation("dalvik.annotation.AnnotationDefault"),
+                  isPresent());
+              assertEquals(2, annotationClassSubject.getDexProgramClass().annotations().size());
+
+              compileResult
+                  .run(parameters.getRuntime(), Main.class)
+                  .assertSuccessWithOutputLines(Main.class.getTypeName());
+            });
+  }
+
+  @MyAnnotation(value = Main.class)
+  static class Main {
+    public static void main(String[] args) {
+      MyAnnotation myAnnotation = Main.class.getAnnotation(MyAnnotation.class);
+      System.out.println(myAnnotation.value().getName());
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface MyAnnotation {
+    Class<?> value() default Object.class;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/annotationdefault/DefaultValueForLiveButNotKeptAnnotationShrinkingTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/annotationdefault/DefaultValueForLiveButNotKeptAnnotationShrinkingTest.java
new file mode 100644
index 0000000..5f3bf9b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/annotationdefault/DefaultValueForLiveButNotKeptAnnotationShrinkingTest.java
@@ -0,0 +1,80 @@
+// 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.shaking.annotations.annotationdefault;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DefaultValueForLiveButNotKeptAnnotationShrinkingTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableProguardCompatibilityMode;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, compat: {0}")
+  public static List<Object[]> params() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(MyAnnotation.class)
+        .addKeepAttributeAnnotationDefault()
+        .addKeepRuntimeVisibleAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .apply(
+            compileResult -> {
+              CodeInspector inspector = compileResult.inspector();
+
+              // MyAnnotation has a @Retention annotation and an @AnnotationDefault annotation.
+              ClassSubject annotationClassSubject = inspector.clazz(MyAnnotation.class);
+              assertThat(annotationClassSubject, isPresent());
+              assertThat(
+                  annotationClassSubject.annotation(Retention.class.getTypeName()), isPresent());
+              assertThat(
+                  annotationClassSubject.annotation("dalvik.annotation.AnnotationDefault"),
+                  isPresent());
+              assertEquals(2, annotationClassSubject.getDexProgramClass().annotations().size());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(Object.class.getTypeName());
+  }
+
+  @MyAnnotation
+  static class Main {
+    public static void main(String[] args) {
+      MyAnnotation myAnnotation = Main.class.getAnnotation(MyAnnotation.class);
+      System.out.println(myAnnotation.value().getName());
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface MyAnnotation {
+    Class<?> value() default Object.class;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/annotationdefault/DefaultValueForUnusedAnnotationShrinkingTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/annotationdefault/DefaultValueForUnusedAnnotationShrinkingTest.java
new file mode 100644
index 0000000..9adafb6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/annotationdefault/DefaultValueForUnusedAnnotationShrinkingTest.java
@@ -0,0 +1,83 @@
+// 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.shaking.annotations.annotationdefault;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DefaultValueForUnusedAnnotationShrinkingTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableProguardCompatibilityMode;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, compat: {0}")
+  public static List<Object[]> params() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepAttributeAnnotationDefault()
+        .addKeepRuntimeVisibleAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .apply(
+            compileResult -> {
+              CodeInspector inspector = compileResult.inspector();
+
+              // MyAnnotation has a @Retention annotation and an @AnnotationDefault annotation.
+              // TODO(b/191741002): MyAnnotation.value() is unused, thus there is no need to retain
+              // the @AnnotationDefault annotation.
+              ClassSubject annotationClassSubject = inspector.clazz(MyAnnotation.class);
+              assertThat(annotationClassSubject, isPresent());
+              assertThat(
+                  annotationClassSubject.annotation(Retention.class.getTypeName()), isPresent());
+              assertThat(
+                  annotationClassSubject.annotation("dalvik.annotation.AnnotationDefault"),
+                  isPresent());
+              assertEquals(2, annotationClassSubject.getDexProgramClass().annotations().size());
+
+              compileResult
+                  .run(parameters.getRuntime(), Main.class)
+                  .assertSuccessWithOutputLines(annotationClassSubject.getFinalName());
+            });
+  }
+
+  @MyAnnotation
+  static class Main {
+    public static void main(String[] args) {
+      MyAnnotation myAnnotation = Main.class.getAnnotation(MyAnnotation.class);
+      System.out.println(myAnnotation.annotationType().getName());
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface MyAnnotation {
+    Class<?> value() default Object.class;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index dfcf7e8..1b4ec31 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -65,6 +65,11 @@
         originalMethod.getMethodDescriptor());
   }
 
+  public static boolean isEnumUnboxingSharedUtilityClass(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(
+        reference, null, SyntheticKind.ENUM_UNBOXING_SHARED_UTILITY_CLASS);
+  }
+
   public static boolean isExternalSynthetic(ClassReference reference) {
     for (SyntheticKind kind : SyntheticKind.values()) {
       if (kind == SyntheticKind.RECORD_TAG) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java
index d554d50..c075406 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java
@@ -43,10 +43,14 @@
   }
 
   public EnumUnboxingInspector assertUnboxedIf(boolean condition, Class<? extends Enum<?>> clazz) {
+    return assertUnboxedIf(condition, clazz.getTypeName());
+  }
+
+  public EnumUnboxingInspector assertUnboxedIf(boolean condition, String className) {
     if (condition) {
-      assertUnboxed(clazz);
+      assertUnboxed(className);
     } else {
-      assertNotUnboxed(clazz);
+      assertNotUnboxed(className);
     }
     return this;
   }
@@ -65,6 +69,13 @@
     return this;
   }
 
+  public EnumUnboxingInspector assertNotUnboxed(String typeName) {
+    assertFalse(
+        unboxedEnums.isUnboxedEnum(
+            dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(typeName))));
+    return this;
+  }
+
   @SafeVarargs
   public final EnumUnboxingInspector assertNotUnboxed(Class<? extends Enum<?>>... classes) {
     for (Class<? extends Enum<?>> clazz : classes) {
diff --git a/third_party/opensource-apps/tivi.tar.gz.sha1 b/third_party/opensource-apps/tivi.tar.gz.sha1
index 50f9741..dc3e882 100644
--- a/third_party/opensource-apps/tivi.tar.gz.sha1
+++ b/third_party/opensource-apps/tivi.tar.gz.sha1
@@ -1 +1 @@
-cde92f3abe4e6a10a6c7ec865d6240c7b625a3a2
\ No newline at end of file
+b5b44fb38064e69308e980fd33651ce03a0b1977
\ No newline at end of file
diff --git a/tools/chrome_data.py b/tools/chrome_data.py
index 7649257..0d4329d 100644
--- a/tools/chrome_data.py
+++ b/tools/chrome_data.py
@@ -278,3 +278,18 @@
     },
   },
 }
+
+def GetLatestVersion():
+  return '200520-monochrome_public_minimal_apks'
+
+def GetName():
+  return 'chrome'
+
+def GetMemoryData(version):
+  assert version == '200520-monochrome_public_minimal_apks'
+  return {
+      'find-xmx-min': 600,
+      'find-xmx-max': 700,
+      'find-xmx-range': 16,
+      'oom-threshold': 625,
+  }
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 192145b..46b09b8 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -62,7 +62,7 @@
     default=False,
     action='store_true')
   parser.add_argument(
-    '--printtimes',
+    '--print-times',
     help='Print timing information from r8',
     default=False,
     action='store_true')
@@ -300,7 +300,7 @@
       cmd.append('-Xmx' + args.xmx)
     if args.ea:
       cmd.append('-ea')
-    if args.printtimes:
+    if args.print_times:
       cmd.append('-Dcom.android.tools.r8.printtimes=1')
     if hasattr(args, 'properties'):
       cmd.extend(args.properties);
@@ -345,7 +345,7 @@
     cmd.extend(otherargs)
     utils.PrintCmd(cmd)
     try:
-      print(subprocess.check_output(cmd, stderr=subprocess.STDOUT))
+      print(subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode('utf-8'))
       return 0
     except subprocess.CalledProcessError as e:
       if args.nolib \
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 2571efc..cc2ee93 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -38,6 +38,9 @@
 import utils
 import run_on_app
 
+import chrome_data
+import iosched_data
+import r8_data
 import youtube_data
 
 # How often the bot/tester should check state
@@ -57,54 +60,18 @@
 EXITCODE = 'exitcode'
 TIMED_OUT = 'timed_out'
 
-BENCHMARK_APPS = [
-    {
-        'app': 'r8',
-        'version': 'cf',
-        'find-xmx-min': 128,
-        'find-xmx-max': 400,
-        'find-xmx-range': 16,
-        'oom-threshold': 247,
-    },
-    {
-        'app': 'chrome',
-        'version': '180917',
-        'find-xmx-min': 256,
-        'find-xmx-max': 450,
-        'find-xmx-range': 16,
-        'oom-threshold': 340,
-    },
-    {
-        'app': 'youtube',
-        'version': youtube_data.LATEST_VERSION,
-        'find-xmx-min': 2800,
-        'find-xmx-max': 3200,
-        'find-xmx-range': 64,
-        'oom-threshold': 3000,
-        # TODO(b/143431825): Youtube can OOM randomly in memory configurations
-        #  that should work.
-        'skip-find-xmx-max': True,
-    },
-    {
-        'app': 'iosched',
-        'version': '2019',
-        'find-xmx-min': 128,
-        'find-xmx-max': 1024,
-        'find-xmx-range': 16,
-        # TODO(b/183371778): Figure out why the need to bump this
-        'oom-threshold': 329,
-    },
-]
+BENCHMARK_APPS = [chrome_data, iosched_data, r8_data, youtube_data]
 
-def find_min_xmx_command(record):
+def find_min_xmx_command(app_data):
+  record = app_data.GetMemoryData(app_data.GetLatestVersion())
   assert record['find-xmx-min'] < record['find-xmx-max']
   assert record['find-xmx-range'] < record['find-xmx-max'] - record['find-xmx-min']
   return [
       'tools/run_on_app.py',
       '--compiler=r8',
       '--compiler-build=lib',
-      '--app=%s' % record['app'],
-      '--version=%s' % record['version'],
+      '--app=%s' % app_data.GetName(),
+      '--version=%s' % app_data.GetLatestVersion(),
       '--no-debug',
       '--no-build',
       '--find-min-xmx',
@@ -113,27 +80,29 @@
       '--find-min-xmx-range-size=%s' % record['find-xmx-range'],
       '--find-min-xmx-archive']
 
-def compile_with_memory_max_command(record):
+def compile_with_memory_max_command(app_data):
   # TODO(b/152939233): Remove this special handling when fixed.
-  factor = 1.25 if record['app'] == 'chrome' else 1.15
+  factor = 1.25 if app_data.GetName() == 'chrome' else 1.15
+  record = app_data.GetMemoryData(app_data.GetLatestVersion())
   return [] if 'skip-find-xmx-max' in record else [
       'tools/run_on_app.py',
       '--compiler=r8',
       '--compiler-build=lib',
-      '--app=%s' % record['app'],
-      '--version=%s' % record['version'],
+      '--app=%s' % app_data.GetName(),
+      '--version=%s' % app_data.GetLatestVersion(),
       '--no-debug',
       '--no-build',
       '--max-memory=%s' % int(record['oom-threshold'] * factor)
   ]
 
-def compile_with_memory_min_command(record):
+def compile_with_memory_min_command(app_data):
+  record = app_data.GetMemoryData(app_data.GetLatestVersion())
   return [
       'tools/run_on_app.py',
       '--compiler=r8',
       '--compiler-build=lib',
-      '--app=%s' % record['app'],
-      '--version=%s' % record['version'],
+      '--app=%s' % app_data.GetName(),
+      '--version=%s' % app_data.GetLatestVersion(),
       '--no-debug',
       '--no-build',
       '--expect-oom',
@@ -176,16 +145,23 @@
        default=False, action='store_true')
   return result.parse_args()
 
-def get_own_file_content():
+def get_file_contents():
+  contents = []
   with open(sys.argv[0], 'r') as us:
-    return us.read()
+    contents.append(us.read())
+  for app_data in BENCHMARK_APPS:
+    with open(app_data.__file__, 'r') as us:
+      contents.append(us.read())
+  return contents
 
-def restart_if_new_version(original_content):
-  new_content = get_own_file_content()
-  log('Lengths %s %s' % (len(original_content), len(new_content)))
+def restart_if_new_version(original_contents):
+  new_contents = get_file_contents()
+  log('Lengths %s %s' % (
+      [len(data) for data in original_contents],
+      [len(data) for data in new_contents]))
   log('is main %s ' % utils.is_main())
   # Restart if the script got updated.
-  if new_content != original_content:
+  if new_contents != original_contents:
     log('Restarting tools/internal_test.py, content changed')
     os.execv(sys.argv[0], sys.argv)
 
@@ -313,7 +289,7 @@
 
 def run_continuously():
   # If this script changes, we will restart ourselves
-  own_content = get_own_file_content()
+  own_content = get_file_contents()
   while True:
     restart_if_new_version(own_content)
     print_magic_file_state()
diff --git a/tools/iosched_data.py b/tools/iosched_data.py
index 5aac7b0..edd0663 100644
--- a/tools/iosched_data.py
+++ b/tools/iosched_data.py
@@ -176,3 +176,19 @@
     },
   },
 }
+
+def GetLatestVersion():
+  return '2019'
+
+def GetName():
+  return 'iosched'
+
+def GetMemoryData(version):
+  assert version == '2019'
+  return {
+      'find-xmx-min': 128,
+      'find-xmx-max': 1024,
+      'find-xmx-range': 16,
+      # TODO(b/183371778): Figure out why the need to bump this.
+      'oom-threshold': 329,
+  }
diff --git a/tools/r8_data.py b/tools/r8_data.py
index c4c509f..6818af3 100644
--- a/tools/r8_data.py
+++ b/tools/r8_data.py
@@ -8,7 +8,7 @@
 ANDROID_L_API = '21'
 
 VERSIONS = {
-    'cf': {
+    '1.2.11-dev': {
       'deploy': {
           'inputs': [utils.PINNED_R8_JAR],
           'pgconf': [os.path.join(utils.REPO_ROOT, 'src', 'main', 'keep.txt')],
@@ -22,3 +22,18 @@
       }
     }
 }
+
+def GetLatestVersion():
+  return '1.2.11-dev'
+
+def GetName():
+  return 'r8'
+
+def GetMemoryData(version):
+  assert version == '1.2.11-dev'
+  return {
+      'find-xmx-min': 128,
+      'find-xmx-max': 400,
+      'find-xmx-range': 16,
+      'oom-threshold': 247,
+  }
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index 6acce42..30205f9 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -10,7 +10,7 @@
 import gradle
 import hashlib
 import jdk
-import optparse
+import argparse
 import os
 import shutil
 import sys
@@ -664,6 +664,7 @@
     'config_file_consumer': remove_print_lines,
     'properties': app.compiler_properties,
     'disable_desugared_lib': False,
+    'print_times': options.print_times,
   })
 
   app_jar = os.path.join(
@@ -843,103 +844,108 @@
 
 
 def parse_options(argv):
-  result = optparse.OptionParser()
-  result.add_option('--app',
-                    help='What app to run on',
-                    choices=[app.name for app in APPS],
-                    action='append')
-  result.add_option('--app-collection', '--app_collection',
-                    help='What app collection to run',
-                    choices=[collection.name for collection in APP_COLLECTIONS],
-                    action='append')
-  result.add_option('--app-logging-filter', '--app_logging_filter',
-                    help='The apps for which to turn on logging',
-                    action='append')
-  result.add_option('--bot',
-                    help='Running on bot, use third_party dependency.',
-                    default=False,
-                    action='store_true')
-  result.add_option('--generate-golem-config', '--generate_golem_config',
-                    help='Generate a new config for golem.',
-                    default=False,
-                    action='store_true')
-  result.add_option('--debug-agent',
-                    help='Enable Java debug agent and suspend compilation '
-                         '(default disabled)',
-                    default=False,
-                    action='store_true')
-  result.add_option('--disable-assertions', '--disable_assertions',
-                    help='Disable assertions when compiling',
-                    default=False,
-                    action='store_true')
-  result.add_option('--emulator-id', '--emulator_id',
-                    help='Id of the emulator to use',
-                    default='emulator-5554')
-  result.add_option('--golem',
-                    help='Running on golem, do not download',
-                    default=False,
-                    action='store_true')
-  result.add_option('--hash',
-                    help='The commit of R8 to use')
-  result.add_option('--internal',
-                    help='Run internal apps if set, otherwise run opensource',
-                    default=False,
-                    action='store_true')
-  result.add_option('--keystore',
-                    help='Path to app.keystore',
-                    default=os.path.join(utils.TOOLS_DIR, 'debug.keystore'))
-  result.add_option('--keystore-password', '--keystore_password',
-                    help='Password for app.keystore',
-                    default='android')
-  result.add_option('--monkey',
-                    help='Whether to install and run app(s) with monkey',
-                    default=False,
-                    action='store_true')
-  result.add_option('--monkey-events', '--monkey_events',
-                    help='Number of events that the monkey should trigger',
-                    default=250,
-                    type=int)
-  result.add_option('--no-build', '--no_build',
-                    help='Run without building ToT first (only when using ToT)',
-                    default=False,
-                    action='store_true')
-  result.add_option('--no-logging', '--no_logging',
-                    help='Disable logging except for errors',
-                    default=False,
-                    action='store_true')
-  result.add_option('--print-dexsegments',
-                    metavar='BENCHMARKNAME',
-                    help='Print the sizes of individual dex segments as ' +
-                         '\'<BENCHMARKNAME>-<APP>-<segment>(CodeSize): '
-                         '<bytes>\'')
-  result.add_option('--print-runtimeraw',
-                    metavar='BENCHMARKNAME',
-                    help='Print the line \'<BENCHMARKNAME>(RunTimeRaw):' +
-                        ' <elapsed> ms\' at the end where <elapsed> is' +
-                        ' the elapsed time in milliseconds.')
-  result.add_option('--quiet',
-                    help='Disable verbose logging',
-                    default=False,
-                    action='store_true')
-  result.add_option('--r8-compilation-steps', '--r8_compilation_steps',
-                    help='Number of times R8 should be run on each app',
-                    default=2,
-                    type=int)
-  result.add_option('--run-tests', '--run_tests',
-                    help='Whether to run instrumentation tests',
-                    default=False,
-                    action='store_true')
-  result.add_option('--sign-apks', '--sign_apks',
-                    help='Whether the APKs should be signed',
-                    default=False,
-                    action='store_true')
-  result.add_option('--shrinker',
-                    help='The shrinkers to use (by default, all are run)',
-                    action='append')
-  result.add_option('--version',
-                    default='main',
-                    help='The version of R8 to use (e.g., 1.4.51)')
-  (options, args) = result.parse_args(argv)
+  result = argparse.ArgumentParser(description = 'Run/compile dump artifacts.')
+  result.add_argument('--app',
+                      help='What app to run on',
+                      choices=[app.name for app in APPS],
+                      action='append')
+  result.add_argument('--app-collection', '--app_collection',
+                      help='What app collection to run',
+                      choices=[collection.name for collection in
+                               APP_COLLECTIONS],
+                      action='append')
+  result.add_argument('--app-logging-filter', '--app_logging_filter',
+                      help='The apps for which to turn on logging',
+                      action='append')
+  result.add_argument('--bot',
+                      help='Running on bot, use third_party dependency.',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--generate-golem-config', '--generate_golem_config',
+                      help='Generate a new config for golem.',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--debug-agent',
+                      help='Enable Java debug agent and suspend compilation '
+                           '(default disabled)',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--disable-assertions', '--disable_assertions',
+                      help='Disable assertions when compiling',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--emulator-id', '--emulator_id',
+                      help='Id of the emulator to use',
+                      default='emulator-5554')
+  result.add_argument('--golem',
+                      help='Running on golem, do not download',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--hash',
+                      help='The commit of R8 to use')
+  result.add_argument('--internal',
+                      help='Run internal apps if set, otherwise run opensource',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--keystore',
+                      help='Path to app.keystore',
+                      default=os.path.join(utils.TOOLS_DIR, 'debug.keystore'))
+  result.add_argument('--keystore-password', '--keystore_password',
+                      help='Password for app.keystore',
+                      default='android')
+  result.add_argument('--monkey',
+                      help='Whether to install and run app(s) with monkey',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--monkey-events', '--monkey_events',
+                      help='Number of events that the monkey should trigger',
+                      default=250,
+                      type=int)
+  result.add_argument('--no-build', '--no_build',
+                      help='Run without building first (only when using ToT)',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--no-logging', '--no_logging',
+                      help='Disable logging except for errors',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--print-times',
+                      help='Print timing information from r8',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--print-dexsegments',
+                      metavar='BENCHMARKNAME',
+                      help='Print the sizes of individual dex segments as ' +
+                           '\'<BENCHMARKNAME>-<APP>-<segment>(CodeSize): '
+                           '<bytes>\'')
+  result.add_argument('--print-runtimeraw',
+                      metavar='BENCHMARKNAME',
+                      help='Print the line \'<BENCHMARKNAME>(RunTimeRaw):' +
+                           ' <elapsed> ms\' at the end where <elapsed> is' +
+                           ' the elapsed time in milliseconds.')
+  result.add_argument('--quiet',
+                      help='Disable verbose logging',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--r8-compilation-steps', '--r8_compilation_steps',
+                      help='Number of times R8 should be run on each app',
+                      default=2,
+                      type=int)
+  result.add_argument('--run-tests', '--run_tests',
+                      help='Whether to run instrumentation tests',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--sign-apks', '--sign_apks',
+                      help='Whether the APKs should be signed',
+                      default=False,
+                      action='store_true')
+  result.add_argument('--shrinker',
+                      help='The shrinkers to use (by default, all are run)',
+                      action='append')
+  result.add_argument('--version',
+                      default='main',
+                      help='The version of R8 to use (e.g., 1.4.51)')
+  (options, args) = result.parse_known_args(argv)
 
   if options.app or options.app_collection:
     if not options.app:
diff --git a/tools/youtube_data.py b/tools/youtube_data.py
index 827041a..64bc995 100644
--- a/tools/youtube_data.py
+++ b/tools/youtube_data.py
@@ -115,3 +115,21 @@
     }
   },
 }
+
+def GetLatestVersion():
+  return LATEST_VERSION
+
+def GetName():
+  return 'youtube'
+
+def GetMemoryData(version):
+  assert version == '16.20'
+  return {
+      'find-xmx-min': 2800,
+      'find-xmx-max': 3200,
+      'find-xmx-range': 64,
+      'oom-threshold': 3000,
+      # TODO(b/143431825): Youtube can OOM randomly in memory configurations
+      #  that should work.
+      'skip-find-xmx-max': True,
+  }