Merge commit 'a350cb5886deb3c5486c3a9ce5dd948f380ab515' into dev-release
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 7837f26..3b9bb74 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -19,11 +19,7 @@
       return [output_api.PresubmitPromptWarning(msg, [])]
   return []
 
-def CheckFormatting(input_api, output_api):
-  branch = (
-      check_output(['git', 'cl', 'upstream'])
-          .strip()
-          .replace('refs/heads/', ''))
+def CheckFormatting(input_api, output_api, branch):
   results = []
   for f in input_api.AffectedFiles():
     path = f.LocalPath()
@@ -47,26 +43,58 @@
   """ % FMT_CMD))
   return results
 
-def CheckDeterministicDebuggingChanged(input_api, output_api):
+def CheckDeterministicDebuggingChanged(input_api, output_api, branch):
   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):
+def CheckForAddedDisassemble(input_api, output_api):
   results = []
-  results.extend(CheckFormatting(input_api, output_api))
+  for (file, line_nr, line) in input_api.RightHandSideLines():
+    if 'disassemble()' in line:
+      results.append(
+          output_api.PresubmitError(
+              '%s:%s %s' % (file.LocalPath(), line_nr, line)))
+  return results
+
+def CheckForCopyRight(input_api, output_api, branch):
+  results = []
+  for f in input_api.AffectedSourceFiles(None):
+    # Check if it is a new file.
+    if f.OldContents():
+      continue
+    contents = f.NewContents()
+    if (not contents) or (len(contents) == 0):
+      continue
+    if not CopyRightInContents(contents):
+      results.append(
+          output_api.PresubmitError('Could not find Copyright in file: %s' % f))
+  return results
+
+def CopyRightInContents(contents):
+  for content_line in contents:
+    if '// Copyright' in content_line:
+      return True
+  return False
+
+def CheckChange(input_api, output_api):
+  branch = (
+      check_output(['git', 'cl', 'upstream'])
+          .strip()
+          .replace('refs/heads/', ''))
+  results = []
   results.extend(CheckDoNotMerge(input_api, output_api))
-  results.extend(CheckDeterministicDebuggingChanged(input_api, output_api))
+  results.extend(CheckFormatting(input_api, output_api, branch))
+  results.extend(
+      CheckDeterministicDebuggingChanged(input_api, output_api, branch))
+  results.extend(CheckForAddedDisassemble(input_api, output_api))
+  results.extend(CheckForCopyRight(input_api, output_api, branch))
   return results
 
 def CheckChangeOnCommit(input_api, output_api):
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 1ce0024..615ca13 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.InitClassLens;
@@ -174,11 +175,15 @@
         ClassInitializerAssertionEnablingAnalysis analysis =
             new ClassInitializerAssertionEnablingAnalysis(
                 appInfo.dexItemFactory(), OptimizationFeedbackSimple.getInstance());
-        for (DexProgramClass clazz : appInfo.classes()) {
-          if (clazz.hasClassInitializer()) {
-            analysis.processNewlyLiveMethod(clazz.getClassInitializer());
-          }
-        }
+        ThreadUtils.processItems(
+            appInfo.classes(),
+            clazz -> {
+              DexEncodedMethod classInitializer = clazz.getClassInitializer();
+              if (classInitializer != null) {
+                analysis.processNewlyLiveMethod(classInitializer);
+              }
+            },
+            executor);
       }
 
       AppView<?> appView = AppView.createForD8(appInfo, options, rewritePrefix);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 9673bc2..2f6197f 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -69,6 +69,8 @@
     private DesugarGraphConsumer desugarGraphConsumer = null;
     private StringConsumer desugaredLibraryKeepRuleConsumer = null;
     private String synthesizedClassPrefix = "";
+    private boolean enableMainDexListCheck = true;
+    private boolean minimalMainDex = false;
 
     private Builder() {
       this(new DefaultD8DiagnosticsHandler());
@@ -174,6 +176,20 @@
       return self();
     }
 
+    @Deprecated
+    // Internal helper for supporting bazel integration.
+    Builder setEnableMainDexListCheck(boolean value) {
+      enableMainDexListCheck = value;
+      return self();
+    }
+
+    @Deprecated
+    // Internal helper for supporting bazel integration.
+    Builder setMinimalMainDex(boolean value) {
+      minimalMainDex = value;
+      return self();
+    }
+
     @Override
     void validate() {
       Reporter reporter = getReporter();
@@ -234,6 +250,8 @@
           getAssertionsConfiguration(),
           getOutputInspections(),
           synthesizedClassPrefix,
+          enableMainDexListCheck,
+          minimalMainDex,
           getThreadCount(),
           factory);
     }
@@ -246,6 +264,8 @@
   private final StringConsumer desugaredLibraryKeepRuleConsumer;
   private final DesugaredLibraryConfiguration libraryConfiguration;
   private final String synthesizedClassPrefix;
+  private final boolean enableMainDexListCheck;
+  private final boolean minimalMainDex;
   private final DexItemFactory factory;
 
   public static Builder builder() {
@@ -306,6 +326,8 @@
       List<AssertionsConfiguration> assertionsConfiguration,
       List<Consumer<Inspector>> outputInspections,
       String synthesizedClassPrefix,
+      boolean enableMainDexListCheck,
+      boolean minimalMainDex,
       int threadCount,
       DexItemFactory factory) {
     super(
@@ -327,6 +349,8 @@
     this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
     this.libraryConfiguration = libraryConfiguration;
     this.synthesizedClassPrefix = synthesizedClassPrefix;
+    this.enableMainDexListCheck = enableMainDexListCheck;
+    this.minimalMainDex = minimalMainDex;
     this.factory = factory;
   }
 
@@ -337,6 +361,8 @@
     desugaredLibraryKeepRuleConsumer = null;
     libraryConfiguration = null;
     synthesizedClassPrefix = null;
+    enableMainDexListCheck = true;
+    minimalMainDex = false;
     factory = null;
   }
 
@@ -350,7 +376,8 @@
       internal.enableCfInterfaceMethodDesugaring = true;
     }
     internal.mainDexListConsumer = getMainDexListConsumer();
-    internal.minimalMainDex = internal.debug;
+    internal.minimalMainDex = internal.debug || minimalMainDex;
+    internal.enableMainDexListCheck = enableMainDexListCheck;
     internal.minApiLevel = getMinApiLevel();
     internal.intermediate = intermediate;
     internal.readCompileTimeAnnotations = intermediate;
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index e504e54..20216c4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
-import com.android.tools.r8.ir.optimize.library.LibraryMethodOptimizer;
+import com.android.tools.r8.ir.optimize.library.LibraryMemberOptimizer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.LibraryModeledPredicate;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -57,7 +57,7 @@
 
   // Optimizations.
   private final CallSiteOptimizationInfoPropagator callSiteOptimizationInfoPropagator;
-  private final LibraryMethodOptimizer libraryMethodOptimizer;
+  private final LibraryMemberOptimizer libraryMemberOptimizer;
   private final ProtoShrinker protoShrinker;
 
   // Optimization results.
@@ -102,7 +102,7 @@
       this.callSiteOptimizationInfoPropagator = null;
     }
 
-    this.libraryMethodOptimizer = new LibraryMethodOptimizer(this);
+    this.libraryMemberOptimizer = new LibraryMemberOptimizer(this);
 
     if (enableWholeProgramOptimizations() && options.protoShrinking().isProtoShrinkingEnabled()) {
       this.protoShrinker = new ProtoShrinker(withLiveness());
@@ -113,7 +113,7 @@
 
   @Override
   public boolean isModeled(DexType type) {
-    return libraryMethodOptimizer.isModeled(type);
+    return libraryMemberOptimizer.isModeled(type);
   }
 
   public static <T extends AppInfo> AppView<T> createForD8(T appInfo, InternalOptions options) {
@@ -247,8 +247,8 @@
     return callSiteOptimizationInfoPropagator;
   }
 
-  public LibraryMethodOptimizer libraryMethodOptimizer() {
-    return libraryMethodOptimizer;
+  public LibraryMemberOptimizer libraryMethodOptimizer() {
+    return libraryMemberOptimizer;
   }
 
   public ProtoShrinker protoShrinker() {
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 4eb9a85..968b1c9 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.utils.StringUtils.LINE_SEPARATOR;
+
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
@@ -15,7 +17,10 @@
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
+import java.io.BufferedReader;
 import java.io.PrintStream;
+import java.io.StringReader;
+import java.util.stream.Collectors;
 
 public class AssemblyWriter extends DexByteCodeWriter {
 
@@ -149,8 +154,14 @@
             assert clazz != null : "Kotlin metadata is a class annotation";
             writeKotlinMetadata(clazz, annotation, ps);
           } else {
-            ps.print("#   ");
-            ps.println(annotation);
+            String annotationString = annotation.toString();
+            String prefix = "#  ";
+            ps.print(
+                new BufferedReader(new StringReader(annotationString))
+                    .lines()
+                    .collect(
+                        Collectors.joining(
+                            LINE_SEPARATOR + prefix + "  ", prefix, LINE_SEPARATOR)));
           }
         }
       }
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 897763a..93ca81d 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -551,7 +551,7 @@
       // We have no debugging info in the code.
       return;
     }
-    int largestPrefix = 1;
+    int largestPrefix = 0;
     int existingThisIndex = -1;
     for (int i = 0; i < localVariables.size(); i++) {
       LocalVariableInfo localVariable = localVariables.get(i);
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 bb1ee15..20337fd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -666,8 +666,7 @@
   public void upgradeClassFileVersion(int version) {
     checkIfObsolete();
     assert version >= 0;
-    assert !hasClassFileVersion() || version >= getClassFileVersion();
-    classFileVersion = version;
+    classFileVersion = Math.max(classFileVersion, version);
   }
 
   public String qualifiedName() {
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 b0a86c6..b2d7140 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -398,12 +398,34 @@
   public final DexType reflectiveOperationExceptionType =
       createStaticallyKnownType(reflectiveOperationExceptionDescriptor);
 
+  public final DexType javaIoFileType = createStaticallyKnownType("Ljava/io/File;");
+  public final DexType javaMathBigIntegerType = createStaticallyKnownType("Ljava/math/BigInteger;");
+  public final DexType javaNioByteOrderType = createStaticallyKnownType("Ljava/nio/ByteOrder;");
+  public final DexType javaUtilCollectionsType =
+      createStaticallyKnownType("Ljava/util/Collections;");
+  public final DexType javaUtilComparatorType = createStaticallyKnownType("Ljava/util/Comparator;");
+  public final DexType javaUtilConcurrentTimeUnitType =
+      createStaticallyKnownType("Ljava/util/concurrent/TimeUnit;");
+  public final DexType javaUtilListType = createStaticallyKnownType("Ljava/util/List;");
+  public final DexType javaUtilLocaleType = createStaticallyKnownType("Ljava/util/Locale;");
+  public final DexType javaUtilLoggingLevelType =
+      createStaticallyKnownType("Ljava/util/logging/Level;");
   public final DexType javaUtilLoggingLoggerType =
       createStaticallyKnownType("Ljava/util/logging/Logger;");
-  public final DexType androidUtilLogType = createStaticallyKnownType("Landroid/util/Log;");
+  public final DexType javaUtilSetType = createStaticallyKnownType("Ljava/util/Set;");
 
+  public final DexType androidOsBuildType = createStaticallyKnownType("Landroid/os/Build;");
   public final DexType androidOsBuildVersionType =
       createStaticallyKnownType("Landroid/os/Build$VERSION;");
+  public final DexType androidOsBundleType = createStaticallyKnownType("Landroid/os/Bundle;");
+  public final DexType androidOsParcelableCreatorType =
+      createStaticallyKnownType("Landroid/os/Parcelable$Creator;");
+  public final DexType androidSystemOsConstantsType =
+      createStaticallyKnownType("Landroid/system/OsConstants;");
+  public final DexType androidUtilLogType = createStaticallyKnownType("Landroid/util/Log;");
+  public final DexType androidUtilPropertyType =
+      createStaticallyKnownType("Landroid/util/Property;");
+  public final DexType androidViewViewType = createStaticallyKnownType("Landroid/view/View;");
 
   public final DexString nestConstructorDescriptor =
       createString("L" + NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME + ";");
@@ -419,12 +441,13 @@
   public final StringBuildingMethods stringBufferMethods =
       new StringBuildingMethods(stringBufferType);
   public final BooleanMembers booleanMembers = new BooleanMembers();
+  public final FloatMembers floatMembers = new FloatMembers();
+  public final IntegerMembers integerMembers = new IntegerMembers();
   public final ObjectsMethods objectsMethods = new ObjectsMethods();
   public final ObjectMembers objectMembers = new ObjectMembers();
-  public final StringMethods stringMethods = new StringMethods();
-  public final LongMethods longMethods = new LongMethods();
+  public final StringMembers stringMembers = new StringMembers();
+  public final LongMembers longMembers = new LongMembers();
   public final DoubleMethods doubleMethods = new DoubleMethods();
-  public final JavaUtilArraysMethods utilArraysMethods = new JavaUtilArraysMethods();
   public final ThrowableMethods throwableMethods = new ThrowableMethods();
   public final AssertionErrorMethods assertionErrorMethods = new AssertionErrorMethods();
   public final ClassMethods classMethods = new ClassMethods();
@@ -441,6 +464,51 @@
   public final PolymorphicMethods polymorphicMethods = new PolymorphicMethods();
   public final ProxyMethods proxyMethods = new ProxyMethods();
 
+  // android.**
+  public final AndroidOsBuildMembers androidOsBuildMembers = new AndroidOsBuildMembers();
+  public final AndroidOsBuildVersionMembers androidOsBuildVersionMembers =
+      new AndroidOsBuildVersionMembers();
+  public final AndroidOsBundleMembers androidOsBundleMembers = new AndroidOsBundleMembers();
+  public final AndroidSystemOsConstantsMembers androidSystemOsConstantsMembers =
+      new AndroidSystemOsConstantsMembers();
+  public final AndroidViewViewMembers androidViewViewMembers = new AndroidViewViewMembers();
+
+  // java.**
+  public final JavaIoFileMembers javaIoFileMembers = new JavaIoFileMembers();
+  public final JavaMathBigIntegerMembers javaMathBigIntegerMembers =
+      new JavaMathBigIntegerMembers();
+  public final JavaNioByteOrderMembers javaNioByteOrderMembers = new JavaNioByteOrderMembers();
+  public final JavaUtilArraysMethods javaUtilArraysMethods = new JavaUtilArraysMethods();
+  public final JavaUtilComparatorMembers javaUtilComparatorMembers =
+      new JavaUtilComparatorMembers();
+  public final JavaUtilConcurrentTimeUnitMembers javaUtilConcurrentTimeUnitMembers =
+      new JavaUtilConcurrentTimeUnitMembers();
+  public final JavaUtilLocaleMembers javaUtilLocaleMembers = new JavaUtilLocaleMembers();
+  public final JavaUtilLoggingLevelMembers javaUtilLoggingLevelMembers =
+      new JavaUtilLoggingLevelMembers();
+
+  public final List<LibraryMembers> libraryMembersCollection =
+      ImmutableList.of(
+          booleanMembers,
+          floatMembers,
+          integerMembers,
+          longMembers,
+          stringMembers,
+          // android.**
+          androidOsBuildMembers,
+          androidOsBuildVersionMembers,
+          androidOsBundleMembers,
+          androidSystemOsConstantsMembers,
+          androidViewViewMembers,
+          // java.**
+          javaIoFileMembers,
+          javaMathBigIntegerMembers,
+          javaNioByteOrderMembers,
+          javaUtilComparatorMembers,
+          javaUtilConcurrentTimeUnitMembers,
+          javaUtilLocaleMembers,
+          javaUtilLoggingLevelMembers);
+
   public final DexString twrCloseResourceMethodName = createString("$closeResource");
   public final DexProto twrCloseResourceMethodProto =
       createProto(voidType, throwableType, autoCloseableType);
@@ -604,7 +672,7 @@
           objectsMethods.requireNonNull,
           objectsMethods.requireNonNullWithMessage,
           objectsMethods.requireNonNullWithMessageSupplier,
-          stringMethods.valueOf);
+          stringMembers.valueOf);
 
   // We assume library methods listed here are `public`, i.e., free from visibility side effects.
   // If not, that library method should not be added here because it literally has side effects.
@@ -672,7 +740,12 @@
     return dexMethod == metafactoryMethod || dexMethod == metafactoryAltMethod;
   }
 
-  public class BooleanMembers {
+  public interface LibraryMembers {
+
+    void forEachFinalField(Consumer<DexField> consumer);
+  }
+
+  public class BooleanMembers implements LibraryMembers {
 
     public final DexField FALSE = createField(boxedBooleanType, boxedBooleanType, "FALSE");
     public final DexField TRUE = createField(boxedBooleanType, boxedBooleanType, "TRUE");
@@ -684,16 +757,291 @@
         createMethod(boxedBooleanType, createProto(boxedBooleanType, booleanType), "valueOf");
 
     private BooleanMembers() {}
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(FALSE);
+      consumer.accept(TRUE);
+      consumer.accept(TYPE);
+    }
   }
 
-  public class LongMethods {
+  public class AndroidOsBuildMembers implements LibraryMembers {
+
+    public final DexField BOOTLOADER = createField(androidOsBuildType, stringType, "BOOTLOADER");
+    public final DexField BRAND = createField(androidOsBuildType, stringType, "BRAND");
+    public final DexField CPU_ABI = createField(androidOsBuildType, stringType, "CPU_ABI");
+    public final DexField CPU_ABI2 = createField(androidOsBuildType, stringType, "CPU_ABI2");
+    public final DexField DEVICE = createField(androidOsBuildType, stringType, "DEVICE");
+    public final DexField DISPLAY = createField(androidOsBuildType, stringType, "DISPLAY");
+    public final DexField FINGERPRINT = createField(androidOsBuildType, stringType, "FINGERPRINT");
+    public final DexField HARDWARE = createField(androidOsBuildType, stringType, "HARDWARE");
+    public final DexField MANUFACTURER =
+        createField(androidOsBuildType, stringType, "MANUFACTURER");
+    public final DexField MODEL = createField(androidOsBuildType, stringType, "MODEL");
+    public final DexField PRODUCT = createField(androidOsBuildType, stringType, "PRODUCT");
+    public final DexField SERIAL = createField(androidOsBuildType, stringType, "SERIAL");
+    public final DexField SUPPORTED_32_BIT_ABIS =
+        createField(androidOsBuildType, stringArrayType, "SUPPORTED_32_BIT_ABIS");
+    public final DexField SUPPORTED_64_BIT_ABIS =
+        createField(androidOsBuildType, stringArrayType, "SUPPORTED_64_BIT_ABIS");
+    public final DexField SUPPORTED_ABIS =
+        createField(androidOsBuildType, stringArrayType, "SUPPORTED_ABIS");
+    public final DexField TIME = createField(androidOsBuildType, longType, "TIME");
+    public final DexField TYPE = createField(androidOsBuildType, stringType, "TYPE");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(BOOTLOADER);
+      consumer.accept(BRAND);
+      consumer.accept(CPU_ABI);
+      consumer.accept(CPU_ABI2);
+      consumer.accept(DEVICE);
+      consumer.accept(DISPLAY);
+      consumer.accept(FINGERPRINT);
+      consumer.accept(HARDWARE);
+      consumer.accept(MANUFACTURER);
+      consumer.accept(MODEL);
+      consumer.accept(PRODUCT);
+      consumer.accept(SERIAL);
+      consumer.accept(SUPPORTED_32_BIT_ABIS);
+      consumer.accept(SUPPORTED_64_BIT_ABIS);
+      consumer.accept(SUPPORTED_ABIS);
+      consumer.accept(TIME);
+      consumer.accept(TYPE);
+    }
+  }
+
+  public class AndroidOsBuildVersionMembers implements LibraryMembers {
+
+    public final DexField CODENAME = createField(androidOsBuildVersionType, stringType, "CODENAME");
+    public final DexField RELEASE = createField(androidOsBuildVersionType, stringType, "RELEASE");
+    public final DexField SDK = createField(androidOsBuildVersionType, stringType, "SDK");
+    public final DexField SDK_INT = createField(androidOsBuildVersionType, intType, "SDK_INT");
+    public final DexField SECURITY_PATCH =
+        createField(androidOsBuildVersionType, stringType, "SECURITY_PATCH");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(CODENAME);
+      consumer.accept(RELEASE);
+      consumer.accept(SDK);
+      consumer.accept(SDK_INT);
+      consumer.accept(SECURITY_PATCH);
+    }
+  }
+
+  public class AndroidOsBundleMembers implements LibraryMembers {
+
+    public final DexField CREATOR =
+        createField(androidOsBundleType, androidOsParcelableCreatorType, "CREATOR");
+    public final DexField EMPTY = createField(androidOsBundleType, androidOsBundleType, "EMPTY");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(CREATOR);
+      consumer.accept(EMPTY);
+    }
+  }
+
+  public class AndroidSystemOsConstantsMembers implements LibraryMembers {
+
+    public final DexField S_IRUSR = createField(androidSystemOsConstantsType, intType, "S_IRUSR");
+    public final DexField S_IXUSR = createField(androidSystemOsConstantsType, intType, "S_IXUSR");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(S_IRUSR);
+      consumer.accept(S_IXUSR);
+    }
+  }
+
+  public class AndroidViewViewMembers implements LibraryMembers {
+
+    public final DexField TRANSLATION_Z =
+        createField(androidViewViewType, androidUtilPropertyType, "TRANSLATION_Z");
+    public final DexField EMPTY_STATE_SET =
+        createField(androidViewViewType, intArrayType, "EMPTY_STATE_SET");
+    public final DexField ENABLED_STATE_SET =
+        createField(androidViewViewType, intArrayType, "ENABLED_STATE_SET");
+    public final DexField PRESSED_ENABLED_STATE_SET =
+        createField(androidViewViewType, intArrayType, "PRESSED_ENABLED_STATE_SET");
+    public final DexField SELECTED_STATE_SET =
+        createField(androidViewViewType, intArrayType, "SELECTED_STATE_SET");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(TRANSLATION_Z);
+      consumer.accept(EMPTY_STATE_SET);
+      consumer.accept(ENABLED_STATE_SET);
+      consumer.accept(PRESSED_ENABLED_STATE_SET);
+      consumer.accept(SELECTED_STATE_SET);
+    }
+  }
+
+  public class FloatMembers implements LibraryMembers {
+
+    public final DexField TYPE = createField(boxedFloatType, classType, "TYPE");
+
+    private FloatMembers() {}
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(TYPE);
+    }
+  }
+
+  public class JavaIoFileMembers implements LibraryMembers {
+
+    public final DexField pathSeparator = createField(javaIoFileType, stringType, "pathSeparator");
+    public final DexField separator = createField(javaIoFileType, stringType, "separator");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(pathSeparator);
+      consumer.accept(separator);
+    }
+  }
+
+  public class JavaMathBigIntegerMembers implements LibraryMembers {
+
+    public final DexField ONE = createField(javaMathBigIntegerType, javaMathBigIntegerType, "ONE");
+    public final DexField ZERO =
+        createField(javaMathBigIntegerType, javaMathBigIntegerType, "ZERO");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(ONE);
+      consumer.accept(ZERO);
+    }
+  }
+
+  public class JavaNioByteOrderMembers implements LibraryMembers {
+
+    public final DexField LITTLE_ENDIAN =
+        createField(javaNioByteOrderType, javaNioByteOrderType, "LITTLE_ENDIAN");
+    public final DexField BIG_ENDIAN =
+        createField(javaNioByteOrderType, javaNioByteOrderType, "BIG_ENDIAN");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(LITTLE_ENDIAN);
+      consumer.accept(BIG_ENDIAN);
+    }
+  }
+
+  public class JavaUtilArraysMethods {
+
+    public final DexMethod asList;
+
+    private JavaUtilArraysMethods() {
+      asList =
+          createMethod(
+              arraysDescriptor,
+              createString("asList"),
+              listDescriptor,
+              new DexString[] {objectArrayDescriptor});
+    }
+  }
+
+  public class JavaUtilComparatorMembers implements LibraryMembers {
+
+    public final DexField EMPTY_LIST =
+        createField(javaUtilCollectionsType, javaUtilListType, "EMPTY_LIST");
+    public final DexField EMPTY_SET =
+        createField(javaUtilCollectionsType, javaUtilSetType, "EMPTY_SET");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(EMPTY_LIST);
+      consumer.accept(EMPTY_SET);
+    }
+  }
+
+  public class JavaUtilConcurrentTimeUnitMembers implements LibraryMembers {
+
+    public final DexField DAYS =
+        createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "DAYS");
+    public final DexField HOURS =
+        createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "HOURS");
+    public final DexField MICROSECONDS =
+        createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "MICROSECONDS");
+    public final DexField MILLISECONDS =
+        createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "MILLISECONDS");
+    public final DexField MINUTES =
+        createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "MINUTES");
+    public final DexField NANOSECONDS =
+        createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "NANOSECONDS");
+    public final DexField SECONDS =
+        createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "SECONDS");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(DAYS);
+      consumer.accept(HOURS);
+      consumer.accept(MICROSECONDS);
+      consumer.accept(MILLISECONDS);
+      consumer.accept(MINUTES);
+      consumer.accept(NANOSECONDS);
+      consumer.accept(SECONDS);
+    }
+  }
+
+  public class JavaUtilLocaleMembers implements LibraryMembers {
+
+    public final DexField ENGLISH = createField(javaUtilLocaleType, javaUtilLocaleType, "ENGLISH");
+    public final DexField ROOT = createField(javaUtilLocaleType, javaUtilLocaleType, "ROOT");
+    public final DexField US = createField(javaUtilLocaleType, javaUtilLocaleType, "US");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(ENGLISH);
+      consumer.accept(ROOT);
+      consumer.accept(US);
+    }
+  }
+
+  public class JavaUtilLoggingLevelMembers implements LibraryMembers {
+
+    public final DexField CONFIG =
+        createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "CONFIG");
+    public final DexField FINE =
+        createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "FINE");
+    public final DexField FINER =
+        createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "FINER");
+    public final DexField FINEST =
+        createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "FINEST");
+    public final DexField SEVERE =
+        createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "SEVERE");
+    public final DexField WARNING =
+        createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "WARNING");
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(CONFIG);
+      consumer.accept(FINE);
+      consumer.accept(FINER);
+      consumer.accept(FINEST);
+      consumer.accept(SEVERE);
+      consumer.accept(WARNING);
+    }
+  }
+
+  public class LongMembers implements LibraryMembers {
+
+    public final DexField TYPE = createField(boxedLongType, classType, "TYPE");
 
     public final DexMethod compare;
 
-    private LongMethods() {
+    private LongMembers() {
       compare = createMethod(boxedLongDescriptor,
           createString("compare"), intDescriptor, new DexString[]{longDescriptor, longDescriptor});
     }
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(TYPE);
+    }
   }
 
   public class DoubleMethods {
@@ -710,17 +1058,13 @@
     }
   }
 
-  public class JavaUtilArraysMethods {
+  public class IntegerMembers implements LibraryMembers {
 
-    public final DexMethod asList;
+    public final DexField TYPE = createField(boxedIntType, classType, "TYPE");
 
-    private JavaUtilArraysMethods() {
-      asList =
-          createMethod(
-              arraysDescriptor,
-              createString("asList"),
-              listDescriptor,
-              new DexString[] {objectArrayDescriptor});
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(TYPE);
     }
   }
 
@@ -1072,7 +1416,11 @@
     }
   }
 
-  public class StringMethods {
+  public class StringMembers implements LibraryMembers {
+
+    public final DexField CASE_INSENSITIVE_ORDER =
+        createField(stringType, javaUtilComparatorType, "CASE_INSENSITIVE_ORDER");
+
     public final DexMethod isEmpty;
     public final DexMethod length;
 
@@ -1098,7 +1446,7 @@
 
     public final DexMethod trim = createMethod(stringType, createProto(stringType), trimName);
 
-    private StringMethods() {
+    private StringMembers() {
       isEmpty = createMethod(
           stringDescriptor, isEmptyMethodName, booleanDescriptor, DexString.EMPTY_ARRAY);
       length = createMethod(
@@ -1145,6 +1493,11 @@
       intern = createMethod(
           stringDescriptor, internMethodName, stringDescriptor, DexString.EMPTY_ARRAY);
     }
+
+    @Override
+    public void forEachFinalField(Consumer<DexField> consumer) {
+      consumer.accept(CASE_INSENSITIVE_ORDER);
+    }
   }
 
   public class StringBuildingMethods {
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 97d49a5..3b4d284 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.utils.DescriptorUtils.getClassBinaryNameFromDescriptor;
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromClassBinaryName;
 
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -218,10 +217,30 @@
     }
   }
 
-  // TODO(b/129925954): better structures for a circle of
-  //  TypeSignature - FieldTypeSignature - ClassTypeSignature - TypeArgument
+  public enum WildcardIndicator {
+    NOT_AN_ARGUMENT,
+    NONE,
+    NEGATIVE,
+    POSITIVE
+  }
+
   public abstract static class FieldTypeSignature
       extends TypeSignature implements DexDefinitionSignature<DexEncodedField> {
+
+    private final WildcardIndicator wildcardIndicator;
+
+    private FieldTypeSignature(WildcardIndicator wildcardIndicator) {
+      this.wildcardIndicator = wildcardIndicator;
+    }
+
+    public final boolean isArgument() {
+      return wildcardIndicator != WildcardIndicator.NOT_AN_ARGUMENT;
+    }
+
+    public WildcardIndicator getWildcardIndicator() {
+      return wildcardIndicator;
+    }
+
     @Override
     public boolean isFieldTypeSignature() {
       return true;
@@ -259,6 +278,32 @@
     public boolean isUnknown() {
       return this == ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE;
     }
+
+    public abstract FieldTypeSignature asArgument(WildcardIndicator indicator);
+
+    public boolean isStar() {
+      return false;
+    }
+  }
+
+  private static final class StarFieldTypeSignature extends FieldTypeSignature {
+
+    private static final StarFieldTypeSignature STAR_FIELD_TYPE_SIGNATURE =
+        new StarFieldTypeSignature();
+
+    private StarFieldTypeSignature() {
+      super(WildcardIndicator.NONE);
+    }
+
+    @Override
+    public FieldTypeSignature asArgument(WildcardIndicator indicator) {
+      throw new Unreachable("Should not be called");
+    }
+
+    @Override
+    public boolean isStar() {
+      return true;
+    }
   }
 
   public static class ClassTypeSignature extends FieldTypeSignature {
@@ -268,7 +313,6 @@
     final DexType type;
     // E.g., for Map<K, V>, a signature will indicate what types are for K and V.
     // Note that this could be nested, e.g., Map<K, Consumer<V>>.
-    // TODO(b/129925954): What about * ?
     final List<FieldTypeSignature> typeArguments;
 
     // TODO(b/129925954): towards immutable structure?
@@ -277,10 +321,17 @@
     ClassTypeSignature innerTypeSignature;
 
     ClassTypeSignature(DexType type, List<FieldTypeSignature> typeArguments) {
+      this(type, typeArguments, WildcardIndicator.NOT_AN_ARGUMENT);
+    }
+
+    private ClassTypeSignature(
+        DexType type, List<FieldTypeSignature> typeArguments, WildcardIndicator indicator) {
+      super(indicator);
       assert type != null;
       assert typeArguments != null;
       this.type = type;
       this.typeArguments = typeArguments;
+      assert typeArguments.stream().allMatch(FieldTypeSignature::isArgument);
     }
 
     public DexType type() {
@@ -302,6 +353,15 @@
     }
 
     @Override
+    public ClassTypeSignature asArgument(WildcardIndicator indicator) {
+      assert indicator != WildcardIndicator.NOT_AN_ARGUMENT;
+      ClassTypeSignature argument = new ClassTypeSignature(type, typeArguments, indicator);
+      argument.innerTypeSignature = this.innerTypeSignature;
+      argument.enclosingTypeSignature = this.enclosingTypeSignature;
+      return argument;
+    }
+
+    @Override
     public ArrayTypeSignature toArrayTypeSignature(AppView<?> appView) {
       return new ArrayTypeSignature(this);
     }
@@ -311,32 +371,18 @@
       outer.innerTypeSignature = inner;
       inner.enclosingTypeSignature = outer;
     }
-
-    // TODO(b/129925954): rewrite GenericSignatureRewriter with this pattern?
-    public interface Converter<R> {
-      R init();
-      R visitType(DexType type, R result);
-      R visitTypeArgument(FieldTypeSignature typeArgument, R result);
-      R visitInnerTypeSignature(ClassTypeSignature innerTypeSignature, R result);
-    }
-
-    public <R> R convert(Converter<R> converter) {
-      R result = converter.init();
-      result = converter.visitType(type, result);
-      for (FieldTypeSignature typeArgument : typeArguments) {
-        result = converter.visitTypeArgument(typeArgument, result);
-      }
-      if (innerTypeSignature != null) {
-        result = converter.visitInnerTypeSignature(innerTypeSignature, result);
-      }
-      return result;
-    }
   }
 
   public static class ArrayTypeSignature extends FieldTypeSignature {
+
     final TypeSignature elementSignature;
 
     ArrayTypeSignature(TypeSignature elementSignature) {
+      this(elementSignature, WildcardIndicator.NOT_AN_ARGUMENT);
+    }
+
+    private ArrayTypeSignature(TypeSignature elementSignature, WildcardIndicator indicator) {
+      super(indicator);
       assert elementSignature != null;
       this.elementSignature = elementSignature;
     }
@@ -356,6 +402,12 @@
     }
 
     @Override
+    public FieldTypeSignature asArgument(WildcardIndicator indicator) {
+      assert indicator != WildcardIndicator.NOT_AN_ARGUMENT;
+      return new ArrayTypeSignature(elementSignature, indicator);
+    }
+
+    @Override
     public TypeSignature toArrayTypeSignature(AppView<?> appView) {
       return new ArrayTypeSignature(this);
     }
@@ -367,9 +419,15 @@
   }
 
   public static class TypeVariableSignature extends FieldTypeSignature {
+
     final String typeVariable;
 
     private TypeVariableSignature(String typeVariable) {
+      this(typeVariable, WildcardIndicator.NOT_AN_ARGUMENT);
+    }
+
+    private TypeVariableSignature(String typeVariable, WildcardIndicator indicator) {
+      super(indicator);
       assert typeVariable != null;
       this.typeVariable = typeVariable;
     }
@@ -385,6 +443,12 @@
     }
 
     @Override
+    public FieldTypeSignature asArgument(WildcardIndicator indicator) {
+      assert indicator != WildcardIndicator.NOT_AN_ARGUMENT;
+      return new TypeVariableSignature(typeVariable, indicator);
+    }
+
+    @Override
     public ArrayTypeSignature toArrayTypeSignature(AppView<?> appView) {
       return new ArrayTypeSignature(this);
     }
@@ -617,9 +681,6 @@
       try {
         setInput(signature);
         return parseClassSignature();
-      } catch (Unimplemented e) {
-        // TODO(b/129925954): Should not catch this once fully implemented
-        return ClassSignature.UNKNOWN_CLASS_SIGNATURE;
       } catch (GenericSignatureFormatError e) {
         throw e;
       } catch (Throwable t) {
@@ -634,9 +695,6 @@
       try {
         setInput(signature);
         return parseMethodTypeSignature();
-      } catch (Unimplemented e) {
-        // TODO(b/129925954): Should not catch this once fully implemented
-        return MethodTypeSignature.UNKNOWN_METHOD_TYPE_SIGNATURE;
       } catch (GenericSignatureFormatError e) {
         throw e;
       } catch (Throwable t) {
@@ -651,9 +709,6 @@
       try {
         setInput(signature);
         return parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
-      } catch (Unimplemented e) {
-        // TODO(b/129925954): Should not catch this once fully implemented
-        return ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE;
       } catch (GenericSignatureFormatError e) {
         throw e;
       } catch (Throwable t) {
@@ -877,15 +932,18 @@
       // TypeArgument ::= (["+" | "-"] FieldTypeSignature) | "*".
       if (symbol == '*') {
         scanSymbol();
-        throw new Unimplemented("GenericSignature.TypeArgument *");
+        return StarFieldTypeSignature.STAR_FIELD_TYPE_SIGNATURE;
       } else if (symbol == '+') {
         scanSymbol();
-        return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION);
+        return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION)
+            .asArgument(WildcardIndicator.POSITIVE);
       } else if (symbol == '-') {
         scanSymbol();
-        return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION);
+        return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION)
+            .asArgument(WildcardIndicator.NEGATIVE);
       } else {
-        return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION);
+        return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION)
+            .asArgument(WildcardIndicator.NONE);
       }
     }
 
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 283777f..fbecfef 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -388,7 +388,8 @@
             }
           },
           lambda -> {
-            assert resolvedHolder.isInterface();
+            assert resolvedHolder.isInterface()
+                || resolvedHolder.type == appInfo.dexItemFactory().objectType;
             LookupTarget target = lookupVirtualDispatchTarget(lambda, appInfo);
             if (target != null) {
               if (target.isLambdaTarget()) {
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 05416df..f417d65 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
@@ -20,16 +20,12 @@
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.LongInterval;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -119,9 +115,6 @@
       if (isNewInstanceWithoutEnvironmentDependentFields(root, assumedNotToDependOnEnvironment)) {
         return false;
       }
-      if (isAliasOfValueThatIsIndependentOfEnvironment(root, assumedNotToDependOnEnvironment)) {
-        return false;
-      }
       return true;
     } finally {
       boolean changed = visited.remove(root);
@@ -414,49 +407,4 @@
 
     return false;
   }
-
-  private boolean isAliasOfValueThatIsIndependentOfEnvironment(
-      Value value, Set<Value> assumedNotToDependOnEnvironment) {
-    // If we are inside a class initializer, and we are reading a final field of the enclosing
-    // class, then check if there is a single write to that field in the class initializer, and that
-    // the value being written into that field does not depend on the environment.
-    //
-    // The reason why we do not currently treat final fields that are written in multiple places is
-    // that the value of the field could then be dependent on the environment due to the control
-    // flow.
-    if (code.method.isClassInitializer()) {
-      assert !value.hasAliasedValue();
-      if (value.isPhi()) {
-        return false;
-      }
-      Instruction definition = value.definition;
-      if (definition.isStaticGet()) {
-        StaticGet staticGet = definition.asStaticGet();
-        DexEncodedField field = appView.appInfo().resolveField(staticGet.getField());
-        if (field != null && field.holder() == context) {
-          List<StaticPut> finalFieldPuts = computeFinalFieldPuts().get(field);
-          if (finalFieldPuts == null || finalFieldPuts.size() != 1) {
-            return false;
-          }
-          StaticPut staticPut = ListUtils.first(finalFieldPuts);
-          return !valueMayDependOnEnvironment(staticPut.value(), assumedNotToDependOnEnvironment);
-        }
-      }
-    }
-    return false;
-  }
-
-  private Map<DexEncodedField, List<StaticPut>> computeFinalFieldPuts() {
-    assert code.method.isClassInitializer();
-    if (finalFieldPuts == null) {
-      finalFieldPuts = new IdentityHashMap<>();
-      for (StaticPut staticPut : code.<StaticPut>instructions(Instruction::isStaticPut)) {
-        DexEncodedField field = appView.appInfo().resolveField(staticPut.getField());
-        if (field != null && field.holder() == context && field.isFinal()) {
-          finalFieldPuts.computeIfAbsent(field, ignore -> new ArrayList<>()).add(staticPut);
-        }
-      }
-    }
-    return finalFieldPuts;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
index 3e34256..6c1136f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
@@ -65,8 +65,7 @@
       }
     }
 
-    // TODO(b/150409786): Move all code rewriting passes into the lens code rewriter.
-    // assert new TypeAnalysis(appView).verifyValuesUpToDate(affectedPhis);
+    assert new TypeAnalysis(appView).verifyValuesUpToDate(affectedPhis);
 
     // Now that the types of all transitively type affected phis have been reset, we can
     // perform a narrowing, starting from the values that are affected by those phis.
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 52aed30..9dea6f8 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
@@ -184,6 +184,28 @@
     onControlFlowEdgesMayChangeListeners.add(listener);
   }
 
+  public boolean hasUniqueSuccessor() {
+    return successors.size() == 1;
+  }
+
+  public boolean hasUniqueNormalSuccessor() {
+    return numberOfNormalSuccessors() == 1;
+  }
+
+  public boolean hasUniqueNormalSuccessorWithUniquePredecessor() {
+    return hasUniqueNormalSuccessor() && getUniqueNormalSuccessor().getPredecessors().size() == 1;
+  }
+
+  public BasicBlock getUniqueSuccessor() {
+    assert hasUniqueSuccessor();
+    return successors.get(0);
+  }
+
+  public BasicBlock getUniqueNormalSuccessor() {
+    assert hasUniqueNormalSuccessor();
+    return ListUtils.last(successors);
+  }
+
   public List<BasicBlock> getSuccessors() {
     return Collections.unmodifiableList(successors);
   }
@@ -200,17 +222,27 @@
     return true;
   }
 
+  public void forEachNormalSuccessor(Consumer<BasicBlock> consumer) {
+    for (int i = successors.size() - numberOfNormalSuccessors(); i < successors.size(); i++) {
+      consumer.accept(successors.get(i));
+    }
+  }
+
+  public boolean hasNormalSuccessor(BasicBlock block) {
+    for (int i = successors.size() - numberOfNormalSuccessors(); i < successors.size(); i++) {
+      if (successors.get(i) == block) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public List<BasicBlock> getNormalSuccessors() {
     if (!hasCatchHandlers()) {
       return successors;
     }
-    Set<Integer> handlers = catchHandlers.getUniqueTargets();
     ImmutableList.Builder<BasicBlock> normals = ImmutableList.builder();
-    for (int i = 0; i < successors.size(); i++) {
-      if (!handlers.contains(i)) {
-        normals.add(successors.get(i));
-      }
-    }
+    forEachNormalSuccessor(normals::add);
     return normals.build();
   }
 
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 63f6a89..f356690 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
@@ -3,19 +3,21 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
+import com.android.tools.r8.graph.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.modeling.LibraryMethodReadSetModeling;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
@@ -24,7 +26,8 @@
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.google.common.collect.ImmutableList;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
 import java.util.BitSet;
 import java.util.Collection;
@@ -77,63 +80,48 @@
 
   // TODO(b/140204899): Refactor lookup methods to be defined in a single place.
   public Collection<DexEncodedMethod> lookupTargets(
-      AppView<? extends AppInfoWithSubtyping> appView, DexType invocationContext) {
-    // Leverage exact receiver type if available.
-    DexEncodedMethod singleTarget = lookupSingleTarget(appView, invocationContext);
-    if (singleTarget != null) {
-      return ImmutableList.of(singleTarget);
-    }
-    if (!isInvokeMethodWithDynamicDispatch() || !appView.appInfo().hasLiveness()) {
+      AppView<AppInfoWithLiveness> appView, DexType invocationContext) {
+    if (!getInvokedMethod().holder.isClassType()) {
       return null;
     }
-    LookupResultSuccess lookupResult =
-        appView
-            .appInfo()
-            .resolveMethod(method.holder, method)
-            .lookupVirtualDispatchTargets(
-                appView.definitionForProgramType(invocationContext),
-                appView.withLiveness().appInfo())
-            .asLookupResultSuccess();
-    if (lookupResult == null || lookupResult.isEmpty()) {
-      return null;
+    if (!isInvokeMethodWithDynamicDispatch()) {
+      DexEncodedMethod singleTarget = lookupSingleTarget(appView, invocationContext);
+      return singleTarget != null ? SetUtils.newIdentityHashSet(singleTarget) : null;
     }
-    assert lookupResult.hasMethodTargets();
-    if (lookupResult.hasLambdaTargets()) {
-      // TODO(b/150277553): Support lambda targets.
-      return null;
+    DexProgramClass refinedReceiverUpperBound =
+        asProgramClassOrNull(
+            appView.definitionFor(
+                TypeAnalysis.getRefinedReceiverType(appView, asInvokeMethodWithReceiver())));
+    DexProgramClass refinedReceiverLowerBound = null;
+    ClassTypeElement refinedReceiverLowerBoundType =
+        asInvokeMethodWithReceiver().getReceiver().getDynamicLowerBoundType(appView);
+    if (refinedReceiverLowerBoundType != null) {
+      refinedReceiverLowerBound =
+          asProgramClassOrNull(appView.definitionFor(refinedReceiverLowerBoundType.getClassType()));
     }
-    DexType staticReceiverType = getInvokedMethod().holder;
-    DexType refinedReceiverType =
-        TypeAnalysis.getRefinedReceiverType(
-            appView.withLiveness(), this.asInvokeMethodWithReceiver());
-    // TODO(b/140204899): Instead of reprocessing here, pass refined receiver to lookup.
-    // Leverage refined receiver type if available.
-    if (refinedReceiverType != staticReceiverType) {
-      ResolutionResult refinedResolution =
-          appView.appInfo().resolveMethod(refinedReceiverType, method);
-      if (refinedResolution.isSingleResolution()) {
-        DexEncodedMethod refinedTarget = refinedResolution.getSingleTarget();
-        Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
-        lookupResult.forEach(
-            methodTarget -> {
-              DexEncodedMethod target = methodTarget.getMethod();
-              if (target == refinedTarget
-                  || appView.isSubtype(target.holder(), refinedReceiverType).isPossiblyTrue()) {
-                result.add(target);
-              }
-            },
-            lambdaTarget -> {
-              throw new Unreachable();
-            });
-        return result;
-      }
-      // If resolution at the refined type fails, conservatively return the full set of targets.
+    ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
+    LookupResult lookupResult;
+    if (refinedReceiverUpperBound != null) {
+      lookupResult =
+          resolutionResult.lookupVirtualDispatchTargets(
+              appView.definitionForProgramType(invocationContext),
+              appView.withLiveness().appInfo(),
+              refinedReceiverUpperBound,
+              refinedReceiverLowerBound);
+    } else {
+      lookupResult =
+          resolutionResult.lookupVirtualDispatchTargets(
+              appView.definitionForProgramType(invocationContext),
+              appView.withLiveness().appInfo());
+    }
+    if (lookupResult.isLookupResultFailure()) {
+      return null;
     }
     Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
     lookupResult.forEach(
         methodTarget -> result.add(methodTarget.getMethod()),
         lambda -> {
-          assert false;
+          // TODO(b/150277553): Support lambda targets.
         });
     return result;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index f7b8688..41b1124 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokePolymorphicRange;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -22,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Collection;
 import java.util.List;
 
@@ -126,7 +126,7 @@
 
   @Override
   public Collection<DexEncodedMethod> lookupTargets(
-      AppView<? extends AppInfoWithSubtyping> appView, DexType invocationContext) {
+      AppView<AppInfoWithLiveness> appView, DexType invocationContext) {
     // TODO(herhut): Implement lookup target for invokePolymorphic.
     return null;
   }
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 18b62e6..92b23eb 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
@@ -218,15 +218,14 @@
                       resolution.lookupVirtualDispatchTargets(
                           appView.definitionForProgramType(context), appView.appInfo());
                   if (lookupResult.isLookupResultSuccess()) {
-                    // TODO(b/150277553): Add lambda methods to the call graph.
                     Set<DexEncodedMethod> targets = new HashSet<>();
                     lookupResult
                         .asLookupResultSuccess()
                         .forEach(
                             methodTarget -> targets.add(methodTarget.getMethod()),
-                            lambdaTarget -> {
-                              assert false;
-                            });
+                            lambdaTarget ->
+                                // The call target will ultimately be the implementation method.
+                                targets.add(lambdaTarget.getImplementationMethod().getMethod()));
                     return targets;
                   }
                 }
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 e29a1c1..1b78ff7 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
@@ -315,11 +315,10 @@
               : null;
       this.lambdaMerger =
           options.enableLambdaMerging ? new LambdaMerger(appViewWithLiveness) : null;
-      this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness);
       this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
+      this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, enumUnboxer);
       this.inliner =
-          new Inliner(
-              appViewWithLiveness, mainDexClasses, lambdaMerger, lensCodeRewriter, enumUnboxer);
+          new Inliner(appViewWithLiveness, mainDexClasses, lambdaMerger, lensCodeRewriter);
       this.outliner = new Outliner(appViewWithLiveness);
       this.memberValuePropagation =
           options.enableValuePropagation ? new MemberValuePropagation(appViewWithLiveness) : null;
@@ -1148,10 +1147,6 @@
       codeRewriter.simplifyDebugLocals(code);
     }
 
-    if (enumUnboxer != null && methodProcessor.isPost()) {
-      enumUnboxer.rewriteCode(code);
-    }
-
     if (appView.graphLense().hasCodeRewritings()) {
       assert lensCodeRewriter != null;
       timing.begin("Lens rewrite");
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 7daa4a1..0d91bab 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
@@ -84,6 +84,7 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
 import com.android.tools.r8.logging.Log;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -99,10 +100,12 @@
 
   private final AppView<? extends AppInfoWithSubtyping> appView;
 
+  private final EnumUnboxer enumUnboxer;
   private final Map<DexProto, DexProto> protoFixupCache = new ConcurrentHashMap<>();
 
-  LensCodeRewriter(AppView<? extends AppInfoWithSubtyping> appView) {
+  LensCodeRewriter(AppView<? extends AppInfoWithSubtyping> appView, EnumUnboxer enumUnboxer) {
     this.appView = appView;
+    this.enumUnboxer = enumUnboxer;
   }
 
   private Value makeOutValue(Instruction insn, IRCode code) {
@@ -117,11 +120,12 @@
 
   /** Replace type appearances, invoke targets and field accesses with actual definitions. */
   public void rewrite(IRCode code, DexEncodedMethod method) {
+    Set<Phi> affectedPhis =
+        enumUnboxer != null ? enumUnboxer.rewriteCode(code) : Sets.newIdentityHashSet();
     GraphLense graphLense = appView.graphLense();
     DexItemFactory factory = appView.dexItemFactory();
     // Rewriting types that affects phi can cause us to compute TOP for cyclic phi's. To solve this
     // we track all phi's that needs to be re-computed.
-    Set<Phi> affectedPhis = Sets.newIdentityHashSet();
     ListIterator<BasicBlock> blocks = code.listIterator();
     boolean mayHaveUnreachableBlocks = false;
     while (blocks.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
index 20ab05c..0365d43 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
@@ -187,7 +187,7 @@
     }
     Instruction definition = root.definition;
     return definition.isInvokeVirtual()
-        && definition.asInvokeVirtual().getInvokedMethod() == dexItemFactory.stringMethods.hashCode;
+        && definition.asInvokeVirtual().getInvokedMethod() == dexItemFactory.stringMembers.hashCode;
   }
 
   static class StringSwitchBuilderInfo {
@@ -376,7 +376,7 @@
           }
           if (instruction.isInvokeVirtual()) {
             InvokeVirtual invoke = instruction.asInvokeVirtual();
-            if (invoke.getInvokedMethod() == dexItemFactory.stringMethods.hashCode
+            if (invoke.getInvokedMethod() == dexItemFactory.stringMembers.hashCode
                 && invoke.getReceiver() == stringValue
                 && invoke.outValue().onlyUsedInBlock(block)) {
               continue;
@@ -503,7 +503,7 @@
 
         InvokeVirtual theInvoke = instructionIterator.next().asInvokeVirtual();
         if (theInvoke == null
-            || theInvoke.getInvokedMethod() != dexItemFactory.stringMethods.equals
+            || theInvoke.getInvokedMethod() != dexItemFactory.stringMembers.equals
             || theInvoke.getReceiver() != stringValue
             || theInvoke.inValues().get(1) != theString.outValue()) {
           return false;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
index b1f53e1..fcfc1e9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
@@ -108,7 +108,7 @@
 
       InvokeVirtual invokeInstruction =
           new InvokeVirtual(
-              appView.dexItemFactory().stringMethods.equals,
+              appView.dexItemFactory().stringMembers.equals,
               code.createValue(PrimitiveTypeElement.getInt()),
               ImmutableList.of(theSwitch.value(), constStringInstruction.outValue()));
       invokeInstruction.setPosition(Position.syntheticNone());
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 e9b9b7e..28ac0fd 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
@@ -361,38 +361,46 @@
           if (clazz == null) {
             // Report missing class since we don't know if it is an interface.
             warnMissingType(encodedMethod.method, method.holder);
-
           } else if (clazz.isInterface()) {
             if (clazz.isLibraryClass()) {
               throw new CompilationError("Unexpected call to a private method " +
                   "defined in library class " + clazz.toSourceString(),
                   getMethodOrigin(encodedMethod.method));
             }
-
-            // This might be either private method call, or a call to default
-            // interface method made via invoke-direct.
-            DexEncodedMethod virtualTarget = null;
-            for (DexEncodedMethod candidate : clazz.virtualMethods()) {
-              if (candidate.method == method) {
-                virtualTarget = candidate;
-                break;
-              }
-            }
-
-            if (virtualTarget != null) {
-              // This is a invoke-direct call to a virtual method.
-              instructions.replaceCurrentInstruction(
-                  new InvokeStatic(defaultAsMethodOfCompanionClass(method),
-                      invokeDirect.outValue(), invokeDirect.arguments()));
-
-            } else {
-              // Otherwise this must be a private instance method call. Note that the referenced
+            DexEncodedMethod directTarget = appView.definitionFor(method);
+            if (directTarget != null) {
+              // This can be a private instance method call. Note that the referenced
               // method is expected to be in the current class since it is private, but desugaring
               // may move some methods or their code into other classes.
-
-              instructions.replaceCurrentInstruction(
-                  new InvokeStatic(privateAsMethodOfCompanionClass(method),
-                      invokeDirect.outValue(), invokeDirect.arguments()));
+              if (directTarget.isPrivateMethod()) {
+                instructions.replaceCurrentInstruction(
+                    new InvokeStatic(
+                        privateAsMethodOfCompanionClass(method),
+                        invokeDirect.outValue(),
+                        invokeDirect.arguments()));
+              } else {
+                instructions.replaceCurrentInstruction(
+                    new InvokeStatic(
+                        defaultAsMethodOfCompanionClass(method),
+                        invokeDirect.outValue(),
+                        invokeDirect.arguments()));
+              }
+            } else {
+              // The method can be a default method in the interface hierarchy.
+              DexClassAndMethod virtualTarget =
+                  appView.appInfo().lookupMaximallySpecificMethod(clazz, method);
+              if (virtualTarget != null) {
+                // This is a invoke-direct call to a virtual method.
+                instructions.replaceCurrentInstruction(
+                    new InvokeStatic(
+                        defaultAsMethodOfCompanionClass(virtualTarget.getMethod().method),
+                        invokeDirect.outValue(),
+                        invokeDirect.arguments()));
+              } else {
+                // The below assert is here because a well-type program should have a target, but we
+                // cannot throw a compilation error, since we have no knowledge about the input.
+                assert false;
+              }
             }
           }
         }
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 d6d698e..85b98ba 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
@@ -378,4 +378,8 @@
     assert !instructions.hasNext();
     nextBlock.copyCatchHandlers(code, blocks, currentBlock, getAppView().options());
   }
+
+  public Map<DexType, LambdaClass> getKnownLambdaClasses() {
+    return knownLambdaClasses;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
index 74b1631..f0b74ea 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
@@ -70,7 +70,7 @@
     }
 
     builder.add(
-        new CfInvoke(INVOKESTATIC, factory.utilArraysMethods.asList, false),
+        new CfInvoke(INVOKESTATIC, factory.javaUtilArraysMethods.asList, false),
         new CfInvoke(
             INVOKEINTERFACE,
             factory.createMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
index b43abaa..342c767 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
@@ -42,8 +42,11 @@
       Set<Value> affectedValues) {
     List<Value> values = invoke.inValues();
     assert values.size() == 1;
-    invoke.outValue().replaceUsers(values.get(0));
-    iterator.remove();
+    if (invoke.hasOutValue()) {
+      invoke.outValue().replaceUsers(values.get(0));
+    }
+    // TODO(b/152853271): Debugging information is lost here (DebugLocalWrite may be required).
+    iterator.removeOrReplaceByDebugLocalRead();
   }
 
   private NumericMethodRewrites() {
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 fee1f02..9f34126 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
@@ -1821,6 +1821,15 @@
       if (!next.isConstInstruction()) {
         continue;
       }
+
+      // We don't want to push const string instructions down into code that has monitors since
+      // we may attach catch handlers that are not catch-all when inlining. This is symmetric in how
+      // we don't do const string canonicalization.
+      if ((next.isConstString() || next.isDexItemBasedConstString())
+          && code.metadata().mayHaveMonitorInstruction()) {
+        continue;
+      }
+
       ConstInstruction instruction = next.asConstInstruction();
       if (!selector.test(instruction) || instruction.outValue().hasLocalInfo()) {
         continue;
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 33d982a..55f42ab 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
@@ -45,7 +45,6 @@
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostOptimization;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
-import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy;
@@ -80,7 +79,6 @@
   private final Set<DexMethod> blacklist;
   private final LambdaMerger lambdaMerger;
   private final LensCodeRewriter lensCodeRewriter;
-  private final EnumUnboxer enumUnboxer;
   final MainDexClasses mainDexClasses;
 
   // State for inlining methods which are known to be called twice.
@@ -93,8 +91,7 @@
       AppView<AppInfoWithLiveness> appView,
       MainDexClasses mainDexClasses,
       LambdaMerger lambdaMerger,
-      LensCodeRewriter lensCodeRewriter,
-      EnumUnboxer enumUnboxer) {
+      LensCodeRewriter lensCodeRewriter) {
     Kotlin.Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
     this.appView = appView;
     this.blacklist =
@@ -103,7 +100,6 @@
             : ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
     this.lambdaMerger = lambdaMerger;
     this.lensCodeRewriter = lensCodeRewriter;
-    this.enumUnboxer = enumUnboxer;
     this.mainDexClasses = mainDexClasses;
   }
 
@@ -599,8 +595,7 @@
         DexEncodedMethod context,
         InliningIRProvider inliningIRProvider,
         LambdaMerger lambdaMerger,
-        LensCodeRewriter lensCodeRewriter,
-        EnumUnboxer enumUnboxer) {
+        LensCodeRewriter lensCodeRewriter) {
       DexItemFactory dexItemFactory = appView.dexItemFactory();
       InternalOptions options = appView.options();
 
@@ -738,9 +733,6 @@
 
       if (inliningIRProvider.shouldApplyCodeRewritings(code.method)) {
         assert lensCodeRewriter != null;
-        if (enumUnboxer != null) {
-          enumUnboxer.rewriteCode(code);
-        }
         lensCodeRewriter.rewrite(code, target);
       }
       if (lambdaMerger != null) {
@@ -1000,13 +992,7 @@
 
           InlineeWithReason inlinee =
               action.buildInliningIR(
-                  appView,
-                  invoke,
-                  context,
-                  inliningIRProvider,
-                  lambdaMerger,
-                  lensCodeRewriter,
-                  enumUnboxer);
+                  appView, invoke, context, inliningIRProvider, lambdaMerger, lensCodeRewriter);
           if (strategy.willExceedBudget(
               code, invoke, inlinee, block, whyAreYouNotInliningReporter)) {
             assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
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 2e41703..bc294fa 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
@@ -69,10 +69,9 @@
       }
       // Add non-null after
       // 1) instructions that implicitly indicate receiver/array is not null.
-      // 2) invocations that call non-overridable library methods that are known to return non null.
-      // 3) invocations that are guaranteed to return a non-null value.
-      // 4) parameters that are not null after the invocation.
-      // 5) field-get instructions that are guaranteed to read a non-null value.
+      // 2) invocations that are guaranteed to return a non-null value.
+      // 3) parameters that are not null after the invocation.
+      // 4) field-get instructions that are guaranteed to read a non-null value.
       InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction current = iterator.next();
@@ -90,26 +89,18 @@
           InvokeMethod invoke = current.asInvokeMethod();
           DexMethod invokedMethod = invoke.getInvokedMethod();
 
-          // Case (2), invocations that call non-overridable library methods that are known to
-          // return non null.
-          if (dexItemFactory.libraryMethodsReturningNonNull.contains(invokedMethod)) {
-            if (current.hasOutValue() && isNullableReferenceTypeWithUsers(outValue)) {
-              knownToBeNonNullValues.add(outValue);
-            }
-          }
-
           DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.method.holder());
           if (singleTarget != null) {
             MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
 
-            // Case (3), invocations that are guaranteed to return a non-null value.
+            // Case (2), invocations that are guaranteed to return a non-null value.
             if (optimizationInfo.neverReturnsNull()) {
               if (invoke.hasOutValue() && isNullableReferenceTypeWithUsers(outValue)) {
                 knownToBeNonNullValues.add(outValue);
               }
             }
 
-            // Case (4), parameters that are not null after the invocation.
+            // Case (3), parameters that are not null after the invocation.
             BitSet nonNullParamOnNormalExits = optimizationInfo.getNonNullParamOnNormalExits();
             if (nonNullParamOnNormalExits != null) {
               for (int i = 0; i < current.inValues().size(); i++) {
@@ -123,7 +114,7 @@
             }
           }
         } else if (current.isFieldGet()) {
-          // Case (5), field-get instructions that are guaranteed to read a non-null value.
+          // Case (4), field-get instructions that are guaranteed to read a non-null value.
           FieldInstruction fieldInstruction = current.asFieldInstruction();
           DexField field = fieldInstruction.getField();
           if (field.type.isReferenceType() && isNullableReferenceTypeWithUsers(outValue)) {
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 81b32e8..4b50b6e 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
@@ -16,7 +16,6 @@
 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;
-import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InitClass;
@@ -33,6 +32,8 @@
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayDeque;
 import java.util.Deque;
 import java.util.HashMap;
@@ -43,7 +44,7 @@
 /**
  * Eliminate redundant field loads.
  *
- * <p>Simple algorithm that goes through all blocks in one pass in dominator order and propagates
+ * <p>Simple algorithm that goes through all blocks in one pass in topological order and propagates
  * active field sets across control-flow edges where the target has only one predecessor.
  */
 // TODO(ager): Evaluate speed/size for computing active field sets in a fixed-point computation.
@@ -52,7 +53,6 @@
   private final AppView<?> appView;
   private final DexEncodedMethod method;
   private final IRCode code;
-  private final DominatorTree dominatorTree;
 
   // Values that may require type propagation.
   private final Set<Value> affectedValues = Sets.newIdentityHashSet();
@@ -68,7 +68,6 @@
     this.appView = appView;
     this.method = code.method;
     this.code = code;
-    dominatorTree = new DominatorTree(code);
   }
 
   public static boolean shouldRun(AppView<?> appView, IRCode code) {
@@ -140,6 +139,13 @@
     }
   }
 
+  public boolean isFinal(DexEncodedField field) {
+    if (field.isProgramField(appView)) {
+      return field.isFinal();
+    }
+    return appView.libraryMethodOptimizer().isFinalLibraryField(field);
+  }
+
   private DexEncodedField resolveField(DexField field) {
     if (appView.enableWholeProgramOptimizations()) {
       return appView.appInfo().resolveField(field);
@@ -152,138 +158,169 @@
 
   public void run() {
     DexType context = method.holder();
-    for (BasicBlock block : dominatorTree.getSortedBlocks()) {
-      computeActiveStateOnBlockEntry(block);
-      InstructionListIterator it = block.listIterator(code);
-      while (it.hasNext()) {
-        Instruction instruction = it.next();
-        if (instruction.isFieldInstruction()) {
-          DexField field = instruction.asFieldInstruction().getField();
-          DexEncodedField definition = resolveField(field);
-          if (definition == null || definition.isVolatile()) {
-            killAllNonFinalActiveFields();
-            continue;
-          }
-
-          if (instruction.isInstanceGet()) {
-            InstanceGet instanceGet = instruction.asInstanceGet();
-            if (instanceGet.outValue().hasLocalInfo()) {
-              continue;
-            }
-            Value object = instanceGet.object().getAliasedValue();
-            FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-            FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
-            if (replacement != null) {
-              replacement.eliminateRedundantRead(it, instanceGet);
-            } else {
-              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.
-            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);
-            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;
-            }
-            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.
-              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.
-            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();
-          DexType clazz = initClass.getClassValue();
-          if (activeState.isClassInitialized(clazz)) {
-            it.removeOrReplaceByDebugLocalRead();
-          }
-          activeState.markClassAsInitialized(clazz);
-        } else if (instruction.isMonitor()) {
-          if (instruction.asMonitor().isEnter()) {
-            killAllNonFinalActiveFields();
-          }
-        } else if (instruction.isInvokeDirect()) {
-          handleInvokeDirect(instruction.asInvokeDirect());
-        } else if (instruction.isInvokeMethod() || instruction.isInvokeCustom()) {
-          killAllNonFinalActiveFields();
-        } else if (instruction.isNewInstance()) {
-          NewInstance newInstance = instruction.asNewInstance();
-          if (newInstance.clazz.classInitializationMayHaveSideEffects(
-              appView,
-              // Types that are a super type of `context` are guaranteed to be initialized already.
-              type -> appView.isSubtype(context, type).isTrue(),
-              Sets.newIdentityHashSet())) {
-            killAllNonFinalActiveFields();
-          }
-        } else {
-          // If the current instruction could trigger a method invocation, it could also cause field
-          // values to change. In that case, it must be handled above.
-          assert !instruction.instructionMayTriggerMethodInvocation(appView, context);
-
-          // If this assertion fails for a new instruction we need to determine if that instruction
-          // has side-effects that can change the value of fields. If so, it must be handled above.
-          // If not, it can be safely added to the assert.
-          assert instruction.isArgument()
-                  || instruction.isArrayGet()
-                  || instruction.isArrayLength()
-                  || instruction.isArrayPut()
-                  || instruction.isAssume()
-                  || instruction.isBinop()
-                  || instruction.isCheckCast()
-                  || instruction.isConstClass()
-                  || instruction.isConstMethodHandle()
-                  || instruction.isConstMethodType()
-                  || instruction.isConstNumber()
-                  || instruction.isConstString()
-                  || instruction.isDebugInstruction()
-                  || instruction.isDexItemBasedConstString()
-                  || instruction.isGoto()
-                  || instruction.isIf()
-                  || instruction.isInstanceOf()
-                  || instruction.isInvokeMultiNewArray()
-                  || instruction.isInvokeNewArray()
-                  || instruction.isMoveException()
-                  || instruction.isNewArrayEmpty()
-                  || instruction.isNewArrayFilledData()
-                  || instruction.isReturn()
-                  || instruction.isSwitch()
-                  || instruction.isThrow()
-                  || instruction.isUnop()
-              : "Unexpected instruction of type " + instruction.getClass().getTypeName();
-        }
+    Reference2IntMap<BasicBlock> pendingNormalSuccessors = new Reference2IntOpenHashMap<>();
+    for (BasicBlock block : code.blocks) {
+      if (!block.hasUniqueSuccessor()) {
+        pendingNormalSuccessors.put(block, block.numberOfNormalSuccessors());
       }
-      recordActiveStateOnBlockExit(block);
+    }
+
+    Set<BasicBlock> visited = Sets.newIdentityHashSet();
+    for (BasicBlock head : code.topologicallySortedBlocks()) {
+      if (!visited.add(head)) {
+        continue;
+      }
+      computeActiveStateOnBlockEntry(head);
+      removeDeadBlockExitStates(head, pendingNormalSuccessors);
+      BasicBlock block = head;
+      BasicBlock end = null;
+      do {
+        InstructionListIterator it = block.listIterator(code);
+        while (it.hasNext()) {
+          Instruction instruction = it.next();
+          if (instruction.isFieldInstruction()) {
+            DexField field = instruction.asFieldInstruction().getField();
+            DexEncodedField definition = resolveField(field);
+            if (definition == null || definition.isVolatile()) {
+              killAllNonFinalActiveFields();
+              continue;
+            }
+
+            if (instruction.isInstanceGet()) {
+              InstanceGet instanceGet = instruction.asInstanceGet();
+              if (instanceGet.outValue().hasLocalInfo()) {
+                continue;
+              }
+              Value object = instanceGet.object().getAliasedValue();
+              FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+              FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
+              if (replacement != null) {
+                replacement.eliminateRedundantRead(it, instanceGet);
+              } else {
+                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.
+              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);
+              ExistingValue value = new ExistingValue(instancePut.value());
+              if (isFinal(definition)) {
+                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;
+              }
+              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.
+                killNonFinalActiveFields(staticGet);
+                FieldValue value = new ExistingValue(staticGet.value());
+                if (isFinal(definition)) {
+                  activeState.putFinalStaticField(field, value);
+                } else {
+                  activeState.putNonFinalStaticField(field, 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.
+              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();
+            DexType clazz = initClass.getClassValue();
+            if (activeState.isClassInitialized(clazz)) {
+              it.removeOrReplaceByDebugLocalRead();
+            }
+            activeState.markClassAsInitialized(clazz);
+          } else if (instruction.isMonitor()) {
+            if (instruction.asMonitor().isEnter()) {
+              killAllNonFinalActiveFields();
+            }
+          } else if (instruction.isInvokeDirect()) {
+            handleInvokeDirect(instruction.asInvokeDirect());
+          } else if (instruction.isInvokeMethod() || instruction.isInvokeCustom()) {
+            killAllNonFinalActiveFields();
+          } else if (instruction.isNewInstance()) {
+            NewInstance newInstance = instruction.asNewInstance();
+            if (newInstance.clazz.classInitializationMayHaveSideEffects(
+                appView,
+                // Types that are a super type of `context` are guaranteed to be initialized
+                // already.
+                type -> appView.isSubtype(context, type).isTrue(),
+                Sets.newIdentityHashSet())) {
+              killAllNonFinalActiveFields();
+            }
+          } else {
+            // If the current instruction could trigger a method invocation, it could also cause
+            // field values to change. In that case, it must be handled above.
+            assert !instruction.instructionMayTriggerMethodInvocation(appView, context);
+
+            // If this assertion fails for a new instruction we need to determine if that
+            // instruction has side-effects that can change the value of fields. If so, it must be
+            // handled above. If not, it can be safely added to the assert.
+            assert instruction.isArgument()
+                    || instruction.isArrayGet()
+                    || instruction.isArrayLength()
+                    || instruction.isArrayPut()
+                    || instruction.isAssume()
+                    || instruction.isBinop()
+                    || instruction.isCheckCast()
+                    || instruction.isConstClass()
+                    || instruction.isConstMethodHandle()
+                    || instruction.isConstMethodType()
+                    || instruction.isConstNumber()
+                    || instruction.isConstString()
+                    || instruction.isDebugInstruction()
+                    || instruction.isDexItemBasedConstString()
+                    || instruction.isGoto()
+                    || instruction.isIf()
+                    || instruction.isInstanceOf()
+                    || instruction.isInvokeMultiNewArray()
+                    || instruction.isInvokeNewArray()
+                    || instruction.isMoveException()
+                    || instruction.isNewArrayEmpty()
+                    || instruction.isNewArrayFilledData()
+                    || instruction.isReturn()
+                    || instruction.isSwitch()
+                    || instruction.isThrow()
+                    || instruction.isUnop()
+                : "Unexpected instruction of type " + instruction.getClass().getTypeName();
+          }
+        }
+        if (block.hasUniqueNormalSuccessorWithUniquePredecessor()) {
+          block = block.getUniqueNormalSuccessor();
+          assert !visited.contains(block);
+          visited.add(block);
+        } else {
+          end = block;
+          block = null;
+        }
+      } while (block != null);
+      assert end != null;
+      recordActiveStateOnBlockExit(end);
     }
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
@@ -382,9 +419,29 @@
     activeState = state;
   }
 
+  private void removeDeadBlockExitStates(
+      BasicBlock current, Reference2IntMap<BasicBlock> pendingNormalSuccessorsMap) {
+    for (BasicBlock predecessor : current.getPredecessors()) {
+      if (predecessor.hasUniqueSuccessor()) {
+        activeStateAtExit.remove(predecessor);
+      } else {
+        if (predecessor.hasNormalSuccessor(current)) {
+          int pendingNormalSuccessors = pendingNormalSuccessorsMap.getInt(predecessor) - 1;
+          if (pendingNormalSuccessors == 0) {
+            activeStateAtExit.remove(predecessor);
+          } else {
+            pendingNormalSuccessorsMap.put(predecessor, pendingNormalSuccessors);
+          }
+        }
+      }
+    }
+  }
+
   private void recordActiveStateOnBlockExit(BasicBlock block) {
     assert !activeStateAtExit.containsKey(block);
-    activeStateAtExit.put(block, activeState);
+    if (!activeState.isEmpty()) {
+      activeStateAtExit.put(block, activeState);
+    }
   }
 
   private void killAllNonFinalActiveFields() {
@@ -419,50 +476,94 @@
 
   static class State {
 
-    private final Map<FieldAndObject, FieldValue> finalInstanceFieldValues = new HashMap<>();
+    private Map<FieldAndObject, FieldValue> finalInstanceFieldValues;
 
-    private final Map<DexField, FieldValue> finalStaticFieldValues = new IdentityHashMap<>();
+    private Map<DexField, FieldValue> finalStaticFieldValues;
 
-    private final Set<DexType> initializedClasses = Sets.newIdentityHashSet();
+    private Set<DexType> initializedClasses;
 
-    private final Map<FieldAndObject, FieldValue> nonFinalInstanceFieldValues = new HashMap<>();
+    private Map<FieldAndObject, FieldValue> nonFinalInstanceFieldValues;
 
-    private final Map<DexField, FieldValue> nonFinalStaticFieldValues = new IdentityHashMap<>();
+    private Map<DexField, FieldValue> nonFinalStaticFieldValues;
 
     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);
+      if (state.finalInstanceFieldValues != null && !state.finalInstanceFieldValues.isEmpty()) {
+        finalInstanceFieldValues = new HashMap<>();
+        finalInstanceFieldValues.putAll(state.finalInstanceFieldValues);
+      }
+      if (state.finalStaticFieldValues != null && !state.finalStaticFieldValues.isEmpty()) {
+        finalStaticFieldValues = new IdentityHashMap<>();
+        finalStaticFieldValues.putAll(state.finalStaticFieldValues);
+      }
+      if (state.initializedClasses != null && !state.initializedClasses.isEmpty()) {
+        initializedClasses = Sets.newIdentityHashSet();
+        initializedClasses.addAll(state.initializedClasses);
+      }
+      if (state.nonFinalInstanceFieldValues != null
+          && !state.nonFinalInstanceFieldValues.isEmpty()) {
+        nonFinalInstanceFieldValues = new HashMap<>();
+        nonFinalInstanceFieldValues.putAll(state.nonFinalInstanceFieldValues);
+      }
+      if (state.nonFinalStaticFieldValues != null && !state.nonFinalStaticFieldValues.isEmpty()) {
+        nonFinalStaticFieldValues = new IdentityHashMap<>();
+        nonFinalStaticFieldValues.putAll(state.nonFinalStaticFieldValues);
+      }
     }
 
     public void clearNonFinalInstanceFields() {
-      nonFinalInstanceFieldValues.clear();
+      nonFinalInstanceFieldValues = null;
     }
 
     public void clearNonFinalStaticFields() {
-      nonFinalStaticFieldValues.clear();
+      nonFinalStaticFieldValues = null;
     }
 
     public FieldValue getInstanceFieldValue(FieldAndObject field) {
-      FieldValue value = nonFinalInstanceFieldValues.get(field);
-      return value != null ? value : finalInstanceFieldValues.get(field);
+      FieldValue value =
+          nonFinalInstanceFieldValues != null ? nonFinalInstanceFieldValues.get(field) : null;
+      if (value != null) {
+        return value;
+      }
+      return finalInstanceFieldValues != null ? finalInstanceFieldValues.get(field) : null;
     }
 
     public FieldValue getStaticFieldValue(DexField field) {
-      FieldValue value = nonFinalStaticFieldValues.get(field);
-      return value != null ? value : finalStaticFieldValues.get(field);
+      FieldValue value =
+          nonFinalStaticFieldValues != null ? nonFinalStaticFieldValues.get(field) : null;
+      if (value != null) {
+        return value;
+      }
+      return finalStaticFieldValues != null ? finalStaticFieldValues.get(field) : null;
     }
 
     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);
+      if (finalInstanceFieldValues != null && state.finalInstanceFieldValues != null) {
+        intersectFieldValues(finalInstanceFieldValues, state.finalInstanceFieldValues);
+      } else {
+        finalInstanceFieldValues = null;
+      }
+      if (finalStaticFieldValues != null && state.finalStaticFieldValues != null) {
+        intersectFieldValues(finalStaticFieldValues, state.finalStaticFieldValues);
+      } else {
+        finalStaticFieldValues = null;
+      }
+      if (initializedClasses != null && state.initializedClasses != null) {
+        intersectInitializedClasses(initializedClasses, state.initializedClasses);
+      } else {
+        initializedClasses = null;
+      }
+      if (nonFinalInstanceFieldValues != null && state.nonFinalInstanceFieldValues != null) {
+        intersectFieldValues(nonFinalInstanceFieldValues, state.nonFinalInstanceFieldValues);
+      } else {
+        nonFinalInstanceFieldValues = null;
+      }
+      if (nonFinalStaticFieldValues != null && state.nonFinalStaticFieldValues != null) {
+        intersectFieldValues(nonFinalStaticFieldValues, state.nonFinalStaticFieldValues);
+      } else {
+        nonFinalStaticFieldValues = null;
+      }
     }
 
     private static <K> void intersectFieldValues(
@@ -476,7 +577,23 @@
     }
 
     public boolean isClassInitialized(DexType clazz) {
-      return initializedClasses.contains(clazz);
+      return initializedClasses != null && initializedClasses.contains(clazz);
+    }
+
+    public boolean isEmpty() {
+      return isEmpty(finalInstanceFieldValues)
+          && isEmpty(finalStaticFieldValues)
+          && isEmpty(initializedClasses)
+          && isEmpty(nonFinalInstanceFieldValues)
+          && isEmpty(nonFinalStaticFieldValues);
+    }
+
+    private static boolean isEmpty(Set<?> set) {
+      return set == null || set.isEmpty();
+    }
+
+    private static boolean isEmpty(Map<?, ?> map) {
+      return map == null || map.isEmpty();
     }
 
     // If a field get instruction throws an exception it did not have an effect on the value of the
@@ -487,17 +604,22 @@
       if (instruction.isInstanceGet()) {
         Value object = instruction.asInstanceGet().object().getAliasedValue();
         FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-        removeNonFinalInstanceField(fieldAndObject);
+        removeInstanceField(fieldAndObject);
       } else if (instruction.isStaticGet()) {
-        removeNonFinalStaticField(field);
+        removeStaticField(field);
       }
     }
 
     private void killActiveInitializedClassesForExceptionalExit(InitClass instruction) {
-      initializedClasses.remove(instruction.getClassValue());
+      if (initializedClasses != null) {
+        initializedClasses.remove(instruction.getClassValue());
+      }
     }
 
     public void markClassAsInitialized(DexType clazz) {
+      if (initializedClasses == null) {
+        initializedClasses = Sets.newIdentityHashSet();
+      }
       initializedClasses.add(clazz);
     }
 
@@ -507,15 +629,21 @@
     }
 
     public void removeFinalInstanceField(FieldAndObject field) {
-      finalInstanceFieldValues.remove(field);
+      if (finalInstanceFieldValues != null) {
+        finalInstanceFieldValues.remove(field);
+      }
     }
 
     public void removeNonFinalInstanceField(FieldAndObject field) {
-      nonFinalInstanceFieldValues.remove(field);
+      if (nonFinalInstanceFieldValues != null) {
+        nonFinalInstanceFieldValues.remove(field);
+      }
     }
 
     public void removeNonFinalInstanceFields(DexField field) {
-      nonFinalInstanceFieldValues.keySet().removeIf(key -> key.field == field);
+      if (nonFinalInstanceFieldValues != null) {
+        nonFinalInstanceFieldValues.keySet().removeIf(key -> key.field == field);
+      }
     }
 
     public void removeStaticField(DexField field) {
@@ -524,28 +652,44 @@
     }
 
     public void removeFinalStaticField(DexField field) {
-      finalStaticFieldValues.remove(field);
+      if (finalStaticFieldValues != null) {
+        finalStaticFieldValues.remove(field);
+      }
     }
 
     public void removeNonFinalStaticField(DexField field) {
-      nonFinalStaticFieldValues.remove(field);
+      if (nonFinalStaticFieldValues != null) {
+        nonFinalStaticFieldValues.remove(field);
+      }
     }
 
     public void putFinalInstanceField(FieldAndObject field, FieldValue value) {
+      if (finalInstanceFieldValues == null) {
+        finalInstanceFieldValues = new HashMap<>();
+      }
       finalInstanceFieldValues.put(field, value);
     }
 
     public void putFinalStaticField(DexField field, FieldValue value) {
+      if (finalStaticFieldValues == null) {
+        finalStaticFieldValues = new IdentityHashMap<>();
+      }
       finalStaticFieldValues.put(field, value);
     }
 
     public void putNonFinalInstanceField(FieldAndObject field, FieldValue value) {
-      assert !finalInstanceFieldValues.containsKey(field);
+      assert finalInstanceFieldValues == null || !finalInstanceFieldValues.containsKey(field);
+      if (nonFinalInstanceFieldValues == null) {
+        nonFinalInstanceFieldValues = new HashMap<>();
+      }
       nonFinalInstanceFieldValues.put(field, value);
     }
 
     public void putNonFinalStaticField(DexField field, FieldValue value) {
-      assert !nonFinalStaticFieldValues.containsKey(field);
+      assert nonFinalStaticFieldValues == null || !nonFinalStaticFieldValues.containsKey(field);
+      if (nonFinalStaticFieldValues == null) {
+        nonFinalStaticFieldValues = new IdentityHashMap<>();
+      }
       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 1e19dea..eef169e 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
@@ -35,6 +35,9 @@
   // Rewrite forName() to const-class if the type is resolvable, accessible and already initialized.
   public static void rewriteGetClassOrForNameToConstClass(
       AppView<AppInfoWithLiveness> appView, IRCode code) {
+    if (!appView.appInfo().canUseConstClassInstructions(appView.options())) {
+      return;
+    }
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     DexType context = code.method.holder();
     ClassInitializationAnalysis classInitializationAnalysis =
@@ -64,6 +67,10 @@
           Value value = code.createValue(typeLattice, current.getLocalInfo());
           ConstClass constClass = new ConstClass(value, type);
           it.replaceCurrentInstruction(constClass);
+          if (appView.options().isGeneratingClassFiles()) {
+            code.method.upgradeClassFileVersion(
+                appView.options().requiredCfVersionForConstClassInstructions());
+          }
         }
       }
     }
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 ea9141f..6a91188 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
@@ -93,19 +93,6 @@
     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.holder());
-    if (dexClass.isEnum()
-        && (code.method.isInitializer()
-            || appView.dexItemFactory().enumMethods.isValueOfMethod(code.method.method, dexClass)
-            || appView.dexItemFactory().enumMethods.isValuesMethod(code.method.method, dexClass))) {
-      return;
-    }
-    analyzeEnumsInMethod(code);
-  }
-
   private void markEnumAsUnboxable(Reason reason, DexProgramClass enumClass) {
     assert enumClass.isEnum();
     reportFailure(enumClass.type, reason);
@@ -118,29 +105,29 @@
       return getEnumUnboxingCandidateOrNull(classType);
     }
     if (lattice.isArrayType()) {
-      ArrayTypeElement arrayLattice = lattice.asArrayType();
-      if (arrayLattice.getBaseType().isClassType()) {
-        DexType classType = arrayLattice.getBaseType().asClassType().getClassType();
-        return getEnumUnboxingCandidateOrNull(classType);
+      ArrayTypeElement arrayType = lattice.asArrayType();
+      if (arrayType.getBaseType().isClassType()) {
+        return getEnumUnboxingCandidateOrNull(arrayType.getBaseType());
       }
     }
     return null;
   }
 
-  private DexProgramClass getEnumUnboxingCandidateOrNull(DexType anyType) {
-    if (!enumsUnboxingCandidates.containsKey(anyType)) {
+  private DexProgramClass getEnumUnboxingCandidateOrNull(DexType type) {
+    if (!enumsUnboxingCandidates.containsKey(type)) {
       return null;
     }
-    return appView.definitionForProgramType(anyType);
+    return appView.definitionForProgramType(type);
   }
 
-  private void analyzeEnumsInMethod(IRCode code) {
+  public void analyzeEnums(IRCode code) {
     Set<DexType> eligibleEnums = Sets.newIdentityHashSet();
     for (BasicBlock block : code.blocks) {
       for (Instruction instruction : block.getInstructions()) {
         Value outValue = instruction.outValue();
         if (outValue != null) {
-          DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(outValue.getType());
+          DexProgramClass enumClass =
+              getEnumUnboxingCandidateOrNull(outValue.getDynamicUpperBoundType(appView));
           if (enumClass != null) {
             Reason reason = validateEnumUsages(code, outValue, enumClass);
             if (reason == Reason.ELIGIBLE) {
@@ -152,9 +139,9 @@
           }
         }
         if (instruction.isConstClass()) {
-          analyzeConstClass(instruction.asConstClass());
+          analyzeConstClass(instruction.asConstClass(), eligibleEnums);
         } else if (instruction.isCheckCast()) {
-          analyzeCheckCast(instruction.asCheckCast());
+          analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
         } 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
@@ -198,24 +185,30 @@
     }
   }
 
-  private void analyzeCheckCast(CheckCast checkCast) {
+  private void analyzeCheckCast(CheckCast checkCast, Set<DexType> eligibleEnums) {
     // 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);
+    if (enumClass == null) {
+      return;
     }
+    if (allowCheckCast(checkCast)) {
+      eligibleEnums.add(enumClass.type);
+      return;
+    }
+    markEnumAsUnboxable(Reason.DOWN_CAST, enumClass);
   }
 
-  private void analyzeConstClass(ConstClass constClass) {
+  private boolean allowCheckCast(CheckCast checkCast) {
+    TypeElement objectType = checkCast.object().getDynamicUpperBoundType(appView);
+    return objectType.equalUpToNullability(
+        TypeElement.fromDexType(checkCast.getType(), definitelyNotNull(), appView));
+  }
+
+  private void analyzeConstClass(ConstClass constClass, Set<DexType> eligibleEnums) {
     // 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");.
@@ -223,6 +216,7 @@
       return;
     }
     if (constClass.outValue() == null) {
+      eligibleEnums.add(constClass.getValue());
       return;
     }
     if (constClass.outValue().hasPhiUsers()) {
@@ -238,6 +232,7 @@
         return;
       }
     }
+    eligibleEnums.add(constClass.getValue());
   }
 
   private void addNullDependencies(Set<Instruction> uses, Set<DexType> eligibleEnums) {
@@ -395,6 +390,14 @@
         return Reason.INVALID_INVOKE;
       }
       if (dexClass.isProgramClass()) {
+        if (dexClass.isEnum() && encodedSingleTarget.isInstanceInitializer()) {
+          if (code.method.holder() == dexClass.type && code.method.isClassInitializer()) {
+            // The enum instance initializer is allowed to be called only from the enum clinit.
+            return Reason.ELIGIBLE;
+          } else {
+            return Reason.INVALID_INIT;
+          }
+        }
         int offset = BooleanUtils.intValue(!encodedSingleTarget.isStatic());
         for (int i = 0; i < singleTarget.proto.parameters.size(); i++) {
           if (invokeMethod.inValues().get(offset + i) == enumValue) {
@@ -427,6 +430,13 @@
       }
       if (singleTarget == factory.enumMethods.ordinal) {
         return Reason.ELIGIBLE;
+      } else if (singleTarget == factory.enumMethods.constructor) {
+        // Enum constructor call is allowed only if first call of an enum initializer.
+        if (code.method.isInstanceInitializer()
+            && code.method.holder() == enumClass.type
+            && isFirstInstructionAfterArguments(invokeMethod, code)) {
+          return Reason.ELIGIBLE;
+        }
       }
       return Reason.UNSUPPORTED_LIBRARY_CALL;
     }
@@ -443,12 +453,9 @@
       if (dexClass == null) {
         return Reason.INVALID_FIELD_PUT;
       }
-      if (dexClass.isEnum()) {
-        return Reason.FIELD_PUT_ON_ENUM;
-      }
       // The put value has to be of the field type.
-      if (field.field.type != enumClass.type) {
-        return Reason.TYPE_MISSMATCH_FIELD_PUT;
+      if (field.field.type.toBaseType(factory) != enumClass.type) {
+        return Reason.TYPE_MISMATCH_FIELD_PUT;
       }
       return Reason.ELIGIBLE;
     }
@@ -474,6 +481,13 @@
       return Reason.INVALID_IF_TYPES;
     }
 
+    if (instruction.isCheckCast()) {
+      if (allowCheckCast(instruction.asCheckCast())) {
+        return Reason.ELIGIBLE;
+      }
+      return Reason.DOWN_CAST;
+    }
+
     if (instruction.isArrayLength()) {
       // MyEnum[] array = ...; array.length; is valid.
       return Reason.ELIGIBLE;
@@ -525,6 +539,16 @@
     return Reason.OTHER_UNSUPPORTED_INSTRUCTION;
   }
 
+  private boolean isFirstInstructionAfterArguments(InvokeMethod invokeMethod, IRCode code) {
+    BasicBlock basicBlock = code.entryBlock();
+    for (Instruction instruction : basicBlock.getInstructions()) {
+      if (!instruction.isArgument()) {
+        return instruction == invokeMethod;
+      }
+    }
+    return false;
+  }
+
   private void reportEnumsAnalysis() {
     assert debugLogEnabled;
     Reporter reporter = appView.options().reporter;
@@ -552,10 +576,13 @@
     }
   }
 
-  public void rewriteCode(IRCode code) {
+  public Set<Phi> rewriteCode(IRCode code) {
+    // This has no effect during primary processing since the enumUnboxerRewriter is set
+    // in between primary and post processing.
     if (enumUnboxerRewriter != null) {
-      enumUnboxerRewriter.rewriteCode(code);
+      return enumUnboxerRewriter.rewriteCode(code);
     }
+    return Sets.newIdentityHashSet();
   }
 
   public void synthesizeUtilityClass(
@@ -611,8 +638,9 @@
     INVALID_FIELD_PUT,
     INVALID_ARRAY_PUT,
     FIELD_PUT_ON_ENUM,
-    TYPE_MISSMATCH_FIELD_PUT,
+    TYPE_MISMATCH_FIELD_PUT,
     INVALID_IF_TYPES,
+    DYNAMIC_TYPE,
     ENUM_METHOD_CALLED_WITH_NULL_RECEIVER,
     OTHER_UNSUPPORTED_INSTRUCTION;
   }
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 73eff15..6ba4154 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
@@ -23,7 +23,6 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 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.TypeElement;
 import com.android.tools.r8.ir.code.ArrayAccess;
 import com.android.tools.r8.ir.code.IRCode;
@@ -85,11 +84,11 @@
     return enumsToUnbox;
   }
 
-  void rewriteCode(IRCode code) {
+  Set<Phi> rewriteCode(IRCode code) {
     // We should not process the enum methods, they will be removed and they may contain invalid
     // rewriting rules.
     if (enumsToUnbox.isEmpty()) {
-      return;
+      return Sets.newIdentityHashSet();
     }
     assert code.isConsistentSSABeforeTypesAreCorrect();
     Set<Phi> affectedPhis = Sets.newIdentityHashSet();
@@ -165,10 +164,8 @@
         assert validateArrayAccess(instruction.asArrayAccess());
       }
     }
-    if (!affectedPhis.isEmpty()) {
-      new DestructivePhiTypeUpdater(appView).recomputeAndPropagateTypes(code, affectedPhis);
-    }
     assert code.isConsistentSSABeforeTypesAreCorrect();
+    return affectedPhis;
   }
 
   private boolean validateArrayAccess(ArrayAccess arrayAccess) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 0bf4d8b..302a58b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -124,7 +124,7 @@
 
   @Override
   public void methodNeverReturnsNull(DexEncodedMethod method) {
-    method.getMutableOptimizationInfo().neverReturnsNull();
+    method.getMutableOptimizationInfo().markNeverReturnsNull();
   }
 
   @Override
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
index fd9d62b..fa0e683 100644
--- 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
@@ -53,7 +53,9 @@
     }
     DexType enumType = invoke.inValues().get(0).getConstInstruction().asConstClass().getValue();
     DexProgramClass enumClass = appView.definitionForProgramType(enumType);
-    if (enumClass == null || enumClass.superType != appView.dexItemFactory().enumType) {
+    if (enumClass == null
+        || !enumClass.isEnum()
+        || enumClass.superType != appView.dexItemFactory().enumType) {
       return;
     }
     TypeElement dynamicUpperBoundType =
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/LibraryMemberOptimizer.java
similarity index 76%
rename from src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
rename to src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index fc687a0..46a1d7c 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/LibraryMemberOptimizer.java
@@ -5,7 +5,9 @@
 package com.android.tools.r8.ir.optimize.library;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory.LibraryMembers;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.IRCode;
@@ -21,23 +23,27 @@
 import java.util.Map;
 import java.util.Set;
 
-public class LibraryMethodOptimizer implements CodeOptimization {
+public class LibraryMemberOptimizer implements CodeOptimization {
 
   private final AppView<?> appView;
 
+  /** Library fields that are assumed to be final. */
+  private final Set<DexEncodedField> finalLibraryFields = Sets.newIdentityHashSet();
+
   /** The library types that are modeled. */
   private final Set<DexType> modeledLibraryTypes = Sets.newIdentityHashSet();
 
   private final Map<DexType, LibraryMethodModelCollection> libraryMethodModelCollections =
       new IdentityHashMap<>();
 
-  public LibraryMethodOptimizer(AppView<?> appView) {
+  public LibraryMemberOptimizer(AppView<?> appView) {
     this.appView = appView;
     register(new BooleanMethodOptimizer(appView));
     register(new ObjectMethodOptimizer(appView));
     register(new ObjectsMethodOptimizer(appView));
     register(new StringMethodOptimizer(appView));
     if (appView.appInfo().hasSubtyping() && appView.options().enableDynamicTypeOptimization) {
+      // Subtyping is required to prove the enum class is a subtype of java.lang.Enum.
       register(new EnumMethodOptimizer(appView));
     }
 
@@ -45,12 +51,35 @@
       register(new LogMethodOptimizer(appView));
     }
 
+    initializeFinalLibraryFields();
+
     LibraryOptimizationInfoInitializer libraryOptimizationInfoInitializer =
         new LibraryOptimizationInfoInitializer(appView);
     libraryOptimizationInfoInitializer.run();
     modeledLibraryTypes.addAll(libraryOptimizationInfoInitializer.getModeledLibraryTypes());
   }
 
+  private void initializeFinalLibraryFields() {
+    for (LibraryMembers libraryMembers : appView.dexItemFactory().libraryMembersCollection) {
+      libraryMembers.forEachFinalField(
+          field -> {
+            DexEncodedField definition = appView.definitionFor(field);
+            if (definition != null) {
+              if (definition.isFinal()) {
+                finalLibraryFields.add(definition);
+              } else {
+                assert false : "Field `" + field.toSourceString() + "` is not final";
+              }
+            }
+          });
+    }
+  }
+
+  /** Returns true if it is safe to assume that the given library field is final. */
+  public boolean isFinalLibraryField(DexEncodedField field) {
+    return finalLibraryFields.contains(field);
+  }
+
   /**
    * Returns true if {@param type} is a library type that is modeled in the compiler.
    *
@@ -77,9 +106,7 @@
 
   @Override
   public void optimize(
-      IRCode code,
-      OptimizationFeedback feedback,
-      MethodProcessor methodProcessor) {
+      IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     InstructionListIterator instructionIterator = code.instructionListIterator();
     while (instructionIterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
index f76aeae..c78a123 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
@@ -48,6 +48,7 @@
         affectedValues.addAll(outValue.affectedValues());
         outValue.replaceUsers(inValue);
       }
+      // TODO(b/152853271): Debugging information is lost here (DebugLocalWrite may be required).
       instructionIterator.removeOrReplaceByDebugLocalRead();
     }
   }
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 09c8f4e..0713076 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
@@ -39,7 +39,7 @@
       InvokeMethod invoke,
       DexEncodedMethod singleTarget,
       Set<Value> affectedValues) {
-    if (singleTarget.method == dexItemFactory.stringMethods.equals) {
+    if (singleTarget.method == dexItemFactory.stringMembers.equals) {
       optimizeEquals(code, instructionIterator, invoke);
     }
   }
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 4a1924b..1d679ecc 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
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
@@ -73,6 +74,7 @@
   private final Map<DexEncodedMethod, CandidateInfo> hostClassInits = new IdentityHashMap<>();
   private final Set<DexEncodedMethod> methodsToBeStaticized = Sets.newIdentityHashSet();
   private final Map<DexField, CandidateInfo> singletonFields = new IdentityHashMap<>();
+  private final Map<DexMethod, CandidateInfo> singletonGetters = new IdentityHashMap<>();
   private final Map<DexType, DexType> candidateToHostMapping = new IdentityHashMap<>();
 
   StaticizingProcessor(
@@ -102,7 +104,7 @@
                 .add(collectOptimizationInfo(feedback)));
 
     // Enqueue instance methods to be staticized (only remove references to 'this'). Intentionally
-    // not collection optimization info for these methods, since they will be reprocessed again
+    // not collecting optimization info for these methods, since they will be reprocessed again
     // below once staticized.
     enqueueMethodsWithCodeOptimizations(
         methodsToBeStaticized, optimizations -> optimizations.add(this::removeReferencesToThis));
@@ -211,19 +213,36 @@
       for (DexEncodedMethod method : info.referencedFrom) {
         IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.holder()));
         assert code != null;
-        List<StaticGet> singletonFieldReads =
+        List<Instruction> singletonUsers =
             Streams.stream(code.instructionIterator())
-                .filter(Instruction::isStaticGet)
-                .map(Instruction::asStaticGet)
-                .filter(get -> get.getField() == info.singletonField.field)
+                .filter(
+                    instruction -> {
+                      if (instruction.isStaticGet()
+                          && instruction.asStaticGet().getField() == info.singletonField.field) {
+                        return true;
+                      }
+                      DexEncodedMethod getter = info.getter.get();
+                      return getter != null
+                          && instruction.isInvokeStatic()
+                          && instruction.asInvokeStatic().getInvokedMethod() == getter.method;
+                    })
                 .collect(Collectors.toList());
         boolean fixableFieldReadsPerUsage = true;
-        for (StaticGet read : singletonFieldReads) {
-          Value dest = read.dest();
+        for (Instruction user : singletonUsers) {
+          if (user.outValue() == null) {
+            continue;
+          }
+          Value dest = user.outValue();
           visited.clear();
           trivialPhis.clear();
-          boolean onlyHasTrivialPhis = testAndCollectPhisComposedOfSameFieldRead(
-              visited, dest.uniquePhiUsers(), read.getField(), trivialPhis);
+          assert user.isInvokeStatic() || user.isStaticGet();
+          DexMember member =
+              user.isStaticGet()
+                  ? user.asStaticGet().getField()
+                  : user.asInvokeStatic().getInvokedMethod();
+          boolean onlyHasTrivialPhis =
+              testAndCollectPhisComposedOfSameMember(
+                  visited, dest.uniquePhiUsers(), member, trivialPhis);
           if (dest.hasPhiUsers() && !onlyHasTrivialPhis) {
             fixableFieldReadsPerUsage = false;
             break;
@@ -263,6 +282,10 @@
         }
       }
       singletonFields.put(candidate.singletonField.field, candidate);
+      DexEncodedMethod getter = candidate.getter.get();
+      if (getter != null) {
+        singletonGetters.put(getter.method, candidate);
+      }
       referencingExtraMethods.addAll(candidate.referencedFrom);
     }
 
@@ -380,28 +403,38 @@
   }
 
   private void rewriteReferences(IRCode code, MethodProcessor methodProcessor) {
-    // Process all singleton field reads and rewrite their users.
-    List<StaticGet> singletonFieldReads =
+    // Fetch all instructions that reference singletons to avoid concurrent modifications to the
+    // instruction list that can arise from doing it directly in the iterator.
+    List<Instruction> singletonUsers =
         Streams.stream(code.instructionIterator())
-            .filter(Instruction::isStaticGet)
-            .map(Instruction::asStaticGet)
-            .filter(get -> singletonFields.containsKey(get.getField()))
+            .filter(
+                instruction ->
+                    (instruction.isStaticGet()
+                            && singletonFields.containsKey(
+                                instruction.asFieldInstruction().getField()))
+                        || (instruction.isInvokeStatic()
+                            && singletonGetters.containsKey(
+                                instruction.asInvokeStatic().getInvokedMethod())))
             .collect(Collectors.toList());
-
-    singletonFieldReads.forEach(
-        read -> {
-          DexField field = read.getField();
-          CandidateInfo candidateInfo = singletonFields.get(field);
-          assert candidateInfo != null;
-          Value value = read.dest();
-          if (value != null) {
-            fixupStaticizedFieldReadUsers(code, value, field);
-          }
-          if (!candidateInfo.preserveRead.get()) {
-            read.removeOrReplaceByDebugLocalRead(code);
-          }
-        });
-
+    for (Instruction singletonUser : singletonUsers) {
+      CandidateInfo candidateInfo;
+      DexMember member;
+      if (singletonUser.isStaticGet()) {
+        candidateInfo = singletonFields.get(singletonUser.asStaticGet().getField());
+        member = singletonUser.asStaticGet().getField();
+      } else {
+        assert singletonUser.isInvokeStatic();
+        candidateInfo = singletonGetters.get(singletonUser.asInvokeStatic().getInvokedMethod());
+        member = singletonUser.asInvokeStatic().getInvokedMethod();
+      }
+      Value value = singletonUser.outValue();
+      if (value != null) {
+        fixupStaticizedFieldUsers(code, value, member);
+      }
+      if (!candidateInfo.preserveRead.get()) {
+        singletonUser.removeOrReplaceByDebugLocalRead(code);
+      }
+    }
     if (!candidateToHostMapping.isEmpty()) {
       remapMovedCandidates(code);
     }
@@ -468,7 +501,7 @@
   //    invoke-virtual { s1, ... } mtd1
   //    goto Exit
   //  b2:
-  //    s2 <- static-get singleton
+  //    s2 <- invoke-static getter()
   //    ...
   //    invoke-virtual { s2, ... } mtd1
   //    goto Exit
@@ -482,7 +515,7 @@
   //    ...
   //    goto Exit
   //  b2:
-  //    s2 <- static-get singleton
+  //    s2 <- invoke-static getter()
   //    ...
   //    goto Exit
   //  Exit:
@@ -493,8 +526,8 @@
   // From staticizer's viewpoint, `sp` is trivial in the sense that it is composed of values that
   // refer to the same singleton field. If so, we can safely relax the assertion; remove uses of
   // field reads; remove quasi-trivial phis; and then remove original field reads.
-  private boolean testAndCollectPhisComposedOfSameFieldRead(
-      Set<Phi> visited, Set<Phi> phisToCheck, DexField field, Set<Phi> trivialPhis) {
+  private boolean testAndCollectPhisComposedOfSameMember(
+      Set<Phi> visited, Set<Phi> phisToCheck, DexMember dexMember, Set<Phi> trivialPhis) {
     for (Phi phi : phisToCheck) {
       if (!visited.add(phi)) {
         continue;
@@ -505,16 +538,20 @@
         if (v.isPhi()) {
           chainedPhis.add(operand.asPhi());
         } else {
-          if (!v.definition.isStaticGet()) {
+          Instruction definition = v.definition;
+          if (!definition.isStaticGet() && !definition.isInvokeStatic()) {
             return false;
           }
-          if (v.definition.asStaticGet().getField() != field) {
+          if (definition.isStaticGet() && definition.asStaticGet().getField() != dexMember) {
+            return false;
+          } else if (definition.isInvokeStatic()
+              && definition.asInvokeStatic().getInvokedMethod() != dexMember) {
             return false;
           }
         }
       }
       if (!chainedPhis.isEmpty()) {
-        if (!testAndCollectPhisComposedOfSameFieldRead(visited, chainedPhis, field, trivialPhis)) {
+        if (!testAndCollectPhisComposedOfSameMember(visited, chainedPhis, dexMember, trivialPhis)) {
           return false;
         }
       }
@@ -525,13 +562,14 @@
 
   // Fixup field read usages. Same as {@link #fixupStaticizedThisUsers} except this one determines
   // quasi-trivial phis, based on the original field.
-  private void fixupStaticizedFieldReadUsers(IRCode code, Value dest, DexField field) {
+  private void fixupStaticizedFieldUsers(IRCode code, Value dest, DexMember member) {
     assert dest != null;
     // During the examine phase, field reads with any phi users have been invalidated, hence zero.
     // However, it may be not true if re-processing introduces phis after optimizing common suffix.
     Set<Phi> trivialPhis = Sets.newIdentityHashSet();
-    boolean onlyHasTrivialPhis = testAndCollectPhisComposedOfSameFieldRead(
-        Sets.newIdentityHashSet(), dest.uniquePhiUsers(), field, trivialPhis);
+    boolean onlyHasTrivialPhis =
+        testAndCollectPhisComposedOfSameMember(
+            Sets.newIdentityHashSet(), dest.uniquePhiUsers(), member, trivialPhis);
     assert !dest.hasPhiUsers() || onlyHasTrivialPhis;
     assert trivialPhis.isEmpty() || onlyHasTrivialPhis;
 
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 140c844..07d330b 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
@@ -852,7 +852,7 @@
     public boolean isToStringMethod(DexMethod method) {
       return method == factory.stringBuilderMethods.toString
           || method == factory.stringBufferMethods.toString
-          || method == factory.stringMethods.valueOf;
+          || method == factory.stringMembers.valueOf;
     }
 
     private boolean canHandleArgumentType(DexType argType) {
@@ -910,7 +910,7 @@
             for (Instruction outUser : out.uniqueUsers()) {
               if (outUser.isInvokeMethodWithReceiver()
                   && outUser.asInvokeMethodWithReceiver().getInvokedMethod()
-                      == factory.stringMethods.intern) {
+                      == factory.stringMembers.intern) {
                 numberOfBuildersWhoseResultIsInterned++;
                 return false;
               }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index a2b8eb5..cc8a24f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -191,7 +191,7 @@
         continue;
       }
 
-      if (invokedMethod == factory.stringMethods.trim) {
+      if (invokedMethod == factory.stringMembers.trim) {
         Value receiver = invoke.getReceiver().getAliasedValue();
         if (receiver.hasLocalInfo() || receiver.isPhi() || !receiver.definition.isConstString()) {
           continue;
@@ -210,7 +210,7 @@
       Function<DexString, Integer> operatorWithNoArg = null;
       BiFunction<DexString, DexString, Integer> operatorWithString = null;
       BiFunction<DexString, Integer, Integer> operatorWithInt = null;
-      if (invokedMethod == factory.stringMethods.hashCode) {
+      if (invokedMethod == factory.stringMembers.hashCode) {
         operatorWithNoArg = rcv -> {
           try {
             return rcv.decodedHashCode();
@@ -219,33 +219,33 @@
             throw new Unreachable();
           }
         };
-      } else if (invokedMethod == factory.stringMethods.length) {
+      } else if (invokedMethod == factory.stringMembers.length) {
         operatorWithNoArg = rcv -> rcv.size;
-      } else if (invokedMethod == factory.stringMethods.isEmpty) {
+      } else if (invokedMethod == factory.stringMembers.isEmpty) {
         operatorWithNoArg = rcv -> rcv.size == 0 ? 1 : 0;
-      } else if (invokedMethod == factory.stringMethods.contains) {
+      } else if (invokedMethod == factory.stringMembers.contains) {
         operatorWithString = (rcv, arg) -> rcv.toString().contains(arg.toString()) ? 1 : 0;
-      } else if (invokedMethod == factory.stringMethods.startsWith) {
+      } else if (invokedMethod == factory.stringMembers.startsWith) {
         operatorWithString = (rcv, arg) -> rcv.startsWith(arg) ? 1 : 0;
-      } else if (invokedMethod == factory.stringMethods.endsWith) {
+      } else if (invokedMethod == factory.stringMembers.endsWith) {
         operatorWithString = (rcv, arg) -> rcv.endsWith(arg) ? 1 : 0;
-      } else if (invokedMethod == factory.stringMethods.equals) {
+      } else if (invokedMethod == factory.stringMembers.equals) {
         operatorWithString = (rcv, arg) -> rcv.equals(arg) ? 1 : 0;
-      } else if (invokedMethod == factory.stringMethods.equalsIgnoreCase) {
+      } else if (invokedMethod == factory.stringMembers.equalsIgnoreCase) {
         operatorWithString = (rcv, arg) -> rcv.toString().equalsIgnoreCase(arg.toString()) ? 1 : 0;
-      } else if (invokedMethod == factory.stringMethods.contentEqualsCharSequence) {
+      } else if (invokedMethod == factory.stringMembers.contentEqualsCharSequence) {
         operatorWithString = (rcv, arg) -> rcv.toString().contentEquals(arg.toString()) ? 1 : 0;
-      } else if (invokedMethod == factory.stringMethods.indexOfInt) {
+      } else if (invokedMethod == factory.stringMembers.indexOfInt) {
         operatorWithInt = (rcv, idx) -> rcv.toString().indexOf(idx);
-      } else if (invokedMethod == factory.stringMethods.indexOfString) {
+      } else if (invokedMethod == factory.stringMembers.indexOfString) {
         operatorWithString = (rcv, arg) -> rcv.toString().indexOf(arg.toString());
-      } else if (invokedMethod == factory.stringMethods.lastIndexOfInt) {
+      } else if (invokedMethod == factory.stringMembers.lastIndexOfInt) {
         operatorWithInt = (rcv, idx) -> rcv.toString().lastIndexOf(idx);
-      } else if (invokedMethod == factory.stringMethods.lastIndexOfString) {
+      } else if (invokedMethod == factory.stringMembers.lastIndexOfString) {
         operatorWithString = (rcv, arg) -> rcv.toString().lastIndexOf(arg.toString());
-      } else if (invokedMethod == factory.stringMethods.compareTo) {
+      } else if (invokedMethod == factory.stringMembers.compareTo) {
         operatorWithString = (rcv, arg) -> rcv.toString().compareTo(arg.toString());
-      } else if (invokedMethod == factory.stringMethods.compareToIgnoreCase) {
+      } else if (invokedMethod == factory.stringMembers.compareToIgnoreCase) {
         operatorWithString = (rcv, arg) -> rcv.toString().compareToIgnoreCase(arg.toString());
       } else {
         continue;
@@ -513,7 +513,7 @@
       if (instr.isInvokeStatic()) {
         InvokeStatic invoke = instr.asInvokeStatic();
         DexMethod invokedMethod = invoke.getInvokedMethod();
-        if (invokedMethod != factory.stringMethods.valueOf) {
+        if (invokedMethod != factory.stringMembers.valueOf) {
           continue;
         }
         assert invoke.inValues().size() == 1;
@@ -546,7 +546,7 @@
       } else if (instr.isInvokeVirtual()) {
         InvokeVirtual invoke = instr.asInvokeVirtual();
         DexMethod invokedMethod = invoke.getInvokedMethod();
-        if (invokedMethod != factory.stringMethods.toString) {
+        if (invokedMethod != factory.stringMembers.toString) {
           continue;
         }
         assert invoke.inValues().size() == 1;
@@ -596,9 +596,9 @@
       if (escapeRoute.isInvokeMethod()) {
         DexMethod invokedMethod = escapeRoute.asInvokeMethod().getInvokedMethod();
         // b/120138731: Only allow known simple operations on const-string
-        if (invokedMethod == appView.dexItemFactory().stringMethods.hashCode
-            || invokedMethod == appView.dexItemFactory().stringMethods.isEmpty
-            || invokedMethod == appView.dexItemFactory().stringMethods.length) {
+        if (invokedMethod == appView.dexItemFactory().stringMembers.hashCode
+            || invokedMethod == appView.dexItemFactory().stringMembers.isEmpty
+            || invokedMethod == appView.dexItemFactory().stringMembers.length) {
           return true;
         }
         // Add more cases to filter out, if any.
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
index fbfacb8..b30d567 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -77,7 +77,7 @@
             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));
+                new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.stringMembers.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));
@@ -94,7 +94,7 @@
                   .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.INVOKEVIRTUAL, factory.stringMembers.concat, false));
       instructions.add(
           new CfInvoke(
               Opcodes.INVOKESPECIAL,
diff --git a/src/main/java/com/android/tools/r8/kotlin/ClassTypeSignatureToRenamedKmTypeConverter.java b/src/main/java/com/android/tools/r8/kotlin/ClassTypeSignatureToRenamedKmTypeConverter.java
deleted file mode 100644
index ea1ddc3..0000000
--- a/src/main/java/com/android/tools/r8/kotlin/ClassTypeSignatureToRenamedKmTypeConverter.java
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.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/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index a1cdd66..b4308b9 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -40,6 +40,11 @@
   public final Intrinsics intrinsics;
   public final Metadata metadata;
 
+  public static final class ClassClassifiers {
+
+    public static final String arrayBinaryName = NAME + "/Array";
+  }
+
   // Mappings from JVM types to Kotlin types (of type DexType)
   final Map<DexType, DexType> knownTypeConversion;
 
@@ -57,6 +62,7 @@
             // https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/index.html
             // Boxed primitives and arrays
             .put(factory.booleanType, factory.createType(addKotlinPrefix("Boolean;")))
+            .put(factory.boxedBooleanType, factory.createType(addKotlinPrefix("Boolean;")))
             .put(factory.booleanArrayType, factory.createType(addKotlinPrefix("BooleanArray;")))
             .put(factory.byteType, factory.createType(addKotlinPrefix("Byte;")))
             .put(factory.byteArrayType, factory.createType(addKotlinPrefix("ByteArray;")))
@@ -65,6 +71,7 @@
             .put(factory.shortType, factory.createType(addKotlinPrefix("Short;")))
             .put(factory.shortArrayType, factory.createType(addKotlinPrefix("ShortArray;")))
             .put(factory.intType, factory.createType(addKotlinPrefix("Int;")))
+            .put(factory.boxedIntType, factory.createType(addKotlinPrefix("Int;")))
             .put(factory.intArrayType, factory.createType(addKotlinPrefix("IntArray;")))
             .put(factory.longType, factory.createType(addKotlinPrefix("Long;")))
             .put(factory.longArrayType, factory.createType(addKotlinPrefix("LongArray;")))
@@ -96,13 +103,17 @@
                 factory.createType(addKotlinPrefix("collections/ListIterator;")))
             .put(factory.iterableType, factory.createType(addKotlinPrefix("collections/Iterable;")))
             .put(
-                factory.mapEntryType,
-                factory.createType(addKotlinPrefix("collections/Map$Entry;")))
+                factory.mapEntryType, factory.createType(addKotlinPrefix("collections/Map$Entry;")))
             // .../jvm/functions/FunctionN -> .../FunctionN
             .putAll(
-                IntStream.rangeClosed(0, 22).boxed().collect(Collectors.toMap(
-                    i -> factory.createType(addKotlinPrefix("jvm/functions/Function" + i + ";")),
-                    i -> factory.createType(addKotlinPrefix("Function" + i + ";")))))
+                IntStream.rangeClosed(0, 22)
+                    .boxed()
+                    .collect(
+                        Collectors.toMap(
+                            i ->
+                                factory.createType(
+                                    addKotlinPrefix("jvm/functions/Function" + i + ";")),
+                            i -> factory.createType(addKotlinPrefix("Function" + i + ";")))))
             .build();
   }
 
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 c891897..db37d8c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -83,16 +83,12 @@
       }
     }
 
-    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 = synthesizer.toRenamedKmType(itfType, null, null, converter);
+      KmType kmType = synthesizer.toRenamedKmType(itfType, null, null, getTypeParameters());
       if (kmType != null) {
         superTypes.add(kmType);
       }
@@ -101,7 +97,7 @@
     if (clazz.superType != appView.dexItemFactory().objectType) {
       // TODO(b/129925954): Use GenericSignature.ClassSignature#superClassSignature
       KmType kmTypeForSupertype =
-          synthesizer.toRenamedKmType(clazz.superType, null, null, converter);
+          synthesizer.toRenamedKmType(clazz.superType, null, null, getTypeParameters());
       if (kmTypeForSupertype != null) {
         superTypes.add(kmTypeForSupertype);
       }
@@ -151,7 +147,7 @@
       if (!method.isInstanceInitializer()) {
         continue;
       }
-      KmConstructor constructor = synthesizer.toRenamedKmConstructor(method);
+      KmConstructor constructor = synthesizer.toRenamedKmConstructor(clazz, method);
       if (constructor != null) {
         constructors.add(constructor);
       }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
new file mode 100644
index 0000000..c7c3701
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
@@ -0,0 +1,49 @@
+// 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.KmClassifier;
+
+// Provides access to information about a kotlin classifier
+public class KotlinClassifierInfo {
+
+  private static boolean isClass(KmClassifier classifier) {
+    return classifier instanceof KmClassifier.Class;
+  }
+
+  private static KmClassifier.Class getClassClassifier(KmClassifier classifier) {
+    return (KmClassifier.Class) classifier;
+  }
+
+  private static boolean isTypeAlias(KmClassifier classifier) {
+    return classifier instanceof KmClassifier.TypeAlias;
+  }
+
+  private static KmClassifier.TypeAlias getTypeAlias(KmClassifier classifier) {
+    return (KmClassifier.TypeAlias) classifier;
+  }
+
+  private static boolean isTypeParameter(KmClassifier classifier) {
+    return classifier instanceof KmClassifier.TypeParameter;
+  }
+
+  private static KmClassifier.TypeParameter getTypeParameter(KmClassifier classifier) {
+    return (KmClassifier.TypeParameter) classifier;
+  }
+
+  public static boolean equals(KmClassifier one, KmClassifier other) {
+    if (isClass(one)) {
+      return isClass(other)
+          && getClassClassifier(one).getName().equals(getClassClassifier(other).getName());
+    }
+    if (isTypeAlias(one)) {
+      return isTypeAlias(other)
+          && getTypeAlias(one).getName().equals(getTypeAlias(other).getName());
+    }
+    assert isTypeParameter(one);
+    return isTypeParameter(other)
+        && getTypeParameter(one).getId() == getTypeParameter(other).getId();
+  }
+}
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 64ed559..e516ff9 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -18,10 +18,12 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.function.BiConsumer;
@@ -146,6 +148,7 @@
     KmDeclarationContainer kmDeclarationContainer = getDeclarations();
     rewriteFunctions(synthesizer, kmDeclarationContainer.getFunctions());
     rewriteProperties(synthesizer, kmDeclarationContainer.getProperties());
+    rewriteTypeAliases(synthesizer, kmDeclarationContainer.getTypeAliases());
   }
 
   private void rewriteFunctions(KotlinMetadataSynthesizer synthesizer, List<KmFunction> functions) {
@@ -164,6 +167,37 @@
     }
   }
 
+  private void rewriteTypeAliases(
+      KotlinMetadataSynthesizer synthesizer, List<KmTypeAlias> typeAliases) {
+    Iterator<KmTypeAlias> iterator = typeAliases.iterator();
+    while (iterator.hasNext()) {
+      KmTypeAlias typeAlias = iterator.next();
+      KotlinTypeInfo expandedRenamed =
+          KotlinTypeInfo.create(typeAlias.expandedType).toRenamed(synthesizer);
+      if (expandedRenamed == null) {
+        // If the expanded type is pruned, the type-alias is also removed. Type-aliases can refer to
+        // other type-aliases in the underlying type, however, we only remove a type-alias when the
+        // expanded type is removed making it impossible to construct any type that references the
+        // type-alias anyway.
+        // TODO(b/151719926): Add a test for the above.
+        iterator.remove();
+        continue;
+      }
+      typeAlias.setExpandedType(expandedRenamed.asKmType());
+      // Modify the underlying type (right-hand side) of the type-alias.
+      KotlinTypeInfo underlyingRenamed =
+          KotlinTypeInfo.create(typeAlias.underlyingType).toRenamed(synthesizer);
+      if (underlyingRenamed == null) {
+        Reporter reporter = synthesizer.appView.options().reporter;
+        reporter.warning(
+            KotlinMetadataDiagnostic.messageInvalidUnderlyingType(clazz, typeAlias.getName()));
+        iterator.remove();
+        continue;
+      }
+      typeAlias.setUnderlyingType(underlyingRenamed.asKmType());
+    }
+  }
+
   private void rewriteProperties(
       KotlinMetadataSynthesizer synthesizer, List<KmProperty> properties) {
     Map<String, KmPropertyGroup.Builder> propertyGroupBuilderMap = new HashMap<>();
@@ -599,9 +633,10 @@
               "outerType",
               sb,
               nextIndent -> appendKmType(newIndent, sb, kmType.getOuterType()));
+          appendKeyValue(newIndent, "raw", sb, JvmExtensionsKt.isRaw(kmType) + "");
           appendKeyValue(
               newIndent,
-              "extensions",
+              "annotations",
               sb,
               nextIndent -> {
                 appendKmList(
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 ed7b49f..142aef3 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmPropertyProcessor;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -29,14 +28,15 @@
 import kotlinx.metadata.KmDeclarationContainer;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
-import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeParameter;
 import kotlinx.metadata.KmValueParameter;
 import kotlinx.metadata.jvm.JvmMethodSignature;
 
 // Provides access to field/method-level Kotlin information.
 public abstract class KotlinMemberInfo {
 
-  private static final List<KotlinValueParameterInfo> EMPTY_PARAM_INFO = ImmutableList.of();
+  private static final List<KotlinValueParameterInfo> EMPTY_VALUE_PARAM_INFO = ImmutableList.of();
+  static final List<KotlinTypeParameterInfo> EMPTY_TYPE_PARAM_INFO = ImmutableList.of();
 
   private static final KotlinMemberInfo NO_KOTLIN_MEMBER_INFO = new NoKotlinMemberInfo();
 
@@ -90,16 +90,24 @@
     final List<KotlinValueParameterInfo> valueParameterInfos;
     // Information from original KmFunction.returnType. Null if this is from a KmConstructor.
     public final KotlinTypeInfo returnType;
+    // Information from original KmFunction.receiverType. Null if this is from a KmConstructor.
+    final KotlinTypeInfo receiverParameterType;
+    // Information about original type parameters. Null if this is from a KmConstructor.
+    final List<KotlinTypeParameterInfo> kotlinTypeParameterInfo;
 
     private KotlinFunctionInfo(
         MemberKind memberKind,
         int flags,
         KotlinTypeInfo returnType,
-        List<KotlinValueParameterInfo> valueParameterInfos) {
+        KotlinTypeInfo receiverParameterType,
+        List<KotlinValueParameterInfo> valueParameterInfos,
+        List<KotlinTypeParameterInfo> kotlinTypeParameterInfo) {
       super(memberKind, flags);
       assert memberKind.isFunction() || memberKind.isConstructor();
       this.returnType = returnType;
+      this.receiverParameterType = receiverParameterType;
       this.valueParameterInfos = valueParameterInfos;
+      this.kotlinTypeParameterInfo = kotlinTypeParameterInfo;
     }
 
     KotlinValueParameterInfo getValueParameterInfo(int i) {
@@ -189,32 +197,51 @@
   }
 
   private static KotlinFunctionInfo createFunctionInfoFromConstructor(KmConstructor kmConstructor) {
-    return createFunctionInfo(
-        CONSTRUCTOR, kmConstructor.getFlags(), null, kmConstructor.getValueParameters());
+    return new KotlinFunctionInfo(
+        CONSTRUCTOR,
+        kmConstructor.getFlags(),
+        null,
+        null,
+        getValueParameters(kmConstructor.getValueParameters()),
+        EMPTY_TYPE_PARAM_INFO);
   }
 
   private static KotlinFunctionInfo createFunctionInfo(
       MemberKind memberKind, KmFunction kmFunction) {
-    return createFunctionInfo(
+    assert memberKind.isFunction();
+    KotlinTypeInfo returnTypeInfo = KotlinTypeInfo.create(kmFunction.getReturnType());
+    KotlinTypeInfo receiverParameterTypeInfo =
+        KotlinTypeInfo.create(kmFunction.getReceiverParameterType());
+    return new KotlinFunctionInfo(
         memberKind,
         kmFunction.getFlags(),
-        kmFunction.getReturnType(),
-        kmFunction.getValueParameters());
+        returnTypeInfo,
+        receiverParameterTypeInfo,
+        getValueParameters(kmFunction.getValueParameters()),
+        getTypeParameters(kmFunction.getTypeParameters()));
   }
 
-  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);
+  private static List<KotlinValueParameterInfo> getValueParameters(
+      List<KmValueParameter> parameters) {
+    if (parameters.isEmpty()) {
+      return EMPTY_VALUE_PARAM_INFO;
     }
-    List<KotlinValueParameterInfo> valueParameterInfos = new ArrayList<>(valueParameters.size());
-    for (KmValueParameter kmValueParameter : valueParameters) {
-      valueParameterInfos.add(KotlinValueParameterInfo.fromKmValueParameter(kmValueParameter));
+    ImmutableList.Builder<KotlinValueParameterInfo> builder = ImmutableList.builder();
+    for (KmValueParameter kmValueParameter : parameters) {
+      builder.add(KotlinValueParameterInfo.fromKmValueParameter(kmValueParameter));
     }
-    return new KotlinFunctionInfo(memberKind, flags, returnTypeInfo, valueParameterInfos);
+    return builder.build();
+  }
+
+  private static List<KotlinTypeParameterInfo> getTypeParameters(List<KmTypeParameter> parameters) {
+    if (parameters.isEmpty()) {
+      return EMPTY_TYPE_PARAM_INFO;
+    }
+    ImmutableList.Builder<KotlinTypeParameterInfo> builder = ImmutableList.builder();
+    for (KmTypeParameter kmTypeParameter : parameters) {
+      builder.add(KotlinTypeParameterInfo.fromKmTypeParameter(kmTypeParameter));
+    }
+    return builder.build();
   }
 
   private static KotlinFieldInfo createFieldInfo(MemberKind memberKind, KmProperty kmProperty) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
new file mode 100644
index 0000000..da33940
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
@@ -0,0 +1,49 @@
+// 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.android.tools.r8.Diagnostic;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+public class KotlinMetadataDiagnostic implements Diagnostic {
+
+  private final Origin origin;
+  private final Position position;
+  private final String message;
+
+  public KotlinMetadataDiagnostic(Origin origin, Position position, String message) {
+    this.origin = origin;
+    this.position = position;
+    this.message = message;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return position;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return message;
+  }
+
+  static KotlinMetadataDiagnostic messageInvalidUnderlyingType(DexClass clazz, String typeAlias) {
+    return new KotlinMetadataDiagnostic(
+        clazz.getOrigin(),
+        Position.UNKNOWN,
+        "The type alias "
+            + typeAlias
+            + " in class "
+            + clazz.type.getName()
+            + " has an invalid underlying type. The type-alias is removed from the output.");
+  }
+}
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 2e90094..bed05c0 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -3,13 +3,13 @@
 // 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.EMPTY_TYPE_PARAM_INFO;
 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;
 import static kotlinx.metadata.Flag.Property.IS_VAR;
 import static kotlinx.metadata.FlagsKt.flagsOf;
 
@@ -23,18 +23,22 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 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.graph.InnerClassAttribute;
 import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinFunctionInfo;
 import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinPropertyInfo;
+import com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.AddKotlinAnyType;
+import com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.KmVisitorOption;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Box;
+import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.function.Consumer;
+import kotlinx.metadata.KmClassifier;
 import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
@@ -47,8 +51,8 @@
 
 class KotlinMetadataSynthesizer {
 
-  private final AppView<AppInfoWithLiveness> appView;
-  private final NamingLens lens;
+  final AppView<AppInfoWithLiveness> appView;
+  final NamingLens lens;
   private final List<KmTypeParameter> classTypeParameters;
 
   public KotlinMetadataSynthesizer(
@@ -107,49 +111,45 @@
       DexType type,
       TypeSignature typeSignature,
       KotlinTypeInfo originalKotlinTypeInfo,
-      ClassTypeSignatureToRenamedKmTypeConverter converter) {
+      List<KmTypeParameter> typeParameters) {
     if (originalKotlinTypeInfo != null && originalKotlinTypeInfo.isTypeAlias()) {
       KmType kmType = new KmType(flagsOf());
       kmType.visitTypeAlias(originalKotlinTypeInfo.asTypeAlias().getName());
       return kmType;
     }
-    return toRenamedKmTypeWithClassifier(type, typeSignature, converter);
+    return toRenamedKmTypeWithClassifier(
+        type, originalKotlinTypeInfo, typeSignature, typeParameters);
   }
 
   private KmType toRenamedKmTypeWithClassifierForFieldSignature(
-      DexType type,
+      KotlinTypeInfo originalTypeInfo,
       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.
+      List<KmTypeParameter> typeParameters) {
     Box<KmType> kmTypeBox = new Box<>();
     populateKmTypeFromSignature(
         fieldSignature,
-        () -> {
+        originalTypeInfo,
+        (kmVisitorOption) -> {
+          assert kmVisitorOption == KmVisitorOption.VISIT_NEW;
           KmType value = new KmType(flagsOf());
           kmTypeBox.set(value);
           return value;
         },
-        converter.getTypeParameters(),
-        appView.dexItemFactory());
+        typeParameters,
+        appView.dexItemFactory(),
+        AddKotlinAnyType.ADD);
     return kmTypeBox.get();
   }
 
   private KmType toRenamedKmTypeWithClassifier(
       DexType type,
+      KotlinTypeInfo originalTypeInfo,
       TypeSignature typeSignature,
-      ClassTypeSignatureToRenamedKmTypeConverter converter) {
+      List<KmTypeParameter> typeParameters) {
     if (typeSignature != null && typeSignature.isFieldTypeSignature()) {
       KmType renamedKmType =
           toRenamedKmTypeWithClassifierForFieldSignature(
-              type, typeSignature.asFieldTypeSignature(), converter);
+              originalTypeInfo, typeSignature.asFieldTypeSignature(), typeParameters);
       if (renamedKmType != null) {
         return renamedKmType;
       }
@@ -165,34 +165,93 @@
     // 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 = toRenamedKmTypeWithClassifier(elementType, null, converter);
-      renamedKmType.getArguments().add(new KmTypeProjection(KmVariance.OUT, argumentType));
+      KmType argumentType = toRenamedKmTypeWithClassifier(elementType, null, null, typeParameters);
+      KmVariance variance =
+          originalTypeInfo != null && originalTypeInfo.isObjectArray()
+              ? originalTypeInfo.getArguments().get(0).variance
+              : KmVariance.INVARIANT;
+      renamedKmType.getArguments().add(new KmTypeProjection(variance, argumentType));
     }
     return renamedKmType;
   }
 
-  KmConstructor toRenamedKmConstructor(DexEncodedMethod method) {
+  KmConstructor toRenamedKmConstructor(DexClass clazz, DexEncodedMethod method) {
     // Make sure it is an instance initializer and live.
     if (!method.isInstanceInitializer()
         || !appView.appInfo().liveMethods.contains(method.method)) {
       return null;
     }
-    KmConstructor kmConstructor = new KmConstructor(method.accessFlags.getAsKotlinFlags());
+    // Take access flags from metadata.
+    KotlinFunctionInfo kotlinFunctionInfo = method.getKotlinMemberInfo().asFunctionInfo();
+    int flags;
+    List<KotlinTypeParameterInfo> originalTypeParameterInfo;
+    if (kotlinFunctionInfo != null) {
+      flags = kotlinFunctionInfo.flags;
+      originalTypeParameterInfo = kotlinFunctionInfo.kotlinTypeParameterInfo;
+    } else {
+      flags = method.accessFlags.getAsKotlinFlags();
+      originalTypeParameterInfo = EMPTY_TYPE_PARAM_INFO;
+    }
+    KmConstructor kmConstructor = new KmConstructor(flags);
     JvmExtensionsKt.setSignature(kmConstructor, toJvmMethodSignature(method.method));
     MethodTypeSignature signature = GenericSignature.Parser.toMethodTypeSignature(method, appView);
     List<KmTypeParameter> typeParameters =
         convertFormalTypeParameters(
+            originalTypeParameterInfo,
             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(method, signature, parameters, converter)) {
+    if (!populateKmValueParameters(method, signature, parameters, typeParameters)) {
       return null;
     }
+    // For inner, non-static classes, the type-parameter for the receiver should not have a
+    // value-parameter:
+    // val myInner : OuterNestedInner = nested.Inner(1)
+    // Will have value-parameters for the constructor:
+    // #  constructors: KmConstructor[
+    // #    KmConstructor{
+    // #      flags: 6,
+    // #      valueParameters: KmValueParameter[
+    // #        KmValueParameter{
+    // #          flags: 0,
+    // #          name: x,
+    // #          type: KmType{
+    // #            flags: 0,
+    // #            classifier: Class(name=kotlin/Int),
+    // #            arguments: KmTypeProjection[],
+    // #            abbreviatedType: null,
+    // #            outerType: null,
+    // #            raw: false,
+    // #            annotations: KmAnnotion[],
+    // #          },
+    // #          varargElementType: null,
+    // #        }
+    // #      ],
+    // #     signature: <init>(Lcom/android/tools/r8/kotlin/metadata/typealias_lib/Outer$Nested;I)V,
+    // #    }
+    // #  ],
+    // A bit weird since the signature obviously have two value-parameters.
+    List<InnerClassAttribute> innerClasses = clazz.getInnerClasses();
+    if (!parameters.isEmpty() && !innerClasses.isEmpty()) {
+      DexType immediateOuterType = null;
+      for (InnerClassAttribute innerClass : innerClasses) {
+        if (innerClass.getInner() == clazz.type) {
+          immediateOuterType = innerClass.getOuter();
+          break;
+        }
+      }
+      if (immediateOuterType != null) {
+        String classifier = toRenamedClassifier(immediateOuterType);
+        KmType potentialReceiver = parameters.get(0).getType();
+        if (potentialReceiver != null
+            && potentialReceiver.classifier instanceof KmClassifier.Class
+            && ((KmClassifier.Class) potentialReceiver.classifier).getName().equals(classifier)) {
+          parameters.remove(0);
+        }
+      }
+    }
     return kmConstructor;
   }
 
@@ -223,18 +282,18 @@
     //  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);
-
+    List<KmTypeParameter> methodTypeParameters = kmFunction.getTypeParameters();
     DexProto proto = method.method.proto;
     DexType returnType = proto.returnType;
     TypeSignature returnSignature = signature.returnType().typeSignature();
+    List<KmTypeParameter> allTypeParameters =
+        convertFormalTypeParameters(
+            kotlinMemberInfo.kotlinTypeParameterInfo,
+            signature.getFormalTypeParameters(),
+            methodTypeParameters::add);
     KmType kmReturnType =
-        toRenamedKmType(returnType, returnSignature, kotlinMemberInfo.returnType, converter);
+        toRenamedKmType(
+            returnType, returnSignature, kotlinMemberInfo.returnType, allTypeParameters);
     if (kmReturnType == null) {
       return null;
     }
@@ -243,30 +302,42 @@
       assert proto.parameters.values.length > 0 : method.method.toSourceString();
       DexType receiverType = proto.parameters.values[0];
       TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
-      KmType kmReceiverType = toRenamedKmType(receiverType, receiverSignature, null, converter);
+      KmType kmReceiverType =
+          toRenamedKmType(
+              receiverType,
+              receiverSignature,
+              kotlinMemberInfo.receiverParameterType,
+              allTypeParameters);
       if (kmReceiverType == null) {
         return null;
       }
       kmFunction.setReceiverParameterType(kmReceiverType);
     }
 
-    if (!populateKmValueParameters(method, signature, kmFunction.getValueParameters(), converter)) {
+    if (!populateKmValueParameters(
+        method, signature, kmFunction.getValueParameters(), allTypeParameters)) {
       return null;
     }
     return kmFunction;
   }
 
   private List<KmTypeParameter> convertFormalTypeParameters(
-      List<FormalTypeParameter> parameters, Consumer<KmTypeParameter> addedFromParameters) {
+      List<KotlinTypeParameterInfo> originalTypeParameterInfo,
+      List<FormalTypeParameter> parameters,
+      Consumer<KmTypeParameter> addedFromParameters) {
     return KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
-        classTypeParameters, parameters, appView.dexItemFactory(), addedFromParameters);
+        classTypeParameters,
+        originalTypeParameterInfo,
+        parameters,
+        appView.dexItemFactory(),
+        addedFromParameters);
   }
 
   private boolean populateKmValueParameters(
       DexEncodedMethod method,
       MethodTypeSignature signature,
       List<KmValueParameter> parameters,
-      ClassTypeSignatureToRenamedKmTypeConverter converter) {
+      List<KmTypeParameter> typeParameters) {
     KotlinFunctionInfo kotlinFunctionInfo = method.getKotlinMemberInfo().asFunctionInfo();
     if (kotlinFunctionInfo == null) {
       return false;
@@ -281,7 +352,11 @@
       TypeSignature parameterTypeSignature = signature.getParameterTypeSignature(i);
       KmValueParameter kmValueParameter =
           toRewrittenKmValueParameter(
-              valueParameterInfo, parameterType, parameterTypeSignature, parameterName, converter);
+              valueParameterInfo,
+              parameterType,
+              parameterTypeSignature,
+              parameterName,
+              typeParameters);
       if (kmValueParameter == null) {
         return false;
       }
@@ -295,13 +370,13 @@
       DexType parameterType,
       TypeSignature parameterTypeSignature,
       String candidateParameterName,
-      ClassTypeSignatureToRenamedKmTypeConverter converter) {
+      List<KmTypeParameter> typeParameters) {
     int flag = valueParameterInfo != null ? valueParameterInfo.flag : flagsOf();
     String name = valueParameterInfo != null ? valueParameterInfo.name : candidateParameterName;
     KmValueParameter kmValueParameter = new KmValueParameter(flag, name);
     KotlinTypeInfo originalKmTypeInfo = valueParameterInfo != null ? valueParameterInfo.type : null;
     KmType kmParamType =
-        toRenamedKmType(parameterType, parameterTypeSignature, originalKmTypeInfo, converter);
+        toRenamedKmType(parameterType, parameterTypeSignature, originalKmTypeInfo, typeParameters);
     if (kmParamType == null) {
       return null;
     }
@@ -309,17 +384,17 @@
     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;
       }
+      // vararg x: T gets translated to x: Array<out T>
       DexType elementType = parameterType.toArrayElementType(appView.dexItemFactory());
       TypeSignature elementSignature =
           parameterTypeSignature != null
               ? parameterTypeSignature.toArrayElementTypeSignature(appView) : null;
-      KmType kmElementType = toRenamedKmType(elementType, elementSignature, null, converter);
+      KmType kmElementType = toRenamedKmType(elementType, elementSignature, null, typeParameters);
       if (kmElementType == null) {
         return null;
       }
@@ -488,12 +563,9 @@
         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);
+            synthesizer.toRenamedKmType(field.field.type, null, null, this.classTypeParameters);
         if (kmPropertyType != null) {
           kmProperty.setReturnType(kmPropertyType);
         }
@@ -512,23 +584,22 @@
       }
       MethodTypeSignature signature =
           GenericSignature.Parser.toMethodTypeSignature(method, appView);
+      // TODO(b/152599446): Update with type parameters for the receiver.
       List<KmTypeParameter> typeParameters =
           KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
               classTypeParameters,
+              KotlinMemberInfo.EMPTY_TYPE_PARAM_INFO,
               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);
+          synthesizer.toRenamedKmType(receiverType, receiverSignature, null, typeParameters);
       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()));
+        return KotlinMetadataSynthesizerUtils.hasEqualClassifier(
+            kmReceiverType, kmProperty.getReceiverParameterType());
       }
       kmProperty.setReceiverParameterType(kmReceiverType);
       return true;
@@ -556,25 +627,24 @@
           : "checkGetterCriteria: " + this.toString();
       MethodTypeSignature signature =
           GenericSignature.Parser.toMethodTypeSignature(getter, appView);
+      // TODO(b/152599446): Update with type parameters for the getter.
       List<KmTypeParameter> typeParameters =
           KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
               this.classTypeParameters,
+              ImmutableList.of(),
               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);
+              returnType, returnSignature, getterInfo.returnType, typeParameters);
       if (kmProperty.getReturnType() == defaultPropertyType) {
         // The property type is not set yet.
         kmProperty.setReturnType(kmPropertyType);
-      } else if (!getDescriptorFromKmType(kmPropertyType)
-          .equals(getDescriptorFromKmType(kmProperty.getReturnType()))) {
+      } else if (!KotlinMetadataSynthesizerUtils.hasEqualClassifier(
+          kmPropertyType, kmProperty.getReturnType())) {
         // If property type is set already (via backing field), make sure it's consistent.
         return null;
       }
@@ -610,27 +680,27 @@
           : "checkSetterCriteria: " + this.toString();
       MethodTypeSignature signature =
           GenericSignature.Parser.toMethodTypeSignature(setter, appView);
+      // TODO(b/152599446): Update with type parameters for the setter.
       List<KmTypeParameter> typeParameters =
           KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
               classTypeParameters,
+              ImmutableList.of(),
               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);
+          synthesizer.toRenamedKmType(
+              valueType, valueSignature, setterInfo.returnType, typeParameters);
       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()))) {
+        if (!KotlinMetadataSynthesizerUtils.hasEqualClassifier(
+            kmPropertyType, kmProperty.getReturnType())) {
           return null;
         }
       }
@@ -639,7 +709,7 @@
           setter.getKotlinMemberInfo().asPropertyInfo().valueParameterInfo;
       KmValueParameter kmValueParameter =
           synthesizer.toRewrittenKmValueParameter(
-              valueParameterInfo, valueType, valueSignature, "value", converter);
+              valueParameterInfo, valueType, valueSignature, "value", typeParameters);
       if (kmValueParameter != null) {
         kmProperty.setSetterParameter(kmValueParameter);
       }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java
index 9382cd9..e773cef 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java
@@ -15,43 +15,74 @@
 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.android.tools.r8.kotlin.Kotlin.ClassClassifiers;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.function.Consumer;
-import java.util.function.Supplier;
+import java.util.function.Function;
+import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmTypeParameter;
 import kotlinx.metadata.KmTypeVisitor;
 import kotlinx.metadata.KmVariance;
 
 class KotlinMetadataSynthesizerUtils {
 
+  // The KmVisitorOption is used for star projections, otherwise we will have late-init failures
+  // due to visitStarProjection adding an argument.
+  enum KmVisitorOption {
+    VISIT_NEW,
+    VISIT_PARENT
+  }
+
+  // The AddKotlinAnyType is carrying around information regarding the need for adding the trivial
+  // type Kotlin/Any. The information is not consistently added, for example, for upper bounds
+  // the trivial bound is not recorded.
+  public enum AddKotlinAnyType {
+    ADD,
+    DISREGARD
+  }
+
   static void populateKmTypeFromSignature(
       FieldTypeSignature typeSignature,
-      Supplier<KmTypeVisitor> typeVisitor,
+      KotlinTypeInfo originalTypeInfo,
+      Function<KmVisitorOption, KmTypeVisitor> typeVisitor,
       List<KmTypeParameter> allTypeParameters,
-      DexItemFactory factory) {
+      DexItemFactory factory,
+      AddKotlinAnyType addAny) {
     if (typeSignature.isClassTypeSignature()) {
       populateKmTypeFromClassTypeSignature(
-          typeSignature.asClassTypeSignature(), typeVisitor, allTypeParameters, factory);
+          typeSignature.asClassTypeSignature(),
+          originalTypeInfo,
+          typeVisitor,
+          allTypeParameters,
+          factory,
+          addAny);
     } else if (typeSignature.isArrayTypeSignature()) {
       populateKmTypeFromArrayTypeSignature(
-          typeSignature.asArrayTypeSignature(), typeVisitor, allTypeParameters, factory);
-    } else {
-      assert typeSignature.isTypeVariableSignature();
+          typeSignature.asArrayTypeSignature(),
+          originalTypeInfo,
+          typeVisitor,
+          allTypeParameters,
+          factory,
+          addAny);
+    } else if (typeSignature.isTypeVariableSignature()) {
       populateKmTypeFromTypeVariableSignature(
           typeSignature.asTypeVariableSignature(), typeVisitor, allTypeParameters);
+    } else {
+      assert typeSignature.isStar();
+      typeVisitor.apply(KmVisitorOption.VISIT_PARENT).visitStarProjection();
     }
   }
 
   private static void populateKmTypeFromTypeVariableSignature(
       TypeVariableSignature typeSignature,
-      Supplier<KmTypeVisitor> visitor,
+      Function<KmVisitorOption, KmTypeVisitor> typeVisitor,
       List<KmTypeParameter> allTypeParameters) {
     for (KmTypeParameter typeParameter : allTypeParameters) {
       if (typeParameter
           .getName()
           .equals(typeSignature.asTypeVariableSignature().getTypeVariable())) {
-        visitor.get().visitTypeParameter(typeParameter.getId());
+        typeVisitor.apply(KmVisitorOption.VISIT_NEW).visitTypeParameter(typeParameter.getId());
         return;
       }
     }
@@ -59,39 +90,79 @@
 
   private static void populateKmTypeFromArrayTypeSignature(
       ArrayTypeSignature typeSignature,
-      Supplier<KmTypeVisitor> visitor,
+      KotlinTypeInfo originalTypeInfo,
+      Function<KmVisitorOption, KmTypeVisitor> typeVisitor,
       List<KmTypeParameter> allTypeParameters,
-      DexItemFactory factory) {
+      DexItemFactory factory,
+      AddKotlinAnyType addAny) {
     ArrayTypeSignature arrayTypeSignature = typeSignature.asArrayTypeSignature();
     if (!arrayTypeSignature.elementSignature().isFieldTypeSignature()) {
       return;
     }
-    KmTypeVisitor kmType = visitor.get();
-    kmType.visitClass(NAME + "/Array");
+    KmTypeVisitor kmType = typeVisitor.apply(KmVisitorOption.VISIT_NEW);
+    kmType.visitClass(ClassClassifiers.arrayBinaryName);
+    KotlinTypeProjectionInfo projectionInfo =
+        originalTypeInfo == null ? null : originalTypeInfo.getArgumentOrNull(0);
     populateKmTypeFromSignature(
         arrayTypeSignature.elementSignature().asFieldTypeSignature(),
-        () -> kmType.visitArgument(flagsOf(), KmVariance.INVARIANT),
+        projectionInfo == null ? null : projectionInfo.typeInfo,
+        (kmVisitorOption) -> {
+          if (kmVisitorOption == KmVisitorOption.VISIT_PARENT) {
+            assert originalTypeInfo.getArguments().size() == 1
+                && originalTypeInfo.getArguments().get(0).isStarProjection();
+            return kmType;
+          } else {
+            // TODO(b/152886451): Variance is only NULL when star projection. If that is the case
+            //  we should just apply starProjection.
+            return kmType.visitArgument(
+                flagsOf(),
+                projectionInfo == null || projectionInfo.variance == null
+                    ? KmVariance.INVARIANT
+                    : projectionInfo.variance);
+          }
+        },
         allTypeParameters,
-        factory);
+        factory,
+        addAny);
   }
 
   private static void populateKmTypeFromClassTypeSignature(
       ClassTypeSignature typeSignature,
-      Supplier<KmTypeVisitor> visitor,
+      KotlinTypeInfo originalTypeInfo,
+      Function<KmVisitorOption, KmTypeVisitor> typeVisitor,
       List<KmTypeParameter> allTypeParameters,
-      DexItemFactory factory) {
+      DexItemFactory factory,
+      AddKotlinAnyType addAny) {
     // No need to record the trivial argument.
-    if (factory.objectType == typeSignature.type()) {
+    if (addAny == AddKotlinAnyType.DISREGARD && factory.objectType == typeSignature.type()) {
       return;
     }
-    KmTypeVisitor kmType = visitor.get();
+    KmTypeVisitor kmType = typeVisitor.apply(KmVisitorOption.VISIT_NEW);
     kmType.visitClass(toClassifier(typeSignature.type(), factory));
-    for (FieldTypeSignature typeArgument : typeSignature.typeArguments()) {
+    for (int i = 0; i < typeSignature.typeArguments().size(); i++) {
+      FieldTypeSignature typeArgument = typeSignature.typeArguments().get(i);
+      KotlinTypeProjectionInfo projectionInfo =
+          originalTypeInfo == null ? null : originalTypeInfo.getArgumentOrNull(i);
       populateKmTypeFromSignature(
           typeArgument,
-          () -> kmType.visitArgument(flagsOf(), KmVariance.INVARIANT),
+          projectionInfo == null ? null : projectionInfo.typeInfo,
+          (kmVisitorOption) -> {
+            if (kmVisitorOption == KmVisitorOption.VISIT_PARENT) {
+              assert projectionInfo == null || projectionInfo.isStarProjection();
+              return kmType;
+            } else {
+              // TODO(b/152886451): Variance is only NULL when star projection. If that is the case
+              //  we should just apply starProjection.
+              return kmType.visitArgument(
+                  flagsOf(),
+                  projectionInfo == null || projectionInfo.variance == null
+                      ? KmVariance.INVARIANT
+                      : projectionInfo.variance);
+            }
+          },
           allTypeParameters,
-          factory);
+          factory,
+          addAny);
     }
   }
 
@@ -121,6 +192,7 @@
    * </pre>
    *
    * @param classTypeParameters
+   * @param originalTypeParameterInfo
    * @param parameters
    * @param factory
    * @param addedFromParameters
@@ -128,6 +200,7 @@
    */
   static List<KmTypeParameter> convertFormalTypeParameters(
       List<KmTypeParameter> classTypeParameters,
+      List<KotlinTypeParameterInfo> originalTypeParameterInfo,
       List<FormalTypeParameter> parameters,
       DexItemFactory factory,
       Consumer<KmTypeParameter> addedFromParameters) {
@@ -136,20 +209,31 @@
     }
     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) {
+    for (int i = 0; i < parameters.size(); i++) {
+      FormalTypeParameter parameter = parameters.get(i);
+      int flags =
+          originalTypeParameterInfo.size() > i
+              ? originalTypeParameterInfo.get(i).getFlags()
+              : flagsOf();
+      KmVariance variance =
+          originalTypeParameterInfo.size() > i
+              ? originalTypeParameterInfo.get(i).getVariance()
+              : KmVariance.INVARIANT;
       KmTypeParameter element =
-          new KmTypeParameter(flagsOf(), parameter.getName(), idCounter++, KmVariance.INVARIANT);
+          new KmTypeParameter(
+              flags,
+              parameter.getName(),
+              getNewId(originalTypeParameterInfo, parameter.getName(), i),
+              variance);
       builder.add(element);
       addedFromParameters.accept(element);
     }
-    idCounter = 0;
     ImmutableList<KmTypeParameter> allTypeParameters = builder.build();
-    for (FormalTypeParameter parameter : parameters) {
-      KmTypeParameter kmTypeParameter =
-          allTypeParameters.get(classTypeParameters.size() + idCounter++);
+    for (int i = 0; i < parameters.size(); i++) {
+      FormalTypeParameter parameter = parameters.get(i);
+      KmTypeParameter kmTypeParameter = allTypeParameters.get(classTypeParameters.size() + i);
       visitUpperBound(parameter.getClassBound(), allTypeParameters, kmTypeParameter, factory);
       if (parameter.getInterfaceBounds() != null) {
         for (FieldTypeSignature interfaceBound : parameter.getInterfaceBounds()) {
@@ -160,6 +244,20 @@
     return allTypeParameters;
   }
 
+  // Tries to pick the id from the type-parameter name. If no such exist, compute the highest id and
+  // add the index of the current argument (to ensure unique naming in sequence).
+  private static int getNewId(
+      List<KotlinTypeParameterInfo> typeParameterInfos, String typeVariable, int currentId) {
+    int maxId = -1;
+    for (KotlinTypeParameterInfo typeParameterInfo : typeParameterInfos) {
+      if (typeParameterInfo.getName().equals(typeVariable)) {
+        return typeParameterInfo.getId();
+      }
+      maxId = Math.max(maxId, typeParameterInfo.getId());
+    }
+    return maxId + 1 + currentId;
+  }
+
   private static void visitUpperBound(
       FieldTypeSignature typeSignature,
       List<KmTypeParameter> allTypeParameters,
@@ -169,6 +267,24 @@
       return;
     }
     populateKmTypeFromSignature(
-        typeSignature, () -> parameter.visitUpperBound(flagsOf()), allTypeParameters, factory);
+        typeSignature,
+        null,
+        (kmVisitorOption) -> {
+          assert kmVisitorOption == KmVisitorOption.VISIT_NEW;
+          return parameter.visitUpperBound(flagsOf());
+        },
+        allTypeParameters,
+        factory,
+        AddKotlinAnyType.DISREGARD);
+  }
+
+  public static boolean hasEqualClassifier(KmType one, KmType other) {
+    if (one == null && other == null) {
+      return true;
+    }
+    if (one == null || other == null) {
+      return false;
+    }
+    return KotlinClassifierInfo.equals(one.classifier, other.classifier);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
index 130a660..e9bc069 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -4,11 +4,19 @@
 
 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.kotlin.Kotlin.ClassClassifiers;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import kotlinx.metadata.KmClassifier;
 import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmTypeProjection;
+import kotlinx.metadata.KmTypeVisitor;
 
 // Provides access to Kotlin information about a kotlin type.
 public class KotlinTypeInfo {
@@ -45,7 +53,95 @@
     return (KmClassifier.TypeAlias) classifier;
   }
 
+  public boolean isClass() {
+    return classifier instanceof KmClassifier.Class;
+  }
+
+  public KmClassifier.Class asClass() {
+    return (KmClassifier.Class) classifier;
+  }
+
+  public boolean isTypeParameter() {
+    return classifier instanceof KmClassifier.TypeParameter;
+  }
+
+  public KmClassifier.TypeParameter asTypeParameter() {
+    return (KmClassifier.TypeParameter) classifier;
+  }
+
+  public boolean isObjectArray() {
+    if (!isClass()) {
+      KmClassifier.Class classifier = (KmClassifier.Class) this.classifier;
+      return classifier.getName().equals(ClassClassifiers.arrayBinaryName) && arguments.size() == 1;
+    }
+    return false;
+  }
+
   public List<KotlinTypeProjectionInfo> getArguments() {
     return arguments;
   }
+
+  public KotlinTypeProjectionInfo getArgumentOrNull(int index) {
+    List<KotlinTypeProjectionInfo> arguments = getArguments();
+    return arguments.size() > index ? getArguments().get(index) : null;
+  }
+
+  public KotlinTypeInfo toRenamed(KotlinMetadataSynthesizer synthesizer) {
+    DexType originalType = getLiveDexTypeFromClassClassifier(synthesizer.appView);
+    if (isClass() && originalType == null) {
+      return null;
+    }
+    KmClassifier renamedClassifier = classifier;
+    if (originalType != null) {
+      String typeClassifier = synthesizer.toRenamedClassifier(originalType);
+      if (typeClassifier != null) {
+        renamedClassifier = new KmClassifier.Class(typeClassifier);
+      }
+    }
+    if (arguments.isEmpty()) {
+      return renamedClassifier == classifier
+          ? this
+          : new KotlinTypeInfo(renamedClassifier, EMPTY_ARGUMENTS);
+    }
+    ImmutableList.Builder<KotlinTypeProjectionInfo> builder = ImmutableList.builder();
+    for (KotlinTypeProjectionInfo argument : arguments) {
+      builder.add(
+          new KotlinTypeProjectionInfo(
+              argument.variance,
+              argument.typeInfo == null ? null : argument.typeInfo.toRenamed(synthesizer)));
+    }
+    return new KotlinTypeInfo(renamedClassifier, builder.build());
+  }
+
+  private DexType getLiveDexTypeFromClassClassifier(AppView<AppInfoWithLiveness> appView) {
+    if (!isClass()) {
+      return null;
+    }
+    String descriptor = DescriptorUtils.getDescriptorFromKotlinClassifier(asClass().getName());
+    DexType type = appView.dexItemFactory().createType(descriptor);
+    if (appView.appInfo().wasPruned(type)) {
+      return null;
+    }
+    return type;
+  }
+
+  public KmType asKmType() {
+    KmType kmType = new KmType(flagsOf());
+    visit(kmType);
+    return kmType;
+  }
+
+  public void visit(KmTypeVisitor visitor) {
+    if (isClass()) {
+      visitor.visitClass(asClass().getName());
+    } else if (isTypeAlias()) {
+      visitor.visitTypeAlias(asTypeAlias().getName());
+    } else {
+      assert isTypeParameter();
+      visitor.visitTypeParameter(asTypeParameter().getId());
+    }
+    for (KotlinTypeProjectionInfo argument : arguments) {
+      argument.visit(visitor);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
new file mode 100644
index 0000000..b9a86d4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
@@ -0,0 +1,48 @@
+// 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.KmTypeParameter;
+import kotlinx.metadata.KmVariance;
+
+// Provides access to Kotlin information about a type-parameter.
+public class KotlinTypeParameterInfo {
+
+  private final int flags;
+  private final int id;
+  private final String name;
+  private final KmVariance variance;
+
+  private KotlinTypeParameterInfo(int flags, int id, String name, KmVariance variance) {
+    this.flags = flags;
+    this.id = id;
+    this.name = name;
+    this.variance = variance;
+  }
+
+  static KotlinTypeParameterInfo fromKmTypeParameter(KmTypeParameter kmTypeParameter) {
+    return new KotlinTypeParameterInfo(
+        kmTypeParameter.getFlags(),
+        kmTypeParameter.getId(),
+        kmTypeParameter.getName(),
+        kmTypeParameter.getVariance());
+  }
+
+  public int getFlags() {
+    return flags;
+  }
+
+  public int getId() {
+    return id;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public KmVariance getVariance() {
+    return variance;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
index 3782bd8..a16c78e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
@@ -4,7 +4,10 @@
 
 package com.android.tools.r8.kotlin;
 
+import static kotlinx.metadata.FlagsKt.flagsOf;
+
 import kotlinx.metadata.KmTypeProjection;
+import kotlinx.metadata.KmTypeVisitor;
 import kotlinx.metadata.KmVariance;
 
 // Provides access to Kotlin information about the type projection of a type (arguments).
@@ -13,7 +16,7 @@
   final KmVariance variance;
   final KotlinTypeInfo typeInfo;
 
-  private KotlinTypeProjectionInfo(KmVariance variance, KotlinTypeInfo typeInfo) {
+  KotlinTypeProjectionInfo(KmVariance variance, KotlinTypeInfo typeInfo) {
     this.variance = variance;
     this.typeInfo = typeInfo;
   }
@@ -22,4 +25,18 @@
     return new KotlinTypeProjectionInfo(
         kmTypeProjection.getVariance(), KotlinTypeInfo.create(kmTypeProjection.getType()));
   }
+
+  public boolean isStarProjection() {
+    return variance == null && typeInfo == null;
+  }
+
+  public void visit(KmTypeVisitor visitor) {
+    KmTypeVisitor kmTypeVisitor = visitor.visitArgument(flagsOf(), variance);
+    // TODO(b/152886451): Check if this check should be before visitor.visitArgument(...).
+    if (isStarProjection()) {
+      kmTypeVisitor.visitStarProjection();
+    } else {
+      typeInfo.visit(kmTypeVisitor);
+    }
+  }
 }
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 d8d62ae..b2f5a80 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -173,7 +173,7 @@
   }
 
   static boolean isClassNameComparison(InvokeVirtual invoke, DexItemFactory dexItemFactory) {
-    return invoke.getInvokedMethod() == dexItemFactory.stringMethods.equals
+    return invoke.getInvokedMethod() == dexItemFactory.stringMembers.equals
         && (isClassNameValue(invoke.getReceiver(), dexItemFactory)
             || isClassNameValue(invoke.inValues().get(1), dexItemFactory));
   }
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 da62097..f4c32c6 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -132,7 +132,8 @@
     ClassNameMinifier classNameMinifier =
         new ClassNameMinifier(
             appView,
-            new ApplyMappingClassNamingStrategy(appView, mappedNames),
+            new ApplyMappingClassNamingStrategy(
+                appView, mappedNames, seedMapper.getMappedToDescriptorNames()),
             // The package naming strategy will actually not be used since all classes and methods
             // will be output with identity name if not found in mapping. However, there is a check
             // in the ClassNameMinifier that the strategy should produce a "fresh" name so we just
@@ -390,10 +391,13 @@
   static class ApplyMappingClassNamingStrategy extends MinificationClassNamingStrategy {
 
     private final Map<DexType, DexString> mappings;
+    private final Set<String> mappedNames;
 
-    ApplyMappingClassNamingStrategy(AppView<?> appView, Map<DexType, DexString> mappings) {
+    ApplyMappingClassNamingStrategy(
+        AppView<?> appView, Map<DexType, DexString> mappings, Set<String> mappedNames) {
       super(appView);
       this.mappings = mappings;
+      this.mappedNames = mappedNames;
     }
 
     @Override
@@ -404,7 +408,16 @@
         Predicate<DexString> isUsed) {
       assert !mappings.containsKey(type);
       assert appView.rootSet().mayBeMinified(type, appView);
-      return super.next(type, packagePrefix, state, isUsed);
+      return super.next(
+          type,
+          packagePrefix,
+          state,
+          candidate -> {
+            if (mappedNames.contains(candidate.toString())) {
+              return true;
+            }
+            return isUsed.test(candidate);
+          });
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
index d7f36da..be69416 100644
--- a/src/main/java/com/android/tools/r8/naming/SeedMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -20,6 +20,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
@@ -37,6 +38,7 @@
 
   static class Builder extends ProguardMap.Builder {
     final Map<String, ClassNamingForMapApplier.Builder> map = new HashMap<>();
+    final Set<String> mappedToDescriptorNames = new HashSet<>();
     private final Reporter reporter;
 
     private Builder(Reporter reporter) {
@@ -47,9 +49,11 @@
     ClassNamingForMapApplier.Builder classNamingBuilder(
         String renamedName, String originalName, Position position) {
       String originalDescriptor = javaTypeToDescriptor(originalName);
+      String renamedDescriptorName = javaTypeToDescriptor(renamedName);
+      mappedToDescriptorNames.add(renamedDescriptorName);
       ClassNamingForMapApplier.Builder classNamingBuilder =
           ClassNamingForMapApplier.builder(
-              javaTypeToDescriptor(renamedName), originalDescriptor, position, reporter);
+              renamedDescriptorName, originalDescriptor, position, reporter);
       if (map.put(originalDescriptor, classNamingBuilder) != null) {
         reporter.error(ProguardMapError.duplicateSourceClass(originalName, position));
       }
@@ -59,7 +63,7 @@
     @Override
     SeedMapper build() {
       reporter.failIfPendingErrors();
-      return new SeedMapper(ImmutableMap.copyOf(map), reporter);
+      return new SeedMapper(ImmutableMap.copyOf(map), mappedToDescriptorNames, reporter);
     }
   }
 
@@ -82,15 +86,20 @@
   }
 
   private final ImmutableMap<String, ClassNamingForMapApplier> mappings;
+  private final Set<String> mappedToDescriptorNames;
   private final Reporter reporter;
 
-  private SeedMapper(Map<String, ClassNamingForMapApplier.Builder> mappings, Reporter reporter) {
+  private SeedMapper(
+      Map<String, ClassNamingForMapApplier.Builder> mappings,
+      Set<String> mappedToDescriptorNames,
+      Reporter reporter) {
     this.reporter = reporter;
     ImmutableMap.Builder<String, ClassNamingForMapApplier> builder = ImmutableMap.builder();
     for(Map.Entry<String, ClassNamingForMapApplier.Builder> entry : mappings.entrySet()) {
       builder.put(entry.getKey(), entry.getValue().build());
     }
     this.mappings = builder.build();
+    this.mappedToDescriptorNames = mappedToDescriptorNames;
     verifyMappingsAreConflictFree();
   }
 
@@ -140,6 +149,10 @@
     return mappings.keySet();
   }
 
+  public Set<String> getMappedToDescriptorNames() {
+    return mappedToDescriptorNames;
+  }
+
   public ClassNamingForMapApplier getMapping(String key) {
     return mappings.get(key);
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 4816759..a52d044 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -147,7 +147,7 @@
       return true;
     }
 
-    // For private static methods we can just relax the access to private, since
+    // For private static methods we can just relax the access to public, since
     // even though JLS prevents from declaring static method in derived class if
     // an instance method with same signature exists in superclass, JVM actually
     // does not take into account access of the static methods.
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 78136a9..f847e9c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -43,10 +43,10 @@
 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.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.Visibility;
-import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.ImmutableSortedSet.Builder;
@@ -546,6 +546,31 @@
     return definition;
   }
 
+  private int largestInputCfVersion = -1;
+
+  public boolean canUseConstClassInstructions(InternalOptions options) {
+    if (!options.isGeneratingClassFiles()) {
+      return true;
+    }
+    if (largestInputCfVersion == -1) {
+      computeLargestCfVersion();
+    }
+    return options.canUseConstClassInstructions(largestInputCfVersion);
+  }
+
+  private synchronized void computeLargestCfVersion() {
+    if (largestInputCfVersion != -1) {
+      return;
+    }
+    for (DexProgramClass clazz : classes()) {
+      // Skip synthetic classes which may not have a specified version.
+      if (clazz.hasClassFileVersion()) {
+        largestInputCfVersion = Math.max(largestInputCfVersion, clazz.getInitialClassFileVersion());
+      }
+    }
+    assert largestInputCfVersion != -1;
+  }
+
   public boolean isLiveProgramClass(DexProgramClass clazz) {
     return liveTypes.contains(clazz.type);
   }
@@ -1313,19 +1338,8 @@
       DexType type,
       Consumer<DexProgramClass> subTypeConsumer,
       Consumer<LambdaDescriptor> callSiteConsumer) {
-    WorkList<DexType> workList = WorkList.newIdentityWorkList();
-    workList.addIfNotSeen(type);
-    while (workList.hasNext()) {
-      DexType subType = workList.next();
-      DexProgramClass clazz = definitionForProgramType(subType);
-      workList.addIfNotSeen(allImmediateSubtypes(subType));
-      if (clazz == null) {
-        continue;
-      }
-      if (isInstantiatedOrPinned(clazz)) {
-        subTypeConsumer.accept(clazz);
-      }
-    }
+    objectAllocationInfoCollection.forEachInstantiatedSubType(
+        type, subTypeConsumer, callSiteConsumer, this);
   }
 
   public void forEachInstantiatedSubTypeInChain(
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 f636d05..176f908 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -52,6 +52,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.DirectMappedDexApplication.Builder;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection;
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
@@ -1443,6 +1444,15 @@
       recordTypeReference(innerClassAttribute.getInner());
       recordTypeReference(innerClassAttribute.getOuter());
     }
+    EnclosingMethodAttribute enclosingMethodAttribute = holder.getEnclosingMethod();
+    if (enclosingMethodAttribute != null) {
+      DexMethod enclosingMethod = enclosingMethodAttribute.getEnclosingMethod();
+      if (enclosingMethod != null) {
+        recordMethodReference(enclosingMethod);
+      } else {
+        recordTypeReference(enclosingMethodAttribute.getEnclosingClass());
+      }
+    }
 
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Type `%s` has become live.", holder.type);
@@ -2726,18 +2736,11 @@
       DexEncodedMethod context = lambdaClassAndContext.getSecond();
       DexProgramClass programClass = lambdaClass.getOrCreateLambdaClass();
       additions.addInstantiatedClass(programClass, context, lambdaClass.addToMainDexList.get());
-
-      // Mark all methods on the desugared lambda classes as live.
-      for (DexEncodedMethod method : programClass.methods()) {
-        additions.addLiveMethod(new ProgramMethod(programClass, method));
-      }
-
-      // Ensure accessors if needed and mark them live too.
-      DexEncodedMethod accessor = lambdaClass.target.ensureAccessibilityIfNeeded(false);
-      if (accessor != null) {
-        DexProgramClass clazz = getProgramClassOrNull(accessor.holder());
-        additions.addLiveMethod(new ProgramMethod(clazz, accessor));
-      }
+      // Mark the instance constructor targeted and live.
+      DexEncodedMethod constructor = programClass.lookupDirectMethod(lambdaClass.constructor);
+      KeepReason reason = KeepReason.instantiatedIn(context);
+      markMethodAsTargeted(programClass, constructor, reason);
+      markDirectStaticOrConstructorMethodAsLive(programClass, constructor, reason);
     }
 
     // Rewrite all of the invoke-dynamic instructions to lambda class instantiations.
@@ -2774,6 +2777,12 @@
   }
 
   private AppInfoWithLiveness createAppInfo(AppInfoWithSubtyping appInfo) {
+    // Once all tracing is done, we generate accessor methods for lambdas.
+    // These are assumed to be simple forwarding or access flag updates, thus no further tracing
+    // is needed. These cannot be generated as part of lambda synthesis as changing a direct method
+    // to a static method will invalidate the reachable method sets for tracing methods.
+    ensureLambdaAccessibility();
+
     // Compute the set of dead proto types.
     deadProtoTypeCandidates.removeIf(this::isTypeLive);
 
@@ -2866,6 +2875,36 @@
     return appInfoWithLiveness;
   }
 
+  private void ensureLambdaAccessibility() {
+    if (lambdaRewriter == null) {
+      return;
+    }
+    lambdaRewriter
+        .getKnownLambdaClasses()
+        .forEach(
+            (type, lambda) -> {
+              DexProgramClass synthesizedClass = getProgramClassOrNull(type);
+              assert synthesizedClass != null;
+              assert liveTypes.contains(synthesizedClass);
+              if (synthesizedClass == null) {
+                return;
+              }
+              DexMethod method = lambda.descriptor.getMainMethod();
+              if (!liveMethods.contains(synthesizedClass.lookupMethod(method))) {
+                return;
+              }
+              DexEncodedMethod accessor = lambda.target.ensureAccessibilityIfNeeded(false);
+              if (accessor == null) {
+                return;
+              }
+              DexProgramClass accessorClass = getProgramClassOrNull(accessor.holder());
+              assert accessorClass != null;
+              if (accessorClass != null) {
+                liveMethods.add(accessorClass, accessor, graphReporter.fakeReportShouldNotBeUsed());
+              }
+            });
+  }
+
   private boolean verifyReferences(DexApplication app) {
     WorkList<DexClass> worklist = WorkList.newIdentityWorkList();
     for (DexProgramClass clazz : liveTypes.getItems()) {
@@ -3668,12 +3707,16 @@
       if (clazz != null && clazz.isInterface()) {
         // Add this interface to the set of pinned items to ensure that we do not merge the
         // interface into its unique subtype, if any.
+        // TODO(b/145344105): This should be superseded by the unknown interface hierarchy.
         pinnedItems.add(clazz.type);
+        KeepReason reason = KeepReason.reflectiveUseIn(method);
+        markInterfaceAsInstantiated(clazz, graphReporter.registerClass(clazz, reason));
 
         // Also pin all of its virtual methods to ensure that the devirtualizer does not perform
         // illegal rewritings of invoke-interface instructions into invoke-virtual instructions.
         for (DexEncodedMethod virtualMethod : clazz.virtualMethods()) {
           pinnedItems.add(virtualMethod.method);
+          markVirtualMethodAsReachable(virtualMethod.method, true, null, reason);
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index 2276790..a47684f 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -86,14 +86,9 @@
   }
 
   /** Get or open the zip output stream. */
-  private synchronized ZipOutputStream getStream(DiagnosticsHandler handler) {
+  private synchronized ZipOutputStream getStream() throws IOException {
     assert !closed;
-    try {
-      getStreamRaw();
-    } catch (IOException e) {
-      handler.error(new ExceptionDiagnostic(e, origin));
-    }
-    return stream;
+    return getStreamRaw();
   }
 
   private void handleIOException(IOException e, DiagnosticsHandler handler) {
@@ -117,9 +112,9 @@
     }
     ZipEntry entry = new ZipEntry(name);
     entry.setTime(0);
-    ZipOutputStream zip = getStream(handler);
     synchronized (this) {
       try {
+        ZipOutputStream zip = getStream();
         zip.putNextEntry(entry);
         zip.closeEntry();
       } catch (IOException e) {
@@ -150,7 +145,7 @@
 
   private void writeFileNow(String name, ByteDataView content, DiagnosticsHandler handler) {
     try {
-      ZipUtils.writeToZipStream(getStream(handler), name, content, ZipEntry.DEFLATED);
+      ZipUtils.writeToZipStream(getStream(), name, content, ZipEntry.DEFLATED);
     } catch (IOException e) {
       handleIOException(e, handler);
     }
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index 271bd70..21a5123 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command.Builder;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -46,6 +47,7 @@
     boolean isCompatMode = false;
     OutputMode outputMode = OutputMode.DexIndexed;
     Path outputPath = null;
+    Path pgMapOutput = null;
     CompilationMode compilationMode = CompilationMode.RELEASE;
     List<Path> program = new ArrayList<>();
     List<Path> library = new ArrayList<>();
@@ -107,6 +109,11 @@
               config.add(Paths.get(operand));
               break;
             }
+          case "--pg-map-output":
+            {
+              pgMapOutput = Paths.get(operand);
+              break;
+            }
           default:
             throw new IllegalArgumentException("Unimplemented option: " + option);
         }
@@ -114,7 +121,7 @@
         program.add(Paths.get(option));
       }
     }
-    R8.run(
+    Builder builder =
         new CompatProguardCommandBuilder(isCompatMode)
             .addProgramFiles(program)
             .addLibraryFiles(library)
@@ -122,7 +129,10 @@
             .addProguardConfigurationFiles(config)
             .setOutput(outputPath, outputMode)
             .setMode(compilationMode)
-            .setMinApiLevel(minApi)
-            .build());
+            .setMinApiLevel(minApi);
+    if (pgMapOutput != null) {
+      builder.setProguardMapOutputPath(pgMapOutput);
+    }
+    R8.run(builder.build());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 6aca533..de25a3b 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -17,8 +17,6 @@
 import java.io.File;
 import java.nio.file.Path;
 import java.util.Map;
-import kotlinx.metadata.KmType;
-import kotlinx.metadata.KmTypeVisitor;
 
 public class DescriptorUtils {
 
@@ -379,36 +377,6 @@
   }
 
   /**
-   * Get a fully qualified name from a classifier in Kotlin metadata.
-   * @param kmType where classifier contains Kotlin internal name, like "org/foo/bar/Baz.Nested"
-   * @return a class descriptor like "Lorg/foo/bar/Baz$Nested;"
-   */
-  public static String getDescriptorFromKmType(KmType kmType) {
-    if (kmType == null) {
-      return null;
-    }
-    Box<String> descriptor = new Box<>(null);
-    kmType.accept(new KmTypeVisitor() {
-      @Override
-      public void visitClass(String name) {
-        // TODO(b/151195430): Remove this if metadata lib is resilient to namespace relocation.
-        //  See b/70169921#comment25 for more details.
-        assert descriptor.get() == null : descriptor.get();
-        descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName(name)));
-      }
-
-      @Override
-      public void visitTypeAlias(String name) {
-        // TODO(b/151195430): Remove this if metadata lib is resilient to namespace relocation.
-        //  See b/70169921#comment25 for more details.
-        assert descriptor.get() == null : descriptor.get();
-        descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName(name)));
-      }
-    });
-    return descriptor.get();
-  }
-
-  /**
    * Get unqualified class name from its binary name.
    *
    * @param classBinaryName a class binary name i.e. "java/lang/Object" or "a/b/C$Inner"
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 ad290b6..51c6934 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -773,7 +773,7 @@
 
   private String messageWarningMissingNestHost(DexClass compiledClass) {
     return messageErrorMissingNestHost(compiledClass)
-        + "Class"
+        + "Class "
         + compiledClass.type.getName()
         + " is considered as not being part of any nest.";
   }
@@ -1202,6 +1202,16 @@
     return minApiLevel >= level.getLevel();
   }
 
+  public boolean canUseConstClassInstructions(int cfVersion) {
+    assert isGeneratingClassFiles();
+    return cfVersion >= requiredCfVersionForConstClassInstructions();
+  }
+
+  public int requiredCfVersionForConstClassInstructions() {
+    assert isGeneratingClassFiles();
+    return Opcodes.V1_5;
+  }
+
   public boolean canUseInvokePolymorphicOnVarHandle() {
     return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.P);
   }
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
index 4a94ac1..42cf72a 100644
--- a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -98,8 +98,7 @@
                 fileNotNamedKt -> {
                   try {
                     // The Kotlin compiler does not require particular naming of files except for
-                    // the extension,
-                    // so just create a file called source.kt in a new directory.
+                    // the extension, so just create a file called source.kt in a new directory.
                     Path fileNamedKt = temp.newFolder().toPath().resolve("source.kt");
                     Files.copy(fileNotNamedKt, fileNamedKt);
                     return fileNamedKt;
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index b8256fe..a642002 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -102,7 +102,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 116, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 115, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -142,7 +142,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 116, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 115, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
diff --git a/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
new file mode 100644
index 0000000..a083cdd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
@@ -0,0 +1,133 @@
+// 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.cf;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+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 GetClassLdcClassTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines(Runner.class.getName());
+
+  private final TestParameters parameters;
+  private final int version;
+
+  @Parameterized.Parameters(name = "{0}, cf:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        new Integer[] {Opcodes.V1_4, Opcodes.V1_5});
+  }
+
+  public GetClassLdcClassTest(TestParameters parameters, int version) {
+    this.parameters = parameters;
+    this.version = version;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    // Check the program works with the code as-is and the version downgraded.
+    testForRuntime(parameters)
+        .addProgramClassFileData(getDowngradedClass(Runner.class))
+        .addProgramClassFileData(getDowngradedClass(TestClass.class))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            inspector -> {
+              if (parameters.isCfRuntime()) {
+                checkVersion(inspector, TestClass.class, version);
+                checkVersion(inspector, Runner.class, version);
+              }
+            });
+  }
+
+  @Test
+  public void testNoVersionUpgrade() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getDowngradedClass(Runner.class))
+        .addProgramClassFileData(getDowngradedClass(TestClass.class))
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(Runner.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            inspector -> {
+              if (parameters.isCfRuntime()) {
+                checkVersion(inspector, TestClass.class, version);
+                checkVersion(inspector, Runner.class, version);
+              }
+            });
+  }
+
+  @Test
+  public void testWithVersionUpgrade() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getDowngradedClass(Runner.class))
+        // Here the main class is not downgraded, thus the output may upgrade to that version.
+        .addProgramClasses(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(Runner.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            inspector -> {
+              if (parameters.isCfRuntime()) {
+                // We are assuming the runtimes we are testing are post CF SE 1.4 (version 48).
+                int cfVersionForRuntime = getVersion(inspector, TestClass.class);
+                assertNotEquals(Opcodes.V1_4, cfVersionForRuntime);
+                // Check that the downgraded class has been bumped to at least SE 1.5 (version 49).
+                int cfVersionAfterUpgrade = getVersion(inspector, Runner.class);
+                assertTrue(cfVersionAfterUpgrade >= Opcodes.V1_5);
+              }
+              // Check that the method uses a const class instruction.
+              assertTrue(
+                  inspector
+                      .clazz(Runner.class)
+                      .uniqueMethodWithName("run")
+                      .streamInstructions()
+                      .anyMatch(i -> i.isConstClass(Runner.class.getTypeName())));
+            });
+  }
+
+  private static int getVersion(CodeInspector inspector, Class<?> clazz) {
+    return inspector.clazz(clazz).getDexClass().asProgramClass().getInitialClassFileVersion();
+  }
+
+  private static void checkVersion(CodeInspector inspector, Class<?> clazz, int version) {
+    assertEquals(version, getVersion(inspector, clazz));
+  }
+
+  private byte[] getDowngradedClass(Class<?> clazz) throws IOException {
+    return transformer(clazz).setVersion(version).transform();
+  }
+
+  static class Runner {
+
+    public void run() {
+      System.out.println(getClass().getName());
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new Runner().run();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/InterfaceWithProxyTest.java b/src/test/java/com/android/tools/r8/classmerging/InterfaceWithProxyTest.java
index 03f5803..ba16ddc 100644
--- a/src/test/java/com/android/tools/r8/classmerging/InterfaceWithProxyTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/InterfaceWithProxyTest.java
@@ -18,6 +18,8 @@
 @RunWith(Parameterized.class)
 public class InterfaceWithProxyTest extends TestBase {
 
+  private static final String EXPECTED = StringUtils.lines("Hello world!");
+
   private final TestParameters parameters;
 
   @Parameterized.Parameters(name = "{0}")
@@ -30,8 +32,15 @@
   }
 
   @Test
-  public void test() throws Exception {
-    String expectedOutput = StringUtils.lines("Hello world!");
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(InterfaceWithProxyTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(InterfaceWithProxyTest.class)
         .addKeepMainRule(TestClass.class)
@@ -39,7 +48,7 @@
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(expectedOutput);
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
index e209e44..7a97972 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
@@ -6,19 +6,21 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.Disassemble;
 import com.android.tools.r8.Disassemble.DisassembleCommand;
 import com.android.tools.r8.JvmTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.debug.DebugTestBase;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Files;
@@ -28,57 +30,85 @@
 import java.util.Collections;
 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 DefaultLambdaWithSelfReferenceTestRunner extends DebugTestBase {
 
-  final Class<?> CLASS = DefaultLambdaWithSelfReferenceTest.class;
-  final String EXPECTED = StringUtils.lines("stateful(stateless)");
+  private static final Class<?> CLASS = DefaultLambdaWithSelfReferenceTest.class;
+  private static final String EXPECTED = StringUtils.lines("stateful(stateless)");
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DefaultLambdaWithSelfReferenceTestRunner(TestParameters parameters) {
+    this.parameters = parameters;
+  }
 
   private void runDebugger(DebugTestConfig config) throws Throwable {
     MethodReference main = Reference.methodFromMethod(CLASS.getMethod("main", String[].class));
-    Command checkThis = conditional((state) ->
-        state.isCfRuntime()
-            ? Collections.singletonList(checkLocal("this"))
-            : ImmutableList.of(
-                checkNoLocal("this"),
-                checkLocal("_this")));
+    Command checkThisLambda =
+        conditional(
+            (state) ->
+                parameters.isCfRuntime()
+                    ? Collections.singletonList(checkLocal("this"))
+                    : ImmutableList.of(checkNoLocal("this"), checkLocal("_this")));
 
-    runDebugTest(config, CLASS,
+    Command checkThisDefaultMethod =
+        conditional(
+            (state) ->
+                parameters.canUseDefaultAndStaticInterfaceMethods()
+                    ? Collections.singletonList(checkLocal("this"))
+                    : ImmutableList.of(checkNoLocal("this"), checkLocal("_this")));
+
+    runDebugTest(
+        config,
+        CLASS,
         breakpoint(main, 26),
         run(),
         checkLine(26),
         stepInto(INTELLIJ_FILTER),
         checkLine(16),
         // When desugaring, the InterfaceProcessor makes this static on the companion class.
-        checkThis,
+        checkThisDefaultMethod,
         breakpoint(main, 27),
         run(),
         checkLine(27),
         stepInto(INTELLIJ_FILTER),
         checkLine(17),
         // When desugaring, the LambdaClass will change this to a static (later moved to companion).
-        checkThis,
+        checkThisLambda,
         run());
   }
 
   @Test
   public void testJvm() throws Throwable {
+    assumeTrue(parameters.isCfRuntime());
     JvmTestBuilder builder = testForJvm().addTestClasspath();
-    builder.run(CLASS).assertSuccessWithOutput(EXPECTED);
+    builder.run(parameters.getRuntime(), CLASS).assertSuccessWithOutput(EXPECTED);
     runDebugger(builder.debugConfig());
   }
 
   @Test
-  public void testR8Cf() throws Throwable {
-    R8TestCompileResult compileResult = testForR8(Backend.CF)
-        .addProgramClassesAndInnerClasses(CLASS)
-        .noMinification()
-        .noTreeShaking()
-        .debug()
-        .compile();
+  public void testR8() throws Throwable {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClassesAndInnerClasses(CLASS)
+            .setMinApi(parameters.getApiLevel())
+            .noMinification()
+            .noTreeShaking()
+            .addKeepAllAttributes()
+            .debug()
+            .compile()
+            .assertNoMessages();
     compileResult
-        // TODO(b/123506120): Add .assertNoMessages()
-        .run(CLASS)
+        .run(parameters.getRuntime(), CLASS)
         .assertSuccessWithOutput(EXPECTED)
         .inspect(inspector -> assertThat(inspector.clazz(CLASS), isPresent()));
     runDebugger(compileResult.debugConfig());
@@ -86,14 +116,15 @@
 
   @Test
   public void testD8() throws Throwable {
+    assumeTrue(parameters.isDexRuntime());
     Path out1 = temp.newFolder().toPath().resolve("out1.zip");
     testForD8()
         .addProgramClassesAndInnerClasses(CLASS)
-        .setMinApi(AndroidApiLevel.K)
+        .setMinApi(parameters.getApiLevel())
         .compile()
-        // TODO(b/123506120): Add .assertNoMessages()
+        .assertNoMessages()
         .writeToZip(out1)
-        .run(CLASS)
+        .run(parameters.getRuntime(), CLASS)
         .assertSuccessWithOutput(EXPECTED);
 
     Path outPerClassDir = temp.newFolder().toPath();
@@ -109,9 +140,9 @@
           .addProgramClasses(CLASS)
           .addClasspathFiles(ToolHelper.getClassPathForTests())
           .setIntermediate(true)
-          .setMinApi(AndroidApiLevel.K)
+          .setMinApi(parameters.getApiLevel())
           .compile()
-          // TODO(b/123506120): Add .assertNoMessages()
+          .assertNoMessages()
           .writeToZip(mainOut);
     }
     for (Path innerClass : innerClasses) {
@@ -121,21 +152,20 @@
           .addProgramFiles(innerClass)
           .addClasspathFiles(ToolHelper.getClassPathForTests())
           .setIntermediate(true)
-          .setMinApi(AndroidApiLevel.K)
+          .setMinApi(parameters.getApiLevel())
           .compile()
-          // TODO(b/123506120): Add .assertNoMessages()
+          .assertNoMessages()
           .writeToZip(out);
     }
 
     Path out2 = temp.newFolder().toPath().resolve("out2.zip");
-    D8TestCompileResult compiledResult = testForD8()
-        .addProgramFiles(outs)
-        .compile();
+    D8TestCompileResult compiledResult =
+        testForD8().addProgramFiles(outs).setMinApi(parameters.getApiLevel()).compile();
 
     compiledResult
-        // TODO(b/123506120): Add .assertNoMessages()
+        .assertNoMessages()
         .writeToZip(out2)
-        .run(CLASS)
+        .run(parameters.getRuntime(), CLASS)
         .assertSuccessWithOutput(EXPECTED);
 
     runDebugger(compiledResult.debugConfig());
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultMethodWithAccessTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultMethodWithAccessTest.java
new file mode 100644
index 0000000..0c778d0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultMethodWithAccessTest.java
@@ -0,0 +1,96 @@
+// 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;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.io.IOException;
+import java.util.List;
+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;
+import org.objectweb.asm.Opcodes;
+// This is a reproduction of b/153042496 in a java-only setting.
+
+@RunWith(Parameterized.class)
+public class DefaultMethodWithAccessTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean implementI0I1;
+
+  @Parameters(name = "{0}, implementI0I1: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public DefaultMethodWithAccessTest(TestParameters parameters, boolean implementI0I1) {
+    this.parameters = parameters;
+    this.implementI0I1 = implementI0I1;
+  }
+
+  @Test
+  public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+    testForRuntime(parameters)
+        .addProgramClasses(I0.class, I1.class, Main.class, Impl.class)
+        .addProgramClassFileData(transformI2AccessToInvokeSpecial())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  private byte[] transformI2AccessToInvokeSpecial() throws IOException {
+    ClassFileTransformer classFileTransformer =
+        transformer(I2.class)
+            .transformMethodInsnInMethod(
+                "access",
+                (opcode, owner, name, descriptor, isInterface, continuation) -> {
+                  continuation.apply(
+                      name.equals("print") ? Opcodes.INVOKESPECIAL : opcode,
+                      owner,
+                      name,
+                      descriptor,
+                      isInterface);
+                });
+    if (implementI0I1) {
+      classFileTransformer.setImplements(I0.class, I1.class);
+    }
+    return classFileTransformer.transform();
+  }
+
+  public interface I0 {
+    void print();
+  }
+
+  public interface I1 {
+    default void print() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public interface I2 extends /* I0, */ I1 {
+
+    static void access(I2 i2) {
+      /* invoke-special */ i2.print();
+    }
+  }
+
+  public static class Impl implements I2 {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      testPrint(new Impl());
+    }
+
+    public static void testPrint(I2 i) {
+      I2.access(i);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/PrivateMethodsInInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/PrivateMethodsInInterfaceTest.java
new file mode 100644
index 0000000..564f831
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/PrivateMethodsInInterfaceTest.java
@@ -0,0 +1,102 @@
+// 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;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+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;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class PrivateMethodsInInterfaceTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public PrivateMethodsInInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime()
+      throws NoSuchMethodException, IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addProgramClasses(SubI.class, Impl.class, Main.class)
+        .addProgramClassFileData(transformIToPrivate())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!", "Hello World!", "Hello World!");
+  }
+
+  private byte[] transformIToPrivate() throws NoSuchMethodException, IOException {
+    return transformer(I.class)
+        .setPrivate(I.class.getDeclaredMethod("bar"))
+        .setPrivate(I.class.getDeclaredMethod("baz", I.class))
+        .transformMethodInsnInMethod(
+            "foo",
+            ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+              continuation.apply(
+                  name.equals("bar") ? Opcodes.INVOKESPECIAL : opcode,
+                  owner,
+                  name,
+                  descriptor,
+                  isInterface);
+            }))
+        .transformMethodInsnInMethod(
+            "baz",
+            ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+              continuation.apply(
+                  name.equals("bar") ? Opcodes.INVOKESPECIAL : opcode,
+                  owner,
+                  name,
+                  descriptor,
+                  isInterface);
+            }))
+        .transform();
+  }
+
+  public interface I {
+
+    default void foo() {
+      bar();
+      I.qux(this);
+    }
+
+    /* private */ default void bar() {
+      System.out.println("Hello World!");
+    }
+
+    /* private */ static void baz(I i) {
+      i.bar();
+    }
+
+    static void qux(I i) {
+      baz(i);
+    }
+  }
+
+  public interface SubI extends I {}
+
+  public static class Impl implements SubI {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Impl impl = new Impl();
+      impl.foo();
+      I.qux(impl);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportTest.java
index 13a4564..e92fcc7 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportTest.java
@@ -21,8 +21,8 @@
 
   public ByteBackportTest(TestParameters parameters) {
     super(parameters, Byte.class, Main.class);
-    registerTarget(AndroidApiLevel.O, 16);
-    registerTarget(AndroidApiLevel.N, 8);
+    registerTarget(AndroidApiLevel.O, 17);
+    registerTarget(AndroidApiLevel.N, 9);
     registerTarget(AndroidApiLevel.K, 7);
   }
 
@@ -34,10 +34,13 @@
       testToUnsignedLong();
     }
 
+    @SuppressWarnings("ResultOfMethodCallIgnored")
     private static void testHashCode() {
       for (int i = Byte.MIN_VALUE; i < Byte.MAX_VALUE; i++) {
         assertEquals(i, Byte.hashCode((byte) i));
       }
+      // Test unused invoke.
+      Byte.hashCode((byte) 1);
     }
 
     private static void testCompare() {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java
new file mode 100644
index 0000000..5f05a31
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java
@@ -0,0 +1,77 @@
+// 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.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 EnumUnboxingSideEffectClInitTest extends EnumUnboxingTestBase {
+  private static final Class<?> ENUM_CLASS = MyEnum.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 EnumUnboxingSideEffectClInitTest(
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<?> classToTest = MainEnum.class;
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EnumUnboxingSideEffectClInitTest.class)
+        .addKeepMainRule(classToTest)
+        .addKeepRules(enumKeepRules.getKeepRule())
+        .enableNeverClassInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .allowDiagnosticInfoMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectDiagnosticMessages(
+            m -> {
+              // The snap keep rule forces to keep the static MainEnum#e field, so the enum
+              // cannot be unboxed anymore.
+              if (enumKeepRules.toString().equals("snap")) {
+                assertEnumIsBoxed(ENUM_CLASS, classToTest.getSimpleName(), m);
+              } else {
+                assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m);
+              }
+            })
+        .run(parameters.getRuntime(), classToTest)
+        .assertSuccessWithOutputLines("0");
+  }
+
+  @NeverClassInline
+  enum MyEnum {
+    A,
+    B
+  }
+
+  @NeverClassInline
+  enum MainEnum {
+    INSTANCE;
+    // The clinit of this enum needs to be reprocessed by the enum unboxer to rewrite MyEnum.a
+    // and the static put instruction to the new field.
+    static MyEnum e = System.currentTimeMillis() > 0 ? MyEnum.A : MyEnum.B;
+
+    public static void main(String[] args) {
+      System.out.println(e.ordinal());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java
new file mode 100644
index 0000000..e2dcc4c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java
@@ -0,0 +1,63 @@
+// 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.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ValueOfEnumUnboxingFailureTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final KeepRule enumKeepRules;
+
+  @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public ValueOfEnumUnboxingFailureTest(
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<?> success = Main.class;
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ValueOfEnumUnboxingFailureTest.class)
+        .addKeepMainRule(success)
+        .enableNeverClassInliningAnnotations()
+        .addKeepRules(enumKeepRules.getKeepRule())
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .allowDiagnosticInfoMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectDiagnosticMessages(
+            m -> assertEnumIsBoxed(success.getDeclaredClasses()[0], success.getSimpleName(), m))
+        .run(parameters.getRuntime(), success)
+        .assertSuccessWithOutput("VALUE1");
+  }
+
+  static class Main {
+
+    @NeverClassInline
+    enum Enum {
+      VALUE1,
+      VALUE2
+    }
+
+    public static void main(String[] args) {
+      System.out.print(java.lang.Enum.valueOf(Enum.class, "VALUE1"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
index b4e1ef6..37f31c9 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
@@ -76,7 +76,7 @@
       System.out.println(Enum.valueOf(EnumValueOf.MyEnum.class, "B").ordinal());
       System.out.println(1);
       try {
-        Enum.valueOf(EnumValueOf.MyEnum.class, "C");
+        iae();
       } catch (IllegalArgumentException argException) {
         System.out.println(argException.getMessage());
         System.out.println(
@@ -84,11 +84,20 @@
                 + " com.android.tools.r8.enumunboxing.ValueOfEnumUnboxingTest.EnumValueOf.MyEnum.C");
       }
       try {
-        Enum.valueOf(EnumValueOf.MyEnum.class, null);
+        npe();
       } catch (NullPointerException npe) {
         System.out.println(npe.getMessage());
         System.out.println("Name is null");
       }
     }
+
+    @SuppressWarnings("ConstantConditions")
+    private static void npe() {
+      Enum.valueOf(MyEnum.class, null);
+    }
+
+    private static void iae() {
+      Enum.valueOf(MyEnum.class, "C");
+    }
   }
 }
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 4d39642..088b075 100644
--- a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
@@ -22,6 +22,7 @@
 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.GenericSignature.WildcardIndicator;
 import com.android.tools.r8.graph.GenericSignatureTestClassA.I;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.AndroidApp;
@@ -31,9 +32,9 @@
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.List;
+import java.util.function.Consumer;
 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;
@@ -50,7 +51,6 @@
   public GenericSignatureTest(TestParameters parameters) {}
 
   @Test
-  @Ignore("b/152709234")
   public void test() throws Exception {
     AndroidApp app =
         testForD8()
@@ -240,6 +240,9 @@
     assertEquals(1, methodTypeSignature.typeSignatures.size());
     parameterSignature = methodTypeSignature.getParameterTypeSignature(0);
     check_supplier(factory, a, y, zz, parameterSignature);
+
+    // check_A_Y_foo for star, negative and positive wildcards
+    check_A_Y_foo_bar_baz(y, appView);
   }
 
   private void check_A_Y(ClassSubject a, ClassSubject y, ClassTypeSignature signature) {
@@ -270,6 +273,47 @@
     assertEquals("TT", typeArgument.asTypeVariableSignature().typeVariable);
   }
 
+  private void check_A_Y_foo_bar_baz(ClassSubject y, AppView<AppInfoWithLiveness> appView) {
+    checkMethodWildCard(y.uniqueMethodWithName("foo"), appView, WildcardIndicator.POSITIVE);
+    checkMethodWildCard(y.uniqueMethodWithName("bar"), appView, WildcardIndicator.NEGATIVE);
+    // Check for star
+    checkFieldTypeSignature(
+        y.uniqueMethodWithName("baz"),
+        appView,
+        typeSignature -> {
+          assertTrue(typeSignature.isStar());
+        });
+  }
+
+  private void checkMethodWildCard(
+      MethodSubject methodSubject,
+      AppView<AppInfoWithLiveness> appView,
+      WildcardIndicator indicator) {
+    checkFieldTypeSignature(
+        methodSubject,
+        appView,
+        typeSignature -> {
+          assertTrue(typeSignature.isTypeVariableSignature());
+          assertEquals(indicator, typeSignature.getWildcardIndicator());
+        });
+  }
+
+  private void checkFieldTypeSignature(
+      MethodSubject methodSubject,
+      AppView<AppInfoWithLiveness> appView,
+      Consumer<FieldTypeSignature> fieldTypeConsumer) {
+    MethodTypeSignature methodTypeSignature =
+        Parser.toMethodTypeSignature(methodSubject.getMethod(), appView);
+    TypeSignature typeSignature = methodTypeSignature.returnType.typeSignature;
+    FieldTypeSignature fieldTypeSignature = typeSignature.asFieldTypeSignature();
+    assertTrue(fieldTypeSignature.isClassTypeSignature());
+    ClassTypeSignature classTypeSignature = fieldTypeSignature.asClassTypeSignature();
+    assertFalse(classTypeSignature.isArgument());
+    assertEquals(1, classTypeSignature.typeArguments.size());
+    FieldTypeSignature typeArgument = classTypeSignature.typeArguments.get(0);
+    fieldTypeConsumer.accept(typeArgument);
+  }
+
   private void check_supplier(
       DexItemFactory factory,
       ClassSubject a,
@@ -308,7 +352,7 @@
     class ZZ<TT> extends YY {
       public YY yy;
 
-      <R extends I> YY newYY(GenericSignatureTestClassB... bs) {
+      <R extends Y & I> YY newYY(GenericSignatureTestClassB... bs) {
         return new YY();
       }
 
@@ -330,6 +374,18 @@
     ZZ<T> zz() {
       return new ZZ<T>();
     }
+
+    List<? extends T> foo() {
+      return null;
+    }
+
+    List<? super T> bar() {
+      return null;
+    }
+
+    List<?> baz() {
+      return null;
+    }
   }
 
   class Z extends Y {}
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java b/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
index 971818b..9644e0d 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
@@ -123,7 +123,7 @@
       Instruction instruction, DexItemFactory dexItemFactory) {
     // Intentionally using toSourceString() because `instruction.getInvokedMethod()` belongs to
     // another factory than the given `dexItemFactory`.
-    String signature = dexItemFactory.stringMethods.hashCode.toSourceString();
+    String signature = dexItemFactory.stringMembers.hashCode.toSourceString();
     return instruction.isInvokeVirtual()
         && instruction.asInvokeVirtual().getInvokedMethod().toSourceString().equals(signature);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithLambdaTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithLambdaTargetTest.java
new file mode 100644
index 0000000..c04f9b6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithLambdaTargetTest.java
@@ -0,0 +1,69 @@
+// 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.callsites;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CallSiteOptimizationWithLambdaTargetTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public CallSiteOptimizationWithLambdaTargetTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(CallSiteOptimizationWithLambdaTargetTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("true", "false");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new A().m(new Object());
+      get().m(null);
+    }
+
+    static I get() {
+      return System.currentTimeMillis() >= 0 ? new A() : System.out::println;
+    }
+  }
+
+  interface I {
+
+    void m(Object o);
+  }
+
+  @NeverClassInline
+  static class A implements I {
+
+    @NeverInline
+    @Override
+    public void m(Object o) {
+      System.out.println(o != null);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java
index 1e323fb..589fa3f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java
@@ -68,8 +68,7 @@
     MethodSubject mainMethodSubject = classSubject.mainMethod();
     assertThat(mainMethodSubject, isPresent());
     assertEquals(
-        // No canonicalization when generating class files.
-        parameters.isCfRuntime() ? 3 : 1,
+        1,
         mainMethodSubject
             .streamInstructions()
             .filter(InstructionSubject::isStaticGet)
@@ -77,7 +76,7 @@
             .filter(enumFieldSubject.getField().field::equals)
             .count());
     assertEquals(
-        3,
+        1,
         mainMethodSubject
             .streamInstructions()
             .filter(InstructionSubject::isStaticGet)
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
index fe3d79a..2b910f5 100644
--- 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
@@ -65,9 +65,8 @@
 
     MethodSubject mMethodSubject = aClassSubject.uniqueMethodWithName("m");
     assertThat(mMethodSubject, isPresent());
-    // TODO(b/152196923): Should be 0.
     assertEquals(
-        2,
+        1,
         countStaticGetInstructions(
             mMethodSubject.asFoundMethodSubject(), fFieldSubject.asFoundFieldSubject()));
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index 5ab6053..b5ad06f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -12,6 +12,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -50,11 +51,14 @@
 import com.android.tools.r8.ir.optimize.staticizer.trivial.TrivialTestClass;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Streams;
+import java.io.IOException;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -64,6 +68,37 @@
 public class ClassStaticizerTest extends TestBase {
   private final TestParameters parameters;
 
+  private static final String EXPECTED =
+      StringUtils.lines(
+          "Simple::bar(Simple::foo())",
+          "Simple::bar(0)",
+          "SimpleWithPhi$Companion::bar(SimpleWithPhi$Companion::foo()) true",
+          "SimpleWithSideEffects::<clinit>()",
+          "SimpleWithSideEffects::bar(SimpleWithSideEffects::foo())",
+          "SimpleWithSideEffects::bar(1)",
+          "SimpleWithParams::bar(SimpleWithParams::foo())",
+          "SimpleWithParams::bar(2)",
+          "SimpleWithGetter::bar(SimpleWithGetter::foo())",
+          "SimpleWithGetter::bar(3)",
+          "Simple::bar(Simple::foo())",
+          "Simple::bar(4)",
+          "Simple::bar(Simple::foo())",
+          "Simple::bar(5)");
+
+  private static final Class<?> main = TrivialTestClass.class;
+  private static final Class<?>[] classes = {
+    NeverInline.class,
+    TrivialTestClass.class,
+    Simple.class,
+    SimpleWithGetter.class,
+    SimpleWithLazyInit.class,
+    SimpleWithParams.class,
+    SimpleWithPhi.class,
+    SimpleWithPhi.Companion.class,
+    SimpleWithSideEffects.class,
+    SimpleWithThrowingGetter.class
+  };
+
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     // TODO(b/112831361): support for class staticizer in CF backend.
@@ -75,21 +110,20 @@
   }
 
   @Test
+  public void testWithoutAccessModification()
+      throws ExecutionException, CompilationFailedException, IOException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(classes)
+        .addKeepMainRule(main)
+        .addKeepAttributes("InnerClasses", "EnclosingMethod")
+        .addOptionsModification(this::configure)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), main)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testTrivial() throws Exception {
-    Class<?> main = TrivialTestClass.class;
-    Class<?>[] classes = {
-        NeverInline.class,
-        TrivialTestClass.class,
-        Simple.class,
-        SimpleWithGetter.class,
-        SimpleWithLazyInit.class,
-        SimpleWithParams.class,
-        SimpleWithPhi.class,
-        SimpleWithPhi.Companion.class,
-        SimpleWithSideEffects.class,
-        SimpleWithThrowingGetter.class
-    };
-    String javaOutput = runOnJava(main);
     TestRunResult result =
         testForR8(parameters.getBackend())
             .addProgramClasses(classes)
@@ -101,7 +135,7 @@
             .allowAccessModification()
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), main)
-            .assertSuccessWithOutput(javaOutput);
+            .assertSuccessWithOutput(EXPECTED);
 
     CodeInspector inspector = result.inspector();
     ClassSubject clazz = inspector.clazz(main);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
index 55487ac..6bc91b4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
@@ -15,11 +15,11 @@
 
   @NeverInline
   String foo() {
-    return bar("Simple::foo()");
+    return bar("SimpleWithGetter::foo()");
   }
 
   @NeverInline
   String bar(String other) {
-    return "Simple::bar(" + other + ")";
+    return "SimpleWithGetter::bar(" + other + ")";
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
index 1720b55..5799823 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -25,4 +25,5 @@
   static final String KT_UNIT = "Lkotlin/Unit;";
 
   static final String KT_FUNCTION1 = "Lkotlin/Function1;";
+  static final String KT_COMPARABLE = "Lkotlin/Comparable;";
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
index 734ce50..b1af80f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
@@ -4,22 +4,29 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isDexClass;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertTrue;
 import static org.hamcrest.CoreMatchers.not;
 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.kotlin.Kotlin.ClassClassifiers;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmClassifierSubject;
 import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeAliasSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeProjectionSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -47,6 +54,7 @@
           "true",
           "42",
           "1",
+          "ClassWithCompanion::fooOnCompanion",
           "42",
           "42",
           "1",
@@ -107,33 +115,49 @@
             .addProgramFiles(typeAliasLibJarMap.get(targetVersion))
             // Keep non-private members of Impl
             .addKeepRules("-keep class **.Impl { !private *; }")
-            // Keep Itf, but allow minification.
-            .addKeepRules("-keep,allowobfuscation class **.Itf")
+            // Keep but allow obfuscation of types.
+            .addKeepRules("-keep,allowobfuscation class " + PKG + ".typealias_lib.** { *; }")
+            .addKeepRules("-keepclassmembernames class " + PKG + ".typealias_lib.**" + " { *; }")
+            // Keep the Companion class for ClassWithCompanionC.
+            .addKeepRules("-keep class **.ClassWithCompanion$Companion { *; }")
+            // Keep the inner class, otherwise it cannot be constructed.
+            .addKeepRules("-keep class **.*Inner { *; }")
             // Keep LibKt that contains the type-aliases and utils.
-            .addKeepRules("-keep class **.LibKt { *; }")
-            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addKeepRules("-keep class **.LibKt, **.Lib_extKt { *; }")
+            // Keep the library test methods
+            .addKeepRules("-keep class " + PKG + ".typealias_lib.*Tester { *; }")
+            .addKeepRules("-keep class " + PKG + ".typealias_lib.*Tester$Companion { *; }")
+            .addKeepAttributes(
+                ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+                ProguardKeepAttributes.SIGNATURE,
+                ProguardKeepAttributes.INNER_CLASSES,
+                ProguardKeepAttributes.ENCLOSING_METHOD)
             .compile()
             .inspect(this::inspect)
             .writeToZip();
 
-    ProcessResult kotlinTestCompileResult =
+    Path appJar =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/typealias_app", "main"))
-            .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/151194785): update to just .compile() once fixed.
-            .compileRaw();
-    // TODO(b/151194785): should be able to compile!
-    assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    assertThat(
-        kotlinTestCompileResult.stderr,
-        containsString(
-            "type mismatch: inferred type is ProgramClass but API /* = Itf */ was expected"));
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(appJar)
+        .run(parameters.getRuntime(), PKG + ".typealias_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspect(CodeInspector inspector) {
-    String itfClassName = PKG + ".typealias_lib.Itf";
-    String libKtClassName = PKG + ".typealias_lib.LibKt";
+    inspectLib(inspector);
+    inspectLibExt(inspector);
+  }
+
+  private void inspectLib(CodeInspector inspector) {
+    String packageName = PKG + ".typealias_lib";
+    String itfClassName = packageName + ".Itf";
+    String libKtClassName = packageName + ".LibKt";
 
     ClassSubject itf = inspector.clazz(itfClassName);
     assertThat(itf, isRenamed());
@@ -149,6 +173,115 @@
     // API entry is kept, hence the presence of Metadata.
     KmPackageSubject kmPackage = libKt.getKmPackage();
     assertThat(kmPackage, isPresent());
-    // TODO(b/151194785): need further inspection: many kinds of type appearances in typealias.
+
+    String arrayDescriptor =
+        DescriptorUtils.getDescriptorFromKotlinClassifier(ClassClassifiers.arrayBinaryName);
+
+    // Check that typealias myAliasedArray<T> = Array<T> exists.
+    KmTypeAliasSubject myAliasedArray = kmPackage.kmTypeAliasWithUniqueName("myAliasedArray");
+    assertThat(myAliasedArray, isPresent());
+    assertEquals(arrayDescriptor, myAliasedArray.expandedType().descriptor());
+
+    // Check that typealias API = Itf has been rewritten correctly.
+    KmTypeAliasSubject api = kmPackage.kmTypeAliasWithUniqueName("API");
+    assertThat(api, isPresent());
+    assertThat(api.expandedType(), isDexClass(itf.getDexClass()));
+    assertThat(api.underlyingType(), isDexClass(itf.getDexClass()));
+
+    // Check that the type-alias APIs exist and that the expanded type is renamed.
+    KmTypeAliasSubject apIs = kmPackage.kmTypeAliasWithUniqueName("APIs");
+    assertThat(apIs, isPresent());
+    assertEquals(arrayDescriptor, apIs.expandedType().descriptor());
+    assertEquals(1, apIs.expandedType().typeArguments().size());
+    KmTypeProjectionSubject expandedArgument = apIs.expandedType().typeArguments().get(0);
+    assertThat(expandedArgument.type(), isDexClass(itf.getDexClass()));
+
+    assertEquals(myAliasedArray.descriptor(packageName), apIs.underlyingType().descriptor());
+    assertEquals(1, apIs.underlyingType().typeArguments().size());
+    KmTypeProjectionSubject underlyingArgument = apIs.underlyingType().typeArguments().get(0);
+    KmTypeSubject type = underlyingArgument.type();
+    assertNotNull(type);
+    assertTrue(type.classifier().isTypeAlias());
+    assertEquals(api.descriptor(packageName), type.descriptor());
+  }
+
+  private void inspectLibExt(CodeInspector inspector) {
+    String packageName = PKG + ".typealias_lib";
+    String libKtClassName = packageName + ".Lib_extKt";
+
+    // Check that Arr has been renamed.
+    ClassSubject arr = inspector.clazz(packageName + ".Arr");
+    assertThat(arr, isRenamed());
+
+    ClassSubject libKt = inspector.clazz(libKtClassName);
+    KmPackageSubject kmPackage = libKt.getKmPackage();
+
+    // typealias Arr1D<K> = Arr<K>
+    KmTypeAliasSubject arr1D = kmPackage.kmTypeAliasWithUniqueName("Arr1D");
+    assertThat(arr1D, isPresent());
+    assertThat(arr1D.expandedType(), isDexClass(arr.getDexClass()));
+
+    // typealias Arr2D<K> = Arr1D<Arr1D<K>>
+    KmTypeAliasSubject arr2D = kmPackage.kmTypeAliasWithUniqueName("Arr2D");
+    assertThat(arr2D, isPresent());
+    assertThat(arr2D.expandedType(), isDexClass(arr.getDexClass()));
+    assertEquals(1, arr2D.expandedType().typeArguments().size());
+    KmTypeProjectionSubject arr2DexpandedArg = arr2D.expandedType().typeArguments().get(0);
+    assertThat(arr2DexpandedArg.type(), isDexClass(arr.getDexClass()));
+
+    assertEquals(arr1D.descriptor(packageName), arr2D.underlyingType().descriptor());
+    assertEquals(1, arr2D.underlyingType().typeArguments().size());
+    KmTypeProjectionSubject arr2DunderlyingArg = arr2D.underlyingType().typeArguments().get(0);
+    assertEquals(arr1D.descriptor(packageName), arr2DunderlyingArg.type().descriptor());
+
+    // typealias IntSet = Set<Int>
+    // typealias MyMapToSetOfInt<K> = MutableMap<K, IntSet>
+    KmTypeAliasSubject intSet = kmPackage.kmTypeAliasWithUniqueName("IntSet");
+    assertThat(intSet, isPresent());
+
+    KmTypeAliasSubject myMapToSetOfInt = kmPackage.kmTypeAliasWithUniqueName("MyMapToSetOfInt");
+    assertThat(myMapToSetOfInt, isPresent());
+    assertEquals(2, myMapToSetOfInt.underlyingType().typeArguments().size());
+    assertEquals(2, myMapToSetOfInt.expandedType().typeArguments().size());
+    assertEquals(1, myMapToSetOfInt.typeParameters().size());
+    KmClassifierSubject typeClassifier =
+        myMapToSetOfInt.underlyingType().typeArguments().get(0).type().classifier();
+    assertTrue(typeClassifier.isTypeParameter());
+    // Check that the type-variable K in 'MyMapToSetOfInt<K>' is the first argument in
+    // MutableMap<K, IntSet>.
+    assertEquals(
+        myMapToSetOfInt.typeParameters().get(0).getId(), typeClassifier.asTypeParameter().getId());
+
+    KmTypeSubject underlyingType = myMapToSetOfInt.underlyingType().typeArguments().get(1).type();
+    assertEquals(intSet.descriptor(packageName), underlyingType.descriptor());
+
+    KmTypeSubject expandedType = myMapToSetOfInt.expandedType().typeArguments().get(1).type();
+    assertEquals(intSet.expandedType(), expandedType);
+
+    // Check that the following exist:
+    // typealias MyHandler = (Int, Any) -> Unit
+    // typealias MyGenericPredicate<T> = (T) -> Boolean
+    assertThat(kmPackage.kmTypeAliasWithUniqueName("MyHandler"), isPresent());
+    KmTypeAliasSubject genericPredicate = kmPackage.kmTypeAliasWithUniqueName("MyGenericPredicate");
+    assertThat(genericPredicate, isPresent());
+
+    // Check that the type-variable T in 'MyGenericPredicate<T>' is the input argument in
+    // (T) -> Boolean.
+    assertEquals(1, genericPredicate.typeParameters().size());
+    assertEquals(2, genericPredicate.expandedType().typeArguments().size());
+    KmTypeProjectionSubject kmTypeGenericArgumentSubject =
+        genericPredicate.expandedType().typeArguments().get(0);
+    assertTrue(kmTypeGenericArgumentSubject.type().classifier().isTypeParameter());
+    assertEquals(
+        genericPredicate.typeParameters().get(0).getId(),
+        kmTypeGenericArgumentSubject.type().classifier().asTypeParameter().getId());
+
+    // typealias ClassWithCompanionC = ClassWithCompanion.Companion
+    KmTypeAliasSubject classWithCompanionC =
+        kmPackage.kmTypeAliasWithUniqueName("ClassWithCompanionC");
+    assertThat(classWithCompanionC, isPresent());
+
+    ClassSubject companionClazz = inspector.clazz(packageName + ".ClassWithCompanion$Companion");
+    assertThat(classWithCompanionC.expandedType(), isDexClass(companionClazz.getDexClass()));
   }
 }
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 3d3d935..a158b99 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
@@ -4,17 +4,38 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isDexClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
+import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.KmPropertySubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeParameterSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeParameterSubjectMixin;
+import com.android.tools.r8.utils.codeinspector.KmTypeProjectionSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
+import com.android.tools.r8.utils.codeinspector.KmValueParameterSubject;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import kotlinx.metadata.KmClassifier.TypeParameter;
+import kotlinx.metadata.KmVariance;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -22,6 +43,12 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInTypeArgumentsTest extends KotlinMetadataTestBase {
+
+  private static final String LIB_PKG = PKG + ".typeargument_lib.";
+
+  private static int FLAG_NONE = 0;
+  private static int FLAG_REIFIED = 1;
+
   private static final String EXPECTED =
       StringUtils.lines(
           "Hello World!",
@@ -43,8 +70,13 @@
           "42",
           "1",
           "2",
+          "9",
+          "3",
           "7",
-          "42");
+          "9",
+          "42",
+          "42",
+          "7");
 
   private final TestParameters parameters;
 
@@ -69,7 +101,6 @@
       Path typeAliasLibJar =
           kotlinc(KOTLINC, targetVersion)
               .addSourceFiles(getKotlinFileInTest(typeAliasLibFolder, "lib"))
-              .addSourceFiles(getKotlinFileInTest(typeAliasLibFolder, "lib_minified"))
               .compile();
       jarMap.put(targetVersion, typeAliasLibJar);
     }
@@ -94,20 +125,27 @@
   }
 
   @Test
-  public void testMetadataInTypeAlias_keepAll() throws Exception {
+  public void testMetadataInTypeAliasWithR8() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
             .addProgramFiles(jarMap.get(targetVersion))
-            .addKeepAllClassesRule()
+            // Keep ClassThatWillBeObfuscated, but allow minification.
+            .addKeepRules("-keep,allowobfuscation class **ClassThatWillBeObfuscated")
+            .addKeepRules("-keepclassmembers class **ClassThatWillBeObfuscated { *; }")
+            // Keep all other classes.
+            .addKeepRules("-keep class **typeargument_lib.PlainBox { *; }")
+            .addKeepRules("-keep class **typeargument_lib.SomeClass { *; }")
+            .addKeepRules("-keep class **typeargument_lib.CoVariant { *; }")
+            .addKeepRules("-keep class **typeargument_lib.ContraVariant { *; }")
+            .addKeepRules("-keep class **typeargument_lib.Invariant { *; }")
+            .addKeepRules("-keep class **typeargument_lib.LibKt { *; }")
             .addKeepAttributes(
                 ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
                 ProguardKeepAttributes.SIGNATURE,
                 ProguardKeepAttributes.INNER_CLASSES,
                 ProguardKeepAttributes.ENCLOSING_METHOD)
             .compile()
-            // 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).
+            .inspect(this::inspect)
             .writeToZip();
 
     Path mainJar =
@@ -118,13 +156,124 @@
 
     // TODO(b/152306391): Reified type-parameters are not flagged correctly.
     testForJvm()
-        .addProgramFiles(mainJar)
-        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
-        .addRunClasspathFiles(libJar)
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(mainJar)
         .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"));
   }
+
+  private void inspect(CodeInspector inspector) {
+    inspectInvariant(inspector);
+    inspectCoVariant(inspector);
+    inspectContraVariant(inspector);
+    inspectExtensions(inspector);
+  }
+
+  private void inspectInvariant(CodeInspector inspector) {
+    ClassSubject someClass = inspector.clazz(LIB_PKG + "SomeClass");
+    assertThat(someClass, isPresent());
+    ClassSubject classThatShouldBeObfuscated =
+        inspector.clazz(LIB_PKG + "ClassThatWillBeObfuscated");
+    assertThat(classThatShouldBeObfuscated, isRenamed());
+
+    // Check that the type-parameters of Invariant is marked as INVARIANT.
+    ClassSubject invariant = inspector.clazz(LIB_PKG + "Invariant");
+    assertThat(invariant, isPresent());
+    KmClassSubject kmClass = invariant.getKmClass();
+    assertThat(kmClass, isPresent());
+    assertEquals(2, kmClass.typeParameters().size());
+    inspectTypeParameter(kmClass, "T", 0, FLAG_NONE, KmVariance.INVARIANT);
+    inspectTypeParameter(kmClass, "C", 1, FLAG_NONE, KmVariance.INVARIANT);
+
+    // Check that funGenerics method has a type-parameter with id = 2.
+    KmFunctionSubject funGenerics = kmClass.kmFunctionWithUniqueName("funGenerics");
+    assertThat(funGenerics, isPresent());
+    assertEquals(1, funGenerics.typeParameters().size());
+    inspectTypeParameter(funGenerics, "R", 2, FLAG_NONE, KmVariance.INVARIANT);
+    assertEquals(1, funGenerics.valueParameters().size());
+    KmValueParameterSubject kmValueParameterSubject = funGenerics.valueParameters().get(0);
+    assertTrue(kmValueParameterSubject.type().classifier().isTypeParameter());
+    TypeParameter typeParameter = kmValueParameterSubject.type().classifier().asTypeParameter();
+    assertEquals(2, typeParameter.getId());
+
+    // Check that the funGenerics method return type is referencing the method type parameter.
+    KmTypeSubject funGenericsReturnType = funGenerics.returnType();
+    assertTrue(funGenericsReturnType.classifier().isTypeParameter());
+    assertEquals(2, funGenericsReturnType.classifier().asTypeParameter().getId());
+
+    // Check funGenericsWithUpperBounds has an upperBound of SomeClass.
+    KmFunctionSubject funGenericsWithUpperBounds =
+        kmClass.kmFunctionWithUniqueName("funGenericsWithUpperBounds");
+    assertThat(funGenericsWithUpperBounds, isPresent());
+    assertEquals(1, funGenericsWithUpperBounds.typeParameters().size());
+    inspectTypeParameter(funGenericsWithUpperBounds, "R", 2, FLAG_NONE, KmVariance.INVARIANT);
+    KmTypeParameterSubject methodTypeParameter = funGenericsWithUpperBounds.typeParameters().get(0);
+    List<KmTypeSubject> upperBounds = methodTypeParameter.upperBounds();
+    assertEquals(2, upperBounds.size());
+    assertThat(upperBounds.get(0), isDexClass(someClass.getDexClass()));
+    assertEquals(KT_COMPARABLE, upperBounds.get(1).descriptor());
+    // Check that the upper bound has a type argument.
+    assertEquals(1, upperBounds.get(1).typeArguments().size());
+    assertThat(
+        upperBounds.get(1).typeArguments().get(0).type(), isDexClass(someClass.getDexClass()));
+  }
+
+  private void inspectCoVariant(CodeInspector inspector) {
+    // Check that the type-parameter for CoVariant is marked as OUT.
+    ClassSubject invariant = inspector.clazz(LIB_PKG + "CoVariant");
+    assertThat(invariant, isPresent());
+    KmClassSubject kmClass = invariant.getKmClass();
+    assertThat(kmClass, isPresent());
+    assertEquals(1, kmClass.typeParameters().size());
+    inspectTypeParameter(kmClass, "T", 0, FLAG_NONE, KmVariance.OUT);
+    // Check that the return type of the property CoVariant.t refers to the type parameter.
+    assertEquals(1, kmClass.getProperties().size());
+    KmPropertySubject t = kmClass.kmPropertyWithUniqueName("t");
+    assertThat(t, isPresent());
+    assertTrue(t.returnType().typeArguments().isEmpty());
+    assertEquals(
+        kmClass.typeParameters().get(0).getId(),
+        t.returnType().classifier().asTypeParameter().getId());
+  }
+
+  private void inspectContraVariant(CodeInspector inspector) {
+    // Check that the type-parameter for ContraVariant is marked as IN.
+    ClassSubject invariant = inspector.clazz(LIB_PKG + "ContraVariant");
+    assertThat(invariant, isPresent());
+    KmClassSubject kmClass = invariant.getKmClass();
+    assertThat(kmClass, isPresent());
+    assertEquals(1, kmClass.typeParameters().size());
+    inspectTypeParameter(kmClass, "T", 0, FLAG_NONE, KmVariance.IN);
+  }
+
+  private void inspectExtensions(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(LIB_PKG + "LibKt");
+    assertThat(clazz, isPresent());
+    KmPackageSubject kmPackage = clazz.getKmPackage();
+    assertThat(kmPackage, isPresent());
+    KmFunctionSubject asListWithVarargs =
+        kmPackage.kmFunctionExtensionWithUniqueName("asListWithVarargs");
+    assertThat(asListWithVarargs, isExtensionFunction());
+    inspectTypeParameter(asListWithVarargs, "T", 0, FLAG_REIFIED, KmVariance.INVARIANT);
+    // Check that the varargs type argument has OUT invariance.
+    List<KmTypeProjectionSubject> kmTypeProjectionSubjects =
+        asListWithVarargs.returnType().typeArguments();
+    assertEquals(1, kmTypeProjectionSubjects.size());
+    KmTypeSubject type = kmTypeProjectionSubjects.get(0).type();
+    assertEquals(1, type.typeArguments().size());
+    KmTypeProjectionSubject kmTypeProjectionSubject = type.typeArguments().get(0);
+    assertEquals(KmVariance.OUT, kmTypeProjectionSubject.variance());
+  }
+
+  private void inspectTypeParameter(
+      KmTypeParameterSubjectMixin subject, String name, int id, int flags, KmVariance variance) {
+    KmTypeParameterSubject typeParameter = subject.kmTypeParameterWithUniqueName(name);
+    assertThat(typeParameter, isPresent());
+    assertEquals(id, typeParameter.getId());
+    assertEquals(flags, typeParameter.getFlags());
+    assertEquals(variance, typeParameter.getVariance());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_app/main.kt
index 396c2e4..f836216 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_app/main.kt
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.kotlin.metadata.typealias_lib.API
 import com.android.tools.r8.kotlin.metadata.typealias_lib.AlphaNaming
-import com.android.tools.r8.kotlin.metadata.typealias_lib.Arr
+import com.android.tools.r8.kotlin.metadata.typealias_lib.Arr1D
 import com.android.tools.r8.kotlin.metadata.typealias_lib.Arr2D
 import com.android.tools.r8.kotlin.metadata.typealias_lib.Arr2DTester
 import com.android.tools.r8.kotlin.metadata.typealias_lib.CWithConstructor
@@ -13,6 +13,7 @@
 import com.android.tools.r8.kotlin.metadata.typealias_lib.ClassWithCompanionC
 import com.android.tools.r8.kotlin.metadata.typealias_lib.FunctionTester
 import com.android.tools.r8.kotlin.metadata.typealias_lib.Impl
+import com.android.tools.r8.kotlin.metadata.typealias_lib.IntSet
 import com.android.tools.r8.kotlin.metadata.typealias_lib.InterfaceTester
 import com.android.tools.r8.kotlin.metadata.typealias_lib.MyAdvancedMap
 import com.android.tools.r8.kotlin.metadata.typealias_lib.MyI
@@ -22,11 +23,10 @@
 import com.android.tools.r8.kotlin.metadata.typealias_lib.OuterTester
 import com.android.tools.r8.kotlin.metadata.typealias_lib.SimpleClassTester
 import com.android.tools.r8.kotlin.metadata.typealias_lib.StillCWithConstructor
-import com.android.tools.r8.kotlin.metadata.typealias_lib.UnderlyingTypeTest
+import com.android.tools.r8.kotlin.metadata.typealias_lib.UnderlyingTypeTester
 import com.android.tools.r8.kotlin.metadata.typealias_lib.UnusedTypeArgument
 import com.android.tools.r8.kotlin.metadata.typealias_lib.VerticalClassMergingTester
 import com.android.tools.r8.kotlin.metadata.typealias_lib.seq
-import com.android.tools.r8.kotlin.metadata.typealias_lib.IntSet as IntSet1
 
 class ProgramClass : Impl() {
   override fun foo(): API {
@@ -46,7 +46,7 @@
 }
 
 fun testArr2D() {
-  val arr1d : Arr<Int> = Arr(42);
+  val arr1d : Arr1D<Int> = Arr1D(42);
   val arr2d : Arr2D<Int> = Arr2D(arr1d);
   println(Arr2DTester.f(Arr2DTester.g(arr2d)).x.x);
 }
@@ -57,10 +57,10 @@
       println("42");
     }
   }
-  InterfaceTester.f(InterfaceTester.g(myInstance)).f()
+  InterfaceTester.f(myInstance).f()
 
   val map : MyMapToSetOfInt<Int> = HashMap();
-  val set : IntSet1 = mutableSetOf(42);
+  val set : IntSet = mutableSetOf(42);
   map.put(1, set);
   println(InterfaceTester.i(InterfaceTester.h(map))[1]?.iterator()?.next() ?: "");
 }
@@ -79,12 +79,12 @@
 fun testNestedClasses() {
   val nested = OuterNested(42);
   val myInner : OuterNestedInner = nested.Inner(1)
-  println(OuterTester.g(nested).y)
-  println(OuterTester.f(myInner).x)
+  println(OuterTester.f(OuterTester.g(nested)).y)
+  println(OuterTester.h(OuterTester.i(myInner)).x)
 }
 
 fun testCompanion() {
-  ClassWithCompanionC.foo;
+  println(ClassWithCompanionC.fooOnCompanion);
 }
 
 fun testConstructor() {
@@ -93,12 +93,12 @@
 
 fun testUnderlyingType() {
   val cWithConstructor = StillCWithConstructor(42)
-  println(UnderlyingTypeTest.f(UnderlyingTypeTest.g(cWithConstructor)).x)
+  println(UnderlyingTypeTester.f(UnderlyingTypeTester.g(cWithConstructor)).x)
   val advancedMap : MyAdvancedMap = HashMap();
   val nested = OuterNested(42);
   val myInner : OuterNestedInner = nested.Inner(1)
   advancedMap.put(nested, myInner);
-  val sameMap = UnderlyingTypeTest.h(UnderlyingTypeTest.i(advancedMap))
+  val sameMap = UnderlyingTypeTester.h(UnderlyingTypeTester.i(advancedMap))
   println(sameMap.get(nested)?.x)
 }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_lib/lib_ext.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_lib/lib_ext.kt
index ba63667..60d5bda 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_lib/lib_ext.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_lib/lib_ext.kt
@@ -1,6 +1,8 @@
 // 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.
+@file:Suppress("UNCHECKED_CAST")
+
 package com.android.tools.r8.kotlin.metadata.typealias_lib
 
 // Unused type aliases
@@ -51,13 +53,14 @@
 class SimpleClassTester {
 
   companion object {
-    fun f(a : SimpleClass) : AlphaNaming {
+    fun f(a : Any) : AlphaNaming {
+      return a as AlphaNaming;
+    }
+
+    fun g(a : AlphaNaming) : Any {
       return a;
     }
 
-    fun g(a : AlphaNaming) : SimpleClass {
-      return a;
-    }
   }
 }
 
@@ -196,11 +199,11 @@
 
   companion object {
 
-    fun f(i : I<Int>) : MyI {
-      return i;
+    fun f(i : Any) : MyI {
+      return i as MyI
     }
 
-    fun g(myI : MyI) : I<Int> {
+    fun g(myI : MyI) : Any {
       return myI;
     }
 
@@ -301,14 +304,21 @@
 
   companion object {
 
-    fun f(a : Outer.Nested.Inner) : OuterNestedInner {
+    fun f(a : Any) : OuterNested {
+      return a as OuterNested;
+    }
+
+    fun g(a : OuterNested) : Any {
       return a;
     }
 
-    fun g(a : OuterNested) : Outer.Nested {
-      return a;
+    fun h(a : Any) : OuterNestedInner {
+      return a as OuterNestedInner;
     }
 
+    fun i(a : OuterNestedInner) : Any {
+      return a;
+    }
   }
 }
 
@@ -317,8 +327,8 @@
 class ClassWithCompanion {
 
   companion object {
-    val foo: String
-      get() = "A.Companion::foo"
+    val fooOnCompanion: String
+      get() = "ClassWithCompanion::fooOnCompanion"
   }
 }
 
@@ -355,11 +365,11 @@
 
   companion object {
 
-    fun f(a : C) : CWithConstructor {
-      return a;
+    fun f(a : Any) : CWithConstructor {
+      return a as CWithConstructor;
     }
 
-    fun g(a : CWithConstructor) : C {
+    fun g(a : CWithConstructor) : Any {
       return a;
     }
   }
@@ -391,7 +401,7 @@
 // },
 typealias MyAdvancedMap = MutableMap<OuterNested, OuterNestedInner>
 
-class UnderlyingTypeTest {
+class UnderlyingTypeTester {
 
   companion object {
 
@@ -399,7 +409,7 @@
       return a;
     }
 
-    fun g(a : C) : StillCWithConstructor {
+    fun g(a : CWithConstructor) : StillCWithConstructor {
       return a;
     }
 
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 c8f4a72..2129081 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
@@ -7,9 +7,13 @@
 import com.android.tools.r8.kotlin.metadata.typeargument_lib.CoVariant
 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.PlainBox
 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.asListWithVarargs
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.asListWithVarargs2
 import com.android.tools.r8.kotlin.metadata.typeargument_lib.asObfuscatedClass
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.asStar
 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
@@ -48,10 +52,20 @@
   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)
+  val asList = CoVariant(42).asListWithVarargs(1, 2)
   println(asList.t.get(0))
   println(asList.t.get(1))
-  println(CoVariant(7).asObfuscatedClass().t.get(0).get(0).x)
+  println(CoVariant(9).asListWithVarargs2(CoVariant(3)).t.get(0))
+  // Peeking into the result will result in an error since the underlying type has been renamed.
+  CoVariant(7).asObfuscatedClass()
+  println(CoVariant(42).asStar().t)
+}
+
+fun testPlainBox() {
+  var plainBox = PlainBox(42)
+  println(plainBox.plainBox)
+  plainBox.plainBox = 7
+  println(plainBox.plainBox)
 }
 
 fun main() {
@@ -59,4 +73,5 @@
   testCoVariant()
   testContraVariant();
   testExtension()
+  testPlainBox()
 }
\ No newline at end of file
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 38234b4..d9d3bea 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,6 +6,10 @@
 
 open class SomeClass
 
+class PlainBox<T>(var plainBox : T)
+
+class ClassThatWillBeObfuscated(val x : Int)
+
 class Invariant<T, C> {
 
   constructor(someValue : C) {
@@ -76,12 +80,23 @@
   return CoVariant(arrayOf(this.t))
 }
 
-inline fun <reified T> CoVariant<T>.asList(vararg ts : T) : CoVariant<Array<out T>> {
+inline fun <reified T> CoVariant<T>.asListWithVarargs(vararg ts : T) : CoVariant<Array<out T>> {
   println(this.t)
   return CoVariant(ts)
 }
 
+fun <T> CoVariant<T>.asListWithVarargs2(vararg ts : CoVariant<T>) : CoVariant<List<T>> {
+  println(this.t)
+  return CoVariant(listOf(ts.get(0).t))
+}
+
 fun <T> CoVariant<T>.asObfuscatedClass() : CoVariant<Array<Array<ClassThatWillBeObfuscated>>> {
   println(this.t)
-  return CoVariant(arrayOf(arrayOf(ClassThatWillBeObfuscated(42))))
+  val classThatWillBeObfuscated = ClassThatWillBeObfuscated(9)
+  println(classThatWillBeObfuscated.x)
+  return CoVariant(arrayOf(arrayOf(classThatWillBeObfuscated)))
+}
+
+fun CoVariant<*>.asStar() : CoVariant<*> {
+  return this;
 }
\ 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
deleted file mode 100644
index 78c3742..0000000
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib_minified.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package com.android.tools.r8.kotlin.metadata.typeargument_lib
-
-class ClassThatWillBeObfuscated(val x : Int)
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java
new file mode 100644
index 0000000..63fe00f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java
@@ -0,0 +1,120 @@
+package com.android.tools.r8.naming.applymapping;
+
+import static com.android.tools.r8.Collectors.toSingle;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotSame;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+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;
+
+// This is a reproduction of b/152715309.
+@RunWith(Parameterized.class)
+public class ApplyMappingDesugarLambdaTest extends TestBase {
+
+  private static final String EXPECTED = "FOO";
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public ApplyMappingDesugarLambdaTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws CompilationFailedException, IOException, ExecutionException {
+    // Create a dictionary to control the naming.
+    Path dictionary = temp.getRoot().toPath().resolve("dictionary.txt");
+    FileUtils.writeTextFile(dictionary, "e");
+
+    final String finalName = "com.android.tools.r8.naming.applymapping.e";
+
+    R8TestCompileResult libraryResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(A.class)
+            .addKeepClassAndMembersRulesWithAllowObfuscation(A.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepRules(
+                "-keeppackagenames", "-classobfuscationdictionary " + dictionary.toString())
+            .compile()
+            .inspect(
+                inspector -> {
+                  assertThat(inspector.clazz(A.class), isRenamed());
+                  assertEquals(finalName, inspector.clazz(A.class).getFinalName());
+                });
+
+    Path libraryPath = libraryResult.writeToZip();
+
+    // Ensure that the library works as supposed.
+    testForD8()
+        .addProgramClasses(I.class, Main.class)
+        .addClasspathFiles(libraryPath)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class, EXPECTED)
+        .assertSuccessWithOutputLines(EXPECTED);
+
+    testForR8(parameters.getBackend())
+        .addClasspathClasses(A.class)
+        .addProgramClasses(I.class, Main.class)
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(I.class)
+        .setMinApi(parameters.getApiLevel())
+        .addApplyMapping(libraryResult.getProguardMap())
+        .addOptionsModification(internalOptions -> internalOptions.enableClassInlining = false)
+        .addKeepRules("-classobfuscationdictionary " + dictionary.toString())
+        .compile()
+        .inspect(
+            inspector -> {
+              // Assert that there is a lambda class created.
+              assertEquals(3, inspector.allClasses().size());
+              FoundClassSubject lambdaClass =
+                  inspector.allClasses().stream()
+                      .filter(FoundClassSubject::isSynthetic)
+                      .collect(toSingle());
+              assertNotSame(finalName, lambdaClass.getFinalName());
+            })
+        .addRunClasspathFiles(libraryPath)
+        .run(parameters.getRuntime(), Main.class, EXPECTED)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  public static class A {
+
+    A(int bar) {
+      System.out.println(bar);
+    }
+  }
+
+  @FunctionalInterface
+  public interface I {
+
+    void doStuff();
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      processI(() -> System.out.println(args[0]));
+    }
+
+    public static void processI(I i) {
+      i.doStuff();
+    }
+  }
+}
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
index 7c8aa34..c37953b 100644
--- a/src/test/java/com/android/tools/r8/regress/b151964517/ConstStringWithMonitorTest.java
+++ b/src/test/java/com/android/tools/r8/regress/b151964517/ConstStringWithMonitorTest.java
@@ -16,7 +16,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().build();
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
   }
 
   public ConstStringWithMonitorTest(TestParameters parameters) {
@@ -28,11 +28,12 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(ConstStringWithMonitorTest.class)
         .noMinification()
+        .setMinApi(parameters.getApiLevel())
         .allowAccessModification()
         .addKeepMainRule(TestClass.class)
         .compile()
-        .runDex2Oat(parameters.getRuntime());
-    // TODO(b/151964517): Should pass verification with assertNoVerificationErrors()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("foobar");
   }
 
   public static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/regress/b152800551/FailedStaticizingRegressionTest.java b/src/test/java/com/android/tools/r8/regress/b152800551/FailedStaticizingRegressionTest.java
new file mode 100644
index 0000000..7ec8ab0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b152800551/FailedStaticizingRegressionTest.java
@@ -0,0 +1,81 @@
+// 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.b152800551;
+
+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.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+// This is a reproduction of b/152800551.
+@RunWith(Parameterized.class)
+public class FailedStaticizingRegressionTest extends TestBase {
+
+  private static final String EXPECTED =
+      StringUtils.lines(
+          "S::foo a 1", "S::foo a 2", "S::foo a 3", "S::foo b 1", "S::foo b 2", "S::foo b 3");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public FailedStaticizingRegressionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  static class S {
+
+    private static S f = new S();
+
+    public static S get() {
+      return f;
+    }
+
+    public void foo() {
+      // Double call of foo so single call inlining does not trigger.
+      foo("a");
+      foo("b");
+    }
+
+    @NeverInline
+    public void foo(String s) {
+      System.out.println("S::foo " + s + " 1");
+      System.out.println("S::foo " + s + " 2");
+      System.out.println("S::foo " + s + " 3");
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      S.get().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java b/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java
new file mode 100644
index 0000000..36972c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java
@@ -0,0 +1,93 @@
+// 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.b152973695;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class CompileToInvalidFileTest extends TestBase {
+  private static final Path INVALID_FILE = Paths.get("!@#/\\INVALID_FILE");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public CompileToInvalidFileTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testCompileToInvalidFileD8() {
+    ensureInvalidFileIsInvalid();
+    try {
+      testForD8()
+          .addProgramClasses(Main.class)
+          .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(INVALID_FILE))
+          .compile();
+      fail("Expected a CompilationFailedException but the code succeeded");
+    } catch (CompilationFailedException ex) {
+      assertInvalidFileNotFound(ex);
+    } catch (Throwable t) {
+      fail("Expected a CompilationFailedException but got instead " + t);
+    }
+  }
+
+  @Test
+  public void testCompileToInvalidFileR8() {
+    ensureInvalidFileIsInvalid();
+    try {
+      testForR8(Backend.CF)
+          .addProgramClasses(Main.class)
+          .addKeepMainRule(Main.class)
+          .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(INVALID_FILE))
+          .compile();
+      fail("Expected a CompilationFailedException but the code succeeded");
+    } catch (CompilationFailedException ex) {
+      assertInvalidFileNotFound(ex);
+    } catch (Throwable t) {
+      fail("Expected a CompilationFailedException but got instead " + t);
+    }
+  }
+
+  private void assertInvalidFileNotFound(CompilationFailedException ex) {
+    assertTrue(ex.getCause().getMessage().contains("File not found"));
+    assertTrue(ex.getCause().getMessage().contains(INVALID_FILE.toString()));
+  }
+
+  private void ensureInvalidFileIsInvalid() {
+    try {
+      Files.newOutputStream(
+          INVALID_FILE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+      fail("Expected an IOException but the code succeeded");
+    } catch (IOException ignored) {
+    } catch (Throwable t) {
+      fail("Expected an IOException but got instead " + t);
+    }
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumValueOfOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumValueOfOptimizationTest.java
new file mode 100644
index 0000000..6f061f3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumValueOfOptimizationTest.java
@@ -0,0 +1,134 @@
+// 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.rewrite.enums;
+
+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 EnumValueOfOptimizationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EnumValueOfOptimizationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testValueOf() throws Exception {
+    testForR8(parameters.getBackend())
+        .addKeepMainRule(Main.class)
+        .addInnerClasses(EnumValueOfOptimizationTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("npe OK", "iae1 OK", "iae2 OK", "iae3 OK", "iae4 OK");
+  }
+
+  enum MyEnum {
+    A,
+    B
+  }
+
+  enum ComplexEnum {
+    A {
+      @Override
+      public String toString() {
+        return "a0";
+      }
+    },
+    B
+  }
+
+  @SuppressWarnings({"unchecked", "ConstantConditions"})
+  static class Main {
+    public static void main(String[] args) {
+      myEnumTest();
+      complexEnumTest();
+    }
+
+    private static void complexEnumTest() {
+      Enum<?> e = null;
+      try {
+        e = subtypeError();
+        System.out.println("iae4 KO");
+      } catch (IllegalArgumentException iae) {
+        System.out.println("iae4 OK");
+      }
+      if (e != null) {
+        throw new Error("enum set");
+      }
+    }
+
+    private static Enum<?> subtypeError() {
+      return Enum.valueOf((Class) ComplexEnum.A.getClass(), "A");
+    }
+
+    private static void myEnumTest() {
+      Enum<?> e = null;
+      try {
+        e = nullClassError();
+        System.out.println("npe KO");
+      } catch (NullPointerException ignored) {
+        System.out.println("npe OK");
+      }
+      if (e != null) {
+        throw new Error("enum set");
+      }
+      try {
+        e = invalidNameError();
+        System.out.println("iae1 KO");
+      } catch (IllegalArgumentException iae) {
+        System.out.println("iae1 OK");
+      }
+      if (e != null) {
+        throw new Error("enum set");
+      }
+      try {
+        e = enumClassError();
+        System.out.println("iae2 KO");
+      } catch (IllegalArgumentException iae) {
+        System.out.println("iae2 OK");
+      }
+      if (e != null) {
+        throw new Error("enum set");
+      }
+      try {
+        e = voidError();
+        System.out.println("iae3 KO");
+      } catch (IllegalArgumentException iae) {
+        System.out.println("iae3 OK");
+      }
+      if (e != null) {
+        throw new Error("enum set");
+      }
+    }
+
+    private static Enum<?> voidError() {
+      return Enum.valueOf((Class) Void.class, "TYPE");
+    }
+
+    private static Enum<?> enumClassError() {
+      return Enum.valueOf(Enum.class, "smth");
+    }
+
+    private static Enum<?> invalidNameError() {
+      return Enum.valueOf(MyEnum.class, "curly");
+    }
+
+    private static Enum<?> nullClassError() {
+      return Enum.valueOf((Class<MyEnum>) null, "a string");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 530b5b5..7bc8279 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.references.ClassReference;
@@ -228,6 +229,25 @@
         });
   }
 
+  public ClassFileTransformer setAccessFlags(Consumer<ClassAccessFlags> fn) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public void visit(
+              int version,
+              int access,
+              String name,
+              String signature,
+              String superName,
+              String[] interfaces) {
+            ClassAccessFlags flags = ClassAccessFlags.fromCfAccessFlags(access);
+            fn.accept(flags);
+            super.visit(
+                version, flags.getAsCfAccessFlags(), name, signature, superName, interfaces);
+          }
+        });
+  }
+
   public ClassFileTransformer setNest(Class<?> host, Class<?>... members) {
     assert !Arrays.asList(members).contains(host);
     return setMinVersion(CfVm.JDK11)
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
index a009244..a58d5ee 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
@@ -3,8 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexClass;
 import java.util.List;
+import kotlinx.metadata.KmTypeParameter;
 
 public class AbsentKmClassSubject extends KmClassSubject {
 
@@ -25,12 +27,12 @@
 
   @Override
   public boolean isRenamed() {
-    return false;
+    throw new Unreachable("Cannot determine if an absent KmClass is renamed");
   }
 
   @Override
   public boolean isSynthetic() {
-    return false;
+    throw new Unreachable("Cannot determine if an absent KmClass is synthetic");
   }
 
   @Override
@@ -94,6 +96,16 @@
   }
 
   @Override
+  public List<KmTypeAliasSubject> getTypeAliases() {
+    return null;
+  }
+
+  @Override
+  public KmTypeAliasSubject kmTypeAliasWithUniqueName(String name) {
+    return null;
+  }
+
+  @Override
   public List<String> getSuperTypeDescriptors() {
     return null;
   }
@@ -127,4 +139,14 @@
   public String getCompanionObject() {
     return null;
   }
+
+  @Override
+  public List<KmTypeParameter> getKmTypeParameters() {
+    return null;
+  }
+
+  @Override
+  public CodeInspector getCodeInspector() {
+    return null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
index 5efcf6e..7e8c837 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.errors.Unreachable;
 import java.util.List;
+import kotlinx.metadata.KmTypeParameter;
 import kotlinx.metadata.jvm.JvmMethodSignature;
 
 public class AbsentKmFunctionSubject extends KmFunctionSubject {
@@ -15,17 +17,17 @@
 
   @Override
   public boolean isRenamed() {
-    return false;
+    throw new Unreachable("Cannot determine if an absent KmFunction is renamed");
   }
 
   @Override
   public boolean isSynthetic() {
-    return false;
+    throw new Unreachable("Cannot determine if an absent KmFunction is synthetic");
   }
 
   @Override
   public boolean isExtension() {
-    return false;
+    throw new Unreachable("Cannot determine if an absent KmFunction is extension");
   }
 
   @Override
@@ -47,4 +49,14 @@
   public KmTypeSubject returnType() {
     return null;
   }
+
+  @Override
+  public List<KmTypeParameter> getKmTypeParameters() {
+    return null;
+  }
+
+  @Override
+  public CodeInspector getCodeInspector() {
+    return null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java
index aef78fc..bbd1df6 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexClass;
 import java.util.List;
 
@@ -20,12 +21,12 @@
 
   @Override
   public boolean isRenamed() {
-    return false;
+    throw new Unreachable("Cannot determine if an absent KmPackage is renamed");
   }
 
   @Override
   public boolean isSynthetic() {
-    return false;
+    throw new Unreachable("Cannot determine if an absent KmPackage is synthetic");
   }
 
   @Override
@@ -87,4 +88,14 @@
   public List<ClassSubject> getReturnTypesInProperties() {
     return null;
   }
+
+  @Override
+  public List<KmTypeAliasSubject> getTypeAliases() {
+    return null;
+  }
+
+  @Override
+  public KmTypeAliasSubject kmTypeAliasWithUniqueName(String name) {
+    return null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPropertySubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPropertySubject.java
index d9c7d55..59773c5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPropertySubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPropertySubject.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.errors.Unreachable;
 import kotlinx.metadata.jvm.JvmFieldSignature;
 import kotlinx.metadata.jvm.JvmMethodSignature;
 
@@ -15,17 +16,17 @@
 
   @Override
   public boolean isRenamed() {
-    return false;
+    throw new Unreachable("Cannot determine if an absent KmProperty is renamed");
   }
 
   @Override
   public boolean isSynthetic() {
-    return false;
+    throw new Unreachable("Cannot determine if an absent KmProperty is synthetic");
   }
 
   @Override
   public boolean isExtension() {
-    return false;
+    throw new Unreachable("Cannot determine if an absent KmProperty is extension");
   }
 
   @Override
@@ -47,4 +48,9 @@
   public JvmMethodSignature setterSignature() {
     return null;
   }
+
+  @Override
+  public KmTypeSubject returnType() {
+    return null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java
new file mode 100644
index 0000000..78ac796
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.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.utils.codeinspector;
+
+import com.android.tools.r8.errors.Unreachable;
+import java.util.List;
+
+public class AbsentKmTypeAliasSubject extends KmTypeAliasSubject {
+
+  @Override
+  public boolean isPresent() {
+    return false;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    throw new Unreachable("Cannot determine if an absent KmTypeAlias is renamed");
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    throw new Unreachable("Cannot determine if an absent KmTypeAlias is synthetic");
+  }
+
+  @Override
+  public String name() {
+    return null;
+  }
+
+  @Override
+  public List<KmTypeParameterSubject> typeParameters() {
+    return null;
+  }
+
+  @Override
+  public String descriptor(String pkg) {
+    return null;
+  }
+
+  @Override
+  public KmTypeSubject expandedType() {
+    return null;
+  }
+
+  @Override
+  public KmTypeSubject underlyingType() {
+    return null;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeParameterSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeParameterSubject.java
new file mode 100644
index 0000000..9bb0998
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeParameterSubject.java
@@ -0,0 +1,47 @@
+// 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.utils.codeinspector;
+
+import com.android.tools.r8.errors.Unreachable;
+import java.util.List;
+import kotlinx.metadata.KmVariance;
+
+public class AbsentKmTypeParameterSubject extends KmTypeParameterSubject {
+
+  @Override
+  public boolean isPresent() {
+    return false;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    throw new Unreachable("Cannot determine if an absent KmPropertyParameter is renamed");
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    throw new Unreachable("Cannot determine if an absent KmPropertyParameter is synthetic");
+  }
+
+  @Override
+  public int getId() {
+    return 0;
+  }
+
+  @Override
+  public int getFlags() {
+    return 0;
+  }
+
+  @Override
+  public KmVariance getVariance() {
+    return null;
+  }
+
+  @Override
+  public List<KmTypeSubject> upperBounds() {
+    return null;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
index 71b30cb..a2185b0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -10,9 +10,11 @@
 import java.util.stream.Collectors;
 import kotlinx.metadata.KmClass;
 import kotlinx.metadata.KmDeclarationContainer;
+import kotlinx.metadata.KmTypeParameter;
 
 public class FoundKmClassSubject extends KmClassSubject
     implements FoundKmDeclarationContainerSubject {
+
   private final CodeInspector codeInspector;
   private final DexClass clazz;
   private final KmClass kmClass;
@@ -115,4 +117,14 @@
   public String getCompanionObject() {
     return kmClass.getCompanionObject();
   }
+
+  @Override
+  public List<KmTypeParameter> getKmTypeParameters() {
+    return kmClass.getTypeParameters();
+  }
+
+  @Override
+  public CodeInspector getCodeInspector() {
+    return codeInspector;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
index 626388f..6363747 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
@@ -18,6 +18,7 @@
 import kotlinx.metadata.KmPropertyExtensionVisitor;
 import kotlinx.metadata.KmPropertyVisitor;
 import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeAlias;
 import kotlinx.metadata.jvm.JvmFieldSignature;
 import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor;
 import kotlinx.metadata.jvm.JvmMethodSignature;
@@ -245,4 +246,22 @@
         .filter(ClassSubject::isPresent)
         .collect(Collectors.toList());
   }
+
+  @Override
+  default List<KmTypeAliasSubject> getTypeAliases() {
+    CodeInspector inspector = codeInspector();
+    return getKmDeclarationContainer().getTypeAliases().stream()
+        .map(typeAlias -> new FoundKmTypeAliasSubject(inspector, typeAlias))
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  default KmTypeAliasSubject kmTypeAliasWithUniqueName(String name) {
+    for (KmTypeAlias typeAlias : getKmDeclarationContainer().getTypeAliases()) {
+      if (typeAlias.getName().equals(name)) {
+        return new FoundKmTypeAliasSubject(codeInspector(), typeAlias);
+      }
+    }
+    return new AbsentKmTypeAliasSubject();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
index f86f718..93a2d96 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
@@ -8,9 +8,11 @@
 import java.util.stream.Collectors;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeParameter;
 import kotlinx.metadata.jvm.JvmMethodSignature;
 
 public class FoundKmFunctionSubject extends KmFunctionSubject {
+
   private final CodeInspector codeInspector;
   private final KmFunction kmFunction;
   private final JvmMethodSignature signature;
@@ -68,4 +70,14 @@
   public KmTypeSubject returnType() {
     return new KmTypeSubject(codeInspector, kmFunction.getReturnType());
   }
+
+  @Override
+  public List<KmTypeParameter> getKmTypeParameters() {
+    return kmFunction.getTypeParameters();
+  }
+
+  @Override
+  public CodeInspector getCodeInspector() {
+    return codeInspector;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
index d6bab2d..accf1b0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
@@ -9,6 +9,7 @@
 import kotlinx.metadata.jvm.JvmMethodSignature;
 
 public class FoundKmPropertySubject extends KmPropertySubject {
+
   private final CodeInspector codeInspector;
   private final KmProperty kmProperty;
   private final JvmFieldSignature fieldSignature;
@@ -67,4 +68,9 @@
   public JvmMethodSignature setterSignature() {
     return setterSignature;
   }
+
+  @Override
+  public KmTypeSubject returnType() {
+    return new KmTypeSubject(codeInspector, kmProperty.getReturnType());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
new file mode 100644
index 0000000..a07125c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
@@ -0,0 +1,65 @@
+// 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.utils.codeinspector;
+
+import static com.android.tools.r8.utils.DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR;
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmTypeAlias;
+
+public class FoundKmTypeAliasSubject extends KmTypeAliasSubject {
+
+  private final KmTypeAlias kmTypeAlias;
+  private final CodeInspector codeInspector;
+
+  FoundKmTypeAliasSubject(CodeInspector codeInspector, KmTypeAlias kmTypeAlias) {
+    this.codeInspector = codeInspector;
+    this.kmTypeAlias = kmTypeAlias;
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return false;
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    return false;
+  }
+
+  @Override
+  public String name() {
+    return kmTypeAlias.getName();
+  }
+
+  @Override
+  public List<KmTypeParameterSubject> typeParameters() {
+    return kmTypeAlias.getTypeParameters().stream()
+        .map(kmTypeParameter -> new FoundKmTypeParameterSubject(codeInspector, kmTypeParameter))
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  public String descriptor(String pkg) {
+    return "L" + getBinaryNameFromJavaType(pkg) + DESCRIPTOR_PACKAGE_SEPARATOR + name() + ";";
+  }
+
+  @Override
+  public KmTypeSubject expandedType() {
+    return new KmTypeSubject(codeInspector, kmTypeAlias.expandedType);
+  }
+
+  @Override
+  public KmTypeSubject underlyingType() {
+    return new KmTypeSubject(codeInspector, kmTypeAlias.underlyingType);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeParameterSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeParameterSubject.java
new file mode 100644
index 0000000..fb5e909
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeParameterSubject.java
@@ -0,0 +1,78 @@
+package com.android.tools.r8.utils.codeinspector;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmTypeParameter;
+import kotlinx.metadata.KmVariance;
+
+public class FoundKmTypeParameterSubject extends KmTypeParameterSubject {
+
+  private final CodeInspector codeInspector;
+  private final KmTypeParameter kmTypeParameter;
+
+  public FoundKmTypeParameterSubject(CodeInspector codeInspector, KmTypeParameter kmTypeParameter) {
+    this.codeInspector = codeInspector;
+    this.kmTypeParameter = kmTypeParameter;
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return false;
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    return false;
+  }
+
+  @Override
+  public int getId() {
+    return kmTypeParameter.getId();
+  }
+
+  @Override
+  public int getFlags() {
+    return kmTypeParameter.getFlags();
+  }
+
+  @Override
+  public KmVariance getVariance() {
+    return kmTypeParameter.getVariance();
+  }
+
+  @Override
+  public List<KmTypeSubject> upperBounds() {
+    return kmTypeParameter.getUpperBounds().stream()
+        .map(kmType -> new KmTypeSubject(codeInspector, kmType))
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (!(obj instanceof FoundKmTypeParameterSubject)) {
+      return false;
+    }
+    KmTypeParameter other = ((FoundKmTypeParameterSubject) obj).kmTypeParameter;
+    if (!kmTypeParameter.getName().equals(other.getName())
+        || kmTypeParameter.getId() != other.getId()
+        || kmTypeParameter.getFlags() != other.getFlags()
+        || kmTypeParameter.getVariance() != other.getVariance()) {
+      return false;
+    }
+    if (kmTypeParameter.getUpperBounds().size() != other.getUpperBounds().size()) {
+      return false;
+    }
+    for (int i = 0; i < kmTypeParameter.getUpperBounds().size(); i++) {
+      if (!KmTypeSubject.areEqual(
+          kmTypeParameter.getUpperBounds().get(i), other.getUpperBounds().get(i))) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
index 3f5be34..753f02a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
@@ -6,7 +6,8 @@
 import com.android.tools.r8.graph.DexClass;
 import java.util.List;
 
-public abstract class KmClassSubject extends Subject implements KmDeclarationContainerSubject {
+public abstract class KmClassSubject extends Subject
+    implements KmDeclarationContainerSubject, KmTypeParameterSubjectMixin {
   public abstract String getName();
 
   public abstract DexClass getDexClass();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassifierSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassifierSubject.java
new file mode 100644
index 0000000..8adb293
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassifierSubject.java
@@ -0,0 +1,47 @@
+// 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.utils.codeinspector;
+
+import kotlinx.metadata.KmClassifier;
+
+public class KmClassifierSubject extends Subject {
+
+  private final KmClassifier classifier;
+
+  public KmClassifierSubject(KmClassifier classifier) {
+    this.classifier = classifier;
+  }
+
+  public boolean isTypeParameter() {
+    return classifier instanceof KmClassifier.TypeParameter;
+  }
+
+  public KmClassifier.TypeParameter asTypeParameter() {
+    return (KmClassifier.TypeParameter) classifier;
+  }
+
+  public boolean isTypeAlias() {
+    return classifier instanceof KmClassifier.TypeAlias;
+  }
+
+  public KmClassifier.TypeAlias asTypeAlias() {
+    return (KmClassifier.TypeAlias) classifier;
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return false;
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    return false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java
index 0893077..24e26da 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java
@@ -29,4 +29,8 @@
   List<KmPropertySubject> getProperties();
 
   List<ClassSubject> getReturnTypesInProperties();
+
+  List<KmTypeAliasSubject> getTypeAliases();
+
+  KmTypeAliasSubject kmTypeAliasWithUniqueName(String name);
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
index 161aa3f..0f9dae1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
@@ -7,7 +7,7 @@
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.jvm.JvmMethodSignature;
 
-public abstract class KmFunctionSubject extends Subject {
+public abstract class KmFunctionSubject extends Subject implements KmTypeParameterSubjectMixin {
   // TODO(b/145824437): This is a dup of KotlinMetadataSynthesizer#isExtension
   static boolean isExtension(KmFunction kmFunction) {
     return kmFunction.getReceiverParameterType() != null;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java
index 826c1f1..dc3f62e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java
@@ -22,4 +22,6 @@
   public abstract JvmMethodSignature getterSignature();
 
   public abstract JvmMethodSignature setterSignature();
+
+  public abstract KmTypeSubject returnType();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
new file mode 100644
index 0000000..f87d368
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
@@ -0,0 +1,20 @@
+// 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.utils.codeinspector;
+
+import java.util.List;
+
+public abstract class KmTypeAliasSubject extends Subject {
+
+  public abstract String name();
+
+  public abstract List<KmTypeParameterSubject> typeParameters();
+
+  public abstract String descriptor(String pkg);
+
+  public abstract KmTypeSubject expandedType();
+
+  public abstract KmTypeSubject underlyingType();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubject.java
new file mode 100644
index 0000000..672ad09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubject.java
@@ -0,0 +1,19 @@
+// 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.utils.codeinspector;
+
+import java.util.List;
+import kotlinx.metadata.KmVariance;
+
+public abstract class KmTypeParameterSubject extends Subject {
+
+  public abstract int getId();
+
+  public abstract int getFlags();
+
+  public abstract KmVariance getVariance();
+
+  public abstract List<KmTypeSubject> upperBounds();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubjectMixin.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubjectMixin.java
new file mode 100644
index 0000000..257c109
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubjectMixin.java
@@ -0,0 +1,34 @@
+// 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.utils.codeinspector;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmTypeParameter;
+
+public interface KmTypeParameterSubjectMixin {
+
+  List<KmTypeParameter> getKmTypeParameters();
+
+  CodeInspector getCodeInspector();
+
+  default List<KmTypeParameterSubject> typeParameters() {
+    CodeInspector codeInspector = getCodeInspector();
+    return getKmTypeParameters().stream()
+        .map(kmTypeParam -> new FoundKmTypeParameterSubject(codeInspector, kmTypeParam))
+        .collect(Collectors.toList());
+  }
+
+  default KmTypeParameterSubject kmTypeParameterWithUniqueName(String name) {
+    FoundKmTypeParameterSubject typeSubject = null;
+    for (KmTypeParameter kmTypeParameter : getKmTypeParameters()) {
+      if (kmTypeParameter.getName().equals(name)) {
+        assert typeSubject == null;
+        typeSubject = new FoundKmTypeParameterSubject(getCodeInspector(), kmTypeParameter);
+      }
+    }
+    return typeSubject != null ? typeSubject : new AbsentKmTypeParameterSubject();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeProjectionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeProjectionSubject.java
index 790ecd2..76f3782 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeProjectionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeProjectionSubject.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import kotlinx.metadata.KmTypeProjection;
+import kotlinx.metadata.KmVariance;
 
 public class KmTypeProjectionSubject extends Subject {
   private final CodeInspector codeInspector;
@@ -19,6 +20,10 @@
     return new KmTypeSubject(codeInspector, kmTypeProjection.getType());
   }
 
+  public KmVariance variance() {
+    return kmTypeProjection.getVariance();
+  }
+
   @Override
   public boolean isPresent() {
     return true;
@@ -33,4 +38,25 @@
   public boolean isSynthetic() {
     throw new Unreachable("Cannot determine if a type argument is synthetic");
   }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (!(obj instanceof KmTypeProjectionSubject)) {
+      return false;
+    }
+    return areEqual(this.kmTypeProjection, ((KmTypeProjectionSubject) obj).kmTypeProjection);
+  }
+
+  public static boolean areEqual(KmTypeProjection one, KmTypeProjection other) {
+    if (one == null && other == null) {
+      return true;
+    }
+    if (one == null || other == null) {
+      return false;
+    }
+    if (one.getVariance() != other.getVariance()) {
+      return false;
+    }
+    return KmTypeSubject.areEqual(one.getType(), other.getType());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
index d671a7d..44a141a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
@@ -10,8 +10,10 @@
 import com.android.tools.r8.utils.Box;
 import java.util.List;
 import java.util.stream.Collectors;
+import kotlinx.metadata.KmAnnotation;
 import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmTypeVisitor;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
 
 public class KmTypeSubject extends Subject {
   private final CodeInspector codeInspector;
@@ -57,6 +59,10 @@
         .collect(Collectors.toList());
   }
 
+  public KmClassifierSubject classifier() {
+    return new KmClassifierSubject(kmType.classifier);
+  }
+
   @Override
   public boolean isPresent() {
     return true;
@@ -72,4 +78,66 @@
   public boolean isSynthetic() {
     throw new Unreachable("Cannot determine if a type is synthetic");
   }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (!(obj instanceof KmTypeSubject)) {
+      return false;
+    }
+    return areEqual(this.kmType, ((KmTypeSubject) obj).kmType);
+  }
+
+  public static boolean areEqual(KmType one, KmType other) {
+    if (one == null && other == null) {
+      return true;
+    }
+    if (one == null || other == null) {
+      return false;
+    }
+    if (one.getFlags() != other.getFlags()) {
+      return false;
+    }
+    if (!one.classifier.toString().equals(other.classifier.toString())) {
+      return false;
+    }
+    if (one.getArguments().size() != other.getArguments().size()) {
+      return false;
+    }
+    for (int i = 0; i < one.getArguments().size(); i++) {
+      if (!KmTypeProjectionSubject.areEqual(
+          one.getArguments().get(i), other.getArguments().get(i))) {
+        return false;
+      }
+    }
+    if (!areEqual(one.getAbbreviatedType(), other.getAbbreviatedType())) {
+      return false;
+    }
+    if (!areEqual(one.getOuterType(), other.getOuterType())) {
+      return false;
+    }
+    // TODO(b/152745540): Add equality for flexibleUpperBoundType.
+    if (JvmExtensionsKt.isRaw(one) != JvmExtensionsKt.isRaw(other)) {
+      return false;
+    }
+    List<KmAnnotation> annotationsOne = JvmExtensionsKt.getAnnotations(one);
+    List<KmAnnotation> annotationsOther = JvmExtensionsKt.getAnnotations(other);
+    if (annotationsOne.size() != annotationsOther.size()) {
+      return false;
+    }
+    for (int i = 0; i < annotationsOne.size(); i++) {
+      KmAnnotation kmAnnotationOne = annotationsOne.get(i);
+      KmAnnotation kmAnnotationOther = annotationsOther.get(i);
+      if (!kmAnnotationOne.getClassName().equals(kmAnnotationOther.getClassName())) {
+        return false;
+      }
+      if (!kmAnnotationOne
+          .getArguments()
+          .keySet()
+          .equals(kmAnnotationOther.getArguments().keySet())) {
+        return false;
+      }
+      assert false : "Not defined how to compare kmAnnotationArguments";
+    }
+    return true;
+  }
 }
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 3bbea86..949903d 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
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
 import com.android.tools.r8.references.MethodReference;
@@ -40,6 +41,10 @@
       type = "@Metadata.KmFunction";
     } else if (subject instanceof KmPropertySubject) {
       type = "@Metadata.KmProperty";
+    } else if (subject instanceof KmTypeParameterSubject) {
+      type = "@Metadata.KmTypeParameter";
+    } else if (subject instanceof KmClassifierSubject) {
+      type = "@Metadata.KmClassifier";
     }
     return type;
   }
@@ -62,6 +67,10 @@
       name = ((KmFunctionSubject) subject).toString();
     } else if (subject instanceof KmPropertySubject) {
       name = ((KmPropertySubject) subject).toString();
+    } else if (subject instanceof KmTypeParameterSubject) {
+      name = ((KmTypeParameterSubject) subject).getId() + "";
+    } else if (subject instanceof KmClassifierSubject) {
+      name = subject.toString();
     }
     return name;
   }
@@ -394,6 +403,31 @@
     };
   }
 
+  public static Matcher<KmTypeSubject> isDexClass(DexClass clazz) {
+    return new TypeSafeMatcher<KmTypeSubject>() {
+      @Override
+      protected boolean matchesSafely(KmTypeSubject item) {
+        String descriptor = item.descriptor();
+        if (descriptor == null) {
+          return false;
+        }
+        return descriptor.equals(clazz.type.toDescriptorString());
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("is class");
+      }
+
+      @Override
+      protected void describeMismatchSafely(KmTypeSubject item, Description mismatchDescription) {
+        mismatchDescription
+            .appendText(item.descriptor())
+            .appendText(" is not " + clazz.type.toDescriptorString());
+      }
+    };
+  }
+
   public static Matcher<RetraceMethodResult> isInlineFrame() {
     return new TypeSafeMatcher<RetraceMethodResult>() {
       @Override
diff --git a/tools/asmifier.py b/tools/asmifier.py
index 094fd71..c2fbdc0 100755
--- a/tools/asmifier.py
+++ b/tools/asmifier.py
@@ -10,13 +10,17 @@
 import sys
 import utils
 
+ASM_VERSION = '7.2'
+ASM_JAR = 'asm-' + ASM_VERSION + '.jar'
+ASM_UTIL_JAR = 'asm-util-' + ASM_VERSION + '.jar'
+
 def run(args, build=True):
   if build:
     gradle.RunGradle(['copyMavenDeps'])
   cmd = []
   cmd.append(jdk.GetJavaExecutable())
-  cp = ":".join([os.path.join(utils.REPO_ROOT, 'build/deps/asm-7.1.jar'),
-                 os.path.join(utils.REPO_ROOT, 'build/deps/asm-util-7.1.jar')])
+  cp = ":".join([os.path.join(utils.REPO_ROOT, 'build/deps/' + ASM_JAR),
+                 os.path.join(utils.REPO_ROOT, 'build/deps/' + ASM_UTIL_JAR)])
   cmd.extend(['-cp', cp])
   cmd.append('org.objectweb.asm.util.ASMifier')
   cmd.extend(args)
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 6bfb5cb..5aa5f97 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -51,6 +51,11 @@
     default=False,
     action='store_true')
   parser.add_argument(
+    '--printtimes',
+    help='Print timing information from r8',
+    default=False,
+    action='store_true')
+  parser.add_argument(
     '--ea',
     help='Enable Java assertions when running the compiler (default disabled)',
     default=False,
@@ -165,6 +170,8 @@
           '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005')
     if args.ea:
       cmd.append('-ea')
+    if args.printtimes:
+      cmd.append('-Dcom.android.tools.r8.printtimes=1')
     cmd.extend(['-cp', '%s:%s' % (wrapper_dir, jar)])
     if compiler == 'd8':
       cmd.append('com.android.tools.r8.D8')
@@ -180,6 +187,8 @@
       cmd.extend(['--classpath', dump.classpath_jar()])
     if compiler != 'd8' and dump.config_file():
       cmd.extend(['--pg-conf', dump.config_file()])
+    if compiler != 'd8':
+      cmd.extend(['--pg-map-output', '%s.map' % out])
     cmd.extend(otherargs)
     utils.PrintCmd(cmd)
     try:
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 11e4c8b..b98be86 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -70,15 +70,15 @@
         'find-xmx-min': 256,
         'find-xmx-max': 450,
         'find-xmx-range': 16,
-        'oom-threshold': 380,
+        'oom-threshold': 335,
     },
     {
         'app': 'youtube',
         'version': '12.22',
-        'find-xmx-min': 800,
-        'find-xmx-max': 1200,
+        'find-xmx-min': 750,
+        'find-xmx-max': 1150,
         'find-xmx-range': 32,
-        'oom-threshold': 1037,
+        'oom-threshold': 950,
         # TODO(b/143431825): Youtube can OOM randomly in memory configurations
         #  that should work.
         'skip-find-xmx-max': True,
diff --git a/tools/retrace_benchmark.py b/tools/retrace_benchmark.py
index 5e2bd3b..21bb738 100755
--- a/tools/retrace_benchmark.py
+++ b/tools/retrace_benchmark.py
@@ -64,8 +64,7 @@
     os.path.join(utils.THIRD_PARTY, 'retrace_benchmark', 'stacktrace.txt')]
   utils.PrintCmd(retrace_args)
   t0 = time.time()
-  subprocess.check_call(
-      retrace_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  subprocess.check_call(retrace_args)
   t1 = time.time()
   if options.print_runtimeraw:
     print('{}(RunTimeRaw): {} ms'
@@ -80,4 +79,3 @@
     utils.check_java_version()
   with utils.TempDir() as temp:
     run_retrace(options, temp)
-