Merge commit '34e27f5ef1e7849efd311fd649791a0024ef686a' into dev-release
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index cc80809..7837f26 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -47,10 +47,26 @@
   """ % FMT_CMD))
   return results
 
+def CheckDeterministicDebuggingChanged(input_api, output_api):
+  for f in input_api.AffectedFiles():
+    path = f.LocalPath()
+    if not path.endswith('InternalOptions.java'):
+      continue
+    branch = (
+        check_output(['git', 'cl', 'upstream'])
+            .strip()
+            .replace('refs/heads/', ''))
+    diff = check_output(
+        ['git', 'diff', '--no-prefix', '-U0', branch, '--', path])
+    if 'DETERMINISTIC_DEBUGGING' in diff:
+      return [output_api.PresubmitError(diff)]
+  return []
+
 def CheckChange(input_api, output_api):
   results = []
   results.extend(CheckFormatting(input_api, output_api))
   results.extend(CheckDoNotMerge(input_api, output_api))
+  results.extend(CheckDeterministicDebuggingChanged(input_api, output_api))
   return results
 
 def CheckChangeOnCommit(input_api, output_api):
diff --git a/build.gradle b/build.gradle
index fd47c97..77b5865 100644
--- a/build.gradle
+++ b/build.gradle
@@ -35,7 +35,7 @@
 
 ext {
     androidSupportVersion = '25.4.0'
-    asmVersion = '7.1'
+    asmVersion = '7.2'
     espressoVersion = '3.0.0'
     fastutilVersion = '7.2.0'
     guavaVersion = '23.0'
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index a6d704b..bf55656 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -132,7 +132,7 @@
     List<DexEncodedMethod> directMethods = new ArrayList<>();
     List<DexEncodedMethod> virtualMethods = new ArrayList<>();
     for (DexEncodedMethod method : methods) {
-      assert method.method.holder == clazz.type;
+      assert method.holder() == clazz.type;
       CfCode code = null;
       if (!method.accessFlags.isAbstract() /*&& !method.accessFlags.isNative()*/) {
         code = buildEmptyThrowingCfCode(method.method);
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 4b310aa..51b6ed7 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -203,7 +203,7 @@
           isStatic
               ? appInfo.lookupStaticTarget(field.holder, field)
               : appInfo.lookupInstanceTarget(field.holder, field);
-      if (baseField != null && baseField.field.holder != field.holder) {
+      if (baseField != null && baseField.holder() != field.holder) {
         field = baseField.field;
       }
       addType(field.holder);
@@ -214,7 +214,7 @@
           noObfuscationTypes.add(field.holder);
         }
         if (baseField.accessFlags.isVisibilityDependingOnPackage()) {
-          keepPackageNames.add(baseField.field.holder.getPackageName());
+          keepPackageNames.add(baseField.holder().getPackageName());
         }
         typeFields.add(field);
       }
@@ -234,7 +234,7 @@
           noObfuscationTypes.add(method.holder);
         }
         if (encodedMethod.accessFlags.isVisibilityDependingOnPackage()) {
-          keepPackageNames.add(encodedMethod.method.holder.getPackageName());
+          keepPackageNames.add(encodedMethod.holder().getPackageName());
         }
         typeMethods.add(method);
       }
@@ -247,7 +247,7 @@
     private void registerMethod(DexEncodedMethod method) {
       DexEncodedMethod superTarget =
           appInfo
-              .resolveMethod(method.method.holder, method.method)
+              .resolveMethod(method.holder(), method.method)
               .lookupInvokeSpecialTarget(context, appInfo);
       if (superTarget != null) {
         addMethod(superTarget.method);
@@ -477,7 +477,7 @@
       if (encodedMethod.accessFlags.isStatic()) {
         append("<clinit>");
       } else {
-        String holderName = encodedMethod.method.holder.toSourceString();
+        String holderName = encodedMethod.holder().toSourceString();
         String constructorName = holderName.substring(holderName.lastIndexOf('.') + 1);
         append(constructorName);
       }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5bca79b1..15af102 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -34,7 +34,9 @@
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 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.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.NestedPrivateMethodLense;
 import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
@@ -45,6 +47,7 @@
 import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.UninstantiatedTypeOptimizationGraphLense;
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector;
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector.UnusedArgumentsGraphLense;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingCfMethods;
 import com.android.tools.r8.ir.optimize.enums.EnumValueInfoMapCollector;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.jar.CfApplicationWriter;
@@ -280,6 +283,11 @@
       if (!options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
         DesugaredLibraryRetargeter.checkForAssumedLibraryTypes(appView);
       }
+      InterfaceMethodRewriter.checkForAssumedLibraryTypes(appView.appInfo(), options);
+      BackportedMethodRewriter.registerAssumedLibraryTypes(options);
+      if (options.enableEnumUnboxing) {
+        EnumUnboxingCfMethods.registerSynthesizedCodeReferences(options.itemFactory);
+      }
 
       List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
       timing.begin("Strip unused code");
@@ -364,21 +372,22 @@
           application = pruner.run(application);
 
           // Recompute the subtyping information.
+          Set<DexType> removedClasses = pruner.getRemovedClasses();
           appView.setAppInfo(
               appView
                   .appInfo()
                   .withLiveness()
                   .prunedCopyFrom(
                       application,
-                      pruner.getRemovedClasses(),
+                      removedClasses,
                       pruner.getMethodsToKeepForConfigurationDebugging()));
-          appView.setAppServices(appView.appServices().prunedCopy(pruner.getRemovedClasses()));
+          appView.setAppServices(appView.appServices().prunedCopy(removedClasses));
           new AbstractMethodRemover(appView.appInfo().withLiveness()).run();
 
           AnnotationRemover annotationRemover =
               annotationRemoverBuilder
                   .computeClassesToRetainInnerClassAttributeFor(appViewWithLiveness)
-                  .build(appViewWithLiveness);
+                  .build(appViewWithLiveness, removedClasses);
           annotationRemover.ensureValid().run();
           classesToRetainInnerClassAttributeFor =
               annotationRemover.getClassesToRetainInnerClassAttributeFor();
@@ -659,6 +668,7 @@
 
             TreePruner pruner = new TreePruner(appViewWithLiveness, treePrunerConfiguration);
             application = pruner.run(application);
+            Set<DexType> removedClasses = pruner.getRemovedClasses();
 
             if (options.usageInformationConsumer != null) {
               ExceptionUtils.withFinishedResourceHandler(
@@ -669,9 +679,9 @@
                     .appInfo()
                     .prunedCopyFrom(
                         application,
-                        CollectionUtils.mergeSets(prunedTypes, pruner.getRemovedClasses()),
+                        CollectionUtils.mergeSets(prunedTypes, removedClasses),
                         pruner.getMethodsToKeepForConfigurationDebugging()));
-            appView.setAppServices(appView.appServices().prunedCopy(pruner.getRemovedClasses()));
+            appView.setAppServices(appView.appServices().prunedCopy(removedClasses));
 
             // TODO(b/130721661): Enable this assert.
             // assert Inliner.verifyNoMethodsInlinedDueToSingleCallSite(appView);
@@ -694,7 +704,7 @@
             assert classesToRetainInnerClassAttributeFor != null;
             AnnotationRemover.builder()
                 .setClassesToRetainInnerClassAttributeFor(classesToRetainInnerClassAttributeFor)
-                .build(appView.withLiveness())
+                .build(appView.withLiveness(), removedClasses)
                 .run();
             if (!mainDexClasses.isEmpty()) {
               // Remove types that no longer exists from the computed main dex list.
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index fea04c7..3e9ae7e 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.utils.InternalOptions.DETERMINISTIC_DEBUGGING;
+
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
@@ -854,6 +856,7 @@
       internal.enableVerticalClassMerging = false;
       internal.enableClassStaticizer = false;
       internal.outline.enabled = false;
+      internal.enableEnumUnboxing = false;
     }
 
     // Amend the proguard-map consumer with options from the proguard configuration.
@@ -925,8 +928,10 @@
     internal.synthesizedClassPrefix = synthesizedClassPrefix;
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
-    assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
-    internal.threadCount = getThreadCount();
+    if (!DETERMINISTIC_DEBUGGING) {
+      assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
+      internal.threadCount = getThreadCount();
+    }
 
     return internal;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
index 93ee213..21a5392 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -75,6 +75,7 @@
   private Set<String> imports = new HashSet<>();
   private List<String> methods = new ArrayList<>();
   private Set<String> methodNames = new HashSet<>();
+  private Set<String> synthesizedTypes = new HashSet<>();
 
   // Per method structures.
 
@@ -261,7 +262,8 @@
     if (field != null) {
       return "options.itemFactory." + field;
     }
-    return "options.itemFactory.createSynthesizedType(" + quote(descriptor) + ")";
+    synthesizedTypes.add(descriptor);
+    return "options.itemFactory.createType(" + quote(descriptor) + ")";
   }
 
   private String dexProto(DexProto proto) {
@@ -523,4 +525,8 @@
   public void print(CfConstMethodType type) {
     throw new Unimplemented(type.getClass().getSimpleName());
   }
+
+  public Set<String> getSynthesizedTypes() {
+    return synthesizedTypes;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index 5b4d9f2..93973c3 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -238,8 +238,8 @@
         if (argumentIndex < 0) {
           argumentType =
               code.method.isInstanceInitializer()
-                  ? new ThisInstanceInfo(instruction.asArgument(), code.method.method.holder)
-                  : createInitializedType(code.method.method.holder);
+                  ? new ThisInstanceInfo(instruction.asArgument(), code.method.holder())
+                  : createInitializedType(code.method.holder());
         } else {
           argumentType =
               createInitializedType(code.method.method.proto.parameters.values[argumentIndex]);
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index a7c3ec0..bd4c6da 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -351,10 +351,10 @@
     String originalClassName;
     if (proguardMap != null) {
       signature = proguardMap.originalSignatureOf(method.method);
-      originalClassName = proguardMap.originalNameOf(method.method.holder);
+      originalClassName = proguardMap.originalNameOf(method.holder());
     } else {
       signature = MethodSignature.fromDexMethod(method.method);
-      originalClassName = method.method.holder.toSourceString();
+      originalClassName = method.holder().toSourceString();
     }
     codeToSignatureMap.put(code, originalClassName + signature);
   }
diff --git a/src/main/java/com/android/tools/r8/errors/InvalidLibrarySuperclassDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InvalidLibrarySuperclassDiagnostic.java
new file mode 100644
index 0000000..2392f0a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/InvalidLibrarySuperclassDiagnostic.java
@@ -0,0 +1,71 @@
+// 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.errors;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+
+/**
+ * Diagnostic for super types of library classes which are not library classes but required for
+ * desugaring.
+ */
+@Keep
+public class InvalidLibrarySuperclassDiagnostic implements DesugarDiagnostic {
+
+  private final Origin origin;
+  private final List<MethodReference> methods;
+  private final ClassReference libraryType;
+  private final ClassReference invalidSuperType;
+  private final String message;
+
+  public InvalidLibrarySuperclassDiagnostic(
+      Origin origin,
+      ClassReference libraryType,
+      ClassReference invalidSuperType,
+      String message,
+      List<MethodReference> methods) {
+    assert origin != null;
+    assert libraryType != null;
+    assert invalidSuperType != null;
+    assert message != null;
+    this.origin = origin;
+    this.libraryType = libraryType;
+    this.invalidSuperType = invalidSuperType;
+    this.message = message;
+    this.methods = methods;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    StringBuilder builder =
+        new StringBuilder()
+            .append("Superclass `")
+            .append(invalidSuperType.getTypeName())
+            .append("` of library class `")
+            .append(libraryType.getTypeName())
+            .append("` is ")
+            .append(message)
+            .append(
+                ". A superclass of a library class should be a library class. This is required for"
+                    + " the desugaring of ");
+    StringUtils.append(builder, methods, ", ", StringUtils.BraceType.NONE);
+    return builder.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index a41e119..8b63cbf 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -141,6 +141,10 @@
 
   @Override
   public DexClass definitionFor(DexType type) {
+    return definitionForWithoutExistenceAssert(type);
+  }
+
+  public final DexClass definitionForWithoutExistenceAssert(DexType type) {
     assert checkIfObsolete();
     DexProgramClass cached = synthesizedClasses.get(type);
     if (cached != null) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 58611d4..4edc070 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -357,7 +357,7 @@
     return true;
   }
 
-  public boolean hasAnyInstantiatedLambdas(DexProgramClass clazz) {
+  public boolean isInstantiatedInterface(DexProgramClass clazz) {
     assert checkIfObsolete();
     return true; // Don't know, there might be.
   }
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 0c4267c..897763a 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -400,7 +400,7 @@
   @Override
   public void registerCodeReferences(DexEncodedMethod method, UseRegistry registry) {
     for (CfInstruction instruction : instructions) {
-      instruction.registerUse(registry, method.method.holder);
+      instruction.registerUse(registry, method.holder());
     }
     for (CfTryCatch tryCatch : tryCatchRanges) {
       for (DexType guard : tryCatch.guards) {
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 85810ed..9dd0398 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -332,7 +332,7 @@
   }
 
   private boolean verifyCorrectnessOfFieldHolder(DexEncodedField field) {
-    assert field.field.holder == type
+    assert field.holder() == type
         : "Expected field `"
             + field.field.toSourceString()
             + "` to have holder `"
@@ -435,8 +435,7 @@
   }
 
   private boolean isSignaturePolymorphicMethod(DexEncodedMethod method, DexItemFactory factory) {
-    assert method.method.holder == factory.methodHandleType
-        || method.method.holder == factory.varHandleType;
+    assert method.holder() == factory.methodHandleType || method.holder() == factory.varHandleType;
     return method.accessFlags.isVarargs()
         && method.accessFlags.isNative()
         && method.method.proto.parameters.size() == 1
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index 37f8853..6c05f12 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -12,7 +12,7 @@
   private final DexEncodedMethod method;
 
   DexClassAndMethod(DexClass holder, DexEncodedMethod method) {
-    assert holder.type == method.method.holder;
+    assert holder.type == method.holder();
     this.holder = holder;
     this.method = method;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
index 49611c6..e73c267 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -73,7 +73,7 @@
     int argumentRegister = code.registerSize - code.incomingRegisterSize;
     if (!method.accessFlags.isStatic()) {
       DexString name = factory.thisName;
-      DexType type = method.method.holder;
+      DexType type = method.holder();
       startArgument(argumentRegister, name, type);
       argumentRegister += ValueType.fromDexType(type).requiredRegisters();
     }
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 10427a3..0d5c50d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -159,6 +159,10 @@
     return isStatic();
   }
 
+  public boolean isVolatile() {
+    return accessFlags.isVolatile();
+  }
+
   public boolean hasAnnotation() {
     return !annotations().isEmpty();
   }
@@ -206,7 +210,7 @@
           && singleValue.asSingleFieldValue().getField() == field) {
         return null;
       }
-      if (singleValue.isMaterializableInContext(appView, code.method.method.holder)) {
+      if (singleValue.isMaterializableInContext(appView, code.method.holder())) {
         TypeElement type = TypeElement.fromDexType(field.type, maybeNull(), appView);
         return singleValue.createMaterializingInstruction(
             appView, code, TypeAndLocalInfoSupplier.create(type, local));
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 01b0e84..bb1ee15 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -450,7 +450,7 @@
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     checkIfObsolete();
     return isInliningCandidate(
-        container.method.holder, inliningReason, appInfo, whyAreYouNotInliningReporter);
+        container.holder(), inliningReason, appInfo, whyAreYouNotInliningReporter);
   }
 
   public boolean isInliningCandidate(
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 30d9369..b0a86c6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -26,6 +26,8 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.LRUCacheTable;
@@ -156,6 +158,7 @@
   public final DexString isEmptyMethodName = createString("isEmpty");
   public final DexString lengthMethodName = createString("length");
 
+  public final DexString concatMethodName = createString("concat");
   public final DexString containsMethodName = createString("contains");
   public final DexString startsWithMethodName = createString("startsWith");
   public final DexString endsWithMethodName = createString("endsWith");
@@ -254,6 +257,8 @@
   public final DexString throwableDescriptor = createString(throwableDescriptorString);
   public final DexString illegalAccessErrorDescriptor =
       createString("Ljava/lang/IllegalAccessError;");
+  public final DexString illegalArgumentExceptionDescriptor =
+      createString("Ljava/lang/IllegalArgumentException;");
   public final DexString icceDescriptor = createString("Ljava/lang/IncompatibleClassChangeError;");
   public final DexString exceptionInInitializerErrorDescriptor =
       createString("Ljava/lang/ExceptionInInitializerError;");
@@ -381,6 +386,8 @@
   public final DexType throwableType = createStaticallyKnownType(throwableDescriptor);
   public final DexType illegalAccessErrorType =
       createStaticallyKnownType(illegalAccessErrorDescriptor);
+  public final DexType illegalArgumentExceptionType =
+      createStaticallyKnownType(illegalArgumentExceptionDescriptor);
   public final DexType icceType = createStaticallyKnownType(icceDescriptor);
   public final DexType exceptionInInitializerErrorType =
       createStaticallyKnownType(exceptionInInitializerErrorDescriptor);
@@ -398,6 +405,15 @@
   public final DexType androidOsBuildVersionType =
       createStaticallyKnownType("Landroid/os/Build$VERSION;");
 
+  public final DexString nestConstructorDescriptor =
+      createString("L" + NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME + ";");
+  public final DexType nestConstructorType = createStaticallyKnownType(nestConstructorDescriptor);
+
+  public final DexString enumUnboxingUtilityDescriptor =
+      createString("L" + EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_CLASS_NAME + ";");
+  public final DexType enumUnboxingUtilityType =
+      createStaticallyKnownType(enumUnboxingUtilityDescriptor);
+
   public final StringBuildingMethods stringBuilderMethods =
       new StringBuildingMethods(stringBuilderType);
   public final StringBuildingMethods stringBufferMethods =
@@ -415,6 +431,8 @@
   public final ConstructorMethods constructorMethods = new ConstructorMethods();
   public final EnumMethods enumMethods = new EnumMethods();
   public final NullPointerExceptionMethods npeMethods = new NullPointerExceptionMethods();
+  public final IllegalArgumentExceptionMethods illegalArgumentExceptionMethods =
+      new IllegalArgumentExceptionMethods();
   public final PrimitiveTypesBoxedTypeFields primitiveTypesBoxedTypeFields =
       new PrimitiveTypesBoxedTypeFields();
   public final AtomicFieldUpdaterMethods atomicFieldUpdaterMethods =
@@ -965,6 +983,13 @@
         createMethod(npeType, createProto(voidType, stringType), constructorMethodName);
   }
 
+  public class IllegalArgumentExceptionMethods {
+
+    public final DexMethod initWithMessage =
+        createMethod(
+            illegalArgumentExceptionType, createProto(voidType, stringType), initMethodName);
+  }
+
   /**
    * All boxed types (Boolean, Byte, ...) have a field named TYPE which contains the Class object
    * for the primitive type.
@@ -1051,6 +1076,7 @@
     public final DexMethod isEmpty;
     public final DexMethod length;
 
+    public final DexMethod concat;
     public final DexMethod contains;
     public final DexMethod startsWith;
     public final DexMethod endsWith;
@@ -1083,6 +1109,7 @@
       DexString[] needsOneObject = { objectDescriptor };
       DexString[] needsOneInt = { intDescriptor };
 
+      concat = createMethod(stringDescriptor, concatMethodName, stringDescriptor, needsOneString);
       contains = createMethod(
           stringDescriptor, containsMethodName, booleanDescriptor, needsOneCharSequence);
       startsWith = createMethod(
@@ -1414,6 +1441,12 @@
     return type;
   }
 
+  // Registration of a type that is only dynamically known (eg, in the desugared lib spec), but
+  // will be referenced during desugaring.
+  public void registerTypeNeededForDesugaring(DexType type) {
+    addPossiblySynthesizedType(type);
+  }
+
   private void addPossiblySynthesizedType(DexType type) {
     if (type.isArrayType()) {
       type = type.toBaseType(this);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 80605e5..5236191 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -6,6 +6,11 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import java.util.ArrayList;
+import java.util.List;
 
 public class DexMethod extends DexMember<DexEncodedMethod, DexMethod> {
 
@@ -28,6 +33,22 @@
     return "Method " + holder + "." + name + " " + proto.toString();
   }
 
+  public MethodReference asMethodReference(AppView<?> appView) {
+    List<TypeReference> parameters = new ArrayList<>();
+    for (DexType value : proto.parameters.values) {
+      parameters.add(Reference.typeFromDescriptor(value.toDescriptorString()));
+    }
+    TypeReference returnType =
+        proto.returnType == appView.dexItemFactory().voidType
+            ? null
+            : Reference.typeFromDescriptor(proto.returnType.toDescriptorString());
+    return Reference.method(
+        Reference.classFromDescriptor(holder.toDescriptorString()),
+        name.toString(),
+        parameters,
+        returnType);
+  }
+
   public int getArity() {
     return proto.parameters.size();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java b/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
index 4fd96f7..23bfb99 100644
--- a/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
@@ -7,6 +7,7 @@
 import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.BiConsumer;
 
 public class EnumValueInfoMapCollection {
 
@@ -94,6 +95,10 @@
       return map.get(field);
     }
 
+    public void forEach(BiConsumer<DexField, EnumValueInfo> consumer) {
+      map.forEach(consumer);
+    }
+
     EnumValueInfoMap rewrittenWithLens(GraphLense lens) {
       ImmutableMap.Builder<DexField, EnumValueInfo> builder = ImmutableMap.builder();
       map.forEach(
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index 049cb7a..97d49a5 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -96,6 +96,8 @@
  */
 public class GenericSignature {
 
+  private static final List<FormalTypeParameter> EMPTY_TYPE_PARAMS = ImmutableList.of();
+
   interface DexDefinitionSignature<T extends DexDefinition> {
     default boolean isClassSignature() {
       return false;
@@ -122,19 +124,51 @@
     }
   }
 
+  public static class FormalTypeParameter {
+
+    final String name;
+    final FieldTypeSignature classBound;
+    final List<FieldTypeSignature> interfaceBounds;
+
+    FormalTypeParameter(
+        String name, FieldTypeSignature classBound, List<FieldTypeSignature> interfaceBounds) {
+      this.name = name;
+      this.classBound = classBound;
+      this.interfaceBounds = interfaceBounds;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public FieldTypeSignature getClassBound() {
+      return classBound;
+    }
+
+    public List<FieldTypeSignature> getInterfaceBounds() {
+      return interfaceBounds;
+    }
+  }
+
   public static class ClassSignature implements DexDefinitionSignature<DexClass> {
     static final ClassSignature UNKNOWN_CLASS_SIGNATURE =
-        new ClassSignature(ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE, ImmutableList.of());
+        new ClassSignature(
+            ImmutableList.of(),
+            ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE,
+            ImmutableList.of());
 
-    // TODO(b/129925954): encoding formal type parameters
+    final List<FormalTypeParameter> formalTypeParameters;
     final ClassTypeSignature superClassSignature;
     final List<ClassTypeSignature> superInterfaceSignatures;
 
     ClassSignature(
+        List<FormalTypeParameter> formalTypeParameters,
         ClassTypeSignature superClassSignature,
         List<ClassTypeSignature> superInterfaceSignatures) {
+      assert formalTypeParameters != null;
       assert superClassSignature != null;
       assert superInterfaceSignatures != null;
+      this.formalTypeParameters = formalTypeParameters;
       this.superClassSignature = superClassSignature;
       this.superInterfaceSignatures = superInterfaceSignatures;
     }
@@ -221,6 +255,10 @@
     public TypeVariableSignature asTypeVariableSignature() {
       return null;
     }
+
+    public boolean isUnknown() {
+      return this == ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE;
+    }
   }
 
   public static class ClassTypeSignature extends FieldTypeSignature {
@@ -331,7 +369,7 @@
   public static class TypeVariableSignature extends FieldTypeSignature {
     final String typeVariable;
 
-    TypeVariableSignature(String typeVariable) {
+    private TypeVariableSignature(String typeVariable) {
       assert typeVariable != null;
       this.typeVariable = typeVariable;
     }
@@ -350,6 +388,10 @@
     public ArrayTypeSignature toArrayTypeSignature(AppView<?> appView) {
       return new ArrayTypeSignature(this);
     }
+
+    public String getTypeVariable() {
+      return typeVariable;
+    }
   }
 
   // TODO(b/129925954): Canonicalization?
@@ -400,20 +442,24 @@
 
   public static class MethodTypeSignature implements DexDefinitionSignature<DexEncodedMethod> {
     static final MethodTypeSignature UNKNOWN_METHOD_TYPE_SIGNATURE =
-        new MethodTypeSignature(ImmutableList.of(), ReturnType.VOID, ImmutableList.of());
+        new MethodTypeSignature(
+            ImmutableList.of(), ImmutableList.of(), ReturnType.VOID, ImmutableList.of());
 
-    // TODO(b/129925954): encoding formal type parameters
+    final List<FormalTypeParameter> formalTypeParameters;
     final List<TypeSignature> typeSignatures;
     final ReturnType returnType;
     final List<TypeSignature> throwsSignatures;
 
     MethodTypeSignature(
+        final List<FormalTypeParameter> formalTypeParameters,
         List<TypeSignature> typeSignatures,
         ReturnType returnType,
         List<TypeSignature> throwsSignatures) {
+      assert formalTypeParameters != null;
       assert typeSignatures != null;
       assert returnType != null;
       assert throwsSignatures != null;
+      this.formalTypeParameters = formalTypeParameters;
       this.typeSignatures = typeSignatures;
       this.returnType = returnType;
       this.throwsSignatures = throwsSignatures;
@@ -443,6 +489,10 @@
     public MethodTypeSignature asMethodTypeSignature() {
       return this;
     }
+
+    public List<FormalTypeParameter> getFormalTypeParameters() {
+      return formalTypeParameters;
+    }
   }
 
   enum Kind {
@@ -518,7 +568,7 @@
 
     public static FieldTypeSignature toFieldTypeSignature(
         DexEncodedField field, AppView<AppInfoWithLiveness> appView) {
-      DexClass currentClassContext = appView.definitionFor(field.field.holder);
+      DexClass currentClassContext = appView.definitionFor(field.holder());
       DexDefinitionSignature<?> signature =
           toGenericSignature(currentClassContext, field, appView);
       if (signature != null) {
@@ -530,7 +580,7 @@
 
     public static MethodTypeSignature toMethodTypeSignature(
         DexEncodedMethod method, AppView<AppInfoWithLiveness> appView) {
-      DexClass currentClassContext = appView.definitionFor(method.method.holder);
+      DexClass currentClassContext = appView.definitionFor(method.holder());
       DexDefinitionSignature<?> signature =
           toGenericSignature(currentClassContext, method, appView);
       if (signature != null) {
@@ -688,7 +738,7 @@
     private ClassSignature parseClassSignature() {
       // ClassSignature ::= FormalTypeParameters? SuperclassSignature SuperinterfaceSignature*.
 
-      parseOptFormalTypeParameters();
+      List<FormalTypeParameter> formalTypeParameters = parseOptFormalTypeParameters();
 
       // SuperclassSignature ::= ClassTypeSignature.
       ClassTypeSignature superClassSignature =
@@ -700,42 +750,53 @@
         builder.add(parseClassTypeSignature(ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION));
       }
 
-      return new ClassSignature(superClassSignature, builder.build());
+      return new ClassSignature(formalTypeParameters, superClassSignature, builder.build());
     }
 
-    private void parseOptFormalTypeParameters() {
+    private List<FormalTypeParameter> parseOptFormalTypeParameters() {
       // FormalTypeParameters ::= "<" FormalTypeParameter+ ">".
-
-      if (symbol == '<') {
-        scanSymbol();
-
-        updateFormalTypeParameter();
-
-        while ((symbol != '>') && (symbol > 0)) {
-          updateFormalTypeParameter();
-        }
-
-        expect('>');
+      if (symbol != '<') {
+        return EMPTY_TYPE_PARAMS;
       }
+      scanSymbol();
+
+      ImmutableList.Builder<FormalTypeParameter> builder = ImmutableList.builder();
+      while ((symbol != '>') && (symbol > 0)) {
+        builder.add(updateFormalTypeParameter());
+      }
+      expect('>');
+      return builder.build();
     }
 
-    private void updateFormalTypeParameter() {
+    private FormalTypeParameter updateFormalTypeParameter() {
       // FormalTypeParameter ::= Identifier ClassBound InterfaceBound*.
       scanIdentifier();
       assert identifier != null;
 
+      String typeParameterIdentifier = identifier;
+
       // ClassBound ::= ":" FieldTypeSignature?.
       expect(':');
 
+      FieldTypeSignature classBound = ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE;
       if (symbol == 'L' || symbol == '[' || symbol == 'T') {
-        parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
+        classBound = parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
       }
 
+      // Only build the interfacebound builder, which is uncommon, if we actually see an interface.
+      ImmutableList.Builder<FieldTypeSignature> builder = null;
       while (symbol == ':') {
         // InterfaceBound ::= ":" FieldTypeSignature.
+        if (builder == null) {
+          builder = ImmutableList.builder();
+        }
         scanSymbol();
-        parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
+        builder.add(parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION));
       }
+      if (builder == null) {
+        return new FormalTypeParameter(typeParameterIdentifier, classBound, null);
+      }
+      return new FormalTypeParameter(typeParameterIdentifier, classBound, builder.build());
     }
 
     private FieldTypeSignature parseFieldTypeSignature(ParserPosition parserPosition) {
@@ -862,7 +923,7 @@
     private MethodTypeSignature parseMethodTypeSignature() {
       // MethodTypeSignature ::=
       //     FormalTypeParameters? "(" TypeSignature* ")" ReturnType ThrowsSignature*.
-      parseOptFormalTypeParameters();
+      List<FormalTypeParameter> formalTypeParameters = parseOptFormalTypeParameters();
 
       expect('(');
 
@@ -890,7 +951,10 @@
       }
 
       return new MethodTypeSignature(
-          parameterSignatureBuilder.build(), returnType, throwsSignatureBuilder.build());
+          formalTypeParameters,
+          parameterSignatureBuilder.build(),
+          returnType,
+          throwsSignatureBuilder.build());
     }
 
     private ReturnType updateReturnType() {
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 7d2d844..e5de7ff 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -15,7 +15,6 @@
 import java.util.ArrayDeque;
 import java.util.Collections;
 import java.util.Deque;
-import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -710,7 +709,7 @@
 
     @Override
     public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
-      Set<DexMethod> result = new HashSet<>();
+      Set<DexMethod> result = Sets.newIdentityHashSet();
       for (DexMethod previous : previousLense.lookupMethodInAllContexts(method)) {
         result.add(methodMap.getOrDefault(previous, previous));
       }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index 269b36b..6fecde6 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -228,7 +228,7 @@
   }
 
   private boolean verifyCorrectnessOfMethodHolder(DexEncodedMethod method) {
-    assert method.method.holder == holder.type
+    assert method.holder() == holder.type
         : "Expected method `"
             + method.method.toSourceString()
             + "` to have holder `"
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
index 2ed505e..546bf90 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
@@ -20,6 +20,12 @@
 
   boolean isInstantiatedDirectly(DexProgramClass clazz);
 
+  boolean isInstantiatedDirectlyOrHasInstantiatedSubtype(DexProgramClass clazz);
+
+  boolean isInterfaceWithUnknownSubtypeHierarchy(DexProgramClass clazz);
+
+  boolean isImmediateInterfaceOfInstantiatedLambda(DexProgramClass clazz);
+
   ObjectAllocationInfoCollection rewrittenWithLens(
       DexDefinitionSupplier definitions, GraphLense lens);
 }
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 3869d23..5999bbf 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -21,28 +21,101 @@
 import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 
-/** Stores the set of instantiated classes along with their allocation sites. */
-public class ObjectAllocationInfoCollectionImpl implements ObjectAllocationInfoCollection {
+/**
+ * Provides information about all possibly instantiated classes and lambdas, their allocation sites,
+ * if known, as well as the full subtyping hierarchy of types above them.
+ */
+public abstract class ObjectAllocationInfoCollectionImpl implements ObjectAllocationInfoCollection {
 
-  private final Map<DexProgramClass, Set<DexEncodedMethod>> classesWithAllocationSiteTracking;
-  private final Set<DexProgramClass> classesWithoutAllocationSiteTracking;
+  /** Instantiated classes with the contexts of the instantiations. */
+  final Map<DexProgramClass, Set<DexEncodedMethod>> classesWithAllocationSiteTracking =
+      new IdentityHashMap<>();
 
-  private ObjectAllocationInfoCollectionImpl(
-      Map<DexProgramClass, Set<DexEncodedMethod>> classesWithAllocationSiteTracking,
-      Set<DexProgramClass> classesWithoutAllocationSiteTracking) {
-    this.classesWithAllocationSiteTracking = classesWithAllocationSiteTracking;
-    this.classesWithoutAllocationSiteTracking = classesWithoutAllocationSiteTracking;
+  /** Instantiated classes without contexts. */
+  final Set<DexProgramClass> classesWithoutAllocationSiteTracking = Sets.newIdentityHashSet();
+
+  /**
+   * Set of interface types for which the subtype hierarchy is unknown from that type.
+   *
+   * <p>E.g., the type is kept thus there could be instantiations of subtypes.
+   *
+   * <p>TODO(b/145344105): Generalize this to typesWithUnknownSubtypeHierarchy.
+   */
+  final Set<DexProgramClass> interfacesWithUnknownSubtypeHierarchy = Sets.newIdentityHashSet();
+
+  /** Map of types directly implemented by lambdas to those lambdas. */
+  final Map<DexType, List<LambdaDescriptor>> instantiatedLambdas = new IdentityHashMap<>();
+
+  /**
+   * Hierarchy for instantiated types mapping a type to the set of immediate subtypes for which some
+   * subtype is either an instantiated class, kept interface or is implemented by an instantiated
+   * lambda.
+   */
+  Map<DexType, Set<DexClass>> instantiatedHierarchy = new IdentityHashMap<>();
+
+  private ObjectAllocationInfoCollectionImpl() {
+    // Only builder can allocate an instance.
   }
 
   public static Builder builder(boolean trackAllocationSites, GraphReporter reporter) {
     return new Builder(trackAllocationSites, reporter);
   }
 
-  public void markNoLongerInstantiated(DexProgramClass clazz) {
-    classesWithAllocationSiteTracking.remove(clazz);
-    classesWithoutAllocationSiteTracking.remove(clazz);
+  public abstract void mutate(Consumer<Builder> mutator, AppInfo appInfo);
+
+  /**
+   * True if a class type might be instantiated directly at the given type.
+   *
+   * <p>Should not be called on interface types.
+   *
+   * <p>TODO(b/145344105): Extend this to not be called on any abstract types.
+   */
+  @Override
+  public boolean isInstantiatedDirectly(DexProgramClass clazz) {
+    if (clazz.isInterface()) {
+      return false;
+    }
+    if (classesWithAllocationSiteTracking.containsKey(clazz)) {
+      assert !classesWithAllocationSiteTracking.get(clazz).isEmpty();
+      return true;
+    }
+    return classesWithoutAllocationSiteTracking.contains(clazz);
+  }
+
+  /** True if the type or subtype of it might be instantiated. */
+  @Override
+  public boolean isInstantiatedDirectlyOrHasInstantiatedSubtype(DexProgramClass clazz) {
+    return (!clazz.isInterface() && isInstantiatedDirectly(clazz))
+        || hasInstantiatedStrictSubtype(clazz);
+  }
+
+  /** True if there might exist an instantiated (strict) subtype of the given type. */
+  public boolean hasInstantiatedStrictSubtype(DexProgramClass clazz) {
+    if (instantiatedHierarchy.get(clazz.type) != null) {
+      return true;
+    }
+    if (!clazz.isInterface()) {
+      return false;
+    }
+    return interfacesWithUnknownSubtypeHierarchy.contains(clazz)
+        || isImmediateInterfaceOfInstantiatedLambda(clazz);
+  }
+
+  /** True if the type is an interface that has unknown instantiations, eg, by being kept. */
+  @Override
+  public boolean isInterfaceWithUnknownSubtypeHierarchy(DexProgramClass clazz) {
+    return clazz.isInterface() && interfacesWithUnknownSubtypeHierarchy.contains(clazz);
+  }
+
+  /** Returns true if the type is an immediate interface of an instantiated lambda. */
+  @Override
+  public boolean isImmediateInterfaceOfInstantiatedLambda(DexProgramClass iface) {
+    return iface.isInterface() && instantiatedLambdas.get(iface.type) != null;
+  }
+
+  public Set<DexClass> getImmediateSubtypesInInstantiatedHierarchy(DexType type) {
+    return instantiatedHierarchy.get(type);
   }
 
   @Override
@@ -57,238 +130,15 @@
   }
 
   @Override
-  public boolean isInstantiatedDirectly(DexProgramClass clazz) {
-    if (classesWithAllocationSiteTracking.containsKey(clazz)) {
-      assert !classesWithAllocationSiteTracking.get(clazz).isEmpty();
-      return true;
-    }
-    return classesWithoutAllocationSiteTracking.contains(clazz);
-  }
-
-  @Override
   public ObjectAllocationInfoCollectionImpl rewrittenWithLens(
       DexDefinitionSupplier definitions, GraphLense lens) {
-    return builder(true, null).rewrittenWithLens(this, definitions, lens).build();
+    return builder(true, null).rewrittenWithLens(this, definitions, lens).build(definitions);
   }
 
-  public static class Builder {
-
-    private final boolean trackAllocationSites;
-
-    /** Instantiated classes with the contexts of the instantiations. */
-    private final Map<DexProgramClass, Set<DexEncodedMethod>> classesWithAllocationSiteTracking =
-        new IdentityHashMap<>();
-
-    /** Instantiated classes without contexts. */
-    private final Set<DexProgramClass> classesWithoutAllocationSiteTracking =
-        Sets.newIdentityHashSet();
-
-    /** Set of types directly implemented by a lambda. */
-    private final Map<DexType, List<LambdaDescriptor>> instantiatedLambdas =
-        new IdentityHashMap<>();
-
-    /**
-     * Hierarchy for instantiated types mapping a type to the set of immediate subtypes for which
-     * some subtype is either instantiated or is implemented by an instantiated lambda.
-     */
-    private final Map<DexType, Set<DexClass>> instantiatedHierarchy = new IdentityHashMap<>();
-
-    /**
-     * Set of interface types for which there may be instantiations, such as lambda expressions or
-     * explicit keep rules.
-     */
-    private final Set<DexProgramClass> instantiatedInterfaceTypes = Sets.newIdentityHashSet();
-
-    /** Subset of the above that are marked instantiated by usages that are not lambdas. */
-    public final Set<DexProgramClass> unknownInstantiatedInterfaceTypes = Sets.newIdentityHashSet();
-
-    private GraphReporter reporter;
-
-    private Builder(boolean trackAllocationSites, GraphReporter reporter) {
-      this.trackAllocationSites = trackAllocationSites;
-      this.reporter = reporter;
-    }
-
-    private boolean shouldTrackAllocationSitesForClass(
-        DexProgramClass clazz, InstantiationReason instantiationReason) {
-      if (!trackAllocationSites) {
-        return false;
-      }
-      if (instantiationReason != InstantiationReason.NEW_INSTANCE_INSTRUCTION) {
-        // There is an allocation site which is not a new-instance instruction.
-        return false;
-      }
-      if (classesWithoutAllocationSiteTracking.contains(clazz)) {
-        // We already gave up on tracking the allocation sites for `clazz` previously.
-        return false;
-      }
-      // We currently only use allocation site information for instance field value propagation.
-      return !clazz.instanceFields().isEmpty();
-    }
-
-    public boolean isInstantiatedDirectlyOrIsInstantiationLeaf(DexProgramClass clazz) {
-      if (clazz.isInterface()) {
-        return instantiatedInterfaceTypes.contains(clazz);
-      }
-      return isInstantiatedDirectly(clazz);
-    }
-
-    public boolean isInstantiatedDirectly(DexProgramClass clazz) {
-      assert !clazz.isInterface();
-      if (classesWithAllocationSiteTracking.containsKey(clazz)) {
-        assert !classesWithAllocationSiteTracking.get(clazz).isEmpty();
-        return true;
-      }
-      return classesWithoutAllocationSiteTracking.contains(clazz);
-    }
-
-    public boolean isInstantiatedDirectlyOrHasInstantiatedSubtype(DexProgramClass clazz) {
-      return isInstantiatedDirectlyOrIsInstantiationLeaf(clazz)
-          || instantiatedHierarchy.containsKey(clazz.type);
-    }
-
-    public void forEachInstantiatedSubType(
-        DexType type,
-        Consumer<DexProgramClass> onClass,
-        Consumer<LambdaDescriptor> onLambda,
-        AppInfo appInfo) {
-      internalForEachInstantiatedSubType(
-          type,
-          onClass,
-          onLambda,
-          instantiatedHierarchy,
-          instantiatedLambdas,
-          this::isInstantiatedDirectlyOrIsInstantiationLeaf,
-          appInfo);
-    }
-
-    public Set<DexClass> getImmediateSubtypesInInstantiatedHierarchy(DexType type) {
-      return instantiatedHierarchy.get(type);
-    }
-
-    /**
-     * Records that {@param clazz} is instantiated in {@param context}.
-     *
-     * @return true if {@param clazz} was not instantiated before.
-     */
-    public boolean recordDirectAllocationSite(
-        DexProgramClass clazz,
-        DexEncodedMethod context,
-        InstantiationReason instantiationReason,
-        KeepReason keepReason,
-        AppInfo appInfo) {
-      assert !clazz.isInterface();
-      if (reporter != null) {
-        reporter.registerClass(clazz, keepReason);
-      }
-      populateInstantiatedHierarchy(appInfo, clazz);
-      if (shouldTrackAllocationSitesForClass(clazz, instantiationReason)) {
-        assert context != null;
-        Set<DexEncodedMethod> allocationSitesForClass =
-            classesWithAllocationSiteTracking.computeIfAbsent(
-                clazz, ignore -> Sets.newIdentityHashSet());
-        allocationSitesForClass.add(context);
-        return allocationSitesForClass.size() == 1;
-      }
-      if (classesWithoutAllocationSiteTracking.add(clazz)) {
-        Set<DexEncodedMethod> allocationSitesForClass =
-            classesWithAllocationSiteTracking.remove(clazz);
-        return allocationSitesForClass == null;
-      }
-      return false;
-    }
-
-    public boolean recordInstantiatedInterface(DexProgramClass iface) {
-      assert iface.isInterface();
-      assert !iface.isAnnotation();
-      unknownInstantiatedInterfaceTypes.add(iface);
-      return instantiatedInterfaceTypes.add(iface);
-    }
-
-    public void recordInstantiatedLambdaInterface(
-        DexType iface, LambdaDescriptor lambda, AppInfo appInfo) {
-      instantiatedLambdas.computeIfAbsent(iface, key -> new ArrayList<>()).add(lambda);
-      populateInstantiatedHierarchy(appInfo, iface);
-    }
-
-    private void populateInstantiatedHierarchy(AppInfo appInfo, DexType type) {
-      DexClass clazz = appInfo.definitionFor(type);
-      if (clazz != null) {
-        populateInstantiatedHierarchy(appInfo, clazz);
-      }
-    }
-
-    private void populateInstantiatedHierarchy(AppInfo appInfo, DexClass clazz) {
-      if (clazz.superType != null) {
-        populateInstantiatedHierarchy(appInfo, clazz.superType, clazz);
-      }
-      for (DexType iface : clazz.interfaces.values) {
-        populateInstantiatedHierarchy(appInfo, iface, clazz);
-      }
-    }
-
-    private void populateInstantiatedHierarchy(AppInfo appInfo, DexType type, DexClass subtype) {
-      if (type == appInfo.dexItemFactory().objectType) {
-        return;
-      }
-      Set<DexClass> subtypes = instantiatedHierarchy.get(type);
-      if (subtypes != null) {
-        subtypes.add(subtype);
-        return;
-      }
-      // This is the first time an instantiation appears below 'type', recursively populate.
-      subtypes = Sets.newIdentityHashSet();
-      subtypes.add(subtype);
-      instantiatedHierarchy.put(type, subtypes);
-      populateInstantiatedHierarchy(appInfo, type);
-    }
-
-    Builder rewrittenWithLens(
-        ObjectAllocationInfoCollectionImpl objectAllocationInfos,
-        DexDefinitionSupplier definitions,
-        GraphLense lens) {
-      objectAllocationInfos.classesWithAllocationSiteTracking.forEach(
-          (clazz, allocationSitesForClass) -> {
-            DexType type = lens.lookupType(clazz.type);
-            if (type.isPrimitiveType()) {
-              return;
-            }
-            DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
-            assert rewrittenClass != null;
-            assert !classesWithAllocationSiteTracking.containsKey(rewrittenClass);
-            classesWithAllocationSiteTracking.put(
-                rewrittenClass,
-                LensUtils.rewrittenWithRenamedSignature(
-                    allocationSitesForClass, definitions, lens));
-          });
-      objectAllocationInfos.classesWithoutAllocationSiteTracking.forEach(
-          clazz -> {
-            DexType type = lens.lookupType(clazz.type);
-            if (type.isPrimitiveType()) {
-              return;
-            }
-            DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
-            assert rewrittenClass != null;
-            assert !classesWithAllocationSiteTracking.containsKey(rewrittenClass);
-            assert !classesWithoutAllocationSiteTracking.contains(rewrittenClass);
-            classesWithoutAllocationSiteTracking.add(rewrittenClass);
-          });
-      return this;
-    }
-
-    public ObjectAllocationInfoCollectionImpl build() {
-      return new ObjectAllocationInfoCollectionImpl(
-          classesWithAllocationSiteTracking, classesWithoutAllocationSiteTracking);
-    }
-  }
-
-  private static void internalForEachInstantiatedSubType(
+  public void forEachInstantiatedSubType(
       DexType type,
-      Consumer<DexProgramClass> subTypeConsumer,
-      Consumer<LambdaDescriptor> lambdaConsumer,
-      Map<DexType, Set<DexClass>> instantiatedHierarchy,
-      Map<DexType, List<LambdaDescriptor>> instantiatedLambdas,
-      Predicate<DexProgramClass> isInstantiatedDirectly,
+      Consumer<DexProgramClass> onClass,
+      Consumer<LambdaDescriptor> onLambda,
       AppInfo appInfo) {
     WorkList<DexClass> worklist = WorkList.newIdentityWorkList();
     if (type == appInfo.dexItemFactory().objectType) {
@@ -307,7 +157,7 @@
         // If no definition for the type is found, populate the worklist with any
         // instantiated subtypes and callback with any lambda instance.
         worklist.addIfNotSeen(instantiatedHierarchy.getOrDefault(type, Collections.emptySet()));
-        instantiatedLambdas.getOrDefault(type, Collections.emptyList()).forEach(lambdaConsumer);
+        instantiatedLambdas.getOrDefault(type, Collections.emptyList()).forEach(onLambda);
       } else {
         worklist.addIfNotSeen(initialClass);
       }
@@ -317,12 +167,287 @@
       DexClass clazz = worklist.next();
       if (clazz.isProgramClass()) {
         DexProgramClass programClass = clazz.asProgramClass();
-        if (isInstantiatedDirectly.test(programClass)) {
-          subTypeConsumer.accept(programClass);
+        if (isInstantiatedDirectly(programClass)
+            || isInterfaceWithUnknownSubtypeHierarchy(programClass)) {
+          onClass.accept(programClass);
         }
       }
       worklist.addIfNotSeen(instantiatedHierarchy.getOrDefault(clazz.type, Collections.emptySet()));
-      instantiatedLambdas.getOrDefault(clazz.type, Collections.emptyList()).forEach(lambdaConsumer);
+      instantiatedLambdas.getOrDefault(clazz.type, Collections.emptyList()).forEach(onLambda);
+    }
+  }
+
+  public static class Builder extends ObjectAllocationInfoCollectionImpl {
+
+    private static class Data {
+
+      private final boolean trackAllocationSites;
+      private final GraphReporter reporter;
+
+      private Data(boolean trackAllocationSites, GraphReporter reporter) {
+        this.trackAllocationSites = trackAllocationSites;
+        this.reporter = reporter;
+      }
+    }
+
+    // Pointer to data valid during the duration of the builder.
+    private Data data;
+
+    private Builder(boolean trackAllocationSites, GraphReporter reporter) {
+      data = new Data(trackAllocationSites, reporter);
+    }
+
+    public ObjectAllocationInfoCollectionImpl build(DexDefinitionSupplier definitions) {
+      assert data != null;
+      if (instantiatedHierarchy == null) {
+        repopulateInstantiatedHierarchy(definitions);
+      }
+      assert validate(definitions);
+      data = null;
+      return this;
+    }
+
+    // Consider a mutation interface that has just the mutation methods.
+    @Override
+    public void mutate(Consumer<Builder> mutator, AppInfo appInfo) {
+      mutator.accept(this);
+      repopulateInstantiatedHierarchy(appInfo);
+    }
+
+    private boolean shouldTrackAllocationSitesForClass(
+        DexProgramClass clazz, InstantiationReason instantiationReason) {
+      if (!data.trackAllocationSites) {
+        return false;
+      }
+      if (instantiationReason != InstantiationReason.NEW_INSTANCE_INSTRUCTION) {
+        // There is an allocation site which is not a new-instance instruction.
+        return false;
+      }
+      if (classesWithoutAllocationSiteTracking.contains(clazz)) {
+        // We already gave up on tracking the allocation sites for `clazz` previously.
+        return false;
+      }
+      // We currently only use allocation site information for instance field value propagation.
+      return !clazz.instanceFields().isEmpty();
+    }
+
+    /**
+     * Records that {@param clazz} is instantiated in {@param context}.
+     *
+     * @return true if {@param clazz} was not instantiated before.
+     */
+    public boolean recordDirectAllocationSite(
+        DexProgramClass clazz,
+        DexEncodedMethod context,
+        InstantiationReason instantiationReason,
+        KeepReason keepReason,
+        AppInfo appInfo) {
+      assert !clazz.isInterface();
+      if (data.reporter != null) {
+        data.reporter.registerClass(clazz, keepReason);
+      }
+      populateInstantiatedHierarchy(appInfo, clazz);
+      if (shouldTrackAllocationSitesForClass(clazz, instantiationReason)) {
+        assert context != null;
+        Set<DexEncodedMethod> allocationSitesForClass =
+            classesWithAllocationSiteTracking.computeIfAbsent(
+                clazz, ignore -> Sets.newIdentityHashSet());
+        allocationSitesForClass.add(context);
+        return allocationSitesForClass.size() == 1;
+      }
+      if (classesWithoutAllocationSiteTracking.add(clazz)) {
+        Set<DexEncodedMethod> allocationSitesForClass =
+            classesWithAllocationSiteTracking.remove(clazz);
+        return allocationSitesForClass == null;
+      }
+      return false;
+    }
+
+    public boolean recordInstantiatedInterface(DexProgramClass iface, AppInfo appInfo) {
+      assert iface.isInterface();
+      assert !iface.isAnnotation();
+      if (interfacesWithUnknownSubtypeHierarchy.add(iface)) {
+        populateInstantiatedHierarchy(appInfo, iface);
+        return true;
+      }
+      return false;
+    }
+
+    public void recordInstantiatedLambdaInterface(
+        DexType iface, LambdaDescriptor lambda, AppInfo appInfo) {
+      instantiatedLambdas.computeIfAbsent(iface, key -> new ArrayList<>()).add(lambda);
+      populateInstantiatedHierarchy(appInfo, iface);
+    }
+
+    private void repopulateInstantiatedHierarchy(DexDefinitionSupplier definitions) {
+      instantiatedHierarchy = new IdentityHashMap<>();
+      classesWithAllocationSiteTracking
+          .keySet()
+          .forEach(clazz -> populateInstantiatedHierarchy(definitions, clazz));
+      classesWithoutAllocationSiteTracking.forEach(
+          clazz -> populateInstantiatedHierarchy(definitions, clazz));
+      interfacesWithUnknownSubtypeHierarchy.forEach(
+          clazz -> populateInstantiatedHierarchy(definitions, clazz));
+      instantiatedLambdas
+          .keySet()
+          .forEach(type -> populateInstantiatedHierarchy(definitions, type));
+    }
+
+    private void populateInstantiatedHierarchy(DexDefinitionSupplier definitions, DexType type) {
+      DexClass clazz = definitions.definitionFor(type);
+      if (clazz != null) {
+        populateInstantiatedHierarchy(definitions, clazz);
+      }
+    }
+
+    private void populateInstantiatedHierarchy(DexDefinitionSupplier definitions, DexClass clazz) {
+      if (clazz.superType != null) {
+        populateInstantiatedHierarchy(definitions, clazz.superType, clazz);
+      }
+      for (DexType iface : clazz.interfaces.values) {
+        populateInstantiatedHierarchy(definitions, iface, clazz);
+      }
+    }
+
+    private void populateInstantiatedHierarchy(
+        DexDefinitionSupplier definitions, DexType type, DexClass subtype) {
+      if (type == definitions.dexItemFactory().objectType) {
+        return;
+      }
+      Set<DexClass> subtypes = instantiatedHierarchy.get(type);
+      if (subtypes != null) {
+        subtypes.add(subtype);
+        return;
+      }
+      // This is the first time an instantiation appears below 'type', recursively populate.
+      subtypes = Sets.newIdentityHashSet();
+      subtypes.add(subtype);
+      instantiatedHierarchy.put(type, subtypes);
+      populateInstantiatedHierarchy(definitions, type);
+    }
+
+    public void markNoLongerInstantiated(DexProgramClass clazz) {
+      classesWithAllocationSiteTracking.remove(clazz);
+      classesWithoutAllocationSiteTracking.remove(clazz);
+      instantiatedHierarchy = null;
+    }
+
+    Builder rewrittenWithLens(
+        ObjectAllocationInfoCollectionImpl objectAllocationInfos,
+        DexDefinitionSupplier definitions,
+        GraphLense lens) {
+      instantiatedHierarchy = null;
+      objectAllocationInfos.classesWithAllocationSiteTracking.forEach(
+          (clazz, allocationSitesForClass) -> {
+            DexType type = lens.lookupType(clazz.type);
+            if (type.isPrimitiveType()) {
+              assert !objectAllocationInfos.hasInstantiatedStrictSubtype(clazz);
+              return;
+            }
+            DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
+            assert rewrittenClass != null;
+            assert !classesWithAllocationSiteTracking.containsKey(rewrittenClass);
+            classesWithAllocationSiteTracking.put(
+                rewrittenClass,
+                LensUtils.rewrittenWithRenamedSignature(
+                    allocationSitesForClass, definitions, lens));
+          });
+      objectAllocationInfos.classesWithoutAllocationSiteTracking.forEach(
+          clazz -> {
+            DexType type = lens.lookupType(clazz.type);
+            if (type.isPrimitiveType()) {
+              assert !objectAllocationInfos.hasInstantiatedStrictSubtype(clazz);
+              return;
+            }
+            DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
+            assert rewrittenClass != null;
+            assert !classesWithAllocationSiteTracking.containsKey(rewrittenClass);
+            assert !classesWithoutAllocationSiteTracking.contains(rewrittenClass);
+            classesWithoutAllocationSiteTracking.add(rewrittenClass);
+          });
+      for (DexProgramClass abstractType :
+          objectAllocationInfos.interfacesWithUnknownSubtypeHierarchy) {
+        DexType type = lens.lookupType(abstractType.type);
+        if (type.isPrimitiveType()) {
+          assert false;
+          continue;
+        }
+        DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
+        assert rewrittenClass != null;
+        assert !interfacesWithUnknownSubtypeHierarchy.contains(rewrittenClass);
+        interfacesWithUnknownSubtypeHierarchy.add(rewrittenClass);
+      }
+      objectAllocationInfos.instantiatedLambdas.forEach(
+          (iface, lambdas) -> {
+            DexType type = lens.lookupType(iface);
+            if (type.isPrimitiveType()) {
+              assert false;
+              return;
+            }
+            assert !instantiatedLambdas.containsKey(type);
+            // TODO(b/150277553): Rewrite lambda descriptor.
+            instantiatedLambdas.put(type, lambdas);
+          });
+      return this;
+    }
+
+    // Validation that all types are linked in the instantiated hierarchy map.
+    boolean validate(DexDefinitionSupplier definitions) {
+      classesWithAllocationSiteTracking.forEach(
+          (clazz, contexts) -> {
+            assert !clazz.isInterface();
+            assert !classesWithoutAllocationSiteTracking.contains(clazz);
+            assert verifyAllSuperTypesAreInHierarchy(definitions, clazz.allImmediateSupertypes());
+          });
+      classesWithoutAllocationSiteTracking.forEach(
+          clazz -> {
+            assert !clazz.isInterface();
+            assert !classesWithAllocationSiteTracking.containsKey(clazz);
+            assert verifyAllSuperTypesAreInHierarchy(definitions, clazz.allImmediateSupertypes());
+          });
+      instantiatedLambdas.forEach(
+          (iface, lambdas) -> {
+            assert !lambdas.isEmpty();
+            DexClass definition = definitions.definitionFor(iface);
+            if (definition != null) {
+              assert definition.isInterface();
+              assert verifyAllSuperTypesAreInHierarchy(
+                  definitions, definition.allImmediateSupertypes());
+            }
+          });
+      for (DexProgramClass iface : interfacesWithUnknownSubtypeHierarchy) {
+        verifyAllSuperTypesAreInHierarchy(definitions, iface.allImmediateSupertypes());
+      }
+      instantiatedHierarchy.forEach(
+          (type, subtypes) -> {
+            assert !subtypes.isEmpty();
+            for (DexClass subtype : subtypes) {
+              assert isImmediateSuperType(type, subtype);
+            }
+          });
+      return true;
+    }
+
+    private boolean verifyAllSuperTypesAreInHierarchy(
+        DexDefinitionSupplier definitions, Iterable<DexType> dexTypes) {
+      for (DexType supertype : dexTypes) {
+        assert typeIsInHierarchy(definitions, supertype);
+      }
+      return true;
+    }
+
+    private boolean typeIsInHierarchy(DexDefinitionSupplier definitions, DexType type) {
+      return type == definitions.dexItemFactory().objectType
+          || instantiatedHierarchy.containsKey(type);
+    }
+
+    private boolean isImmediateSuperType(DexType type, DexClass subtype) {
+      for (DexType supertype : subtype.allImmediateSupertypes()) {
+        if (type == supertype) {
+          return true;
+        }
+      }
+      return false;
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index e8cae8d..283777f 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -45,6 +45,10 @@
     return false;
   }
 
+  public boolean isIncompatibleClassChangeErrorResult() {
+    return false;
+  }
+
   /** Returns non-null if isFailedResolution() is true, otherwise null. */
   public FailedResolutionResult asFailedResolution() {
     return null;
@@ -101,7 +105,7 @@
       InstantiatedObject instance, AppInfoWithClassHierarchy appInfo);
 
   public abstract DexClassAndMethod lookupVirtualDispatchTarget(
-      DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo);
+      DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo);
 
   public abstract LookupTarget lookupVirtualDispatchTarget(
       LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo);
@@ -119,12 +123,12 @@
       assert initialResolutionHolder != null;
       assert resolvedHolder != null;
       assert resolvedMethod != null;
-      assert resolvedHolder.type == resolvedMethod.method.holder;
+      assert resolvedHolder.type == resolvedMethod.holder();
       this.resolvedHolder = resolvedHolder;
       this.resolvedMethod = resolvedMethod;
       this.initialResolutionHolder = initialResolutionHolder;
       assert !resolvedMethod.isPrivateMethod()
-          || initialResolutionHolder.type == resolvedMethod.method.holder;
+          || initialResolutionHolder.type == resolvedMethod.holder();
     }
 
     public DexClass getResolvedHolder() {
@@ -324,7 +328,7 @@
       // It appears as if this check is also in place for non-initializer methods too.
       // See NestInvokeSpecialMethodAccessWithIntermediateTest.
       if ((target.isInstanceInitializer() || target.isPrivateMethod())
-          && target.method.holder != symbolicReference.type) {
+          && target.holder() != symbolicReference.type) {
         return null;
       }
       // Runtime exceptions:
@@ -440,7 +444,7 @@
         Consumer<DexProgramClass> lambdaInstantiatedConsumer =
             subType -> {
               subTypeConsumer.accept(subType);
-              if (appInfo.hasAnyInstantiatedLambdas(subType)) {
+              if (appInfo.isInstantiatedInterface(subType)) {
                 hasInstantiatedLambdas.set(true);
               }
             };
@@ -516,7 +520,7 @@
 
     @Override
     public DexClassAndMethod lookupVirtualDispatchTarget(
-        DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
+        DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
       return lookupVirtualDispatchTarget(dynamicInstance, appInfo, initialResolutionHolder.type);
     }
 
@@ -542,9 +546,7 @@
     }
 
     private DexClassAndMethod lookupVirtualDispatchTarget(
-        DexProgramClass dynamicInstance,
-        AppInfoWithClassHierarchy appInfo,
-        DexType resolutionHolder) {
+        DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo, DexType resolutionHolder) {
       assert appInfo.isSubtype(dynamicInstance.type, resolutionHolder)
           : dynamicInstance.type + " is not a subtype of " + resolutionHolder;
       // TODO(b/148591377): Enable this assertion.
@@ -567,7 +569,7 @@
         }
         if (candidate == null || candidate == DexEncodedMethod.SENTINEL) {
           // We cannot find a target above the resolved method.
-          if (current.type == overrideTarget.method.holder) {
+          if (current.type == overrideTarget.holder()) {
             return null;
           }
           current = current.superType == null ? null : appInfo.definitionFor(current.superType);
@@ -584,7 +586,7 @@
     }
 
     private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
-        DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
+        DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
       return appInfo.lookupMaximallySpecificMethod(dynamicInstance, resolvedMethod.method);
     }
 
@@ -646,7 +648,7 @@
       }
       // For package private methods, a valid override has to be inside the package.
       assert resolvedMethod.accessFlags.isPackagePrivate();
-      return resolvedMethod.method.holder.isSamePackage(candidate.method.holder);
+      return resolvedMethod.holder().isSamePackage(candidate.holder());
     }
   }
 
@@ -702,7 +704,7 @@
 
     @Override
     public DexClassAndMethod lookupVirtualDispatchTarget(
-        DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
+        DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
       return null;
     }
 
@@ -808,6 +810,11 @@
           ? INSTANCE
           : new IncompatibleClassResult(methodsCausingError);
     }
+
+    @Override
+    public boolean isIncompatibleClassChangeErrorResult() {
+      return true;
+    }
   }
 
   public static class NoSuchMethodResult extends FailedResolutionResult {
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
index fdb4dc1..061cbcf 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -127,7 +127,7 @@
   }
 
   private boolean hasKotlincClinitAssertionCode(DexEncodedMethod method) {
-    if (method.method.holder == dexItemFactory.kotlin.kotlinAssertions) {
+    if (method.holder() == dexItemFactory.kotlin.kotlinAssertions) {
       CfCode code = method.getCode().asCfCode();
       for (int i = 1; i < code.instructions.size(); i++) {
         CfInstruction instruction = code.instructions.get(i - 1);
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
index f32143d..7ecd527 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
@@ -74,7 +74,7 @@
 
     // Record that the enclosing class is guaranteed to be initialized at the allocation site.
     AppInfoWithSubtyping appInfo = appView.appInfo();
-    DexType guaranteedToBeInitialized = context.method.holder;
+    DexType guaranteedToBeInitialized = context.holder();
     DexType existingGuaranteedToBeInitialized =
         mapping.getOrDefault(key, guaranteedToBeInitialized);
     mapping.put(
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
index 237f9ab..944d680 100644
--- a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
@@ -39,6 +39,10 @@
     return mergedClasses.containsKey(type);
   }
 
+  public boolean isTarget(DexType type) {
+    return !getSourcesFor(type).isEmpty();
+  }
+
   @Override
   public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
     for (List<DexType> sourcesForTarget : sources.values()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index 7167e72..94ee9cf 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -97,7 +97,7 @@
   }
 
   public boolean isClassDefinitelyLoadedBeforeInstruction(DexType type, Instruction instruction) {
-    DexType context = code.method.method.holder;
+    DexType context = code.method.holder();
     BasicBlock block = instruction.getBlock();
 
     // Visit the instructions in `block` prior to `instruction`.
@@ -335,7 +335,7 @@
       if (!resolutionResult.isSingleResolution()) {
         return false;
       }
-      DexType holder = resolutionResult.getSingleTarget().method.holder;
+      DexType holder = resolutionResult.getSingleTarget().holder();
       return appView.isSubtype(holder, type).isTrue();
     }
 
@@ -394,7 +394,7 @@
       if (!resolutionResult.isSingleResolution()) {
         return false;
       }
-      DexType holder = resolutionResult.getSingleTarget().method.holder;
+      DexType holder = resolutionResult.getSingleTarget().holder();
       return appView.isSubtype(holder, type).isTrue();
     }
 
@@ -430,7 +430,7 @@
       if (!resolutionResult.isSingleResolution()) {
         return false;
       }
-      DexType holder = resolutionResult.getSingleTarget().method.holder;
+      DexType holder = resolutionResult.getSingleTarget().holder();
       return appView.isSubtype(holder, type).isTrue();
     }
 
@@ -505,11 +505,11 @@
         enqueue(clazz.type, visited, worklist);
       } else if (definition.isDexEncodedField()) {
         DexEncodedField field = definition.asDexEncodedField();
-        enqueue(field.field.holder, visited, worklist);
+        enqueue(field.holder(), visited, worklist);
       } else if (definition.isDexEncodedMethod()) {
         assert instruction.isInvokeMethod();
         DexEncodedMethod method = definition.asDexEncodedMethod();
-        enqueue(method.method.holder, visited, worklist);
+        enqueue(method.holder(), visited, worklist);
         enqueueInitializedClassesOnNormalExit(method, instruction.inValues(), visited, worklist);
       } else {
         assert false;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java
index 0fc8bd0..9ae6c86 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java
@@ -37,7 +37,7 @@
       }
       if (instr.isInvokeMethod()) {
         DexEncodedMethod target =
-            instr.asInvokeMethod().lookupSingleTarget(appView, code.method.method.holder);
+            instr.asInvokeMethod().lookupSingleTarget(appView, code.method.holder());
         if (target != null && target.getOptimizationInfo().returnValueOnlyDependsOnArguments()) {
           continue;
         }
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 f5cd48d..406c9e6 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
@@ -36,7 +36,7 @@
   public static Set<DexType> computeInitializedClassesOnNormalExit(
       AppView<AppInfoWithLiveness> appView, IRCode code) {
     DominatorTree dominatorTree = new DominatorTree(code, Assumption.MAY_HAVE_UNREACHABLE_BLOCKS);
-    Visitor visitor = new Visitor(appView, code.method.method.holder);
+    Visitor visitor = new Visitor(appView, code.method.holder());
     for (BasicBlock dominator : dominatorTree.normalExitDominatorBlocks()) {
       if (dominator.hasCatchHandlers()) {
         // When determining which classes that are guaranteed to be initialized from a given
@@ -115,8 +115,8 @@
     public Void handleFieldInstruction(FieldInstruction instruction) {
       DexEncodedField field = appView.appInfo().resolveField(instruction.getField());
       if (field != null) {
-        if (field.field.holder.isClassType()) {
-          markInitializedOnNormalExit(field.field.holder);
+        if (field.holder().isClassType()) {
+          markInitializedOnNormalExit(field.holder());
         } else {
           assert false : "Expected holder of field type to be a class type";
         }
@@ -132,7 +132,7 @@
         if (method.holder.isClassType()) {
           DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context);
           if (singleTarget != null) {
-            markInitializedOnNormalExit(singleTarget.method.holder);
+            markInitializedOnNormalExit(singleTarget.holder());
             markInitializedOnNormalExit(
                 singleTarget.getOptimizationInfo().getInitializedClassesOnNormalExit());
           } else {
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 9fcee4a..05416df 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
@@ -84,7 +84,7 @@
   public ValueMayDependOnEnvironmentAnalysis(AppView<?> appView, IRCode code) {
     this.appView = appView;
     this.code = code;
-    this.context = code.method.method.holder;
+    this.context = code.method.holder();
   }
 
   public boolean valueMayDependOnEnvironment(Value value) {
@@ -433,7 +433,7 @@
       if (definition.isStaticGet()) {
         StaticGet staticGet = definition.asStaticGet();
         DexEncodedField field = appView.appInfo().resolveField(staticGet.getField());
-        if (field != null && field.field.holder == context) {
+        if (field != null && field.holder() == context) {
           List<StaticPut> finalFieldPuts = computeFinalFieldPuts().get(field);
           if (finalFieldPuts == null || finalFieldPuts.size() != 1) {
             return false;
@@ -452,7 +452,7 @@
       finalFieldPuts = new IdentityHashMap<>();
       for (StaticPut staticPut : code.<StaticPut>instructions(Instruction::isStaticPut)) {
         DexEncodedField field = appView.appInfo().resolveField(staticPut.getField());
-        if (field != null && field.field.holder == context && field.isFinal()) {
+        if (field != null && field.holder() == context && field.isFinal()) {
           finalFieldPuts.computeIfAbsent(field, ignore -> new ArrayList<>()).add(staticPut);
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index d5bd10a..75d5423 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -148,7 +148,7 @@
       return;
     }
 
-    DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context.method.holder);
+    DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context.holder());
     if (singleTarget == null) {
       // We just lost track.
       abstractInstanceFieldValues.remove(clazz);
@@ -173,7 +173,7 @@
               initializationInfo.asArgumentInitializationInfo();
           Value argument = invoke.arguments().get(argumentInitializationInfo.getArgumentIndex());
           AbstractValue abstractValue =
-              entry.getValue().join(argument.getAbstractValue(appView, context.method.holder));
+              entry.getValue().join(argument.getAbstractValue(appView, context.holder()));
           assert !abstractValue.isBottom();
           if (!abstractValue.isUnknown()) {
             entry.setValue(abstractValue);
@@ -294,7 +294,7 @@
     if (abstractValue.isUnknown()) {
       return true;
     }
-    assert abstractValue == value.getAbstractValue(appView, context.method.holder);
+    assert abstractValue == value.getAbstractValue(appView, context.holder());
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index af990b1..08989c0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -49,7 +49,7 @@
       DexProgramClass clazz,
       DexEncodedMethod method) {
     assert clazz != null;
-    assert clazz.type == method.method.holder;
+    assert clazz.type == method.holder();
     this.appView = appView;
     this.clazz = clazz;
     this.code = code;
@@ -129,7 +129,7 @@
 
     // Then check if any of the instructions that precede the given instruction in the current block
     // may read the field.
-    DexType context = method.method.holder;
+    DexType context = method.holder();
     InstructionIterator instructionIterator = block.iterator();
     while (instructionIterator.hasNext()) {
       Instruction current = instructionIterator.next();
@@ -164,7 +164,7 @@
    * and its transitive predecessors.
    */
   private Map<BasicBlock, AbstractFieldSet> createFieldsMaybeReadBeforeBlockInclusive() {
-    DexType context = method.method.holder;
+    DexType context = method.holder();
     Map<BasicBlock, AbstractFieldSet> result = new IdentityHashMap<>();
     Deque<BasicBlock> worklist = DequeUtils.newArrayDeque(code.entryBlock());
     while (!worklist.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
index 3aec803..58b4194 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -86,7 +86,7 @@
     assert appView.enableWholeProgramOptimizations();
     assert method.isInstanceInitializer();
 
-    DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
+    DexProgramClass clazz = appView.definitionFor(method.holder()).asProgramClass();
     if (!appView.options().enableValuePropagationForInstanceFields) {
       return EmptyInstanceFieldInitializationInfoCollection.getInstance();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 4d57a5e..e2ab677 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -53,7 +53,7 @@
     assert appView.enableWholeProgramOptimizations();
     assert method.isClassInitializer();
     timing.begin("Analyze class initializer");
-    DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
+    DexProgramClass clazz = appView.definitionFor(method.holder()).asProgramClass();
     new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method)
         .computeFieldOptimizationInfo(classInitializerDefaultsResult);
     timing.end();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index 2a70440..08b29ce 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -130,7 +130,7 @@
    */
   public void rewriteCode(DexEncodedMethod method, IRCode code) {
     if (method.isClassInitializer()
-        && classesWithRemovedExtensionFields.contains(method.method.holder)
+        && classesWithRemovedExtensionFields.contains(method.holder())
         && code.metadata().mayHaveStaticPut()) {
       rewriteClassInitializer(code);
     }
@@ -204,7 +204,7 @@
       return false;
     }
 
-    DexClass clazz = appView.definitionFor(encodedField.field.holder);
+    DexClass clazz = appView.definitionFor(encodedField.holder());
     if (clazz == null || !clazz.isProgramClass()) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index feca06c..0801f16 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -36,6 +36,7 @@
 import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -64,11 +65,16 @@
 
   /** Returns true if an action was deferred. */
   public boolean deferDeadProtoBuilders(
-      DexProgramClass clazz, DexEncodedMethod context, BooleanSupplier register) {
+      DexProgramClass clazz,
+      DexEncodedMethod context,
+      BooleanSupplier register,
+      Enqueuer enqueuer) {
     if (references.isDynamicMethod(context) && references.isGeneratedMessageLiteBuilder(clazz)) {
       if (register.getAsBoolean()) {
-        assert builders.getOrDefault(clazz, context) == context;
-        builders.put(clazz, context);
+        if (enqueuer.getMode().isFinalTreeShaking()) {
+          assert builders.getOrDefault(clazz, context) == context;
+          builders.put(clazz, context);
+        }
         return true;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 390f2b2..b2d813a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -99,7 +99,7 @@
    * newMessageInfo is still pending.
    */
   private void rewriteDynamicMethod(DexEncodedMethod method, IRCode code) {
-    DexClass context = appView.definitionFor(method.method.holder);
+    DexClass context = appView.definitionFor(method.holder());
     if (context == null || !context.isProgramClass()) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
index 71750f1..79dea35 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
@@ -43,6 +43,7 @@
   @Override
   public boolean registerConstClass(DexType type) {
     if (references.isDynamicMethod(getContextMethod())) {
+      enqueuer.addDeadProtoTypeCandidate(type);
       return false;
     }
     return super.registerConstClass(type);
@@ -58,6 +59,7 @@
   @Override
   public boolean registerStaticFieldRead(DexField field) {
     if (references.isDynamicMethod(getContextMethod())) {
+      enqueuer.addDeadProtoTypeCandidate(field.holder);
       return false;
     }
     return super.registerStaticFieldRead(field);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
index de37f38..1321984 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
@@ -36,7 +36,7 @@
   @Override
   public Reason computeInliningReason(
       InvokeMethod invoke, DexEncodedMethod target, DexEncodedMethod context) {
-    DexProgramClass enclosingClass = appView.definitionFor(context.method.holder).asProgramClass();
+    DexProgramClass enclosingClass = appView.definitionFor(context.holder()).asProgramClass();
     if (references.isAbstractGeneratedMessageLiteBuilder(enclosingClass)
         && invoke.isInvokeSuper()) {
       // Aggressively inline invoke-super calls inside the GeneratedMessageLite builders. Such
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index e414f23..d6d2990 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -135,7 +135,7 @@
       return;
     }
 
-    DexType holder = encodedMethod.method.holder;
+    DexType holder = encodedMethod.holder();
     if (seenButNotLiveProtos.containsKey(holder)) {
       // The proto is now live instead of dead.
       liveProtos.put(holder, seenButNotLiveProtos.remove(holder));
@@ -150,7 +150,7 @@
 
   private void createProtoMessageInfoFromDynamicMethod(
       DexEncodedMethod dynamicMethod, Map<DexType, ProtoMessageInfo> protos) {
-    DexType holder = dynamicMethod.method.holder;
+    DexType holder = dynamicMethod.holder();
     assert !protos.containsKey(holder);
 
     DexClass context = appView.definitionFor(holder);
@@ -254,7 +254,7 @@
     for (DexEncodedMethod findLiteExtensionByNumberMethod : findLiteExtensionByNumberMethods) {
       IRCode code =
           findLiteExtensionByNumberMethod.buildIR(
-              appView, appView.appInfo().originFor(findLiteExtensionByNumberMethod.method.holder));
+              appView, appView.appInfo().originFor(findLiteExtensionByNumberMethod.holder()));
       for (BasicBlock block : code.blocks(BasicBlock::isReturnBlock)) {
         Value returnValue = block.exit().asReturn().returnValue().getAliasedValue();
         if (returnValue.isPhi()) {
@@ -275,7 +275,7 @@
             continue;
           }
 
-          DexProgramClass holder = asProgramClassOrNull(appView.definitionFor(field.field.holder));
+          DexProgramClass holder = asProgramClassOrNull(appView.definitionFor(field.holder()));
           if (holder == null) {
             assert false;
             continue;
@@ -365,7 +365,7 @@
       }
 
       DexEncodedMethod dynamicMethod = protoMessageInfo.getDynamicMethod();
-      DexProgramClass clazz = appView.definitionFor(dynamicMethod.method.holder).asProgramClass();
+      DexProgramClass clazz = appView.definitionFor(dynamicMethod.holder()).asProgramClass();
 
       for (ProtoFieldInfo protoFieldInfo : protoMessageInfo.getFields()) {
         DexEncodedField valueStorage = protoFieldInfo.getValueStorage(appView, protoMessageInfo);
@@ -526,7 +526,7 @@
       return;
     }
 
-    DexClass clazz = appView.definitionFor(encodedOneOfCaseField.field.holder);
+    DexClass clazz = appView.definitionFor(encodedOneOfCaseField.holder());
     if (clazz == null || !clazz.isProgramClass()) {
       assert false;
       return;
@@ -555,7 +555,7 @@
       return;
     }
 
-    if (encodedOneOfField.field.holder != encodedOneOfCaseField.field.holder) {
+    if (encodedOneOfField.holder() != encodedOneOfCaseField.holder()) {
       assert false;
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
index ac88305..f026295 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
@@ -220,7 +220,7 @@
   }
 
   public DexType getType() {
-    return dynamicMethod.method.holder;
+    return dynamicMethod.holder();
   }
 
   public boolean hasFields() {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
index 3449c30..0d500e9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
@@ -39,7 +39,7 @@
    */
   public static ClassInitializerSideEffect classInitializerCanBePostponed(
       AppView<?> appView, IRCode code) {
-    DexType context = code.method.method.holder;
+    DexType context = code.method.holder();
     OptionalBool controlFlowMayDependOnEnvironment = OptionalBool.unknown();
     boolean mayHaveSideEffects = false;
 
@@ -113,7 +113,7 @@
         StaticPut staticPut = instruction.asStaticPut();
         DexEncodedField field = appView.appInfo().resolveField(staticPut.getField());
         if (field == null
-            || field.field.holder != context
+            || field.holder() != context
             || environmentAnalysis.valueMayDependOnEnvironment(staticPut.value())
             || instruction.instructionInstanceCanThrow(appView, context).isThrowing()) {
           return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index 34a83d1..3f5640d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -72,7 +72,7 @@
     Value returnedValue =
         code.createValue(classClassType(appView, definitelyNotNull()), debugLocalInfo);
     ConstClass instruction = new ConstClass(returnedValue, type);
-    assert !instruction.instructionMayHaveSideEffects(appView, code.method.method.holder);
+    assert !instruction.instructionMayHaveSideEffects(appView, code.method.holder());
     return instruction;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index ef38879..855694a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -85,7 +85,7 @@
   public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
     DexEncodedField encodedField = appView.appInfo().resolveField(field);
     return isMemberVisibleFromOriginalContext(
-        appView, context, encodedField.field.holder, encodedField.accessFlags);
+        appView, context, encodedField.holder(), encodedField.accessFlags);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index dc0b133..0994dc0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -90,7 +90,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index f4a8a14..cd29ec9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -192,7 +192,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   @Override
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 6611bc7..9fb9f27 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
@@ -279,7 +279,7 @@
     assert super.verifyTypes(appView);
 
     TypeElement inType = src().getType();
-    TypeElement outType = outValue().getType();
+    TypeElement outType = getOutType();
     if (isAssumeNone() || isAssumeDynamicType()) {
       assert inType.isReferenceType() : inType;
       assert outType.equals(inType)
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 37d9b9e..52aed30 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -540,6 +540,10 @@
     return phis;
   }
 
+  public boolean isEntry() {
+    return getPredecessors().isEmpty();
+  }
+
   public boolean isFilled() {
     return filled;
   }
@@ -1750,7 +1754,7 @@
     if (hasMoveException) {
       // Remove the move-exception instruction.
       move = entry().asMoveException();
-      exceptionTypeLattice = move.outValue().getType();
+      exceptionTypeLattice = move.getOutType();
       exceptionType = move.getExceptionType();
       assert move.getDebugValues().isEmpty();
       getInstructions().remove(0);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 4e5c923..1537f85 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -228,7 +228,7 @@
       throw new IllegalStateException();
     }
 
-    assert !current.hasOutValue() || current.outValue().getType().isInt();
+    assert !current.hasOutValue() || current.getOutType().isInt();
 
     // Replace the instruction by const-number.
     ConstNumber constNumber = code.createIntConstant(value, current.getLocalInfo());
@@ -250,7 +250,7 @@
 
     // Replace the instruction by static-get.
     TypeElement newType = TypeElement.fromDexType(field.type, maybeNull(), appView);
-    TypeElement oldType = current.hasOutValue() ? current.outValue().getType() : null;
+    TypeElement oldType = current.getOutType();
     Value value = code.createValue(newType, current.getLocalInfo());
     StaticGet staticGet = new StaticGet(value, field);
     for (Value inValue : current.inValues()) {
@@ -495,8 +495,8 @@
       Set<BasicBlock> blocksToRemove,
       DexType downcast) {
     assert blocksToRemove != null;
-    DexType codeHolder = code.method.method.holder;
-    DexType inlineeHolder = inlinee.method.method.holder;
+    DexType codeHolder = code.method.holder();
+    DexType inlineeHolder = inlinee.method.holder();
     if (codeHolder != inlineeHolder && inlinee.method.isOnlyInlinedIntoNestMembers()) {
       // Should rewrite private calls to virtual calls.
       assert NestUtils.sameNest(codeHolder, inlineeHolder, appView);
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 2d7e409..da952bd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -171,7 +171,7 @@
 
     assert inType.isPreciseType();
 
-    TypeElement outType = outValue().getType();
+    TypeElement outType = getOutType();
     TypeElement castType = TypeElement.fromDexType(getType(), inType.nullability(), appView);
 
     if (inType.lessThanOrEqual(castType, appView)) {
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 e77f315..bb5788b 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
@@ -44,10 +44,7 @@
 
   public static ConstClass copyOf(IRCode code, ConstClass original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outValue().getType(),
-            original.getLocalInfo());
+        new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
@@ -142,7 +139,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
index 996bf38..a390b3f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -38,10 +38,7 @@
 
   public static ConstMethodHandle copyOf(IRCode code, ConstMethodHandle original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outValue().getType(),
-            original.getLocalInfo());
+        new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
index 6cd4d0b..774475d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
@@ -38,10 +38,7 @@
 
   public static ConstMethodType copyOf(IRCode code, ConstMethodType original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outValue().getType(),
-            original.getLocalInfo());
+        new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 2683b66..06aef0a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -55,10 +55,7 @@
 
   public static ConstNumber copyOf(IRCode code, ConstNumber original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outValue().getType(),
-            original.getLocalInfo());
+        new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
@@ -251,7 +248,7 @@
   @Override
   public String toString() {
     if (outValue != null) {
-      return super.toString() + " " + value + " (" + outValue().getType() + ")";
+      return super.toString() + " " + value + " (" + getOutType() + ")";
     } else {
       return super.toString() + " " + value + " (dead)";
     }
@@ -316,13 +313,13 @@
 
   @Override
   public TypeElement evaluate(AppView<?> appView) {
-    return outValue().getType();
+    return getOutType();
   }
 
   @Override
   public boolean verifyTypes(AppView<?> appView) {
     assert super.verifyTypes(appView);
-    assert !isZero() || outValue().getType().isPrimitiveType() || outValue().getType().isNullType();
+    assert !isZero() || getOutType().isPrimitiveType() || getOutType().isNullType();
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index c98d8b7..a90a67e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -42,10 +42,7 @@
 
   public static ConstString copyOf(IRCode code, ConstString original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outValue().getType(),
-            original.getLocalInfo());
+        new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
index 5884660..f8a36db 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
@@ -79,7 +79,7 @@
   @Override
   public boolean verifyTypes(AppView<?> appView) {
     super.verifyTypes(appView);
-    assert src().getType().lessThanOrEqual(outValue().getType(), appView);
+    assert src().getType().lessThanOrEqual(getOutType(), appView);
     return true;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 70ebd80..bc29749 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -48,10 +48,7 @@
 
   public static DexItemBasedConstString copyOf(IRCode code, DexItemBasedConstString original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outValue().getType(),
-            original.getLocalInfo());
+        new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index d7e0067..18f8326 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -232,7 +232,7 @@
     assert isFieldGet();
     DexEncodedField field = appView.appInfo().resolveField(getField());
     if (field != null) {
-      DexClass holder = appView.definitionFor(field.field.holder);
+      DexClass holder = appView.definitionFor(field.holder());
       if (holder != null && holder.isLibraryClass() && field.isStatic() && field.isFinal()) {
         return appView.abstractValueFactory().createSingleFieldValue(field.field);
       }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index 5c488c4..e46af2c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -116,6 +116,10 @@
     return result;
   }
 
+  public boolean mayHaveInitClass() {
+    return get(Opcodes.INIT_CLASS);
+  }
+
   public boolean mayHaveInstanceGet() {
     return get(Opcodes.INSTANCE_GET);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index da0592d..42068de 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -199,14 +199,14 @@
 
   public BasicBlock targetFromCondition(ConstNumber value) {
     assert isZeroTest();
-    assert verifyTypeCompatible(value.outValue().getType(), type);
+    assert verifyTypeCompatible(value.getOutType(), type);
     return targetFromCondition(Long.signum(value.getRawValue()));
   }
 
   public BasicBlock targetFromCondition(ConstNumber left, ConstNumber right) {
     assert !isZeroTest();
     assert left.outType() == right.outType();
-    assert verifyTypeCompatible(left.outValue().getType(), type);
+    assert verifyTypeCompatible(left.getOutType(), type);
     return targetFromCondition(Long.signum(left.getRawValue() - right.getRawValue()));
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 6edc6da..0740db4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -127,7 +127,7 @@
     // * IncompatibleClassChangeError (instance-* instruction for static fields)
     // * IllegalAccessError (not visible from the access context)
     // * NullPointerException (null receiver)
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 116a3e6..58bcd28 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -142,7 +142,7 @@
     // * IllegalAccessError (not visible from the access context)
     // * NullPointerException (null receiver)
     // * not read at all
-    boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method.method.holder);
+    boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method.holder());
     assert appView.enableWholeProgramOptimizations() || haveSideEffects
         : "Expected instance-put instruction to have side effects in D8";
     return !haveSideEffects;
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 654c2e8..b280728 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
@@ -593,7 +593,7 @@
   /** Returns true is this instruction can be treated as dead code if its outputs are not used. */
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
     // TODO(b/129530569): instructions with fine-grained side effect analysis may use:
-    // return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    // return !instructionMayHaveSideEffects(appView, code.method.holder());
     return !instructionInstanceCanThrow();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index ddc8952..d6aaf36 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -190,12 +190,12 @@
 
     // Verify that the target method is accessible in the current context.
     if (!isMemberVisibleFromOriginalContext(
-        appView, context, target.method.holder, target.accessFlags)) {
+        appView, context, target.holder(), target.accessFlags)) {
       return true;
     }
 
     // Verify that the target method does not have side-effects.
-    DexClass clazz = appView.definitionFor(target.method.holder);
+    DexClass clazz = appView.definitionFor(target.holder());
     if (clazz == null) {
       assert false : "Expected to be able to find the enclosing class of a method definition";
       return true;
@@ -218,7 +218,7 @@
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
     DexEncodedMethod method = code.method;
-    if (instructionMayHaveSideEffects(appView, method.method.holder)) {
+    if (instructionMayHaveSideEffects(appView, method.holder())) {
       return false;
     }
 
@@ -236,7 +236,7 @@
           if (appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())
               && invoke.getReceiver() == getReceiver()) {
             // If another constructor call than `this` is found, then it must not have side effects.
-            if (invoke.instructionMayHaveSideEffects(appView, method.method.holder)) {
+            if (invoke.instructionMayHaveSideEffects(appView, method.holder())) {
               return false;
             }
             if (otherInitCalls == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 9b708bc..63f6a89 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -118,9 +118,7 @@
             methodTarget -> {
               DexEncodedMethod target = methodTarget.getMethod();
               if (target == refinedTarget
-                  || appView
-                      .isSubtype(target.method.holder, refinedReceiverType)
-                      .isPossiblyTrue()) {
+                  || appView.isSubtype(target.holder(), refinedReceiverType).isPossiblyTrue()) {
                 result.add(target);
               }
             },
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index a71d185..41fac8c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -184,7 +184,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 7992a1c..b285dd6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -197,7 +197,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 2d29972..01a6a76 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -183,7 +183,7 @@
 
       // Verify that the target method is accessible in the current context.
       if (!isMemberVisibleFromOriginalContext(
-          appView, context, target.method.holder, target.accessFlags)) {
+          appView, context, target.holder(), target.accessFlags)) {
         return true;
       }
 
@@ -200,12 +200,14 @@
         return false;
       }
 
-      return target.method.holder.classInitializationMayHaveSideEffects(
-          appView,
-          // Types that are a super type of `context` are guaranteed to be initialized
-          // already.
-          type -> appView.isSubtype(context, type).isTrue(),
-          Sets.newIdentityHashSet());
+      return target
+          .holder()
+          .classInitializationMayHaveSideEffects(
+              appView,
+              // Types that are a super type of `context` are guaranteed to be initialized
+              // already.
+              type -> appView.isSubtype(context, type).isTrue(),
+              Sets.newIdentityHashSet());
     }
 
     return true;
@@ -213,6 +215,6 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 711c911..c30a8cb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -180,7 +180,7 @@
 
       // Verify that the target method is accessible in the current context.
       if (!isMemberVisibleFromOriginalContext(
-          appView, context, target.method.holder, target.accessFlags)) {
+          appView, context, target.holder(), target.accessFlags)) {
         return true;
       }
 
@@ -200,6 +200,6 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index adcc848..24e831c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -73,7 +73,7 @@
 
   @Override
   public String toString() {
-    return super.toString() + " (" + outValue().getType() + ")";
+    return super.toString() + " (" + getOutType() + ")";
   }
 
   @Override
@@ -128,7 +128,7 @@
     super.verifyTypes(appView);
     // DebugLocalWrite defines it's own verification of types but should be allowed to call super.
     if (!this.isDebugLocalWrite()) {
-      assert src().getType().equals(outValue().getType());
+      assert src().getType().equals(getOutType());
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index 2211a59..87643df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -151,6 +151,6 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 }
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 d8acd0b..1090554 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
@@ -196,7 +196,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   public void markNoSpilling() {
@@ -226,7 +226,7 @@
 
   @Override
   public boolean verifyTypes(AppView<?> appView) {
-    TypeElement type = outValue().getType();
+    TypeElement type = getOutType();
     assert type.isClassType();
     assert type.asClassType().getClassType() == clazz || appView.options().testing.allowTypeErrors;
     assert type.isDefinitelyNotNull();
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 2c246f92..84a86f2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -38,10 +38,7 @@
 
   public static StaticGet copyOf(IRCode code, StaticGet original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outValue().getType(),
-            original.getLocalInfo());
+        new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
@@ -156,7 +153,7 @@
     // * IncompatibleClassChangeError (static-* instruction for instance fields)
     // * IllegalAccessError (not visible from the access context)
     // * side-effects in <clinit>
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 16c102b..7249d94 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -139,7 +139,7 @@
     // * IllegalAccessError (not visible from the access context)
     // * side-effects in <clinit>
     // * not read _globally_
-    boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method.method.holder);
+    boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method.holder());
     assert appView.enableWholeProgramOptimizations() || haveSideEffects
         : "Expected static-put instruction to have side effects in D8";
     return !haveSideEffects;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index e84f64a..57c923c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -826,7 +826,7 @@
       builder.append("(");
       if (isConstant && definition.asConstNumber().outValue != null) {
         ConstNumber constNumber = definition.asConstNumber();
-        if (constNumber.outValue().getType().isSinglePrimitive()) {
+        if (constNumber.getOutType().isSinglePrimitive()) {
           builder.append((int) constNumber.getRawValue());
         } else {
           builder.append(constNumber.getRawValue());
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 6ff7e2f..18b62e6 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
@@ -178,7 +178,7 @@
         if (singleTarget != null) {
           assert !source.accessFlags.isBridge() || singleTarget != currentMethod.method;
           DexProgramClass clazz =
-              asProgramClassOrNull(appView.definitionFor(singleTarget.method.holder));
+              asProgramClassOrNull(appView.definitionFor(singleTarget.holder()));
           if (clazz != null) {
             // For static invokes, the class could be initialized.
             if (type == Invoke.Type.STATIC) {
@@ -253,8 +253,7 @@
         return;
       }
 
-      DexProgramClass clazz =
-          asProgramClassOrNull(appView.definitionFor(encodedField.field.holder));
+      DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(encodedField.holder()));
       if (clazz == null) {
         return;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index ae3e7cd..74f24c8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -328,7 +328,7 @@
       }
     }
     return new CfCode(
-        method.method.holder,
+        method.holder(),
         stackHeightTracker.maxHeight,
         registerAllocator.registersUsed(),
         instructions,
@@ -386,7 +386,7 @@
             || constNumber == null
             || add == null
             || store == null
-            || constNumber.outValue().getType() != TypeElement.getInt()) {
+            || constNumber.getOutType() != TypeElement.getInt()) {
           it.next();
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 1f1d2e5..f13653a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -427,7 +427,7 @@
   private void buildMethodEnterSynchronization(IRBuilder builder) {
     assert needsGeneratedMethodSynchronization;
     currentlyGeneratingMethodSynchronization = true;
-    DexType type = method.method.holder;
+    DexType type = method.holder();
     int monitorRegister;
     if (isStatic()) {
       monitorRegister = state.push(type).register;
@@ -633,7 +633,7 @@
       return ((CfNew) instruction).getType();
     }
     if (type.isUninitializedThis()) {
-      return method.method.holder;
+      return method.holder();
     }
     assert type.isTop();
     return null;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index a4ca85a..6e47281 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -1404,7 +1404,7 @@
     @Override
     public void addInstructions(DexBuilder builder, List<Instruction> instructions) {
       Move move = getMove();
-      TypeElement moveType = move.outValue().getType();
+      TypeElement moveType = move.getOutType();
       int src = srcRegister(builder);
       int dest = destRegister(builder);
       Instruction instruction;
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 993a69c..35250d1 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
@@ -530,7 +530,7 @@
     int argumentIndex = 0;
 
     if (!method.isStatic()) {
-      writeCallback.accept(register, method.method.holder);
+      writeCallback.accept(register, method.holder());
       addThisArgument(register);
       argumentIndex++;
       register++;
@@ -932,7 +932,7 @@
   void addThisArgument(int register) {
     boolean receiverCouldBeNull = context != null && context != method;
     Nullability nullability = receiverCouldBeNull ? maybeNull() : definitelyNotNull();
-    TypeElement receiverType = TypeElement.fromDexType(method.method.holder, nullability, appView);
+    TypeElement receiverType = TypeElement.fromDexType(method.holder(), nullability, appView);
     addThisArgument(register, receiverType);
   }
 
@@ -1463,14 +1463,14 @@
       // therefore we use an invoke-direct instead. We need to do this as the Android Runtime
       // will not allow invoke-virtual of a private method.
       DexMethod invocationMethod = (DexMethod) item;
-      DexType holderType = method.method.holder;
+      DexType holderType = method.holder();
       if (invocationMethod.holder == holderType) {
         DexClass holderClass = appView.definitionFor(holderType);
         assert holderClass != null && holderClass.isProgramClass();
         if (holderClass != null) {
           DexEncodedMethod directTarget = holderClass.lookupDirectMethod(invocationMethod);
           if (directTarget != null && !directTarget.isStatic()) {
-            assert invocationMethod.holder == directTarget.method.holder;
+            assert invocationMethod.holder == directTarget.holder();
             type = Type.DIRECT;
           }
         }
@@ -2332,7 +2332,7 @@
 
   private void addInstruction(Instruction ir, Position position) {
     assert verifyOutValueType(ir);
-    hasImpreciseValues |= ir.outValue() != null && !ir.outValue().getType().isPreciseType();
+    hasImpreciseValues |= ir.outValue() != null && !ir.getOutType().isPreciseType();
     ir.setPosition(position);
     attachLocalValues(ir);
     currentBlock.add(ir, metadata);
@@ -2363,13 +2363,11 @@
   }
 
   private boolean verifyOutValueType(Instruction ir) {
-    assert ir.outValue() == null
-        || ir.isArrayGet()
-        || ir.evaluate(appView) == ir.outValue().getType();
+    assert ir.outValue() == null || ir.isArrayGet() || ir.evaluate(appView) == ir.getOutType();
     assert ir.outValue() == null
         || !ir.isArrayGet()
-        || ir.evaluate(appView) == ir.outValue().getType()
-        || (ir.outValue().getType().isBottom() && ir.evaluate(appView).isReferenceType());
+        || ir.evaluate(appView) == ir.getOutType()
+        || (ir.getOutType().isBottom() && ir.evaluate(appView).isReferenceType());
     return true;
   }
 
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 cae0c84..e29a1c1 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
@@ -94,6 +94,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.LibraryMethodOverrideAnalysis;
 import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -181,7 +182,7 @@
       OptimizationFeedbackSimple.getInstance();
   private DexString highestSortingString;
 
-  private List<com.android.tools.r8.utils.Action> onWaveDoneActions = null;
+  private List<Action> onWaveDoneActions = null;
 
   private final List<DexString> neverMergePrefixes;
   boolean seenNotNeverMergePrefix = false;
@@ -609,10 +610,10 @@
         if (appView.options().enableNeverMergePrefixes) {
           for (DexString neverMergePrefix : neverMergePrefixes) {
             // Synthetic classes will always be merged.
-            if (method.method.holder.isD8R8SynthesizedClassType()) {
+            if (method.holder().isD8R8SynthesizedClassType()) {
               continue;
             }
-            if (method.method.holder.descriptor.startsWith(neverMergePrefix)) {
+            if (method.holder().descriptor.startsWith(neverMergePrefix)) {
               seenNeverMergePrefix = true;
             } else {
               seenNotNeverMergePrefix = true;
@@ -904,7 +905,7 @@
     ThreadUtils.processItems(
         methods,
         method -> {
-          IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.method.holder));
+          IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.holder()));
           assert code != null;
           assert !method.getCode().isOutlineCode();
           // Instead of repeating all the optimizations of rewriteCode(), only run the
@@ -928,7 +929,7 @@
   }
 
   private void forEachSynthesizedServiceLoaderMethod(DexEncodedMethod method) {
-    IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.method.holder));
+    IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.holder()));
     assert code != null;
     codeRewriter.rewriteMoveResult(code);
     removeDeadCodeAndFinalizeIR(
@@ -1006,7 +1007,7 @@
       count++;
       result = appView.dexItemFactory().createType(DescriptorUtils.javaTypeToDescriptor(name));
 
-    } while (appView.definitionFor(result) != null);
+    } while (appView.appInfo().definitionForWithoutExistenceAssert(result) != null);
     return result;
   }
 
@@ -1085,7 +1086,7 @@
 
   private Timing rewriteCode(
       DexEncodedMethod method, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
-    Origin origin = appView.appInfo().originFor(method.method.holder);
+    Origin origin = appView.appInfo().originFor(method.holder());
     return ExceptionUtils.withOriginAttachmentHandler(
         origin,
         new MethodPosition(method.method),
@@ -1147,6 +1148,10 @@
       codeRewriter.simplifyDebugLocals(code);
     }
 
+    if (enumUnboxer != null && methodProcessor.isPost()) {
+      enumUnboxer.rewriteCode(code);
+    }
+
     if (appView.graphLense().hasCodeRewritings()) {
       assert lensCodeRewriter != null;
       timing.begin("Lens rewrite");
@@ -1154,10 +1159,6 @@
       timing.end();
     }
 
-    if (enumUnboxer != null && methodProcessor.isPost()) {
-      enumUnboxer.rewriteCode(code);
-    }
-
     if (method.isProcessed()) {
       assert !appView.enableWholeProgramOptimizations()
           || !appView.appInfo().withLiveness().neverReprocess.contains(method.method);
@@ -1215,7 +1216,7 @@
 
     if (memberValuePropagation != null) {
       timing.begin("Propagate member values");
-      memberValuePropagation.rewriteWithConstantValues(code, method.method.holder);
+      memberValuePropagation.rewriteWithConstantValues(code, method.holder());
       timing.end();
     }
 
@@ -1284,7 +1285,7 @@
     if (devirtualizer != null) {
       assert code.verifyTypes(appView);
       timing.begin("Devirtualize invoke interface");
-      devirtualizer.devirtualizeInvokeInterface(code, method.method.holder);
+      devirtualizer.devirtualizeInvokeInterface(code, method.holder());
       timing.end();
     }
 
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 bb541cf..7daa4a1 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
@@ -7,6 +7,27 @@
 import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
 import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
 import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_METHOD_HANDLE;
+import static com.android.tools.r8.ir.code.Opcodes.INIT_CLASS;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_OF;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_MULTI_NEW_ARRAY;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_NEW_ARRAY;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_POLYMORPHIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+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.STATIC_GET;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -86,7 +107,7 @@
 
   private Value makeOutValue(Instruction insn, IRCode code) {
     if (insn.outValue() != null) {
-      TypeElement oldType = insn.outValue().getType();
+      TypeElement oldType = insn.getOutType();
       TypeElement newType =
           oldType.fixupClassTypeReferences(appView.graphLense()::lookupType, appView);
       return code.createValue(newType, insn.getLocalInfo());
@@ -114,291 +135,370 @@
       InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction current = iterator.next();
-        if (current.isInvokeCustom()) {
-          InvokeCustom invokeCustom = current.asInvokeCustom();
-          DexCallSite callSite = invokeCustom.getCallSite();
-          DexCallSite newCallSite = rewriteCallSite(callSite, method);
-          if (newCallSite != callSite) {
-            Value newOutValue = makeOutValue(invokeCustom, code);
-            InvokeCustom newInvokeCustom =
-                new InvokeCustom(newCallSite, newOutValue, invokeCustom.inValues());
-            iterator.replaceCurrentInstruction(newInvokeCustom);
-            if (newOutValue != null && newOutValue.getType() != invokeCustom.outValue().getType()) {
-              affectedPhis.addAll(newOutValue.uniquePhiUsers());
-            }
-          }
-        } else if (current.isConstMethodHandle()) {
-          DexMethodHandle handle = current.asConstMethodHandle().getValue();
-          DexMethodHandle newHandle = rewriteDexMethodHandle(
-              handle, method, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
-          if (newHandle != handle) {
-            Value newOutValue = makeOutValue(current, code);
-            iterator.replaceCurrentInstruction(new ConstMethodHandle(newOutValue, newHandle));
-            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
-              affectedPhis.addAll(newOutValue.uniquePhiUsers());
-            }
-          }
-        } else if (current.isInitClass()) {
-          InitClass initClass = current.asInitClass();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  initClass.getClassValue(), (t, v) -> new InitClass(v, t));
-        } else if (current.isInvokeMethod()) {
-          InvokeMethod invoke = current.asInvokeMethod();
-          DexMethod invokedMethod = invoke.getInvokedMethod();
-          DexType invokedHolder = invokedMethod.holder;
-          if (invokedHolder.isArrayType()) {
-            DexType baseType = invokedHolder.toBaseType(factory);
-            new InstructionReplacer(code, current, iterator, affectedPhis)
-                .replaceInstructionIfTypeChanged(
-                    baseType,
-                    (t, v) -> {
-                      DexType mappedHolder = invokedHolder.replaceBaseType(t, factory);
-                      // Just reuse proto and name, as no methods on array types cant be renamed nor
-                      // change signature.
-                      DexMethod actualTarget =
-                          factory.createMethod(
-                              mappedHolder, invokedMethod.proto, invokedMethod.name);
-                      return Invoke.create(VIRTUAL, actualTarget, null, v, invoke.inValues());
-                    });
-            continue;
-          }
-          if (!invokedHolder.isClassType()) {
-            assert false;
-            continue;
-          }
-          if (invoke.isInvokeDirect()) {
-            checkInvokeDirect(method.method, invoke.asInvokeDirect());
-          }
-          GraphLenseLookupResult lenseLookup =
-              graphLense.lookupMethod(invokedMethod, method.method, invoke.getType());
-          DexMethod actualTarget = lenseLookup.getMethod();
-          Invoke.Type actualInvokeType = lenseLookup.getType();
-          if (actualTarget != invokedMethod || invoke.getType() != actualInvokeType) {
-            RewrittenPrototypeDescription prototypeChanges =
-                graphLense.lookupPrototypeChanges(actualTarget);
-
-            List<Value> newInValues;
-            ArgumentInfoCollection argumentInfoCollection =
-                prototypeChanges.getArgumentInfoCollection();
-            if (argumentInfoCollection.isEmpty()) {
-              newInValues = invoke.inValues();
-            } else {
-              if (argumentInfoCollection.hasRemovedArguments()) {
-                if (Log.ENABLED) {
-                  Log.info(
-                      getClass(),
-                      "Invoked method "
-                          + invokedMethod.toSourceString()
-                          + " with "
-                          + argumentInfoCollection.numberOfRemovedArguments()
-                          + " arguments removed");
-                }
-              }
-              newInValues = new ArrayList<>(actualTarget.proto.parameters.size());
-              for (int i = 0; i < invoke.inValues().size(); i++) {
-                ArgumentInfo argumentInfo = argumentInfoCollection.getArgumentInfo(i);
-                if (argumentInfo.isRewrittenTypeInfo()) {
-                  RewrittenTypeInfo argInfo = argumentInfo.asRewrittenTypeInfo();
-                  Value rewrittenValue =
-                      rewriteValueIfDefault(
-                          code,
-                          iterator,
-                          argInfo.getOldType(),
-                          argInfo.getNewType(),
-                          invoke.inValues().get(i));
-                  newInValues.add(rewrittenValue);
-                } else if (!argumentInfo.isRemovedArgumentInfo()) {
-                  newInValues.add(invoke.inValues().get(i));
+        switch (current.opcode()) {
+          case INVOKE_CUSTOM:
+            {
+              InvokeCustom invokeCustom = current.asInvokeCustom();
+              DexCallSite callSite = invokeCustom.getCallSite();
+              DexCallSite newCallSite = rewriteCallSite(callSite, method);
+              if (newCallSite != callSite) {
+                Value newOutValue = makeOutValue(invokeCustom, code);
+                InvokeCustom newInvokeCustom =
+                    new InvokeCustom(newCallSite, newOutValue, invokeCustom.inValues());
+                iterator.replaceCurrentInstruction(newInvokeCustom);
+                if (newOutValue != null && newOutValue.getType() != invokeCustom.getOutType()) {
+                  affectedPhis.addAll(newOutValue.uniquePhiUsers());
                 }
               }
             }
+            break;
 
-            ConstInstruction constantReturnMaterializingInstruction = null;
-            if (prototypeChanges.hasBeenChangedToReturnVoid(appView) && invoke.outValue() != null) {
-              constantReturnMaterializingInstruction =
-                  prototypeChanges.getConstantReturn(code, invoke.getPosition());
-              if (invoke.outValue().hasLocalInfo()) {
-                constantReturnMaterializingInstruction
-                    .outValue()
-                    .setLocalInfo(invoke.outValue().getLocalInfo());
-              }
-              invoke.outValue().replaceUsers(constantReturnMaterializingInstruction.outValue());
-              if (graphLense.lookupType(invoke.getReturnType()) != invoke.getReturnType()) {
-                affectedPhis.addAll(
-                    constantReturnMaterializingInstruction.outValue().uniquePhiUsers());
+          case CONST_METHOD_HANDLE:
+            {
+              DexMethodHandle handle = current.asConstMethodHandle().getValue();
+              DexMethodHandle newHandle =
+                  rewriteDexMethodHandle(handle, method, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
+              if (newHandle != handle) {
+                Value newOutValue = makeOutValue(current, code);
+                iterator.replaceCurrentInstruction(new ConstMethodHandle(newOutValue, newHandle));
+                if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
+                  affectedPhis.addAll(newOutValue.uniquePhiUsers());
+                }
               }
             }
+            break;
 
-            Value newOutValue =
-                prototypeChanges.hasBeenChangedToReturnVoid(appView)
-                    ? null
-                    : makeOutValue(invoke, code);
-
-            if (prototypeChanges.hasExtraNullParameter()) {
-              iterator.previous();
-              Value extraNullValue = iterator.insertConstNullInstruction(code, appView.options());
-              iterator.next();
-              newInValues.add(extraNullValue);
+          case INIT_CLASS:
+            {
+              InitClass initClass = current.asInitClass();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      initClass.getClassValue(), (t, v) -> new InitClass(v, t));
             }
+            break;
 
-            assert newInValues.size()
-                == actualTarget.proto.parameters.size() + (actualInvokeType == STATIC ? 0 : 1);
+          case INVOKE_DIRECT:
+          case INVOKE_INTERFACE:
+          case INVOKE_POLYMORPHIC:
+          case INVOKE_STATIC:
+          case INVOKE_SUPER:
+          case INVOKE_VIRTUAL:
+            {
+              InvokeMethod invoke = current.asInvokeMethod();
+              DexMethod invokedMethod = invoke.getInvokedMethod();
+              DexType invokedHolder = invokedMethod.holder;
+              if (invokedHolder.isArrayType()) {
+                DexType baseType = invokedHolder.toBaseType(factory);
+                new InstructionReplacer(code, current, iterator, affectedPhis)
+                    .replaceInstructionIfTypeChanged(
+                        baseType,
+                        (t, v) -> {
+                          DexType mappedHolder = invokedHolder.replaceBaseType(t, factory);
+                          // Just reuse proto and name, as no methods on array types cant be renamed
+                          // nor change signature.
+                          DexMethod actualTarget =
+                              factory.createMethod(
+                                  mappedHolder, invokedMethod.proto, invokedMethod.name);
+                          return Invoke.create(VIRTUAL, actualTarget, null, v, invoke.inValues());
+                        });
+                continue;
+              }
+              if (!invokedHolder.isClassType()) {
+                assert false;
+                continue;
+              }
+              if (invoke.isInvokeDirect()) {
+                checkInvokeDirect(method.method, invoke.asInvokeDirect());
+              }
+              GraphLenseLookupResult lenseLookup =
+                  graphLense.lookupMethod(invokedMethod, method.method, invoke.getType());
+              DexMethod actualTarget = lenseLookup.getMethod();
+              Invoke.Type actualInvokeType = lenseLookup.getType();
+              if (actualTarget != invokedMethod || invoke.getType() != actualInvokeType) {
+                RewrittenPrototypeDescription prototypeChanges =
+                    graphLense.lookupPrototypeChanges(actualTarget);
 
-            Invoke newInvoke =
-                Invoke.create(actualInvokeType, actualTarget, null, newOutValue, newInValues);
-            iterator.replaceCurrentInstruction(newInvoke);
-            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
-              affectedPhis.addAll(newOutValue.uniquePhiUsers());
-            }
+                List<Value> newInValues;
+                ArgumentInfoCollection argumentInfoCollection =
+                    prototypeChanges.getArgumentInfoCollection();
+                if (argumentInfoCollection.isEmpty()) {
+                  newInValues = invoke.inValues();
+                } else {
+                  if (argumentInfoCollection.hasRemovedArguments()) {
+                    if (Log.ENABLED) {
+                      Log.info(
+                          getClass(),
+                          "Invoked method "
+                              + invokedMethod.toSourceString()
+                              + " with "
+                              + argumentInfoCollection.numberOfRemovedArguments()
+                              + " arguments removed");
+                    }
+                  }
+                  newInValues = new ArrayList<>(actualTarget.proto.parameters.size());
+                  for (int i = 0; i < invoke.inValues().size(); i++) {
+                    ArgumentInfo argumentInfo = argumentInfoCollection.getArgumentInfo(i);
+                    if (argumentInfo.isRewrittenTypeInfo()) {
+                      RewrittenTypeInfo argInfo = argumentInfo.asRewrittenTypeInfo();
+                      Value rewrittenValue =
+                          rewriteValueIfDefault(
+                              code,
+                              iterator,
+                              argInfo.getOldType(),
+                              argInfo.getNewType(),
+                              invoke.inValues().get(i));
+                      newInValues.add(rewrittenValue);
+                    } else if (!argumentInfo.isRemovedArgumentInfo()) {
+                      newInValues.add(invoke.inValues().get(i));
+                    }
+                  }
+                }
 
-            if (constantReturnMaterializingInstruction != null) {
-              if (block.hasCatchHandlers()) {
-                // Split the block to ensure no instructions after throwing instructions.
-                iterator
-                    .split(code, blocks)
-                    .listIterator(code)
-                    .add(constantReturnMaterializingInstruction);
-              } else {
-                iterator.add(constantReturnMaterializingInstruction);
+                ConstInstruction constantReturnMaterializingInstruction = null;
+                if (prototypeChanges.hasBeenChangedToReturnVoid(appView)
+                    && invoke.outValue() != null) {
+                  constantReturnMaterializingInstruction =
+                      prototypeChanges.getConstantReturn(code, invoke.getPosition());
+                  if (invoke.outValue().hasLocalInfo()) {
+                    constantReturnMaterializingInstruction
+                        .outValue()
+                        .setLocalInfo(invoke.outValue().getLocalInfo());
+                  }
+                  invoke.outValue().replaceUsers(constantReturnMaterializingInstruction.outValue());
+                  if (graphLense.lookupType(invoke.getReturnType()) != invoke.getReturnType()) {
+                    affectedPhis.addAll(
+                        constantReturnMaterializingInstruction.outValue().uniquePhiUsers());
+                  }
+                }
+
+                Value newOutValue =
+                    prototypeChanges.hasBeenChangedToReturnVoid(appView)
+                        ? null
+                        : makeOutValue(invoke, code);
+
+                if (prototypeChanges.hasExtraNullParameter()) {
+                  iterator.previous();
+                  Value extraNullValue =
+                      iterator.insertConstNullInstruction(code, appView.options());
+                  iterator.next();
+                  newInValues.add(extraNullValue);
+                }
+
+                assert newInValues.size()
+                    == actualTarget.proto.parameters.size() + (actualInvokeType == STATIC ? 0 : 1);
+
+                Invoke newInvoke =
+                    Invoke.create(actualInvokeType, actualTarget, null, newOutValue, newInValues);
+                iterator.replaceCurrentInstruction(newInvoke);
+                if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
+                  affectedPhis.addAll(newOutValue.uniquePhiUsers());
+                }
+
+                if (constantReturnMaterializingInstruction != null) {
+                  if (block.hasCatchHandlers()) {
+                    // Split the block to ensure no instructions after throwing instructions.
+                    iterator
+                        .split(code, blocks)
+                        .listIterator(code)
+                        .add(constantReturnMaterializingInstruction);
+                  } else {
+                    iterator.add(constantReturnMaterializingInstruction);
+                  }
+                }
+
+                DexType actualReturnType = actualTarget.proto.returnType;
+                DexType expectedReturnType = graphLense.lookupType(invokedMethod.proto.returnType);
+                if (newInvoke.outValue() != null && actualReturnType != expectedReturnType) {
+                  throw new Unreachable(
+                      "Unexpected need to insert a cast. Possibly related to resolving"
+                          + " b/79143143.\n"
+                          + invokedMethod
+                          + " type changed from "
+                          + expectedReturnType
+                          + " to "
+                          + actualReturnType);
+                }
               }
             }
+            break;
 
-            DexType actualReturnType = actualTarget.proto.returnType;
-            DexType expectedReturnType = graphLense.lookupType(invokedMethod.proto.returnType);
-            if (newInvoke.outValue() != null && actualReturnType != expectedReturnType) {
-              throw new Unreachable(
-                  "Unexpected need to insert a cast. Possibly related to resolving b/79143143.\n"
-                      + invokedMethod
-                      + " type changed from " + expectedReturnType
-                      + " to " + actualReturnType);
+          case INSTANCE_GET:
+            {
+              InstanceGet instanceGet = current.asInstanceGet();
+              DexField field = instanceGet.getField();
+              DexField actualField = graphLense.lookupField(field);
+              DexMethod replacementMethod =
+                  graphLense.lookupGetFieldForMethod(actualField, method.method);
+              if (replacementMethod != null) {
+                Value newOutValue = makeOutValue(current, code);
+                iterator.replaceCurrentInstruction(
+                    new InvokeStatic(replacementMethod, newOutValue, current.inValues()));
+                if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
+                  affectedPhis.addAll(current.outValue().uniquePhiUsers());
+                }
+              } else if (actualField != field) {
+                Value newOutValue = makeOutValue(instanceGet, code);
+                iterator.replaceCurrentInstruction(
+                    new InstanceGet(newOutValue, instanceGet.object(), actualField));
+                if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
+                  affectedPhis.addAll(newOutValue.uniquePhiUsers());
+                }
+              }
             }
-          }
-        } else if (current.isInstanceGet()) {
-          InstanceGet instanceGet = current.asInstanceGet();
-          DexField field = instanceGet.getField();
-          DexField actualField = graphLense.lookupField(field);
-          DexMethod replacementMethod =
-              graphLense.lookupGetFieldForMethod(actualField, method.method);
-          if (replacementMethod != null) {
-            Value newOutValue = makeOutValue(current, code);
-            iterator.replaceCurrentInstruction(
-                new InvokeStatic(replacementMethod, newOutValue, current.inValues()));
-            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
-              affectedPhis.addAll(current.outValue().uniquePhiUsers());
+            break;
+
+          case INSTANCE_PUT:
+            {
+              InstancePut instancePut = current.asInstancePut();
+              DexField field = instancePut.getField();
+              DexField actualField = graphLense.lookupField(field);
+              DexMethod replacementMethod =
+                  graphLense.lookupPutFieldForMethod(actualField, method.method);
+              if (replacementMethod != null) {
+                iterator.replaceCurrentInstruction(
+                    new InvokeStatic(replacementMethod, null, current.inValues()));
+              } else if (actualField != field) {
+                Value rewrittenValue =
+                    rewriteValueIfDefault(
+                        code, iterator, field.type, actualField.type, instancePut.value());
+                InstancePut newInstancePut =
+                    InstancePut.createPotentiallyInvalid(
+                        actualField, instancePut.object(), rewrittenValue);
+                iterator.replaceCurrentInstruction(newInstancePut);
+              }
             }
-          } else if (actualField != field) {
-            Value newOutValue = makeOutValue(instanceGet, code);
-            iterator.replaceCurrentInstruction(
-                new InstanceGet(newOutValue, instanceGet.object(), actualField));
-            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
-              affectedPhis.addAll(newOutValue.uniquePhiUsers());
+            break;
+
+          case STATIC_GET:
+            {
+              StaticGet staticGet = current.asStaticGet();
+              DexField field = staticGet.getField();
+              DexField actualField = graphLense.lookupField(field);
+              DexMethod replacementMethod =
+                  graphLense.lookupGetFieldForMethod(actualField, method.method);
+              if (replacementMethod != null) {
+                Value newOutValue = makeOutValue(current, code);
+                iterator.replaceCurrentInstruction(
+                    new InvokeStatic(replacementMethod, newOutValue, current.inValues()));
+                if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
+                  affectedPhis.addAll(newOutValue.uniquePhiUsers());
+                }
+              } else if (actualField != field) {
+                Value newOutValue = makeOutValue(staticGet, code);
+                iterator.replaceCurrentInstruction(new StaticGet(newOutValue, actualField));
+                if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
+                  affectedPhis.addAll(newOutValue.uniquePhiUsers());
+                }
+              }
             }
-          }
-        } else if (current.isInstancePut()) {
-          InstancePut instancePut = current.asInstancePut();
-          DexField field = instancePut.getField();
-          DexField actualField = graphLense.lookupField(field);
-          DexMethod replacementMethod =
-              graphLense.lookupPutFieldForMethod(actualField, method.method);
-          if (replacementMethod != null) {
-            iterator.replaceCurrentInstruction(
-                new InvokeStatic(replacementMethod, null, current.inValues()));
-          } else if (actualField != field) {
-            Value rewrittenValue =
-                rewriteValueIfDefault(
-                    code, iterator, field.type, actualField.type, instancePut.value());
-            InstancePut newInstancePut =
-                InstancePut.createPotentiallyInvalid(
-                    actualField, instancePut.object(), rewrittenValue);
-            iterator.replaceCurrentInstruction(newInstancePut);
-          }
-        } else if (current.isStaticGet()) {
-          StaticGet staticGet = current.asStaticGet();
-          DexField field = staticGet.getField();
-          DexField actualField = graphLense.lookupField(field);
-          DexMethod replacementMethod =
-              graphLense.lookupGetFieldForMethod(actualField, method.method);
-          if (replacementMethod != null) {
-            Value newOutValue = makeOutValue(current, code);
-            iterator.replaceCurrentInstruction(
-                new InvokeStatic(replacementMethod, newOutValue, current.inValues()));
-            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
-              affectedPhis.addAll(newOutValue.uniquePhiUsers());
+            break;
+
+          case STATIC_PUT:
+            {
+              StaticPut staticPut = current.asStaticPut();
+              DexField field = staticPut.getField();
+              DexField actualField = graphLense.lookupField(field);
+              DexMethod replacementMethod =
+                  graphLense.lookupPutFieldForMethod(actualField, method.method);
+              if (replacementMethod != null) {
+                iterator.replaceCurrentInstruction(
+                    new InvokeStatic(replacementMethod, current.outValue(), current.inValues()));
+              } else if (actualField != field) {
+                Value rewrittenValue =
+                    rewriteValueIfDefault(
+                        code, iterator, field.type, actualField.type, staticPut.value());
+                StaticPut newStaticPut = new StaticPut(rewrittenValue, actualField);
+                iterator.replaceCurrentInstruction(newStaticPut);
+              }
             }
-          } else if (actualField != field) {
-            Value newOutValue = makeOutValue(staticGet, code);
-            iterator.replaceCurrentInstruction(new StaticGet(newOutValue, actualField));
-            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
-              affectedPhis.addAll(newOutValue.uniquePhiUsers());
+            break;
+
+          case CHECK_CAST:
+            {
+              CheckCast checkCast = current.asCheckCast();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      checkCast.getType(), (t, v) -> new CheckCast(v, checkCast.object(), t));
             }
-          }
-        } else if (current.isStaticPut()) {
-          StaticPut staticPut = current.asStaticPut();
-          DexField field = staticPut.getField();
-          DexField actualField = graphLense.lookupField(field);
-          DexMethod replacementMethod =
-              graphLense.lookupPutFieldForMethod(actualField, method.method);
-          if (replacementMethod != null) {
-            iterator.replaceCurrentInstruction(
-                new InvokeStatic(replacementMethod, current.outValue(), current.inValues()));
-          } else if (actualField != field) {
-            Value rewrittenValue =
-                rewriteValueIfDefault(
-                    code, iterator, field.type, actualField.type, staticPut.value());
-            StaticPut newStaticPut = new StaticPut(rewrittenValue, actualField);
-            iterator.replaceCurrentInstruction(newStaticPut);
-          }
-        } else if (current.isCheckCast()) {
-          CheckCast checkCast = current.asCheckCast();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  checkCast.getType(), (t, v) -> new CheckCast(v, checkCast.object(), t));
-        } else if (current.isConstClass()) {
-          ConstClass constClass = current.asConstClass();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  constClass.getValue(), (t, v) -> new ConstClass(v, t));
-        } else if (current.isInstanceOf()) {
-          InstanceOf instanceOf = current.asInstanceOf();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  instanceOf.type(), (t, v) -> new InstanceOf(v, instanceOf.value(), t));
-        } else if (current.isInvokeMultiNewArray()) {
-          InvokeMultiNewArray multiNewArray = current.asInvokeMultiNewArray();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  multiNewArray.getArrayType(),
-                  (t, v) -> new InvokeMultiNewArray(t, v, multiNewArray.inValues()));
-        } else if (current.isInvokeNewArray()) {
-          InvokeNewArray newArray = current.asInvokeNewArray();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  newArray.getArrayType(), (t, v) -> new InvokeNewArray(t, v, newArray.inValues()));
-        } else if (current.isMoveException()) {
-          MoveException moveException = current.asMoveException();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  moveException.getExceptionType(),
-                  (t, v) -> new MoveException(v, t, appView.options()));
-        } else if (current.isNewArrayEmpty()) {
-          NewArrayEmpty newArrayEmpty = current.asNewArrayEmpty();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  newArrayEmpty.type, (t, v) -> new NewArrayEmpty(v, newArrayEmpty.size(), t));
-        } else if (current.isNewInstance()) {
-          DexType type = current.asNewInstance().clazz;
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(type, NewInstance::new);
-        } else if (current.outValue() != null) {
-          // For all other instructions, substitute any changed type.
-          TypeElement typeLattice = current.outValue().getType();
-          TypeElement substituted =
-              typeLattice.fixupClassTypeReferences(graphLense::lookupType, appView);
-          if (substituted != typeLattice) {
-            current.outValue().setType(substituted);
-            affectedPhis.addAll(current.outValue().uniquePhiUsers());
-          }
+            break;
+
+          case CONST_CLASS:
+            {
+              ConstClass constClass = current.asConstClass();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      constClass.getValue(), (t, v) -> new ConstClass(v, t));
+            }
+            break;
+
+          case INSTANCE_OF:
+            {
+              InstanceOf instanceOf = current.asInstanceOf();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      instanceOf.type(), (t, v) -> new InstanceOf(v, instanceOf.value(), t));
+            }
+            break;
+
+          case INVOKE_MULTI_NEW_ARRAY:
+            {
+              InvokeMultiNewArray multiNewArray = current.asInvokeMultiNewArray();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      multiNewArray.getArrayType(),
+                      (t, v) -> new InvokeMultiNewArray(t, v, multiNewArray.inValues()));
+            }
+            break;
+
+          case INVOKE_NEW_ARRAY:
+            {
+              InvokeNewArray newArray = current.asInvokeNewArray();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      newArray.getArrayType(),
+                      (t, v) -> new InvokeNewArray(t, v, newArray.inValues()));
+            }
+            break;
+
+          case MOVE_EXCEPTION:
+            {
+              MoveException moveException = current.asMoveException();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      moveException.getExceptionType(),
+                      (t, v) -> new MoveException(v, t, appView.options()));
+            }
+            break;
+
+          case NEW_ARRAY_EMPTY:
+            {
+              NewArrayEmpty newArrayEmpty = current.asNewArrayEmpty();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      newArrayEmpty.type, (t, v) -> new NewArrayEmpty(v, newArrayEmpty.size(), t));
+            }
+            break;
+
+          case NEW_INSTANCE:
+            {
+              DexType type = current.asNewInstance().clazz;
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(type, NewInstance::new);
+            }
+            break;
+
+          default:
+            if (current.hasOutValue()) {
+              // For all other instructions, substitute any changed type.
+              TypeElement type = current.getOutType();
+              TypeElement substituted =
+                  type.fixupClassTypeReferences(graphLense::lookupType, appView);
+              if (substituted != type) {
+                current.outValue().setType(substituted);
+                affectedPhis.addAll(current.outValue().uniquePhiUsers());
+              }
+            }
+            break;
         }
       }
     }
@@ -685,7 +785,7 @@
         Instruction newInstruction = constructor.apply(newType, newOutValue);
         iterator.replaceCurrentInstruction(newInstruction);
         if (newOutValue != null) {
-          if (newOutValue.getType() != current.outValue().getType()) {
+          if (newOutValue.getType() != current.getOutType()) {
             affectedPhis.addAll(newOutValue.uniquePhiUsers());
           } else {
             assert current.hasInvariantOutType();
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 e742528..328365e 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
@@ -175,7 +175,7 @@
       OptimizationFeedback feedback) {
     // TODO(b/140766440): Make IRConverter#process receive a list of CodeOptimization to conduct.
     //   Then, we can share IRCode creation there.
-    Origin origin = appView.appInfo().originFor(method.method.holder);
+    Origin origin = appView.appInfo().originFor(method.holder());
     if (appView.options().skipIR) {
       feedback.markProcessed(method, ConstraintWithTarget.NEVER);
       return;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
index d7edc8a..58f7155 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
@@ -114,7 +114,7 @@
         }
       }
       for (Instruction instruction : block.getInstructions()) {
-        if (instruction.outValue() != null && !instruction.outValue().getType().isPreciseType()) {
+        if (instruction.outValue() != null && !instruction.getOutType().isPreciseType()) {
           impreciseValues.add(instruction.outValue());
         }
 
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 43db91d..49c8000 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
@@ -125,6 +125,11 @@
     }
   }
 
+  public static void registerAssumedLibraryTypes(InternalOptions options) {
+    // TODO(b/150693139): Remove the pre-registration once fixed.
+    BackportedMethods.registerSynthesizedCodeReferences(options.itemFactory);
+  }
+
   public void desugar(IRCode code) {
     if (!enabled) {
       return; // Nothing to do!
@@ -154,7 +159,7 @@
       if (provider.requiresGenerationOfCode()) {
         DexMethod newMethod = provider.provideMethod(appView);
         methodProviders.putIfAbsent(newMethod, provider);
-        holders.add(code.method.method.holder);
+        holders.add(code.method.holder());
       }
     }
     if (!affectedValues.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 3d16d19..82c7c6a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -18,9 +19,9 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.graph.ResolutionResult.IncompatibleClassResult;
 import com.android.tools.r8.ir.synthetic.ExceptionThrowingSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
@@ -74,14 +75,6 @@
       return signatures.size() == merged.size() ? this : new MethodSignatures(merged);
     }
 
-    MethodSignatures merge(List<MethodSignatures> others) {
-      MethodSignatures merged = this;
-      for (MethodSignatures other : others) {
-        merged = merged.merge(others);
-      }
-      return merged;
-    }
-
     boolean isEmpty() {
       return signatures.isEmpty();
     }
@@ -151,7 +144,7 @@
     }
   }
 
-  // Specialized context to disable reporting when traversing the library strucure.
+  // Specialized context to disable reporting when traversing the library structure.
   private static class LibraryReportingContext extends ReportingContext {
 
     static final LibraryReportingContext LIBRARY_CONTEXT = new LibraryReportingContext();
@@ -281,22 +274,36 @@
     return ClassInfo.create(superInfo, additionalForwards.build());
   }
 
-  // Resolves a method signature from the point of 'clazz', if it must target a default method
+  // Looks up a method signature from the point of 'clazz', if it can dispatch to a default method
   // the 'addForward' call-back is called with the target of the forward.
   private void resolveForwardForSignature(
       DexClass clazz, DexMethod method, BiConsumer<DexClass, DexEncodedMethod> addForward) {
-    ResolutionResult resolution = appView.appInfo().resolveMethod(clazz, method);
-    // If resolution fails, install a method throwing IncompatibleClassChangeError.
-    if (resolution.isFailedResolution()) {
-      if (resolution instanceof IncompatibleClassResult) {
+    // Resolve the default method at base type as the symbolic holder at call sites is not known.
+    // The dispatch target is then looked up from the possible "instance" class.
+    // Doing so can cause an invalid invoke to become valid (at runtime resolution at a subtype
+    // might have failed which is hidden by the insertion of the forward method). However, not doing
+    // so could cause valid dispatches to become invalid by resolving to private overrides.
+    DexClassAndMethod virtualDispatchTarget =
+        appView
+            .appInfo()
+            .resolveMethodOnInterface(method.holder, method)
+            .lookupVirtualDispatchTarget(clazz, appView.appInfo());
+    if (virtualDispatchTarget == null) {
+      // If no target is found due to multiple default method targets, preserve ICCE behavior.
+      ResolutionResult resolutionFromSubclass = appView.appInfo().resolveMethod(clazz, method);
+      if (resolutionFromSubclass.isIncompatibleClassChangeErrorResult()) {
         addICCEThrowingMethod(method, clazz);
+        return;
       }
+      assert resolutionFromSubclass.isFailedResolution()
+          || resolutionFromSubclass.getSingleTarget().isPrivateMethod();
       return;
     }
-    DexEncodedMethod target = resolution.getSingleTarget();
-    DexClass targetHolder = appView.definitionFor(target.method.holder);
+
+    DexEncodedMethod target = virtualDispatchTarget.getMethod();
+    DexClass targetHolder = virtualDispatchTarget.getHolder();
     // Don-t forward if the target is explicitly marked as 'dont-rewrite'
-    if (targetHolder == null || dontRewrite(targetHolder, target)) {
+    if (dontRewrite(targetHolder, target)) {
       return;
     }
 
@@ -330,7 +337,7 @@
 
   private boolean isRetargetMethod(DexLibraryClass holder, DexEncodedMethod method) {
     assert needsLibraryInfo();
-    assert holder.type == method.method.holder;
+    assert holder.type == method.holder();
     assert method.isNonPrivateVirtualMethod();
     if (method.isFinal()) {
       return false;
@@ -375,6 +382,16 @@
     if (!clazz.isProgramClass()) {
       return;
     }
+
+    DexEncodedMethod methodOnSelf = clazz.lookupMethod(target.method);
+    if (methodOnSelf != null) {
+      throw new CompilationError(
+          "Attempt to add forwarding method that conflicts with existing method.",
+          null,
+          clazz.getOrigin(),
+          new MethodPosition(methodOnSelf.method));
+    }
+
     DexMethod method = target.method;
     // 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.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index 2be1832..b5c3f3e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -152,12 +152,12 @@
     MethodAccessFlags newAccessFlags = method.accessFlags.copy();
     newAccessFlags.setBridge();
     newAccessFlags.setSynthetic();
-    DexMethod newMethod = factory.createMethod(method.method.holder, newProto, method.method.name);
+    DexMethod newMethod = factory.createMethod(method.holder(), newProto, method.method.name);
     ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
         ForwardMethodSourceCode.builder(newMethod);
     forwardSourceCodeBuilder
         .setReceiver(clazz.type)
-        .setTargetReceiver(method.method.holder)
+        .setTargetReceiver(method.holder())
         .setTarget(method.method)
         .setInvokeType(Invoke.Type.VIRTUAL)
         .setCastResult();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
index 7b53a35..738f4aa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
@@ -49,7 +49,7 @@
 
   public void rewriteNestBasedAccesses(
       DexEncodedMethod encodedMethod, IRCode code, AppView<?> appView) {
-    DexClass currentClass = appView.definitionFor(encodedMethod.method.holder);
+    DexClass currentClass = appView.definitionFor(encodedMethod.holder());
     assert currentClass != null;
     if (!currentClass.isInANest()) {
       return;
@@ -120,7 +120,7 @@
 
   private void addDeferredBridges(Collection<DexEncodedMethod> bridges) {
     for (DexEncodedMethod bridge : bridges) {
-      DexClass holder = definitionFor(bridge.method.holder);
+      DexClass holder = definitionFor(bridge.holder());
       assert holder != null && holder.isProgramClass();
       holder.asProgramClass().addMethod(bridge);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index c81eedb..11180c4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -106,7 +106,7 @@
 
   public void desugar(IRCode code) {
 
-    if (wrapperSynthesizor.hasSynthesized(code.method.method.holder)) {
+    if (wrapperSynthesizor.hasSynthesized(code.method.holder())) {
       return;
     }
 
@@ -140,7 +140,7 @@
     if (!shouldRegisterCallback(encodedMethod)) {
       return true;
     }
-    DexProgramClass holderClass = appView.definitionForProgramType(encodedMethod.method.holder);
+    DexProgramClass holderClass = appView.definitionForProgramType(encodedMethod.holder());
     DexMethod installedCallback =
         methodWithVivifiedTypeInSignature(encodedMethod.method, holderClass.type, appView);
     assert holderClass.lookupMethod(installedCallback) != null;
@@ -161,7 +161,7 @@
 
   public void registerCallbackIfRequired(DexEncodedMethod encodedMethod) {
     if (shouldRegisterCallback(encodedMethod)) {
-      DexClass dexClass = appView.definitionFor(encodedMethod.method.holder);
+      DexClass dexClass = appView.definitionFor(encodedMethod.holder());
       assert dexClass != null;
       registerCallback(dexClass, encodedMethod);
     }
@@ -197,7 +197,6 @@
     return overridesLibraryMethod(dexClass, method);
   }
 
-
   private boolean overridesLibraryMethod(DexClass theClass, DexMethod method) {
     // We look up everywhere to see if there is a supertype/interface implementing the method...
     WorkList<DexType> workList = WorkList.newIdentityWorkList();
@@ -253,7 +252,7 @@
   }
 
   private synchronized void addCallBackSignature(DexClass dexClass, DexEncodedMethod method) {
-    assert dexClass.type == method.method.holder;
+    assert dexClass.type == method.holder();
     if (callBackMethods.computeIfAbsent(dexClass, key -> new HashSet<>()).add(method)) {
       pendingCallBackMethods.computeIfAbsent(dexClass, key -> new ArrayList<>()).add(method);
     }
@@ -347,22 +346,22 @@
     appView.options().reporter.warning(new StringDiagnostic(sb.toString()));
   }
 
-  private void warnInvalidInvoke(DexType type, DexMethod invokedMethod, String debugString) {
+  public void reportInvalidInvoke(DexType type, DexMethod invokedMethod, String debugString) {
     DexType desugaredType = appView.rewritePrefix.rewrittenType(type, appView);
     appView
         .options()
         .reporter
-        .warning(
+        .info(
             new StringDiagnostic(
                 "Invoke to "
                     + invokedMethod.holder
                     + "#"
                     + invokedMethod.name
-                    + " may not work correctly at runtime ("
+                    + " may not work correctly at runtime (Cannot convert "
                     + debugString
-                    + " type "
+                    + "type "
                     + desugaredType
-                    + " is a desugared type)."));
+                    + ")."));
   }
 
   public static DexType vivifiedTypeFor(DexType type, AppView<?> appView) {
@@ -398,6 +397,7 @@
       InstructionListIterator iterator,
       ListIterator<BasicBlock> blockIterator) {
     DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+    boolean invalidConversion = false;
     if (trackedAPIs != null) {
       trackedAPIs.add(invokedMethod);
     }
@@ -412,12 +412,13 @@
         // Return conversion added only if return value is used.
         if (invokeMethod.outValue() != null
             && invokeMethod.outValue().numberOfUsers() + invokeMethod.outValue().numberOfPhiUsers()
-            > 0) {
+                > 0) {
           returnConversion =
               createReturnConversionAndReplaceUses(code, invokeMethod, returnType, newReturnType);
         }
       } else {
-        warnInvalidInvoke(returnType, invokeMethod.getInvokedMethod(), "return");
+        reportInvalidInvoke(returnType, invokeMethod.getInvokedMethod(), "return ");
+        invalidConversion = true;
         newReturnType = returnType;
       }
     } else {
@@ -445,7 +446,8 @@
               createParameterConversion(code, argType, argVivifiedType, inValue));
           newInValues.add(parameterConversions.get(parameterConversions.size() - 1).outValue());
         } else {
-          warnInvalidInvoke(argType, invokeMethod.getInvokedMethod(), "parameter");
+          reportInvalidInvoke(argType, invokeMethod.getInvokedMethod(), "parameter ");
+          invalidConversion = true;
           newInValues.add(invokeMethod.inValues().get(i + receiverShift));
         }
       } else {
@@ -465,7 +467,8 @@
             invokeMethod.outValue(),
             newInValues);
     assert newDexMethod
-        == methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView);
+            == methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView)
+        || invalidConversion;
 
     // Insert and reschedule all instructions.
     iterator.previous();
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
index 3949767..0e52152 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -5,7 +5,24 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.*;
+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.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+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.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+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;
@@ -118,7 +135,7 @@
             appView
                 .appInfo()
                 .withClassHierarchy()
-                .lookupSuperTarget(invoke.getInvokedMethod(), code.method.method.holder);
+                .lookupSuperTarget(invoke.getInvokedMethod(), code.method.holder());
         // Final methods can be rewritten as a normal invoke.
         if (dexEncodedMethod != null && !dexEncodedMethod.isFinal()) {
           DexMethod retargetMethod =
@@ -270,7 +287,7 @@
             && dexClass.isLibraryClass()
             && dexClass.type != appView.dexItemFactory().objectType) {
           for (DexType dexType : map.keySet()) {
-            if (inherit(dexClass.asLibraryClass(), dexType)) {
+            if (inherit(dexClass.asLibraryClass(), dexType, emulatedDispatchMethods)) {
               addedMethods.addAll(addInterfacesAndForwardingMethods(clazz, map.get(dexType)));
             }
           }
@@ -282,13 +299,21 @@
       converter.processMethodsConcurrently(addedMethods, executorService);
     }
 
-    private boolean inherit(DexLibraryClass clazz, DexType typeToInherit) {
+    private boolean inherit(DexLibraryClass clazz, DexType typeToInherit, Set<DexMethod> retarget) {
       DexLibraryClass current = clazz;
       while (current.type != appView.dexItemFactory().objectType) {
         if (current.type == typeToInherit) {
           return true;
         }
-        current = appView.definitionFor(current.superType).asLibraryClass();
+        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;
     }
@@ -413,6 +438,29 @@
     }
   }
 
+  private void reportInvalidLibrarySupertype(
+      DexLibraryClass libraryClass, Set<DexMethod> 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,
+            appView);
+  }
+
   private DexType dispatchInterfaceTypeFor(DexMethod method) {
     return dispatchTypeFor(method, "dispatchInterface");
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index 30bc78e..e3c15ae 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -125,7 +125,7 @@
 
   boolean canGenerateWrapper(DexType type) {
     DexClass dexClass = appView.definitionFor(type);
-    if (dexClass == null) {
+    if (dexClass == null || dexClass.accessFlags.isFinal()) {
       return false;
     }
     return dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation();
@@ -169,18 +169,9 @@
   private DexClass getValidClassToWrap(DexType type) {
     DexClass dexClass = appView.definitionFor(type);
     // The dexClass should be a library class, so it cannot be null.
-    assert dexClass != null
-        && (dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation());
-    if (dexClass.accessFlags.isFinal()) {
-      throw appView
-          .options()
-          .reporter
-          .fatalError(
-              new StringDiagnostic(
-                  "Cannot generate a wrapper for final class "
-                      + dexClass.type
-                      + ". Add a custom conversion in the desugared library."));
-    }
+    assert dexClass != null;
+    assert dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation();
+    assert !dexClass.accessFlags.isFinal();
     return dexClass;
   }
 
@@ -223,7 +214,7 @@
     DexTypeList interfaces =
         isItf ? new DexTypeList(new DexType[] {wrappingType}) : DexTypeList.empty();
     return new DexProgramClass(
-        wrapperField.field.holder,
+        wrapperField.holder(),
         null,
         new SynthesizedOrigin("Desugared library API Converter", getClass()),
         ClassAccessFlags.fromSharedAccessFlags(
@@ -260,23 +251,24 @@
     //   return v3;
     Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
     for (DexEncodedMethod dexEncodedMethod : dexMethods) {
-      DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
+      DexClass holderClass = appView.definitionFor(dexEncodedMethod.holder());
       boolean isInterface;
       if (holderClass == null) {
-        assert appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface()
-            .containsValue(dexEncodedMethod.method.holder);
+        assert appView
+            .options()
+            .desugaredLibraryConfiguration
+            .getEmulateLibraryInterface()
+            .containsValue(dexEncodedMethod.holder());
         isInterface = true;
       } else {
         isInterface = holderClass.isInterface();
       }
       DexMethod methodToInstall =
           factory.createMethod(
-              wrapperField.field.holder,
-              dexEncodedMethod.method.proto,
-              dexEncodedMethod.method.name);
+              wrapperField.holder(), dexEncodedMethod.method.proto, dexEncodedMethod.method.name);
       CfCode cfCode;
       if (dexEncodedMethod.isFinal()) {
-        invalidWrappers.add(wrapperField.field.holder);
+        invalidWrappers.add(wrapperField.holder());
         finalMethods.add(dexEncodedMethod.method);
         continue;
       } else {
@@ -312,15 +304,15 @@
     //   return v3;
     Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
     for (DexEncodedMethod dexEncodedMethod : dexMethods) {
-      DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
+      DexClass holderClass = appView.definitionFor(dexEncodedMethod.holder());
       assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
       boolean isInterface = holderClass == null || holderClass.isInterface();
       DexMethod methodToInstall =
           DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
-              dexEncodedMethod.method, wrapperField.field.holder, appView);
+              dexEncodedMethod.method, wrapperField.holder(), appView);
       CfCode cfCode;
       if (dexEncodedMethod.isFinal()) {
-        invalidWrappers.add(wrapperField.field.holder);
+        invalidWrappers.add(wrapperField.holder());
         finalMethods.add(dexEncodedMethod.method);
         continue;
       } else {
@@ -342,7 +334,7 @@
     List<DexEncodedMethod> generatedMethods = new ArrayList<>();
     // Generate only abstract methods for library override detection.
     for (DexEncodedMethod dexEncodedMethod : dexMethods) {
-      DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
+      DexClass holderClass = appView.definitionFor(dexEncodedMethod.holder());
       assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
       if (!dexEncodedMethod.isFinal()) {
         DexMethod methodToInstall =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 1a4c416..e9b9b7e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -62,6 +62,7 @@
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 //
@@ -143,6 +144,37 @@
     initializeEmulatedInterfaceVariables();
   }
 
+  public static void checkForAssumedLibraryTypes(AppInfo appInfo, InternalOptions options) {
+    DesugaredLibraryConfiguration config = options.desugaredLibraryConfiguration;
+    BiConsumer<DexType, DexType> registerEntry = registerMapEntry(appInfo);
+    config.getEmulateLibraryInterface().forEach(registerEntry);
+    config.getCustomConversions().forEach(registerEntry);
+    config.getRetargetCoreLibMember().forEach((method, types) -> types.forEach(registerEntry));
+  }
+
+  private static BiConsumer<DexType, DexType> registerMapEntry(AppInfo appInfo) {
+    return (key, value) -> {
+      registerType(appInfo, key);
+      registerType(appInfo, value);
+    };
+  }
+
+  private static void registerType(AppInfo appInfo, DexType type) {
+    appInfo.dexItemFactory().registerTypeNeededForDesugaring(type);
+    DexClass clazz = appInfo.definitionFor(type);
+    if (clazz != null && clazz.isLibraryClass() && clazz.isInterface()) {
+      clazz.forEachMethod(
+          m -> {
+            if (m.isDefaultMethod()) {
+              appInfo.dexItemFactory().registerTypeNeededForDesugaring(m.method.proto.returnType);
+              for (DexType param : m.method.proto.parameters.values) {
+                appInfo.dexItemFactory().registerTypeNeededForDesugaring(param);
+              }
+            }
+          });
+    }
+  }
+
   private void initializeEmulatedInterfaceVariables() {
     Map<DexType, DexType> emulateLibraryInterface =
         options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
@@ -238,7 +270,7 @@
                         invokeStatic.outValue(), invokeStatic.arguments()));
                 requiredDispatchClasses
                     .computeIfAbsent(clazz.asLibraryClass(), k -> Sets.newConcurrentHashSet())
-                    .add(appInfo.definitionFor(encodedMethod.method.holder).asProgramClass());
+                    .add(appInfo.definitionFor(encodedMethod.holder()).asProgramClass());
               }
             } else {
               instructions.replaceCurrentInstruction(
@@ -269,8 +301,7 @@
             // WARNING: This may result in incorrect code on older platforms!
             // Retarget call to an appropriate method of companion class.
             DexMethod amendedMethod =
-                amendDefaultMethod(
-                    appInfo.definitionFor(encodedMethod.method.holder), invokedMethod);
+                amendDefaultMethod(appInfo.definitionFor(encodedMethod.holder()), invokedMethod);
             instructions.replaceCurrentInstruction(
                 new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
                     invokeSuper.outValue(), invokeSuper.arguments()));
@@ -284,9 +315,9 @@
               DexEncodedMethod dexEncodedMethod =
                   appView
                       .appInfo()
-                      .lookupSuperTarget(invokeSuper.getInvokedMethod(), code.method.method.holder);
+                      .lookupSuperTarget(invokeSuper.getInvokedMethod(), code.method.holder());
               if (dexEncodedMethod != null) {
-                DexClass dexClass = appView.definitionFor(dexEncodedMethod.method.holder);
+                DexClass dexClass = appView.definitionFor(dexEncodedMethod.holder());
                 if (dexClass != null && dexClass.isLibraryClass()) {
                   // Rewriting is required because the super invoke resolves into a missing
                   // method (method is on desugared library). Find out if it needs to be
@@ -418,8 +449,8 @@
       // interfaces.
       return null;
     }
-    if (!singleTarget.isAbstract() && isEmulatedInterface(singleTarget.method.holder)) {
-      return singleTarget.method.holder;
+    if (!singleTarget.isAbstract() && isEmulatedInterface(singleTarget.holder())) {
+      return singleTarget.holder();
     }
     return null;
   }
@@ -616,8 +647,8 @@
         }
         emulationMethods.add(
             DexEncodedMethod.toEmulateDispatchLibraryMethod(
-                method.method.holder,
-                emulateInterfaceLibraryMethod(method.method, method.method.holder, factory),
+                method.holder(),
+                emulateInterfaceLibraryMethod(method.method, method.holder(), factory),
                 companionMethod,
                 libraryMethod,
                 extraDispatchCases,
@@ -719,6 +750,10 @@
     return type.descriptor.toString().endsWith(COMPANION_CLASS_NAME_SUFFIX + ";");
   }
 
+  public static boolean isEmulatedLibraryClassType(DexType type) {
+    return type.descriptor.toString().endsWith(EMULATE_LIBRARY_CLASS_NAME_SUFFIX + ";");
+  }
+
   // Gets the interface class for a companion class `type`.
   private DexType getInterfaceClassType(DexType type) {
     return getInterfaceClassType(type, factory);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 4409c30..ab5a9cd 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -320,7 +320,7 @@
     } else {
       assert code.isCfCode();
       for (CfInstruction insn : code.asCfCode().getInstructions()) {
-        if (insn instanceof CfInvoke && ((CfInvoke) insn).isInvokeSuper(method.method.holder)) {
+        if (insn instanceof CfInvoke && ((CfInvoke) insn).isInvokeSuper(method.holder())) {
           return false;
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index d440b01..2ac43f0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -96,7 +96,7 @@
         invocationContext == null ? null : lookupTargetMethod(appInfo, invocationContext);
     if (targetMethod != null) {
       targetAccessFlags = targetMethod.accessFlags.copy();
-      targetHolder = targetMethod.method.holder;
+      targetHolder = targetMethod.holder();
     } else {
       targetAccessFlags = null;
       targetHolder = null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 4ee35d6..d6d698e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -135,7 +135,7 @@
    */
   public void desugarLambdas(DexEncodedMethod encodedMethod, IRCode code) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
-    DexType currentType = encodedMethod.method.holder;
+    DexType currentType = encodedMethod.holder();
     ListIterator<BasicBlock> blocks = code.listIterator();
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
@@ -145,7 +145,7 @@
         if (instruction.isInvokeCustom()) {
           InvokeCustom invoke = instruction.asInvokeCustom();
           LambdaDescriptor descriptor =
-              inferLambdaDescriptor(invoke.getCallSite(), encodedMethod.method.holder);
+              inferLambdaDescriptor(invoke.getCallSite(), encodedMethod.holder());
           if (descriptor == LambdaDescriptor.MATCH_FAILED) {
             continue;
           }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index b78757d..f4e1f4a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -51,7 +51,6 @@
   private static final String NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX =
       NEST_ACCESS_NAME_PREFIX + "sfput";
   public static final String NEST_CONSTRUCTOR_NAME = NEST_ACCESS_NAME_PREFIX + "Constructor";
-  private static final String FULL_NEST_CONTRUCTOR_NAME = "L" + NEST_CONSTRUCTOR_NAME + ";";
 
   protected final AppView<?> appView;
   // Following maps are there to avoid creating the bridges multiple times
@@ -161,7 +160,7 @@
 
   private DexProgramClass createNestAccessConstructor() {
     return new DexProgramClass(
-        appView.dexItemFactory().createType(FULL_NEST_CONTRUCTOR_NAME),
+        appView.dexItemFactory().nestConstructorType,
         null,
         new SynthesizedOrigin("Nest based access desugaring", getClass()),
         // Make the synthesized class public since shared in the whole program.
@@ -238,7 +237,7 @@
   }
 
   private DexMethod computeFieldBridge(DexEncodedField field, boolean isGet) {
-    DexType holderType = field.field.holder;
+    DexType holderType = field.holder();
     DexType fieldType = field.field.type;
     int bridgeParameterCount =
         BooleanUtils.intValue(!field.isStatic()) + BooleanUtils.intValue(!isGet);
@@ -259,10 +258,10 @@
   boolean invokeRequiresRewriting(DexEncodedMethod method, DexClass contextClass) {
     assert method != null;
     // Rewrite only when targeting other nest members private fields.
-    if (!method.accessFlags.isPrivate() || method.method.holder == contextClass.type) {
+    if (!method.accessFlags.isPrivate() || method.holder() == contextClass.type) {
       return false;
     }
-    DexClass methodHolder = definitionFor(method.method.holder);
+    DexClass methodHolder = definitionFor(method.holder());
     assert methodHolder != null; // from encodedMethod
     return methodHolder.getNestHost() == contextClass.getNestHost();
   }
@@ -270,10 +269,10 @@
   boolean fieldAccessRequiresRewriting(DexEncodedField field, DexClass contextClass) {
     assert field != null;
     // Rewrite only when targeting other nest members private fields.
-    if (!field.accessFlags.isPrivate() || field.field.holder == contextClass.type) {
+    if (!field.accessFlags.isPrivate() || field.holder() == contextClass.type) {
       return false;
     }
-    DexClass fieldHolder = definitionFor(field.field.holder);
+    DexClass fieldHolder = definitionFor(field.holder());
     assert fieldHolder != null; // from encodedField
     return fieldHolder.getNestHost() == contextClass.getNestHost();
   }
@@ -295,7 +294,7 @@
   }
 
   DexMethod ensureFieldAccessBridge(DexEncodedField field, boolean isGet) {
-    DexClass holder = definitionFor(field.field.holder);
+    DexClass holder = definitionFor(field.holder());
     assert holder != null;
     DexMethod bridgeMethod = computeFieldBridge(field, isGet);
     if (holderRequiresBridge(holder)) {
@@ -314,7 +313,7 @@
 
   DexMethod ensureInvokeBridge(DexEncodedMethod method) {
     // We add bridges only when targeting other nest members.
-    DexClass holder = definitionFor(method.method.holder);
+    DexClass holder = definitionFor(method.holder());
     assert holder != null;
     DexMethod bridgeMethod;
     if (method.isInstanceInitializer()) {
@@ -508,7 +507,7 @@
     }
 
     public DexType getHolder() {
-      return field.field.holder;
+      return field.holder();
     }
 
     public DexField getField() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
index 1c3b6a2..6f5c3f2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
@@ -18,6 +18,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
 
 public abstract class PrefixRewritingMapper {
 
@@ -47,6 +48,8 @@
 
   public abstract boolean isRewriting();
 
+  public abstract void forAllRewrittenTypes(Consumer<DexType> consumer);
+
   public static class DesugarPrefixRewritingMapper extends PrefixRewritingMapper {
 
     private final Set<DexType> notRewritten = Sets.newConcurrentHashSet();
@@ -70,6 +73,11 @@
       return factory.createString("L" + DescriptorUtils.getBinaryNameFromJavaType(prefix));
     }
 
+    @Override
+    public void forAllRewrittenTypes(Consumer<DexType> consumer) {
+      rewritten.keySet().forEach(consumer);
+    }
+
     private void validatePrefixes(Map<String, String> initialPrefixes) {
       String[] prefixes = initialPrefixes.keySet().toArray(new String[0]);
       for (int i = 0; i < prefixes.length; i++) {
@@ -192,5 +200,8 @@
     public boolean isRewriting() {
       return false;
     }
+
+    @Override
+    public void forAllRewrittenTypes(Consumer<DexType> consumer) {}
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
index c4c9bd7..ca10fe6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
@@ -56,7 +56,7 @@
   private <E> void addDeferredBridgesAndMapMethods(
       Map<E, DexEncodedMethod> bridges, BiConsumer<E, DexMethod> lensInserter) {
     for (Map.Entry<E, DexEncodedMethod> entry : bridges.entrySet()) {
-      DexClass holder = definitionFor(entry.getValue().method.holder);
+      DexClass holder = definitionFor(entry.getValue().holder());
       assert holder != null && holder.isProgramClass();
       holder.asProgramClass().addMethod(entry.getValue());
       lensInserter.accept(entry.getKey(), entry.getValue().method);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index 17250cf..e477fe3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -99,7 +99,7 @@
           new InvokeStatic(twrCloseResourceMethod, null, invoke.inValues()));
 
       // Mark as a class referencing utility class.
-      referencingClasses.add(appInfo.definitionFor(code.method.method.holder).asProgramClass());
+      referencingClasses.add(appInfo.definitionFor(code.method.holder()).asProgramClass());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 023ef1c..bc3c320 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -36,6 +36,7 @@
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.Cmp;
 import com.android.tools.r8.ir.code.If;
@@ -47,6 +48,59 @@
 
 public final class BackportedMethods {
 
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    factory.createSynthesizedType("Ljava/lang/ArithmeticException;");
+    factory.createSynthesizedType("Ljava/lang/AssertionError;");
+    factory.createSynthesizedType("Ljava/lang/Double;");
+    factory.createSynthesizedType("Ljava/lang/ExceptionInInitializerError;");
+    factory.createSynthesizedType("Ljava/lang/Float;");
+    factory.createSynthesizedType("Ljava/lang/IllegalAccessException;");
+    factory.createSynthesizedType("Ljava/lang/IllegalArgumentException;");
+    factory.createSynthesizedType("Ljava/lang/IndexOutOfBoundsException;");
+    factory.createSynthesizedType("Ljava/lang/Integer;");
+    factory.createSynthesizedType("Ljava/lang/Iterable;");
+    factory.createSynthesizedType("Ljava/lang/Long;");
+    factory.createSynthesizedType("Ljava/lang/Math;");
+    factory.createSynthesizedType("Ljava/lang/NoSuchMethodException;");
+    factory.createSynthesizedType("Ljava/lang/NullPointerException;");
+    factory.createSynthesizedType("Ljava/lang/NumberFormatException;");
+    factory.createSynthesizedType("Ljava/lang/Runnable;");
+    factory.createSynthesizedType("Ljava/lang/SecurityException;");
+    factory.createSynthesizedType("Ljava/lang/reflect/InvocationTargetException;");
+    factory.createSynthesizedType("Ljava/lang/reflect/Method;");
+    factory.createSynthesizedType("Ljava/util/AbstractMap$SimpleImmutableEntry;");
+    factory.createSynthesizedType("Ljava/util/ArrayList;");
+    factory.createSynthesizedType("Ljava/util/Arrays;");
+    factory.createSynthesizedType("Ljava/util/Collection;");
+    factory.createSynthesizedType("Ljava/util/Collections;");
+    factory.createSynthesizedType("Ljava/util/Comparator;");
+    factory.createSynthesizedType("Ljava/util/Enumeration;");
+    factory.createSynthesizedType("Ljava/util/HashMap;");
+    factory.createSynthesizedType("Ljava/util/HashSet;");
+    factory.createSynthesizedType("Ljava/util/Iterator;");
+    factory.createSynthesizedType("Ljava/util/List;");
+    factory.createSynthesizedType("Ljava/util/ListIterator;");
+    factory.createSynthesizedType("Ljava/util/Map$Entry;");
+    factory.createSynthesizedType("Ljava/util/Map;");
+    factory.createSynthesizedType("Ljava/util/Objects;");
+    factory.createSynthesizedType("Ljava/util/Optional;");
+    factory.createSynthesizedType("Ljava/util/OptionalDouble;");
+    factory.createSynthesizedType("Ljava/util/OptionalInt;");
+    factory.createSynthesizedType("Ljava/util/OptionalLong;");
+    factory.createSynthesizedType("Ljava/util/Set;");
+    factory.createSynthesizedType("Ljava/util/function/Consumer;");
+    factory.createSynthesizedType("Ljava/util/function/DoubleConsumer;");
+    factory.createSynthesizedType("Ljava/util/function/IntConsumer;");
+    factory.createSynthesizedType("Ljava/util/function/LongConsumer;");
+    factory.createSynthesizedType("Ljava/util/function/Supplier;");
+    factory.createSynthesizedType("Ljava/util/stream/DoubleStream;");
+    factory.createSynthesizedType("Ljava/util/stream/IntStream;");
+    factory.createSynthesizedType("Ljava/util/stream/LongStream;");
+    factory.createSynthesizedType("Ljava/util/stream/Stream;");
+    factory.createSynthesizedType("[Ljava/lang/Class;");
+    factory.createSynthesizedType("[Ljava/lang/Object;");
+  }
+
   public static CfCode BooleanMethods_compare(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
@@ -235,7 +289,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Math;"),
+                    options.itemFactory.createType("Ljava/lang/Math;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType,
                         options.itemFactory.intType,
@@ -397,15 +451,15 @@
                 false),
             new CfConstString(options.itemFactory.createString("close")),
             new CfConstNumber(0, ValueType.INT),
-            new CfNewArray(options.itemFactory.createSynthesizedType("[Ljava/lang/Class;")),
+            new CfNewArray(options.itemFactory.createType("[Ljava/lang/Class;")),
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
                     options.itemFactory.classType,
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/lang/reflect/Method;"),
+                        options.itemFactory.createType("Ljava/lang/reflect/Method;"),
                         options.itemFactory.stringType,
-                        options.itemFactory.createSynthesizedType("[Ljava/lang/Class;")),
+                        options.itemFactory.createType("[Ljava/lang/Class;")),
                     options.itemFactory.createString("getMethod")),
                 false),
             new CfStore(ValueType.OBJECT, 2),
@@ -413,15 +467,15 @@
             new CfLoad(ValueType.OBJECT, 2),
             new CfLoad(ValueType.OBJECT, 1),
             new CfConstNumber(0, ValueType.INT),
-            new CfNewArray(options.itemFactory.createSynthesizedType("[Ljava/lang/Object;")),
+            new CfNewArray(options.itemFactory.createType("[Ljava/lang/Object;")),
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/reflect/Method;"),
+                    options.itemFactory.createType("Ljava/lang/reflect/Method;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType,
                         options.itemFactory.objectType,
-                        options.itemFactory.createSynthesizedType("[Ljava/lang/Object;")),
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
                     options.itemFactory.createString("invoke")),
                 false),
             new CfStackInstruction(CfStackInstruction.Opcode.Pop),
@@ -430,7 +484,7 @@
             label5,
             new CfStore(ValueType.OBJECT, 2),
             label6,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/AssertionError;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/AssertionError;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -477,7 +531,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/AssertionError;"),
+                    options.itemFactory.createType("Ljava/lang/AssertionError;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType,
                         options.itemFactory.stringType,
@@ -488,7 +542,7 @@
             label7,
             new CfStore(ValueType.OBJECT, 2),
             label8,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/AssertionError;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/AssertionError;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -535,7 +589,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/AssertionError;"),
+                    options.itemFactory.createType("Ljava/lang/AssertionError;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType,
                         options.itemFactory.stringType,
@@ -550,8 +604,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/reflect/InvocationTargetException;"),
+                    options.itemFactory.createType("Ljava/lang/reflect/InvocationTargetException;"),
                     options.itemFactory.createProto(options.itemFactory.throwableType),
                     options.itemFactory.createString("getCause")),
                 false),
@@ -577,40 +630,36 @@
                 label2,
                 label4,
                 ImmutableList.of(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NoSuchMethodException;")),
+                    options.itemFactory.createType("Ljava/lang/NoSuchMethodException;")),
+                ImmutableList.of(label5)),
+            new CfTryCatch(
+                label2,
+                label4,
+                ImmutableList.of(options.itemFactory.createType("Ljava/lang/SecurityException;")),
                 ImmutableList.of(label5)),
             new CfTryCatch(
                 label2,
                 label4,
                 ImmutableList.of(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/SecurityException;")),
-                ImmutableList.of(label5)),
-            new CfTryCatch(
-                label2,
-                label4,
-                ImmutableList.of(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IllegalAccessException;")),
+                    options.itemFactory.createType("Ljava/lang/IllegalAccessException;")),
                 ImmutableList.of(label7)),
             new CfTryCatch(
                 label2,
                 label4,
                 ImmutableList.of(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IllegalArgumentException;")),
+                    options.itemFactory.createType("Ljava/lang/IllegalArgumentException;")),
                 ImmutableList.of(label7)),
             new CfTryCatch(
                 label2,
                 label4,
                 ImmutableList.of(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/ExceptionInInitializerError;")),
+                    options.itemFactory.createType("Ljava/lang/ExceptionInInitializerError;")),
                 ImmutableList.of(label7)),
             new CfTryCatch(
                 label2,
                 label4,
                 ImmutableList.of(
-                    options.itemFactory.createSynthesizedType(
+                    options.itemFactory.createType(
                         "Ljava/lang/reflect/InvocationTargetException;")),
                 ImmutableList.of(label9)),
             new CfTryCatch(
@@ -635,14 +684,14 @@
         6,
         ImmutableList.of(
             label0,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/util/ArrayList;")),
+            new CfNew(options.itemFactory.createType("Ljava/util/ArrayList;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             new CfArrayLength(),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/ArrayList;"),
+                    options.itemFactory.createType("Ljava/util/ArrayList;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.intType),
                     options.itemFactory.createString("<init>")),
@@ -670,7 +719,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -678,7 +727,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/ArrayList;"),
+                    options.itemFactory.createType("Ljava/util/ArrayList;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.objectType),
                     options.itemFactory.createString("add")),
@@ -692,10 +741,10 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/List;"),
-                        options.itemFactory.createSynthesizedType("Ljava/util/List;")),
+                        options.itemFactory.createType("Ljava/util/List;"),
+                        options.itemFactory.createType("Ljava/util/List;")),
                     options.itemFactory.createString("unmodifiableList")),
                 false),
             new CfReturn(ValueType.OBJECT),
@@ -717,15 +766,14 @@
         ImmutableList.of(
             label0,
             new CfNew(
-                options.itemFactory.createSynthesizedType(
-                    "Ljava/util/AbstractMap$SimpleImmutableEntry;")),
+                options.itemFactory.createType("Ljava/util/AbstractMap$SimpleImmutableEntry;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             label1,
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -735,7 +783,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -743,8 +791,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/util/AbstractMap$SimpleImmutableEntry;"),
+                    options.itemFactory.createType("Ljava/util/AbstractMap$SimpleImmutableEntry;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType,
                         options.itemFactory.objectType,
@@ -775,14 +822,14 @@
         8,
         ImmutableList.of(
             label0,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/util/HashMap;")),
+            new CfNew(options.itemFactory.createType("Ljava/util/HashMap;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             new CfArrayLength(),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashMap;"),
+                    options.itemFactory.createType("Ljava/util/HashMap;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.intType),
                     options.itemFactory.createString("<init>")),
@@ -809,14 +856,14 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Map$Entry;"),
+                    options.itemFactory.createType("Ljava/util/Map$Entry;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("getKey")),
                 true),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -827,14 +874,14 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Map$Entry;"),
+                    options.itemFactory.createType("Ljava/util/Map$Entry;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("getValue")),
                 true),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -847,7 +894,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashMap;"),
+                    options.itemFactory.createType("Ljava/util/HashMap;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType,
                         options.itemFactory.objectType,
@@ -856,8 +903,7 @@
                 false),
             new CfIf(If.Type.EQ, ValueType.OBJECT, label7),
             label6,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/IllegalArgumentException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/IllegalArgumentException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -896,8 +942,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IllegalArgumentException;"),
+                    options.itemFactory.createType("Ljava/lang/IllegalArgumentException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -911,10 +956,10 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Map;"),
-                        options.itemFactory.createSynthesizedType("Ljava/util/Map;")),
+                        options.itemFactory.createType("Ljava/util/Map;"),
+                        options.itemFactory.createType("Ljava/util/Map;")),
                     options.itemFactory.createString("unmodifiableMap")),
                 false),
             new CfReturn(ValueType.OBJECT),
@@ -938,14 +983,14 @@
         6,
         ImmutableList.of(
             label0,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/util/HashSet;")),
+            new CfNew(options.itemFactory.createType("Ljava/util/HashSet;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             new CfArrayLength(),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashSet;"),
+                    options.itemFactory.createType("Ljava/util/HashSet;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.intType),
                     options.itemFactory.createString("<init>")),
@@ -973,7 +1018,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -981,15 +1026,14 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashSet;"),
+                    options.itemFactory.createType("Ljava/util/HashSet;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.objectType),
                     options.itemFactory.createString("add")),
                 false),
             new CfIf(If.Type.NE, ValueType.INT, label5),
             label4,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/IllegalArgumentException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/IllegalArgumentException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -1028,8 +1072,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IllegalArgumentException;"),
+                    options.itemFactory.createType("Ljava/lang/IllegalArgumentException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -1043,10 +1086,10 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Set;"),
-                        options.itemFactory.createSynthesizedType("Ljava/util/Set;")),
+                        options.itemFactory.createType("Ljava/util/Set;"),
+                        options.itemFactory.createType("Ljava/util/Set;")),
                     options.itemFactory.createString("unmodifiableSet")),
                 false),
             new CfReturn(ValueType.OBJECT),
@@ -1069,20 +1112,20 @@
         4,
         ImmutableList.of(
             label0,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/util/ArrayList;")),
+            new CfNew(options.itemFactory.createType("Ljava/util/ArrayList;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collection;"),
+                    options.itemFactory.createType("Ljava/util/Collection;"),
                     options.itemFactory.createProto(options.itemFactory.intType),
                     options.itemFactory.createString("size")),
                 true),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/ArrayList;"),
+                    options.itemFactory.createType("Ljava/util/ArrayList;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.intType),
                     options.itemFactory.createString("<init>")),
@@ -1093,9 +1136,9 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collection;"),
+                    options.itemFactory.createType("Ljava/util/Collection;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Iterator;")),
+                        options.itemFactory.createType("Ljava/util/Iterator;")),
                     options.itemFactory.createString("iterator")),
                 true),
             new CfStore(ValueType.OBJECT, 2),
@@ -1104,7 +1147,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("hasNext")),
                 true),
@@ -1113,7 +1156,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("next")),
                 true),
@@ -1124,7 +1167,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -1132,7 +1175,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/ArrayList;"),
+                    options.itemFactory.createType("Ljava/util/ArrayList;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.objectType),
                     options.itemFactory.createString("add")),
@@ -1145,10 +1188,10 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/List;"),
-                        options.itemFactory.createSynthesizedType("Ljava/util/List;")),
+                        options.itemFactory.createType("Ljava/util/List;"),
+                        options.itemFactory.createType("Ljava/util/List;")),
                     options.itemFactory.createString("unmodifiableList")),
                 false),
             new CfReturn(ValueType.OBJECT),
@@ -1174,20 +1217,20 @@
         4,
         ImmutableList.of(
             label0,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/util/HashMap;")),
+            new CfNew(options.itemFactory.createType("Ljava/util/HashMap;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Map;"),
+                    options.itemFactory.createType("Ljava/util/Map;"),
                     options.itemFactory.createProto(options.itemFactory.intType),
                     options.itemFactory.createString("size")),
                 true),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashMap;"),
+                    options.itemFactory.createType("Ljava/util/HashMap;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.intType),
                     options.itemFactory.createString("<init>")),
@@ -1198,17 +1241,17 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Map;"),
+                    options.itemFactory.createType("Ljava/util/Map;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Set;")),
+                        options.itemFactory.createType("Ljava/util/Set;")),
                     options.itemFactory.createString("entrySet")),
                 true),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Set;"),
+                    options.itemFactory.createType("Ljava/util/Set;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Iterator;")),
+                        options.itemFactory.createType("Ljava/util/Iterator;")),
                     options.itemFactory.createString("iterator")),
                 true),
             new CfStore(ValueType.OBJECT, 2),
@@ -1217,7 +1260,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("hasNext")),
                 true),
@@ -1226,11 +1269,11 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("next")),
                 true),
-            new CfCheckCast(options.itemFactory.createSynthesizedType("Ljava/util/Map$Entry;")),
+            new CfCheckCast(options.itemFactory.createType("Ljava/util/Map$Entry;")),
             new CfStore(ValueType.OBJECT, 3),
             label3,
             new CfLoad(ValueType.OBJECT, 1),
@@ -1239,14 +1282,14 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Map$Entry;"),
+                    options.itemFactory.createType("Ljava/util/Map$Entry;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("getKey")),
                 true),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -1256,14 +1299,14 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Map$Entry;"),
+                    options.itemFactory.createType("Ljava/util/Map$Entry;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("getValue")),
                 true),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -1272,7 +1315,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashMap;"),
+                    options.itemFactory.createType("Ljava/util/HashMap;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType,
                         options.itemFactory.objectType,
@@ -1287,10 +1330,10 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Map;"),
-                        options.itemFactory.createSynthesizedType("Ljava/util/Map;")),
+                        options.itemFactory.createType("Ljava/util/Map;"),
+                        options.itemFactory.createType("Ljava/util/Map;")),
                     options.itemFactory.createString("unmodifiableMap")),
                 false),
             new CfReturn(ValueType.OBJECT),
@@ -1313,20 +1356,20 @@
         4,
         ImmutableList.of(
             label0,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/util/HashSet;")),
+            new CfNew(options.itemFactory.createType("Ljava/util/HashSet;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collection;"),
+                    options.itemFactory.createType("Ljava/util/Collection;"),
                     options.itemFactory.createProto(options.itemFactory.intType),
                     options.itemFactory.createString("size")),
                 true),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashSet;"),
+                    options.itemFactory.createType("Ljava/util/HashSet;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.intType),
                     options.itemFactory.createString("<init>")),
@@ -1337,9 +1380,9 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collection;"),
+                    options.itemFactory.createType("Ljava/util/Collection;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Iterator;")),
+                        options.itemFactory.createType("Ljava/util/Iterator;")),
                     options.itemFactory.createString("iterator")),
                 true),
             new CfStore(ValueType.OBJECT, 2),
@@ -1348,7 +1391,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("hasNext")),
                 true),
@@ -1357,7 +1400,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("next")),
                 true),
@@ -1368,7 +1411,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -1376,7 +1419,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashSet;"),
+                    options.itemFactory.createType("Ljava/util/HashSet;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.objectType),
                     options.itemFactory.createString("add")),
@@ -1389,10 +1432,10 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Set;"),
-                        options.itemFactory.createSynthesizedType("Ljava/util/Set;")),
+                        options.itemFactory.createType("Ljava/util/Set;"),
+                        options.itemFactory.createType("Ljava/util/Set;")),
                     options.itemFactory.createString("unmodifiableSet")),
                 false),
             new CfReturn(ValueType.OBJECT),
@@ -1413,18 +1456,18 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/List;")),
+                        options.itemFactory.createType("Ljava/util/List;")),
                     options.itemFactory.createString("emptyList")),
                 false),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Enumeration;"),
-                        options.itemFactory.createSynthesizedType("Ljava/util/Collection;")),
+                        options.itemFactory.createType("Ljava/util/Enumeration;"),
+                        options.itemFactory.createType("Ljava/util/Collection;")),
                     options.itemFactory.createString("enumeration")),
                 false),
             new CfReturn(ValueType.OBJECT)),
@@ -1443,17 +1486,17 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/List;")),
+                        options.itemFactory.createType("Ljava/util/List;")),
                     options.itemFactory.createString("emptyList")),
                 false),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/List;"),
+                    options.itemFactory.createType("Ljava/util/List;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Iterator;")),
+                        options.itemFactory.createType("Ljava/util/Iterator;")),
                     options.itemFactory.createString("iterator")),
                 true),
             new CfReturn(ValueType.OBJECT)),
@@ -1473,17 +1516,17 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/List;")),
+                        options.itemFactory.createType("Ljava/util/List;")),
                     options.itemFactory.createString("emptyList")),
                 false),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/List;"),
+                    options.itemFactory.createType("Ljava/util/List;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/ListIterator;")),
+                        options.itemFactory.createType("Ljava/util/ListIterator;")),
                     options.itemFactory.createString("listIterator")),
                 true),
             new CfReturn(ValueType.OBJECT)),
@@ -1505,7 +1548,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Double;"),
+                    options.itemFactory.createType("Ljava/lang/Double;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType, options.itemFactory.doubleType),
                     options.itemFactory.createString("doubleToLongBits")),
@@ -1539,7 +1582,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Double;"),
+                    options.itemFactory.createType("Ljava/lang/Double;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.doubleType),
                     options.itemFactory.createString("isInfinite")),
@@ -1549,7 +1592,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Double;"),
+                    options.itemFactory.createType("Ljava/lang/Double;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.doubleType),
                     options.itemFactory.createString("isNaN")),
@@ -1581,7 +1624,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Float;"),
+                    options.itemFactory.createType("Ljava/lang/Float;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.floatType),
                     options.itemFactory.createString("isInfinite")),
@@ -1591,7 +1634,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Float;"),
+                    options.itemFactory.createType("Ljava/lang/Float;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.floatType),
                     options.itemFactory.createString("isNaN")),
@@ -1666,7 +1709,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Integer;"),
+                    options.itemFactory.createType("Ljava/lang/Integer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType,
                         options.itemFactory.intType,
@@ -1726,7 +1769,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Integer;"),
+                    options.itemFactory.createType("Ljava/lang/Integer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType,
                         options.itemFactory.stringType,
@@ -1794,7 +1837,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.stringType,
@@ -1810,8 +1853,7 @@
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
             new CfIf(If.Type.EQ, ValueType.INT, label5),
             label4,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -1878,7 +1920,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;"),
+                    options.itemFactory.createType("Ljava/lang/NumberFormatException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -1959,7 +2001,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Integer;"),
+                    options.itemFactory.createType("Ljava/lang/Integer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.stringType,
                         options.itemFactory.intType,
@@ -1994,7 +2036,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.stringType,
                         options.itemFactory.longType,
@@ -2033,7 +2075,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType,
                         options.itemFactory.longType,
@@ -2184,7 +2226,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.stringType,
@@ -2240,14 +2282,13 @@
             new CfLoad(ValueType.INT, 2),
             new CfIf(If.Type.NE, ValueType.INT, label3),
             label2,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfConstString(options.itemFactory.createString("empty string")),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;"),
+                    options.itemFactory.createType("Ljava/lang/NumberFormatException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -2261,8 +2302,7 @@
             new CfConstNumber(36, ValueType.INT),
             new CfIfCmp(If.Type.LE, ValueType.INT, label5),
             label4,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfConstString(options.itemFactory.createString("illegal radix: ")),
             new CfLoad(ValueType.INT, 1),
@@ -2285,7 +2325,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;"),
+                    options.itemFactory.createType("Ljava/lang/NumberFormatException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -2298,7 +2338,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.longType,
@@ -2366,14 +2406,13 @@
             new CfConstNumber(-1, ValueType.INT),
             new CfIfCmp(If.Type.NE, ValueType.INT, label15),
             label14,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;"),
+                    options.itemFactory.createType("Ljava/lang/NumberFormatException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -2400,7 +2439,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.longType,
@@ -2410,8 +2449,7 @@
             new CfNumberConversion(NumericType.LONG, NumericType.INT),
             new CfIfCmp(If.Type.LE, ValueType.INT, label18),
             label17,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfConstString(options.itemFactory.createString("Too large for unsigned long: ")),
             new CfLoad(ValueType.OBJECT, 0),
@@ -2426,7 +2464,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;"),
+                    options.itemFactory.createType("Ljava/lang/NumberFormatException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -2570,7 +2608,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.stringType,
                         options.itemFactory.longType,
@@ -2636,7 +2674,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.stringType,
                         options.itemFactory.longType,
@@ -2674,7 +2712,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Integer;"),
+                    options.itemFactory.createType("Ljava/lang/Integer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType, options.itemFactory.intType),
                     options.itemFactory.createString("numberOfTrailingZeros")),
@@ -2741,7 +2779,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.longType,
@@ -2870,12 +2908,12 @@
             new CfLoad(ValueType.INT, 4),
             new CfReturn(ValueType.INT),
             label4,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -2934,12 +2972,12 @@
             new CfLoad(ValueType.LONG, 4),
             new CfReturn(ValueType.LONG),
             label7,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -2964,12 +3002,12 @@
             new CfConstNumber(-2147483648, ValueType.INT),
             new CfIfCmp(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3000,12 +3038,12 @@
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
             new CfIf(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3155,7 +3193,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Math;"),
+                    options.itemFactory.createType("Ljava/lang/Math;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.longType,
@@ -3287,7 +3325,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Math;"),
+                    options.itemFactory.createType("Ljava/lang/Math;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.longType,
@@ -3316,12 +3354,12 @@
             new CfConstNumber(2147483647, ValueType.INT),
             new CfIfCmp(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3352,12 +3390,12 @@
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
             new CfIf(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3405,12 +3443,12 @@
             new CfLoad(ValueType.INT, 4),
             new CfReturn(ValueType.INT),
             label4,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3449,7 +3487,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType, options.itemFactory.longType),
                     options.itemFactory.createString("numberOfLeadingZeros")),
@@ -3461,7 +3499,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType, options.itemFactory.longType),
                     options.itemFactory.createString("numberOfLeadingZeros")),
@@ -3472,7 +3510,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType, options.itemFactory.longType),
                     options.itemFactory.createString("numberOfLeadingZeros")),
@@ -3485,7 +3523,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType, options.itemFactory.longType),
                     options.itemFactory.createString("numberOfLeadingZeros")),
@@ -3545,12 +3583,12 @@
             new CfLoad(ValueType.LONG, 5),
             new CfReturn(ValueType.LONG),
             label15,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3575,7 +3613,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Math;"),
+                    options.itemFactory.createType("Ljava/lang/Math;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.longType,
@@ -3727,12 +3765,12 @@
             new CfConstNumber(-2147483648, ValueType.INT),
             new CfIfCmp(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3762,12 +3800,12 @@
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
             new CfIf(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3795,7 +3833,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Math;"),
+                    options.itemFactory.createType("Ljava/lang/Math;"),
                     options.itemFactory.createProto(
                         options.itemFactory.doubleType, options.itemFactory.doubleType),
                     options.itemFactory.createString("nextUp")),
@@ -3821,7 +3859,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Math;"),
+                    options.itemFactory.createType("Ljava/lang/Math;"),
                     options.itemFactory.createProto(
                         options.itemFactory.floatType, options.itemFactory.floatType),
                     options.itemFactory.createString("nextUp")),
@@ -3866,12 +3904,12 @@
             new CfLoad(ValueType.INT, 4),
             new CfReturn(ValueType.INT),
             label4,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3930,12 +3968,12 @@
             new CfLoad(ValueType.LONG, 4),
             new CfReturn(ValueType.LONG),
             label7,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3967,12 +4005,12 @@
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
             new CfIf(If.Type.EQ, ValueType.INT, label3),
             label2,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -4009,8 +4047,7 @@
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
             new CfIfCmp(If.Type.LE, ValueType.INT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/IndexOutOfBoundsException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/IndexOutOfBoundsException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -4103,8 +4140,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IndexOutOfBoundsException;"),
+                    options.itemFactory.createType("Ljava/lang/IndexOutOfBoundsException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -4138,8 +4174,7 @@
             new CfLoad(ValueType.INT, 2),
             new CfIfCmp(If.Type.LE, ValueType.INT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/IndexOutOfBoundsException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/IndexOutOfBoundsException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -4214,8 +4249,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IndexOutOfBoundsException;"),
+                    options.itemFactory.createType("Ljava/lang/IndexOutOfBoundsException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -4246,8 +4280,7 @@
             new CfLoad(ValueType.INT, 1),
             new CfIfCmp(If.Type.LT, ValueType.INT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/IndexOutOfBoundsException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/IndexOutOfBoundsException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -4304,8 +4337,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IndexOutOfBoundsException;"),
+                    options.itemFactory.createType("Ljava/lang/IndexOutOfBoundsException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -4342,7 +4374,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Comparator;"),
+                    options.itemFactory.createType("Ljava/util/Comparator;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType,
                         options.itemFactory.objectType,
@@ -4428,7 +4460,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.booleanArrayType,
@@ -4457,7 +4489,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.byteArrayType,
@@ -4486,7 +4518,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.charArrayType,
@@ -4515,7 +4547,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.doubleArrayType,
@@ -4544,7 +4576,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.floatArrayType,
@@ -4573,7 +4605,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.intArrayType,
@@ -4602,7 +4634,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.longArrayType,
@@ -4631,7 +4663,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.shortArrayType,
@@ -4647,24 +4679,24 @@
             new CfReturn(ValueType.INT),
             label34,
             new CfLoad(ValueType.OBJECT, 0),
-            new CfInstanceOf(options.itemFactory.createSynthesizedType("[Ljava/lang/Object;")),
+            new CfInstanceOf(options.itemFactory.createType("[Ljava/lang/Object;")),
             new CfIf(If.Type.EQ, ValueType.INT, label38),
             label35,
             new CfLoad(ValueType.OBJECT, 1),
-            new CfInstanceOf(options.itemFactory.createSynthesizedType("[Ljava/lang/Object;")),
+            new CfInstanceOf(options.itemFactory.createType("[Ljava/lang/Object;")),
             new CfIf(If.Type.EQ, ValueType.INT, label36),
             new CfLoad(ValueType.OBJECT, 0),
-            new CfCheckCast(options.itemFactory.createSynthesizedType("[Ljava/lang/Object;")),
+            new CfCheckCast(options.itemFactory.createType("[Ljava/lang/Object;")),
             new CfLoad(ValueType.OBJECT, 1),
-            new CfCheckCast(options.itemFactory.createSynthesizedType("[Ljava/lang/Object;")),
+            new CfCheckCast(options.itemFactory.createType("[Ljava/lang/Object;")),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
-                        options.itemFactory.createSynthesizedType("[Ljava/lang/Object;"),
-                        options.itemFactory.createSynthesizedType("[Ljava/lang/Object;")),
+                        options.itemFactory.createType("[Ljava/lang/Object;"),
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
                     options.itemFactory.createString("deepEquals")),
                 false),
             new CfIf(If.Type.EQ, ValueType.INT, label36),
@@ -4831,7 +4863,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType,
                         options.itemFactory.objectType,
@@ -4866,19 +4898,18 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType,
                         options.itemFactory.objectType,
                         options.itemFactory.stringType),
                     options.itemFactory.createString("requireNonNull")),
                 false),
-            new CfCheckCast(
-                options.itemFactory.createSynthesizedType("Ljava/util/function/Supplier;")),
+            new CfCheckCast(options.itemFactory.createType("Ljava/util/function/Supplier;")),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/function/Supplier;"),
+                    options.itemFactory.createType("Ljava/util/function/Supplier;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("get")),
                 true),
@@ -4889,7 +4920,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType,
                         options.itemFactory.objectType,
@@ -4917,14 +4948,13 @@
             new CfLoad(ValueType.OBJECT, 0),
             new CfIf(If.Type.NE, ValueType.OBJECT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NullPointerException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createType("Ljava/lang/NullPointerException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -4952,7 +4982,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.stringType,
                         options.itemFactory.objectType,
@@ -5012,7 +5042,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Optional;"),
+                    options.itemFactory.createType("Ljava/util/Optional;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5023,14 +5053,14 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Optional;"),
+                    options.itemFactory.createType("Ljava/util/Optional;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("get")),
                 false),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/function/Consumer;"),
+                    options.itemFactory.createType("Ljava/util/function/Consumer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.objectType),
                     options.itemFactory.createString("accept")),
@@ -5041,7 +5071,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Runnable;"),
+                    options.itemFactory.createType("Ljava/lang/Runnable;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("run")),
                 true),
@@ -5069,7 +5099,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalDouble;"),
+                    options.itemFactory.createType("Ljava/util/OptionalDouble;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5080,15 +5110,14 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalDouble;"),
+                    options.itemFactory.createType("Ljava/util/OptionalDouble;"),
                     options.itemFactory.createProto(options.itemFactory.doubleType),
                     options.itemFactory.createString("getAsDouble")),
                 false),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/util/function/DoubleConsumer;"),
+                    options.itemFactory.createType("Ljava/util/function/DoubleConsumer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.doubleType),
                     options.itemFactory.createString("accept")),
@@ -5099,7 +5128,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Runnable;"),
+                    options.itemFactory.createType("Ljava/lang/Runnable;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("run")),
                 true),
@@ -5127,7 +5156,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalInt;"),
+                    options.itemFactory.createType("Ljava/util/OptionalInt;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5138,14 +5167,14 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalInt;"),
+                    options.itemFactory.createType("Ljava/util/OptionalInt;"),
                     options.itemFactory.createProto(options.itemFactory.intType),
                     options.itemFactory.createString("getAsInt")),
                 false),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/function/IntConsumer;"),
+                    options.itemFactory.createType("Ljava/util/function/IntConsumer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.intType),
                     options.itemFactory.createString("accept")),
@@ -5156,7 +5185,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Runnable;"),
+                    options.itemFactory.createType("Ljava/lang/Runnable;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("run")),
                 true),
@@ -5184,7 +5213,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalLong;"),
+                    options.itemFactory.createType("Ljava/util/OptionalLong;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5195,14 +5224,14 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalLong;"),
+                    options.itemFactory.createType("Ljava/util/OptionalLong;"),
                     options.itemFactory.createProto(options.itemFactory.longType),
                     options.itemFactory.createString("getAsLong")),
                 false),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/function/LongConsumer;"),
+                    options.itemFactory.createType("Ljava/util/function/LongConsumer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.longType),
                     options.itemFactory.createString("accept")),
@@ -5213,7 +5242,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Runnable;"),
+                    options.itemFactory.createType("Ljava/lang/Runnable;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("run")),
                 true),
@@ -5239,7 +5268,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Optional;"),
+                    options.itemFactory.createType("Ljava/util/Optional;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5270,7 +5299,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalDouble;"),
+                    options.itemFactory.createType("Ljava/util/OptionalDouble;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5301,7 +5330,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalInt;"),
+                    options.itemFactory.createType("Ljava/util/OptionalInt;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5332,7 +5361,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalLong;"),
+                    options.itemFactory.createType("Ljava/util/OptionalLong;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5365,7 +5394,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -5376,7 +5405,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Optional;"),
+                    options.itemFactory.createType("Ljava/util/Optional;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5389,23 +5418,23 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/function/Supplier;"),
+                    options.itemFactory.createType("Ljava/util/function/Supplier;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("get")),
                 true),
-            new CfCheckCast(options.itemFactory.createSynthesizedType("Ljava/util/Optional;")),
+            new CfCheckCast(options.itemFactory.createType("Ljava/util/Optional;")),
             new CfStore(ValueType.OBJECT, 2),
             label4,
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
                 false),
-            new CfCheckCast(options.itemFactory.createSynthesizedType("Ljava/util/Optional;")),
+            new CfCheckCast(options.itemFactory.createType("Ljava/util/Optional;")),
             new CfReturn(ValueType.OBJECT),
             label5),
         ImmutableList.of(),
@@ -5427,7 +5456,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Optional;"),
+                    options.itemFactory.createType("Ljava/util/Optional;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5437,16 +5466,16 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Optional;"),
+                    options.itemFactory.createType("Ljava/util/Optional;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("get")),
                 false),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;"),
+                    options.itemFactory.createType("Ljava/util/stream/Stream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;"),
+                        options.itemFactory.createType("Ljava/util/stream/Stream;"),
                         options.itemFactory.objectType),
                     options.itemFactory.createString("of")),
                 true),
@@ -5455,9 +5484,9 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;"),
+                    options.itemFactory.createType("Ljava/util/stream/Stream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;")),
+                        options.itemFactory.createType("Ljava/util/stream/Stream;")),
                     options.itemFactory.createString("empty")),
                 true),
             new CfReturn(ValueType.OBJECT),
@@ -5481,7 +5510,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalDouble;"),
+                    options.itemFactory.createType("Ljava/util/OptionalDouble;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5491,17 +5520,16 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalDouble;"),
+                    options.itemFactory.createType("Ljava/util/OptionalDouble;"),
                     options.itemFactory.createProto(options.itemFactory.doubleType),
                     options.itemFactory.createString("getAsDouble")),
                 false),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/DoubleStream;"),
+                    options.itemFactory.createType("Ljava/util/stream/DoubleStream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType(
-                            "Ljava/util/stream/DoubleStream;"),
+                        options.itemFactory.createType("Ljava/util/stream/DoubleStream;"),
                         options.itemFactory.doubleType),
                     options.itemFactory.createString("of")),
                 true),
@@ -5510,10 +5538,9 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/DoubleStream;"),
+                    options.itemFactory.createType("Ljava/util/stream/DoubleStream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType(
-                            "Ljava/util/stream/DoubleStream;")),
+                        options.itemFactory.createType("Ljava/util/stream/DoubleStream;")),
                     options.itemFactory.createString("empty")),
                 true),
             new CfReturn(ValueType.OBJECT),
@@ -5537,7 +5564,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalInt;"),
+                    options.itemFactory.createType("Ljava/util/OptionalInt;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5547,16 +5574,16 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalInt;"),
+                    options.itemFactory.createType("Ljava/util/OptionalInt;"),
                     options.itemFactory.createProto(options.itemFactory.intType),
                     options.itemFactory.createString("getAsInt")),
                 false),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/IntStream;"),
+                    options.itemFactory.createType("Ljava/util/stream/IntStream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/IntStream;"),
+                        options.itemFactory.createType("Ljava/util/stream/IntStream;"),
                         options.itemFactory.intType),
                     options.itemFactory.createString("of")),
                 true),
@@ -5565,9 +5592,9 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/IntStream;"),
+                    options.itemFactory.createType("Ljava/util/stream/IntStream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/IntStream;")),
+                        options.itemFactory.createType("Ljava/util/stream/IntStream;")),
                     options.itemFactory.createString("empty")),
                 true),
             new CfReturn(ValueType.OBJECT),
@@ -5591,7 +5618,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalLong;"),
+                    options.itemFactory.createType("Ljava/util/OptionalLong;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5601,16 +5628,16 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalLong;"),
+                    options.itemFactory.createType("Ljava/util/OptionalLong;"),
                     options.itemFactory.createProto(options.itemFactory.longType),
                     options.itemFactory.createString("getAsLong")),
                 false),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/LongStream;"),
+                    options.itemFactory.createType("Ljava/util/stream/LongStream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/LongStream;"),
+                        options.itemFactory.createType("Ljava/util/stream/LongStream;"),
                         options.itemFactory.longType),
                     options.itemFactory.createString("of")),
                 true),
@@ -5619,9 +5646,9 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/LongStream;"),
+                    options.itemFactory.createType("Ljava/util/stream/LongStream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/LongStream;")),
+                        options.itemFactory.createType("Ljava/util/stream/LongStream;")),
                     options.itemFactory.createString("empty")),
                 true),
             new CfReturn(ValueType.OBJECT),
@@ -5723,9 +5750,9 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;"),
+                    options.itemFactory.createType("Ljava/util/stream/Stream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;")),
+                        options.itemFactory.createType("Ljava/util/stream/Stream;")),
                     options.itemFactory.createString("empty")),
                 true),
             new CfGoto(label2),
@@ -5734,9 +5761,9 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;"),
+                    options.itemFactory.createType("Ljava/util/stream/Stream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;"),
+                        options.itemFactory.createType("Ljava/util/stream/Stream;"),
                         options.itemFactory.objectType),
                     options.itemFactory.createString("of")),
                 true),
@@ -5849,14 +5876,13 @@
             label0,
             new CfLoad(ValueType.OBJECT, 0),
             new CfIf(If.Type.NE, ValueType.OBJECT, label1),
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NullPointerException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfConstString(options.itemFactory.createString("delimiter")),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createType("Ljava/lang/NullPointerException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -5965,14 +5991,13 @@
             label0,
             new CfLoad(ValueType.OBJECT, 0),
             new CfIf(If.Type.NE, ValueType.OBJECT, label1),
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NullPointerException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfConstString(options.itemFactory.createString("delimiter")),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createType("Ljava/lang/NullPointerException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -5994,9 +6019,9 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Iterable;"),
+                    options.itemFactory.createType("Ljava/lang/Iterable;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Iterator;")),
+                        options.itemFactory.createType("Ljava/util/Iterator;")),
                     options.itemFactory.createString("iterator")),
                 true),
             new CfStore(ValueType.OBJECT, 3),
@@ -6005,7 +6030,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("hasNext")),
                 true),
@@ -6016,7 +6041,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("next")),
                 true),
@@ -6036,7 +6061,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("hasNext")),
                 true),
@@ -6060,7 +6085,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("next")),
                 true),
@@ -6115,8 +6140,7 @@
             new CfLoad(ValueType.INT, 1),
             new CfIf(If.Type.GE, ValueType.INT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/IllegalArgumentException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/IllegalArgumentException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -6155,8 +6179,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IllegalArgumentException;"),
+                    options.itemFactory.createType("Ljava/lang/IllegalArgumentException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
index da20118..a356e8b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -112,18 +112,18 @@
           break;
         case PACKAGE:
           if (entry.value.size == 0) {
-            if (!method.method.holder.descriptor.contains(dexItemFactory.descriptorSeparator)) {
+            if (!method.holder().descriptor.contains(dexItemFactory.descriptorSeparator)) {
               transformation = entry.entry.getTransformation();
             }
-          } else if (method.method.holder.descriptor.startsWith(entry.value)) {
+          } else if (method.holder().descriptor.startsWith(entry.value)) {
             transformation = entry.entry.getTransformation();
           }
           break;
         case CLASS:
-          if (method.method.holder.descriptor.equals(entry.value)) {
+          if (method.holder().descriptor.equals(entry.value)) {
             transformation = entry.entry.getTransformation();
           }
-          if (isDescriptorForClassOrInnerClass(entry.value, method.method.holder.descriptor)) {
+          if (isDescriptorForClassOrInnerClass(entry.value, method.holder().descriptor)) {
             transformation = entry.entry.getTransformation();
           }
           break;
@@ -311,7 +311,7 @@
     if (method.isClassInitializer()) {
       clinit = method;
     } else {
-      DexClass clazz = appView.definitionFor(method.method.holder);
+      DexClass clazz = appView.definitionFor(method.holder());
       if (clazz == null) {
         return;
       }
@@ -328,7 +328,7 @@
       if (current.isInvokeMethod()) {
         InvokeMethod invoke = current.asInvokeMethod();
         if (invoke.getInvokedMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
-          if (method.method.holder == dexItemFactory.kotlin.kotlinAssertions) {
+          if (method.holder() == dexItemFactory.kotlin.kotlinAssertions) {
             rewriteKotlinAssertionEnable(code, transformation, iterator, invoke);
           } else {
             iterator.replaceCurrentInstruction(code.createIntConstant(0));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 368b58a..35895b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -98,7 +98,7 @@
             continue;
           }
         }
-        Collection<DexEncodedMethod> targets = invoke.lookupTargets(appView, context.method.holder);
+        Collection<DexEncodedMethod> targets = invoke.lookupTargets(appView, context.holder());
         assert invoke.isInvokeMethodWithDynamicDispatch()
             // For other invocation types, the size of targets should be at most one.
             || targets == null || targets.size() <= 1;
@@ -231,7 +231,7 @@
       if (abstractValue.isSingleValue()) {
         assert appView.options().enablePropagationOfConstantsAtCallSites;
         SingleValue singleValue = abstractValue.asSingleValue();
-        if (singleValue.isMaterializableInContext(appView, code.method.method.holder)) {
+        if (singleValue.isMaterializableInContext(appView, code.method.holder())) {
           Instruction replacement =
               singleValue.createMaterializingInstruction(appView, code, instr);
           replacement.setPosition(instr.getPosition());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index 025c178..ce853a2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -133,7 +133,7 @@
       return ClassInitializerDefaultsResult.empty();
     }
 
-    DexClass clazz = appView.definitionFor(method.method.holder);
+    DexClass clazz = appView.definitionFor(method.holder());
     if (clazz == null) {
       return ClassInitializerDefaultsResult.empty();
     }
@@ -160,7 +160,7 @@
       Value value = put.value();
       if (unnecessaryStaticPuts.contains(put)) {
         if (fieldType == dexItemFactory.stringType) {
-          fieldsWithStaticValues.put(field, getDexStringValue(value, method.method.holder));
+          fieldsWithStaticValues.put(field, getDexStringValue(value, method.holder()));
         } else if (fieldType.isClassType() || fieldType.isArrayType()) {
           if (value.isZero()) {
             fieldsWithStaticValues.put(field, DexValueNull.NULL);
@@ -380,7 +380,7 @@
           } else if (instruction.isStaticGet()) {
             StaticGet get = instruction.asStaticGet();
             DexEncodedField field = appView.appInfo().resolveField(get.getField());
-            if (field != null && field.field.holder == clazz.type) {
+            if (field != null && field.holder() == clazz.type) {
               isReadBefore.add(field.field);
             } else if (instruction.instructionMayHaveSideEffects(appView, clazz.type)) {
               // Reading another field is only OK if the read does not have side-effects.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 5b78d8a..fee1f02 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1115,7 +1115,7 @@
     BasicBlock defaultTarget = theSwitch.fallthroughBlock();
     SwitchCaseEliminator eliminator = null;
     BasicBlockBehavioralSubsumption behavioralSubsumption =
-        new BasicBlockBehavioralSubsumption(appView, code.method.method.holder);
+        new BasicBlockBehavioralSubsumption(appView, code.method.holder());
 
     // Compute the set of switch cases that can be removed.
     for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
@@ -1231,7 +1231,7 @@
         }
 
         // Check if the invoked method is known to return one of its arguments.
-        DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method.method.holder);
+        DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method.holder());
         if (target != null && target.getOptimizationInfo().returnsArgument()) {
           int argumentIndex = target.getOptimizationInfo().getReturnedArgument();
           // Replace the out value of the invoke with the argument and ignore the out value.
@@ -1353,7 +1353,7 @@
     // If the cast type is not accessible in the current context, we should not remove the cast
     // in order to preserve IllegalAccessError. Note that JVM and ART behave differently: see
     // {@link com.android.tools.r8.ir.optimize.checkcast.IllegalAccessErrorTest}.
-    if (!isTypeVisibleFromContext(appView, code.method.method.holder, castType)) {
+    if (!isTypeVisibleFromContext(appView, code.method.holder(), castType)) {
       return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
     }
 
@@ -1410,7 +1410,7 @@
       InstanceOf instanceOf, InstructionListIterator it, IRCode code) {
     // If the instance-of type is not accessible in the current context, we should not remove the
     // instance-of instruction in order to preserve IllegalAccessError.
-    if (!isTypeVisibleFromContext(appView, code.method.method.holder, instanceOf.type())) {
+    if (!isTypeVisibleFromContext(appView, code.method.holder(), instanceOf.type())) {
       return false;
     }
 
@@ -2050,8 +2050,7 @@
           for (ConstInstruction value : values) {
             stringValues.add(value.outValue());
           }
-          Value invokeValue =
-              code.createValue(newArray.outValue().getType(), newArray.getLocalInfo());
+          Value invokeValue = code.createValue(newArray.getOutType(), newArray.getLocalInfo());
           InvokeNewArray invoke =
               new InvokeNewArray(dexItemFactory.stringArrayType, invokeValue, stringValues);
           for (Value value : newArray.inValues()) {
@@ -2469,7 +2468,7 @@
               }
             }
           } else {
-            DexType context = code.method.method.holder;
+            DexType context = code.method.holder();
             AbstractValue abstractValue = lhs.getAbstractValue(appView, context);
             if (abstractValue.isSingleConstClassValue() || abstractValue.isSingleFieldValue()) {
               AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
@@ -2761,7 +2760,7 @@
 
         InvokeMethod invoke = insn.asInvokeMethod();
         DexEncodedMethod singleTarget =
-            invoke.lookupSingleTarget(appView.withLiveness(), code.method.method.holder);
+            invoke.lookupSingleTarget(appView.withLiveness(), code.method.holder());
         if (singleTarget == null || !singleTarget.getOptimizationInfo().neverReturnsNormally()) {
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index 2dff525..c98c262 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -88,7 +88,7 @@
 
   public void canonicalize(AppView<?> appView, IRCode code) {
     DexEncodedMethod method = code.method;
-    DexType context = method.method.holder;
+    DexType context = method.holder();
     Object2ObjectLinkedOpenCustomHashMap<Instruction, List<Value>> valuesDefinedByConstant =
         new Object2ObjectLinkedOpenCustomHashMap<>(
             new Strategy<Instruction>() {
@@ -149,8 +149,7 @@
           continue;
         }
         SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue();
-        if (method.isClassInitializer()
-            && method.method.holder == singleFieldValue.getField().holder) {
+        if (method.isClassInitializer() && method.holder() == singleFieldValue.getField().holder) {
           // Avoid that canonicalization inserts a read before the unique write in the class
           // initializer, as that would change the program behavior.
           continue;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index d225220..d9773d9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -105,7 +105,7 @@
       return true;
     }
 
-    DexClass clazz = appView.definitionFor(singleTarget.method.holder);
+    DexClass clazz = appView.definitionFor(singleTarget.holder());
     if (!clazz.isProgramClass()) {
       if (clazz.isClasspathClass()) {
         whyAreYouNotInliningReporter.reportClasspathMethod();
@@ -211,12 +211,12 @@
     // Don't inline code with references beyond root main dex classes into a root main dex class.
     // If we do this it can increase the size of the main dex dependent classes.
     if (reason != Reason.FORCE
-        && inlineeRefersToClassesNotInMainDex(method.method.holder, singleTarget)) {
+        && inlineeRefersToClassesNotInMainDex(method.holder(), singleTarget)) {
       whyAreYouNotInliningReporter.reportInlineeRefersToClassesNotInMainDex();
       return false;
     }
     assert reason != Reason.FORCE
-        || !inlineeRefersToClassesNotInMainDex(method.method.holder, singleTarget);
+        || !inlineeRefersToClassesNotInMainDex(method.holder(), singleTarget);
     return true;
   }
 
@@ -360,8 +360,8 @@
     // - the current method has already triggered the holder for the target method to be
     //   initialized, or
     // - there is no non-trivial class initializer.
-    DexType targetHolder = target.method.holder;
-    if (appView.appInfo().isSubtype(method.method.holder, targetHolder)) {
+    DexType targetHolder = target.holder();
+    if (appView.appInfo().isSubtype(method.holder(), targetHolder)) {
       return true;
     }
     DexClass clazz = appView.definitionFor(targetHolder);
@@ -374,14 +374,14 @@
           appView.withInitializedClassesInInstanceMethods(
               analysis ->
                   analysis.isClassDefinitelyLoadedInInstanceMethodsOn(
-                      target.method.holder, method.method.holder),
+                      target.holder(), method.holder()),
               false);
       if (targetIsGuaranteedToBeInitialized) {
         return true;
       }
     }
     if (classInitializationAnalysis.isClassDefinitelyLoadedBeforeInstruction(
-        target.method.holder, invoke)) {
+        target.holder(), invoke)) {
       return true;
     }
     // Check for class initializer side effects when loading this class, as inlining might remove
@@ -445,8 +445,8 @@
 
     // Allow inlining a constructor into a constructor of the same class, as the constructor code
     // is expected to adhere to the VM specification.
-    DexType callerMethodHolder = method.method.holder;
-    DexType calleeMethodHolder = inlinee.method.method.holder;
+    DexType callerMethodHolder = method.holder();
+    DexType calleeMethodHolder = inlinee.method.holder();
     // Calling a constructor on the same class from a constructor can always be inlined.
     if (method.isInstanceInitializer() && callerMethodHolder == calleeMethodHolder) {
       return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 82ea8ab..897b85b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -136,7 +136,7 @@
         if (target == null) {
           continue;
         }
-        DexType holderType = target.method.holder;
+        DexType holderType = target.holder();
         DexClass holderClass = appView.definitionFor(holderType);
         // Make sure we are not landing on another interface, e.g., interface's default method.
         if (holderClass == null || holderClass.isInterface()) {
@@ -284,7 +284,7 @@
       // Most likely due to a missing class, or invoke is already as specific as it gets.
       return target;
     }
-    DexClass newTargetClass = appView.definitionFor(newTarget.method.holder);
+    DexClass newTargetClass = appView.definitionFor(newTarget.holder());
     if (newTargetClass == null
         || newTargetClass.isLibraryClass()
         || !canInvokeTargetWithInvokeVirtual(newTarget)
@@ -296,13 +296,12 @@
   }
 
   private boolean canInvokeTargetWithInvokeVirtual(DexEncodedMethod target) {
-    return target.isNonPrivateVirtualMethod()
-        && appView.isInterface(target.method.holder).isFalse();
+    return target.isNonPrivateVirtualMethod() && appView.isInterface(target.holder()).isFalse();
   }
 
   private boolean hasAccessToInvokeTargetFromContext(DexEncodedMethod target, DexType context) {
     assert !target.accessFlags.isPrivate();
-    DexType holder = target.method.holder;
+    DexType holder = target.holder();
     if (holder == context) {
       // It is always safe to invoke a method from the same enclosing class.
       return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index 8048e3a..d518ef6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -78,8 +78,7 @@
               TypeElement.fromDexType(invokedMethod.holder, definitelyNotNull(), appView);
           dynamicLowerBoundType = null;
         } else {
-          DexEncodedMethod singleTarget =
-              invoke.lookupSingleTarget(appView, code.method.method.holder);
+          DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.method.holder());
           if (singleTarget == null) {
             continue;
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
index 5dba772..76ca615 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -110,7 +110,7 @@
               }
             });
 
-    DexType context = code.method.method.holder;
+    DexType context = code.method.holder();
     // Collect invocations along with arguments.
     for (BasicBlock block : code.blocks) {
       for (Instruction current : block.getInstructions()) {
@@ -143,7 +143,7 @@
           }
           // Verify that the target method is accessible in the current context.
           if (!isMemberVisibleFromOriginalContext(
-              appView, context, target.method.holder, target.accessFlags)) {
+              appView, context, target.holder(), target.accessFlags)) {
             continue;
           }
           // Check if the call could throw a NPE as a result of the receiver being null.
@@ -207,7 +207,7 @@
                 }
               }
               Value canonicalizedValue =
-                  code.createValue(invoke.outValue().getType(), invoke.outValue().getLocalInfo());
+                  code.createValue(invoke.getOutType(), invoke.outValue().getLocalInfo());
               Invoke canonicalizedInvoke =
                   Invoke.create(
                       invoke.getType(),
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 b56dece..33d982a 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
@@ -164,7 +164,7 @@
         new InliningConstraints(appView, GraphLense.getIdentityLense());
     for (Instruction instruction : code.instructions()) {
       ConstraintWithTarget state =
-          instructionAllowedForInlining(instruction, inliningConstraints, method.method.holder);
+          instructionAllowedForInlining(instruction, inliningConstraints, method.holder());
       if (state == ConstraintWithTarget.NEVER) {
         result = state;
         break;
@@ -192,12 +192,12 @@
   }
 
   boolean hasInliningAccess(DexEncodedMethod method, DexEncodedMethod target) {
-    if (!isVisibleWithFlags(target.method.holder, method.method.holder, target.accessFlags)) {
+    if (!isVisibleWithFlags(target.holder(), method.holder(), target.accessFlags)) {
       return false;
     }
     // The class needs also to be visible for us to have access.
-    DexClass targetClass = appView.definitionFor(target.method.holder);
-    return isVisibleWithFlags(target.method.holder, method.method.holder, targetClass.accessFlags);
+    DexClass targetClass = appView.definitionFor(target.holder());
+    return isVisibleWithFlags(target.holder(), method.holder(), targetClass.accessFlags);
   }
 
   private boolean isVisibleWithFlags(DexType target, DexType context, AccessFlags flags) {
@@ -710,7 +710,7 @@
           lockValue =
               code.createValue(
                   TypeElement.fromDexType(dexItemFactory.objectType, definitelyNotNull(), appView));
-          monitorEnterBlockIterator.add(new ConstClass(lockValue, target.method.holder));
+          monitorEnterBlockIterator.add(new ConstClass(lockValue, target.holder()));
         } else {
           lockValue = entryBlock.getInstructions().getFirst().asArgument().outValue();
         }
@@ -738,10 +738,10 @@
 
       if (inliningIRProvider.shouldApplyCodeRewritings(code.method)) {
         assert lensCodeRewriter != null;
-        lensCodeRewriter.rewrite(code, target);
         if (enumUnboxer != null) {
           enumUnboxer.rewriteCode(code);
         }
+        lensCodeRewriter.rewrite(code, target);
       }
       if (lambdaMerger != null) {
         lambdaMerger.rewriteCodeForInlining(target, code, context, inliningIRProvider);
@@ -964,7 +964,7 @@
         if (current.isInvokeMethod()) {
           InvokeMethod invoke = current.asInvokeMethod();
           // TODO(b/142116551): This should be equivalent to invoke.lookupSingleTarget()!
-          DexEncodedMethod singleTarget = oracle.lookupSingleTarget(invoke, context.method.holder);
+          DexEncodedMethod singleTarget = oracle.lookupSingleTarget(invoke, context.holder());
           if (singleTarget == null) {
             WhyAreYouNotInliningReporter.handleInvokeWithUnknownTarget(invoke, appView, context);
             continue;
@@ -1115,8 +1115,8 @@
         // method holder as a fallback.
         receiverType = invoke.getInvokedMethod().holder;
       }
-      if (!appView.appInfo().isSubtype(receiverType, target.method.holder)) {
-        return target.method.holder;
+      if (!appView.appInfo().isSubtype(receiverType, target.holder())) {
+        return target.holder();
       }
     }
     return null;
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 a1307a0..ea77caf 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
@@ -197,7 +197,7 @@
     DexEncodedMethod alternativeDexEncodedMethod =
         lookupFunction.apply(singleResolutionResult, superContext);
     if (alternativeDexEncodedMethod != null
-        && alternativeDexEncodedMethod.method.holder == superContext.type) {
+        && alternativeDexEncodedMethod.holder() == superContext.type) {
       return alternativeDexEncodedMethod;
     }
     return null;
@@ -354,8 +354,8 @@
       // `invocationContext` has access to the definition of the field.
       //
       // See, for example, InlineNonReboundFieldTest (b/128604123).
-      if (field.holder != target.field.holder) {
-        DexType actualFieldHolder = graphLense.lookupType(target.field.holder);
+      if (field.holder != target.holder()) {
+        DexType actualFieldHolder = graphLense.lookupType(target.holder());
         fieldConstraintWithTarget =
             ConstraintWithTarget.meet(
                 fieldConstraintWithTarget,
@@ -379,7 +379,7 @@
       return ConstraintWithTarget.ALWAYS;
     }
     if (target != null) {
-      DexType methodHolder = graphLense.lookupType(target.method.holder);
+      DexType methodHolder = graphLense.lookupType(target.holder());
       DexClass methodClass = appView.definitionFor(methodHolder);
       if (methodClass != null) {
         if (!allowStaticInterfaceMethodCalls && methodClass.isInterface() && target.hasCode()) {
@@ -423,7 +423,7 @@
       return ConstraintWithTarget.NEVER;
     }
 
-    DexType methodHolder = graphLense.lookupType(resolutionTarget.method.holder);
+    DexType methodHolder = graphLense.lookupType(resolutionTarget.holder());
     DexClass methodClass = appView.definitionFor(methodHolder);
     assert methodClass != null;
     ConstraintWithTarget methodConstraintWithTarget =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 4bf7a6d..bf7aa9b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.optimize;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinition;
@@ -14,7 +15,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -139,10 +139,9 @@
           .createMaterializingInstruction(appView, code, instruction);
     }
 
-    TypeElement typeLattice = instruction.outValue().getType();
     if (returnValueRule.isField()) {
       DexField field = returnValueRule.getField();
-      assert typeLattice == TypeElement.fromDexType(field.type, Nullability.maybeNull(), appView);
+      assert instruction.getOutType() == TypeElement.fromDexType(field.type, maybeNull(), appView);
 
       DexEncodedField staticField = appView.appInfo().lookupStaticTarget(field.holder, field);
       if (staticField == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
index c6694f4..508e394 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
@@ -46,7 +46,7 @@
     InstructionListIterator iterator = code.instructionListIterator();
     DexClass callerHolderClass = appView.definitionFor(callerHolder);
     assert callerHolderClass != null;
-    assert code.method.method.holder != callerHolder;
+    assert code.method.holder() != callerHolder;
     while (iterator.hasNext()) {
       Instruction instruction = iterator.next();
       if (instruction.isInvokeDirect()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 15904c5..2e41703 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -98,8 +98,7 @@
             }
           }
 
-          DexEncodedMethod singleTarget =
-              invoke.lookupSingleTarget(appView, code.method.method.holder);
+          DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.method.holder());
           if (singleTarget != null) {
             MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index ab9fa71..d9ebcf6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -862,7 +862,7 @@
       // See whether we could move this invoke somewhere else. We reuse the logic from inlining
       // here, as the constraints are the same.
       ConstraintWithTarget constraint =
-          invoke.inliningConstraint(inliningConstraints, method.method.holder);
+          invoke.inliningConstraint(inliningConstraints, method.holder());
       if (constraint != ConstraintWithTarget.ALWAYS) {
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index de9d4cc..81b32e8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -4,12 +4,15 @@
 
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.utils.PredicateUtils.not;
+
 import com.android.tools.r8.errors.Unreachable;
 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.DexField;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -29,12 +32,11 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
-import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
+import java.util.ArrayDeque;
+import java.util.Deque;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -56,18 +58,11 @@
   private final Set<Value> affectedValues = Sets.newIdentityHashSet();
 
   // Maps keeping track of fields that have an already loaded value at basic block entry.
-  private final Map<BasicBlock, Set<DexType>> activeInitializedClassesAtEntry =
-      new IdentityHashMap<>();
-  private final Map<BasicBlock, Map<FieldAndObject, FieldValue>> activeInstanceFieldsAtEntry =
-      new IdentityHashMap<>();
-  private final Map<BasicBlock, Map<DexField, FieldValue>> activeStaticFieldsAtEntry =
-      new IdentityHashMap<>();
+  private final Map<BasicBlock, State> activeStateAtExit = new IdentityHashMap<>();
 
   // Maps keeping track of fields with already loaded values for the current block during
   // elimination.
-  private Set<DexType> activeInitializedClasses;
-  private Map<FieldAndObject, FieldValue> activeInstanceFieldValues;
-  private Map<DexField, FieldValue> activeStaticFieldValues;
+  private State activeState;
 
   public RedundantFieldLoadElimination(AppView<?> appView, IRCode code) {
     this.appView = appView;
@@ -78,7 +73,7 @@
 
   public static boolean shouldRun(AppView<?> appView, IRCode code) {
     return appView.options().enableRedundantFieldLoadElimination
-        && code.metadata().mayHaveFieldGet();
+        && (code.metadata().mayHaveFieldGet() || code.metadata().mayHaveInitClass());
   }
 
   private interface FieldValue {
@@ -145,41 +140,28 @@
     }
   }
 
-  private boolean couldBeVolatile(DexField field) {
-    DexEncodedField definition;
+  private DexEncodedField resolveField(DexField field) {
     if (appView.enableWholeProgramOptimizations()) {
-      definition = appView.appInfo().resolveField(field);
-    } else {
-      if (field.holder != method.method.holder) {
-        return true;
-      }
-      definition = appView.definitionFor(field);
+      return appView.appInfo().resolveField(field);
     }
-    return definition == null || definition.accessFlags.isVolatile();
+    if (field.holder == method.holder()) {
+      return appView.definitionFor(field);
+    }
+    return null;
   }
 
   public void run() {
-    DexType context = method.method.holder;
+    DexType context = method.holder();
     for (BasicBlock block : dominatorTree.getSortedBlocks()) {
-      activeInitializedClasses =
-          activeInitializedClassesAtEntry.containsKey(block)
-              ? activeInitializedClassesAtEntry.get(block)
-              : Sets.newIdentityHashSet();
-      activeInstanceFieldValues =
-          activeInstanceFieldsAtEntry.containsKey(block)
-              ? activeInstanceFieldsAtEntry.get(block)
-              : new HashMap<>();
-      activeStaticFieldValues =
-          activeStaticFieldsAtEntry.containsKey(block)
-              ? activeStaticFieldsAtEntry.get(block)
-              : new IdentityHashMap<>();
+      computeActiveStateOnBlockEntry(block);
       InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction instruction = it.next();
         if (instruction.isFieldInstruction()) {
           DexField field = instruction.asFieldInstruction().getField();
-          if (couldBeVolatile(field)) {
-            killAllActiveFields();
+          DexEncodedField definition = resolveField(field);
+          if (definition == null || definition.isVolatile()) {
+            killAllNonFinalActiveFields();
             continue;
           }
 
@@ -190,56 +172,71 @@
             }
             Value object = instanceGet.object().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-            if (activeInstanceFieldValues.containsKey(fieldAndObject)) {
-              FieldValue replacement = activeInstanceFieldValues.get(fieldAndObject);
+            FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
+            if (replacement != null) {
               replacement.eliminateRedundantRead(it, instanceGet);
             } else {
-              activeInstanceFieldValues.put(fieldAndObject, new ExistingValue(instanceGet.value()));
+              activeState.putNonFinalInstanceField(
+                  fieldAndObject, new ExistingValue(instanceGet.value()));
             }
           } else if (instruction.isInstancePut()) {
             InstancePut instancePut = instruction.asInstancePut();
             // An instance-put instruction can potentially write the given field on all objects
             // because of aliases.
-            killActiveFields(instancePut);
+            killNonFinalActiveFields(instancePut);
             // ... but at least we know the field value for this particular object.
             Value object = instancePut.object().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-            activeInstanceFieldValues.put(fieldAndObject, new ExistingValue(instancePut.value()));
+            ExistingValue value = new ExistingValue(instancePut.value());
+            if (definition.isFinal()) {
+              assert method.isInstanceInitializer() || verifyWasInstanceInitializer();
+              activeState.putFinalInstanceField(fieldAndObject, value);
+            } else {
+              activeState.putNonFinalInstanceField(fieldAndObject, value);
+            }
           } else if (instruction.isStaticGet()) {
             StaticGet staticGet = instruction.asStaticGet();
             if (staticGet.outValue().hasLocalInfo()) {
               continue;
             }
-            if (activeStaticFieldValues.containsKey(field)) {
-              FieldValue replacement = activeStaticFieldValues.get(field);
+            FieldValue replacement = activeState.getStaticFieldValue(field);
+            if (replacement != null) {
               replacement.eliminateRedundantRead(it, staticGet);
             } else {
               // A field get on a different class can cause <clinit> to run and change static
               // field values.
-              killActiveFields(staticGet);
-              activeStaticFieldValues.put(field, new ExistingValue(staticGet.value()));
+              killNonFinalActiveFields(staticGet);
+              activeState.putNonFinalStaticField(field, new ExistingValue(staticGet.value()));
             }
           } else if (instruction.isStaticPut()) {
             StaticPut staticPut = instruction.asStaticPut();
             // A field put on a different class can cause <clinit> to run and change static
             // field values.
-            killActiveFields(staticPut);
-            activeStaticFieldValues.put(field, new ExistingValue(staticPut.value()));
+            killNonFinalActiveFields(staticPut);
+            ExistingValue value = new ExistingValue(staticPut.value());
+            if (definition.isFinal()) {
+              assert method.isClassInitializer();
+              activeState.putFinalStaticField(field, value);
+            } else {
+              activeState.putNonFinalStaticField(field, value);
+            }
           }
         } else if (instruction.isInitClass()) {
           InitClass initClass = instruction.asInitClass();
           assert !initClass.outValue().hasAnyUsers();
-          if (activeInitializedClasses.contains(initClass.getClassValue())) {
+          DexType clazz = initClass.getClassValue();
+          if (activeState.isClassInitialized(clazz)) {
             it.removeOrReplaceByDebugLocalRead();
           }
+          activeState.markClassAsInitialized(clazz);
         } else if (instruction.isMonitor()) {
           if (instruction.asMonitor().isEnter()) {
-            killAllActiveFields();
+            killAllNonFinalActiveFields();
           }
         } else if (instruction.isInvokeDirect()) {
           handleInvokeDirect(instruction.asInvokeDirect());
         } else if (instruction.isInvokeMethod() || instruction.isInvokeCustom()) {
-          killAllActiveFields();
+          killAllNonFinalActiveFields();
         } else if (instruction.isNewInstance()) {
           NewInstance newInstance = instruction.asNewInstance();
           if (newInstance.clazz.classInitializationMayHaveSideEffects(
@@ -247,7 +244,7 @@
               // Types that are a super type of `context` are guaranteed to be initialized already.
               type -> appView.isSubtype(context, type).isTrue(),
               Sets.newIdentityHashSet())) {
-            killAllActiveFields();
+            killAllNonFinalActiveFields();
           }
         } else {
           // If the current instruction could trigger a method invocation, it could also cause field
@@ -286,7 +283,7 @@
               : "Unexpected instruction of type " + instruction.getClass().getTypeName();
         }
       }
-      propagateActiveStateFrom(block);
+      recordActiveStateOnBlockExit(block);
     }
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
@@ -294,22 +291,33 @@
     assert code.isConsistentSSA();
   }
 
+  private boolean verifyWasInstanceInitializer() {
+    VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
+    assert verticallyMergedClasses != null;
+    assert verticallyMergedClasses.isTarget(method.holder());
+    assert appView
+        .dexItemFactory()
+        .isConstructor(appView.graphLense().getOriginalMethodSignature(method.method));
+    assert method.getOptimizationInfo().forceInline();
+    return true;
+  }
+
   private void handleInvokeDirect(InvokeDirect invoke) {
     if (!appView.enableWholeProgramOptimizations()) {
-      killAllActiveFields();
+      killAllNonFinalActiveFields();
       return;
     }
 
     DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.holder());
     if (singleTarget == null || !singleTarget.isInstanceInitializer()) {
-      killAllActiveFields();
+      killAllNonFinalActiveFields();
       return;
     }
 
     InstanceInitializerInfo instanceInitializerInfo =
         singleTarget.getOptimizationInfo().getInstanceInitializerInfo();
     if (instanceInitializerInfo.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
-      killAllActiveFields();
+      killAllNonFinalActiveFields();
     }
 
     InstanceFieldInitializationInfoCollection fieldInitializationInfos =
@@ -325,13 +333,13 @@
                 invoke.getArgument(info.asArgumentInitializationInfo().getArgumentIndex());
             Value object = invoke.getReceiver().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field.field, object);
-            activeInstanceFieldValues.put(fieldAndObject, new ExistingValue(value));
+            activeState.putNonFinalInstanceField(fieldAndObject, new ExistingValue(value));
           } else if (info.isSingleValue()) {
             SingleValue value = info.asSingleValue();
             if (value.isMaterializableInContext(appView, method.holder())) {
               Value object = invoke.getReceiver().getAliasedValue();
               FieldAndObject fieldAndObject = new FieldAndObject(field.field, object);
-              activeInstanceFieldValues.put(fieldAndObject, new MaterializableValue(value));
+              activeState.putNonFinalInstanceField(fieldAndObject, new MaterializableValue(value));
             }
           } else {
             assert info.isTypeInitializationInfo();
@@ -339,84 +347,206 @@
         });
   }
 
-  private void propagateActiveStateFrom(BasicBlock block) {
-    for (BasicBlock successor : block.getSuccessors()) {
+  private void computeActiveStateOnBlockEntry(BasicBlock block) {
+    if (block.isEntry()) {
+      activeState = new State();
+      return;
+    }
+    Deque<State> predecessorExitStates = new ArrayDeque<>(block.getPredecessors().size());
+    for (BasicBlock predecessor : block.getPredecessors()) {
+      State predecessorExitState = activeStateAtExit.get(predecessor);
+      if (predecessorExitState == null) {
+        // Not processed yet.
+        activeState = new State();
+        return;
+      }
       // Allow propagation across exceptional edges, just be careful not to propagate if the
       // throwing instruction is a field instruction.
-      if (successor.getPredecessors().size() == 1) {
-        if (block.hasCatchSuccessor(successor)) {
-          Instruction exceptionalExit = block.exceptionalExit();
-          if (exceptionalExit != null) {
-            if (exceptionalExit.isFieldInstruction()) {
-              killActiveFieldsForExceptionalExit(exceptionalExit.asFieldInstruction());
-            } else if (exceptionalExit.isInitClass()) {
-              killActiveInitializedClassesForExceptionalExit(exceptionalExit.asInitClass());
-            }
+      if (predecessor.hasCatchSuccessor(block)) {
+        Instruction exceptionalExit = predecessor.exceptionalExit();
+        if (exceptionalExit != null) {
+          predecessorExitState = new State(predecessorExitState);
+          if (exceptionalExit.isFieldInstruction()) {
+            predecessorExitState.killActiveFieldsForExceptionalExit(
+                exceptionalExit.asFieldInstruction());
+          } else if (exceptionalExit.isInitClass()) {
+            predecessorExitState.killActiveInitializedClassesForExceptionalExit(
+                exceptionalExit.asInitClass());
           }
         }
-        assert !activeInitializedClassesAtEntry.containsKey(successor);
-        activeInitializedClassesAtEntry.put(
-            successor, SetUtils.newIdentityHashSet(activeInitializedClasses));
-        assert !activeInstanceFieldsAtEntry.containsKey(successor);
-        activeInstanceFieldsAtEntry.put(successor, new HashMap<>(activeInstanceFieldValues));
-        assert !activeStaticFieldsAtEntry.containsKey(successor);
-        activeStaticFieldsAtEntry.put(successor, new IdentityHashMap<>(activeStaticFieldValues));
       }
+      predecessorExitStates.addLast(predecessorExitState);
     }
+    State state = new State(predecessorExitStates.removeFirst());
+    predecessorExitStates.forEach(state::intersect);
+    activeState = state;
   }
 
-  private void killAllActiveFields() {
-    activeInstanceFieldValues.clear();
-    activeStaticFieldValues.clear();
+  private void recordActiveStateOnBlockExit(BasicBlock block) {
+    assert !activeStateAtExit.containsKey(block);
+    activeStateAtExit.put(block, activeState);
   }
 
-  private void killActiveFields(FieldInstruction instruction) {
+  private void killAllNonFinalActiveFields() {
+    activeState.clearNonFinalInstanceFields();
+    activeState.clearNonFinalStaticFields();
+  }
+
+  private void killNonFinalActiveFields(FieldInstruction instruction) {
     DexField field = instruction.getField();
     if (instruction.isInstancePut()) {
       // Remove all the field/object pairs that refer to this field to make sure
       // that we are conservative.
-      List<FieldAndObject> keysToRemove = new ArrayList<>();
-      for (FieldAndObject key : activeInstanceFieldValues.keySet()) {
-        if (key.field == field) {
-          keysToRemove.add(key);
-        }
-      }
-      keysToRemove.forEach(activeInstanceFieldValues::remove);
+      activeState.removeNonFinalInstanceFields(field);
     } else if (instruction.isStaticPut()) {
-      if (field.holder != code.method.method.holder) {
+      if (field.holder != code.method.holder()) {
         // Accessing a static field on a different object could cause <clinit> to run which
         // could modify any static field on any other object.
-        activeStaticFieldValues.clear();
+        activeState.clearNonFinalStaticFields();
       } else {
-        activeStaticFieldValues.remove(field);
+        activeState.removeNonFinalStaticField(field);
       }
     } else if (instruction.isStaticGet()) {
-      if (field.holder != code.method.method.holder) {
+      if (field.holder != code.method.holder()) {
         // Accessing a static field on a different object could cause <clinit> to run which
         // could modify any static field on any other object.
-        activeStaticFieldValues.clear();
+        activeState.clearNonFinalStaticFields();
       }
     } else if (instruction.isInstanceGet()) {
       throw new Unreachable();
     }
   }
 
-  // If a field get instruction throws an exception it did not have an effect on the
-  // value of the field. Therefore, when propagating across exceptional edges for a
-  // field get instruction we have to exclude that field from the set of known
-  // field values.
-  private void killActiveFieldsForExceptionalExit(FieldInstruction instruction) {
-    DexField field = instruction.getField();
-    if (instruction.isInstanceGet()) {
-      Value object = instruction.asInstanceGet().object().getAliasedValue();
-      FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-      activeInstanceFieldValues.remove(fieldAndObject);
-    } else if (instruction.isStaticGet()) {
-      activeStaticFieldValues.remove(field);
-    }
-  }
+  static class State {
 
-  private void killActiveInitializedClassesForExceptionalExit(InitClass instruction) {
-    activeInitializedClasses.remove(instruction.getClassValue());
+    private final Map<FieldAndObject, FieldValue> finalInstanceFieldValues = new HashMap<>();
+
+    private final Map<DexField, FieldValue> finalStaticFieldValues = new IdentityHashMap<>();
+
+    private final Set<DexType> initializedClasses = Sets.newIdentityHashSet();
+
+    private final Map<FieldAndObject, FieldValue> nonFinalInstanceFieldValues = new HashMap<>();
+
+    private final Map<DexField, FieldValue> nonFinalStaticFieldValues = new IdentityHashMap<>();
+
+    public State() {}
+
+    public State(State state) {
+      finalInstanceFieldValues.putAll(state.finalInstanceFieldValues);
+      finalStaticFieldValues.putAll(state.finalStaticFieldValues);
+      initializedClasses.addAll(state.initializedClasses);
+      nonFinalInstanceFieldValues.putAll(state.nonFinalInstanceFieldValues);
+      nonFinalStaticFieldValues.putAll(state.nonFinalStaticFieldValues);
+    }
+
+    public void clearNonFinalInstanceFields() {
+      nonFinalInstanceFieldValues.clear();
+    }
+
+    public void clearNonFinalStaticFields() {
+      nonFinalStaticFieldValues.clear();
+    }
+
+    public FieldValue getInstanceFieldValue(FieldAndObject field) {
+      FieldValue value = nonFinalInstanceFieldValues.get(field);
+      return value != null ? value : finalInstanceFieldValues.get(field);
+    }
+
+    public FieldValue getStaticFieldValue(DexField field) {
+      FieldValue value = nonFinalStaticFieldValues.get(field);
+      return value != null ? value : finalStaticFieldValues.get(field);
+    }
+
+    public void intersect(State state) {
+      intersectFieldValues(finalInstanceFieldValues, state.finalInstanceFieldValues);
+      intersectFieldValues(finalStaticFieldValues, state.finalStaticFieldValues);
+      intersectInitializedClasses(initializedClasses, state.initializedClasses);
+      intersectFieldValues(nonFinalInstanceFieldValues, state.nonFinalInstanceFieldValues);
+      intersectFieldValues(nonFinalStaticFieldValues, state.nonFinalStaticFieldValues);
+    }
+
+    private static <K> void intersectFieldValues(
+        Map<K, FieldValue> fieldValues, Map<K, FieldValue> other) {
+      fieldValues.entrySet().removeIf(entry -> other.get(entry.getKey()) != entry.getValue());
+    }
+
+    private static void intersectInitializedClasses(
+        Set<DexType> initializedClasses, Set<DexType> other) {
+      initializedClasses.removeIf(not(other::contains));
+    }
+
+    public boolean isClassInitialized(DexType clazz) {
+      return initializedClasses.contains(clazz);
+    }
+
+    // If a field get instruction throws an exception it did not have an effect on the value of the
+    // field. Therefore, when propagating across exceptional edges for a field get instruction we
+    // have to exclude that field from the set of known field values.
+    public void killActiveFieldsForExceptionalExit(FieldInstruction instruction) {
+      DexField field = instruction.getField();
+      if (instruction.isInstanceGet()) {
+        Value object = instruction.asInstanceGet().object().getAliasedValue();
+        FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+        removeNonFinalInstanceField(fieldAndObject);
+      } else if (instruction.isStaticGet()) {
+        removeNonFinalStaticField(field);
+      }
+    }
+
+    private void killActiveInitializedClassesForExceptionalExit(InitClass instruction) {
+      initializedClasses.remove(instruction.getClassValue());
+    }
+
+    public void markClassAsInitialized(DexType clazz) {
+      initializedClasses.add(clazz);
+    }
+
+    public void removeInstanceField(FieldAndObject field) {
+      removeFinalInstanceField(field);
+      removeNonFinalInstanceField(field);
+    }
+
+    public void removeFinalInstanceField(FieldAndObject field) {
+      finalInstanceFieldValues.remove(field);
+    }
+
+    public void removeNonFinalInstanceField(FieldAndObject field) {
+      nonFinalInstanceFieldValues.remove(field);
+    }
+
+    public void removeNonFinalInstanceFields(DexField field) {
+      nonFinalInstanceFieldValues.keySet().removeIf(key -> key.field == field);
+    }
+
+    public void removeStaticField(DexField field) {
+      removeFinalStaticField(field);
+      removeNonFinalStaticField(field);
+    }
+
+    public void removeFinalStaticField(DexField field) {
+      finalStaticFieldValues.remove(field);
+    }
+
+    public void removeNonFinalStaticField(DexField field) {
+      nonFinalStaticFieldValues.remove(field);
+    }
+
+    public void putFinalInstanceField(FieldAndObject field, FieldValue value) {
+      finalInstanceFieldValues.put(field, value);
+    }
+
+    public void putFinalStaticField(DexField field, FieldValue value) {
+      finalStaticFieldValues.put(field, value);
+    }
+
+    public void putNonFinalInstanceField(FieldAndObject field, FieldValue value) {
+      assert !finalInstanceFieldValues.containsKey(field);
+      nonFinalInstanceFieldValues.put(field, value);
+    }
+
+    public void putNonFinalStaticField(DexField field, FieldValue value) {
+      assert !nonFinalStaticFieldValues.containsKey(field);
+      nonFinalStaticFieldValues.put(field, value);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 1b56e8c..1e19dea 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -36,7 +36,7 @@
   public static void rewriteGetClassOrForNameToConstClass(
       AppView<AppInfoWithLiveness> appView, IRCode code) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
-    DexType context = code.method.method.holder;
+    DexType context = code.method.holder();
     ClassInitializationAnalysis classInitializationAnalysis =
         new ClassInitializationAnalysis(appView, code);
     for (BasicBlock block : code.blocks) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 8e0858a..6b854e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -351,8 +351,7 @@
         Instruction instruction = instructionIterator.next();
         if (instruction.throwsOnNullInput()) {
           Value couldBeNullValue = instruction.getNonNullInput();
-          if (isThrowNullCandidate(
-              couldBeNullValue, instruction, appView, code.method.method.holder)) {
+          if (isThrowNullCandidate(couldBeNullValue, instruction, appView, code.method.holder())) {
             if (instruction.isInstanceGet() || instruction.isInstancePut()) {
               ++numberOfInstanceGetOrInstancePutWithNullReceiver;
             } else if (instruction.isInvokeMethodWithReceiver()) {
@@ -451,7 +450,7 @@
       IRCode code,
       AssumeDynamicTypeRemover assumeDynamicTypeRemover,
       Set<Value> affectedValues) {
-    DexType context = code.method.method.holder;
+    DexType context = code.method.holder();
     DexField field = instruction.getField();
     DexType fieldType = field.type;
     if (fieldType.isAlwaysNull(appView)) {
@@ -507,7 +506,7 @@
       AssumeDynamicTypeRemover assumeDynamicTypeRemover,
       Set<BasicBlock> blocksToBeRemoved,
       Set<Value> affectedValues) {
-    DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method.method.holder);
+    DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method.holder());
     if (target == null) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index db393ed..7b60b90 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -157,8 +157,7 @@
           // Constructors must be named `<init>`.
           return null;
         }
-        newSignature =
-            appView.dexItemFactory().createMethod(method.method.holder, newProto, newName);
+        newSignature = appView.dexItemFactory().createMethod(method.holder(), newProto, newName);
         count++;
       } while (!isMethodSignatureAvailable(newSignature));
       return newSignature;
@@ -198,8 +197,7 @@
           // Constructors must be named `<init>`.
           return null;
         }
-        newSignature =
-            appView.dexItemFactory().createMethod(method.method.holder, newProto, newName);
+        newSignature = appView.dexItemFactory().createMethod(method.holder(), newProto, newName);
         count++;
       } while (methodPool.hasSeen(equivalence.wrap(newSignature)));
       return newSignature;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 81f68de..7f0ad04 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -160,7 +160,7 @@
       if (eligibleClass.classInitializationMayHaveSideEffects(
           appView,
           // Types that are a super type of the current context are guaranteed to be initialized.
-          type -> appView.isSubtype(method.method.holder, type).isTrue(),
+          type -> appView.isSubtype(method.holder(), type).isTrue(),
           Sets.newIdentityHashSet())) {
         return EligibilityStatus.HAS_CLINIT;
       }
@@ -170,7 +170,7 @@
     assert root.isStaticGet();
 
     StaticGet staticGet = root.asStaticGet();
-    if (staticGet.instructionMayHaveSideEffects(appView, method.method.holder)) {
+    if (staticGet.instructionMayHaveSideEffects(appView, method.holder())) {
       return EligibilityStatus.RETRIEVAL_MAY_HAVE_SIDE_EFFECTS;
     }
     DexEncodedField field = appView.appInfo().resolveField(staticGet.getField());
@@ -265,8 +265,7 @@
 
         if (user.isInvokeMethod()) {
           InvokeMethod invokeMethod = user.asInvokeMethod();
-          DexEncodedMethod singleTarget =
-              invokeMethod.lookupSingleTarget(appView, method.method.holder);
+          DexEncodedMethod singleTarget = invokeMethod.lookupSingleTarget(appView, method.holder());
           if (singleTarget == null) {
             return user; // Not eligible.
           }
@@ -507,8 +506,7 @@
             continue;
           }
 
-          DexEncodedMethod singleTarget =
-              invoke.lookupSingleTarget(appView, code.method.method.holder);
+          DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.method.holder());
           if (singleTarget == null || !indirectMethodCallsOnInstance.contains(singleTarget)) {
             throw new IllegalClassInlinerStateException();
           }
@@ -569,7 +567,7 @@
           continue;
         }
 
-        DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.method.holder);
+        DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.holder());
         if (singleTarget != null) {
           Predicate<InvokeMethod> noSideEffectsPredicate =
               dexItemFactory.libraryMethodsWithoutSideEffects.getOrDefault(
@@ -1156,7 +1154,7 @@
       }
 
       // Check if the method is inline-able by standard inliner.
-      DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.method.holder);
+      DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.holder());
       if (singleTarget == null) {
         return false;
       }
@@ -1190,7 +1188,7 @@
   }
 
   private boolean exemptFromInstructionLimit(DexEncodedMethod inlinee) {
-    DexType inlineeHolder = inlinee.method.holder;
+    DexType inlineeHolder = inlinee.holder();
     DexClass inlineeClass = appView.definitionFor(inlineeHolder);
     assert inlineeClass != null;
 
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 84b952b..ea9141f 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
@@ -4,6 +4,8 @@
 
 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.DexApplication;
 import com.android.tools.r8.graph.DexClass;
@@ -28,6 +30,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
@@ -86,13 +89,14 @@
       debugLogEnabled = false;
       debugLogs = null;
     }
+    assert !appView.options().debug;
     enumsUnboxingCandidates = new EnumUnboxingCandidateAnalysis(appView, this).findCandidates();
   }
 
   public void analyzeEnums(IRCode code) {
     // Enum <clinit> and <init> are analyzed in between the two processing phases using optimization
     // feedback. Methods valueOf and values are generated by javac and are analyzed differently.
-    DexClass dexClass = appView.definitionFor(code.method.method.holder);
+    DexClass dexClass = appView.definitionFor(code.method.holder());
     if (dexClass.isEnum()
         && (code.method.isInitializer()
             || appView.dexItemFactory().enumMethods.isValueOfMethod(code.method.method, dexClass)
@@ -140,11 +144,6 @@
           if (enumClass != null) {
             Reason reason = validateEnumUsages(code, outValue, enumClass);
             if (reason == Reason.ELIGIBLE) {
-              if (instruction.isCheckCast()) {
-                // We are doing a type check, which typically means the in-value is of an upper
-                // type and cannot be dealt with.
-                markEnumAsUnboxable(Reason.DOWN_CAST, enumClass);
-              }
               eligibleEnums.add(enumClass.type);
             }
           }
@@ -152,15 +151,10 @@
             addNullDependencies(outValue.uniqueUsers(), eligibleEnums);
           }
         }
-        // If we have a ConstClass referencing directly an enum, it cannot be unboxed, except if
-        // the constClass is in an enum valueOf method (in this case the valueOf method will be
-        // removed or the enum will be marked as non unboxable).
         if (instruction.isConstClass()) {
-          ConstClass constClass = instruction.asConstClass();
-          if (enumsUnboxingCandidates.containsKey(constClass.getValue())) {
-            markEnumAsUnboxable(
-                Reason.CONST_CLASS, appView.definitionForProgramType(constClass.getValue()));
-          }
+          analyzeConstClass(instruction.asConstClass());
+        } else if (instruction.isCheckCast()) {
+          analyzeCheckCast(instruction.asCheckCast());
         } else if (instruction.isInvokeStatic()) {
           // TODO(b/150370354): Since we temporary allow enum unboxing on enums with values and
           // valueOf static methods only if such methods are unused, such methods cannot be
@@ -204,6 +198,48 @@
     }
   }
 
+  private void analyzeCheckCast(CheckCast checkCast) {
+    // We are doing a type check, which typically means the in-value is of an upper
+    // type and cannot be dealt with.
+    // If the cast is on a dynamically typed object, the checkCast can be simply removed.
+    // This allows enum array clone and valueOf to work correctly.
+    TypeElement objectType = checkCast.object().getDynamicUpperBoundType(appView);
+    if (objectType.equalUpToNullability(
+        TypeElement.fromDexType(checkCast.getType(), definitelyNotNull(), appView))) {
+      return;
+    }
+    DexProgramClass enumClass =
+        getEnumUnboxingCandidateOrNull(checkCast.getType().toBaseType(factory));
+    if (enumClass != null) {
+      markEnumAsUnboxable(Reason.DOWN_CAST, enumClass);
+    }
+  }
+
+  private void analyzeConstClass(ConstClass constClass) {
+    // We are using the ConstClass of an enum, which typically means the enum cannot be unboxed.
+    // We however allow unboxing if the ConstClass is only used as an argument to Enum#valueOf, to
+    // allow unboxing of: MyEnum a = Enum.valueOf(MyEnum.class, "A");.
+    if (!enumsUnboxingCandidates.containsKey(constClass.getValue())) {
+      return;
+    }
+    if (constClass.outValue() == null) {
+      return;
+    }
+    if (constClass.outValue().hasPhiUsers()) {
+      markEnumAsUnboxable(
+          Reason.CONST_CLASS, appView.definitionForProgramType(constClass.getValue()));
+      return;
+    }
+    for (Instruction user : constClass.outValue().uniqueUsers()) {
+      if (!(user.isInvokeStatic()
+          && user.asInvokeStatic().getInvokedMethod() == factory.enumMethods.valueOf)) {
+        markEnumAsUnboxable(
+            Reason.CONST_CLASS, appView.definitionForProgramType(constClass.getValue()));
+        return;
+      }
+    }
+  }
+
   private void addNullDependencies(Set<Instruction> uses, Set<DexType> eligibleEnums) {
     for (Instruction use : uses) {
       if (use.isInvokeMethod()) {
@@ -349,7 +385,7 @@
         return Reason.INVALID_INVOKE_ON_ARRAY;
       }
       DexEncodedMethod encodedSingleTarget =
-          invokeMethod.lookupSingleTarget(appView, code.method.method.holder);
+          invokeMethod.lookupSingleTarget(appView, code.method.holder());
       if (encodedSingleTarget == null) {
         return Reason.INVALID_INVOKE;
       }
@@ -359,14 +395,6 @@
         return Reason.INVALID_INVOKE;
       }
       if (dexClass.isProgramClass()) {
-        // All invokes in the program are generally valid, but specific care is required
-        // for values() and valueOf().
-        if (dexClass.isEnum() && factory.enumMethods.isValuesMethod(singleTarget, dexClass)) {
-          return Reason.VALUES_INVOKE;
-        }
-        if (dexClass.isEnum() && factory.enumMethods.isValueOfMethod(singleTarget, dexClass)) {
-          return Reason.VALUE_OF_INVOKE;
-        }
         int offset = BooleanUtils.intValue(!encodedSingleTarget.isStatic());
         for (int i = 0; i < singleTarget.proto.parameters.size(); i++) {
           if (invokeMethod.inValues().get(offset + i) == enumValue) {
@@ -384,8 +412,8 @@
       if (dexClass.type != factory.enumType) {
         return Reason.UNSUPPORTED_LIBRARY_CALL;
       }
-      // TODO(b/147860220): Methods toString(), name(), compareTo(), EnumSet and EnumMap may be
-      // interesting to model. A the moment rewrite only Enum#ordinal().
+      // TODO(b/147860220): Methods toString(), name(), compareTo(), EnumSet and EnumMap may
+      // be interesting to model. A the moment rewrite only Enum#ordinal() and Enum#valueOf.
       if (debugLogEnabled) {
         if (singleTarget == factory.enumMethods.compareTo) {
           return Reason.COMPARE_TO_INVOKE;
@@ -397,10 +425,10 @@
           return Reason.TO_STRING_INVOKE;
         }
       }
-      if (singleTarget != factory.enumMethods.ordinal) {
-        return Reason.UNSUPPORTED_LIBRARY_CALL;
+      if (singleTarget == factory.enumMethods.ordinal) {
+        return Reason.ELIGIBLE;
       }
-      return Reason.ELIGIBLE;
+      return Reason.UNSUPPORTED_LIBRARY_CALL;
     }
 
     // A field put is valid only if the field is not on an enum, and the field type and the valuePut
@@ -411,7 +439,7 @@
       if (field == null) {
         return Reason.INVALID_FIELD_PUT;
       }
-      DexProgramClass dexClass = appView.definitionForProgramType(field.field.holder);
+      DexProgramClass dexClass = appView.definitionForProgramType(field.holder());
       if (dexClass == null) {
         return Reason.INVALID_FIELD_PUT;
       }
@@ -603,7 +631,7 @@
       for (DexProgramClass clazz : appView.appInfo().classes()) {
         if (enumsToUnbox.contains(clazz.type)) {
           assert clazz.instanceFields().size() == 0;
-          clearEnumtoUnboxMethods(clazz);
+          clearEnumToUnboxMethods(clazz);
         } else {
           clazz.getMethodCollection().replaceMethods(this::fixupMethod);
           fixupFields(clazz.staticFields(), clazz::setStaticField);
@@ -616,7 +644,7 @@
       return lensBuilder.build(factory, appView.graphLense());
     }
 
-    private void clearEnumtoUnboxMethods(DexProgramClass clazz) {
+    private void clearEnumToUnboxMethods(DexProgramClass clazz) {
       // 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.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
index 0bf0aed..a09ea37 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.MemberType;
@@ -36,6 +37,10 @@
 
 public final class EnumUnboxingCfMethods {
 
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    factory.createSynthesizedType("Ljava/lang/NullPointerException;");
+  }
+
   public static CfCode EnumUnboxingMethods_compareTo(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
@@ -52,13 +57,12 @@
             new CfLoad(ValueType.INT, 1),
             new CfIf(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NullPointerException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createType("Ljava/lang/NullPointerException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -89,13 +93,12 @@
             new CfLoad(ValueType.INT, 0),
             new CfIf(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NullPointerException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createType("Ljava/lang/NullPointerException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -129,13 +132,12 @@
             new CfLoad(ValueType.INT, 0),
             new CfIf(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NullPointerException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createType("Ljava/lang/NullPointerException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
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 0d065ff..73eff15 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
@@ -24,27 +24,28 @@
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ArrayAccess;
-import com.android.tools.r8.ir.code.ConstNumber;
 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.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.MemberType;
-import com.android.tools.r8.ir.code.NumericType;
 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.synthetic.EnumUnboxingCfCodeProvider;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -56,6 +57,7 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory factory;
   private final EnumValueInfoMapCollection enumsToUnbox;
+  private final Map<DexMethod, DexType> extraMethods = new ConcurrentHashMap<>();
 
   private final DexType utilityClassType;
   private final DexMethod ordinalUtilityMethod;
@@ -71,7 +73,7 @@
     }
     this.enumsToUnbox = builder.build();
 
-    this.utilityClassType = factory.createType("L" + ENUM_UNBOXING_UTILITY_CLASS_NAME + ";");
+    this.utilityClassType = factory.enumUnboxingUtilityType;
     this.ordinalUtilityMethod =
         factory.createMethod(
             utilityClassType,
@@ -89,6 +91,7 @@
     if (enumsToUnbox.isEmpty()) {
       return;
     }
+    assert code.isConsistentSSABeforeTypesAreCorrect();
     Set<Phi> affectedPhis = Sets.newIdentityHashSet();
     InstructionListIterator iterator = code.instructionListIterator();
     while (iterator.hasNext()) {
@@ -99,14 +102,38 @@
         InvokeMethodWithReceiver invokeMethod = instruction.asInvokeMethodWithReceiver();
         DexMethod invokedMethod = invokeMethod.getInvokedMethod();
         if (invokedMethod == factory.enumMethods.ordinal
-            && invokeMethod.getReceiver().getType().isInt()) {
+            && isEnumToUnboxOrInt(invokeMethod.getReceiver().getType())) {
           instruction =
               new InvokeStatic(
                   ordinalUtilityMethod, invokeMethod.outValue(), invokeMethod.inValues());
           iterator.replaceCurrentInstruction(instruction);
           requiresOrdinalUtilityMethod = true;
+          continue;
         }
         // TODO(b/147860220): rewrite also other enum methods.
+      } else if (instruction.isInvokeStatic()) {
+        InvokeStatic invokeStatic = instruction.asInvokeStatic();
+        DexMethod invokedMethod = invokeStatic.getInvokedMethod();
+        if (invokedMethod == factory.enumMethods.valueOf
+            && invokeStatic.inValues().get(0).isConstClass()) {
+          DexType enumType =
+              invokeStatic.inValues().get(0).getConstInstruction().asConstClass().getValue();
+          if (enumsToUnbox.containsEnum(enumType)) {
+            DexMethod valueOfMethod = computeValueOfUtilityMethod(enumType);
+            Value outValue = invokeStatic.outValue();
+            Value rewrittenOutValue = null;
+            if (outValue != null) {
+              rewrittenOutValue = code.createValue(TypeElement.getInt());
+              affectedPhis.addAll(outValue.uniquePhiUsers());
+            }
+            iterator.replaceCurrentInstruction(
+                new InvokeStatic(
+                    valueOfMethod,
+                    rewrittenOutValue,
+                    Collections.singletonList(invokeStatic.inValues().get(1))));
+            continue;
+          }
+        }
       }
       // Rewrites direct access to enum values into the corresponding int, $VALUES is not
       // supported.
@@ -124,10 +151,8 @@
           EnumValueInfo enumValueInfo = enumValueInfoMap.getEnumValueInfo(staticGet.getField());
           assert enumValueInfo != null
               : "Invalid read to " + staticGet.getField().name + ", error during enum analysis";
-          instruction = new ConstNumber(staticGet.outValue(), enumValueInfo.convertToInt());
-          staticGet.outValue().setType(PrimitiveTypeElement.fromNumericType(NumericType.INT));
-          iterator.replaceCurrentInstruction(instruction);
           affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
+          iterator.replaceCurrentInstruction(code.createIntConstant(enumValueInfo.convertToInt()));
         }
       }
       // Rewrite array accesses from MyEnum[] (OBJECT) to int[] (INT).
@@ -137,8 +162,8 @@
           instruction = arrayAccess.withMemberType(MemberType.INT);
           iterator.replaceCurrentInstruction(instruction);
         }
+        assert validateArrayAccess(instruction.asArrayAccess());
       }
-      assert validateEnumToUnboxRemoved(instruction);
     }
     if (!affectedPhis.isEmpty()) {
       new DestructivePhiTypeUpdater(appView).recomputeAndPropagateTypes(code, affectedPhis);
@@ -146,33 +171,43 @@
     assert code.isConsistentSSABeforeTypesAreCorrect();
   }
 
-  private boolean shouldRewriteArrayAccess(ArrayAccess arrayAccess) {
+  private boolean validateArrayAccess(ArrayAccess arrayAccess) {
     ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
-    return arrayAccess.getMemberType() == MemberType.OBJECT
-        && arrayType.getNesting() == 1
-        && arrayType.getBaseType().isInt();
+    assert arrayAccess.getMemberType() != MemberType.OBJECT
+        || arrayType.getNesting() > 1
+        || arrayType.getBaseType().isReferenceType();
+    return true;
   }
 
-  private boolean validateEnumToUnboxRemoved(Instruction instruction) {
-    if (instruction.isArrayAccess()) {
-      ArrayAccess arrayAccess = instruction.asArrayAccess();
-      ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
-      assert arrayAccess.getMemberType() != MemberType.OBJECT
-          || arrayType.getNesting() > 1
-          || arrayType.getBaseType().isReferenceType();
-    }
-    if (instruction.outValue() == null) {
+  private boolean isEnumToUnboxOrInt(TypeElement type) {
+    if (type.isInt()) {
       return true;
     }
-    TypeElement typeLattice = instruction.outValue().getType();
-    assert !typeLattice.isClassType()
-        || !enumsToUnbox.containsEnum(typeLattice.asClassType().getClassType());
-    if (typeLattice.isArrayType()) {
-      TypeElement arrayBaseTypeLattice = typeLattice.asArrayType().getBaseType();
-      assert !arrayBaseTypeLattice.isClassType()
-          || !enumsToUnbox.containsEnum(arrayBaseTypeLattice.asClassType().getClassType());
+    if (!type.isClassType()) {
+      return false;
     }
-    return true;
+    return enumsToUnbox.containsEnum(type.asClassType().getClassType());
+  }
+
+  private DexMethod computeValueOfUtilityMethod(DexType type) {
+    assert enumsToUnbox.containsEnum(type);
+    DexMethod valueOf =
+        factory.createMethod(
+            utilityClassType,
+            factory.createProto(factory.intType, factory.stringType),
+            "valueOf" + type.toSourceString().replace('.', '$'));
+    extraMethods.putIfAbsent(valueOf, type);
+    return valueOf;
+  }
+
+  private boolean shouldRewriteArrayAccess(ArrayAccess arrayAccess) {
+    ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
+    if (arrayType.getNesting() != 1) {
+      return false;
+    }
+    TypeElement baseType = arrayType.getBaseType();
+    return baseType.isClassType()
+        && enumsToUnbox.containsEnum(baseType.asClassType().getClassType());
   }
 
   // TODO(b/150172351): Synthesize the utility class upfront in the enqueuer.
@@ -182,6 +217,11 @@
     // Synthesize a class which holds various utility methods that may be called from the IR
     // rewriting. If any of these methods are not used, they will be removed by the Enqueuer.
     List<DexEncodedMethod> requiredMethods = new ArrayList<>();
+    extraMethods.forEach(
+        (method, enumType) -> {
+          requiredMethods.add(synthesizeValueOfUtilityMethod(method, enumType));
+        });
+    requiredMethods.sort((m1, m2) -> m1.method.name.slowCompareTo(m2.method.name));
     if (requiresOrdinalUtilityMethod) {
       requiredMethods.add(synthesizeOrdinalMethod());
     }
@@ -215,6 +255,21 @@
     converter.optimizeSynthesizedClass(utilityClass, executorService);
   }
 
+  private DexEncodedMethod synthesizeValueOfUtilityMethod(DexMethod method, DexType enumType) {
+    CfCode cfCode =
+        new EnumUnboxingCfCodeProvider.EnumUnboxingValueOfCfCodeProvider(
+                appView, utilityClassType, enumType, enumsToUnbox.getEnumValueInfoMap(enumType))
+            .generateCfCode();
+    return new DexEncodedMethod(
+        method,
+        synthesizedMethodAccessFlags(),
+        DexAnnotationSet.empty(),
+        ParameterAnnotationsList.empty(),
+        cfCode,
+        REQUIRED_CLASS_FILE_VERSION,
+        true);
+  }
+
   // TODO(b/150178516): Add a test for this case.
   private boolean utilityClassInMainDexList() {
     for (DexType toUnbox : enumsToUnbox.enumSet()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index ba24e19..791c889 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -85,7 +85,7 @@
     TypeElement[] staticTypes = new TypeElement[size];
     if (!encodedMethod.isStatic()) {
       staticTypes[0] =
-          TypeElement.fromDexType(encodedMethod.method.holder, definitelyNotNull(), appView);
+          TypeElement.fromDexType(encodedMethod.holder(), definitelyNotNull(), appView);
     }
     for (int i = 0; i < encodedMethod.method.getArity(); i++) {
       staticTypes[i + argOffset] =
@@ -168,7 +168,7 @@
         Value aliasedValue = arg.getAliasedValue();
         if (!aliasedValue.isPhi()) {
           AbstractValue abstractValue =
-              aliasedValue.definition.getAbstractValue(appView, method.method.holder);
+              aliasedValue.definition.getAbstractValue(appView, method.holder());
           if (abstractValue.isNonTrivial()) {
             newCallSiteInfo.constants.put(i, abstractValue);
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 19c897f..4889b2e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -177,7 +177,7 @@
       return;
     }
 
-    DexClass clazz = appView.definitionFor(method.method.holder);
+    DexClass clazz = appView.definitionFor(method.holder());
     if (clazz == null) {
       return;
     }
@@ -241,8 +241,7 @@
         case INVOKE_STATIC:
           {
             InvokeStatic invoke = insn.asInvokeStatic();
-            DexEncodedMethod singleTarget =
-                invoke.lookupSingleTarget(appView, method.method.holder);
+            DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.holder());
             if (singleTarget == null) {
               return; // Not allowed.
             }
@@ -263,7 +262,7 @@
             DexMethod invokedMethod = invoke.getInvokedMethod();
             DexType returnType = invokedMethod.proto.returnType;
             if (returnType.isClassType()
-                && appView.appInfo().isRelatedBySubtyping(returnType, method.method.holder)) {
+                && appView.appInfo().isRelatedBySubtyping(returnType, method.holder())) {
               return; // Not allowed, could introduce an alias of the receiver.
             }
             callsReceiver.add(new Pair<>(Invoke.Type.VIRTUAL, invokedMethod));
@@ -373,7 +372,7 @@
         if (definition.isArgument()) {
           feedback.methodReturnsArgument(method, definition.asArgument().getIndex());
         }
-        DexType context = method.method.holder;
+        DexType context = method.holder();
         AbstractValue abstractReturnValue = definition.getAbstractValue(appView, context);
         if (abstractReturnValue.isNonTrivial()) {
           feedback.methodReturnsAbstractValue(method, appView, abstractReturnValue);
@@ -417,7 +416,7 @@
       return;
     }
 
-    DexClass clazz = appView.appInfo().definitionFor(method.method.holder);
+    DexClass clazz = appView.appInfo().definitionFor(method.holder());
     if (clazz == null) {
       assert false;
       return;
@@ -687,7 +686,7 @@
     if (method.isStatic()) {
       // Identifies if the method preserves class initialization after inlining.
       feedback.markTriggerClassInitBeforeAnySideEffect(
-          method, triggersClassInitializationBeforeSideEffect(method.method.holder, code, appView));
+          method, triggersClassInitializationBeforeSideEffect(method.holder(), code, appView));
     } else {
       // Identifies if the method preserves null check of the receiver after inlining.
       final Value receiver = code.getThis();
@@ -707,7 +706,7 @@
     return alwaysTriggerExpectedEffectBeforeAnythingElse(
         code,
         (instruction, it) -> {
-          DexType context = code.method.method.holder;
+          DexType context = code.method.holder();
           if (instruction.definitelyTriggersClassInitialization(
               clazz, context, appView, DIRECTLY, AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW)) {
             // In order to preserve class initialization semantic, the exception must not be caught
@@ -849,7 +848,7 @@
               // We found a NPE check on the value.
               return InstructionEffect.DESIRED_EFFECT;
             }
-          } else if (instr.instructionMayHaveSideEffects(appView, code.method.method.holder)) {
+          } else if (instr.instructionMayHaveSideEffects(appView, code.method.holder())) {
             // If the current instruction is const-string, this could load the parameter name.
             // Just make sure it is indeed not throwing.
             if (instr.isConstString() && !instr.instructionInstanceCanThrow()) {
@@ -1014,7 +1013,7 @@
     if (appView.appInfo().mayHaveSideEffects.containsKey(method.method)) {
       return;
     }
-    DexType context = method.method.holder;
+    DexType context = method.holder();
     if (method.isClassInitializer()) {
       // For class initializers, we also wish to compute if the class initializer has observable
       // side effects.
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 3a98637..608c41f 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
@@ -38,7 +38,7 @@
       return cached;
     }
     Position position = Position.getPositionForInlining(appView, invoke, context);
-    Origin origin = appView.appInfo().originFor(method.method.holder);
+    Origin origin = appView.appInfo().originFor(method.holder());
     return method.buildInliningIR(
         context, appView, valueNumberGenerator, position, origin, methodProcessor);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index 1ff91c0..091a5e5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -32,8 +32,7 @@
       return;
     }
 
-    Collection<DexEncodedMethod> possibleTargets =
-        invoke.lookupTargets(appView, context.method.holder);
+    Collection<DexEncodedMethod> possibleTargets = invoke.lookupTargets(appView, context.holder());
     if (possibleTargets == null) {
       // In principle, this invoke might target any method in the program, but we do not want to
       // report a message for each of the methods in `AppInfoWithLiveness#whyAreYouNotInlining`,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index 64026ca..b141817 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -220,7 +220,7 @@
         "final field `"
             + instancePut.getField()
             + "` must be initialized in a constructor of `"
-            + callee.method.holder.toSourceString()
+            + callee.holder().toSourceString()
             + "`.");
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
index 1648958..eb8b742 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
@@ -218,7 +218,7 @@
 
   private boolean shouldRewrite(DexType type) {
     // Rewrite references to lambda classes if we are outside the class.
-    return type != (context != null ? context : method).method.holder;
+    return type != (context != null ? context : method).holder();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index ce2394c..0bdaa57 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -133,7 +133,7 @@
         Inliner inliner,
         DexEncodedMethod context,
         InliningIRProvider provider) {
-      DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
+      DexProgramClass clazz = appView.definitionFor(method.holder()).asProgramClass();
       assert clazz != null;
 
       LambdaGroup lambdaGroup = lambdaGroups.get(clazz);
@@ -162,7 +162,7 @@
           assert resolution.isSingleResolution();
           DexEncodedMethod singleTarget = resolution.getSingleTarget();
           assert singleTarget != null;
-          invokesToInline.put(invoke, new InliningInfo(singleTarget, singleTarget.method.holder));
+          invokesToInline.put(invoke, new InliningInfo(singleTarget, singleTarget.holder()));
         }
       }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
index 3e602b7..4d79186 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
@@ -54,7 +54,7 @@
     return field.name == context.kotlin.functional.kotlinStyleLambdaInstanceName
         && lambda == field.type
         && context.factory.isClassConstructor(context.method.method)
-        && context.method.method.holder == lambda;
+        && context.method.holder() == lambda;
   }
 
   @Override
@@ -100,10 +100,10 @@
     // Allow calls to a constructor from other classes if the lambda is singleton,
     // otherwise allow such a call only from the same class static initializer.
     boolean isSingletonLambda = group.isStateless() && group.isSingletonLambda(lambda);
-    return (isSingletonLambda == (context.method.method.holder == lambda)) &&
-        invoke.isInvokeDirect() &&
-        context.factory.isConstructor(method) &&
-        CaptureSignature.getCaptureSignature(method.proto.parameters).equals(group.id().capture);
+    return (isSingletonLambda == (context.method.holder() == lambda))
+        && invoke.isInvokeDirect()
+        && context.factory.isConstructor(method)
+        && CaptureSignature.getCaptureSignature(method.proto.parameters).equals(group.id().capture);
   }
 
   private boolean isValidVirtualCall(InvokeMethod invoke) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
index 31444c6..16d514c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
@@ -70,7 +70,7 @@
       InvokeMethod invoke,
       Set<Value> affectedValues) {
     Value argument = invoke.arguments().get(0);
-    AbstractValue abstractValue = argument.getAbstractValue(appView, code.method.method.holder);
+    AbstractValue abstractValue = argument.getAbstractValue(appView, code.method.holder());
     if (abstractValue.isSingleNumberValue()) {
       instructionIterator.replaceCurrentInstructionWithStaticGet(
           appView,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
new file mode 100644
index 0000000..fd9d62b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
@@ -0,0 +1,76 @@
+// 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.optimize.library;
+
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Assume;
+import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
+
+public class EnumMethodOptimizer implements LibraryMethodModelCollection {
+  private final AppView<?> appView;
+
+  EnumMethodOptimizer(AppView<?> appView) {
+    this.appView = appView;
+  }
+
+  @Override
+  public DexType getType() {
+    return appView.dexItemFactory().enumType;
+  }
+
+  @Override
+  public void optimize(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      DexEncodedMethod singleTarget,
+      Set<Value> affectedValues) {
+    if (singleTarget.method == appView.dexItemFactory().enumMethods.valueOf
+        && invoke.inValues().get(0).isConstClass()) {
+      insertAssumeDynamicType(code, instructionIterator, invoke);
+    }
+  }
+
+  private void insertAssumeDynamicType(
+      IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
+    // TODO(b/152516470): Support unboxing enums with Enum#valueOf in try-catch.
+    if (invoke.getBlock().hasCatchHandlers()) {
+      return;
+    }
+    DexType enumType = invoke.inValues().get(0).getConstInstruction().asConstClass().getValue();
+    DexProgramClass enumClass = appView.definitionForProgramType(enumType);
+    if (enumClass == null || enumClass.superType != appView.dexItemFactory().enumType) {
+      return;
+    }
+    TypeElement dynamicUpperBoundType =
+        TypeElement.fromDexType(enumType, definitelyNotNull(), appView);
+    Value outValue = invoke.outValue();
+    if (outValue == null) {
+      return;
+    }
+    // Replace usages of out-value by the out-value of the AssumeDynamicType instruction.
+    Value specializedOutValue = code.createValue(outValue.getType(), outValue.getLocalInfo());
+    outValue.replaceUsers(specializedOutValue);
+
+    // Insert AssumeDynamicType instruction.
+    Assume<DynamicTypeAssumption> assumeInstruction =
+        Assume.createAssumeDynamicTypeInstruction(
+            dynamicUpperBoundType, null, specializedOutValue, outValue, invoke, appView);
+    assumeInstruction.setPosition(appView.options().debug ? invoke.getPosition() : Position.none());
+    instructionIterator.add(assumeInstruction);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
index f3d7cc6..fc687a0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
@@ -37,6 +37,9 @@
     register(new ObjectMethodOptimizer(appView));
     register(new ObjectsMethodOptimizer(appView));
     register(new StringMethodOptimizer(appView));
+    if (appView.appInfo().hasSubtyping() && appView.options().enableDynamicTypeOptimization) {
+      register(new EnumMethodOptimizer(appView));
+    }
 
     if (LogMethodOptimizer.isEnabled(appView)) {
       register(new LogMethodOptimizer(appView));
@@ -83,8 +86,7 @@
       Instruction instruction = instructionIterator.next();
       if (instruction.isInvokeMethod()) {
         InvokeMethod invoke = instruction.asInvokeMethod();
-        DexEncodedMethod singleTarget =
-            invoke.lookupSingleTarget(appView, code.method.method.holder);
+        DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.method.holder());
         if (singleTarget != null) {
           optimizeInvoke(code, instructionIterator, invoke, singleTarget, affectedValues);
         }
@@ -103,7 +105,7 @@
       Set<Value> affectedValues) {
     LibraryMethodModelCollection optimizer =
         libraryMethodModelCollections.getOrDefault(
-            singleTarget.method.holder, NopLibraryMethodModelCollection.getInstance());
+            singleTarget.holder(), NopLibraryMethodModelCollection.getInstance());
     optimizer.optimize(code, instructionIterator, invoke, singleTarget, affectedValues);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index 501a761..09c8f4e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -47,7 +47,7 @@
   private void optimizeEquals(
       IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
     if (appView.appInfo().hasLiveness()) {
-      DexType context = code.method.method.holder;
+      DexType context = code.method.holder();
       Value first = invoke.arguments().get(0).getAliasedValue();
       Value second = invoke.arguments().get(1).getAliasedValue();
       if (isPrunedClassNameComparison(first, second, context)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java
index 127b144..3b93c50 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java
@@ -113,7 +113,7 @@
     Instruction current = it.next();
     if (position != current.getPosition()
         || !current.isConstNumber()
-        || current.outValue().getType() != TypeElement.getInt()
+        || current.getOutType() != TypeElement.getInt()
         || current.asConstNumber().getIntValue() < -128
         || current.asConstNumber().getIntValue() > 127
         || !it.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 3c99a56..41df56a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -78,11 +79,11 @@
     }
 
     boolean isHostClassInitializer(DexEncodedMethod method) {
-      return factory.isClassConstructor(method.method) && method.method.holder == hostType();
+      return factory.isClassConstructor(method.method) && method.holder() == hostType();
     }
 
     DexType hostType() {
-      return singletonField.field.holder;
+      return singletonField.holder();
     }
 
     DexClass hostClass() {
@@ -212,7 +213,7 @@
   public final void examineMethodCode(DexEncodedMethod method, IRCode code) {
     Set<Instruction> alreadyProcessed = Sets.newIdentityHashSet();
 
-    CandidateInfo receiverClassCandidateInfo = candidates.get(method.method.holder);
+    CandidateInfo receiverClassCandidateInfo = candidates.get(method.holder());
     Value receiverValue = code.getThis(); // NOTE: is null for static methods.
     if (receiverClassCandidateInfo != null) {
       if (receiverValue != null) {
@@ -224,13 +225,13 @@
 
         // If the candidate is still valid, ignore all instructions
         // we treat as valid usages on receiver.
-        if (candidates.get(method.method.holder) != null) {
+        if (candidates.get(method.holder()) != null) {
           alreadyProcessed.addAll(receiverValue.uniqueUsers());
         }
       } else {
         // We are inside a static method of candidate class.
         // Check if this is a valid getter of the singleton field.
-        if (method.method.proto.returnType == method.method.holder) {
+        if (method.method.proto.returnType == method.holder()) {
           List<Instruction> examined = isValidGetter(receiverClassCandidateInfo, code);
           if (examined != null) {
             DexEncodedMethod getter = receiverClassCandidateInfo.getter.get();
@@ -267,12 +268,13 @@
         NewInstance newInstance = instruction.asNewInstance();
         CandidateInfo candidateInfo = processInstantiation(method, iterator, newInstance);
         if (candidateInfo != null) {
+          alreadyProcessed.addAll(newInstance.outValue().aliasedUsers());
           // For host class initializers having eligible instantiation we also want to
           // ensure that the rest of the initializer consist of code w/o side effects.
           // This must guarantee that removing field access will not result in missing side
           // effects, otherwise we can still staticize, but cannot remove singleton reads.
           while (iterator.hasNext()) {
-            if (!isAllowedInHostClassInitializer(method.method.holder, iterator.next(), code)) {
+            if (!isAllowedInHostClassInitializer(method.holder(), iterator.next(), code)) {
               candidateInfo.preserveRead.set(true);
               iterator.previous();
               break;
@@ -396,10 +398,10 @@
       return candidateInfo.invalidate();
     }
 
-    if (candidateValue.numberOfUsers() != 2) {
-      // We expect only two users for each instantiation: constructor call and
-      // static field write. We only check count here, since the exact instructions
-      // will be checked later.
+    if (candidateValue.numberOfUsers() < 2) {
+      // We expect two special users for each instantiation: constructor call and static field
+      // write. We allow the instance to have other users as well, as long as they are valid
+      // according to the user analysis.
       return candidateInfo.invalidate();
     }
 
@@ -411,7 +413,7 @@
     //        invoke-direct {v0, ...}, void <candidate-type>.<init>(...)
     //        sput-object v0, <instance-field>
     //        ...
-    //        ...
+    //        ... // other usages that are valid according to the user analysis.
     //
     // In case we guarantee candidate constructor does not access <instance-field>
     // directly or indirectly we can guarantee that all the potential reads get
@@ -422,26 +424,33 @@
       // Intentionally empty.
     }
     iterator.previous();
-
     if (!iterator.hasNext()) {
       return candidateInfo.invalidate();
     }
-    if (!isValidInitCall(candidateInfo, iterator.next(), candidateValue, candidateType)) {
+    Set<Instruction> users = SetUtils.newIdentityHashSet(candidateValue.uniqueUsers());
+    Instruction constructorCall = iterator.next();
+    if (!isValidInitCall(candidateInfo, constructorCall, candidateValue, candidateType)) {
       iterator.previous();
       return candidateInfo.invalidate();
     }
-
+    boolean removedConstructorCall = users.remove(constructorCall);
+    assert removedConstructorCall;
     if (!iterator.hasNext()) {
       return candidateInfo.invalidate();
     }
-    if (!isValidStaticPut(candidateInfo, iterator.next())) {
+    Instruction staticPut = iterator.next();
+    if (!isValidStaticPut(candidateInfo, staticPut)) {
       iterator.previous();
       return candidateInfo.invalidate();
     }
+    boolean removedStaticPut = users.remove(staticPut);
+    assert removedStaticPut;
     if (candidateInfo.fieldWrites.incrementAndGet() > 1) {
       return candidateInfo.invalidate();
     }
-
+    if (!isSelectedValueUsersValid(candidateInfo, candidateValue, false, users)) {
+      return candidateInfo.invalidate();
+    }
     return candidateInfo;
   }
 
@@ -463,7 +472,7 @@
 
     if (ListUtils.lastIndexMatching(values, v -> v.getAliasedValue() == candidateValue) != 0
         || methodInvoked == null
-        || methodInvoked.method.holder != candidateType) {
+        || methodInvoked.holder() != candidateType) {
       return false;
     }
 
@@ -567,59 +576,79 @@
   private CandidateInfo analyzeAllValueUsers(
       CandidateInfo candidateInfo, Value value, boolean ignoreSuperClassInitInvoke) {
     assert value != null && value == value.getAliasedValue();
-
     if (value.numberOfPhiUsers() > 0) {
       return candidateInfo.invalidate();
     }
+    if (!isSelectedValueUsersValid(
+        candidateInfo, value, ignoreSuperClassInitInvoke, value.uniqueUsers())) {
+      return candidateInfo.invalidate();
+    }
+    return candidateInfo;
+  }
 
-    Set<Instruction> currentUsers = value.uniqueUsers();
+  private boolean isSelectedValueUsersValid(
+      CandidateInfo candidateInfo,
+      Value value,
+      boolean ignoreSuperClassInitInvoke,
+      Set<Instruction> currentUsers) {
     while (!currentUsers.isEmpty()) {
       Set<Instruction> indirectUsers = Sets.newIdentityHashSet();
       for (Instruction user : currentUsers) {
-        if (user.isAssume()) {
-          if (user.outValue().numberOfPhiUsers() > 0) {
-            return candidateInfo.invalidate();
-          }
-          indirectUsers.addAll(user.outValue().uniqueUsers());
-          continue;
+        if (!isValidValueUser(
+            candidateInfo, value, ignoreSuperClassInitInvoke, indirectUsers, user)) {
+          return false;
         }
-        if (user.isInvokeVirtual() || user.isInvokeDirect() /* private methods */) {
-          InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
-          Predicate<Value> isAliasedValue = v -> v.getAliasedValue() == value;
-          DexMethod methodReferenced = invoke.getInvokedMethod();
-          if (factory.isConstructor(methodReferenced)) {
-            assert user.isInvokeDirect();
-            if (ignoreSuperClassInitInvoke
-                && ListUtils.lastIndexMatching(invoke.inValues(), isAliasedValue) == 0
-                && methodReferenced == factory.objectMembers.constructor) {
-              // If we are inside candidate constructor and analyzing usages
-              // of the receiver, we want to ignore invocations of superclass
-              // constructor which will be removed after staticizing.
-              continue;
-            }
-            return candidateInfo.invalidate();
-          }
-          AppInfoWithLiveness appInfo = appView.appInfo();
-          ResolutionResult resolutionResult =
-              appInfo.resolveMethod(methodReferenced.holder, methodReferenced);
-          DexEncodedMethod methodInvoked =
-              user.isInvokeDirect()
-                  ? resolutionResult.lookupInvokeDirectTarget(candidateInfo.candidate, appInfo)
-                  : resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
-          if (ListUtils.lastIndexMatching(invoke.inValues(), isAliasedValue) == 0
-              && methodInvoked != null
-              && methodInvoked.method.holder == candidateInfo.candidate.type) {
-            continue;
-          }
-        }
-
-        // All other users are not allowed.
-        return candidateInfo.invalidate();
       }
       currentUsers = indirectUsers;
     }
+    return true;
+  }
 
-    return candidateInfo;
+  private boolean isValidValueUser(
+      CandidateInfo candidateInfo,
+      Value value,
+      boolean ignoreSuperClassInitInvoke,
+      Set<Instruction> indirectUsers,
+      Instruction user) {
+    if (user.isAssume()) {
+      if (user.outValue().numberOfPhiUsers() > 0) {
+        return false;
+      }
+      indirectUsers.addAll(user.outValue().uniqueUsers());
+      return true;
+    }
+    if (user.isInvokeVirtual() || user.isInvokeDirect() /* private methods */) {
+      InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
+      Predicate<Value> isAliasedValue = v -> v.getAliasedValue() == value;
+      DexMethod methodReferenced = invoke.getInvokedMethod();
+      if (factory.isConstructor(methodReferenced)) {
+        assert user.isInvokeDirect();
+        if (ignoreSuperClassInitInvoke
+            && ListUtils.lastIndexMatching(invoke.inValues(), isAliasedValue) == 0
+            && methodReferenced == factory.objectMembers.constructor) {
+          // If we are inside candidate constructor and analyzing usages
+          // of the receiver, we want to ignore invocations of superclass
+          // constructor which will be removed after staticizing.
+          return true;
+        }
+        return false;
+      }
+      AppInfoWithLiveness appInfo = appView.appInfo();
+      ResolutionResult resolutionResult =
+          appInfo.resolveMethod(methodReferenced.holder, methodReferenced);
+      DexEncodedMethod methodInvoked =
+          user.isInvokeDirect()
+              ? resolutionResult.lookupInvokeDirectTarget(candidateInfo.candidate, appInfo)
+              : resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
+      if (ListUtils.lastIndexMatching(invoke.inValues(), isAliasedValue) == 0
+          && methodInvoked != null
+          && methodInvoked.holder() == candidateInfo.candidate.type) {
+        return true;
+      }
+    }
+
+    // All other users are not allowed.
+    return false;
   }
 
   // Perform staticizing candidates:
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index f79593b..4a1924b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.staticizer;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
@@ -18,8 +20,10 @@
 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.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
@@ -184,7 +188,7 @@
         if (method.isStatic() || factory().isConstructor(method.method)) {
           continue;
         }
-        IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.method.holder));
+        IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.holder()));
         assert code != null;
         Value thisValue = code.getThis();
         assert thisValue != null;
@@ -205,7 +209,7 @@
       // CHECK: references to field read usages are fixable.
       boolean fixableFieldReads = true;
       for (DexEncodedMethod method : info.referencedFrom) {
-        IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.method.holder));
+        IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.holder()));
         assert code != null;
         List<StaticGet> singletonFieldReads =
             Streams.stream(code.instructionIterator())
@@ -301,7 +305,7 @@
       Collection<BiConsumer<IRCode, MethodProcessor>> codeOptimizations,
       OptimizationFeedback feedback,
       OneTimeMethodProcessor methodProcessor) {
-    Origin origin = appView.appInfo().originFor(method.method.holder);
+    Origin origin = appView.appInfo().originFor(method.holder());
     IRCode code = method.buildIR(appView, origin);
     codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code, methodProcessor));
     CodeRewriter.removeAssumeInstructions(appView, code);
@@ -329,9 +333,8 @@
     assert candidateInfo != null;
 
     // Find and remove instantiation and its users.
-    for (Instruction instruction : code.instructions()) {
-      if (instruction.isNewInstance()
-          && instruction.asNewInstance().clazz == candidateInfo.candidate.type) {
+    for (NewInstance newInstance : code.<NewInstance>instructions(Instruction::isNewInstance)) {
+      if (newInstance.clazz == candidateInfo.candidate.type) {
         // Remove all usages
         // NOTE: requiring (a) the instance initializer to be trivial, (b) not allowing
         //       candidates with instance fields and (c) requiring candidate to directly
@@ -340,10 +343,31 @@
         assert candidateInfo.candidate.superType == factory().objectType;
         assert candidateInfo.candidate.instanceFields().size() == 0;
 
-        Value singletonValue = instruction.outValue();
+        Value singletonValue = newInstance.outValue();
         assert singletonValue != null;
-        singletonValue.uniqueUsers().forEach(user -> user.removeOrReplaceByDebugLocalRead(code));
-        instruction.removeOrReplaceByDebugLocalRead(code);
+
+        InvokeDirect uniqueConstructorInvoke =
+            newInstance.getUniqueConstructorInvoke(appView.dexItemFactory());
+        assert uniqueConstructorInvoke != null;
+        uniqueConstructorInvoke.removeOrReplaceByDebugLocalRead(code);
+
+        StaticPut uniqueStaticPut = null;
+        for (Instruction user : singletonValue.uniqueUsers()) {
+          if (user.isStaticPut()) {
+            assert uniqueStaticPut == null;
+            uniqueStaticPut = user.asStaticPut();
+          }
+        }
+        assert uniqueStaticPut != null;
+        uniqueStaticPut.removeOrReplaceByDebugLocalRead(code);
+
+        if (newInstance.outValue().hasAnyUsers()) {
+          TypeElement type = TypeElement.fromDexType(newInstance.clazz, maybeNull(), appView);
+          newInstance.replace(
+              new StaticGet(code.createValue(type), candidateInfo.singletonField.field), code);
+        } else {
+          newInstance.removeOrReplaceByDebugLocalRead(code);
+        }
         return;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index 20ea5df..140c844 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -228,24 +228,23 @@
       int concatenationCount = 0;
       // During the second iteration, count builders' usage.
       for (Instruction instr : code.instructions()) {
-        if (!instr.isInvokeVirtual()) {
-          continue;
-        }
-        InvokeVirtual invoke = instr.asInvokeVirtual();
-        DexMethod invokedMethod = invoke.getInvokedMethod();
-        if (optimizationConfiguration.isAppendMethod(invokedMethod)) {
-          concatenationCount++;
-          // The analysis might be overwhelmed.
-          if (concatenationCount > CONCATENATION_THRESHOLD) {
-            return ImmutableSet.of();
-          }
-        } else if (optimizationConfiguration.isToStringMethod(invokedMethod)) {
-          assert invoke.inValues().size() == 1;
-          Value receiver = invoke.getReceiver().getAliasedValue();
-          for (Value builder : collectAllLinkedBuilders(receiver)) {
-            if (builderToStringCounts.containsKey(builder)) {
-              int count = builderToStringCounts.getInt(builder);
-              builderToStringCounts.put(builder, count + 1);
+        if (instr.isInvokeMethod()) {
+          InvokeMethod invoke = instr.asInvokeMethod();
+          DexMethod invokedMethod = invoke.getInvokedMethod();
+          if (optimizationConfiguration.isAppendMethod(invokedMethod)) {
+            concatenationCount++;
+            // The analysis might be overwhelmed.
+            if (concatenationCount > CONCATENATION_THRESHOLD) {
+              return ImmutableSet.of();
+            }
+          } else if (optimizationConfiguration.isToStringMethod(invokedMethod)) {
+            assert invoke.arguments().size() == 1;
+            Value receiver = invoke.getArgument(0).getAliasedValue();
+            for (Value builder : collectAllLinkedBuilders(receiver)) {
+              if (builderToStringCounts.containsKey(builder)) {
+                int count = builderToStringCounts.getInt(builder);
+                builderToStringCounts.put(builder, count + 1);
+              }
             }
           }
         }
@@ -582,9 +581,9 @@
         if (instr == null) {
           break;
         }
-        InvokeVirtual invoke = instr.asInvokeVirtual();
-        assert invoke.inValues().size() == 1;
-        Value builder = invoke.getReceiver().getAliasedValue();
+        InvokeMethod invoke = instr.asInvokeMethod();
+        assert invoke.arguments().size() == 1;
+        Value builder = invoke.getArgument(0).getAliasedValue();
         Value outValue = invoke.outValue();
         if (outValue == null || outValue.isDead(appView, code)) {
           // If the out value is still used but potentially dead, replace it with a dummy string.
@@ -627,16 +626,16 @@
     }
 
     private boolean isToStringOfInterest(Set<Value> candidateBuilders, Instruction instr) {
-      if (!instr.isInvokeVirtual()) {
+      if (!instr.isInvokeMethod()) {
         return false;
       }
-      InvokeVirtual invoke = instr.asInvokeVirtual();
+      InvokeMethod invoke = instr.asInvokeMethod();
       DexMethod invokedMethod = invoke.getInvokedMethod();
       if (!optimizationConfiguration.isToStringMethod(invokedMethod)) {
         return false;
       }
-      assert invoke.inValues().size() == 1;
-      Value builder = invoke.getReceiver().getAliasedValue();
+      assert invoke.arguments().size() == 1;
+      Value builder = invoke.getArgument(0).getAliasedValue();
       if (!candidateBuilders.contains(builder)) {
         return false;
       }
@@ -736,11 +735,11 @@
       InstructionListIterator it = code.instructionListIterator();
       while (it.hasNext()) {
         Instruction instr = it.next();
-        if (instr.isInvokeVirtual()) {
-          InvokeVirtual invoke = instr.asInvokeVirtual();
+        if (instr.isInvokeMethod()) {
+          InvokeMethod invoke = instr.asInvokeMethod();
           DexMethod invokedMethod = invoke.getInvokedMethod();
           if (optimizationConfiguration.isToStringMethod(invokedMethod)
-              && buildersToRemove.contains(invoke.getReceiver().getAliasedValue())) {
+              && buildersToRemove.contains(invoke.getArgument(0).getAliasedValue())) {
             it.removeOrReplaceByDebugLocalRead();
           }
         }
@@ -852,7 +851,8 @@
     @Override
     public boolean isToStringMethod(DexMethod method) {
       return method == factory.stringBuilderMethods.toString
-          || method == factory.stringBufferMethods.toString;
+          || method == factory.stringBufferMethods.toString
+          || method == factory.stringMethods.valueOf;
     }
 
     private boolean canHandleArgumentType(DexType argType) {
@@ -900,20 +900,12 @@
 
         InvokeMethod invoke = escapeRoute.asInvokeMethod();
         DexMethod invokedMethod = invoke.getInvokedMethod();
-        // Make sure builder's uses are local, i.e., not escaping from the current method.
-        if (invokedMethod.holder != builderType) {
-          logEscapingRoute(false);
-          return false;
-        }
-        // <init> is legitimate.
-        if (optimizationConfiguration.isBuilderInit(invokedMethod, builderType)) {
-          return true;
-        }
+
         if (optimizationConfiguration.isToStringMethod(invokedMethod)) {
           Value out = escapeRoute.outValue();
           if (out != null) {
-            // If Builder#toString is interned, it could be used for equality check.
-            // Replacing builder-based runtime result with a compile time constant may change
+            // If Builder#toString or String#valueOf is interned, it could be used for equality
+            // check. Replacing builder-based runtime result with a compile time constant may change
             // the program's runtime behavior.
             for (Instruction outUser : out.uniqueUsers()) {
               if (outUser.isInvokeMethodWithReceiver()
@@ -924,7 +916,18 @@
               }
             }
           }
-          // Otherwise, use of Builder#toString is legitimate.
+          // Otherwise, use of Builder#toString and String#valueOf is legitimate.
+          return true;
+        }
+
+        // Make sure builder's uses are local, i.e., not escaping from the current method.
+        if (invokedMethod.holder != builderType) {
+          logEscapingRoute(false);
+          return false;
+        }
+
+        // <init> is legitimate.
+        if (optimizationConfiguration.isBuilderInit(invokedMethod, builderType)) {
           return true;
         }
         // Even though all invocations belong to the builder type, there are some methods other
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
index 1cb6482..f7e377d 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
@@ -139,14 +139,14 @@
       if (move.definition.isArgument()) {
         Argument argument = move.definition.asArgument();
         int argumentRegister = argument.outValue().getLiveIntervals().getRegister();
-        Value to = new FixedRegisterValue(argument.outValue().getType(), move.dst);
-        Value from = new FixedRegisterValue(argument.outValue().getType(), argumentRegister);
+        Value to = new FixedRegisterValue(argument.getOutType(), move.dst);
+        Value from = new FixedRegisterValue(argument.getOutType(), argumentRegister);
         instruction = new Move(to, from);
       } else {
         assert move.definition.isOutConstant();
         ConstInstruction definition = move.definition.getOutConstantConstInstruction();
         if (definition.isConstNumber()) {
-          Value to = new FixedRegisterValue(move.definition.outValue().getType(), move.dst);
+          Value to = new FixedRegisterValue(move.definition.getOutType(), move.dst);
           instruction = new ConstNumber(to, definition.asConstNumber().getRawValue());
         } else {
           throw new Unreachable("Unexpected definition");
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 50885dc..cb1823a 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
@@ -30,7 +30,6 @@
 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.utils.StringDiagnostic;
 import java.util.ArrayList;
 import java.util.List;
 import org.objectweb.asm.Opcodes;
@@ -41,24 +40,14 @@
     super(appView, holder);
   }
 
-  boolean shouldConvert(
-      DexType type, DesugaredLibraryAPIConverter converter, DexString methodName) {
+  boolean shouldConvert(DexType type, DesugaredLibraryAPIConverter converter, DexMethod method) {
     if (!appView.rewritePrefix.hasRewrittenType(type, appView)) {
       return false;
     }
     if (converter.canConvert(type)) {
       return true;
     }
-    appView
-        .options()
-        .reporter
-        .warning(
-            new StringDiagnostic(
-                "Desugared library API conversion failed for "
-                    + type
-                    + ", unexpected behavior for method "
-                    + methodName
-                    + "."));
+    converter.reportInvalidInvoke(type, method, "");
     return false;
   }
 
@@ -101,7 +90,7 @@
       DexType[] newParameters = forwardMethod.proto.parameters.values.clone();
       for (DexType param : forwardMethod.proto.parameters.values) {
         instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
-        if (shouldConvert(param, converter, forwardMethod.name)) {
+        if (shouldConvert(param, converter, forwardMethod)) {
           instructions.add(
               new CfInvoke(
                   Opcodes.INVOKESTATIC,
@@ -132,7 +121,7 @@
         instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, newForwardMethod, false));
       }
 
-      if (shouldConvert(returnType, converter, forwardMethod.name)) {
+      if (shouldConvert(returnType, converter, forwardMethod)) {
         instructions.add(
             new CfInvoke(
                 Opcodes.INVOKESTATIC,
@@ -188,7 +177,7 @@
       int stackIndex = 1;
       for (DexType param : forwardMethod.proto.parameters.values) {
         instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
-        if (shouldConvert(param, converter, forwardMethod.name)) {
+        if (shouldConvert(param, converter, forwardMethod)) {
           instructions.add(
               new CfInvoke(
                   Opcodes.INVOKESTATIC,
@@ -208,7 +197,7 @@
       }
 
       DexType returnType = forwardMethod.proto.returnType;
-      if (shouldConvert(returnType, converter, forwardMethod.name)) {
+      if (shouldConvert(returnType, converter, forwardMethod)) {
         instructions.add(
             new CfInvoke(
                 Opcodes.INVOKESTATIC,
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
new file mode 100644
index 0000000..fbfacb8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -0,0 +1,107 @@
+// 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.synthetic;
+
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public abstract class EnumUnboxingCfCodeProvider extends SyntheticCfCodeProvider {
+
+  EnumUnboxingCfCodeProvider(AppView<?> appView, DexType holder) {
+    super(appView, holder);
+  }
+
+  public static class EnumUnboxingValueOfCfCodeProvider extends EnumUnboxingCfCodeProvider {
+
+    private DexType enumType;
+    private EnumValueInfoMap map;
+
+    public EnumUnboxingValueOfCfCodeProvider(
+        AppView<?> appView, DexType holder, DexType enumType, EnumValueInfoMap map) {
+      super(appView, holder);
+      this.enumType = enumType;
+      this.map = map;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      // Generated static method, for class com.x.MyEnum {A,B} would look like:
+      // int UtilityClass#com.x.MyEnum_valueOf(String s) {
+      // 	if (s == null) { throw npe("Name is null"); }
+      // 	if (s.equals("A")) { return 1;}
+      // 	if (s.equals("B")) { return 2;}
+      //  throw new IllegalArgumentException(
+      //            "No enum constant com.x.MyEnum." + s);
+      DexItemFactory factory = appView.dexItemFactory();
+      List<CfInstruction> instructions = new ArrayList<>();
+
+      // if (s == null) { throw npe("Name is null"); }
+      CfLabel nullDest = new CfLabel();
+      instructions.add(new CfLoad(ValueType.fromDexType(factory.stringType), 0));
+      instructions.add(new CfIf(If.Type.NE, ValueType.OBJECT, nullDest));
+      instructions.add(new CfNew(factory.npeType));
+      instructions.add(new CfStackInstruction(Opcode.Dup));
+      instructions.add(new CfConstString(appView.dexItemFactory().createString("Name is null")));
+      instructions.add(
+          new CfInvoke(Opcodes.INVOKESPECIAL, factory.npeMethods.initWithMessage, false));
+      instructions.add(new CfThrow());
+      instructions.add(nullDest);
+
+      // if (s.equals("A")) { return 1;}
+      // if (s.equals("B")) { return 2;}
+      map.forEach(
+          (field, enumValueInfo) -> {
+            CfLabel dest = new CfLabel();
+            instructions.add(new CfLoad(ValueType.fromDexType(factory.stringType), 0));
+            instructions.add(new CfConstString(field.name));
+            instructions.add(
+                new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.stringMethods.equals, false));
+            instructions.add(new CfIf(If.Type.EQ, ValueType.INT, dest));
+            instructions.add(new CfConstNumber(enumValueInfo.convertToInt(), ValueType.INT));
+            instructions.add(new CfReturn(ValueType.INT));
+            instructions.add(dest);
+          });
+
+      // throw new IllegalArgumentException("No enum constant com.x.MyEnum." + s);
+      instructions.add(new CfNew(factory.illegalArgumentExceptionType));
+      instructions.add(new CfStackInstruction(Opcode.Dup));
+      instructions.add(
+          new CfConstString(
+              appView
+                  .dexItemFactory()
+                  .createString(
+                      "No enum constant " + enumType.toSourceString().replace('$', '.') + ".")));
+      instructions.add(new CfLoad(ValueType.fromDexType(factory.stringType), 0));
+      instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.stringMethods.concat, false));
+      instructions.add(
+          new CfInvoke(
+              Opcodes.INVOKESPECIAL,
+              factory.illegalArgumentExceptionMethods.initWithMessage,
+              false));
+      instructions.add(new CfThrow());
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 688d59b..e04f07c 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -128,7 +128,8 @@
     ClassWriter writer = new ClassWriter(0);
     int markerStringPoolIndex = writer.newConst(markerString);
     assert markerStringPoolIndex == MARKER_STRING_CONSTANT_POOL_INDEX;
-    writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, null);
+    String sourceDebug = getSourceDebugExtension(clazz.annotations());
+    writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, sourceDebug);
     int version = getClassFileVersion(clazz);
     int access = clazz.accessFlags.getAsCfAccessFlags();
     String desc = namingLens.lookupDescriptor(clazz.type).toString();
@@ -242,6 +243,16 @@
     return res.toString();
   }
 
+  private String getSourceDebugExtension(DexAnnotationSet annotations) {
+    DexValue debugExtensions =
+        getSystemAnnotationValue(
+            annotations, application.dexItemFactory.annotationSourceDebugExtension);
+    if (debugExtensions == null) {
+      return null;
+    }
+    return debugExtensions.asDexValueString().getValue().toString();
+  }
+
   private ImmutableMap<DexString, DexValue> getAnnotationDefaults(DexAnnotationSet annotations) {
     DexValue value =
         getSystemAnnotationValue(annotations, application.dexItemFactory.annotationDefault);
diff --git a/src/main/java/com/android/tools/r8/kotlin/ClassTypeSignatureToRenamedKmTypeConverter.java b/src/main/java/com/android/tools/r8/kotlin/ClassTypeSignatureToRenamedKmTypeConverter.java
new file mode 100644
index 0000000..ea1ddc3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/ClassTypeSignatureToRenamedKmTypeConverter.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static kotlinx.metadata.FlagsKt.flagsOf;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.List;
+import java.util.function.Function;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeParameter;
+import kotlinx.metadata.KmTypeProjection;
+import kotlinx.metadata.KmVariance;
+
+class ClassTypeSignatureToRenamedKmTypeConverter implements ClassTypeSignature.Converter<KmType> {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final List<KmTypeParameter> typeParameters;
+  private final Function<DexType, String> toRenamedClassClassifier;
+
+  ClassTypeSignatureToRenamedKmTypeConverter(
+      AppView<AppInfoWithLiveness> appView,
+      List<KmTypeParameter> typeParameters,
+      Function<DexType, String> toRenamedClassClassifier) {
+    this.appView = appView;
+    this.typeParameters = typeParameters;
+    this.toRenamedClassClassifier = toRenamedClassClassifier;
+  }
+
+  @Override
+  public KmType init() {
+    return new KmType(flagsOf());
+  }
+
+  @Override
+  public KmType visitType(DexType type, KmType result) {
+    String classifier = toRenamedClassClassifier.apply(type);
+    if (classifier == null) {
+      return null;
+    }
+    result.visitClass(classifier);
+    return result;
+  }
+
+  @Override
+  public KmType visitTypeArgument(FieldTypeSignature typeArgument, KmType result) {
+    if (result == null) {
+      return null;
+    }
+    List<KmTypeProjection> arguments = result.getArguments();
+    KotlinMetadataSynthesizerUtils.populateKmTypeFromSignature(
+        typeArgument,
+        () -> {
+          KmType kmTypeArgument = new KmType(flagsOf());
+          arguments.add(new KmTypeProjection(KmVariance.INVARIANT, kmTypeArgument));
+          return kmTypeArgument;
+        },
+        typeParameters,
+        appView.dexItemFactory());
+    return result;
+  }
+
+  @Override
+  public KmType visitInnerTypeSignature(ClassTypeSignature innerTypeSignature, KmType result) {
+    // Do nothing
+    return result;
+  }
+
+  public List<KmTypeParameter> getTypeParameters() {
+    return typeParameters;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index 136947b..c891897 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -6,9 +6,6 @@
 
 import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toKmType;
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedClassifier;
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmConstructor;
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmType;
 import static kotlinx.metadata.Flag.IS_SEALED;
 
 import com.android.tools.r8.graph.AppView;
@@ -22,7 +19,6 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
 import kotlinx.metadata.KmClass;
 import kotlinx.metadata.KmConstructor;
@@ -77,21 +73,26 @@
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    KotlinMetadataSynthesizer synthesizer = new KotlinMetadataSynthesizer(appView, lens, this);
     if (appView.options().enableKotlinMetadataRewritingForRenamedClasses
         && lens.lookupType(clazz.type, appView.dexItemFactory()) != clazz.type) {
-      String renamedClassifier = toRenamedClassifier(clazz.type, appView, lens);
+      String renamedClassifier = synthesizer.toRenamedClassifier(clazz.type);
       if (renamedClassifier != null) {
         assert !kmClass.getName().equals(renamedClassifier);
         kmClass.setName(renamedClassifier);
       }
     }
 
+    ClassTypeSignatureToRenamedKmTypeConverter converter =
+        new ClassTypeSignatureToRenamedKmTypeConverter(
+            appView, getTypeParameters(), synthesizer::toRenamedClassifier);
+
     // Rewriting upward hierarchy.
     List<KmType> superTypes = kmClass.getSupertypes();
     superTypes.clear();
     for (DexType itfType : clazz.interfaces.values) {
       // TODO(b/129925954): Use GenericSignature.ClassSignature#superInterfaceSignatures
-      KmType kmType = toRenamedKmType(itfType, null, appView, lens);
+      KmType kmType = synthesizer.toRenamedKmType(itfType, null, null, converter);
       if (kmType != null) {
         superTypes.add(kmType);
       }
@@ -99,7 +100,8 @@
     assert clazz.superType != null;
     if (clazz.superType != appView.dexItemFactory().objectType) {
       // TODO(b/129925954): Use GenericSignature.ClassSignature#superClassSignature
-      KmType kmTypeForSupertype = toRenamedKmType(clazz.superType, null, appView, lens);
+      KmType kmTypeForSupertype =
+          synthesizer.toRenamedKmType(clazz.superType, null, null, converter);
       if (kmTypeForSupertype != null) {
         superTypes.add(kmTypeForSupertype);
       }
@@ -131,7 +133,7 @@
     sealedSubclasses.clear();
     if (IS_SEALED.invoke(kmClass.getFlags())) {
       for (DexType subtype : appView.appInfo().allImmediateSubtypes(clazz.type)) {
-        String classifier = toRenamedClassifier(subtype, appView, lens);
+        String classifier = synthesizer.toRenamedClassifier(subtype);
         if (classifier != null) {
           sealedSubclasses.add(classifier);
         }
@@ -149,7 +151,7 @@
       if (!method.isInstanceInitializer()) {
         continue;
       }
-      KmConstructor constructor = toRenamedKmConstructor(method, appView, lens);
+      KmConstructor constructor = synthesizer.toRenamedKmConstructor(method);
       if (constructor != null) {
         constructors.add(constructor);
       }
@@ -162,7 +164,7 @@
 
     // TODO(b/151193864): enum entries
 
-    rewriteDeclarationContainer(appView, lens);
+    rewriteDeclarationContainer(synthesizer);
   }
 
   @Override
@@ -190,11 +192,13 @@
   @Override
   public String toString(String indent) {
     StringBuilder sb = new StringBuilder(indent);
-    sb.append("Metadata.Class {");
-    sb.append(StringUtils.LINE_SEPARATOR);
-    sb.append(kmDeclarationContainerToString(indent + INDENT));
-    sb.append(indent);
-    sb.append("}");
+    appendKmSection(
+        indent,
+        "Metadata.Class",
+        sb,
+        newIndent -> {
+          appendKmClass(newIndent, sb, kmClass);
+        });
     return sb.toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index 627ac9d..fc3fbbe 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedBinaryName;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
@@ -46,12 +44,13 @@
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
     ListIterator<String> partClassIterator = partClassNames.listIterator();
+    KotlinMetadataSynthesizer synthesizer = new KotlinMetadataSynthesizer(appView, lens, this);
     while (partClassIterator.hasNext()) {
       String partClassName = partClassIterator.next();
       partClassIterator.remove();
       DexType partClassType = appView.dexItemFactory().createType(
           DescriptorUtils.getDescriptorFromClassBinaryName(partClassName));
-      String renamedPartClassName = toRenamedBinaryName(partClassType, appView, lens);
+      String renamedPartClassName = synthesizer.toRenamedBinaryName(partClassType);
       if (renamedPartClassName != null) {
         partClassIterator.add(renamedPartClassName);
       }
@@ -82,6 +81,6 @@
 
   @Override
   public String toString(String indent) {
-    return indent + "MultiFileClassFacade(" + StringUtils.join(partClassNames, ", ") + ")";
+    return indent + "MetaData.MultiFileClassFacade(" + StringUtils.join(partClassNames, ", ") + ")";
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index e67cd05..90f265c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,15 +4,12 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedBinaryName;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.StringUtils;
 import kotlinx.metadata.KmPackage;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -45,11 +42,12 @@
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
     DexType facadeClassType = appView.dexItemFactory().createType(
         DescriptorUtils.getDescriptorFromClassBinaryName(facadeClassName));
-    facadeClassName = toRenamedBinaryName(facadeClassType, appView, lens);
+    KotlinMetadataSynthesizer synthesizer = new KotlinMetadataSynthesizer(appView, lens, this);
+    facadeClassName = synthesizer.toRenamedBinaryName(facadeClassType);
     if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
-    rewriteDeclarationContainer(appView, lens);
+    rewriteDeclarationContainer(synthesizer);
   }
 
   @Override
@@ -85,12 +83,14 @@
   @Override
   public String toString(String indent) {
     StringBuilder sb = new StringBuilder(indent);
-    sb.append("Metadata.MultiFileClassPart {");
-    sb.append(StringUtils.LINE_SEPARATOR);
-    sb.append(kmDeclarationContainerToString(indent + INDENT));
-    appendKeyValue(indent + INDENT, "facadeClassName", facadeClassName, sb);
-    sb.append(indent);
-    sb.append("}");
+    appendKmSection(
+        indent,
+        "Metadata.MultiFileClassPart",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "facadeClassName", sb, facadeClassName);
+          appendKmPackage(newIndent, sb, kmPackage);
+        });
     return sb.toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index 08fc8ef..8ec966b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -38,7 +38,7 @@
     if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
-    rewriteDeclarationContainer(appView, lens);
+    rewriteDeclarationContainer(new KotlinMetadataSynthesizer(appView, lens, this));
   }
 
   @Override
@@ -66,11 +66,13 @@
   @Override
   public String toString(String indent) {
     StringBuilder sb = new StringBuilder(indent);
-    sb.append("Metadata.MultiFileClassPart {\n");
-    sb.append(kmDeclarationContainerToString(indent + INDENT));
-    appendKeyValue(indent + INDENT, "package", kmPackage.toString(), sb);
-    sb.append(indent);
-    sb.append("}");
+    appendKmSection(
+        indent,
+        "Metadata.FileFacade",
+        sb,
+        newIndent -> {
+          appendKmPackage(newIndent, sb, kmPackage);
+        });
     return sb.toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index 967cf05..64ed559 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunction;
+import static com.android.tools.r8.utils.StringUtils.LINE_SEPARATOR;
 import static kotlinx.metadata.Flag.Class.IS_COMPANION_OBJECT;
 
 import com.android.tools.r8.errors.Unreachable;
@@ -12,29 +12,46 @@
 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.kotlin.KotlinMemberInfo.KotlinFieldInfo;
+import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinPropertyInfo;
 import com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.KmPropertyGroup;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.function.BiFunction;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import kotlinx.metadata.KmAnnotation;
+import kotlinx.metadata.KmAnnotationArgument;
+import kotlinx.metadata.KmClass;
+import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmDeclarationContainer;
 import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.KmPackage;
 import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmTypeAlias;
 import kotlinx.metadata.KmTypeParameter;
+import kotlinx.metadata.KmTypeProjection;
+import kotlinx.metadata.KmValueParameter;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+import kotlinx.metadata.jvm.JvmFieldSignature;
+import kotlinx.metadata.jvm.JvmMethodSignature;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 // Provides access to package/class-level Kotlin information.
 public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> {
+
   final DexClass clazz;
+  private static final List<KmTypeParameter> EMPTY_TYPE_PARAMS = ImmutableList.of();
 
   KotlinInfo(MetadataKind metadata, DexClass clazz) {
     assert clazz != null;
@@ -52,6 +69,13 @@
 
   abstract KotlinClassHeader createHeader();
 
+  public final List<KmTypeParameter> getTypeParameters() {
+    if (!this.isClass()) {
+      return EMPTY_TYPE_PARAMS;
+    }
+    return this.asClass().kmClass.getTypeParameters();
+  }
+
   public enum Kind {
     Class, File, Synthetic, Part, Facade
   }
@@ -116,15 +140,37 @@
 
   // {@link KmClass} and {@link KmPackage} are inherited from {@link KmDeclarationContainer} that
   // abstract functions and properties. Rewriting of those portions can be unified here.
-  void rewriteDeclarationContainer(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+  void rewriteDeclarationContainer(KotlinMetadataSynthesizer synthesizer) {
     assert clazz != null;
 
     KmDeclarationContainer kmDeclarationContainer = getDeclarations();
-    Map<String, KmPropertyGroup.Builder> propertyGroupBuilderMap = new HashMap<>();
+    rewriteFunctions(synthesizer, kmDeclarationContainer.getFunctions());
+    rewriteProperties(synthesizer, kmDeclarationContainer.getProperties());
+  }
 
+  private void rewriteFunctions(KotlinMetadataSynthesizer synthesizer, List<KmFunction> functions) {
+    functions.clear();
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (method.isInitializer()) {
+        continue;
+      }
+      if (method.isKotlinFunction() || method.isKotlinExtensionFunction()) {
+        KmFunction function = synthesizer.toRenamedKmFunction(method);
+        if (function != null) {
+          functions.add(function);
+        }
+      }
+      // TODO(b/151194869): What should we do for methods that fall into this category---no mark?
+    }
+  }
+
+  private void rewriteProperties(
+      KotlinMetadataSynthesizer synthesizer, List<KmProperty> properties) {
+    Map<String, KmPropertyGroup.Builder> propertyGroupBuilderMap = new HashMap<>();
     // Backing fields for a companion object are declared in its host class.
     Iterable<DexEncodedField> fields = clazz.fields();
     Predicate<DexEncodedField> backingFieldTester = DexEncodedField::isKotlinBackingField;
+    List<KmTypeParameter> classTypeParameters = getTypeParameters();
     if (isClass()) {
       KotlinClass ktClass = asClass();
       if (IS_COMPANION_OBJECT.invoke(ktClass.kmClass.getFlags()) && ktClass.hostClass != null) {
@@ -132,54 +178,46 @@
         backingFieldTester = DexEncodedField::isKotlinBackingFieldForCompanionObject;
       }
     }
-
     for (DexEncodedField field : fields) {
       if (backingFieldTester.test(field)) {
-        String name = field.getKotlinMemberInfo().propertyName;
+        KotlinFieldInfo kotlinFieldInfo = field.getKotlinMemberInfo().asFieldInfo();
+        assert kotlinFieldInfo != null;
+        String name = kotlinFieldInfo.propertyName;
         assert name != null;
         KmPropertyGroup.Builder builder =
             propertyGroupBuilderMap.computeIfAbsent(
                 name,
-                k -> KmPropertyGroup.builder(field.getKotlinMemberInfo().propertyFlags, name));
+                k -> KmPropertyGroup.builder(kotlinFieldInfo.flags, name, classTypeParameters));
         builder.foundBackingField(field);
       }
     }
-
-    List<KmFunction> functions = kmDeclarationContainer.getFunctions();
-    functions.clear();
     for (DexEncodedMethod method : clazz.methods()) {
       if (method.isInitializer()) {
         continue;
       }
-
-      if (method.isKotlinFunction() || method.isKotlinExtensionFunction()) {
-        KmFunction function = toRenamedKmFunction(method, appView, lens);
-        if (function != null) {
-          functions.add(function);
-        }
-        continue;
-      }
       if (method.isKotlinProperty() || method.isKotlinExtensionProperty()) {
-        String name = method.getKotlinMemberInfo().propertyName;
+        assert method.getKotlinMemberInfo().isPropertyInfo();
+        KotlinPropertyInfo kotlinPropertyInfo = method.getKotlinMemberInfo().asPropertyInfo();
+        String name = kotlinPropertyInfo.propertyName;
         assert name != null;
         KmPropertyGroup.Builder builder =
             propertyGroupBuilderMap.computeIfAbsent(
                 name,
                 // Hitting here (creating a property builder) after visiting all fields means that
                 // this property doesn't have a backing field. Don't use members' flags.
-                k -> KmPropertyGroup.builder(method.getKotlinMemberInfo().propertyFlags, name));
-        switch (method.getKotlinMemberInfo().memberKind) {
+                k -> KmPropertyGroup.builder(kotlinPropertyInfo.flags, name, classTypeParameters));
+        switch (kotlinPropertyInfo.memberKind) {
           case EXTENSION_PROPERTY_GETTER:
             builder.isExtensionGetter();
             // fallthrough;
           case PROPERTY_GETTER:
-            builder.foundGetter(method, method.getKotlinMemberInfo().flags);
+            builder.foundGetter(method, kotlinPropertyInfo);
             break;
           case EXTENSION_PROPERTY_SETTER:
             builder.isExtensionSetter();
             // fallthrough;
           case PROPERTY_SETTER:
-            builder.foundSetter(method, method.getKotlinMemberInfo().flags);
+            builder.foundSetter(method, kotlinPropertyInfo);
             break;
           case EXTENSION_PROPERTY_ANNOTATIONS:
             builder.isExtensionAnnotations();
@@ -190,20 +228,16 @@
           default:
             throw new Unreachable("Not a Kotlin property: " + method.getKotlinMemberInfo());
         }
-        continue;
       }
-
       // TODO(b/151194869): What should we do for methods that fall into this category---no mark?
     }
-
-    List<KmProperty> properties = kmDeclarationContainer.getProperties();
     properties.clear();
     for (KmPropertyGroup.Builder builder : propertyGroupBuilderMap.values()) {
       KmPropertyGroup group = builder.build();
       if (group == null) {
         continue;
       }
-      KmProperty property = group.toRenamedKmProperty(appView, lens);
+      KmProperty property = group.toRenamedKmProperty(synthesizer);
       if (property != null) {
         properties.add(property);
       }
@@ -212,124 +246,557 @@
 
   public abstract String toString(String indent);
 
-  String kmDeclarationContainerToString(String indent) {
-    StringBuilder sb = new StringBuilder();
-    KmDeclarationContainer declarations = getDeclarations();
-    appendKmSection(indent, "functions", declarations.getFunctions(), this::kmFunctionToString, sb);
-    appendKmSection(
-        indent, "properties", declarations.getProperties(), this::kmPropertyToString, sb);
-    appendKmSection(
-        indent, "typeAliases", declarations.getTypeAliases(), this::kmTypeAliasToString, sb);
-    return sb.toString();
+  static final String INDENT = "  ";
+
+  private static <T> void appendKmHelper(
+      String key, StringBuilder sb, Action appendContent, String start, String end) {
+    sb.append(key);
+    sb.append(start);
+    appendContent.execute();
+    sb.append(end);
   }
 
-  final String INDENT = "  ";
+  static <T> void appendKmSection(
+      String indent, String typeDescription, StringBuilder sb, Consumer<String> appendContent) {
+    appendKmHelper(
+        typeDescription,
+        sb,
+        () -> appendContent.accept(indent + INDENT),
+        "{" + LINE_SEPARATOR,
+        indent + "}");
+  }
 
-  private <T> void appendKmSection(
+  static <T> void appendKmList(
       String indent,
-      String header,
+      String typeDescription,
+      StringBuilder sb,
       List<T> items,
-      BiFunction<String, T, String> stringify,
-      StringBuilder sb) {
-    if (items.size() > 0) {
-      sb.append(indent);
-      sb.append(header);
-      sb.append(": [");
-      sb.append(StringUtils.LINE_SEPARATOR);
+      BiConsumer<String, T> appendItem) {
+    if (items.isEmpty()) {
+      sb.append(typeDescription).append("[]");
+      return;
     }
-    for (T item : items) {
-      sb.append(stringify.apply(indent + INDENT, item));
-      sb.append(",");
-      sb.append(StringUtils.LINE_SEPARATOR);
-    }
-    if (items.size() > 0) {
-      sb.append(indent);
-      sb.append("]");
-      sb.append(StringUtils.LINE_SEPARATOR);
-    }
+    appendKmHelper(
+        typeDescription,
+        sb,
+        () -> {
+          for (T kmItem : items) {
+            sb.append(indent).append(INDENT);
+            appendItem.accept(indent + INDENT, kmItem);
+            sb.append(LINE_SEPARATOR);
+          }
+        },
+        "[" + LINE_SEPARATOR,
+        indent + "]");
   }
 
-  private String kmFunctionToString(String indent, KmFunction function) {
-    assert function != null;
-    StringBuilder sb = new StringBuilder();
+  static void appendKeyValue(
+      String indent, String key, StringBuilder sb, Consumer<String> appendValue) {
     sb.append(indent);
-    sb.append("KmFunction {");
-    sb.append(StringUtils.LINE_SEPARATOR);
-    String newIndent = indent + INDENT;
-    KmType receiverParameterType = function.getReceiverParameterType();
+    appendKmHelper(key, sb, () -> appendValue.accept(indent), ": ", "," + LINE_SEPARATOR);
+  }
+
+  static void appendKeyValue(String indent, String key, StringBuilder sb, String value) {
+    sb.append(indent);
+    appendKmHelper(key, sb, () -> sb.append(value), ": ", "," + LINE_SEPARATOR);
+  }
+
+  static void appendKmDeclarationContainer(
+      String indent, StringBuilder sb, KmDeclarationContainer container) {
     appendKeyValue(
-        newIndent,
-        "receiverParameterType",
-        receiverParameterType == null ? "null" : kmTypeToString(receiverParameterType),
-        sb);
-    appendKeyValue(newIndent, "returnType", kmTypeToString(function.returnType), sb);
-    appendKeyValue(newIndent, "name", function.getName(), sb);
-    // TODO(b/148581822): Print flags, generic signature etc.
-    sb.append(indent);
-    sb.append("}");
-    return sb.toString();
+        indent,
+        "functions",
+        sb,
+        newIndent -> {
+          appendKmList(
+              newIndent,
+              "KmFunction",
+              sb,
+              container.getFunctions().stream()
+                  .sorted(Comparator.comparing(KmFunction::getName))
+                  .collect(Collectors.toList()),
+              (nextIndent, kmFunction) -> {
+                appendKmFunction(nextIndent, sb, kmFunction);
+              });
+        });
+    appendKeyValue(
+        indent,
+        "properties",
+        sb,
+        newIndent -> {
+          appendKmList(
+              newIndent,
+              "KmProperty",
+              sb,
+              container.getProperties().stream()
+                  .sorted(Comparator.comparing(KmProperty::getName))
+                  .collect(Collectors.toList()),
+              (nextIndent, kmProperty) -> {
+                appendKmProperty(nextIndent, sb, kmProperty);
+              });
+        });
+    appendKeyValue(
+        indent,
+        "typeAliases",
+        sb,
+        newIndent -> {
+          appendKmList(
+              newIndent,
+              "KmTypeAlias",
+              sb,
+              container.getTypeAliases().stream()
+                  .sorted(Comparator.comparing(KmTypeAlias::getName))
+                  .collect(Collectors.toList()),
+              (nextIndent, kmTypeAlias) -> {
+                appendTypeAlias(nextIndent, sb, kmTypeAlias);
+              });
+        });
   }
 
-  private String kmPropertyToString(String indent, KmProperty property) {
-    // TODO(b/148581822): Add information.
-    return indent + "KmProperty { " + property + "}";
+  static void appendKmPackage(String indent, StringBuilder sb, KmPackage kmPackage) {
+    appendKmDeclarationContainer(indent, sb, kmPackage);
+    appendKeyValue(indent, "moduleName", sb, JvmExtensionsKt.getModuleName(kmPackage));
+    appendKeyValue(
+        indent,
+        "localDelegatedProperties",
+        sb,
+        nextIndent -> {
+          appendKmList(
+              nextIndent,
+              "KmProperty",
+              sb,
+              JvmExtensionsKt.getLocalDelegatedProperties(kmPackage),
+              (nextNextIndent, kmProperty) -> {
+                appendKmProperty(nextNextIndent, sb, kmProperty);
+              });
+        });
   }
 
-  private String kmTypeAliasToString(String indent, KmTypeAlias alias) {
-    assert alias != null;
-    StringBuilder sb = new StringBuilder(indent);
-    sb.append("KmTypeAlias {");
-    sb.append(StringUtils.LINE_SEPARATOR);
-    String newIndent = indent + INDENT;
-    appendKeyValue(newIndent, "name", alias.getName(), sb);
-    if (!alias.getTypeParameters().isEmpty()) {
-      appendKeyValue(
-          newIndent,
-          "typeParameters",
-          alias.getTypeParameters().stream()
-              .map(KmTypeParameter::getName)
-              .collect(Collectors.joining(",")),
-          sb);
+  static void appendKmClass(String indent, StringBuilder sb, KmClass kmClass) {
+    appendKeyValue(indent, "flags", sb, kmClass.getFlags() + "");
+    appendKeyValue(indent, "name", sb, kmClass.getName());
+    appendKeyValue(
+        indent,
+        "typeParameters",
+        sb,
+        newIndent -> {
+          appendTypeParameters(newIndent, sb, kmClass.getTypeParameters());
+        });
+    appendKeyValue(
+        indent,
+        "superTypes",
+        sb,
+        newIndent -> {
+          appendKmList(
+              newIndent,
+              "KmType",
+              sb,
+              kmClass.getSupertypes(),
+              (nextIndent, kmType) -> {
+                appendKmType(nextIndent, sb, kmType);
+              });
+        });
+    String companionObject = kmClass.getCompanionObject();
+    appendKeyValue(
+        indent, "enumEntries", sb, "[" + StringUtils.join(kmClass.getEnumEntries(), ",") + "]");
+    appendKeyValue(
+        indent, "companionObject", sb, companionObject == null ? "null" : companionObject);
+    appendKeyValue(
+        indent,
+        "sealedSubclasses",
+        sb,
+        "[" + StringUtils.join(kmClass.getSealedSubclasses(), ",") + "]");
+    appendKeyValue(
+        indent, "nestedClasses", sb, "[" + StringUtils.join(kmClass.getNestedClasses(), ",") + "]");
+    appendKeyValue(
+        indent,
+        "anonymousObjectOriginName",
+        sb,
+        JvmExtensionsKt.getAnonymousObjectOriginName(kmClass));
+    appendKeyValue(indent, "moduleName", sb, JvmExtensionsKt.getModuleName(kmClass));
+    appendKeyValue(
+        indent,
+        "localDelegatedProperties",
+        sb,
+        nextIndent -> {
+          appendKmList(
+              nextIndent,
+              "KmProperty",
+              sb,
+              JvmExtensionsKt.getLocalDelegatedProperties(kmClass),
+              (nextNextIndent, kmProperty) -> {
+                appendKmProperty(nextNextIndent, sb, kmProperty);
+              });
+        });
+    appendKeyValue(
+        indent,
+        "constructors",
+        sb,
+        newIndent -> {
+          appendKmList(
+              newIndent,
+              "KmConstructor",
+              sb,
+              kmClass.getConstructors(),
+              (nextIndent, constructor) -> {
+                appendKmConstructor(nextIndent, sb, constructor);
+              });
+        });
+    appendKmDeclarationContainer(indent, sb, kmClass);
+  }
+
+  private static void appendKmConstructor(
+      String indent, StringBuilder sb, KmConstructor constructor) {
+    appendKmSection(
+        indent,
+        "KmConstructor",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "flags", sb, constructor.getFlags() + "");
+          appendKeyValue(
+              newIndent,
+              "valueParameters",
+              sb,
+              nextIndent ->
+                  appendValueParameters(nextIndent, sb, constructor.getValueParameters()));
+          JvmMethodSignature signature = JvmExtensionsKt.getSignature(constructor);
+          appendKeyValue(
+              newIndent, "signature", sb, signature != null ? signature.asString() : "null");
+        });
+  }
+
+  private static void appendKmFunction(String indent, StringBuilder sb, KmFunction function) {
+    appendKmSection(
+        indent,
+        "KmFunction",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "flags", sb, function.getFlags() + "");
+          appendKeyValue(newIndent, "name", sb, function.getName());
+          appendKeyValue(
+              newIndent,
+              "receiverParameterType",
+              sb,
+              nextIndent -> appendKmType(nextIndent, sb, function.getReceiverParameterType()));
+          appendKeyValue(
+              newIndent,
+              "returnType",
+              sb,
+              nextIndent -> appendKmType(nextIndent, sb, function.getReturnType()));
+          appendKeyValue(
+              newIndent,
+              "typeParameters",
+              sb,
+              nextIndent -> appendTypeParameters(nextIndent, sb, function.getTypeParameters()));
+          appendKeyValue(
+              newIndent,
+              "valueParameters",
+              sb,
+              nextIndent -> appendValueParameters(nextIndent, sb, function.getValueParameters()));
+          JvmMethodSignature signature = JvmExtensionsKt.getSignature(function);
+          appendKeyValue(
+              newIndent, "signature", sb, signature != null ? signature.asString() : "null");
+          appendKeyValue(
+              newIndent,
+              "lambdaClassOriginName",
+              sb,
+              JvmExtensionsKt.getLambdaClassOriginName(function));
+        });
+  }
+
+  private static void appendKmProperty(String indent, StringBuilder sb, KmProperty kmProperty) {
+    appendKmSection(
+        indent,
+        "KmProperty",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "flags", sb, kmProperty.getFlags() + "");
+          appendKeyValue(newIndent, "name", sb, kmProperty.getName());
+          appendKeyValue(
+              newIndent,
+              "receiverParameterType",
+              sb,
+              nextIndent -> appendKmType(nextIndent, sb, kmProperty.getReceiverParameterType()));
+          appendKeyValue(
+              newIndent,
+              "returnType",
+              sb,
+              nextIndent -> appendKmType(nextIndent, sb, kmProperty.getReturnType()));
+          appendKeyValue(
+              newIndent,
+              "typeParameters",
+              sb,
+              nextIndent -> appendTypeParameters(nextIndent, sb, kmProperty.getTypeParameters()));
+          appendKeyValue(newIndent, "getterFlags", sb, kmProperty.getGetterFlags() + "");
+          appendKeyValue(newIndent, "setterFlags", sb, kmProperty.getSetterFlags() + "");
+          appendKeyValue(
+              newIndent,
+              "setterParameter",
+              sb,
+              nextIndent -> appendValueParameter(nextIndent, sb, kmProperty.getSetterParameter()));
+          appendKeyValue(newIndent, "jvmFlags", sb, JvmExtensionsKt.getJvmFlags(kmProperty) + "");
+          JvmFieldSignature fieldSignature = JvmExtensionsKt.getFieldSignature(kmProperty);
+          appendKeyValue(
+              newIndent,
+              "fieldSignature",
+              sb,
+              fieldSignature != null ? fieldSignature.asString() : "null");
+          JvmMethodSignature getterSignature = JvmExtensionsKt.getGetterSignature(kmProperty);
+          appendKeyValue(
+              newIndent,
+              "getterSignature",
+              sb,
+              getterSignature != null ? getterSignature.asString() : "null");
+          JvmMethodSignature setterSignature = JvmExtensionsKt.getSetterSignature(kmProperty);
+          appendKeyValue(
+              newIndent,
+              "setterSignature",
+              sb,
+              setterSignature != null ? setterSignature.asString() : "null");
+          JvmMethodSignature syntheticMethod =
+              JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty);
+          appendKeyValue(
+              newIndent,
+              "syntheticMethodForAnnotations",
+              sb,
+              syntheticMethod != null ? syntheticMethod.asString() : "null");
+        });
+  }
+
+  private static void appendKmType(String indent, StringBuilder sb, KmType kmType) {
+    if (kmType == null) {
+      sb.append("null");
+      return;
     }
-    appendType(newIndent, "underlyingType", alias.underlyingType, sb);
-    appendType(newIndent, "expandedType", alias.expandedType, sb);
-    // TODO(b/151783973): Extend with annotations.
-    sb.append(indent);
-    sb.append("}");
-    return sb.toString();
+    appendKmSection(
+        indent,
+        "KmType",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "flags", sb, kmType.getFlags() + "");
+          appendKeyValue(newIndent, "classifier", sb, kmType.classifier.toString());
+          appendKeyValue(
+              newIndent,
+              "arguments",
+              sb,
+              nextIndent -> {
+                appendKmList(
+                    nextIndent,
+                    "KmTypeProjection",
+                    sb,
+                    kmType.getArguments(),
+                    (nextNextIndent, kmTypeProjection) -> {
+                      appendKmTypeProjection(nextNextIndent, sb, kmTypeProjection);
+                    });
+              });
+          appendKeyValue(
+              newIndent,
+              "abbreviatedType",
+              sb,
+              nextIndent -> appendKmType(newIndent, sb, kmType.getAbbreviatedType()));
+          appendKeyValue(
+              newIndent,
+              "outerType",
+              sb,
+              nextIndent -> appendKmType(newIndent, sb, kmType.getOuterType()));
+          appendKeyValue(
+              newIndent,
+              "extensions",
+              sb,
+              nextIndent -> {
+                appendKmList(
+                    nextIndent,
+                    "KmAnnotion",
+                    sb,
+                    JvmExtensionsKt.getAnnotations(kmType),
+                    (nextNextIndent, kmAnnotation) -> {
+                      appendKmAnnotation(nextNextIndent, sb, kmAnnotation);
+                    });
+              });
+        });
   }
 
-  void appendType(String indent, String key, KmType kmType, StringBuilder sb) {
-    sb.append(indent);
-    sb.append(key);
-    sb.append(" {");
-    sb.append(StringUtils.LINE_SEPARATOR);
-    String newIndent = indent + INDENT;
-    appendKeyValue(newIndent, "classifier", kmType.classifier.toString(), sb);
-    if (!kmType.getArguments().isEmpty()) {
-      appendKeyValue(
-          newIndent,
-          "arguments",
-          kmType.getArguments().stream()
-              .map(arg -> arg.getType().classifier.toString())
-              .collect(Collectors.joining(",")),
-          sb);
+  private static void appendKmTypeProjection(
+      String indent, StringBuilder sb, KmTypeProjection projection) {
+    appendKmSection(
+        indent,
+        "KmTypeProjection",
+        sb,
+        newIndent -> {
+          appendKeyValue(
+              newIndent,
+              "type",
+              sb,
+              nextIndent -> {
+                appendKmType(nextIndent, sb, projection.getType());
+              });
+          if (projection.getVariance() != null) {
+            appendKeyValue(newIndent, "variance", sb, projection.getVariance().name());
+          }
+        });
+  }
+
+  private static void appendValueParameters(
+      String indent, StringBuilder sb, List<KmValueParameter> valueParameters) {
+    appendKmList(
+        indent,
+        "KmValueParameter",
+        sb,
+        valueParameters,
+        (newIndent, parameter) -> {
+          appendValueParameter(newIndent, sb, parameter);
+        });
+  }
+
+  private static void appendValueParameter(
+      String indent, StringBuilder sb, KmValueParameter valueParameter) {
+    if (valueParameter == null) {
+      sb.append("null");
+      return;
     }
-    sb.append(indent);
-    sb.append("}");
-    sb.append(StringUtils.LINE_SEPARATOR);
+    appendKmSection(
+        indent,
+        "KmValueParameter",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "flags", sb, valueParameter.getFlags() + "");
+          appendKeyValue(newIndent, "name", sb, valueParameter.getName());
+          appendKeyValue(
+              newIndent,
+              "type",
+              sb,
+              nextIndent -> {
+                appendKmType(nextIndent, sb, valueParameter.getType());
+              });
+          appendKeyValue(
+              newIndent,
+              "varargElementType",
+              sb,
+              nextIndent -> {
+                appendKmType(nextIndent, sb, valueParameter.getVarargElementType());
+              });
+        });
   }
 
-  void appendKeyValue(String indent, String key, String value, StringBuilder sb) {
-    sb.append(indent);
-    sb.append(key);
-    sb.append(": ");
-    sb.append(value);
-    sb.append(StringUtils.LINE_SEPARATOR);
+  private static void appendTypeParameters(
+      String indent, StringBuilder sb, List<KmTypeParameter> typeParameters) {
+    appendKmList(
+        indent,
+        "KmTypeParameter",
+        sb,
+        typeParameters,
+        (newIndent, parameter) -> {
+          appendTypeParameter(newIndent, sb, parameter);
+        });
   }
 
-  private String kmTypeToString(KmType type) {
-    return DescriptorUtils.getDescriptorFromKmType(type);
+  private static void appendTypeParameter(
+      String indent, StringBuilder sb, KmTypeParameter typeParameter) {
+    appendKmSection(
+        indent,
+        "KmTypeParameter",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "id", sb, typeParameter.getId() + "");
+          appendKeyValue(newIndent, "flags", sb, typeParameter.getFlags() + "");
+          appendKeyValue(newIndent, "name", sb, typeParameter.getName());
+          appendKeyValue(newIndent, "variance", sb, typeParameter.getVariance().name());
+          appendKeyValue(
+              newIndent,
+              "upperBounds",
+              sb,
+              nextIndent -> {
+                appendKmList(
+                    nextIndent,
+                    "KmType",
+                    sb,
+                    typeParameter.getUpperBounds(),
+                    (nextNextIndent, kmType) -> {
+                      appendKmType(nextNextIndent, sb, kmType);
+                    });
+              });
+          appendKeyValue(
+              newIndent,
+              "extensions",
+              sb,
+              nextIndent -> {
+                appendKmList(
+                    nextIndent,
+                    "KmAnnotion",
+                    sb,
+                    JvmExtensionsKt.getAnnotations(typeParameter),
+                    (nextNextIndent, kmAnnotation) -> {
+                      appendKmAnnotation(nextNextIndent, sb, kmAnnotation);
+                    });
+              });
+        });
+  }
+
+  private static void appendTypeAlias(String indent, StringBuilder sb, KmTypeAlias kmTypeAlias) {
+    appendKmSection(
+        indent,
+        "KmTypeAlias",
+        sb,
+        newIndent -> {
+          appendKeyValue(
+              newIndent,
+              "annotations",
+              sb,
+              nextIndent -> {
+                appendKmList(
+                    nextIndent,
+                    "KmAnnotation",
+                    sb,
+                    kmTypeAlias.getAnnotations(),
+                    (nextNextIndent, kmAnnotation) -> {
+                      appendKmAnnotation(nextNextIndent, sb, kmAnnotation);
+                    });
+              });
+          appendKeyValue(
+              newIndent,
+              "expandedType",
+              sb,
+              nextIndent -> {
+                appendKmType(nextIndent, sb, kmTypeAlias.expandedType);
+              });
+          appendKeyValue(newIndent, "flags", sb, kmTypeAlias.getFlags() + "");
+          appendKeyValue(newIndent, "name", sb, kmTypeAlias.getName());
+          appendKeyValue(
+              newIndent,
+              "typeParameters",
+              sb,
+              nextIndent -> {
+                appendTypeParameters(nextIndent, sb, kmTypeAlias.getTypeParameters());
+              });
+          appendKeyValue(
+              newIndent,
+              "underlyingType",
+              sb,
+              nextIndent -> {
+                appendKmType(nextIndent, sb, kmTypeAlias.underlyingType);
+              });
+        });
+  }
+
+  private static void appendKmAnnotation(
+      String indent, StringBuilder sb, KmAnnotation kmAnnotation) {
+    appendKmSection(
+        indent,
+        "KmAnnotation",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "className", sb, kmAnnotation.getClassName());
+          appendKeyValue(
+              newIndent,
+              "arguments",
+              sb,
+              nextIndent -> {
+                Map<String, KmAnnotationArgument<?>> arguments = kmAnnotation.getArguments();
+                for (String key : arguments.keySet()) {
+                  appendKeyValue(nextIndent, key, sb, arguments.get(key).toString());
+                }
+              });
+        });
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
index dfdb28b..ed7b49f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
@@ -3,6 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.CONSTRUCTOR;
+import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.EXTENSION_FUNCTION;
+import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.EXTENSION_PROPERTY_GETTER;
+import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.EXTENSION_PROPERTY_SETTER;
+import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.FUNCTION;
+import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.PROPERTY_GETTER;
+import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.PROPERTY_SETTER;
+import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.getJvmMethodSignature;
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmFieldSignature;
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.isExtension;
@@ -10,7 +18,6 @@
 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.kotlin.KotlinMetadataJvmExtensionUtils.KmFunctionProcessor;
 import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmPropertyProcessor;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableList;
@@ -18,18 +25,20 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmDeclarationContainer;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
+import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmValueParameter;
+import kotlinx.metadata.jvm.JvmMethodSignature;
 
 // Provides access to field/method-level Kotlin information.
-public class KotlinMemberInfo {
-  private static final List<KmValueParameter> EMPTY_PARAM = ImmutableList.of();
+public abstract class KotlinMemberInfo {
+
   private static final List<KotlinValueParameterInfo> EMPTY_PARAM_INFO = ImmutableList.of();
 
-  private static final KotlinMemberInfo NO_KOTLIN_MEMBER_INFO =
-      new KotlinMemberInfo(MemberKind.NONE, 0, EMPTY_PARAM);
+  private static final KotlinMemberInfo NO_KOTLIN_MEMBER_INFO = new NoKotlinMemberInfo();
 
   public static KotlinMemberInfo getNoKotlinMemberInfo() {
     return NO_KOTLIN_MEMBER_INFO;
@@ -38,76 +47,198 @@
   public final MemberKind memberKind;
   // Original member flags. May be necessary to keep Kotlin-specific flag, e.g., suspend function.
   final int flags;
-  // TODO(b/151194869): better to split into FunctionInfo v.s. PropertyInfo ?
-  // Original property flags. E.g., for property getter, getter flags are stored to `flags`, while
-  // the property's flags are stored here, in case of properties without a backing field.
-  final int propertyFlags;
-  // Original property name for (extension) property. Otherwise, null.
-  final String propertyName;
-  // Information from original KmValueParameter(s) if available. Otherwise, null.
-  private final List<KotlinValueParameterInfo> valueParameterInfos;
 
-  // Constructor for KmFunction
-  private KotlinMemberInfo(
-      MemberKind memberKind, int flags, List<KmValueParameter> kmValueParameters) {
-    this(memberKind, flags, 0, null, kmValueParameters);
-  }
-
-  // Constructor for a backing field and a getter in KmProperty
-  private KotlinMemberInfo(
-      MemberKind memberKind, int flags, int propertyFlags, String propertyName) {
-    this(memberKind, flags, propertyFlags, propertyName, EMPTY_PARAM);
-  }
-
-  // Constructor for a setter in KmProperty
-  private KotlinMemberInfo(
-      MemberKind memberKind,
-      int flags,
-      int propertyFlags,
-      String propertyName,
-      KmValueParameter kmValueParameter) {
-    this(
-        memberKind,
-        flags,
-        propertyFlags,
-        propertyName,
-        kmValueParameter != null ? ImmutableList.of(kmValueParameter) : EMPTY_PARAM);
-  }
-
-  private KotlinMemberInfo(
-      MemberKind memberKind,
-      int flags,
-      int propertyFlags,
-      String propertyName,
-      List<KmValueParameter> kmValueParameters) {
+  private KotlinMemberInfo(MemberKind memberKind, int flags) {
     this.memberKind = memberKind;
     this.flags = flags;
-    this.propertyFlags = propertyFlags;
-    this.propertyName = propertyName;
-    assert kmValueParameters != null;
-    if (kmValueParameters.isEmpty()) {
-      this.valueParameterInfos = EMPTY_PARAM_INFO;
-    } else {
-      this.valueParameterInfos = new ArrayList<>(kmValueParameters.size());
-      for (KmValueParameter kmValueParameter : kmValueParameters) {
-        valueParameterInfos.add(KotlinValueParameterInfo.fromKmValueParameter(kmValueParameter));
-      }
+  }
+
+  public boolean isFunctionInfo() {
+    return false;
+  }
+
+  public KotlinFunctionInfo asFunctionInfo() {
+    return null;
+  }
+
+  public boolean isFieldInfo() {
+    return false;
+  }
+
+  public KotlinFieldInfo asFieldInfo() {
+    return null;
+  }
+
+  public boolean isPropertyInfo() {
+    return false;
+  }
+
+  public KotlinPropertyInfo asPropertyInfo() {
+    return null;
+  }
+
+  private static class NoKotlinMemberInfo extends KotlinMemberInfo {
+
+    private NoKotlinMemberInfo() {
+      super(MemberKind.NONE, 0);
     }
   }
 
-  KotlinValueParameterInfo getValueParameterInfo(int i) {
-    if (valueParameterInfos.isEmpty()) {
-      return null;
+  public static class KotlinFunctionInfo extends KotlinMemberInfo {
+
+    // Information from original KmValueParameter(s) if available.
+    final List<KotlinValueParameterInfo> valueParameterInfos;
+    // Information from original KmFunction.returnType. Null if this is from a KmConstructor.
+    public final KotlinTypeInfo returnType;
+
+    private KotlinFunctionInfo(
+        MemberKind memberKind,
+        int flags,
+        KotlinTypeInfo returnType,
+        List<KotlinValueParameterInfo> valueParameterInfos) {
+      super(memberKind, flags);
+      assert memberKind.isFunction() || memberKind.isConstructor();
+      this.returnType = returnType;
+      this.valueParameterInfos = valueParameterInfos;
     }
-    if (i < 0 || i >= valueParameterInfos.size()) {
-      return null;
+
+    KotlinValueParameterInfo getValueParameterInfo(int i) {
+      if (valueParameterInfos.isEmpty()) {
+        return null;
+      }
+      if (i < 0 || i >= valueParameterInfos.size()) {
+        return null;
+      }
+      return valueParameterInfos.get(i);
     }
-    return valueParameterInfos.get(i);
+
+    @Override
+    public boolean isFunctionInfo() {
+      return true;
+    }
+
+    @Override
+    public KotlinFunctionInfo asFunctionInfo() {
+      return this;
+    }
+  }
+
+  public static class KotlinFieldInfo extends KotlinMemberInfo {
+
+    // Original property name for (extension) property. Otherwise, null.
+    final String propertyName;
+
+    private KotlinFieldInfo(MemberKind memberKind, int flags, String propertyName) {
+      super(memberKind, flags);
+      this.propertyName = propertyName;
+    }
+
+    @Override
+    public boolean isFieldInfo() {
+      return true;
+    }
+
+    @Override
+    public KotlinFieldInfo asFieldInfo() {
+      return this;
+    }
+  }
+
+  public static class KotlinPropertyInfo extends KotlinMemberInfo {
+
+    // Original getter flags. E.g., for property getter.
+    final int getterFlags;
+
+    // Original setter flags. E.g., for property setter.
+    final int setterFlags;
+
+    // Original property name for (extension) property. Otherwise, null.
+    final String propertyName;
+
+    // Original return type information. This should never be NULL (even for setters without field).
+    final KotlinTypeInfo returnType;
+
+    // Information from original KmValueParameter if available.
+    final KotlinValueParameterInfo valueParameterInfo;
+
+    private KotlinPropertyInfo(
+        MemberKind memberKind,
+        int flags,
+        int getterFlags,
+        int setterFlags,
+        String propertyName,
+        KotlinTypeInfo returnType,
+        KotlinValueParameterInfo valueParameterInfo) {
+      super(memberKind, flags);
+      this.getterFlags = getterFlags;
+      this.setterFlags = setterFlags;
+      this.propertyName = propertyName;
+      this.returnType = returnType;
+      this.valueParameterInfo = valueParameterInfo;
+    }
+
+    @Override
+    public KotlinPropertyInfo asPropertyInfo() {
+      return this;
+    }
+
+    @Override
+    public boolean isPropertyInfo() {
+      return true;
+    }
+  }
+
+  private static KotlinFunctionInfo createFunctionInfoFromConstructor(KmConstructor kmConstructor) {
+    return createFunctionInfo(
+        CONSTRUCTOR, kmConstructor.getFlags(), null, kmConstructor.getValueParameters());
+  }
+
+  private static KotlinFunctionInfo createFunctionInfo(
+      MemberKind memberKind, KmFunction kmFunction) {
+    return createFunctionInfo(
+        memberKind,
+        kmFunction.getFlags(),
+        kmFunction.getReturnType(),
+        kmFunction.getValueParameters());
+  }
+
+  private static KotlinFunctionInfo createFunctionInfo(
+      MemberKind memberKind, int flags, KmType returnType, List<KmValueParameter> valueParameters) {
+    assert memberKind.isFunction() || memberKind.isConstructor();
+    KotlinTypeInfo returnTypeInfo = KotlinTypeInfo.create(returnType);
+    assert memberKind.isFunction() || memberKind.isConstructor();
+    if (valueParameters.isEmpty()) {
+      return new KotlinFunctionInfo(memberKind, flags, returnTypeInfo, EMPTY_PARAM_INFO);
+    }
+    List<KotlinValueParameterInfo> valueParameterInfos = new ArrayList<>(valueParameters.size());
+    for (KmValueParameter kmValueParameter : valueParameters) {
+      valueParameterInfos.add(KotlinValueParameterInfo.fromKmValueParameter(kmValueParameter));
+    }
+    return new KotlinFunctionInfo(memberKind, flags, returnTypeInfo, valueParameterInfos);
+  }
+
+  private static KotlinFieldInfo createFieldInfo(MemberKind memberKind, KmProperty kmProperty) {
+    assert memberKind.isBackingField() || memberKind.isBackingFieldForCompanionObject();
+    return new KotlinFieldInfo(memberKind, kmProperty.getFlags(), kmProperty.getName());
+  }
+
+  private static KotlinPropertyInfo createPropertyInfo(
+      MemberKind memberKind, KmProperty kmProperty) {
+    assert memberKind.isProperty();
+    return new KotlinPropertyInfo(
+        memberKind,
+        kmProperty.getFlags(),
+        kmProperty.getGetterFlags(),
+        kmProperty.getSetterFlags(),
+        kmProperty.getName(),
+        KotlinTypeInfo.create(kmProperty.getReturnType()),
+        KotlinValueParameterInfo.fromKmValueParameter(kmProperty.getSetterParameter()));
   }
 
   public enum MemberKind {
     NONE,
 
+    CONSTRUCTOR,
     FUNCTION,
     EXTENSION_FUNCTION,
 
@@ -122,6 +253,10 @@
     EXTENSION_PROPERTY_SETTER,
     EXTENSION_PROPERTY_ANNOTATIONS;
 
+    public boolean isConstructor() {
+      return this == CONSTRUCTOR;
+    }
+
     public boolean isFunction() {
       return this == FUNCTION || isExtensionFunction();
     }
@@ -159,23 +294,37 @@
       return;
     }
 
-    KmDeclarationContainer kmDeclarationContainer = kotlinInfo.getDeclarations();
-    String companionObject = null;
-    if (kotlinInfo.isClass()) {
-      companionObject = kotlinInfo.asClass().kmClass.getCompanionObject();
-    }
-
+    Map<String, KmConstructor> kmConstructorMap = new HashMap<>();
     Map<String, KmFunction> kmFunctionMap = new HashMap<>();
     Map<String, KmProperty> kmPropertyFieldMap = new HashMap<>();
     Map<String, KmProperty> kmPropertyGetterMap = new HashMap<>();
     Map<String, KmProperty> kmPropertySetterMap = new HashMap<>();
 
-    kmDeclarationContainer.getFunctions().forEach(kmFunction -> {
-      KmFunctionProcessor functionProcessor = new KmFunctionProcessor(kmFunction, reporter);
-      if (functionProcessor.signature() != null) {
-        kmFunctionMap.put(functionProcessor.signature().asString(), kmFunction);
-      }
-    });
+    KmDeclarationContainer kmDeclarationContainer = kotlinInfo.getDeclarations();
+    String companionObject = null;
+    if (kotlinInfo.isClass()) {
+      companionObject = kotlinInfo.asClass().kmClass.getCompanionObject();
+      kotlinInfo
+          .asClass()
+          .kmClass
+          .getConstructors()
+          .forEach(
+              kmConstructor -> {
+                JvmMethodSignature methodSignature = getJvmMethodSignature(kmConstructor, reporter);
+                if (methodSignature != null) {
+                  kmConstructorMap.put(methodSignature.asString(), kmConstructor);
+                }
+              });
+    }
+    kmDeclarationContainer
+        .getFunctions()
+        .forEach(
+            kmFunction -> {
+              JvmMethodSignature methodSignature = getJvmMethodSignature(kmFunction, reporter);
+              if (methodSignature != null) {
+                kmFunctionMap.put(methodSignature.asString(), kmFunction);
+              }
+            });
     kmDeclarationContainer.getProperties().forEach(kmProperty -> {
       KmPropertyProcessor propertyProcessor = new KmPropertyProcessor(kmProperty, reporter);
       if (propertyProcessor.fieldSignature() != null) {
@@ -200,72 +349,35 @@
       if (kmPropertyFieldMap.containsKey(key)) {
         KmProperty kmProperty = kmPropertyFieldMap.get(key);
         field.setKotlinMemberInfo(
-            new KotlinMemberInfo(
+            createFieldInfo(
                 clazz == kotlinInfo.clazz
                     ? MemberKind.PROPERTY_BACKING_FIELD
                     : MemberKind.COMPANION_OBJECT_BACKING_FIELD,
-                kmProperty.getFlags(),
-                kmProperty.getFlags(),
-                kmProperty.getName()));
+                kmProperty));
       }
     }
 
     for (DexEncodedMethod method : clazz.methods()) {
-      if (method.isInitializer()) {
-        continue;
-      }
       String key = toJvmMethodSignature(method.method).asString();
-      if (kmFunctionMap.containsKey(key)) {
+      if (kmConstructorMap.containsKey(key)) {
+        // Interestingly we cannot assert that the method is a jvm initializer, because the jvm
+        // signature can be a different.
+        method.setKotlinMemberInfo(createFunctionInfoFromConstructor(kmConstructorMap.get(key)));
+      } else if (kmFunctionMap.containsKey(key)) {
         KmFunction kmFunction = kmFunctionMap.get(key);
-        if (isExtension(kmFunction)) {
-          method.setKotlinMemberInfo(
-              new KotlinMemberInfo(
-                  MemberKind.EXTENSION_FUNCTION,
-                  kmFunction.getFlags(),
-                  kmFunction.getValueParameters()));
-        } else {
-          method.setKotlinMemberInfo(
-              new KotlinMemberInfo(
-                  MemberKind.FUNCTION,
-                  kmFunction.getFlags(),
-                  kmFunction.getValueParameters()));
-        }
+        method.setKotlinMemberInfo(
+            createFunctionInfo(
+                isExtension(kmFunction) ? EXTENSION_FUNCTION : FUNCTION, kmFunction));
       } else if (kmPropertyGetterMap.containsKey(key)) {
         KmProperty kmProperty = kmPropertyGetterMap.get(key);
-        if (isExtension(kmProperty)) {
-          method.setKotlinMemberInfo(
-              new KotlinMemberInfo(
-                  MemberKind.EXTENSION_PROPERTY_GETTER,
-                  kmProperty.getGetterFlags(),
-                  kmProperty.getFlags(),
-                  kmProperty.getName()));
-        } else {
-          method.setKotlinMemberInfo(
-              new KotlinMemberInfo(
-                  MemberKind.PROPERTY_GETTER,
-                  kmProperty.getGetterFlags(),
-                  kmProperty.getFlags(),
-                  kmProperty.getName()));
-        }
+        method.setKotlinMemberInfo(
+            createPropertyInfo(
+                isExtension(kmProperty) ? EXTENSION_PROPERTY_GETTER : PROPERTY_GETTER, kmProperty));
       } else if (kmPropertySetterMap.containsKey(key)) {
         KmProperty kmProperty = kmPropertySetterMap.get(key);
-        if (isExtension(kmProperty)) {
-          method.setKotlinMemberInfo(
-              new KotlinMemberInfo(
-                  MemberKind.EXTENSION_PROPERTY_SETTER,
-                  kmProperty.getSetterFlags(),
-                  kmProperty.getFlags(),
-                  kmProperty.getName(),
-                  kmProperty.getSetterParameter()));
-        } else {
-          method.setKotlinMemberInfo(
-              new KotlinMemberInfo(
-                  MemberKind.PROPERTY_SETTER,
-                  kmProperty.getSetterFlags(),
-                  kmProperty.getFlags(),
-                  kmProperty.getName(),
-                  kmProperty.getSetterParameter()));
-        }
+        method.setKotlinMemberInfo(
+            createPropertyInfo(
+                isExtension(kmProperty) ? EXTENSION_PROPERTY_SETTER : PROPERTY_SETTER, kmProperty));
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
index 474c123..855a71f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
@@ -24,14 +24,12 @@
 import kotlinx.metadata.KmConstructorVisitor;
 import kotlinx.metadata.KmExtensionType;
 import kotlinx.metadata.KmFunction;
-import kotlinx.metadata.KmFunctionExtensionVisitor;
-import kotlinx.metadata.KmFunctionVisitor;
 import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.KmPropertyExtensionVisitor;
 import kotlinx.metadata.KmPropertyVisitor;
 import kotlinx.metadata.jvm.JvmConstructorExtensionVisitor;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
 import kotlinx.metadata.jvm.JvmFieldSignature;
-import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor;
 import kotlinx.metadata.jvm.JvmMethodSignature;
 import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor;
 
@@ -174,37 +172,25 @@
     }
   }
 
-  static class KmFunctionProcessor {
-    // Custom name via @JvmName("..."). Otherwise, null.
-    private JvmMethodSignature signature = null;
+  // Custom name via @JvmName("..."). Otherwise, null.
+  static JvmMethodSignature getJvmMethodSignature(KmConstructor kmConstructor, Reporter reporter) {
+    return remapJvmMethodSignature(JvmExtensionsKt.getSignature(kmConstructor), reporter);
+  }
 
-    KmFunctionProcessor(KmFunction kmFunction, Reporter reporter) {
-      kmFunction.accept(new KmFunctionVisitor() {
-        @Override
-        public KmFunctionExtensionVisitor visitExtensions(KmExtensionType type) {
-          if (type != JvmFunctionExtensionVisitor.TYPE) {
-            return null;
-          }
-          return new JvmFunctionExtensionVisitor() {
-            @Override
-            public void visit(JvmMethodSignature desc) {
-              assert signature == null : signature.asString();
-              signature = desc;
-            }
-          };
-        }
-      });
-      if (signature != null) {
-        String remappedDesc = remapKotlinTypeInDesc(signature.getDesc(), reporter);
-        if (remappedDesc != null && !remappedDesc.equals(signature.getDesc())) {
-          signature = new JvmMethodSignature(signature.getName(), remappedDesc);
-        }
+  // Custom name via @JvmName("..."). Otherwise, null.
+  static JvmMethodSignature getJvmMethodSignature(KmFunction kmFunction, Reporter reporter) {
+    return remapJvmMethodSignature(JvmExtensionsKt.getSignature(kmFunction), reporter);
+  }
+
+  private static JvmMethodSignature remapJvmMethodSignature(
+      JvmMethodSignature signature, Reporter reporter) {
+    if (signature != null) {
+      String remappedDesc = remapKotlinTypeInDesc(signature.getDesc(), reporter);
+      if (remappedDesc != null && !remappedDesc.equals(signature.getDesc())) {
+        signature = new JvmMethodSignature(signature.getName(), remappedDesc);
       }
     }
-
-    JvmMethodSignature signature() {
-      return signature;
-    }
+    return signature;
   }
 
   static class KmPropertyProcessor {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index 5d1777f..f83796f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -61,6 +61,8 @@
   }
 
   public void run(ExecutorService executorService) throws ExecutionException {
+    // TODO(b/152283077): Don't disable the assert.
+    appView.appInfo().disableDefinitionForAssert();
     ThreadUtils.processItems(
         appView.appInfo().classes(),
         clazz -> {
@@ -92,6 +94,7 @@
           }
         },
         executorService);
+    appView.appInfo().enableDefinitionForAssert();
   }
 
   private DexAnnotation createKotlinMetadataAnnotation(KotlinClassHeader header) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
index 43b5cf2..2e90094 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -3,9 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.Kotlin.NAME;
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmFieldSignature;
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.populateKmTypeFromSignature;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.toClassifier;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier;
 import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor;
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType;
@@ -24,16 +25,21 @@
 import com.android.tools.r8.graph.GenericSignature;
 import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
+import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinFunctionInfo;
+import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinPropertyInfo;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Box;
 import java.util.List;
 import java.util.function.Consumer;
 import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeParameter;
 import kotlinx.metadata.KmTypeProjection;
 import kotlinx.metadata.KmValueParameter;
 import kotlinx.metadata.KmVariance;
@@ -41,6 +47,17 @@
 
 class KotlinMetadataSynthesizer {
 
+  private final AppView<AppInfoWithLiveness> appView;
+  private final NamingLens lens;
+  private final List<KmTypeParameter> classTypeParameters;
+
+  public KotlinMetadataSynthesizer(
+      AppView<AppInfoWithLiveness> appView, NamingLens lens, KotlinInfo<?> kotlinInfo) {
+    this.appView = appView;
+    this.lens = lens;
+    this.classTypeParameters = kotlinInfo.getTypeParameters();
+  }
+
   static boolean isExtension(KmFunction kmFunction) {
     return kmFunction.getReceiverParameterType() != null;
   }
@@ -55,8 +72,7 @@
     return kmType;
   }
 
-  static DexType toRenamedType(
-      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+  private DexType toRenamedType(DexType type) {
     // For library or classpath class, synthesize @Metadata always.
     // For a program class, make sure it is live.
     if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) {
@@ -70,141 +86,92 @@
     return renamedType;
   }
 
-  static String toRenamedBinaryName(
-      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    DexType renamedType = toRenamedType(type, appView, lens);
+  String toRenamedBinaryName(DexType type) {
+    DexType renamedType = toRenamedType(type);
     if (renamedType == null) {
       return null;
     }
     return getBinaryNameFromDescriptor(renamedType.toDescriptorString());
   }
 
-  static String toRenamedClassifier(
-      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    // E.g., V -> kotlin/Unit, J -> kotlin/Long, [J -> kotlin/LongArray
-    if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) {
-      DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type);
-      assert convertedType != null;
-      return descriptorToKotlinClassifier(convertedType.toDescriptorString());
-    }
-    // E.g., [Ljava/lang/String; -> kotlin/Array
-    if (type.isArrayType()) {
-      return NAME + "/Array";
-    }
-    DexType renamedType = toRenamedType(type, appView, lens);
-    if (renamedType == null) {
+  String toRenamedClassifier(DexType type) {
+    type = type.isClassType() ? toRenamedType(type) : type;
+    if (type == null) {
       return null;
     }
-    return descriptorToKotlinClassifier(renamedType.toDescriptorString());
+    return toClassifier(type, appView.dexItemFactory());
   }
 
   // TODO(b/148654451): Canonicalization?
-  static KmType toRenamedKmType(
+  KmType toRenamedKmType(
       DexType type,
       TypeSignature typeSignature,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
-    if (typeSignature != null
-        && typeSignature.isFieldTypeSignature()
-        && typeSignature.asFieldTypeSignature().isClassTypeSignature()
-        && typeSignature.asFieldTypeSignature().asClassTypeSignature().type() == type) {
-      return toRenamedKmType(
-          typeSignature.asFieldTypeSignature().asClassTypeSignature(), appView, lens);
+      KotlinTypeInfo originalKotlinTypeInfo,
+      ClassTypeSignatureToRenamedKmTypeConverter converter) {
+    if (originalKotlinTypeInfo != null && originalKotlinTypeInfo.isTypeAlias()) {
+      KmType kmType = new KmType(flagsOf());
+      kmType.visitTypeAlias(originalKotlinTypeInfo.asTypeAlias().getName());
+      return kmType;
     }
+    return toRenamedKmTypeWithClassifier(type, typeSignature, converter);
+  }
 
-    String classifier = toRenamedClassifier(type, appView, lens);
+  private KmType toRenamedKmTypeWithClassifierForFieldSignature(
+      DexType type,
+      FieldTypeSignature fieldSignature,
+      ClassTypeSignatureToRenamedKmTypeConverter converter) {
+    if (fieldSignature.isClassTypeSignature()) {
+      ClassTypeSignature classTypeSignature = fieldSignature.asClassTypeSignature();
+      if (classTypeSignature.type() != type) {
+        return null;
+      }
+      return classTypeSignature.convert(converter);
+    }
+    // If we fail to set kmType.classifier we will get a UninitializedPropertyAccessException:
+    // lateinit property classifier has not been initialized.
+    Box<KmType> kmTypeBox = new Box<>();
+    populateKmTypeFromSignature(
+        fieldSignature,
+        () -> {
+          KmType value = new KmType(flagsOf());
+          kmTypeBox.set(value);
+          return value;
+        },
+        converter.getTypeParameters(),
+        appView.dexItemFactory());
+    return kmTypeBox.get();
+  }
+
+  private KmType toRenamedKmTypeWithClassifier(
+      DexType type,
+      TypeSignature typeSignature,
+      ClassTypeSignatureToRenamedKmTypeConverter converter) {
+    if (typeSignature != null && typeSignature.isFieldTypeSignature()) {
+      KmType renamedKmType =
+          toRenamedKmTypeWithClassifierForFieldSignature(
+              type, typeSignature.asFieldTypeSignature(), converter);
+      if (renamedKmType != null) {
+        return renamedKmType;
+      }
+    }
+    String classifier = toRenamedClassifier(type);
     if (classifier == null) {
       return null;
     }
-    // TODO(b/151194869): Mysterious, why attempts to properly set flags bothers kotlinc?
-    //   and/or why wiping out flags works for KmType but not KmFunction?!
-    KmType kmType = new KmType(flagsOf());
-    kmType.visitClass(classifier);
+    // Seems like no flags for KmType are ever set, thus passing in flagsOf() seems ok.
+    KmType renamedKmType = new KmType(flagsOf());
+    renamedKmType.visitClass(classifier);
     // TODO(b/151194164): Can be generalized too, like ArrayTypeSignature.Converter ?
     // E.g., java.lang.String[] -> KmType(kotlin/Array, KmTypeProjection(OUT, kotlin/String))
     if (type.isArrayType() && !type.isPrimitiveArrayType()) {
       DexType elementType = type.toArrayElementType(appView.dexItemFactory());
-      KmType argumentType = toRenamedKmType(elementType, null, appView, lens);
-      kmType.getArguments().add(new KmTypeProjection(KmVariance.OUT, argumentType));
-    }
-    return kmType;
-  }
-
-  static KmType toRenamedKmType(
-      ClassTypeSignature classTypeSignature,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
-    return classTypeSignature.convert(
-        new ClassTypeSignatureToRenamedKmTypeConverter(appView, lens));
-  }
-
-  static class ClassTypeSignatureToRenamedKmTypeConverter
-      implements ClassTypeSignature.Converter<KmType> {
-
-    private final AppView<AppInfoWithLiveness> appView;
-    private final NamingLens lens;
-
-    ClassTypeSignatureToRenamedKmTypeConverter(
-        AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-      this.appView = appView;
-      this.lens = lens;
-    }
-
-    @Override
-    public KmType init() {
-      return new KmType(flagsOf());
-    }
-
-    @Override
-    public KmType visitType(DexType type, KmType result) {
-      if (result == null) {
-        return result;
-      }
-      String classifier = toRenamedClassifier(type, appView, lens);
-      if (classifier == null) {
-        return null;
-      }
-      result.visitClass(classifier);
-      return result;
-    }
-
-    @Override
-    public KmType visitTypeArgument(FieldTypeSignature typeArgument, KmType result) {
-      if (result == null) {
-        return result;
-      }
-      if (typeArgument.isClassTypeSignature()) {
-        KmType argumentType = typeArgument.asClassTypeSignature().convert(this);
-        result.getArguments().add(new KmTypeProjection(KmVariance.INVARIANT, argumentType));
-      }
-      // TODO(b/151194164): for TypeVariableSignature, there is KmType::visitTypeParameter.
-      return result;
-    }
-
-    @Override
-    public KmType visitInnerTypeSignature(ClassTypeSignature innerTypeSignature, KmType result) {
-      // Do nothing
-      return result;
-    }
-  }
-
-  private static KmType setRenamedKmType(
-      DexType type,
-      TypeSignature signature,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens,
-      Consumer<KmType> consumer) {
-    KmType renamedKmType = toRenamedKmType(type, signature, appView, lens);
-    if (renamedKmType != null) {
-      consumer.accept(renamedKmType);
+      KmType argumentType = toRenamedKmTypeWithClassifier(elementType, null, converter);
+      renamedKmType.getArguments().add(new KmTypeProjection(KmVariance.OUT, argumentType));
     }
     return renamedKmType;
   }
 
-  static KmConstructor toRenamedKmConstructor(
-      DexEncodedMethod method,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
+  KmConstructor toRenamedKmConstructor(DexEncodedMethod method) {
     // Make sure it is an instance initializer and live.
     if (!method.isInstanceInitializer()
         || !appView.appInfo().liveMethods.contains(method.method)) {
@@ -213,17 +180,23 @@
     KmConstructor kmConstructor = new KmConstructor(method.accessFlags.getAsKotlinFlags());
     JvmExtensionsKt.setSignature(kmConstructor, toJvmMethodSignature(method.method));
     MethodTypeSignature signature = GenericSignature.Parser.toMethodTypeSignature(method, appView);
+    List<KmTypeParameter> typeParameters =
+        convertFormalTypeParameters(
+            signature.getFormalTypeParameters(),
+            kmTypeParameter -> {
+              assert false : "KmConstructor cannot have additional type parameters";
+            });
+    ClassTypeSignatureToRenamedKmTypeConverter converter =
+        new ClassTypeSignatureToRenamedKmTypeConverter(
+            appView, typeParameters, this::toRenamedClassifier);
     List<KmValueParameter> parameters = kmConstructor.getValueParameters();
-    if (!populateKmValueParameters(parameters, method, signature, appView, lens)) {
+    if (!populateKmValueParameters(method, signature, parameters, converter)) {
       return null;
     }
     return kmConstructor;
   }
 
-  static KmFunction toRenamedKmFunction(
-      DexEncodedMethod method,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
+  KmFunction toRenamedKmFunction(DexEncodedMethod method) {
     // For library overrides, synthesize @Metadata always.
     // For regular methods, make sure it is live or pinned.
     if (!method.isLibraryMethodOverride().isTrue()
@@ -237,9 +210,11 @@
         : method.toSourceString() + " -> " + renamedMethod.toSourceString();
     // TODO(b/151194869): Should we keep kotlin-specific flags only while synthesizing the base
     //  value from general JVM flags?
+    KotlinFunctionInfo kotlinMemberInfo = method.getKotlinMemberInfo().asFunctionInfo();
+    assert kotlinMemberInfo != null;
     int flag =
-        appView.appInfo().isPinned(method.method) && method.getKotlinMemberInfo() != null
-            ? method.getKotlinMemberInfo().flags
+        appView.appInfo().isPinned(method.method)
+            ? kotlinMemberInfo.flags
             : method.accessFlags.getAsKotlinFlags();
     KmFunction kmFunction = new KmFunction(flag, renamedMethod.name.toString());
     JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(renamedMethod));
@@ -248,57 +223,65 @@
     //  on demand? That may require utils to map internal encoding of signature back to
     //  corresponding backend definitions, like DexAnnotation (DEX) or Signature attribute (CF).
     MethodTypeSignature signature = GenericSignature.Parser.toMethodTypeSignature(method, appView);
+    List<KmTypeParameter> typeParameters = kmFunction.getTypeParameters();
+    ClassTypeSignatureToRenamedKmTypeConverter converter =
+        new ClassTypeSignatureToRenamedKmTypeConverter(
+            appView,
+            convertFormalTypeParameters(signature.getFormalTypeParameters(), typeParameters::add),
+            this::toRenamedClassifier);
 
     DexProto proto = method.method.proto;
     DexType returnType = proto.returnType;
     TypeSignature returnSignature = signature.returnType().typeSignature();
-    KmType kmReturnType = setRenamedKmType(
-        returnType, returnSignature, appView, lens, kmFunction::setReturnType);
+    KmType kmReturnType =
+        toRenamedKmType(returnType, returnSignature, kotlinMemberInfo.returnType, converter);
     if (kmReturnType == null) {
       return null;
     }
-
+    kmFunction.setReturnType(kmReturnType);
     if (method.isKotlinExtensionFunction()) {
-      assert proto.parameters.values.length > 0
-          : method.method.toSourceString();
+      assert proto.parameters.values.length > 0 : method.method.toSourceString();
       DexType receiverType = proto.parameters.values[0];
       TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
-      KmType kmReceiverType = setRenamedKmType(
-          receiverType, receiverSignature, appView, lens, kmFunction::setReceiverParameterType);
+      KmType kmReceiverType = toRenamedKmType(receiverType, receiverSignature, null, converter);
       if (kmReceiverType == null) {
         return null;
       }
+      kmFunction.setReceiverParameterType(kmReceiverType);
     }
 
-    List<KmValueParameter> parameters = kmFunction.getValueParameters();
-    if (!populateKmValueParameters(parameters, method, signature, appView, lens)) {
+    if (!populateKmValueParameters(method, signature, kmFunction.getValueParameters(), converter)) {
       return null;
     }
     return kmFunction;
   }
 
-  private static boolean populateKmValueParameters(
-      List<KmValueParameter> parameters,
+  private List<KmTypeParameter> convertFormalTypeParameters(
+      List<FormalTypeParameter> parameters, Consumer<KmTypeParameter> addedFromParameters) {
+    return KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
+        classTypeParameters, parameters, appView.dexItemFactory(), addedFromParameters);
+  }
+
+  private boolean populateKmValueParameters(
       DexEncodedMethod method,
       MethodTypeSignature signature,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
+      List<KmValueParameter> parameters,
+      ClassTypeSignatureToRenamedKmTypeConverter converter) {
+    KotlinFunctionInfo kotlinFunctionInfo = method.getKotlinMemberInfo().asFunctionInfo();
+    if (kotlinFunctionInfo == null) {
+      return false;
+    }
     boolean isExtension = method.isKotlinExtensionFunction();
     for (int i = isExtension ? 1 : 0; i < method.method.proto.parameters.values.length; i++) {
       DexType parameterType = method.method.proto.parameters.values[i];
       DebugLocalInfo debugLocalInfo = method.getParameterInfo().get(i);
       String parameterName = debugLocalInfo != null ? debugLocalInfo.name.toString() : ("p" + i);
       KotlinValueParameterInfo valueParameterInfo =
-          method.getKotlinMemberInfo().getValueParameterInfo(isExtension ? i - 1 : i);
+          kotlinFunctionInfo.getValueParameterInfo(isExtension ? i - 1 : i);
       TypeSignature parameterTypeSignature = signature.getParameterTypeSignature(i);
       KmValueParameter kmValueParameter =
           toRewrittenKmValueParameter(
-              valueParameterInfo,
-              parameterType,
-              parameterTypeSignature,
-              parameterName,
-              appView,
-              lens);
+              valueParameterInfo, parameterType, parameterTypeSignature, parameterName, converter);
       if (kmValueParameter == null) {
         return false;
       }
@@ -307,26 +290,28 @@
     return true;
   }
 
-  private static KmValueParameter toRewrittenKmValueParameter(
+  private KmValueParameter toRewrittenKmValueParameter(
       KotlinValueParameterInfo valueParameterInfo,
       DexType parameterType,
       TypeSignature parameterTypeSignature,
       String candidateParameterName,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
+      ClassTypeSignatureToRenamedKmTypeConverter converter) {
     int flag = valueParameterInfo != null ? valueParameterInfo.flag : flagsOf();
     String name = valueParameterInfo != null ? valueParameterInfo.name : candidateParameterName;
     KmValueParameter kmValueParameter = new KmValueParameter(flag, name);
-    KmType kmParamType = setRenamedKmType(
-        parameterType, parameterTypeSignature, appView, lens, kmValueParameter::setType);
+    KotlinTypeInfo originalKmTypeInfo = valueParameterInfo != null ? valueParameterInfo.type : null;
+    KmType kmParamType =
+        toRenamedKmType(parameterType, parameterTypeSignature, originalKmTypeInfo, converter);
     if (kmParamType == null) {
       return null;
     }
+    kmValueParameter.setType(kmParamType);
     if (valueParameterInfo != null) {
       JvmExtensionsKt.getAnnotations(kmParamType).addAll(valueParameterInfo.annotations);
     }
 
     if (valueParameterInfo != null && valueParameterInfo.isVararg) {
+      // TODO(b/152389234): Test for arrays in varargs.
       if (!parameterType.isArrayType()) {
         return null;
       }
@@ -334,11 +319,11 @@
       TypeSignature elementSignature =
           parameterTypeSignature != null
               ? parameterTypeSignature.toArrayElementTypeSignature(appView) : null;
-      KmType kmElementType = setRenamedKmType(
-          elementType, elementSignature, appView, lens, kmValueParameter::setVarargElementType);
+      KmType kmElementType = toRenamedKmType(elementType, elementSignature, null, converter);
       if (kmElementType == null) {
         return null;
       }
+      kmValueParameter.setVarargElementType(kmElementType);
     }
 
     return kmValueParameter;
@@ -370,58 +355,65 @@
    * getter, and so on.
    */
   static class KmPropertyGroup {
+
     final int flags;
     final String name;
     final DexEncodedField field;
-    final DexEncodedMethod getter;
-    final int getterFlags;
-    final DexEncodedMethod setter;
-    final int setterFlags;
+    private final DexEncodedMethod getter;
+    private final KotlinPropertyInfo getterInfo;
+    private final DexEncodedMethod setter;
+    private final KotlinPropertyInfo setterInfo;
     final DexEncodedMethod annotations;
     final boolean isExtension;
+    private final List<KmTypeParameter> classTypeParameters;
 
     private KmPropertyGroup(
         int flags,
         String name,
         DexEncodedField field,
         DexEncodedMethod getter,
-        int getterFlags,
+        KotlinPropertyInfo getterInfo,
         DexEncodedMethod setter,
-        int setterFlags,
+        KotlinPropertyInfo setterInfo,
         DexEncodedMethod annotations,
-        boolean isExtension) {
+        boolean isExtension,
+        List<KmTypeParameter> classTypeParameters) {
       this.flags = flags;
       this.name = name;
       this.field = field;
       this.getter = getter;
-      this.getterFlags = getterFlags;
+      this.getterInfo = getterInfo;
       this.setter = setter;
-      this.setterFlags = setterFlags;
+      this.setterInfo = setterInfo;
       this.annotations = annotations;
       this.isExtension = isExtension;
+      this.classTypeParameters = classTypeParameters;
     }
 
-    static Builder builder(int flags, String name) {
-      return new Builder(flags, name);
+    static Builder builder(int flags, String name, List<KmTypeParameter> classTypeParameters) {
+      return new Builder(flags, name, classTypeParameters);
     }
 
     static class Builder {
+
       private final int flags;
       private final String name;
       private DexEncodedField field;
       private DexEncodedMethod getter;
-      private int getterFlags;
+      private KotlinPropertyInfo getterInfo;
       private DexEncodedMethod setter;
-      private int setterFlags;
+      private KotlinPropertyInfo setterInfo;
       private DexEncodedMethod annotations;
+      private List<KmTypeParameter> classTypeParameters;
 
       private boolean isExtensionGetter;
       private boolean isExtensionSetter;
       private boolean isExtensionAnnotations;
 
-      private Builder(int flags, String name) {
+      private Builder(int flags, String name, List<KmTypeParameter> classTypeParameters) {
         this.flags = flags;
         this.name = name;
+        this.classTypeParameters = classTypeParameters;
       }
 
       Builder foundBackingField(DexEncodedField field) {
@@ -429,15 +421,15 @@
         return this;
       }
 
-      Builder foundGetter(DexEncodedMethod getter, int flags) {
+      Builder foundGetter(DexEncodedMethod getter, KotlinPropertyInfo propertyInfo) {
         this.getter = getter;
-        this.getterFlags = flags;
+        this.getterInfo = propertyInfo;
         return this;
       }
 
-      Builder foundSetter(DexEncodedMethod setter, int flags) {
+      Builder foundSetter(DexEncodedMethod setter, KotlinPropertyInfo propertyInfo) {
         this.setter = setter;
-        this.setterFlags = flags;
+        this.setterInfo = propertyInfo;
         return this;
       }
 
@@ -476,177 +468,234 @@
           }
         }
         return new KmPropertyGroup(
-            flags, name, field, getter, getterFlags, setter, setterFlags, annotations, isExtension);
+            flags,
+            name,
+            field,
+            getter,
+            getterInfo,
+            setter,
+            setterInfo,
+            annotations,
+            isExtension,
+            classTypeParameters);
       }
     }
 
-    KmProperty toRenamedKmProperty(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-      KmProperty kmProperty = new KmProperty(flags, name, flagsOf(), flagsOf());
-      KmType kmPropertyType = null;
-      KmType kmReceiverType = null;
-
-      // A flag to indicate we can rename the property name. This will become false if any member
-      // is pinned. Then, we conservatively assume that users want the property to be pinned too.
-      // That is, we won't rename the property even though some other members could be renamed.
-      boolean canChangePropertyName = true;
-      // A candidate property name. Not overwritten by the following members, hence the order of
-      // preference: a backing field, getter, and setter.
-      String renamedPropertyName = name;
-      if (field != null) {
-        if (appView.appInfo().isPinned(field.field)) {
-          canChangePropertyName = false;
+    private String setFieldForProperty(
+        KmProperty kmProperty,
+        KmType defaultPropertyType,
+        AppView<AppInfoWithLiveness> appView,
+        NamingLens lens,
+        KotlinMetadataSynthesizer synthesizer) {
+      DexField renamedField = lens.lookupField(field.field, appView.dexItemFactory());
+      ClassTypeSignatureToRenamedKmTypeConverter converter =
+          new ClassTypeSignatureToRenamedKmTypeConverter(
+              appView, this.classTypeParameters, synthesizer::toRenamedClassifier);
+      if (kmProperty.getReturnType() == defaultPropertyType) {
+        KmType kmPropertyType =
+            synthesizer.toRenamedKmType(field.field.type, null, null, converter);
+        if (kmPropertyType != null) {
+          kmProperty.setReturnType(kmPropertyType);
         }
-        DexField renamedField = lens.lookupField(field.field, appView.dexItemFactory());
-        if (canChangePropertyName && renamedField.name != field.field.name) {
-          renamedPropertyName = renamedField.name.toString();
-        }
-        FieldTypeSignature signature =
-            GenericSignature.Parser.toFieldTypeSignature(field, appView);
-        kmPropertyType =
-            setRenamedKmType(field.field.type, signature, appView, lens, kmProperty::setReturnType);
-        JvmExtensionsKt.setFieldSignature(kmProperty, toJvmFieldSignature(renamedField));
       }
+      JvmExtensionsKt.setFieldSignature(kmProperty, toJvmFieldSignature(renamedField));
+      return appView.appInfo().isPinned(field.field) ? null : renamedField.name.toString();
+    }
 
-      GetterSetterCriteria criteria = checkGetterCriteria();
-      if (criteria == GetterSetterCriteria.VIOLATE) {
-        return null;
+    private boolean setReceiverParameterTypeForExtensionProperty(
+        KmProperty kmProperty,
+        DexEncodedMethod method,
+        AppView<AppInfoWithLiveness> appView,
+        KotlinMetadataSynthesizer synthesizer) {
+      if (!isExtension) {
+        return true;
       }
+      MethodTypeSignature signature =
+          GenericSignature.Parser.toMethodTypeSignature(method, appView);
+      List<KmTypeParameter> typeParameters =
+          KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
+              classTypeParameters,
+              signature.getFormalTypeParameters(),
+              appView.dexItemFactory(),
+              kmTypeParameter -> {});
+      ClassTypeSignatureToRenamedKmTypeConverter converter =
+          new ClassTypeSignatureToRenamedKmTypeConverter(
+              appView, typeParameters, synthesizer::toRenamedClassifier);
+      DexType receiverType = method.method.proto.parameters.values[0];
+      TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
+      KmType kmReceiverType =
+          synthesizer.toRenamedKmType(receiverType, receiverSignature, null, converter);
+      if (kmProperty.getReceiverParameterType() != null) {
+        // If the receiver type for the extension property is set already make sure it's consistent.
+        return getDescriptorFromKmType(kmReceiverType)
+            .equals(getDescriptorFromKmType(kmProperty.getReceiverParameterType()));
+      }
+      kmProperty.setReceiverParameterType(kmReceiverType);
+      return true;
+    }
 
-      if (criteria == GetterSetterCriteria.MET) {
-        assert getter != null && getter.method.proto.parameters.size() == (isExtension ? 1 : 0)
-            : "checkGetterCriteria: " + this.toString();
-        MethodTypeSignature signature =
-            GenericSignature.Parser.toMethodTypeSignature(getter, appView);
-        if (isExtension) {
-          TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
-          kmReceiverType =
-              setRenamedKmType(
-                  getter.method.proto.parameters.values[0],
-                  receiverSignature,
-                  appView,
-                  lens,
-                  kmProperty::setReceiverParameterType);
-        }
-
-        DexType returnType = getter.method.proto.returnType;
-        TypeSignature returnSignature = signature.returnType().typeSignature();
-        if (kmPropertyType == null) {
-          // The property type is not set yet.
-          kmPropertyType = setRenamedKmType(
-              returnType, returnSignature, appView, lens, kmProperty::setReturnType);
-        } else {
-          // If property type is set already (via backing field), make sure it's consistent.
-          KmType kmPropertyTypeFromGetter =
-              toRenamedKmType(returnType, returnSignature, appView, lens);
-          if (!getDescriptorFromKmType(kmPropertyType)
-              .equals(getDescriptorFromKmType(kmPropertyTypeFromGetter))) {
-            return null;
-          }
-        }
-        if (appView.appInfo().isPinned(getter.method)) {
-          canChangePropertyName = false;
-        }
-        DexMethod renamedGetter = lens.lookupMethod(getter.method, appView.dexItemFactory());
-        if (canChangePropertyName
-            && renamedGetter.name != getter.method.name
-            && renamedPropertyName.equals(name)) {
-          renamedPropertyName = renamedGetter.name.toString();
-        }
-        kmProperty.setGetterFlags(getterFlags);
-        JvmExtensionsKt.setGetterSignature(kmProperty, toJvmMethodSignature(renamedGetter));
-      } else if (field != null) {
+    private String setGetterForProperty(
+        KmProperty kmProperty,
+        KmType defaultPropertyType,
+        AppView<AppInfoWithLiveness> appView,
+        NamingLens lens,
+        KotlinMetadataSynthesizer synthesizer) {
+      if (checkGetterCriteria() == GetterSetterCriteria.NOT_AVAILABLE) {
         // Property without getter.
         // Even though a getter does not exist, `kotlinc` still set getter flags and use them to
         // determine when to direct field access v.s. getter calls for property resolution.
-        kmProperty.setGetterFlags(field.accessFlags.getAsKotlinFlags());
+        if (field != null) {
+          kmProperty.setGetterFlags(field.accessFlags.getAsKotlinFlags());
+        }
+        return kmProperty.getName();
       }
+      assert checkGetterCriteria() == GetterSetterCriteria.MET;
+      assert getter != null;
+      DexProto proto = getter.method.proto;
+      assert proto.parameters.size() == (isExtension ? 1 : 0)
+          : "checkGetterCriteria: " + this.toString();
+      MethodTypeSignature signature =
+          GenericSignature.Parser.toMethodTypeSignature(getter, appView);
+      List<KmTypeParameter> typeParameters =
+          KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
+              this.classTypeParameters,
+              signature.getFormalTypeParameters(),
+              appView.dexItemFactory(),
+              kmTypeParameter -> {});
+      ClassTypeSignatureToRenamedKmTypeConverter converter =
+          new ClassTypeSignatureToRenamedKmTypeConverter(
+              appView, typeParameters, synthesizer::toRenamedClassifier);
+      DexType returnType = proto.returnType;
+      TypeSignature returnSignature = signature.returnType().typeSignature();
+      KmType kmPropertyType =
+          synthesizer.toRenamedKmType(
+              returnType, returnSignature, getterInfo.returnType, converter);
+      if (kmProperty.getReturnType() == defaultPropertyType) {
+        // The property type is not set yet.
+        kmProperty.setReturnType(kmPropertyType);
+      } else if (!getDescriptorFromKmType(kmPropertyType)
+          .equals(getDescriptorFromKmType(kmProperty.getReturnType()))) {
+        // If property type is set already (via backing field), make sure it's consistent.
+        return null;
+      }
+      DexMethod renamedGetter = lens.lookupMethod(getter.method, appView.dexItemFactory());
+      kmProperty.setGetterFlags(getter.accessFlags.getAsKotlinFlags());
+      JvmExtensionsKt.setGetterSignature(kmProperty, toJvmMethodSignature(renamedGetter));
+      return appView.appInfo().isPinned(getter.method) ? null : renamedGetter.name.toString();
+    }
 
-      criteria = checkSetterCriteria();
+    private String setSetterForProperty(
+        KmProperty kmProperty,
+        KmType defaultPropertyType,
+        AppView<AppInfoWithLiveness> appView,
+        NamingLens lens,
+        KotlinMetadataSynthesizer synthesizer) {
+      GetterSetterCriteria criteria = checkSetterCriteria();
       if (criteria == GetterSetterCriteria.VIOLATE) {
-        return null;
+        return kmProperty.getName();
       }
-
-      if (criteria == GetterSetterCriteria.MET) {
-        assert setter != null && setter.method.proto.parameters.size() == (isExtension ? 2 : 1)
-            : "checkSetterCriteria: " + this.toString();
-        MethodTypeSignature signature =
-            GenericSignature.Parser.toMethodTypeSignature(setter, appView);
-        if (isExtension) {
-          DexType receiverType = setter.method.proto.parameters.values[0];
-          TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
-          if (kmReceiverType == null) {
-            kmReceiverType =
-                setRenamedKmType(
-                    receiverType,
-                    receiverSignature,
-                    appView,
-                    lens,
-                    kmProperty::setReceiverParameterType);
-          } else {
-            // If the receiver type for the extension property is set already (via getter),
-            // make sure it's consistent.
-            KmType kmReceiverTypeFromSetter =
-                toRenamedKmType(receiverType, receiverSignature, appView, lens);
-            if (!getDescriptorFromKmType(kmReceiverType)
-                .equals(getDescriptorFromKmType(kmReceiverTypeFromSetter))) {
-              return null;
-            }
-          }
+      if (criteria == GetterSetterCriteria.NOT_AVAILABLE) {
+        if (field != null && IS_VAR.invoke(flags)) {
+          // Editable property without setter.
+          // Even though a setter does not exist, `kotlinc` still set setter flags and use them to
+          // determine when to direct field access v.s. setter calls for property resolution.
+          kmProperty.setSetterFlags(field.accessFlags.getAsKotlinFlags());
         }
-
-        int valueIndex = isExtension ? 1 : 0;
-        DexType valueType = setter.method.proto.parameters.values[valueIndex];
-        TypeSignature valueSignature = signature.getParameterTypeSignature(valueIndex);
-        if (kmPropertyType == null) {
-          // The property type is not set yet.
-          kmPropertyType =
-              setRenamedKmType(valueType, valueSignature, appView, lens, kmProperty::setReturnType);
-        } else {
-          // If property type is set already (via either backing field or getter),
-          // make sure it's consistent.
-          KmType kmPropertyTypeFromSetter =
-              toRenamedKmType(valueType, valueSignature, appView, lens);
-          if (!getDescriptorFromKmType(kmPropertyType)
-              .equals(getDescriptorFromKmType(kmPropertyTypeFromSetter))) {
-            return null;
-          }
-        }
-        KotlinValueParameterInfo valueParameterInfo =
-            setter.getKotlinMemberInfo().getValueParameterInfo(valueIndex);
-        KmValueParameter kmValueParameter = toRewrittenKmValueParameter(
-            valueParameterInfo, valueType, valueSignature, "value", appView, lens);
-        if (kmValueParameter != null) {
-          kmProperty.setSetterParameter(kmValueParameter);
-        }
-        if (appView.appInfo().isPinned(setter.method)) {
-          canChangePropertyName = false;
-        }
-        DexMethod renamedSetter = lens.lookupMethod(setter.method, appView.dexItemFactory());
-        if (canChangePropertyName
-            && renamedSetter.name != setter.method.name
-            && renamedPropertyName.equals(name)) {
-          renamedPropertyName = renamedSetter.name.toString();
-        }
-        kmProperty.setSetterFlags(setterFlags);
-        JvmExtensionsKt.setSetterSignature(kmProperty, toJvmMethodSignature(renamedSetter));
-      } else if (field != null && IS_VAR.invoke(flags)) {
-        // Editable property without setter.
-        // Even though a setter does not exist, `kotlinc` still set setter flags and use them to
-        // determine when to direct field access v.s. setter calls for property resolution.
-        kmProperty.setGetterFlags(field.accessFlags.getAsKotlinFlags());
+        return kmProperty.getName();
       }
-
-      // If the property type remains null at the end, bail out to synthesize this property.
-      if (kmPropertyType == null) {
-        return null;
+      assert criteria == GetterSetterCriteria.MET;
+      assert setter != null;
+      DexProto proto = setter.method.proto;
+      assert proto.parameters.size() == (isExtension ? 2 : 1)
+          : "checkSetterCriteria: " + this.toString();
+      MethodTypeSignature signature =
+          GenericSignature.Parser.toMethodTypeSignature(setter, appView);
+      List<KmTypeParameter> typeParameters =
+          KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
+              classTypeParameters,
+              signature.getFormalTypeParameters(),
+              appView.dexItemFactory(),
+              kmTypeParameter -> {});
+      ClassTypeSignatureToRenamedKmTypeConverter converter =
+          new ClassTypeSignatureToRenamedKmTypeConverter(
+              appView, typeParameters, synthesizer::toRenamedClassifier);
+      int valueIndex = isExtension ? 1 : 0;
+      DexType valueType = proto.parameters.values[valueIndex];
+      TypeSignature valueSignature = signature.getParameterTypeSignature(valueIndex);
+      KmType kmPropertyType =
+          synthesizer.toRenamedKmType(valueType, valueSignature, setterInfo.returnType, converter);
+      if (kmProperty.getReturnType() == defaultPropertyType) {
+        // The property type is not set yet.
+        kmProperty.setReturnType(kmPropertyType);
+      } else {
+        // If property type is set already make sure it's consistent.
+        if (!getDescriptorFromKmType(kmPropertyType)
+            .equals(getDescriptorFromKmType(kmProperty.getReturnType()))) {
+          return null;
+        }
       }
-      // For extension property, if the receiver type remains null at the end, bail out too.
-      if (isExtension && kmReceiverType == null) {
-        return null;
+      assert setter.getKotlinMemberInfo().isPropertyInfo();
+      KotlinValueParameterInfo valueParameterInfo =
+          setter.getKotlinMemberInfo().asPropertyInfo().valueParameterInfo;
+      KmValueParameter kmValueParameter =
+          synthesizer.toRewrittenKmValueParameter(
+              valueParameterInfo, valueType, valueSignature, "value", converter);
+      if (kmValueParameter != null) {
+        kmProperty.setSetterParameter(kmValueParameter);
+      }
+      DexMethod renamedSetter = lens.lookupMethod(setter.method, appView.dexItemFactory());
+      kmProperty.setSetterFlags(setterInfo.setterFlags);
+      JvmExtensionsKt.setSetterSignature(kmProperty, toJvmMethodSignature(renamedSetter));
+      return appView.appInfo().isPinned(setter.method) ? null : renamedSetter.name.toString();
+    }
+
+    KmProperty toRenamedKmProperty(KotlinMetadataSynthesizer synthesizer) {
+      AppView<AppInfoWithLiveness> appView = synthesizer.appView;
+      NamingLens lens = synthesizer.lens;
+      KmProperty kmProperty = new KmProperty(flags, name, flagsOf(), flagsOf());
+
+      // Set default values
+      KmType defaultPropertyType = new KmType(flagsOf());
+      kmProperty.setReturnType(defaultPropertyType);
+
+      String renamedPropertyName = name;
+      if (getter != null) {
+        if (checkGetterCriteria() == GetterSetterCriteria.VIOLATE) {
+          return null;
+        }
+        if (!setReceiverParameterTypeForExtensionProperty(
+            kmProperty, getter, appView, synthesizer)) {
+          return null;
+        }
+        renamedPropertyName =
+            setGetterForProperty(kmProperty, defaultPropertyType, appView, lens, synthesizer);
+      }
+      if (setter != null) {
+        if (checkSetterCriteria() == GetterSetterCriteria.VIOLATE) {
+          return null;
+        }
+        if (!setReceiverParameterTypeForExtensionProperty(
+            kmProperty, setter, appView, synthesizer)) {
+          return null;
+        }
+        String renamedName =
+            setSetterForProperty(kmProperty, defaultPropertyType, appView, lens, synthesizer);
+        if (renamedPropertyName != null) {
+          renamedPropertyName = renamedName;
+        }
+      }
+      // Setting the property type from the field has to be done after the getter, otherwise we
+      // may potentially loose type-argument information.
+      if (field != null) {
+        String renamedName =
+            setFieldForProperty(kmProperty, defaultPropertyType, appView, lens, synthesizer);
+        if (renamedPropertyName != null) {
+          renamedPropertyName = renamedName;
+        }
       }
       // Rename the property name if and only if none of participating members is pinned, and
       // any of them is indeed renamed (to a new name).
-      if (canChangePropertyName && !renamedPropertyName.equals(name)) {
+      if (renamedPropertyName != null) {
         kmProperty.setName(renamedPropertyName);
       }
       return kmProperty;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java
new file mode 100644
index 0000000..9382cd9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java
@@ -0,0 +1,174 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static com.android.tools.r8.kotlin.Kotlin.NAME;
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier;
+import static kotlinx.metadata.FlagsKt.flagsOf;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.ArrayTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
+import com.android.tools.r8.graph.GenericSignature.TypeVariableSignature;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import kotlinx.metadata.KmTypeParameter;
+import kotlinx.metadata.KmTypeVisitor;
+import kotlinx.metadata.KmVariance;
+
+class KotlinMetadataSynthesizerUtils {
+
+  static void populateKmTypeFromSignature(
+      FieldTypeSignature typeSignature,
+      Supplier<KmTypeVisitor> typeVisitor,
+      List<KmTypeParameter> allTypeParameters,
+      DexItemFactory factory) {
+    if (typeSignature.isClassTypeSignature()) {
+      populateKmTypeFromClassTypeSignature(
+          typeSignature.asClassTypeSignature(), typeVisitor, allTypeParameters, factory);
+    } else if (typeSignature.isArrayTypeSignature()) {
+      populateKmTypeFromArrayTypeSignature(
+          typeSignature.asArrayTypeSignature(), typeVisitor, allTypeParameters, factory);
+    } else {
+      assert typeSignature.isTypeVariableSignature();
+      populateKmTypeFromTypeVariableSignature(
+          typeSignature.asTypeVariableSignature(), typeVisitor, allTypeParameters);
+    }
+  }
+
+  private static void populateKmTypeFromTypeVariableSignature(
+      TypeVariableSignature typeSignature,
+      Supplier<KmTypeVisitor> visitor,
+      List<KmTypeParameter> allTypeParameters) {
+    for (KmTypeParameter typeParameter : allTypeParameters) {
+      if (typeParameter
+          .getName()
+          .equals(typeSignature.asTypeVariableSignature().getTypeVariable())) {
+        visitor.get().visitTypeParameter(typeParameter.getId());
+        return;
+      }
+    }
+  }
+
+  private static void populateKmTypeFromArrayTypeSignature(
+      ArrayTypeSignature typeSignature,
+      Supplier<KmTypeVisitor> visitor,
+      List<KmTypeParameter> allTypeParameters,
+      DexItemFactory factory) {
+    ArrayTypeSignature arrayTypeSignature = typeSignature.asArrayTypeSignature();
+    if (!arrayTypeSignature.elementSignature().isFieldTypeSignature()) {
+      return;
+    }
+    KmTypeVisitor kmType = visitor.get();
+    kmType.visitClass(NAME + "/Array");
+    populateKmTypeFromSignature(
+        arrayTypeSignature.elementSignature().asFieldTypeSignature(),
+        () -> kmType.visitArgument(flagsOf(), KmVariance.INVARIANT),
+        allTypeParameters,
+        factory);
+  }
+
+  private static void populateKmTypeFromClassTypeSignature(
+      ClassTypeSignature typeSignature,
+      Supplier<KmTypeVisitor> visitor,
+      List<KmTypeParameter> allTypeParameters,
+      DexItemFactory factory) {
+    // No need to record the trivial argument.
+    if (factory.objectType == typeSignature.type()) {
+      return;
+    }
+    KmTypeVisitor kmType = visitor.get();
+    kmType.visitClass(toClassifier(typeSignature.type(), factory));
+    for (FieldTypeSignature typeArgument : typeSignature.typeArguments()) {
+      populateKmTypeFromSignature(
+          typeArgument,
+          () -> kmType.visitArgument(flagsOf(), KmVariance.INVARIANT),
+          allTypeParameters,
+          factory);
+    }
+  }
+
+  static String toClassifier(DexType type, DexItemFactory factory) {
+    // E.g., V -> kotlin/Unit, J -> kotlin/Long, [J -> kotlin/LongArray
+    if (factory.kotlin.knownTypeConversion.containsKey(type)) {
+      DexType convertedType = factory.kotlin.knownTypeConversion.get(type);
+      assert convertedType != null;
+      return descriptorToKotlinClassifier(convertedType.toDescriptorString());
+    }
+    // E.g., [Ljava/lang/String; -> kotlin/Array
+    if (type.isArrayType()) {
+      return NAME + "/Array";
+    }
+    return descriptorToKotlinClassifier(type.toDescriptorString());
+  }
+
+  /**
+   * Utility method building up all type-parameters from {@code classTypeParameters} combined with
+   * {@code parameters}, where the consumer {@code addedFromParameters} is called for every
+   * conversion of {@Code FormalTypeParameter} to {@Code KmTypeParameter}.
+   *
+   * <pre>
+   *  classTypeParameters: [KmTypeParameter(T), KmTypeParameter(S)]
+   *  parameters: [FormalTypeParameter(R)]
+   *  result: [KmTypeParameter(T), KmTypeParameter(S), KmTypeParameter(R)].
+   * </pre>
+   *
+   * @param classTypeParameters
+   * @param parameters
+   * @param factory
+   * @param addedFromParameters
+   * @return
+   */
+  static List<KmTypeParameter> convertFormalTypeParameters(
+      List<KmTypeParameter> classTypeParameters,
+      List<FormalTypeParameter> parameters,
+      DexItemFactory factory,
+      Consumer<KmTypeParameter> addedFromParameters) {
+    if (parameters.isEmpty()) {
+      return classTypeParameters;
+    }
+    ImmutableList.Builder<KmTypeParameter> builder = ImmutableList.builder();
+    builder.addAll(classTypeParameters);
+    int idCounter = classTypeParameters.size();
+    // Assign type-variables ids to names. All generic signatures has been minified at this point,
+    // but it may be that type-variables are used before we can see them (is that allowed?).
+    for (FormalTypeParameter parameter : parameters) {
+      KmTypeParameter element =
+          new KmTypeParameter(flagsOf(), parameter.getName(), idCounter++, KmVariance.INVARIANT);
+      builder.add(element);
+      addedFromParameters.accept(element);
+    }
+    idCounter = 0;
+    ImmutableList<KmTypeParameter> allTypeParameters = builder.build();
+    for (FormalTypeParameter parameter : parameters) {
+      KmTypeParameter kmTypeParameter =
+          allTypeParameters.get(classTypeParameters.size() + idCounter++);
+      visitUpperBound(parameter.getClassBound(), allTypeParameters, kmTypeParameter, factory);
+      if (parameter.getInterfaceBounds() != null) {
+        for (FieldTypeSignature interfaceBound : parameter.getInterfaceBounds()) {
+          visitUpperBound(interfaceBound, allTypeParameters, kmTypeParameter, factory);
+        }
+      }
+    }
+    return allTypeParameters;
+  }
+
+  private static void visitUpperBound(
+      FieldTypeSignature typeSignature,
+      List<KmTypeParameter> allTypeParameters,
+      KmTypeParameter parameter,
+      DexItemFactory factory) {
+    if (typeSignature.isUnknown()) {
+      return;
+    }
+    populateKmTypeFromSignature(
+        typeSignature, () -> parameter.visitUpperBound(flagsOf()), allTypeParameters, factory);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
new file mode 100644
index 0000000..130a660
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import kotlinx.metadata.KmClassifier;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeProjection;
+
+// Provides access to Kotlin information about a kotlin type.
+public class KotlinTypeInfo {
+
+  static final List<KotlinTypeProjectionInfo> EMPTY_ARGUMENTS = ImmutableList.of();
+
+  private final KmClassifier classifier;
+  private final List<KotlinTypeProjectionInfo> arguments;
+
+  private KotlinTypeInfo(KmClassifier classifier, List<KotlinTypeProjectionInfo> arguments) {
+    this.classifier = classifier;
+    this.arguments = arguments;
+  }
+
+  static KotlinTypeInfo create(KmType kmType) {
+    if (kmType == null) {
+      return null;
+    }
+    if (kmType.getArguments().isEmpty()) {
+      return new KotlinTypeInfo(kmType.classifier, EMPTY_ARGUMENTS);
+    }
+    ImmutableList.Builder<KotlinTypeProjectionInfo> arguments = new ImmutableList.Builder<>();
+    for (KmTypeProjection argument : kmType.getArguments()) {
+      arguments.add(KotlinTypeProjectionInfo.create(argument));
+    }
+    return new KotlinTypeInfo(kmType.classifier, arguments.build());
+  }
+
+  public boolean isTypeAlias() {
+    return classifier instanceof KmClassifier.TypeAlias;
+  }
+
+  public KmClassifier.TypeAlias asTypeAlias() {
+    return (KmClassifier.TypeAlias) classifier;
+  }
+
+  public List<KotlinTypeProjectionInfo> getArguments() {
+    return arguments;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
new file mode 100644
index 0000000..3782bd8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import kotlinx.metadata.KmTypeProjection;
+import kotlinx.metadata.KmVariance;
+
+// Provides access to Kotlin information about the type projection of a type (arguments).
+public class KotlinTypeProjectionInfo {
+
+  final KmVariance variance;
+  final KotlinTypeInfo typeInfo;
+
+  private KotlinTypeProjectionInfo(KmVariance variance, KotlinTypeInfo typeInfo) {
+    this.variance = variance;
+    this.typeInfo = typeInfo;
+  }
+
+  static KotlinTypeProjectionInfo create(KmTypeProjection kmTypeProjection) {
+    return new KotlinTypeProjectionInfo(
+        kmTypeProjection.getVariance(), KotlinTypeInfo.create(kmTypeProjection.getType()));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
index 3134b8a..e358c50 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
@@ -19,24 +19,36 @@
   final int flag;
   // Indicates whether the formal parameter is originally `vararg`.
   final boolean isVararg;
+  // Original information about the type.
+  final KotlinTypeInfo type;
+
   // TODO(b/151194869): Should we treat them as normal annotations? E.g., shrinking and renaming?
   // Annotations on the type of value parameter.
   final List<KmAnnotation> annotations;
 
   private KotlinValueParameterInfo(
-      String name, int flag, boolean isVararg, List<KmAnnotation> annotations) {
+      String name,
+      int flag,
+      boolean isVararg,
+      KotlinTypeInfo type,
+      List<KmAnnotation> annotations) {
     this.name = name;
     this.flag = flag;
     this.isVararg = isVararg;
+    this.type = type;
     this.annotations = annotations;
   }
 
   static KotlinValueParameterInfo fromKmValueParameter(KmValueParameter kmValueParameter) {
+    if (kmValueParameter == null) {
+      return null;
+    }
     KmType kmType = kmValueParameter.getType();
     return new KotlinValueParameterInfo(
         kmValueParameter.getName(),
         kmValueParameter.getFlags(),
         kmValueParameter.getVarargElementType() != null,
+        KotlinTypeInfo.create(kmType),
         kmType != null ? JvmExtensionsKt.getAnnotations(kmType) : ImmutableList.of());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index b178579..5506792 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -273,7 +273,7 @@
       return;
     }
     assert definition.field != field;
-    assert definition.field.holder != field.holder;
+    assert definition.holder() != field.holder;
     // If the definition is renamed,
     if (renaming.containsKey(definition.field)) {
       // Assign the same, renamed name as the definition to the reference.
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
index 9286684..6652d4a 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
@@ -52,7 +52,7 @@
   public DexString getOrCreateNameFor(DexField field) {
     DexEncodedField encodedField = appView.appInfo().resolveField(field);
     if (encodedField != null) {
-      DexClass clazz = appView.definitionFor(encodedField.field.holder);
+      DexClass clazz = appView.definitionFor(encodedField.holder());
       if (clazz == null) {
         return field.name;
       }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index d5873a9..332477a 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -148,13 +148,13 @@
     }
     Value in = instruction.value();
     if (!in.isConstString()) {
-      warnUndeterminedIdentifierIfNecessary(field, method.method.holder, instruction, null);
+      warnUndeterminedIdentifierIfNecessary(field, method.holder(), instruction, null);
       return iterator;
     }
     DexString original = in.getConstInstruction().asConstString().getValue();
     DexReference itemBasedString = inferMemberOrTypeFromNameString(appView, original);
     if (itemBasedString == null) {
-      warnUndeterminedIdentifierIfNecessary(field, method.method.holder, instruction, original);
+      warnUndeterminedIdentifierIfNecessary(field, method.holder(), instruction, original);
       return iterator;
     }
     // Move the cursor back to $fieldPut
@@ -213,7 +213,7 @@
     if (isReflectionMethod(appView.dexItemFactory(), invokedMethod) || isClassNameComparison) {
       DexReference itemBasedString = identifyIdentifier(invoke, appView);
       if (itemBasedString == null) {
-        DexType context = method.method.holder;
+        DexType context = method.holder();
         warnUndeterminedIdentifierIfNecessary(invokedMethod, context, invoke, null);
         return iterator;
       }
@@ -280,14 +280,13 @@
       for (int i = 0; i < ins.size(); i++) {
         Value in = ins.get(i);
         if (!in.isConstString()) {
-          warnUndeterminedIdentifierIfNecessary(invokedMethod, method.method.holder, invoke, null);
+          warnUndeterminedIdentifierIfNecessary(invokedMethod, method.holder(), invoke, null);
           continue;
         }
         DexString original = in.getConstInstruction().asConstString().getValue();
         DexReference itemBasedString = inferMemberOrTypeFromNameString(appView, original);
         if (itemBasedString == null) {
-          warnUndeterminedIdentifierIfNecessary(
-              invokedMethod, method.method.holder, invoke, original);
+          warnUndeterminedIdentifierIfNecessary(invokedMethod, method.holder(), invoke, original);
           continue;
         }
         // Move the cursor back to $invoke
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index da38ed4..d8d62ae 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptorIfValidJavaType;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -27,6 +28,7 @@
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.List;
@@ -282,13 +284,13 @@
   }
 
   static DexReference inferMemberOrTypeFromNameString(
-      DexDefinitionSupplier definitions, DexString dexString) {
+      AppView<AppInfoWithLiveness> appView, DexString dexString) {
     // "fully.qualified.ClassName.fieldOrMethodName"
     // "fully.qualified.ClassName#fieldOrMethodName"
-    DexReference itemBasedString = inferMemberFromNameString(definitions, dexString);
+    DexReference itemBasedString = inferMemberFromNameString(appView, dexString);
     if (itemBasedString == null) {
       // "fully.qualified.ClassName"
-      return inferTypeFromNameString(definitions, dexString);
+      return inferTypeFromNameString(appView, dexString);
     }
     return itemBasedString;
   }
@@ -320,7 +322,7 @@
   }
 
   private static DexReference inferMemberFromNameString(
-      DexDefinitionSupplier definitions, DexString dexString) {
+      AppView<AppInfoWithLiveness> appView, DexString dexString) {
     String identifier = dexString.toString();
     String typeIdentifier = null;
     String memberIdentifier = null;
@@ -348,8 +350,9 @@
     if (maybeDescriptor == null) {
       return null;
     }
-    DexType type = definitions.dexItemFactory().createType(maybeDescriptor);
-    DexClass holder = definitions.definitionFor(type);
+    DexType type = appView.dexItemFactory().createType(maybeDescriptor);
+    // TODO(b/150736225): Should we move the identification of identifiers into the initial tracing?
+    DexClass holder = appView.appInfo().definitionForWithoutExistenceAssert(type);
     if (holder == null) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
index 777d006..6c78e0b 100644
--- a/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
@@ -9,15 +9,13 @@
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableMap;
-import java.util.IdentityHashMap;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -28,9 +26,9 @@
 // Naming lens for rewriting type prefixes.
 public class PrefixRewritingNamingLens extends NamingLens {
 
-  final Map<DexType, DexString> classRenaming = new IdentityHashMap<>();
   final NamingLens namingLens;
   final InternalOptions options;
+  final AppView<?> appView;
 
   public static NamingLens createPrefixRewritingNamingLens(AppView<?> appView) {
     return createPrefixRewritingNamingLens(appView, NamingLens.getIdentityLens());
@@ -45,18 +43,21 @@
   }
 
   public PrefixRewritingNamingLens(NamingLens namingLens, AppView<?> appView) {
+    this.appView = appView;
     this.namingLens = namingLens;
     this.options = appView.options();
-    DexItemFactory itemFactory = options.itemFactory;
-    PrefixRewritingMapper rewritePrefix = appView.rewritePrefix;
-    itemFactory.forAllTypes(
-        type -> {
-          if (rewritePrefix.hasRewrittenType(type, appView)) {
-            classRenaming.put(type, rewritePrefix.rewrittenType(type, appView).descriptor);
-          }
-        });
-    // Verify that no type would have been renamed by both lenses.
-    assert namingLens.verifyNoOverlap(classRenaming);
+  }
+
+  private boolean isRenamed(DexType type) {
+    return getRenaming(type) != null;
+  }
+
+  private DexString getRenaming(DexType type) {
+    DexString descriptor = null;
+    if (appView.rewritePrefix.hasRewrittenType(type, appView)) {
+      descriptor = appView.rewritePrefix.rewrittenType(type, appView).descriptor;
+    }
+    return descriptor;
   }
 
   @Override
@@ -66,17 +67,18 @@
 
   @Override
   public DexString prefixRewrittenType(DexType type) {
-    return classRenaming.get(type);
+    return getRenaming(type);
   }
 
   @Override
   public DexString lookupDescriptor(DexType type) {
-    return classRenaming.getOrDefault(type, namingLens.lookupDescriptor(type));
+    DexString renaming = getRenaming(type);
+    return renaming != null ? renaming : namingLens.lookupDescriptor(type);
   }
 
   @Override
   public DexString lookupInnerName(InnerClassAttribute attribute, InternalOptions options) {
-    if (classRenaming.containsKey(attribute.getInner())) {
+    if (isRenamed(attribute.getInner())) {
       // Prefix rewriting does not influence the inner name.
       return attribute.getInnerName();
     }
@@ -85,7 +87,7 @@
 
   @Override
   public DexString lookupName(DexMethod method) {
-    if (classRenaming.containsKey(method.holder)) {
+    if (isRenamed(method.holder)) {
       // Prefix rewriting does not influence the method name.
       return method.name;
     }
@@ -94,7 +96,7 @@
 
   @Override
   public DexString lookupMethodName(DexCallSite callSite) {
-    if (classRenaming.containsKey(callSite.bootstrapMethod.rewrittenTarget.holder)) {
+    if (isRenamed(callSite.bootstrapMethod.rewrittenTarget.holder)) {
       // Prefix rewriting does not influence the inner name.
       return callSite.methodName;
     }
@@ -103,7 +105,7 @@
 
   @Override
   public DexString lookupName(DexField field) {
-    if (classRenaming.containsKey(field.holder)) {
+    if (isRenamed(field.holder)) {
       // Prefix rewriting does not influence the field name.
       return field.name;
     }
@@ -127,9 +129,10 @@
   }
 
   private boolean verifyNotPrefixRewrittenPackage(String packageName) {
-    for (DexType dexType : classRenaming.keySet()) {
-      assert !dexType.getPackageDescriptor().equals(packageName);
-    }
+    appView.rewritePrefix.forAllRewrittenTypes(
+        dexType -> {
+          assert !dexType.getPackageDescriptor().equals(packageName);
+        });
     return true;
   }
 
@@ -140,7 +143,7 @@
     // If compiling the desugared library, the mapping needs to be printed.
     // When debugging the program, both mapping files need to be merged.
     if (options.isDesugaredLibraryCompilation()) {
-      classRenaming.keySet().forEach(consumer);
+      appView.rewritePrefix.forAllRewrittenTypes(consumer);
     }
     namingLens.forAllRenamedTypes(consumer);
   }
@@ -150,13 +153,16 @@
       Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
     Map<String, T> renamedItemsPrefixRewritting;
     if (clazz == DexType.class) {
-      renamedItemsPrefixRewritting =
-          classRenaming.keySet().stream()
-              .filter(item -> predicate.test(clazz.cast(item)))
-              .map(clazz::cast)
-              .collect(ImmutableMap.toImmutableMap(namer, i -> i));
+      renamedItemsPrefixRewritting = new HashMap<>();
+      appView.rewritePrefix.forAllRewrittenTypes(
+          item -> {
+            T cast = clazz.cast(item);
+            if (predicate.test(cast)) {
+              renamedItemsPrefixRewritting.put(namer.apply(cast), cast);
+            }
+          });
     } else {
-      renamedItemsPrefixRewritting = ImmutableMap.of();
+      renamedItemsPrefixRewritting = Collections.emptyMap();
     }
     Map<String, T> renamedItemsMinifier = namingLens.getRenamedItems(clazz, predicate, namer);
     // The Collector throws an exception for duplicated keys.
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 9f32739..da62097 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -340,7 +340,8 @@
         // have no effect.
         continue;
       }
-      DexClass dexClass = appView.definitionFor(type);
+      // TODO(b/150736225): Is this sound? What if the type is a library type that has been pruned?
+      DexClass dexClass = appView.appInfo().definitionForWithoutExistenceAssert(type);
       if (dexClass == null) {
         computeDefaultInterfaceMethodMappingsForType(
             type,
@@ -411,7 +412,7 @@
       // TODO(b/136694827): Check for already used and report an error. It seems like this can be
       //  done already in the reservation step for classes since there is only one 'path', unlike
       //  members that can be reserved differently in the hierarchy.
-      DexClass clazz = appView.definitionFor(type);
+      DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(type);
       if (clazz == null) {
         return type.descriptor;
       }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 4d20f6b..42e03e0 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -142,7 +142,7 @@
       // TODO(b/128404854) Rebind to the lowest library class or program class. For now we allow
       //  searching in library for methods, but this should be done on classpath instead.
       if (target != null && target.method != method) {
-        DexClass targetClass = appView.definitionFor(target.method.holder);
+        DexClass targetClass = appView.definitionFor(target.holder());
         if (originalClass.isProgramClass()) {
           // In Java bytecode, it is only possible to target interface methods that are in one of
           // the immediate super-interfaces via a super-invocation (see IndirectSuperInterfaceTest).
@@ -159,14 +159,14 @@
           // visibility problems when rebinding.
           final DexEncodedMethod finalTarget = target;
           Set<DexEncodedMethod> contexts = methodsWithContexts.get(method);
-          if (contexts.stream().anyMatch(context ->
-              mayNeedBridgeForVisibility(context.method.holder, finalTarget))) {
+          if (contexts.stream()
+              .anyMatch(context -> mayNeedBridgeForVisibility(context.holder(), finalTarget))) {
             target =
                 insertBridgeForVisibilityIfNeeded(
                     method, target, originalClass, targetClass, lookupTarget);
           }
         }
-        builder.map(method, lense.lookupMethod(validTargetFor(target.method, method)));
+        builder.map(method, lense.lookupMethod(validTargetFor(target.method, method)), invokeType);
       }
     }
   }
@@ -217,7 +217,7 @@
   }
 
   private boolean mayNeedBridgeForVisibility(DexType context, DexEncodedMethod method) {
-    DexType holderType = method.method.holder;
+    DexType holderType = method.holder();
     DexClass holder = appView.definitionFor(holderType);
     if (holder == null) {
       return false;
@@ -315,7 +315,7 @@
         .allMatch(
             context ->
                 isMemberVisibleFromOriginalContext(
-                    appView, context.method.holder, target.field.holder, target.accessFlags))) {
+                    appView, context.holder(), target.holder(), target.accessFlags))) {
       builder.map(
           field, lense.lookupField(validTargetFor(target.field, field, DexClass::lookupField)));
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
index a845de2..95e2772 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
@@ -9,45 +9,72 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.google.common.collect.ImmutableMap;
+import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.Set;
 
 public class MemberRebindingLense extends NestedGraphLense {
 
-  public static class Builder extends NestedGraphLense.Builder {
+  public static class Builder {
 
     private final AppView<?> appView;
 
+    private final Map<DexField, DexField> fieldMap = new IdentityHashMap<>();
+    private final Map<Invoke.Type, Map<DexMethod, DexMethod>> methodMaps = new IdentityHashMap<>();
+
     protected Builder(AppView<?> appView) {
       this.appView = appView;
     }
 
+    public void map(DexField from, DexField to) {
+      if (from == to) {
+        assert !fieldMap.containsKey(from);
+        return;
+      }
+      fieldMap.put(from, to);
+    }
+
+    public void map(DexMethod from, DexMethod to, Invoke.Type type) {
+      if (from == to) {
+        assert !methodMaps.containsKey(type) || methodMaps.get(type).getOrDefault(from, to) == to;
+        return;
+      }
+      Map<DexMethod, DexMethod> methodMap =
+          methodMaps.computeIfAbsent(type, ignore -> new IdentityHashMap<>());
+      assert methodMap.getOrDefault(from, to) == to;
+      methodMap.put(from, to);
+    }
+
     public GraphLense build(GraphLense previousLense) {
-      assert typeMap.isEmpty();
-      if (methodMap.isEmpty() && fieldMap.isEmpty()) {
+      if (fieldMap.isEmpty() && methodMaps.isEmpty()) {
         return previousLense;
       }
-      return new MemberRebindingLense(appView, methodMap, fieldMap, previousLense);
+      return new MemberRebindingLense(appView, methodMaps, fieldMap, previousLense);
     }
   }
 
   private final AppView<?> appView;
+  private final Map<Invoke.Type, Map<DexMethod, DexMethod>> methodMaps;
 
   public MemberRebindingLense(
       AppView<?> appView,
-      Map<DexMethod, DexMethod> methodMap,
+      Map<Invoke.Type, Map<DexMethod, DexMethod>> methodMaps,
       Map<DexField, DexField> fieldMap,
       GraphLense previousLense) {
     super(
         ImmutableMap.of(),
-        methodMap,
+        ImmutableMap.of(),
         fieldMap,
         null,
         null,
         previousLense,
         appView.dexItemFactory());
     this.appView = appView;
+    this.methodMaps = methodMaps;
   }
 
   public static Builder builder(AppView<?> appView) {
@@ -55,6 +82,28 @@
   }
 
   @Override
+  public boolean isLegitimateToHaveEmptyMappings() {
+    return true;
+  }
+
+  @Override
+  public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
+    GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
+    Map<DexMethod, DexMethod> methodMap = methodMaps.getOrDefault(type, Collections.emptyMap());
+    DexMethod newMethod = methodMap.get(previous.getMethod());
+    if (newMethod != null) {
+      return new GraphLenseLookupResult(
+          newMethod, mapInvocationType(newMethod, method, previous.getType()));
+    }
+    return previous;
+  }
+
+  @Override
+  public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
+    return previousLense.lookupMethodInAllContexts(method);
+  }
+
+  @Override
   protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
     return super.mapVirtualInterfaceInvocationTypes(appView, newMethod, originalMethod, type);
   }
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 6bf54dd..4bb1b2b 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -31,20 +30,18 @@
   private final Set<DexAnnotation> annotationsToRetain;
   private final Set<DexType> classesToRetainInnerClassAttributeFor;
   private final ProguardKeepAttributes keep;
-
-  public AnnotationRemover(
-      AppView<AppInfoWithLiveness> appView, Set<DexType> classesToRetainInnerClassAttributeFor) {
-    this(appView, classesToRetainInnerClassAttributeFor, ImmutableSet.of());
-  }
+  private final Set<DexType> removedClasses;
 
   private AnnotationRemover(
       AppView<AppInfoWithLiveness> appView,
       Set<DexType> classesToRetainInnerClassAttributeFor,
-      Set<DexAnnotation> annotationsToRetain) {
+      Set<DexAnnotation> annotationsToRetain,
+      Set<DexType> removedClasses) {
     this.appView = appView;
     this.annotationsToRetain = annotationsToRetain;
     this.classesToRetainInnerClassAttributeFor = classesToRetainInnerClassAttributeFor;
     this.keep = appView.options().getProguardConfiguration().getKeepAttributes();
+    this.removedClasses = removedClasses;
   }
 
   public static Builder builder() {
@@ -230,12 +227,15 @@
   private DexEncodedAnnotation rewriteEncodedAnnotation(DexEncodedAnnotation original) {
     GraphLense graphLense = appView.graphLense();
     DexType annotationType = original.type.toBaseType(appView.dexItemFactory());
+    if (removedClasses.contains(annotationType)) {
+      return null;
+    }
     DexType rewrittenType = graphLense.lookupType(annotationType);
     DexEncodedAnnotation rewrite =
         original.rewrite(
             graphLense::lookupType, element -> rewriteAnnotationElement(rewrittenType, element));
     assert rewrite != null;
-    DexClass annotationClass = appView.definitionFor(rewrittenType);
+    DexClass annotationClass = appView.appInfo().definitionFor(rewrittenType);
     assert annotationClass == null
         || appView.appInfo().isNonProgramTypeOrLiveProgramType(rewrittenType);
     return rewrite;
@@ -378,8 +378,7 @@
         }
         for (DexProgramClass clazz : appView.appInfo().classes()) {
           // 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.
+          // therefore need to keep the enclosing method and inner classes attributes, if requested.
           if (appView.appInfo().isPinned(clazz.type)) {
             for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
               DexType inner = innerClassAttribute.getInner();
@@ -413,10 +412,11 @@
       annotationsToRetain.add(annotation);
     }
 
-    public AnnotationRemover build(AppView<AppInfoWithLiveness> appView) {
+    public AnnotationRemover build(
+        AppView<AppInfoWithLiveness> appView, Set<DexType> removedClasses) {
       assert classesToRetainInnerClassAttributeFor != null;
       return new AnnotationRemover(
-          appView, classesToRetainInnerClassAttributeFor, annotationsToRetain);
+          appView, classesToRetainInnerClassAttributeFor, annotationsToRetain, removedClasses);
     }
   }
 }
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 fdd1274..78136a9 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -35,15 +35,16 @@
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.android.tools.r8.graph.PresortedComparable;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 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.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
+import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.PredicateSet;
-import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.ImmutableSet;
@@ -56,7 +57,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Deque;
-import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -70,7 +70,8 @@
 
 /** Encapsulates liveness and reachability information for an application. */
 public class AppInfoWithLiveness extends AppInfoWithSubtyping implements InstantiatedSubTypeInfo {
-
+  /** Set of reachable proto types that will be dead code eliminated. */
+  private final Set<DexType> deadProtoTypes;
   /** Set of types that are mentioned in the program, but for which no definition exists. */
   private final Set<DexType> missingTypes;
   /**
@@ -83,9 +84,6 @@
    * ServiceLoader.load() or ServiceLoader.loadInstalled().
    */
   public final Set<DexType> instantiatedAppServices;
-  /** Cache for {@link #isInstantiatedDirectlyOrIndirectly(DexProgramClass)}. */
-  private final IdentityHashMap<DexType, Boolean> indirectlyInstantiatedTypes =
-      new IdentityHashMap<>();
   /**
    * Set of methods that are the immediate target of an invoke. They might not actually be live but
    * are required so that invokes can find the method. If such a method is not live (i.e. not
@@ -188,14 +186,13 @@
   /** A map from enum types to their value types and ordinals. */
   final EnumValueInfoMapCollection enumValueInfoMaps;
 
-  final Set<DexType> instantiatedLambdas;
-
   /* A cache to improve the lookup performance of lookupSingleVirtualTarget */
   private final SingleTargetLookupCache singleTargetLookupCache = new SingleTargetLookupCache();
 
   // TODO(zerny): Clean up the constructors so we have just one.
   AppInfoWithLiveness(
       DirectMappedDexApplication application,
+      Set<DexType> deadProtoTypes,
       Set<DexType> missingTypes,
       Set<DexType> liveTypes,
       Set<DexType> instantiatedAppServices,
@@ -233,10 +230,10 @@
       Set<DexType> prunedTypes,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       EnumValueInfoMapCollection enumValueInfoMaps,
-      Set<DexType> instantiatedLambdas,
       Set<DexType> constClassReferences,
       Map<DexType, Visibility> initClassReferences) {
     super(application);
+    this.deadProtoTypes = deadProtoTypes;
     this.missingTypes = missingTypes;
     this.liveTypes = liveTypes;
     this.instantiatedAppServices = instantiatedAppServices;
@@ -274,13 +271,13 @@
     this.prunedTypes = prunedTypes;
     this.switchMaps = switchMaps;
     this.enumValueInfoMaps = enumValueInfoMaps;
-    this.instantiatedLambdas = instantiatedLambdas;
     this.constClassReferences = constClassReferences;
     this.initClassReferences = initClassReferences;
   }
 
   public AppInfoWithLiveness(
       AppInfoWithSubtyping appInfoWithSubtyping,
+      Set<DexType> deadProtoTypes,
       Set<DexType> missingTypes,
       Set<DexType> liveTypes,
       Set<DexType> instantiatedAppServices,
@@ -318,10 +315,10 @@
       Set<DexType> prunedTypes,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       EnumValueInfoMapCollection enumValueInfoMaps,
-      Set<DexType> instantiatedLambdas,
       Set<DexType> constClassReferences,
       Map<DexType, Visibility> initClassReferences) {
     super(appInfoWithSubtyping);
+    this.deadProtoTypes = deadProtoTypes;
     this.missingTypes = missingTypes;
     this.liveTypes = liveTypes;
     this.instantiatedAppServices = instantiatedAppServices;
@@ -359,7 +356,6 @@
     this.prunedTypes = prunedTypes;
     this.switchMaps = switchMaps;
     this.enumValueInfoMaps = enumValueInfoMaps;
-    this.instantiatedLambdas = instantiatedLambdas;
     this.constClassReferences = constClassReferences;
     this.initClassReferences = initClassReferences;
   }
@@ -367,6 +363,7 @@
   private AppInfoWithLiveness(AppInfoWithLiveness previous) {
     this(
         previous,
+        previous.deadProtoTypes,
         previous.missingTypes,
         previous.liveTypes,
         previous.instantiatedAppServices,
@@ -404,7 +401,6 @@
         previous.prunedTypes,
         previous.switchMaps,
         previous.enumValueInfoMaps,
-        previous.instantiatedLambdas,
         previous.constClassReferences,
         previous.initClassReferences);
     copyMetadataFromPrevious(previous);
@@ -417,6 +413,7 @@
       Collection<DexReference> additionalPinnedItems) {
     this(
         application,
+        previous.deadProtoTypes,
         previous.missingTypes,
         previous.liveTypes,
         previous.instantiatedAppServices,
@@ -458,7 +455,6 @@
             : CollectionUtils.mergeSets(previous.prunedTypes, removedClasses),
         previous.switchMaps,
         previous.enumValueInfoMaps,
-        previous.instantiatedLambdas,
         previous.constClassReferences,
         previous.initClassReferences);
     copyMetadataFromPrevious(previous);
@@ -470,10 +466,10 @@
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       EnumValueInfoMapCollection enumValueInfoMaps) {
     super(previous);
+    this.deadProtoTypes = previous.deadProtoTypes;
     this.missingTypes = previous.missingTypes;
     this.liveTypes = previous.liveTypes;
     this.instantiatedAppServices = previous.instantiatedAppServices;
-    this.instantiatedLambdas = previous.instantiatedLambdas;
     this.targetedMethods = previous.targetedMethods;
     this.failedResolutionTargets = previous.failedResolutionTargets;
     this.bootstrapMethods = previous.bootstrapMethods;
@@ -513,21 +509,39 @@
     previous.markObsolete();
   }
 
-  // TODO(b/150736225): Don't disable this assert.
-  private boolean dontAssertDefinitionFor = true;
-
   public static AppInfoWithLivenessModifier modifier() {
     return new AppInfoWithLivenessModifier();
   }
 
+  private boolean assertDefinitionFor = true;
+
+  public void disableDefinitionForAssert() {
+    assertDefinitionFor = false;
+  }
+
+  public void enableDefinitionForAssert() {
+    assertDefinitionFor = true;
+  }
+
   @Override
   public DexClass definitionFor(DexType type) {
     DexClass definition = super.definitionFor(type);
-    assert dontAssertDefinitionFor
-        || definition != null
-        || missingTypes.contains(type)
-        // TODO(b/149363884): Remove this exception once fixed.
-        || type.toDescriptorString().endsWith("$Builder;")
+    assert !assertDefinitionFor
+            || definition != null
+            || deadProtoTypes.contains(type)
+            || missingTypes.contains(type)
+            // TODO(b/150693139): Remove these exceptions once fixed.
+            || InterfaceMethodRewriter.isCompanionClassType(type)
+            || InterfaceMethodRewriter.hasDispatchClassSuffix(type)
+            || InterfaceMethodRewriter.isEmulatedLibraryClassType(type)
+            || type.toDescriptorString().startsWith("L$r8$backportedMethods$")
+            || type.toDescriptorString().startsWith("Lj$/$r8$backportedMethods$")
+            || type.toDescriptorString().startsWith("Lj$/$r8$retargetLibraryMember$")
+            || TwrCloseResourceRewriter.isUtilityClassDescriptor(type)
+            // TODO(b/150736225): Not sure how to remove these.
+            || DesugaredLibraryAPIConverter.isVivifiedType(type)
+            // TODO(b/149363884): Handle references to dead proto builders.
+            || type.toDescriptorString().endsWith("$Builder;")
         : "Failed lookup of non-missing type: " + type;
     return definition;
   }
@@ -561,7 +575,7 @@
     }
     for (DexCallSite callSite : callSites) {
       for (DexEncodedMethod method : lookupLambdaImplementedMethods(callSite)) {
-        worklist.add(method.method.holder);
+        worklist.add(method.holder());
       }
     }
     while (!worklist.isEmpty()) {
@@ -735,8 +749,9 @@
     return objectAllocationInfoCollection;
   }
 
-  ObjectAllocationInfoCollectionImpl getMutableObjectAllocationInfoCollection() {
-    return objectAllocationInfoCollection;
+  void mutateObjectAllocationInfoCollection(
+      Consumer<ObjectAllocationInfoCollectionImpl.Builder> mutator) {
+    objectAllocationInfoCollection.mutate(mutator, this);
   }
 
   void removeFromSingleTargetLookupCache(DexClass clazz) {
@@ -764,30 +779,14 @@
     assert checkIfObsolete();
     DexType type = clazz.type;
     return type.isD8R8SynthesizedClassType()
-        || objectAllocationInfoCollection.isInstantiatedDirectly(clazz)
+        || (!clazz.isInterface() && objectAllocationInfoCollection.isInstantiatedDirectly(clazz))
+        // TODO(b/145344105): Model annotations in the object allocation info.
         || (clazz.isAnnotation() && liveTypes.contains(type));
   }
 
   public boolean isInstantiatedIndirectly(DexProgramClass clazz) {
     assert checkIfObsolete();
-    if (hasAnyInstantiatedLambdas(clazz)) {
-      return true;
-    }
-    DexType type = clazz.type;
-    synchronized (indirectlyInstantiatedTypes) {
-      if (indirectlyInstantiatedTypes.containsKey(type)) {
-        return indirectlyInstantiatedTypes.get(type).booleanValue();
-      }
-      for (DexType directSubtype : allImmediateSubtypes(type)) {
-        DexProgramClass directSubClass = asProgramClassOrNull(definitionFor(directSubtype));
-        if (directSubClass == null || isInstantiatedDirectlyOrIndirectly(directSubClass)) {
-          indirectlyInstantiatedTypes.put(type, Boolean.TRUE);
-          return true;
-        }
-      }
-      indirectlyInstantiatedTypes.put(type, Boolean.FALSE);
-      return false;
-    }
+    return objectAllocationInfoCollection.hasInstantiatedStrictSubtype(clazz);
   }
 
   public boolean isInstantiatedDirectlyOrIndirectly(DexProgramClass clazz) {
@@ -855,16 +854,16 @@
     if (fieldAccessInfo == null || !fieldAccessInfo.isWritten()) {
       return false;
     }
-    DexType holder = field.field.holder;
+    DexType holder = field.holder();
     return fieldAccessInfo.isWrittenOnlyInMethodSatisfying(
-        method -> method.isInstanceInitializer() && method.method.holder == holder);
+        method -> method.isInstanceInitializer() && method.holder() == holder);
   }
 
   public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexEncodedField field) {
     assert checkIfObsolete();
     assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
     DexEncodedMethod staticInitializer =
-        definitionFor(field.field.holder).asProgramClass().getClassInitializer();
+        definitionFor(field.holder()).asProgramClass().getClassInitializer();
     return staticInitializer != null && isFieldOnlyWrittenInMethod(field, staticInitializer);
   }
 
@@ -876,7 +875,7 @@
   }
 
   private boolean isLibraryOrClasspathField(DexEncodedField field) {
-    DexClass holder = definitionFor(field.field.holder);
+    DexClass holder = definitionFor(field.holder());
     return holder == null || holder.isLibraryClass() || holder.isClasspathClass();
   }
 
@@ -905,9 +904,9 @@
   }
 
   @Override
-  public boolean hasAnyInstantiatedLambdas(DexProgramClass clazz) {
+  public boolean isInstantiatedInterface(DexProgramClass clazz) {
     assert checkIfObsolete();
-    return instantiatedLambdas.contains(clazz.type);
+    return objectAllocationInfoCollection.isInterfaceWithUnknownSubtypeHierarchy(clazz);
   }
 
   @Override
@@ -940,53 +939,6 @@
     return false;
   }
 
-  private boolean canVirtualMethodBeImplementedInExtraSubclass(
-      DexProgramClass clazz, DexMethod method) {
-    // For functional interfaces that are instantiated by lambdas, we may not have synthesized all
-    // the lambda classes yet, and therefore the set of subtypes for the holder may still be
-    // incomplete.
-    if (hasAnyInstantiatedLambdas(clazz)) {
-      return true;
-    }
-    // If `clazz` is kept and `method` is a library method or a library method override, then it is
-    // possible to create a class that inherits from `clazz` and overrides the library method.
-    // Similarly, if `clazz` is kept and `method` is kept directly on `clazz` or indirectly on one
-    // of its supertypes, then it is possible to create a class that inherits from `clazz` and
-    // overrides the kept method.
-    if (isPinned(clazz.type)) {
-      ResolutionResult resolutionResult = resolveMethod(clazz, method);
-      if (resolutionResult.isSingleResolution()) {
-        DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
-        return !resolutionTarget.isProgramMethod(this)
-            || resolutionTarget.isLibraryMethodOverride().isPossiblyTrue()
-            || isVirtualMethodPinnedDirectlyOrInAncestor(clazz, method);
-      }
-    }
-    return false;
-  }
-
-  private boolean isVirtualMethodPinnedDirectlyOrInAncestor(
-      DexProgramClass currentClass, DexMethod method) {
-    // Look in all ancestor types, including `currentClass` itself.
-    Set<DexProgramClass> visited = SetUtils.newIdentityHashSet(currentClass);
-    Deque<DexProgramClass> worklist = new ArrayDeque<>(visited);
-    while (!worklist.isEmpty()) {
-      DexClass clazz = worklist.removeFirst();
-      assert visited.contains(clazz);
-      DexEncodedMethod methodInClass = clazz.lookupVirtualMethod(method);
-      if (methodInClass != null && isPinned(methodInClass.method)) {
-        return true;
-      }
-      for (DexType superType : clazz.allImmediateSupertypes()) {
-        DexProgramClass superClass = asProgramClassOrNull(definitionFor(superType));
-        if (superClass != null && visited.add(superClass)) {
-          worklist.addLast(superClass);
-        }
-      }
-    }
-    return false;
-  }
-
   public Set<DexReference> getPinnedItems() {
     assert checkIfObsolete();
     return pinnedItems;
@@ -1001,6 +953,10 @@
       Collection<DexType> removedClasses,
       Collection<DexReference> additionalPinnedItems) {
     assert checkIfObsolete();
+    if (!removedClasses.isEmpty()) {
+      // Rebuild the hierarchy.
+      objectAllocationInfoCollection.mutate(mutator -> {}, this);
+    }
     return new AppInfoWithLiveness(this, application, removedClasses, additionalPinnedItems);
   }
 
@@ -1037,6 +993,7 @@
 
     return new AppInfoWithLiveness(
         application,
+        deadProtoTypes,
         missingTypes,
         rewriteItems(liveTypes, lens::lookupType),
         rewriteItems(instantiatedAppServices, lens::lookupType),
@@ -1079,7 +1036,6 @@
         prunedTypes,
         rewriteReferenceKeys(switchMaps, lens::lookupField),
         enumValueInfoMaps.rewrittenWithLens(lens),
-        rewriteItems(instantiatedLambdas, lens::lookupType),
         rewriteItems(constClassReferences, lens::lookupType),
         rewriteReferenceKeys(initClassReferences, lens::lookupType));
   }
@@ -1208,7 +1164,7 @@
     // TODO(b/148769279): Disable lookup single target on lambda's for now.
     if (resolvedHolder.isInterface()
         && resolvedHolder.isProgramClass()
-        && hasAnyInstantiatedLambdas(resolvedHolder.asProgramClass())) {
+        && isInstantiatedInterface(resolvedHolder.asProgramClass())) {
       singleTargetLookupCache.addToCache(refinedReceiverType, method, null);
       return null;
     }
@@ -1387,9 +1343,7 @@
   }
 
   private boolean isInstantiatedOrPinned(DexProgramClass clazz) {
-    return isInstantiatedDirectly(clazz)
-        || isPinned(clazz.type)
-        || hasAnyInstantiatedLambdas(clazz);
+    return isInstantiatedDirectly(clazz) || isPinned(clazz.type) || isInstantiatedInterface(clazz);
   }
 
   public boolean isPinnedNotProgramOrLibraryOverride(DexReference reference) {
@@ -1406,7 +1360,7 @@
       DexClass clazz = definitionFor(reference.asDexType());
       return clazz == null
           || clazz.isNotProgramClass()
-          || hasAnyInstantiatedLambdas(clazz.asProgramClass());
+          || isInstantiatedInterface(clazz.asProgramClass());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
index 99f2d84..0f1b348 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
-import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
@@ -34,12 +33,13 @@
 
   public void modify(AppInfoWithLiveness appInfo) {
     // Instantiated classes.
-    ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection =
-        appInfo.getMutableObjectAllocationInfoCollection();
-    noLongerInstantiatedClasses.forEach(
-        clazz -> {
-          objectAllocationInfoCollection.markNoLongerInstantiated(clazz);
-          appInfo.removeFromSingleTargetLookupCache(clazz);
+    appInfo.mutateObjectAllocationInfoCollection(
+        mutator -> {
+          noLongerInstantiatedClasses.forEach(
+              clazz -> {
+                mutator.markNoLongerInstantiated(clazz);
+                appInfo.removeFromSingleTargetLookupCache(clazz);
+              });
         });
     // Written fields.
     FieldAccessInfoCollectionImpl fieldAccessInfoCollection =
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index db955c9..c04ddf5 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -18,7 +18,7 @@
 public class DefaultEnqueuerUseRegistry extends UseRegistry {
 
   private final ProgramMethod context;
-  private final Enqueuer enqueuer;
+  protected final Enqueuer enqueuer;
 
   public DefaultEnqueuerUseRegistry(
       AppView<?> appView, DexProgramClass holder, DexEncodedMethod method, Enqueuer enqueuer) {
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 20dde1b..f636d05 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -220,6 +221,9 @@
    */
   private final Set<DexClass> liveNonProgramTypes = Sets.newIdentityHashSet();
 
+  /** Set of reachable proto types that will be dead code eliminated. */
+  private final Set<DexProgramClass> deadProtoTypeCandidates = Sets.newIdentityHashSet();
+
   /** Set of missing types. */
   private final Set<DexType> missingTypes = Sets.newIdentityHashSet();
 
@@ -411,26 +415,121 @@
     this.annotationRemoverBuilder = annotationRemoverBuilder;
   }
 
+  public void addDeadProtoTypeCandidate(DexType type) {
+    assert type.isProgramType(appView);
+    addDeadProtoTypeCandidate(appView.definitionFor(type).asProgramClass());
+  }
+
+  public void addDeadProtoTypeCandidate(DexProgramClass clazz) {
+    deadProtoTypeCandidates.add(clazz);
+  }
+
   private boolean isProgramClass(DexType type) {
     return getProgramClassOrNull(type) != null;
   }
 
+  private void recordReference(DexReference r) {
+    if (r.isDexType()) {
+      recordTypeReference(r.asDexType());
+    } else if (r.isDexField()) {
+      recordFieldReference(r.asDexField());
+    } else {
+      assert r.isDexMethod();
+      recordMethodReference(r.asDexMethod());
+    }
+  }
+
+  private void recordTypeReference(DexType type) {
+    if (type == null) {
+      return;
+    }
+    if (type.isArrayType()) {
+      type = type.toBaseType(appView.dexItemFactory());
+    }
+    if (!type.isClassType()) {
+      return;
+    }
+    // Lookup the definition, ignoring the result. This populates the missing and referenced sets.
+    definitionFor(type);
+  }
+
+  private void recordMethodReference(DexMethod method) {
+    recordTypeReference(method.holder);
+    recordTypeReference(method.proto.returnType);
+    for (DexType type : method.proto.parameters.values) {
+      recordTypeReference(type);
+    }
+  }
+
+  private void recordFieldReference(DexField field) {
+    recordTypeReference(field.holder);
+    recordTypeReference(field.type);
+  }
+
   private DexClass definitionFor(DexType type) {
     DexClass clazz = appView.definitionFor(type);
     if (clazz == null) {
       reportMissingClass(type);
       return null;
     }
-    if (clazz.isProgramClass()) {
-      return clazz;
+    if (clazz.isNotProgramClass()) {
+      addLiveNonProgramType(clazz);
     }
-    if (liveNonProgramTypes.add(clazz) && clazz.isLibraryClass()) {
+    return clazz;
+  }
+
+  private void addLiveNonProgramType(DexClass clazz) {
+    assert clazz.isNotProgramClass();
+    // Fast path to avoid the worklist when the class is already seen.
+    if (!liveNonProgramTypes.add(clazz)) {
+      return;
+    }
+    Deque<DexClass> worklist = new ArrayDeque<>();
+    worklist.addLast(clazz);
+    while (!worklist.isEmpty()) {
+      DexClass definition = worklist.removeFirst();
+      processNewLiveNonProgramType(definition, worklist);
+    }
+  }
+
+  private void processNewLiveNonProgramType(DexClass clazz, Deque<DexClass> worklist) {
+    assert clazz.isNotProgramClass();
+    if (clazz.isLibraryClass()) {
       // TODO(b/149201735): This likely needs to apply to classpath too.
       ensureMethodsContinueToWidenAccess(clazz);
       // Only libraries must not derive program. Classpath classes can, assuming correct keep rules.
       warnIfLibraryTypeInheritsFromProgramType(clazz.asLibraryClass());
     }
-    return clazz;
+    for (DexEncodedField field : clazz.fields()) {
+      addNonProgramClassToWorklist(field.field.type, worklist);
+    }
+    for (DexEncodedMethod method : clazz.methods()) {
+      addNonProgramClassToWorklist(method.method.proto.returnType, worklist);
+      for (DexType param : method.method.proto.parameters.values) {
+        addNonProgramClassToWorklist(param, worklist);
+      }
+    }
+    for (DexType supertype : clazz.allImmediateSupertypes()) {
+      addNonProgramClassToWorklist(supertype, worklist);
+    }
+  }
+
+  private void addNonProgramClassToWorklist(DexType type, Deque<DexClass> worklist) {
+    if (type.isArrayType()) {
+      type = type.toBaseType(appView.dexItemFactory());
+    }
+    if (!type.isClassType()) {
+      return;
+    }
+    DexClass definition = appView.definitionFor(type);
+    if (definition == null) {
+      reportMissingClass(type);
+      return;
+    }
+    if (definition.isProgramClass() || !liveNonProgramTypes.add(definition)) {
+      return;
+    }
+    worklist.addLast(definition);
   }
 
   private DexProgramClass getProgramClassOrNull(DexType type) {
@@ -495,7 +594,7 @@
       }
     } else if (item.isDexEncodedField()) {
       DexEncodedField dexEncodedField = item.asDexEncodedField();
-      DexProgramClass holder = getProgramClassOrNull(dexEncodedField.field.holder);
+      DexProgramClass holder = getProgramClassOrNull(dexEncodedField.holder());
       if (holder != null) {
         workList.enqueueMarkFieldKeptAction(
             holder,
@@ -504,7 +603,7 @@
       }
     } else if (item.isDexEncodedMethod()) {
       DexEncodedMethod encodedMethod = item.asDexEncodedMethod();
-      DexProgramClass holder = getProgramClassOrNull(encodedMethod.method.holder);
+      DexProgramClass holder = getProgramClassOrNull(encodedMethod.holder());
       if (holder != null) {
         workList.enqueueMarkMethodKeptAction(
             holder,
@@ -537,7 +636,7 @@
   // Utility to avoid adding to the worklist if already live.
   private boolean enqueueMarkMethodLiveAction(
       DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
-    assert method.method.holder == clazz.type;
+    assert method.holder() == clazz.type;
     if (liveMethods.add(clazz, method, reason)) {
       workList.enqueueMarkMethodLiveAction(clazz, method, reason);
       return true;
@@ -650,14 +749,12 @@
     }
 
     DexEncodedMethod contextMethod = context.getMethod();
-    markLambdaAsInstantiated(descriptor, contextMethod);
-    transitionMethodsForInstantiatedLambda(descriptor);
     if (lambdaRewriter != null) {
       assert contextMethod.getCode().isCfCode() : "Unexpected input type with lambdas";
       CfCode code = contextMethod.getCode().asCfCode();
       if (code != null) {
         LambdaClass lambdaClass =
-            lambdaRewriter.getOrCreateLambdaClass(descriptor, contextMethod.method.holder);
+            lambdaRewriter.getOrCreateLambdaClass(descriptor, contextMethod.holder());
         lambdaClasses.put(lambdaClass.type, new Pair<>(lambdaClass, contextMethod));
         lambdaCallSites
             .computeIfAbsent(contextMethod, k -> new IdentityHashMap<>())
@@ -670,6 +767,8 @@
         desugaredLambdaImplementationMethods.add(descriptor.implHandle.asMethod());
       }
     } else {
+      markLambdaAsInstantiated(descriptor, contextMethod);
+      transitionMethodsForInstantiatedLambda(descriptor);
       callSites.add(callSite);
     }
 
@@ -839,6 +938,7 @@
                 workList.enqueueTraceInvokeDirectAction(
                     invokedMethod, currentHolder, currentMethod));
     if (skipTracing) {
+      addDeadProtoTypeCandidate(invokedMethod.holder);
       return false;
     }
 
@@ -854,7 +954,10 @@
       return appView.withGeneratedMessageLiteBuilderShrinker(
           shrinker ->
               shrinker.deferDeadProtoBuilders(
-                  clazz, currentMethod, () -> liveTypes.registerDeferredAction(clazz, action)),
+                  clazz,
+                  currentMethod,
+                  () -> liveTypes.registerDeferredAction(clazz, action),
+                  this),
           false);
     }
     return false;
@@ -997,6 +1100,7 @@
         registerDeferredActionForDeadProtoBuilder(
             type, currentMethod, () -> workList.enqueueTraceNewInstanceAction(type, context));
     if (skipTracing) {
+      addDeadProtoTypeCandidate(type);
       return false;
     }
 
@@ -1059,7 +1163,7 @@
       fieldAccessInfoCollection.get(encodedField.field).setReadFromMethodHandle();
     }
 
-    DexProgramClass clazz = getProgramClassOrNull(encodedField.field.holder);
+    DexProgramClass clazz = getProgramClassOrNull(encodedField.holder());
     if (clazz == null) {
       return false;
     }
@@ -1108,7 +1212,7 @@
       fieldAccessInfoCollection.get(encodedField.field).setWrittenFromMethodHandle();
     }
 
-    DexProgramClass clazz = getProgramClassOrNull(encodedField.field.holder);
+    DexProgramClass clazz = getProgramClassOrNull(encodedField.holder());
     if (clazz == null) {
       return false;
     }
@@ -1156,7 +1260,8 @@
       fieldAccessInfoCollection.get(encodedField.field).setReadFromMethodHandle();
     }
 
-    if (!isProgramClass(encodedField.field.holder)) {
+    DexProgramClass holder = getProgramClassOrNull(encodedField.holder());
+    if (holder == null) {
       // No need to trace into the non-program code.
       return false;
     }
@@ -1174,6 +1279,7 @@
                       encodedField, fieldAccessInfoCollection, pinnedItems),
               false);
       if (skipTracing) {
+        addDeadProtoTypeCandidate(holder);
         return false;
       }
     }
@@ -1213,7 +1319,8 @@
       fieldAccessInfoCollection.get(encodedField.field).setWrittenFromMethodHandle();
     }
 
-    if (!isProgramClass(encodedField.field.holder)) {
+    DexProgramClass holder = getProgramClassOrNull(encodedField.holder());
+    if (holder == null) {
       // No need to trace into the non-program code.
       return false;
     }
@@ -1231,6 +1338,7 @@
                       encodedField, fieldAccessInfoCollection, pinnedItems),
               false);
       if (skipTracing) {
+        addDeadProtoTypeCandidate(holder);
         return false;
       }
     }
@@ -1255,7 +1363,7 @@
     if (methodHolderClass != null && methodHolderClass.isInterface()) {
       return method;
     }
-    DexClass holderClass = appView.definitionFor(currentMethod.method.holder);
+    DexClass holderClass = appView.definitionFor(currentMethod.holder());
     if (holderClass == null || holderClass.superType == null || holderClass.isInterface()) {
       // We do not know better or this call is made from an interface.
       return method;
@@ -1331,10 +1439,9 @@
     }
 
     // Mark types in inner-class attributes referenced.
-    InnerClassAttribute innerClassAttributes = holder.getInnerClassAttributeForThisClass();
-    if (innerClassAttributes != null) {
-      recordTypeReference(innerClassAttributes.getInner());
-      recordTypeReference(innerClassAttributes.getOuter());
+    for (InnerClassAttribute innerClassAttribute : holder.getInnerClasses()) {
+      recordTypeReference(innerClassAttribute.getInner());
+      recordTypeReference(innerClassAttribute.getOuter());
     }
 
     if (Log.ENABLED) {
@@ -1525,11 +1632,7 @@
 
   private SingleResolutionResult resolveMethod(DexMethod method, KeepReason reason) {
     // Record the references in case they are not program types.
-    recordTypeReference(method.holder);
-    recordTypeReference(method.proto.returnType);
-    for (DexType param : method.proto.parameters.values) {
-      recordTypeReference(param);
-    }
+    recordMethodReference(method);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     if (resolutionResult.isFailedResolution()) {
       reportMissingMethod(method);
@@ -1541,11 +1644,7 @@
   private SingleResolutionResult resolveMethod(
       DexMethod method, KeepReason reason, boolean interfaceInvoke) {
     // Record the references in case they are not program types.
-    recordTypeReference(method.holder);
-    recordTypeReference(method.proto.returnType);
-    for (DexType param : method.proto.parameters.values) {
-      recordTypeReference(param);
-    }
+    recordMethodReference(method);
     ResolutionResult resolutionResult =
         appInfo.resolveMethod(method.holder, method, interfaceInvoke);
     if (resolutionResult.isFailedResolution()) {
@@ -1617,10 +1716,7 @@
     DexType holder = method.holder;
     DexProgramClass clazz = getProgramClassOrNull(holder);
     if (clazz == null) {
-      recordTypeReference(method.proto.returnType);
-      for (DexType param : method.proto.parameters.values) {
-        recordTypeReference(param);
-      }
+      recordMethodReference(method);
       return;
     }
     // TODO(zerny): Is it ok that we lookup in both the direct and virtual pool here?
@@ -1717,7 +1813,7 @@
 
   private void markMethodAsTargeted(
       DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
-    assert method.method.holder == clazz.type;
+    assert method.holder() == clazz.type;
     if (!targetedMethods.add(method, reason)) {
       // Already targeted.
       return;
@@ -1792,7 +1888,7 @@
   void markInterfaceAsInstantiated(DexProgramClass clazz, KeepReasonWitness witness) {
     assert !clazz.isAnnotation();
     assert clazz.isInterface();
-    if (!objectAllocationInfoCollection.recordInstantiatedInterface(clazz)) {
+    if (!objectAllocationInfoCollection.recordInstantiatedInterface(clazz, appInfo)) {
       return;
     }
     markTypeAsLive(clazz, witness);
@@ -1804,12 +1900,11 @@
     for (DexType iface : descriptor.interfaces) {
       checkLambdaInterface(iface, context);
       objectAllocationInfoCollection.recordInstantiatedLambdaInterface(iface, descriptor, appInfo);
-      // TODO(b/150277553): Lambdas should be accurately traces and thus not be added here.
-      if (lambdaRewriter == null) {
-        DexProgramClass clazz = getProgramClassOrNull(iface);
-        if (clazz != null) {
-          objectAllocationInfoCollection.recordInstantiatedInterface(clazz);
-        }
+      // TODO(b/150277553): Lambdas should be accurately traced and thus not be added here.
+      assert lambdaRewriter == null;
+      DexProgramClass clazz = getProgramClassOrNull(iface);
+      if (clazz != null) {
+        objectAllocationInfoCollection.recordInstantiatedInterface(clazz, appInfo);
       }
     }
   }
@@ -1820,7 +1915,7 @@
       StringDiagnostic message =
           new StringDiagnostic(
               "Lambda expression implements missing interface `" + itf.toSourceString() + "`",
-              appInfo.originFor(context.method.holder));
+              appInfo.originFor(context.holder()));
       options.reporter.warning(message);
     } else if (!clazz.isInterface()) {
       StringDiagnostic message =
@@ -1829,7 +1924,7 @@
                   + "`"
                   + itf.toSourceString()
                   + "`",
-              appInfo.originFor(context.method.holder));
+              appInfo.originFor(context.holder()));
       options.reporter.warning(message);
     }
   }
@@ -1969,7 +2064,7 @@
           && appView.rewritePrefix.hasRewrittenTypeInSignature(method.method.proto, appView)) {
         DexMethod methodToResolve =
             DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
-                method.method, method.method.holder, appView);
+                method.method, method.holder(), appView);
         assert methodToResolve != method.method;
         markLibraryOrClasspathOverrideLive(
             instantiation,
@@ -2124,7 +2219,7 @@
       DexProgramClass holder, DexEncodedField field, KeepReason reason) {
     assert field != null;
     assert field.isProgramField(appView);
-    markTypeAsLive(field.field.holder, reason);
+    markTypeAsLive(field.holder(), reason);
     markTypeAsLive(field.field.type, reason);
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Adding instance field `%s` to live set.", field.field);
@@ -2141,7 +2236,7 @@
 
   private void markDirectStaticOrConstructorMethodAsLive(
       DexProgramClass clazz, DexEncodedMethod encodedMethod, KeepReason reason) {
-    assert encodedMethod.method.holder == clazz.type;
+    assert encodedMethod.holder() == clazz.type;
 
     if (!enqueueMarkMethodLiveAction(clazz, encodedMethod, reason)) {
       // Already marked live.
@@ -2196,7 +2291,7 @@
     if (info == null) {
       return false;
     }
-    DexClass clazz = appView.definitionFor(field.field.holder);
+    DexClass clazz = appView.definitionFor(field.holder());
     DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
     return defaultInitializer != null
         ? info.isWrittenOutside(defaultInitializer)
@@ -2211,10 +2306,21 @@
     return targetedMethods.contains(method);
   }
 
+  public boolean isTypeLive(DexClass clazz) {
+    return clazz.isProgramClass()
+        ? isTypeLive(clazz.asProgramClass())
+        : isNonProgramTypeLive(clazz);
+  }
+
   public boolean isTypeLive(DexProgramClass clazz) {
     return liveTypes.contains(clazz);
   }
 
+  public boolean isNonProgramTypeLive(DexClass clazz) {
+    assert !clazz.isProgramClass();
+    return liveNonProgramTypes.contains(clazz);
+  }
+
   // Package protected due to entry point from worklist.
   void markInstanceFieldAsReachable(DexEncodedField encodedField, KeepReason reason) {
     DexField field = encodedField.field;
@@ -2250,19 +2356,6 @@
     }
   }
 
-  private void recordTypeReference(DexType type) {
-    if (type == null) {
-      return;
-    }
-    if (type.isArrayType()) {
-      type = type.toBaseType(appView.dexItemFactory());
-    }
-    if (!type.isClassType()) {
-      return;
-    }
-    getProgramClassOrNull(type);
-  }
-
   private void markVirtualMethodAsReachable(
       DexMethod method, boolean interfaceInvoke, ProgramMethod contextOrNull, KeepReason reason) {
     if (method.holder.isArrayType()) {
@@ -2281,10 +2374,7 @@
     if (holder == null) {
       // TODO(b/139464956): clean this.
       // Ensure that the full proto of the targeted method is referenced.
-      recordTypeReference(method.proto.returnType);
-      for (DexType type : method.proto.parameters.values) {
-        recordTypeReference(type);
-      }
+      recordMethodReference(method);
       return;
     }
 
@@ -2385,7 +2475,7 @@
     failedResolutionTargets.add(symbolicMethod);
     failedResolution.forEachFailureDependency(
         method -> {
-          DexProgramClass clazz = getProgramClassOrNull(method.method.holder);
+          DexProgramClass clazz = getProgramClassOrNull(method.holder());
           if (clazz != null) {
             failedResolutionTargets.add(method.method);
             markMethodAsTargeted(clazz, method, reason);
@@ -2430,14 +2520,14 @@
     }
     // If invoke target is invalid (inaccessible or not an instance-method) record it and stop.
     // TODO(b/146016987): We should be passing the full program context and not looking it up again.
-    DexProgramClass fromHolder = appInfo.definitionFor(from.method.holder).asProgramClass();
+    DexProgramClass fromHolder = appInfo.definitionFor(from.holder()).asProgramClass();
     DexEncodedMethod target = resolution.lookupInvokeSuperTarget(fromHolder, appInfo);
     if (target == null) {
       failedResolutionTargets.add(resolution.getResolvedMethod().method);
       return;
     }
 
-    DexProgramClass clazz = getProgramClassOrNull(target.method.holder);
+    DexProgramClass clazz = getProgramClassOrNull(target.holder());
     if (clazz == null) {
       return;
     }
@@ -2645,7 +2735,7 @@
       // Ensure accessors if needed and mark them live too.
       DexEncodedMethod accessor = lambdaClass.target.ensureAccessibilityIfNeeded(false);
       if (accessor != null) {
-        DexProgramClass clazz = getProgramClassOrNull(accessor.method.holder);
+        DexProgramClass clazz = getProgramClassOrNull(accessor.holder());
         additions.addLiveMethod(new ProgramMethod(clazz, accessor));
       }
     }
@@ -2684,50 +2774,52 @@
   }
 
   private AppInfoWithLiveness createAppInfo(AppInfoWithSubtyping appInfo) {
+    // Compute the set of dead proto types.
+    deadProtoTypeCandidates.removeIf(this::isTypeLive);
+
     // Remove the temporary mappings that have been inserted into the field access info collection
     // and verify that the mapping is then one-to-one.
     fieldAccessInfoCollection.removeIf(
         (field, info) -> field != info.getField() || info == MISSING_FIELD_ACCESS_INFO);
     assert fieldAccessInfoCollection.verifyMappingIsOneToOne();
 
-    // Ensure references from various root set collections.
-    rootSet
-        .noSideEffects
-        .keySet()
-        .forEach(
-            r -> {
-              if (r.isDexType()) {
-                recordTypeReference(r.asDexType());
-              } else if (r.isDexField()) {
-                recordTypeReference(r.asDexField().holder);
-                recordTypeReference(r.asDexField().type);
-              } else {
-                assert r.isDexMethod();
-                recordTypeReference(r.asDexMethod().holder);
-                recordTypeReference(r.asDexMethod().proto.returnType);
-                for (DexType param : r.asDexMethod().proto.parameters.values) {
-                  recordTypeReference(param);
-                }
-              }
-            });
+    // Verify all references on the input app before synthesizing definitions.
+    assert verifyReferences(appInfo.app());
+
+    // Prune the root set items that turned out to be dead.
+    // TODO(b/150736225): Pruning of dead root set items is still incomplete.
+    rootSet.pruneDeadItems(appView, this);
+
+    // Ensure references from all hard coded factory items.
+    appView.dexItemFactory().forEachPossiblyCompilerSynthesizedType(this::recordTypeReference);
 
     // Rebuild a new app only containing referenced types.
-    appView.dexItemFactory().forEachPossiblyCompilerSynthesizedType(this::recordTypeReference);
     Set<DexLibraryClass> libraryClasses = Sets.newIdentityHashSet();
     Set<DexClasspathClass> classpathClasses = Sets.newIdentityHashSet();
     for (DexClass clazz : liveNonProgramTypes) {
-      traverseHierarchy(clazz, libraryClasses, classpathClasses);
+      if (clazz.isLibraryClass()) {
+        libraryClasses.add(clazz.asLibraryClass());
+      } else if (clazz.isClasspathClass()) {
+        classpathClasses.add(clazz.asClasspathClass());
+      } else {
+        assert false;
+      }
     }
+
+    // Add just referenced non-program types. We can't replace the program classes at this point as
+    // they are needed in tree pruning.
     Builder appBuilder = appInfo.app().asDirect().builder();
     appBuilder.replaceLibraryClasses(libraryClasses);
     appBuilder.replaceClasspathClasses(classpathClasses);
-    // Can't replace the program classes at this point as they are needed in tree pruning.
-    // Post process the app to add synthetic content.
     DirectMappedDexApplication app = appBuilder.build();
 
+    // Verify the references on the pruned application after type synthesis.
+    assert verifyReferences(app);
+
     AppInfoWithLiveness appInfoWithLiveness =
         new AppInfoWithLiveness(
             app,
+            SetUtils.mapIdentityHashSet(deadProtoTypeCandidates, DexProgramClass::getType),
             missingTypes,
             SetUtils.mapIdentityHashSet(liveTypes.getItems(), DexProgramClass::getType),
             Collections.unmodifiableSet(instantiatedAppServices),
@@ -2740,7 +2832,7 @@
             toSortedDescriptorSet(liveMethods.getItems()),
             // Filter out library fields and pinned fields, because these are read by default.
             fieldAccessInfoCollection,
-            objectAllocationInfoCollection.build(),
+            objectAllocationInfoCollection.build(appInfo),
             // TODO(b/132593519): Do we require these sets to be sorted for determinism?
             toImmutableSortedMap(virtualInvokes, PresortedComparable::slowCompare),
             toImmutableSortedMap(interfaceInvokes, PresortedComparable::slowCompare),
@@ -2768,54 +2860,72 @@
             Collections.emptySet(),
             Collections.emptyMap(),
             EnumValueInfoMapCollection.empty(),
-            // TODO(b/150277553): Remove this once object allocation contains the information.
-            SetUtils.mapIdentityHashSet(
-                objectAllocationInfoCollection.unknownInstantiatedInterfaceTypes,
-                DexProgramClass::getType),
             constClassReferences,
             initClassReferences);
     appInfo.markObsolete();
     return appInfoWithLiveness;
   }
 
-  private void traverseHierarchy(
-      DexClass clazz,
-      Set<DexLibraryClass> libraryClasses,
-      Set<DexClasspathClass> classpathClasses) {
-    if (clazz.isLibraryClass()) {
-      libraryClasses.add(clazz.asLibraryClass());
-    } else if (clazz.isClasspathClass()) {
-      classpathClasses.add(clazz.asClasspathClass());
+  private boolean verifyReferences(DexApplication app) {
+    WorkList<DexClass> worklist = WorkList.newIdentityWorkList();
+    for (DexProgramClass clazz : liveTypes.getItems()) {
+      worklist.addIfNotSeen(clazz);
     }
-    Deque<DexType> worklist = new ArrayDeque<>();
-    if (clazz.superType != null) {
-      worklist.add(clazz.superType);
+    while (worklist.hasNext()) {
+      DexClass clazz = worklist.next();
+      assert verifyReferencedType(clazz, worklist, app);
     }
-    Collections.addAll(worklist, clazz.interfaces.values);
-    while (!worklist.isEmpty()) {
-      DexType type = worklist.pop();
-      DexClass definition = appView.definitionFor(type);
-      if (definition == null) {
-        continue;
-      }
-      if (definition.isProgramClass()) {
-        // TODO(b/120884788): This should assert not possible once fixed.
-        continue;
-      }
-      if (definition.isLibraryClass()) {
-        if (!libraryClasses.add(definition.asLibraryClass())) {
-          continue;
-        }
-      } else if (definition.isClasspathClass()) {
-        if (!classpathClasses.add(definition.asClasspathClass())) {
-          continue;
-        }
-      }
-      if (definition.superType != null) {
-        worklist.add(definition.superType);
-      }
-      Collections.addAll(worklist, definition.interfaces.values);
+    return true;
+  }
+
+  private boolean verifyReferencedType(
+      DexType type, WorkList<DexClass> worklist, DexApplication app) {
+    if (type.isArrayType()) {
+      type = type.toBaseType(appView.dexItemFactory());
     }
+    if (!type.isClassType()) {
+      return true;
+    }
+    DexClass clazz = app.definitionFor(type);
+    if (clazz == null) {
+      assert missingTypes.contains(type) : "Expected type to be in missing types': " + type;
+    } else {
+      assert !missingTypes.contains(type) : "Type with definition also in missing types: " + type;
+      // Eager assert while the context is still present.
+      assert clazz.isProgramClass() || liveNonProgramTypes.contains(clazz)
+          : "Expected type to be in live non-program types: " + clazz;
+      worklist.addIfNotSeen(clazz);
+    }
+    return true;
+  }
+
+  private boolean verifyReferencedType(
+      DexClass clazz, WorkList<DexClass> worklist, DexApplication app) {
+    for (DexType supertype : clazz.allImmediateSupertypes()) {
+      assert verifyReferencedType(supertype, worklist, app);
+    }
+    assert clazz.isProgramClass() || liveNonProgramTypes.contains(clazz)
+        : "Expected type to be in live non-program types: " + clazz;
+    for (DexEncodedField field : clazz.fields()) {
+      if (clazz.isNotProgramClass() || isFieldReferenced(field)) {
+        assert verifyReferencedType(field.field.type, worklist, app);
+      }
+    }
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (clazz.isNotProgramClass() || isMethodTargeted(method)) {
+        assert verifyReferencedMethod(method, worklist, app);
+      }
+    }
+    return true;
+  }
+
+  private boolean verifyReferencedMethod(
+      DexEncodedMethod method, WorkList<DexClass> worklist, DexApplication app) {
+    assert verifyReferencedType(method.method.proto.returnType, worklist, app);
+    for (DexType param : method.method.proto.parameters.values) {
+      assert verifyReferencedType(param, worklist, app);
+    }
+    return true;
   }
 
   private void synthesizeLibraryConversionWrappers(SyntheticAdditions additions) {
@@ -2826,7 +2936,7 @@
     // Generate first the callbacks since they may require extra wrappers.
     List<DexEncodedMethod> callbacks = desugaredLibraryWrapperAnalysis.generateCallbackMethods();
     for (DexEncodedMethod callback : callbacks) {
-      DexProgramClass clazz = getProgramClassOrNull(callback.method.holder);
+      DexProgramClass clazz = getProgramClassOrNull(callback.holder());
       additions.addLiveMethod(new ProgramMethod(clazz, callback));
     }
 
@@ -3111,7 +3221,7 @@
         } else {
           DexEncodedMethod implementation = target.getDefaultInterfaceMethodImplementation();
           if (implementation != null) {
-            DexProgramClass companion = getProgramClassOrNull(implementation.method.holder);
+            DexProgramClass companion = getProgramClassOrNull(implementation.holder());
             markTypeAsLive(companion, graphReporter.reportCompanionClass(holder, companion));
             markVirtualMethodAsLive(
                 companion,
@@ -3128,7 +3238,7 @@
 
   // Package protected due to entry point from worklist.
   void markFieldAsKept(DexProgramClass holder, DexEncodedField target, KeepReason reason) {
-    assert holder.type == target.field.holder;
+    assert holder.type == target.holder();
     if (target.accessFlags.isStatic()) {
       markStaticFieldAsLive(target, reason);
     } else {
@@ -3206,7 +3316,7 @@
   void markMethodAsLive(DexEncodedMethod method, KeepReason reason) {
     assert liveMethods.contains(method);
 
-    DexProgramClass clazz = getProgramClassOrNull(method.method.holder);
+    DexProgramClass clazz = getProgramClassOrNull(method.holder());
     if (clazz == null) {
       return;
     }
@@ -3221,7 +3331,7 @@
         if (Log.ENABLED) {
           Log.verbose(getClass(), "Found super invoke constraint on `%s`.", superCallTarget.method);
         }
-        DexProgramClass targetClass = getProgramClassOrNull(superCallTarget.method.holder);
+        DexProgramClass targetClass = getProgramClassOrNull(superCallTarget.holder());
         assert targetClass != null;
         if (targetClass != null) {
           markMethodAsTargeted(
@@ -3246,7 +3356,7 @@
 
   private void markReferencedTypesAsLive(DexEncodedMethod method) {
     markTypeAsLive(
-        method.method.holder, clazz -> graphReporter.reportClassReferencedFrom(clazz, method));
+        method.holder(), clazz -> graphReporter.reportClassReferencedFrom(clazz, method));
     markParameterAndReturnTypesAsLive(method);
   }
 
@@ -3290,7 +3400,7 @@
   }
 
   private void handleReflectiveBehavior(DexEncodedMethod method) {
-    DexType originHolder = method.method.holder;
+    DexType originHolder = method.holder();
     Origin origin = appInfo.originFor(originHolder);
     IRCode code = method.buildIR(appView, origin);
     InstructionIterator iterator = code.instructionIterator();
@@ -3578,7 +3688,7 @@
       DexType type = invoke.inValues().get(0).definition.asConstClass().getValue();
       DexProgramClass clazz = getProgramClassOrNull(type);
       if (clazz != null && clazz.accessFlags.isEnum()) {
-        DexProgramClass holder = getProgramClassOrNull(method.method.holder);
+        DexProgramClass holder = getProgramClassOrNull(method.holder());
         markEnumValuesAsReachable(clazz, KeepReason.invokedFrom(holder, method));
       }
     }
@@ -3603,7 +3713,7 @@
                       + "` is being passed to the method `"
                       + invoke.getInvokedMethod().toSourceString()
                       + "`, but was not found in `META-INF/services/`.",
-                  appInfo.originFor(method.method.holder)));
+                  appInfo.originFor(method.holder())));
         }
         return;
       }
@@ -3772,10 +3882,7 @@
     @Override
     public boolean addMethod(DexMethod method) {
       // Record the references in case they are not program types.
-      recordTypeReference(method.proto.returnType);
-      for (DexType param : method.proto.parameters.values) {
-        recordTypeReference(param);
-      }
+      recordMethodReference(method);
       DexProgramClass holder = getProgramClassOrNull(method.holder);
       if (holder == null) {
         return false;
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 c795bcc..2a46ba3 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -264,7 +264,7 @@
 
   public void enqueueMarkReachableFieldAction(
       DexProgramClass clazz, DexEncodedField field, KeepReason reason) {
-    assert field.field.holder == clazz.type;
+    assert field.holder() == clazz.type;
     queue.add(new MarkReachableFieldAction(field, reason));
   }
 
@@ -294,13 +294,13 @@
 
   void enqueueMarkMethodLiveAction(
       DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
-    assert method.method.holder == clazz.type;
+    assert method.holder() == clazz.type;
     queue.add(new MarkMethodLiveAction(method, reason));
   }
 
   void enqueueMarkMethodKeptAction(
       DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
-    assert method.method.holder == clazz.type;
+    assert method.holder() == clazz.type;
     queue.add(new MarkMethodKeptAction(clazz, method, reason));
   }
 
@@ -317,7 +317,7 @@
 
   public void enqueueTraceInvokeDirectAction(
       DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
-    assert currentMethod.method.holder == currentHolder.type;
+    assert currentMethod.holder() == currentHolder.type;
     queue.add(new TraceInvokeDirectAction(invokedMethod, currentHolder, currentMethod));
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index cf863de..6b222d7 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -171,7 +171,7 @@
 
   public KeepReasonWitness reportCompatKeepDefaultInitializer(
       DexProgramClass holder, DexEncodedMethod defaultInitializer) {
-    assert holder.type == defaultInitializer.method.holder;
+    assert holder.type == defaultInitializer.holder();
     assert holder.getDefaultInitializer() == defaultInitializer;
     if (keptGraphConsumer != null) {
       reportEdge(
@@ -183,7 +183,7 @@
   }
 
   public KeepReasonWitness reportCompatKeepMethod(DexProgramClass holder, DexEncodedMethod method) {
-    assert holder.type == method.method.holder;
+    assert holder.type == method.holder();
     // TODO(b/141729349): This compat rule is from the method to itself and has not edge. Fix it.
     // The rule is stating that if the method is targeted it is live. Since such an edge does
     // not contribute to additional information in the kept graph as it stands (no distinction
@@ -234,7 +234,7 @@
   public KeepReasonWitness reportReachableClassInitializer(
       DexProgramClass clazz, DexEncodedMethod initializer) {
     if (initializer != null) {
-      assert clazz.type == initializer.method.holder;
+      assert clazz.type == initializer.holder();
       assert initializer.isClassInitializer();
       if (keptGraphConsumer != null) {
         ClassGraphNode source = getClassGraphNode(clazz.type);
@@ -284,7 +284,7 @@
 
   public KeepReasonWitness reportCompanionMethod(
       DexEncodedMethod definition, DexEncodedMethod implementation) {
-    assert InterfaceMethodRewriter.isCompanionClassType(implementation.method.holder);
+    assert InterfaceMethodRewriter.isCompanionClassType(implementation.holder());
     if (keptGraphConsumer == null) {
       return KeepReasonWitness.INSTANCE;
     }
@@ -359,7 +359,7 @@
     if (skipReporting(reason)) {
       return KeepReasonWitness.INSTANCE;
     }
-    if (reason.edgeKind() == EdgeKind.IsLibraryMethod && isNonProgramClass(method.method.holder)) {
+    if (reason.edgeKind() == EdgeKind.IsLibraryMethod && isNonProgramClass(method.holder())) {
       // Don't report edges to actual library methods.
       // TODO(b/120959039): This should be dead code once no library classes are ever enqueued.
       return KeepReasonWitness.INSTANCE;
@@ -415,6 +415,11 @@
     return reasonInfo.computeIfAbsent(kind, k -> new GraphEdgeInfo(k));
   }
 
+  private DexClass definitionFor(DexType type) {
+    // The query of the graph can be outside program referenced types and should not fail.
+    return appView.appInfo().definitionForWithoutExistenceAssert(type);
+  }
+
   AnnotationGraphNode getAnnotationGraphNode(DexItem type) {
     return annotationNodes.computeIfAbsent(
         type,
@@ -431,7 +436,7 @@
     return classNodes.computeIfAbsent(
         type,
         t -> {
-          DexClass definition = appView.definitionFor(t);
+          DexClass definition = definitionFor(t);
           return new ClassGraphNode(
               definition != null && definition.isNotProgramClass(),
               Reference.classFromDescriptor(t.toDescriptorString()));
@@ -442,7 +447,7 @@
     return methodNodes.computeIfAbsent(
         context,
         m -> {
-          DexClass holderDefinition = appView.definitionFor(context.holder);
+          DexClass holderDefinition = definitionFor(context.holder);
           Builder<TypeReference> builder = ImmutableList.builder();
           for (DexType param : m.proto.parameters.values) {
             builder.add(Reference.typeFromDescriptor(param.toDescriptorString()));
@@ -463,7 +468,7 @@
     return fieldNodes.computeIfAbsent(
         context,
         f -> {
-          DexClass holderDefinition = appView.definitionFor(context.holder);
+          DexClass holderDefinition = definitionFor(context.holder);
           return new FieldGraphNode(
               holderDefinition != null && holderDefinition.isNotProgramClass(),
               Reference.field(
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index c66d4a4..dd76e19 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -152,7 +152,7 @@
 
     private InvokedFrom(DexProgramClass holder, DexEncodedMethod method) {
       super(method);
-      assert holder.type == method.method.holder;
+      assert holder.type == method.holder();
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 7e9ed91..5b1f672 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -473,13 +473,21 @@
           configurationBuilder.addRule(rule);
           return true;
         }
+        if (acceptString("neverreprocessclassinitializer")) {
+          configurationBuilder.addRule(
+              parseReprocessClassInitializerRule(
+                  ReprocessClassInitializerRule.Type.NEVER, optionStart));
+          return true;
+        }
         if (acceptString("neverreprocessmethod")) {
           configurationBuilder.addRule(
               parseReprocessMethodRule(ReprocessMethodRule.Type.NEVER, optionStart));
           return true;
         }
         if (acceptString("reprocessclassinitializer")) {
-          configurationBuilder.addRule(parseReprocessClassInitializerRule(optionStart));
+          configurationBuilder.addRule(
+              parseReprocessClassInitializerRule(
+                  ReprocessClassInitializerRule.Type.ALWAYS, optionStart));
           return true;
         }
         if (acceptString("reprocessmethod")) {
@@ -822,10 +830,11 @@
       return keepRuleBuilder.build();
     }
 
-    private ReprocessClassInitializerRule parseReprocessClassInitializerRule(Position start)
+    private ReprocessClassInitializerRule parseReprocessClassInitializerRule(
+        ReprocessClassInitializerRule.Type type, Position start)
         throws ProguardRuleParserException {
       ReprocessClassInitializerRule.Builder builder =
-          ReprocessClassInitializerRule.builder().setOrigin(origin).setStart(start);
+          ReprocessClassInitializerRule.builder().setOrigin(origin).setStart(start).setType(type);
       parseClassSpec(builder, false);
       Position end = getPosition();
       builder.setSource(getSourceSnippet(contents, start, end));
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index 41aa9c5..d59c63e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -65,6 +65,14 @@
     return null;
   }
 
+  public boolean isReprocessClassInitializerRule() {
+    return false;
+  }
+
+  public ReprocessClassInitializerRule asReprocessClassInitializerRule() {
+    return null;
+  }
+
   public boolean isReprocessMethodRule() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
index f75a307..590ede0 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -57,7 +57,7 @@
   public static ProguardKeepRule buildMethodKeepRule(DexClass clazz, DexEncodedMethod method) {
     // TODO(b/122295241): These generated rules should be linked into the graph, eg, the method
     // using identified reflection should be the source keeping the target alive.
-    assert clazz.type == method.method.holder;
+    assert clazz.type == method.holder();
     ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
     builder.setOrigin(proguardCompatOrigin);
     builder.setType(ProguardKeepRuleType.KEEP_CLASS_MEMBERS);
diff --git a/src/main/java/com/android/tools/r8/shaking/ReprocessClassInitializerRule.java b/src/main/java/com/android/tools/r8/shaking/ReprocessClassInitializerRule.java
index fa7d352..58e03d9 100644
--- a/src/main/java/com/android/tools/r8/shaking/ReprocessClassInitializerRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ReprocessClassInitializerRule.java
@@ -3,19 +3,32 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import java.util.List;
 
 public class ReprocessClassInitializerRule extends ProguardConfigurationRule {
 
+  public enum Type {
+    ALWAYS,
+    NEVER
+  }
+
   public static class Builder
       extends ProguardConfigurationRule.Builder<ReprocessClassInitializerRule, Builder> {
 
+    private Type type;
+
     private Builder() {
       super();
     }
 
+    public Builder setType(Type type) {
+      this.type = type;
+      return this;
+    }
+
     @Override
     public Builder self() {
       return this;
@@ -36,10 +49,13 @@
           inheritanceAnnotation,
           inheritanceClassName,
           inheritanceIsExtends,
-          memberRules);
+          memberRules,
+          type);
     }
   }
 
+  private final Type type;
+
   private ReprocessClassInitializerRule(
       Origin origin,
       Position position,
@@ -53,7 +69,8 @@
       ProguardTypeMatcher inheritanceAnnotation,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
-      List<ProguardMemberRule> memberRules) {
+      List<ProguardMemberRule> memberRules,
+      Type type) {
     super(
         origin,
         position,
@@ -68,14 +85,36 @@
         inheritanceClassName,
         inheritanceIsExtends,
         memberRules);
+    this.type = type;
   }
 
   public static Builder builder() {
     return new Builder();
   }
 
+  public Type getType() {
+    return type;
+  }
+
+  @Override
+  public boolean isReprocessClassInitializerRule() {
+    return true;
+  }
+
+  @Override
+  public ReprocessClassInitializerRule asReprocessClassInitializerRule() {
+    return this;
+  }
+
   @Override
   String typeString() {
-    return "reprocessclassinitializer";
+    switch (type) {
+      case ALWAYS:
+        return "reprocessclassinitializer";
+      case NEVER:
+        return "neverreprocessclassinitializer";
+      default:
+        throw new Unreachable();
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 928de3a..c89dd04 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.shaking.ReprocessClassInitializerRule.Type.ALWAYS;
+import static com.android.tools.r8.shaking.ReprocessClassInitializerRule.Type.NEVER;
+
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
@@ -14,6 +17,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -377,7 +381,7 @@
       DexEncodedMethod target =
           appView.appInfo().resolveMethod(subType, referenceInSubType).getSingleTarget();
       // But, the resolution should not be landed on the current type we are visiting.
-      if (target == null || target.method.holder == type) {
+      if (target == null || target.holder() == type) {
         continue;
       }
       ProguardMemberRule ruleInSubType = assumeRulePool.get(target.method);
@@ -598,7 +602,7 @@
 
   private boolean canInsertForwardingMethod(DexClass holder, DexEncodedMethod target) {
     return appView.options().isGeneratingDex()
-        || ArrayUtils.contains(holder.interfaces.values, target.method.holder);
+        || ArrayUtils.contains(holder.interfaces.values, target.holder());
   }
 
   private void markMatchingOverriddenMethods(
@@ -1056,7 +1060,7 @@
         if (options.isInterfaceMethodDesugaringEnabled()
             && encodedMethod.hasCode()
             && (encodedMethod.isPrivateMethod() || encodedMethod.isStaticMember())) {
-          DexClass holder = appView.definitionFor(encodedMethod.method.holder);
+          DexClass holder = appView.definitionFor(encodedMethod.holder());
           if (holder != null && holder.isInterface()) {
             if (rule.isSpecific()) {
               options.reporter.warning(
@@ -1219,7 +1223,16 @@
     } else if (context instanceof ReprocessClassInitializerRule) {
       DexProgramClass clazz = item.asProgramClass();
       if (clazz != null && clazz.hasClassInitializer()) {
-        reprocess.add(clazz.getClassInitializer().method);
+        switch (context.asReprocessClassInitializerRule().getType()) {
+          case ALWAYS:
+            reprocess.add(clazz.getClassInitializer().method);
+            break;
+          case NEVER:
+            neverReprocess.add(clazz.getClassInitializer().method);
+            break;
+          default:
+            throw new Unreachable();
+        }
         context.markAsUsed();
       }
     } else if (context.isReprocessMethodRule()) {
@@ -1306,8 +1319,8 @@
       this.noObfuscation = noObfuscation;
       this.reasonAsked = reasonAsked;
       this.checkDiscarded = checkDiscarded;
-      this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
-      this.forceInline = Collections.unmodifiableSet(forceInline);
+      this.alwaysInline = alwaysInline;
+      this.forceInline = forceInline;
       this.neverInline = neverInline;
       this.bypassClinitForInlining = bypassClinitForInlining;
       this.whyAreYouNotInlining = whyAreYouNotInlining;
@@ -1317,7 +1330,7 @@
       this.neverReprocess = neverReprocess;
       this.alwaysClassInline = alwaysClassInline;
       this.neverClassInline = neverClassInline;
-      this.neverMerge = Collections.unmodifiableSet(neverMerge);
+      this.neverMerge = neverMerge;
       this.neverPropagateValue = neverPropagateValue;
       this.mayHaveSideEffects = mayHaveSideEffects;
       this.noSideEffects = noSideEffects;
@@ -1448,6 +1461,45 @@
       assumedValues.remove(reference);
     }
 
+    public void pruneDeadItems(DexDefinitionSupplier definitions, Enqueuer enqueuer) {
+      pruneDeadReferences(neverMerge, definitions, enqueuer);
+      pruneDeadReferences(alwaysInline, definitions, enqueuer);
+      pruneDeadReferences(noSideEffects.keySet(), definitions, enqueuer);
+    }
+
+    private static void pruneDeadReferences(
+        Set<? extends DexReference> references,
+        DexDefinitionSupplier definitions,
+        Enqueuer enqueuer) {
+      references.removeIf(
+          reference -> {
+            if (reference.isDexField()) {
+              DexEncodedField definition = definitions.definitionFor(reference.asDexField());
+              if (definition == null) {
+                return true;
+              }
+              DexClass holder = definitions.definitionFor(definition.holder());
+              if (holder.isProgramClass()) {
+                return !enqueuer.isFieldReferenced(definition);
+              }
+              return !enqueuer.isNonProgramTypeLive(holder);
+            } else if (reference.isDexMethod()) {
+              DexEncodedMethod definition = definitions.definitionFor(reference.asDexMethod());
+              if (definition == null) {
+                return true;
+              }
+              DexClass holder = definitions.definitionFor(definition.holder());
+              if (holder.isProgramClass()) {
+                return !enqueuer.isMethodLive(definition) && !enqueuer.isMethodTargeted(definition);
+              }
+              return !enqueuer.isNonProgramTypeLive(holder);
+            } else {
+              DexClass definition = definitions.definitionFor(reference.asDexType());
+              return definition == null || !enqueuer.isTypeLive(definition);
+            }
+          });
+    }
+
     public void move(DexReference original, DexReference rewritten) {
       copy(original, rewritten);
       prune(original);
diff --git a/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java b/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java
index 7326fad..4675107 100644
--- a/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java
+++ b/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java
@@ -63,8 +63,8 @@
     }
     if (method.accessFlags.isMoreVisibleThan(
         existing.accessFlags,
-        method.method.holder.getPackageName(),
-        existing.method.holder.getPackageName())) {
+        method.holder().getPackageName(),
+        existing.holder().getPackageName())) {
       items.put(wrapped, method);
       return AddMethodIfMoreVisibleResult.ADDED_MORE_VISIBLE;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 1fb713e..7c679e0 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -349,8 +349,8 @@
         : reachableOrReferencedFields.toArray(DexEncodedField.EMPTY_ARRAY);
   }
 
-  public Collection<DexType> getRemovedClasses() {
-    return Collections.unmodifiableCollection(prunedTypes);
+  public Set<DexType> getRemovedClasses() {
+    return Collections.unmodifiableSet(prunedTypes);
   }
 
   public Collection<DexReference> getMethodsToKeepForConfigurationDebugging() {
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 92fa339..2fb7435 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
@@ -332,9 +333,9 @@
   private boolean isMergeCandidate(
       DexProgramClass sourceClass, DexProgramClass targetClass, Set<DexType> pinnedTypes) {
     assert targetClass != null;
-
-    if (appInfo.getObjectAllocationInfoCollection().isInstantiatedDirectly(sourceClass)
-        || appInfo.instantiatedLambdas.contains(sourceClass.type)
+    ObjectAllocationInfoCollection allocationInfo = appInfo.getObjectAllocationInfoCollection();
+    if (allocationInfo.isInstantiatedDirectly(sourceClass)
+        || allocationInfo.isInterfaceWithUnknownSubtypeHierarchy(sourceClass)
         || appInfo.isPinned(sourceClass.type)
         || pinnedTypes.contains(sourceClass.type)
         || appInfo.neverMerge.contains(sourceClass.type)) {
@@ -736,7 +737,7 @@
         // Conservatively find all possible targets for this method.
         LookupResultSuccess lookupResult =
             appInfo
-                .resolveMethodOnInterface(method.method.holder, method.method)
+                .resolveMethodOnInterface(method.holder(), method.method)
                 .lookupVirtualDispatchTargets(target, appInfo)
                 .asLookupResultSuccess();
         assert lookupResult != null;
@@ -988,7 +989,7 @@
                   Rename.ALWAYS,
                   appView
                       .dexItemFactory()
-                      .prependTypeToProto(virtualMethod.method.holder, virtualMethod.method.proto));
+                      .prependTypeToProto(virtualMethod.holder(), virtualMethod.method.proto));
           makeStatic(resultingDirectMethod);
 
           // Update method pool collection now that we are adding a new public method.
@@ -1327,7 +1328,7 @@
     private DexEncodedMethod renameConstructor(
         DexEncodedMethod method, Predicate<DexMethod> availableMethodSignatures) {
       assert method.isInstanceInitializer();
-      DexType oldHolder = method.method.holder;
+      DexType oldHolder = method.holder();
 
       DexMethod newSignature;
       int count = 1;
@@ -1363,7 +1364,7 @@
       // renamed already.
       assert !method.accessFlags.isConstructor() || strategy == Rename.NEVER;
       DexString oldName = method.method.name;
-      DexType oldHolder = method.method.holder;
+      DexType oldHolder = method.holder();
 
       DexMethod newSignature;
       switch (strategy) {
@@ -1398,7 +1399,7 @@
     private DexEncodedField renameFieldIfNeeded(
         DexEncodedField field, Predicate<DexField> availableFieldSignatures) {
       DexString oldName = field.field.name;
-      DexType oldHolder = field.field.holder;
+      DexType oldHolder = field.holder();
 
       DexField newSignature =
           application.dexItemFactory.createField(target.type, field.field.type, oldName);
@@ -1647,7 +1648,7 @@
             code.computeInliningConstraint(
                 method,
                 appView,
-                new SingleTypeMapperGraphLense(method.method.holder, invocationContext),
+                new SingleTypeMapperGraphLense(method.holder(), invocationContext),
                 invocationContext);
         if (constraint == ConstraintWithTarget.NEVER) {
           return AbortReason.UNSAFE_INLINING;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index eded127..1013539 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -125,7 +125,7 @@
       if (virtualToDirectMethodMap != null) {
         GraphLenseLookupResult lookup = virtualToDirectMethodMap.get(previous.getMethod());
         if (lookup != null) {
-          // If the super class A of the enclosing class B (i.e., context.method.holder)
+          // If the super class A of the enclosing class B (i.e., context.holder())
           // has been merged into B during vertical class merging, and this invoke-super instruction
           // was resolving to a method in A, then the target method has been changed to a direct
           // method and moved into B, so that we need to use an invoke-direct instruction instead of
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 f9f955c..ad290b6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.errors.IncompleteNestNestDesugarDiagnosic;
 import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
+import com.android.tools.r8.errors.InvalidLibrarySuperclassDiagnostic;
 import com.android.tools.r8.errors.MissingNestHostNestDesugarDiagnostic;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
@@ -70,6 +71,7 @@
 import java.util.function.BiConsumer;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 import org.objectweb.asm.Opcodes;
 
 public class InternalOptions {
@@ -183,6 +185,7 @@
     enableLambdaMerging = false;
     enableHorizontalClassMerging = false;
     enableVerticalClassMerging = false;
+    enableEnumUnboxing = false;
     enableUninstantiatedTypeOptimization = false;
     enableUnusedArgumentRemoval = false;
     outline.enabled = false;
@@ -701,6 +704,8 @@
   /** A set of dexitems we have reported missing to dedupe warnings. */
   private final Set<DexItem> reportedMissingForDesugaring = Sets.newConcurrentHashSet();
 
+  private final Set<DexItem> invalidLibraryClasses = Sets.newConcurrentHashSet();
+
   public void errorMissingClassMissingNestHost(DexClass compiledClass) {
     throw reporter.fatalError(messageErrorMissingNestHost(compiledClass));
   }
@@ -844,6 +849,26 @@
     }
   }
 
+  public void warningInvalidLibrarySuperclassForDesugar(
+      Origin origin,
+      DexType libraryType,
+      DexType invalidSuperType,
+      String message,
+      Set<DexMethod> retarget,
+      AppView<?> appView) {
+    if (invalidLibraryClasses.add(invalidSuperType)) {
+      reporter.warning(
+          new InvalidLibrarySuperclassDiagnostic(
+              origin,
+              Reference.classFromDescriptor(libraryType.toDescriptorString()),
+              Reference.classFromDescriptor(invalidSuperType.toDescriptorString()),
+              message,
+              retarget.stream()
+                  .map(method -> method.asMethodReference(appView))
+                  .collect(Collectors.toList())));
+    }
+  }
+
   public void warningMissingEnclosingMember(DexType clazz, Origin origin, int version) {
     TypeVersionPair pair = new TypeVersionPair(version, clazz);
     synchronized (missingEnclosingMembers) {
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 6bce9c1..9c18d3a 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -185,7 +185,7 @@
       if (parsedData != null || parsedKotlinSourceDebugExtensions.containsKey(holder)) {
         return parsedData;
       }
-      DexClass clazz = appView.definitionFor(currentMethod.method.holder);
+      DexClass clazz = appView.definitionFor(currentMethod.holder());
       DexValueString dexValueString = appView.getSourceDebugExtensionForType(clazz);
       if (dexValueString != null) {
         parsedData = KotlinSourceDebugExtensionParser.parse(dexValueString.value.toString());
@@ -456,7 +456,7 @@
         continue;
       }
       // We use the same name for interface names even if it has different types.
-      DexProgramClass clazz = appView.definitionForProgramType(method.method.holder);
+      DexProgramClass clazz = appView.definitionForProgramType(method.holder());
       DexClassAndMethod lookupResult =
           appView.appInfo().lookupMaximallySpecificMethod(clazz, method.method);
       if (lookupResult == null) {
@@ -783,7 +783,7 @@
     }
     method.setCode(
         new CfCode(
-            method.method.holder,
+            method.holder(),
             oldCode.getMaxStack(),
             oldCode.getMaxLocals(),
             newInstructions,
diff --git a/src/main/java/com/android/tools/r8/utils/PredicateUtils.java b/src/main/java/com/android/tools/r8/utils/PredicateUtils.java
index 2c6fac0..880da5e 100644
--- a/src/main/java/com/android/tools/r8/utils/PredicateUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/PredicateUtils.java
@@ -16,4 +16,8 @@
     }
     return null;
   }
+
+  public static <T> Predicate<T> not(Predicate<T> predicate) {
+    return t -> !predicate.test(t);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index 8c659b1..30f6489 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -1000,7 +1000,9 @@
               "lang.SecurityManager.checkMulticastLjava_net_InetAddress.SecurityManager_checkMulticast_A01",
               anyDexVm())
           .put("lang.SecurityManager.Constructor.SecurityManager_Constructor_A01", anyDexVm())
-          .put("lang.SecurityManager.getClassContext.SecurityManager_getClassContext_A01", anyDexVm())
+          .put(
+              "lang.SecurityManager.getClassContext.SecurityManager_getClassContext_A01",
+              anyDexVm())
           .put(
               "lang.SecurityManager.checkMemberAccessLjava_lang_ClassI.SecurityManager_checkMemberAccess_A03",
               anyDexVm())
@@ -1117,7 +1119,9 @@
           .put(
               "lang.SecurityManager.checkLinkLjava_lang_String.SecurityManager_checkLink_A02",
               anyDexVm())
-          .put("lang.SecurityManager.classLoaderDepth.SecurityManager_classLoaderDepth_A01", anyDexVm())
+          .put(
+              "lang.SecurityManager.classLoaderDepth.SecurityManager_classLoaderDepth_A01",
+              anyDexVm())
           .put(
               "lang.SecurityManager.checkPermissionLjava_security_Permission.SecurityManager_checkPermission_A02",
               anyDexVm())
@@ -1740,22 +1744,25 @@
           .put("lang.reflect.Field.toGenericString.Field_toGenericString_A01", cf())
           .build(); // end of failuresToTriage
 
-
   public static final Multimap<String, TestCondition> bugs =
       new ImmutableListMultimap.Builder<String, TestCondition>()
           // The following StringBuffer/StringBuilder tests fails because we remove, e.g.,
           // new StringBuffer(-5) if it is dead code (but it should trow), see b/133745205
-          .put("lang.StringBuffer.ConstructorLjava_lang_String.StringBuffer_Constructor_A02",
+          .put(
+              "lang.StringBuffer.ConstructorLjava_lang_String.StringBuffer_Constructor_A02",
               match(R8DEX_COMPILER))
-          .put("lang.StringBuffer.ConstructorLjava_lang_CharSequence.StringBuffer_Constructor_A02",
+          .put(
+              "lang.StringBuffer.ConstructorLjava_lang_CharSequence.StringBuffer_Constructor_A02",
               match(R8DEX_COMPILER))
-          .put("lang.StringBuffer.ConstructorI.StringBuffer_Constructor_A02",
+          .put("lang.StringBuffer.ConstructorI.StringBuffer_Constructor_A02", match(R8DEX_COMPILER))
+          .put(
+              "lang.StringBuilder.ConstructorI.StringBuilder_Constructor_A02",
               match(R8DEX_COMPILER))
-          .put("lang.StringBuilder.ConstructorI.StringBuilder_Constructor_A02",
-                match(R8DEX_COMPILER))
-          .put("lang.StringBuilder.ConstructorLjava_lang_CharSequence.StringBuilder_Constructor_A02",
+          .put(
+              "lang.StringBuilder.ConstructorLjava_lang_CharSequence.StringBuilder_Constructor_A02",
               match(R8DEX_COMPILER))
-          .put("lang.StringBuilder.ConstructorLjava_lang_String.StringBuilder_Constructor_A02",
+          .put(
+              "lang.StringBuilder.ConstructorLjava_lang_String.StringBuilder_Constructor_A02",
               match(R8DEX_COMPILER))
           .build();
 
@@ -1814,9 +1821,11 @@
           .put(
               "util.concurrent.AbstractExecutorService.invokeAllLjava_util_CollectionJLjava_util_concurrent_TimeUnit.AbstractExecutorService_invokeAll_A06",
               match(runtimes(Runtime.ART_V4_0_4)))
+          .put(
+              "util.concurrent.Executors.newCachedThreadPoolLjava_util_concurrent_ThreadFactory.Executors_newCachedThreadPool_A01",
+              anyDexVm())
           .put("lang.ref.SoftReference.get.SoftReference_get_A01", cf())
           .put("lang.ref.WeakReference.get.WeakReference_get_A01", cf())
-
           .build(); // end of flakyWhenRun
 
   public static final Multimap<String, TestCondition> timeoutsWhenRun =
diff --git a/src/test/java/com/android/tools/r8/NeverReprocessClassInitializer.java b/src/test/java/com/android/tools/r8/NeverReprocessClassInitializer.java
new file mode 100644
index 0000000..d0fbd19
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/NeverReprocessClassInitializer.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+public @interface NeverReprocessClassInitializer {}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 569c222..e28b4eb 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -53,6 +53,7 @@
   private boolean enableMemberValuePropagationAnnotations = false;
   private boolean enableMergeAnnotations = false;
   private boolean enableNeverClassInliningAnnotations = false;
+  private boolean enableNeverReprocessClassInitializerAnnotations = false;
   private boolean enableNeverReprocessMethodAnnotations = false;
   private boolean enableReprocessClassInitializerAnnotations = false;
   private boolean enableReprocessMethodAnnotations = false;
@@ -72,6 +73,7 @@
         || enableMemberValuePropagationAnnotations
         || enableMergeAnnotations
         || enableNeverClassInliningAnnotations
+        || enableNeverReprocessClassInitializerAnnotations
         || enableNeverReprocessMethodAnnotations
         || enableReprocessClassInitializerAnnotations
         || enableReprocessMethodAnnotations
@@ -407,6 +409,16 @@
     return self();
   }
 
+  public T enableNeverReprocessClassInitializerAnnotations() {
+    if (!enableNeverReprocessClassInitializerAnnotations) {
+      enableNeverReprocessClassInitializerAnnotations = true;
+      addInternalKeepRules(
+          "-neverreprocessclassinitializer @com.android.tools.r8.NeverReprocessClassInitializer"
+              + " class *");
+    }
+    return self();
+  }
+
   public T enableReprocessMethodAnnotations() {
     if (!enableReprocessMethodAnnotations) {
       enableReprocessMethodAnnotations = true;
diff --git a/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java b/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
new file mode 100644
index 0000000..56cde8f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
@@ -0,0 +1,85 @@
+// 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.annotations;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.retrace.KotlinInlineFunctionRetraceTest;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
+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 SourceDebugExtensionTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SourceDebugExtensionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    CfRuntime cfRuntime =
+        parameters.isCfRuntime() ? parameters.getRuntime().asCf() : TestRuntime.getCheckedInJdk9();
+    Path kotlinSources =
+        kotlinc(cfRuntime, getStaticTemp(), KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addSourceFiles(
+                getFilesInTestFolderRelativeToClass(
+                    KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
+            .compile();
+    CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
+    inspectSourceDebugExtension(kotlinInspector);
+    testForR8(parameters.getBackend())
+        .addProgramFiles(kotlinSources)
+        .addKeepAttributes(ProguardKeepAttributes.SOURCE_DEBUG_EXTENSION)
+        .addKeepAllClassesRule()
+        .setMode(CompilationMode.RELEASE)
+        .setMinApi(parameters.getApiLevel())
+        .allowDiagnosticWarningMessages(
+            parameters.isDexRuntime()
+                && parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M))
+        .compile()
+        .assertAllWarningMessagesMatch(
+            containsString(
+                "Type `kotlin.jvm.internal.Intrinsics` was not found, it is required for default"
+                    + " or static interface methods"))
+        .inspect(this::inspectSourceDebugExtension);
+  }
+
+  private void inspectSourceDebugExtension(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz("retrace.InlineFunctionKt");
+    assertThat(clazz, isPresent());
+    AnnotationSubject sourceDebugExtensions =
+        clazz.annotation("dalvik.annotation.SourceDebugExtension");
+    assertThat(sourceDebugExtensions, isPresent());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index 862bb79..3240d08 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -28,6 +28,7 @@
 import java.nio.file.Paths;
 import java.util.Calendar;
 import java.util.List;
+import java.util.TreeSet;
 
 public abstract class MethodGenerationBase extends TestBase {
 
@@ -102,8 +103,7 @@
                 if (method.isInitializer()) {
                   continue;
                 }
-                String methodName =
-                    method.method.holder.getName() + "_" + method.method.name.toString();
+                String methodName = method.holder().getName() + "_" + method.method.name.toString();
                 codePrinter.visitMethod(methodName, method.getCode().asCfCode());
               }
             });
@@ -117,8 +117,15 @@
   private void generateRawOutput(CfCodePrinter codePrinter, Path tempFile) throws IOException {
     try (PrintStream printer = new PrintStream(Files.newOutputStream(tempFile))) {
       printer.print(getHeaderString(this.getClass()));
+      printer.println("import com.android.tools.r8.graph.DexItemFactory;");
       codePrinter.getImports().forEach(i -> printer.println("import " + i + ";"));
       printer.println("public final class " + getGeneratedClassName() + " {\n");
+      printer.println(
+          "public static void registerSynthesizedCodeReferences(DexItemFactory factory) {");
+      for (String type : new TreeSet<>(codePrinter.getSynthesizedTypes())) {
+        printer.println("factory.createSynthesizedType(\"" + type + "\");");
+      }
+      printer.println("}");
       codePrinter.getMethods().forEach(printer::println);
       printer.println("}");
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InvalidLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InvalidLibraryTest.java
new file mode 100644
index 0000000..64f7a72
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InvalidLibraryTest.java
@@ -0,0 +1,178 @@
+// 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.desugar.desugaredlibrary;
+
+import static org.hamcrest.core.StringContains.containsString;
+
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.errors.InvalidLibrarySuperclassDiagnostic;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvalidLibraryTest extends DesugaredLibraryTestBase {
+
+  private static Path customLib;
+  private static Path superclassAsClasspath;
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines("1970-01-02T10:17:36.789Z", "1970-01-12T10:20:54.321123456Z");
+  private static final String INVALID_RESULT =
+      StringUtils.lines("1970-01-02T10:17:36.789Z", "1970-01-12T10:20:54.321Z");
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameterized.Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public InvalidLibraryTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    customLib =
+        testForD8(getStaticTemp())
+            .addProgramClasses(CustomLibraryClass.class)
+            .setMinApi(AndroidApiLevel.B)
+            .compile()
+            .writeToZip();
+    superclassAsClasspath =
+        testForD8(getStaticTemp())
+            .addProgramClasses(SuperLibraryClass.class)
+            .setMinApi(AndroidApiLevel.B)
+            .compile()
+            .writeToZip();
+  }
+
+  @Test
+  public void testProgramSupertype() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(
+            Executor.class, SuperLibraryClass.class, LocalClass.class, LocalClassOverride.class)
+        .addLibraryClasses(CustomLibraryClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(customLib)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testClasspathSupertype() throws Exception {
+    Assume.assumeTrue(requiresAnyCoreLibDesugaring(parameters));
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(Executor.class, LocalClass.class, LocalClassOverride.class)
+        .addClasspathClasses(SuperLibraryClass.class)
+        .addLibraryClasses(CustomLibraryClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .inspectDiagnosticMessages(this::assertWarningInvalidLibrary)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(customLib, superclassAsClasspath)
+        .run(parameters.getRuntime(), Executor.class)
+        // The code requires desugaring to be run correctly, but with the classpath superclass,
+        // desugaring is incorrectly performed. The code therefore falls-backs to the default
+        // implementation in Date, which happens to be correct in one case, but incorrect
+        // in the other case (Warning was raised).
+        .assertSuccessWithOutput(INVALID_RESULT);
+  }
+
+  @Test
+  public void testNullSupertype() throws Exception {
+    Assume.assumeTrue(requiresAnyCoreLibDesugaring(parameters));
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(Executor.class, LocalClass.class, LocalClassOverride.class)
+        .addLibraryClasses(CustomLibraryClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .inspectDiagnosticMessages(this::assertWarningInvalidLibrary)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(customLib, superclassAsClasspath)
+        .run(parameters.getRuntime(), Executor.class)
+        // The code requires desugaring to be run correctly, but with the missing supertype,
+        // desugaring could not be performed and the code cannot simply run (Warning was raised).
+        .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
+  }
+
+  private void assertWarningInvalidLibrary(TestDiagnosticMessages testDiagnosticMessages) {
+    assert testDiagnosticMessages.getWarnings().stream()
+        .anyMatch(diagnostic -> diagnostic instanceof InvalidLibrarySuperclassDiagnostic);
+  }
+
+  static class Executor {
+    public static void main(String[] args) {
+      System.out.println(new LocalClass(123456789).toInstant());
+      System.out.println(getOverrideAsLocalClass().toInstant());
+    }
+
+    public static LocalClass getOverrideAsLocalClass() {
+      return new LocalClassOverride(987654321);
+    }
+  }
+
+  static class SuperLibraryClass extends Date {
+    public SuperLibraryClass(int nanos) {
+      super(nanos);
+    }
+  }
+
+  static class CustomLibraryClass extends SuperLibraryClass {
+    public CustomLibraryClass(int nanos) {
+      super(nanos);
+    }
+  }
+
+  static class LocalClass extends CustomLibraryClass {
+    public LocalClass(int nanos) {
+      super(nanos);
+    }
+  }
+
+  static class LocalClassOverride extends LocalClass {
+    public LocalClassOverride(int nanos) {
+      super(nanos);
+    }
+
+    @Override
+    public Instant toInstant() {
+      return super.toInstant().plusNanos(123456);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassErrorTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassErrorTest.java
deleted file mode 100644
index 403ae3c..0000000
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassErrorTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
-
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.fail;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.TestDiagnosticMessages;
-import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import java.time.Year;
-import org.junit.Test;
-
-public class APIConversionFinalClassErrorTest extends DesugaredLibraryTestBase {
-
-  @Test
-  public void testFinalMethod() {
-    try {
-      testForD8()
-          .setMinApi(AndroidApiLevel.B)
-          .addProgramClasses(Executor.class)
-          .addLibraryClasses(CustomLibClass.class)
-          .enableCoreLibraryDesugaring(AndroidApiLevel.B)
-          .compileWithExpectedDiagnostics(this::assertDiagnosis);
-      fail("Expected compilation error");
-    } catch (CompilationFailedException ignored) {
-
-    }
-  }
-
-  private void assertDiagnosis(TestDiagnosticMessages d) {
-    assertEquals(
-        "Cannot generate a wrapper for final class java.time.Year."
-            + " Add a custom conversion in the desugared library.",
-        d.getErrors().get(0).getDiagnosticMessage());
-  }
-
-  static class Executor {
-
-    public static void main(String[] args) {
-      System.out.println(CustomLibClass.call(Year.now()));
-    }
-  }
-
-  // This class will be put at compilation time as library and on the runtime class path.
-  // This class is convenient for easy testing. Each method plays the role of methods in the
-  // platform APIs for which argument/return values need conversion.
-  static class CustomLibClass {
-
-    public static long call(Year year) {
-      return 0L;
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassTest.java
new file mode 100644
index 0000000..f9d1828
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassTest.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.core.StringContains.containsString;
+
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.nio.file.Path;
+import java.time.Year;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class APIConversionFinalClassTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.O;
+
+  private static Path customLib;
+
+  @Parameterized.Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+  }
+
+  public APIConversionFinalClassTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    customLib =
+        testForD8(getStaticTemp())
+            .addProgramClasses(CustomLibClass.class)
+            .setMinApi(MIN_SUPPORTED)
+            .compile()
+            .writeToZip();
+  }
+
+  @Test
+  public void testFinalMethod() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .setMinApi(AndroidApiLevel.B)
+        .addProgramClasses(Executor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .inspectDiagnosticMessages(this::assertDiagnosis)
+        .addRunClasspathFiles(customLib)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
+  }
+
+  private void assertDiagnosis(TestDiagnosticMessages d) {
+    assertTrue(d.getInfos().get(0).getDiagnosticMessage().contains("libCall"));
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      System.out.println(CustomLibClass.libCall(Year.now()));
+    }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
+  static class CustomLibClass {
+
+    // We use Year because Year is a final class with no custom conversion but Year has been
+    // unused in the Android library so far.
+    public static long libCall(Year year) {
+      return 0L;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringImpossibleTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringImpossibleTest.java
new file mode 100644
index 0000000..88ede59
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringImpossibleTest.java
@@ -0,0 +1,128 @@
+// 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.desugaring.interfacemethods;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DefaultInterfaceMethodDesugaringImpossibleTest extends TestBase {
+
+  private static final String EXPECTED = StringUtils.lines("I.m()");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DefaultInterfaceMethodDesugaringImpossibleTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private Collection<Class<?>> getProgramClasses() {
+    return ImmutableList.of(TestClass.class, I.class);
+  }
+
+  private Collection<byte[]> getProgramClassData() throws Exception {
+    return ImmutableList.of(
+        transformer(A.class)
+            .setAccessFlags(
+                A.class.getDeclaredMethod("m"),
+                flags -> {
+                  assert flags.isPublic();
+                  flags.unsetPublic();
+                  flags.setPrivate();
+                  flags.setStatic();
+                })
+            .transform());
+  }
+
+  @Test
+  public void testJVM() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getProgramClassData())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    try {
+      testForD8()
+          .addProgramClasses(getProgramClasses())
+          .addProgramClassFileData(getProgramClassData())
+          .setMinApi(parameters.getApiLevel())
+          .compileWithExpectedDiagnostics(this::checkDesugarError)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED);
+      assertTrue(parameters.canUseDefaultAndStaticInterfaceMethods());
+    } catch (CompilationFailedException e) {
+      assertFalse(parameters.canUseDefaultAndStaticInterfaceMethods());
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    try {
+      testForR8(parameters.getBackend())
+          .addProgramClasses(getProgramClasses())
+          .addProgramClassFileData(getProgramClassData())
+          .addKeepAllClassesRule()
+          .setMinApi(parameters.getApiLevel())
+          .compileWithExpectedDiagnostics(this::checkDesugarError)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED);
+      assertTrue(parameters.canUseDefaultAndStaticInterfaceMethods());
+    } catch (CompilationFailedException e) {
+      assertFalse(parameters.canUseDefaultAndStaticInterfaceMethods());
+    }
+  }
+
+  private void checkDesugarError(TestDiagnosticMessages diagnostics) {
+    if (!parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      diagnostics.assertErrorMessageThatMatches(containsString("forwarding method that conflicts"));
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      I a = new A();
+      a.m();
+    }
+  }
+
+  interface I {
+
+    default void m() {
+      System.out.println("I.m()");
+    }
+  }
+
+  static class A implements I {
+
+    public /* will be: private static */ void m() {
+      System.out.println("A.m()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java
new file mode 100644
index 0000000..a8c5c6f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java
@@ -0,0 +1,174 @@
+// 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.desugaring.interfacemethods;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest
+    extends TestBase {
+
+  private static final String EXPECTED = StringUtils.lines("I.m()");
+
+  private final TestParameters parameters;
+  private final boolean invalidInvoke;
+
+  @Parameterized.Parameters(name = "{0}, invalid:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest(
+      TestParameters parameters, boolean invalidInvoke) {
+    this.parameters = parameters;
+    this.invalidInvoke = invalidInvoke;
+  }
+
+  private Collection<Class<?>> getProgramClasses() {
+    return ImmutableList.of(I.class, A.class, C.class);
+  }
+
+  private Collection<byte[]> getProgramClassData() throws Exception {
+    return ImmutableList.of(
+        transformer(B.class)
+            .setAccessFlags(
+                B.class.getDeclaredMethod("m"),
+                flags -> {
+                  assert flags.isPublic();
+                  flags.unsetPublic();
+                  flags.setPrivate();
+                  flags.setStatic();
+                })
+            .transform(),
+        transformer(TestClass.class)
+            .transformMethodInsnInMethod(
+                "main",
+                (opcode, owner, name, descriptor, isInterface, continuation) -> {
+                  if (invalidInvoke && opcode == Opcodes.INVOKEVIRTUAL) {
+                    assertEquals("m", name);
+                    continuation.apply(
+                        opcode,
+                        DescriptorUtils.getBinaryNameFromJavaType(C.class.getTypeName()),
+                        name,
+                        descriptor,
+                        isInterface);
+                  } else {
+                    continuation.apply(opcode, owner, name, descriptor, isInterface);
+                  }
+                })
+            .transform());
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    checkResult(
+        testForRuntime(parameters)
+            .addProgramClasses(getProgramClasses())
+            .addProgramClassFileData(getProgramClassData())
+            .run(parameters.getRuntime(), TestClass.class));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    checkResult(
+        testForR8(parameters.getBackend())
+            .addProgramClasses(getProgramClasses())
+            .addProgramClassFileData(getProgramClassData())
+            .addKeepAllClassesRule()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .run(parameters.getRuntime(), TestClass.class));
+  }
+
+  private void checkResult(TestRunResult<?> result) {
+    // Invalid invoke case is where the invoke-virtual targets C.m.
+    if (invalidInvoke) {
+      // Up to 4.4 the exception for targeting a private static was ICCE.
+      if (isDexOlderThanOrEqual(Version.V4_4_4)) {
+        result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+        return;
+      }
+      // Then up to 6.0 the runtime just ignores privates leading to incorrectly hitting I.m
+      if (isDexOlderThanOrEqual(Version.V6_0_1)) {
+        result.assertSuccessWithOutput(EXPECTED);
+        return;
+      }
+      if (!unexpectedArtFailure() && !parameters.canUseDefaultAndStaticInterfaceMethods()) {
+        assert false : "Dead code until future ART behavior change. See b/152199517";
+        // Desugaring will insert a forwarding bridge which will hide the "invalid invoke" case.
+        // Thus, a future ART runtime that does not have the invalid IAE for the private override
+        // will end up calling the forward method to I.m.
+        result.assertSuccessWithOutput(EXPECTED);
+      }
+      // The expected behavior is IAE since the resolved method is private.
+      result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
+      return;
+    }
+
+    // The non-invalid case is where the invoke-virtual targets A.m.
+
+    // In the successful case ART since 6.0 incorrectly throws IAE due to the private override.
+    if (unexpectedArtFailure()) {
+      result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
+      return;
+    }
+
+    // The expected behavior is that the resolution of A.m will resolve and hit I.m.
+    result.assertSuccessWithOutput(EXPECTED);
+  }
+
+  private boolean isDexOlderThanOrEqual(Version version) {
+    return parameters.isDexRuntime()
+        && parameters.getRuntime().asDex().getVm().getVersion().isOlderThanOrEqual(version);
+  }
+
+  private boolean unexpectedArtFailure() {
+    return parameters.isDexRuntime()
+        && parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_6_0_1_HOST);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      // Same as DefaultInterfaceMethodDesugaringWithStaticResolutionTest, but targets a class A.
+      A /* or C */ a = new C();
+      a.m();
+    }
+  }
+
+  interface I {
+
+    default void m() {
+      System.out.println("I.m()");
+    }
+  }
+
+  static class A implements I {}
+
+  static class B extends A {
+
+    public /* will be: private static */ void m() {
+      System.out.println("B.m()");
+    }
+  }
+
+  static class C extends B implements I {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionTest.java
index bdd7960..c649c8d 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionTest.java
@@ -1,13 +1,11 @@
 package com.android.tools.r8.desugaring.interfacemethods;
 
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.D8TestRunResult;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -15,6 +13,8 @@
 @RunWith(Parameterized.class)
 public class DefaultInterfaceMethodDesugaringWithStaticResolutionTest extends TestBase {
 
+  private static final String EXPECTED = StringUtils.lines("I.m()");
+
   private final TestParameters parameters;
 
   @Parameterized.Parameters(name = "{0}")
@@ -32,43 +32,29 @@
     testForJvm()
         .addTestClasspath()
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("I.m()");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
-    D8TestRunResult result =
-        testForD8()
-            .addInnerClasses(DefaultInterfaceMethodDesugaringWithStaticResolutionTest.class)
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .run(parameters.getRuntime(), TestClass.class);
-    // TODO(b/152163087): Should always succeed with "I.m()".
-    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
-      result.assertSuccessWithOutputLines("I.m()");
-    } else {
-      result.assertFailureWithErrorThatMatches(
-          containsString(AbstractMethodError.class.getTypeName()));
-    }
+    testForD8()
+        .addInnerClasses(DefaultInterfaceMethodDesugaringWithStaticResolutionTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   @Test
   public void testR8() throws Exception {
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(DefaultInterfaceMethodDesugaringWithStaticResolutionTest.class)
-            .addKeepAllClassesRule()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .run(parameters.getRuntime(), TestClass.class);
-    // TODO(b/152163087): Should always succeed with "I.m()".
-    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
-      result.assertSuccessWithOutputLines("I.m()");
-    } else {
-      result.assertFailureWithErrorThatMatches(
-          containsString(AbstractMethodError.class.getTypeName()));
-    }
+    testForR8(parameters.getBackend())
+        .addInnerClasses(DefaultInterfaceMethodDesugaringWithStaticResolutionTest.class)
+        .addKeepAllClassesRule()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
new file mode 100644
index 0000000..b4e1ef6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
@@ -0,0 +1,94 @@
+// 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.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+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 ValueOfEnumUnboxingTest extends EnumUnboxingTestBase {
+
+  private static final Class<?>[] SUCCESSES = {
+    EnumValueOf.class,
+  };
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final KeepRule enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public ValueOfEnumUnboxingTest(
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    R8TestCompileResult compile =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(ValueOfEnumUnboxingTest.class)
+            .addKeepMainRules(SUCCESSES)
+            .enableNeverClassInliningAnnotations()
+            .addKeepRules(enumKeepRules.getKeepRule())
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+            .allowDiagnosticInfoMessages()
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+    for (Class<?> success : SUCCESSES) {
+      R8TestRunResult run =
+          compile
+              .inspectDiagnosticMessages(
+                  m ->
+                      assertEnumIsUnboxed(
+                          success.getDeclaredClasses()[0], success.getSimpleName(), m))
+              .run(parameters.getRuntime(), success)
+              .assertSuccess();
+      assertLines2By2Correct(run.getStdOut());
+    }
+  }
+
+  static class EnumValueOf {
+
+    @NeverClassInline
+    enum MyEnum {
+      A,
+      B
+    }
+
+    public static void main(String[] args) {
+      System.out.println(Enum.valueOf(EnumValueOf.MyEnum.class, "A").ordinal());
+      System.out.println(0);
+      System.out.println(Enum.valueOf(EnumValueOf.MyEnum.class, "B").ordinal());
+      System.out.println(1);
+      try {
+        Enum.valueOf(EnumValueOf.MyEnum.class, "C");
+      } catch (IllegalArgumentException argException) {
+        System.out.println(argException.getMessage());
+        System.out.println(
+            "No enum constant"
+                + " com.android.tools.r8.enumunboxing.ValueOfEnumUnboxingTest.EnumValueOf.MyEnum.C");
+      }
+      try {
+        Enum.valueOf(EnumValueOf.MyEnum.class, null);
+      } catch (NullPointerException npe) {
+        System.out.println(npe.getMessage());
+        System.out.println("Name is null");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
index 33321eb..4d39642 100644
--- a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
@@ -8,16 +8,21 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 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.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.Parser;
 import com.android.tools.r8.graph.GenericSignature.ReturnType;
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
+import com.android.tools.r8.graph.GenericSignatureTestClassA.I;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -28,11 +33,24 @@
 import java.util.List;
 import java.util.function.Function;
 import java.util.function.Supplier;
+import org.junit.Ignore;
 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 GenericSignatureTest extends TestBase {
 
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public GenericSignatureTest(TestParameters parameters) {}
+
   @Test
+  @Ignore("b/152709234")
   public void test() throws Exception {
     AndroidApp app =
         testForD8()
@@ -62,6 +80,8 @@
     assertThat(cy, isPresent());
     ClassSubject cyy = inspector.clazz(GenericSignatureTestClassCYY.class);
     assertThat(cyy, isPresent());
+    ClassSubject i = inspector.clazz(I.class);
+    assertThat(cyy, isPresent());
 
     DexEncodedMethod method;
 
@@ -80,13 +100,22 @@
     // Testing ClassSignature
     //
 
-    // class CYY<T extends A<T>.Y> extends CY<T>
+    // class <T:GenericSignatureTestClassA<T>.Y>CYY<T extends A<T>.Y> extends CY<T>
     DexClass clazz = cyy.getDexClass();
     assertNotNull(clazz);
     classSignature = Parser.toClassSignature(clazz, appView);
     assertNotNull(classSignature);
 
-    // TODO(b/129925954): test formal type parameter of CYY
+    assertEquals(1, classSignature.formalTypeParameters.size());
+    FormalTypeParameter formalTypeParameter = classSignature.formalTypeParameters.get(0);
+    assertEquals("T", formalTypeParameter.name);
+    assertNull(formalTypeParameter.interfaceBounds);
+    assertTrue(formalTypeParameter.classBound.isClassTypeSignature());
+    ClassTypeSignature classBoundSignature = formalTypeParameter.classBound.asClassTypeSignature();
+    assertEquals(y.getDexClass().type, classBoundSignature.innerTypeSignature.type);
+    assertEquals(1, classBoundSignature.typeArguments.size());
+    assertEquals(
+        "T", classBoundSignature.typeArguments.get(0).asTypeVariableSignature().typeVariable);
 
     assertTrue(classSignature.superInterfaceSignatures.isEmpty());
     classTypeSignature = classSignature.superClassSignature;
@@ -126,6 +155,18 @@
     methodTypeSignature = Parser.toMethodTypeSignature(method, appView);
     assertNotNull(methodTypeSignature);
 
+    assertEquals(1, methodTypeSignature.formalTypeParameters.size());
+    FormalTypeParameter methodFormalParameter = methodTypeSignature.formalTypeParameters.get(0);
+    assertTrue(methodFormalParameter.classBound.isClassTypeSignature());
+    assertEquals(
+        y.getDexClass().getType(),
+        methodFormalParameter.classBound.asClassTypeSignature().innerTypeSignature.type);
+    assertNotNull(methodFormalParameter.interfaceBounds);
+    assertEquals(1, methodFormalParameter.interfaceBounds.size());
+    FieldTypeSignature interfaceBound = methodFormalParameter.interfaceBounds.get(0);
+    assertTrue(interfaceBound.isClassTypeSignature());
+    assertEquals(i.getDexClass().getType(), interfaceBound.asClassTypeSignature().type);
+
     // return type: A$Y$YY
     returnType = methodTypeSignature.returnType();
     assertFalse(returnType.isVoidDescriptor());
@@ -257,6 +298,9 @@
 //
 
 class GenericSignatureTestClassA<T> {
+
+  interface I {}
+
   class Y {
 
     class YY {}
@@ -264,7 +308,7 @@
     class ZZ<TT> extends YY {
       public YY yy;
 
-      YY newYY(GenericSignatureTestClassB... bs) {
+      <R extends I> YY newYY(GenericSignatureTestClassB... bs) {
         return new YY();
       }
 
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index 07db6bd..2438ab2 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -83,12 +83,9 @@
     DexEncodedMethod method = getMethod(inspector, DEFAULT_CLASS_NAME, "int", "x",
         ImmutableList.of());
     assertFalse(
-        appInfo
-            .resolveMethod(method.method.holder, method.method)
-            .getSingleTarget()
-            .isVirtualMethod());
-    assertNull(appInfo.lookupDirectTarget(method.method, method.method.holder));
-    assertNotNull(appInfo.lookupStaticTarget(method.method, method.method.holder));
+        appInfo.resolveMethod(method.holder(), method.method).getSingleTarget().isVirtualMethod());
+    assertNull(appInfo.lookupDirectTarget(method.method, method.holder()));
+    assertNotNull(appInfo.lookupStaticTarget(method.method, method.holder()));
 
     if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(DexVm.Version.V4_4_4)) {
       // Dalvik rejects at verification time instead of producing the
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index 741be70..20ed6dc 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -94,7 +94,7 @@
             .asLookupResultSuccess();
     assertNotNull(lookupResult);
     assertFalse(lookupResult.hasLambdaTargets());
-    if (appInfo().subtypes(method.method.holder).stream()
+    if (appInfo().subtypes(method.holder()).stream()
         .allMatch(t -> appInfo().definitionFor(t).isInterface())) {
       Counter counter = new Counter();
       lookupResult.forEach(
diff --git a/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java b/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java
index 0d31dca..f191b3b 100644
--- a/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java
@@ -153,7 +153,7 @@
     IRCode code = simpleCode();
     InstructionListIterator it = new LinearFlowInstructionListIterator(code, code.blocks.get(1));
     Instruction current = it.previous();
-    assertTrue(current.isConstNumber() && current.outValue().getType().isReferenceType());
+    assertTrue(current.isConstNumber() && current.getOutType().isReferenceType());
     it.next();
     current = it.next();
     assertTrue(current.isArrayGet());
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
index a644df9..bdeda3c 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
@@ -104,7 +104,7 @@
                 code,
                 instruction ->
                     instruction.isConstNumber() && instruction.asConstNumber().getRawValue() != 0);
-        assertEquals(getFloat(), constNumberInstruction.outValue().getType());
+        assertEquals(getFloat(), constNumberInstruction.getOutType());
       }
     };
   }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
index e3b40cb..e421309 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
@@ -98,7 +98,7 @@
       for (Instruction instruction : code.instructions()) {
         if (instruction.isConstNumber()) {
           ConstNumber constNumberInstruction = instruction.asConstNumber();
-          assertEquals(expectedType, constNumberInstruction.outValue().getType());
+          assertEquals(expectedType, constNumberInstruction.getOutType());
         }
       }
 
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java
index 0f40b4b..9fb2f9b 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java
@@ -111,7 +111,7 @@
   private static Consumer<IRCode> testInspector(TypeElement expectedType) {
     return code -> {
       ConstNumber constNumberInstruction = getMatchingInstruction(code, Instruction::isConstNumber);
-      assertEquals(expectedType, constNumberInstruction.outValue().getType());
+      assertEquals(expectedType, constNumberInstruction.getOutType());
     };
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java
index f6ad83a..961827a 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java
@@ -116,7 +116,7 @@
       for (Instruction instruction : code.instructions()) {
         if (instruction.isConstNumber()) {
           ConstNumber constNumberInstruction = instruction.asConstNumber();
-          assertEquals(expectedType, constNumberInstruction.outValue().getType());
+          assertEquals(expectedType, constNumberInstruction.getOutType());
         }
       }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
index 2a2ad92..b5997bf 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
@@ -43,8 +43,8 @@
 
   private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
     // TODO(b/139246447): should avoid visiting A#<init>, which is trivial, default init!
-    assert encodedMethod.method.holder.toSourceString().endsWith("A")
-        && encodedMethod.toSourceString().contains("<init>")
+    assert encodedMethod.holder().toSourceString().endsWith("A")
+            && encodedMethod.toSourceString().contains("<init>")
         : "Unexpected revisit: " + encodedMethod.toSourceString();
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
index 4890c6a..54a3b8e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
@@ -64,7 +64,7 @@
     assert encodedMethod.method.name.toString().equals("m")
         : "Unexpected revisit: " + encodedMethod.toSourceString();
     CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
-    if (encodedMethod.method.holder.toSourceString().endsWith("$C")) {
+    if (encodedMethod.holder().toSourceString().endsWith("$C")) {
       assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
     } else {
       assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNull();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
index a0a436d..96b85ec 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
@@ -61,7 +61,7 @@
     assert encodedMethod.method.name.toString().equals("m")
         : "Unexpected revisit: " + encodedMethod.toSourceString();
     CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
-    if (encodedMethod.method.holder.toSourceString().endsWith("$C")) {
+    if (encodedMethod.holder().toSourceString().endsWith("$C")) {
       assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
     } else {
       assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNull();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
index ab5d763..14ff937 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
@@ -65,7 +65,7 @@
     CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
     assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
     AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(1);
-    if (encodedMethod.method.holder.toSourceString().endsWith("$A")) {
+    if (encodedMethod.holder().toSourceString().endsWith("$A")) {
       assert abstractValue.isSingleStringValue()
           && abstractValue.asSingleStringValue().getDexString().toString().equals("nul");
     } else {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
index 8cc1e12..204f41b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
@@ -66,7 +66,7 @@
     CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
     TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     assert upperBoundType.isDefinitelyNotNull();
-    if (encodedMethod.method.holder.toSourceString().endsWith("$A")) {
+    if (encodedMethod.holder().toSourceString().endsWith("$A")) {
       assert upperBoundType.isClassType()
           && upperBoundType.asClassType().getClassType().toSourceString().endsWith("$Sub1");
     } else {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
index ad7b3ae..4882c32 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
@@ -66,7 +66,7 @@
       TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
       assert upperBoundType.isNullable();
       assert upperBoundType.isClassType()
-          && upperBoundType.asClassType().getClassType().equals(encodedMethod.method.holder);
+          && upperBoundType.asClassType().getClassType().equals(encodedMethod.holder());
     } else {
       assert methodName.equals("test");
       assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
index 0551286..0009024 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
@@ -64,10 +64,10 @@
     TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     assert upperBoundType.isClassType()
         && upperBoundType.asClassType().getClassType().toSourceString().endsWith("$A");
-    if (encodedMethod.method.holder.toSourceString().endsWith("$A")) {
+    if (encodedMethod.holder().toSourceString().endsWith("$A")) {
       assert upperBoundType.isDefinitelyNotNull();
     } else {
-      assert encodedMethod.method.holder.toSourceString().endsWith("$B");
+      assert encodedMethod.holder().toSourceString().endsWith("$B");
       assert upperBoundType.isNullable();
     }
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFieldLoadEliminationMeetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFieldLoadEliminationMeetTest.java
new file mode 100644
index 0000000..776df6b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFieldLoadEliminationMeetTest.java
@@ -0,0 +1,64 @@
+package com.android.tools.r8.ir.optimize.redundantfieldloadelimination;
+
+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.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.PrintStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RedundantFieldLoadEliminationMeetTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public RedundantFieldLoadEliminationMeetTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(RedundantFieldLoadEliminationMeetTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+
+    MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertEquals(
+        1, mainMethodSubject.streamInstructions().filter(InstructionSubject::isStaticGet).count());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      boolean unknown = System.currentTimeMillis() > 0;
+      PrintStream out = System.out;
+      String message = unknown ? "Hello world!" : "Unexpected";
+      System.out.println(message);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalInstanceFieldLoadAfterStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalInstanceFieldLoadAfterStoreTest.java
new file mode 100644
index 0000000..329e574
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalInstanceFieldLoadAfterStoreTest.java
@@ -0,0 +1,147 @@
+package com.android.tools.r8.ir.optimize.redundantfieldloadelimination;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.optimize.redundantfieldloadelimination.RedundantFinalStaticFieldLoadAfterStoreTest.A;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.FoundFieldSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RedundantFinalInstanceFieldLoadAfterStoreTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public RedundantFinalInstanceFieldLoadAfterStoreTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(RedundantFinalInstanceFieldLoadAfterStoreTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("0", "42", "42", "42");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+
+    FieldSubject fFieldSubject = aClassSubject.uniqueFieldWithName("f");
+    assertThat(fFieldSubject, isPresent());
+
+    MethodSubject initMethodSubject = aClassSubject.init();
+    assertThat(initMethodSubject, isPresent());
+    assertEquals(
+        0,
+        countInstanceGetInstructions(
+            initMethodSubject.asFoundMethodSubject(), fFieldSubject.asFoundFieldSubject()));
+
+    MethodSubject mMethodSubject = aClassSubject.uniqueMethodWithName("m");
+    assertThat(mMethodSubject, isPresent());
+    assertEquals(
+        2,
+        countInstanceGetInstructions(
+            mMethodSubject.asFoundMethodSubject(), fFieldSubject.asFoundFieldSubject()));
+  }
+
+  private long countInstanceGetInstructions(
+      FoundMethodSubject methodSubject, FoundFieldSubject fieldSubject) {
+    return methodSubject
+        .streamInstructions()
+        .filter(InstructionSubject::isInstanceGet)
+        .map(InstructionSubject::getField)
+        .filter(fieldSubject.getField().field::equals)
+        .count();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new A();
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    @NeverPropagateValue final long f;
+
+    static volatile boolean read;
+    static volatile boolean initialized;
+
+    A() {
+      fork();
+      waitUntilRead();
+      f = System.currentTimeMillis() > 0 ? 42 : 0;
+      initialized = true;
+      killNonFinalActiveFields();
+      System.out.println(f); // Redundant, since `f` is final and guaranteed to be initialized.
+      killNonFinalActiveFields();
+      System.out.println(f); // Redundant, since `f` is final and guaranteed to be initialized.
+    }
+
+    @NeverInline
+    void m() {
+      System.out.println(f);
+      read = true;
+      waitUntilInitialized();
+      System.out.println(f); // Not redundant, since `f` is not guaranteed to be initialized.
+    }
+
+    @NeverInline
+    void fork() {
+      new Thread(this::m).start();
+    }
+
+    @NeverInline
+    void killNonFinalActiveFields() {
+      if (System.currentTimeMillis() < 0) {
+        System.out.println(this);
+      }
+    }
+
+    @NeverInline
+    void waitUntilInitialized() {
+      while (!initialized) {
+        Thread.yield();
+      }
+    }
+
+    @NeverInline
+    void waitUntilRead() {
+      while (!read) {
+        Thread.yield();
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java
new file mode 100644
index 0000000..fe3d79a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java
@@ -0,0 +1,118 @@
+package com.android.tools.r8.ir.optimize.redundantfieldloadelimination;
+
+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.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.FoundFieldSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RedundantFinalStaticFieldLoadAfterStoreTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public RedundantFinalStaticFieldLoadAfterStoreTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(RedundantFinalStaticFieldLoadAfterStoreTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("42", "42", "42", "42");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+
+    FieldSubject fFieldSubject = aClassSubject.uniqueFieldWithName("f");
+    assertThat(fFieldSubject, isPresent());
+
+    MethodSubject initMethodSubject = aClassSubject.clinit();
+    assertThat(initMethodSubject, isPresent());
+    assertEquals(
+        0,
+        countStaticGetInstructions(
+            initMethodSubject.asFoundMethodSubject(), fFieldSubject.asFoundFieldSubject()));
+
+    MethodSubject mMethodSubject = aClassSubject.uniqueMethodWithName("m");
+    assertThat(mMethodSubject, isPresent());
+    // TODO(b/152196923): Should be 0.
+    assertEquals(
+        2,
+        countStaticGetInstructions(
+            mMethodSubject.asFoundMethodSubject(), fFieldSubject.asFoundFieldSubject()));
+  }
+
+  private long countStaticGetInstructions(
+      FoundMethodSubject methodSubject, FoundFieldSubject fieldSubject) {
+    return methodSubject
+        .streamInstructions()
+        .filter(InstructionSubject::isStaticGet)
+        .map(InstructionSubject::getField)
+        .filter(fieldSubject.getField().field::equals)
+        .count();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A.m();
+    }
+  }
+
+  static class A {
+
+    @NeverPropagateValue static final long f;
+
+    static {
+      f = System.currentTimeMillis() > 0 ? 42 : 0;
+      killNonFinalActiveFields();
+      System.out.println(f); // Redundant, since `f` is final and guaranteed to be initialized.
+      killNonFinalActiveFields();
+      System.out.println(f); // Redundant, since `f` is final and guaranteed to be initialized.
+    }
+
+    @NeverInline
+    static void m() {
+      System.out.println(A.f);
+      killNonFinalActiveFields();
+      System.out.println(A.f); // Redundant, since `f` is guaranteed to be initialized.
+    }
+
+    @NeverInline
+    static void killNonFinalActiveFields() {
+      if (System.currentTimeMillis() < 0) {
+        System.out.println(A.class);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionClassWithNewInstanceUserTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionClassWithNewInstanceUserTest.java
new file mode 100644
index 0000000..74dc3f4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionClassWithNewInstanceUserTest.java
@@ -0,0 +1,85 @@
+package com.android.tools.r8.ir.optimize.staticizer;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 CompanionClassWithNewInstanceUserTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public CompanionClassWithNewInstanceUserTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(CompanionClassWithNewInstanceUserTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    if (parameters.isCfRuntime()) {
+      // Class staticizer is disabled when generating class files.
+      assertThat(inspector.clazz(Companion.class), isPresent());
+    } else {
+      // The companion class has been removed.
+      assertThat(inspector.clazz(Companion.class), not(isPresent()));
+
+      // The companion method has been moved to the companion host class.
+      ClassSubject hostClassSubject = inspector.clazz(CompanionHost.class);
+      assertThat(hostClassSubject, isPresent());
+      assertThat(hostClassSubject.uniqueMethodWithName("method"), isPresent());
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Companion companion = CompanionHost.COMPANION;
+    }
+  }
+
+  static class CompanionHost {
+
+    static final Companion COMPANION;
+
+    static {
+      Companion companion = new Companion();
+      COMPANION = companion;
+      companion.method();
+    }
+  }
+
+  static class Companion {
+
+    @NeverInline
+    public void method() {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderTest.java
new file mode 100644
index 0000000..0dcda2d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderTest.java
@@ -0,0 +1,60 @@
+package com.android.tools.r8.ir.optimize.string;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+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.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UnusedStringBuilderTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public UnusedStringBuilderTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(UnusedStringBuilderTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccess();
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+
+    MethodSubject methodSubject = testClassSubject.mainMethod();
+    assertThat(methodSubject, isPresent());
+    assertTrue(methodSubject.streamInstructions().allMatch(InstructionSubject::isReturnVoid));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      String.valueOf(new StringBuilder().append("x=").append(42));
+      new StringBuilder().append("x=").append(42).toString();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
index 6087795..6fb984f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -53,7 +53,6 @@
           // The Util class is there, but its instance methods have been inlined.
           ClassSubject utilClass = inspector.clazz("class_staticizer.Util");
           assertThat(utilClass, isPresent());
-          AtomicInteger nonStaticMethodCount = new AtomicInteger();
           assertTrue(
               utilClass.allMethods().stream()
                   .filter(Predicates.not(FoundMethodSubject::isStatic))
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
index 7514ff5..9d75aaa 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
@@ -36,7 +36,12 @@
 public class MetadataRewriteInCompanionTest extends KotlinMetadataTestBase {
   private static final String EXPECTED =
       StringUtils.lines(
-          "B.Companion::foo", "B.Companion::foo", "B.Companion::foo", "B.Companion::foo");
+          "B.Companion::foo",
+          "B.Companion::foo",
+          "B.Companion::foo",
+          "B.Companion::foo",
+          "B.Companion::bar",
+          "Hello World!");
 
   private final TestParameters parameters;
 
@@ -125,8 +130,8 @@
             // Property in companion with @JvmField is defined in the host class, without accessors.
             .addKeepRules("-keepclassmembers class **.B { *** elt2; }")
             .addKeepRules("-keep class **.I { <methods>; }")
-            // Keep getters for B$Companion.(eltN|foo) which will be referenced at the app.
-            .addKeepRules("-keepclassmembers class **.B$* { *** get*(...); }")
+            // Keep getters/setters for B$Companion.(eltN|foo) which will be referenced at the app.
+            .addKeepRules("-keepclassmembers class **.B$* { *** get*(...); *** set*(...); }")
             // Keep the companion instance in the B class
             .addKeepRules("-keepclassmembers class **.B { *** Companion; }")
             // Keep the name of companion class
@@ -239,5 +244,9 @@
     MethodSubject fooGetter = companion.uniqueMethodWithName("getFoo");
     assertThat(fooGetter, isPresent());
     assertThat(fooGetter, not(isRenamed()));
+
+    MethodSubject barSetter = companion.uniqueMethodWithName("setBar");
+    assertThat(barSetter, isPresent());
+    assertThat(barSetter, not(isRenamed()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
index 07fbdd9..3d3d935 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
@@ -5,13 +5,11 @@
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -25,7 +23,28 @@
 @RunWith(Parameterized.class)
 public class MetadataRewriteInTypeArgumentsTest extends KotlinMetadataTestBase {
   private static final String EXPECTED =
-      StringUtils.lines("42", "1", "42", "42", "1", "42", "42", "42", "1", "42");
+      StringUtils.lines(
+          "Hello World!",
+          "42",
+          "1",
+          "42",
+          "42",
+          "1",
+          "42",
+          "42",
+          "42",
+          "1",
+          "42",
+          "1",
+          "42",
+          "42",
+          "42",
+          "42",
+          "42",
+          "1",
+          "2",
+          "7",
+          "42");
 
   private final TestParameters parameters;
 
@@ -50,6 +69,7 @@
       Path typeAliasLibJar =
           kotlinc(KOTLINC, targetVersion)
               .addSourceFiles(getKotlinFileInTest(typeAliasLibFolder, "lib"))
+              .addSourceFiles(getKotlinFileInTest(typeAliasLibFolder, "lib_minified"))
               .compile();
       jarMap.put(targetVersion, typeAliasLibJar);
     }
@@ -74,26 +94,37 @@
   }
 
   @Test
-  public void testMetadataInTypeAlias_renamed() throws Exception {
+  public void testMetadataInTypeAlias_keepAll() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
             .addProgramFiles(jarMap.get(targetVersion))
             .addKeepAllClassesRule()
+            .addKeepAttributes(
+                ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+                ProguardKeepAttributes.SIGNATURE,
+                ProguardKeepAttributes.INNER_CLASSES,
+                ProguardKeepAttributes.ENCLOSING_METHOD)
             .compile()
-            // TODO(b/151925520): Add inspections when program compiles
+            // TODO(b/151925520): Add inspections when program compiles correctly.
+            // TODO(mkroghj): Also inspect the renaming of lib_minified
+            //  (not now, but when program compiles correctly).
             .writeToZip();
 
-    ProcessResult kotlinTestCompileResult =
+    Path mainJar =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/typeargument_app", "main"))
-            .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/151925520): update to just .compile() once fixed.
-            .compileRaw();
-    // TODO(b/151925520): should be able to compile!
-    assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    assertThat(
-        kotlinTestCompileResult.stderr,
-        containsString("no type arguments expected for constructor Invariant()"));
+            .compile();
+
+    // TODO(b/152306391): Reified type-parameters are not flagged correctly.
+    testForJvm()
+        .addProgramFiles(mainJar)
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addRunClasspathFiles(libJar)
+        .run(parameters.getRuntime(), PKG + ".typeargument_app.MainKt")
+        .assertFailureWithErrorThatMatches(
+            containsString(
+                "This function has a reified type parameter and thus can only be inlined at"
+                    + " compilation time, not called directly"));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt
index 403021e..ef4e23f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt
@@ -10,4 +10,7 @@
   B.elt1.doStuff()
   B.elt2.doStuff()
   println(B.foo)
+  println(B.bar)
+  B.bar = "Hello World!";
+  println(B.bar)
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
index 8e69219..54db448 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
@@ -24,5 +24,10 @@
     val elt2: Super = B()
     val foo: String
       get() = "B.Companion::foo"
+    var bar : String = "B.Companion::bar"
+      get() = field
+      set(value) {
+        field = value
+      }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_app/main.kt
index 7fc2b69..c8f4a72 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_app/main.kt
@@ -8,7 +8,10 @@
 import com.android.tools.r8.kotlin.metadata.typeargument_lib.ContraVariant
 import com.android.tools.r8.kotlin.metadata.typeargument_lib.Invariant
 import com.android.tools.r8.kotlin.metadata.typeargument_lib.SomeClass
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.asList
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.asObfuscatedClass
 import com.android.tools.r8.kotlin.metadata.typeargument_lib.unBoxAndBox
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.unboxAndPutInBox
 import com.android.tools.r8.kotlin.metadata.typeargument_lib.update
 
 class SomeSubClass(val x : Int) : SomeClass(), Comparable<SomeClass> {
@@ -24,7 +27,7 @@
 fun testInvariant() {
   val subtype1 = SomeSubClass(42)
   val subtype2 = SomeSubClass(1)
-  val inv = Invariant<SomeSubClass>()
+  val inv = Invariant<SomeSubClass, String>("Hello World!")
   println(inv.classGenerics(subtype1).x)
   println(inv.funGenerics(subtype2).x)
   println(inv.funGenericsWithUpperBound(subtype1).x)
@@ -43,6 +46,12 @@
 fun testExtension() {
   println(CoVariant(42).unBoxAndBox().t)
   println(CoVariant(1).update(42).t)
+  println(CoVariant(1).unboxAndPutInBox(CoVariant(42)).t)
+  println(CoVariant(42).asList().t.get(0))
+  val asList = CoVariant(42).asList(1, 2)
+  println(asList.t.get(0))
+  println(asList.t.get(1))
+  println(CoVariant(7).asObfuscatedClass().t.get(0).get(0).x)
 }
 
 fun main() {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib.kt
index 7c8b6cb..38234b4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib.kt
@@ -6,7 +6,11 @@
 
 open class SomeClass
 
-class Invariant<T> {
+class Invariant<T, C> {
+
+  constructor(someValue : C) {
+    println(someValue)
+  }
 
   fun classGenerics(t : T) : T {
     return t
@@ -52,7 +56,6 @@
   }
 }
 
-
 fun <T> CoVariant<T>.unBoxAndBox() : CoVariant<T> {
   return CoVariant(this.t)
 }
@@ -61,3 +64,24 @@
   println(this.t)
   return CoVariant(t)
 }
+
+fun <T> CoVariant<T>.unboxAndPutInBox(box : CoVariant<T>) : CoVariant<T> {
+  println(this.t)
+  println(box.t)
+  return CoVariant(box.t)
+}
+
+inline fun <reified T> CoVariant<T>.asList() : CoVariant<Array<T>> {
+  println(this.t)
+  return CoVariant(arrayOf(this.t))
+}
+
+inline fun <reified T> CoVariant<T>.asList(vararg ts : T) : CoVariant<Array<out T>> {
+  println(this.t)
+  return CoVariant(ts)
+}
+
+fun <T> CoVariant<T>.asObfuscatedClass() : CoVariant<Array<Array<ClassThatWillBeObfuscated>>> {
+  println(this.t)
+  return CoVariant(arrayOf(arrayOf(ClassThatWillBeObfuscated(42))))
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib_minified.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib_minified.kt
new file mode 100644
index 0000000..78c3742
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib_minified.kt
@@ -0,0 +1,3 @@
+package com.android.tools.r8.kotlin.metadata.typeargument_lib
+
+class ClassThatWillBeObfuscated(val x : Int)
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingConflictTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingConflictTest.java
index e081a44..b1ae543 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingConflictTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingConflictTest.java
@@ -4,11 +4,8 @@
 
 package com.android.tools.r8.memberrebinding;
 
-import static org.hamcrest.core.StringContains.containsString;
-
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -35,36 +32,28 @@
 
   @Test
   public void test() throws Exception {
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(A.class, TestClass.class)
-            .addProgramClassFileData(
-                transformer(B.class)
-                    .removeMethods(
-                        (access, name, descriptor, signature, exceptions) -> {
-                          if (name.equals("foo")) {
-                            assert MethodAccessFlags.fromCfAccessFlags(access, false).isSynthetic();
-                            return true;
-                          }
-                          return false;
-                        })
-                    .transform())
-            .addInnerClasses(MemberRebindingConflictTestClasses.class)
-            .addKeepMainRule(TestClass.class)
-            .enableInliningAnnotations()
-            .enableMergeAnnotations()
-            .enableNeverClassInliningAnnotations()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .run(parameters.getRuntime(), TestClass.class);
-
-    if (parameters.isDexRuntime()
-        && parameters.getRuntime().asDex().getVm().getVersion().isDalvik()) {
-      result.assertSuccessWithOutputLines("foo", "bar", "foo", "baz");
-    } else {
-      result.assertFailureWithErrorThatMatches(
-          containsString(IllegalAccessError.class.getTypeName()));
-    }
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, TestClass.class)
+        .addProgramClassFileData(
+            transformer(B.class)
+                .removeMethods(
+                    (access, name, descriptor, signature, exceptions) -> {
+                      if (name.equals("foo")) {
+                        assert MethodAccessFlags.fromCfAccessFlags(access, false).isSynthetic();
+                        return true;
+                      }
+                      return false;
+                    })
+                .transform())
+        .addInnerClasses(MemberRebindingConflictTestClasses.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("foo", "bar", "foo", "baz");
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
index 32c754e..0556439 100644
--- a/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
+++ b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
@@ -276,12 +276,8 @@
     // compilation (R8) will eliminate field loads on non-volatile fields.
     assertEquals(1, countIget(mfOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
     assertEquals(1, countSget(msfOnA.getMethod().getCode().asDexCode(), sfOnA.getField().field));
-    // TODO(111380066). This could be 2 in stead of 4, but right now the optimization tracks the
-    // combined set of fields for all successors, and for synchronized code all blocks have
-    // exceptional edges for ensuring monitor exit causing the active load to be invalidated for
-    // both normal and exceptional successors.
-    assertEquals(4,
-        countIget(mfWithMonitorOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
+    assertEquals(
+        2, countIget(mfWithMonitorOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
 
     // For fields on other class both separate compilation (D8) and whole program
     // compilation (R8) will differ in the eliminated field loads of non-volatile fields.
diff --git a/src/test/java/com/android/tools/r8/regress/b151964517/ConstStringWithMonitorTest.java b/src/test/java/com/android/tools/r8/regress/b151964517/ConstStringWithMonitorTest.java
new file mode 100644
index 0000000..7c8aa34
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b151964517/ConstStringWithMonitorTest.java
@@ -0,0 +1,74 @@
+// 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.regress.b151964517;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ConstStringWithMonitorTest extends TestBase {
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().build();
+  }
+
+  public ConstStringWithMonitorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void regress() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ConstStringWithMonitorTest.class)
+        .noMinification()
+        .allowAccessModification()
+        .addKeepMainRule(TestClass.class)
+        .compile()
+        .runDex2Oat(parameters.getRuntime());
+    // TODO(b/151964517): Should pass verification with assertNoVerificationErrors()
+  }
+
+  public static class TestClass {
+    public static void main(String[] args) {
+      try {
+        System.out.println(FooBar.tryIt());
+      } catch (IllegalStateException e) {
+        e.printStackTrace();
+      }
+    }
+  }
+
+  public static class Value {
+    public final String value;
+
+    public Value(String value) {
+      this.value = value;
+    }
+  }
+
+  public static class FooBar {
+    private static Object mLock = new Object();
+
+    public static String tryIt() {
+      Value value = synchronizedMethod("foobar");
+      return value.value;
+    }
+
+    private static Value synchronizedMethod(String s) {
+      synchronized (mLock) {
+        if (System.currentTimeMillis() < 2) {
+          s.length();
+          throw new IllegalStateException("wrong" + s);
+        }
+        return new Value(s);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index 0d89583..7bd9cc9 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -186,8 +186,7 @@
       Assert.assertNull(singleVirtualTarget);
     } else {
       Assert.assertNotNull(singleVirtualTarget);
-      Assert.assertEquals(
-          toType(singleTargetHolderOrNull, appInfo), singleVirtualTarget.method.holder);
+      Assert.assertEquals(toType(singleTargetHolderOrNull, appInfo), singleVirtualTarget.holder());
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
index a9dc70f..18cbb66 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
@@ -133,7 +133,7 @@
     DexEncodedMethod targetSuper =
         resolutionResult.lookupInvokeSuperTarget(callerClassDefinition, appInfo);
     if (inSameNest) {
-      assertEquals(definingClassDefinition.type, targetSpecial.method.holder);
+      assertEquals(definingClassDefinition.type, targetSpecial.holder());
       assertEquals(targetSpecial, targetSuper);
     } else {
       assertNull(targetSpecial);
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
index 490b7dc..58575d8 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
@@ -109,7 +109,7 @@
     DexEncodedMethod targetSuper =
         resolutionResult.lookupInvokeSuperTarget(callerClassDefinition, appInfo);
     if (inSameNest) {
-      assertEquals(definingClassDefinition.type, targetSpecial.method.holder);
+      assertEquals(definingClassDefinition.type, targetSpecial.holder());
       assertEquals(targetSpecial, targetSuper);
     } else {
       assertNull(targetSpecial);
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
index 8e21be0..79e8f76 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
@@ -142,7 +142,7 @@
     DexEncodedMethod targetSuper =
         resolutionResult.lookupInvokeSuperTarget(callerClassDefinition, appInfo);
     if (inSameNest && symbolicReferenceIsDefiningType) {
-      assertEquals(definingClassDefinition.type, targetSpecial.method.holder);
+      assertEquals(definingClassDefinition.type, targetSpecial.holder());
       assertEquals(targetSpecial, targetSuper);
     } else {
       assertNull(targetSpecial);
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java
index 49426c4..db75b6e 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java
@@ -108,7 +108,7 @@
     // Verify that looking up the dispatch target returns the defining method.
     DexEncodedMethod targetSpecial =
         resolutionResult.lookupInvokeSpecialTarget(callerClassDefinition, appInfo);
-    assertEquals(definingClassDefinition.type, targetSpecial.method.holder);
+    assertEquals(definingClassDefinition.type, targetSpecial.holder());
 
     DexEncodedMethod targetSuper =
         resolutionResult.lookupInvokeSuperTarget(callerClassDefinition, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
index 005f292..b6023ce 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
@@ -48,7 +48,7 @@
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     // Currently R8 will resolve to L::f as that is the first in the topological search.
     // Resolution may return any of the matches, so it is valid if this expectation changes.
-    assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
+    assertEquals(L.class.getTypeName(), resolutionTarget.holder().toSourceString());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
index 0c3ed4c..2068ef1 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
@@ -51,7 +51,7 @@
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
-    assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
+    assertEquals(L.class.getTypeName(), resolutionTarget.holder().toSourceString());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
index 42f7600..64908c4 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
@@ -51,7 +51,7 @@
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
-    assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
+    assertEquals(R.class.getTypeName(), resolutionTarget.holder().toSourceString());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
index c29509d..4841f77 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
@@ -54,7 +54,7 @@
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
-    assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
+    assertEquals(L.class.getTypeName(), resolutionTarget.holder().toSourceString());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
index e7c5ab8..6059ea1 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
@@ -54,7 +54,7 @@
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
-    assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
+    assertEquals(R.class.getTypeName(), resolutionTarget.holder().toSourceString());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
index 3d7071d..433575f 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
@@ -55,8 +55,7 @@
     Set<String> holders = new HashSet<>();
     resolutionResult
         .asFailedResolution()
-        .forEachFailureDependency(
-            target -> holders.add(target.method.holder.toSourceString()));
+        .forEachFailureDependency(target -> holders.add(target.holder().toSourceString()));
     assertEquals(ImmutableSet.of(L.class.getTypeName(), R.class.getTypeName()), holders);
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
index b5c2018..e04908b 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
@@ -45,7 +45,7 @@
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
-    assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
+    assertEquals(L.class.getTypeName(), resolutionTarget.holder().toSourceString());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
index 867cfe9..7472e89 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
@@ -45,7 +45,7 @@
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
-    assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
+    assertEquals(R.class.getTypeName(), resolutionTarget.holder().toSourceString());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
index b2a4ce2..5966b9d 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
@@ -56,8 +56,7 @@
     Set<String> holders = new HashSet<>();
     resolutionResult
         .asFailedResolution()
-        .forEachFailureDependency(
-            m -> holders.add(m.method.holder.toSourceString()));
+        .forEachFailureDependency(m -> holders.add(m.holder().toSourceString()));
     assertEquals(ImmutableSet.of(I.class.getTypeName(), J.class.getTypeName()), holders);
   }
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 91f0c4d..ae4947a 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -85,6 +85,7 @@
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inSwitch"), 11);
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), 1);
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), 1);
+      assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("nonValueStaticField"), 1);
     } else {
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("simple"));
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("local"));
@@ -96,7 +97,6 @@
     }
 
     assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
-    assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
     assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("phi"));
   }
 
@@ -129,6 +129,7 @@
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("inlined"), "TWO");
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), "DOWN");
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), "TWO");
+      assertNameReplacedWithConst(clazz.uniqueMethodWithName("nonValueStaticField"), "TWO");
     } else {
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("simple"));
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("local"));
@@ -141,7 +142,6 @@
     // TODO(jakew) this should be allowed!
     assertNameWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
 
-    assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
     assertNameWasNotReplaced(clazz.uniqueMethodWithName("phi"));
   }
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
index 2ecc9eb..5dd5ec0 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
@@ -63,6 +63,7 @@
     return Number.DOWN.name();
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
   private static String nonValueStaticField() {
     return Number.DEFAULT.name();
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java b/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
index 534b16c..e1eee2c 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
@@ -73,6 +73,7 @@
     return Number.DOWN.ordinal();
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
   private static long nonValueStaticField() {
     return Number.DEFAULT.ordinal();
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
new file mode 100644
index 0000000..fe72a94
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
@@ -0,0 +1,108 @@
+// 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.shaking.assumenosideeffects;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class B152492625 extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public B152492625(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private void noCallToWait(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    assertThat(classSubject, isPresent());
+    classSubject.forAllMethods(
+        foundMethodSubject ->
+            foundMethodSubject
+                .instructions(InstructionSubject::isInvokeVirtual)
+                .forEach(
+                    instructionSubject -> {
+                      Assert.assertNotEquals(
+                          "wait", instructionSubject.getMethod().name.toString());
+                    }));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(B152492625.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::noCallToWait)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+  }
+
+  @Test
+  public void testProguardNotRemovingWait() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
+
+    testForProguard()
+        .addInnerClasses(B152492625.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
+        .addKeepRules("-dontwarn " + B152492625.class.getTypeName())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(IllegalMonitorStateException.class);
+  }
+
+  @Test
+  public void testProguardRemovingWait() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
+
+    testForProguard()
+        .addInnerClasses(B152492625.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-assumenosideeffects class java.lang.Object { void wait(); }")
+        .addKeepRules("-dontwarn " + B152492625.class.getTypeName())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::noCallToWait)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+  }
+
+  static class TestClass {
+
+    public void m() throws Exception {
+      System.out.println("Hello, world");
+      // test fails if wait is not removed.
+      wait();
+    }
+
+    public static void main(String[] args) throws Exception {
+      new TestClass().m();
+    }
+  }
+
+  static class B {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
index 2387e7c..de762ac 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.StringUtils;
-import java.io.ByteArrayOutputStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.junit.Test;
@@ -46,7 +45,6 @@
 
   @Test
   public void test() throws Throwable {
-    ByteArrayOutputStream baos = new ByteArrayOutputStream();
     testForR8(Backend.CF)
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
         .addKeepRuleFiles(MAIN_KEEP)
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 98f6b1e..3bbea86 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -102,8 +102,14 @@
 
       @Override
       public void describeMismatchSafely(final Subject subject, Description description) {
-        description
-            .appendText(type(subject) + " ").appendValue(name(subject)).appendText(" was not");
+        if (subject instanceof ClassSubject || subject instanceof MemberSubject) {
+          description
+              .appendText(type(subject) + " ")
+              .appendValue(name(subject))
+              .appendText(" was not");
+        } else {
+          description.appendText(type(subject) + " ").appendText(" was not found");
+        }
       }
     };
   }
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 6d6f0fe..9182539 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -737,6 +737,8 @@
           '-Pandroid.enableR8.fullMode=' + str(IsR8FullMode(shrinker)).lower()]
   if app.has_lint_task:
     args.extend(['-x', app_module + ':lintVital' + app_flavor])
+  if options.bot:
+    args.extend(['--console=plain', '--info'])
 
   # Warm up gradle if pre_runs > 0. For posterity we generate the same sequence
   # as the benchmarking at https://github.com/madsager/santa-tracker-android.
diff --git a/tools/update_prebuilds_in_android.py b/tools/update_prebuilds_in_android.py
index f3a0923..ee2b100 100755
--- a/tools/update_prebuilds_in_android.py
+++ b/tools/update_prebuilds_in_android.py
@@ -8,12 +8,8 @@
 import os
 from shutil import copyfile
 import sys
-import tempfile
 import utils
-import urllib
-
-BUILD_ROOT = "https://storage.googleapis.com/r8-releases/raw/"
-MASTER_BUILD_ROOT = "%smaster/" % BUILD_ROOT
+import archive
 
 JAR_TARGETS_MAP = {
   'full': [
@@ -66,8 +62,6 @@
       print ('WARNING: Not copying ' + src + ' -> ' + dest +
              ', as' + dest + ' does not exist already')
 
-
-
 def copy_jar_targets(root, target_root, jar_targets, maps):
   srcs = map((lambda t: t[0] + '.jar'), jar_targets)
   dests = map((lambda t: t[1] + '.jar'), jar_targets)
@@ -77,19 +71,19 @@
   copy_targets(root, target_root, OTHER_TARGETS, OTHER_TARGETS)
 
 def download_hash(root, commit_hash, target):
-  url = MASTER_BUILD_ROOT + commit_hash + '/' + target
-  download_target(root, url, target)
+  download_target(root, target, commit_hash, True)
 
 def download_version(root, version, target):
-  url = BUILD_ROOT + version + '/' + target
-  download_target(root, url, target)
+  download_target(root, target, version, False)
 
-def download_target(root, url, target):
+def download_target(root, target, hash_or_version, is_hash):
   download_path = os.path.join(root, target)
+  url = archive.GetUploadDestination(
+    hash_or_version,
+    target,
+    is_hash)
   print 'Downloading: ' + url + ' -> ' + download_path
-  result = urllib.urlretrieve(url, download_path)
-  if 'X-GUploader-Request-Result: success' not in str(result[1]):
-    raise IOError('Failed to download ' + url)
+  utils.download_file_from_cloud_storage(url, download_path)
 
 def main_download(hash, maps, targets, target_root, version):
   jar_targets = JAR_TARGETS_MAP[targets]