Merge commit '3a4ce52559b6599d93172be4ae2011e6ddd99da7' into dev-release
diff --git a/build.gradle b/build.gradle
index d3b511c..1a9edec 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2347,3 +2347,11 @@
         }
     }
 }
+
+allprojects {
+  tasks.withType(Exec) {
+    doFirst {
+      println commandLine
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 15053ae..74b5d06 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -149,7 +149,7 @@
 
   private static void run(AndroidApp inputApp, InternalOptions options, ExecutorService executor)
       throws IOException {
-    Timing timing = new Timing("D8");
+    Timing timing = Timing.create("D8", options);
     try {
       // Disable global optimizations.
       options.disableGlobalOptimizations();
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 8ce6eed..830f6b6 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -201,7 +201,7 @@
     AndroidApp library =
         AndroidApp.builder().addLibraryFiles(getAndroidJarPath(compilationApiLevel)).build();
     DirectMappedDexApplication dexApplication =
-        new ApplicationReader(library, options, new Timing()).read().toDirect();
+        new ApplicationReader(library, options, Timing.empty()).read().toDirect();
 
     // Collect all the methods that the library desugar configuration adds support for.
     Set<DexClass> classesWithAllMethodsSupported = Sets.newIdentityHashSet();
@@ -285,7 +285,7 @@
     // Build a plain text file with the desugared APIs.
     List<String> desugaredApisSignatures = new ArrayList<>();
 
-    DexApplication.Builder builder = DexApplication.builder(options, new Timing());
+    DexApplication.Builder builder = DexApplication.builder(options, Timing.empty());
     supportedMethods.supportedMethods.forEach(
         (clazz, methods) -> {
           String classBinaryName =
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 87c15ab..50a4a70 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -104,7 +104,7 @@
 
   private static void desugar(
       AndroidApp inputApp, InternalOptions options, ExecutorService executor) throws IOException {
-    Timing timing = new Timing("L8 desugaring");
+    Timing timing = Timing.create("L8 desugaring", options);
     try {
       // Disable global optimizations.
       options.disableGlobalOptimizations();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 77c4895..58e68dc 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.AppliedGraphLens;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -102,7 +101,6 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -148,7 +146,7 @@
     if (options.printMemory) {
       System.gc();
     }
-    this.timing = new Timing("R8", options.printMemory);
+    timing = Timing.create("R8", options);
     options.itemFactory.resetSortedIndices();
   }
 
@@ -316,6 +314,7 @@
                 .run(executorService));
 
         AppView<AppInfoWithLiveness> appViewWithLiveness = runEnqueuer(executorService, appView);
+        application = appViewWithLiveness.appInfo().app();
         assert appView.rootSet().verifyKeptFieldsAreAccessedAndLive(appViewWithLiveness.appInfo());
         assert appView.rootSet().verifyKeptMethodsAreTargetedAndLive(appViewWithLiveness.appInfo());
         assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness.appInfo());
@@ -503,13 +502,10 @@
       appView.setAppServices(appView.appServices().rewrittenWithLens(appView.graphLense()));
 
       timing.begin("Create IR");
-      Map<String, String> additionalRewritePrefix;
-      Set<DexCallSite> desugaredCallSites;
       CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
       try {
         IRConverter converter = new IRConverter(appView, timing, printer, mainDexClasses);
         application = converter.optimize(executorService);
-        desugaredCallSites = converter.getDesugaredCallSites();
       } finally {
         timing.end();
       }
@@ -705,13 +701,12 @@
                 options.reporter, options.getProguardConfiguration().getApplyMappingFile());
         timing.begin("apply-mapping");
         namingLens =
-            new ProguardMapMinifier(appView.withLiveness(), seedMapper, desugaredCallSites)
+            new ProguardMapMinifier(appView.withLiveness(), seedMapper)
                 .run(executorService, timing);
         timing.end();
       } else if (options.isMinifying()) {
         timing.begin("Minification");
-        namingLens =
-            new Minifier(appView.withLiveness(), desugaredCallSites).run(executorService, timing);
+        namingLens = new Minifier(appView.withLiveness()).run(executorService, timing);
         timing.end();
       } else {
         // Rewrite signature annotations for applications that are not minified.
diff --git a/src/main/java/com/android/tools/r8/ReadKeepFile.java b/src/main/java/com/android/tools/r8/ReadKeepFile.java
index c953c8d..37ca30b 100644
--- a/src/main/java/com/android/tools/r8/ReadKeepFile.java
+++ b/src/main/java/com/android/tools/r8/ReadKeepFile.java
@@ -16,7 +16,7 @@
 
   private static final String DEFAULT_KEEP_FILE_NAME = "build/proguard.cfg";
 
-  final Timing timing = new Timing("ReadKeepFile");
+  final Timing timing = Timing.empty();
 
   private void readProguardKeepFile(String fileName) {
     System.out.println("  - reading " + fileName);
diff --git a/src/main/java/com/android/tools/r8/ReadProguardMap.java b/src/main/java/com/android/tools/r8/ReadProguardMap.java
index a05537c..31bc2d8 100644
--- a/src/main/java/com/android/tools/r8/ReadProguardMap.java
+++ b/src/main/java/com/android/tools/r8/ReadProguardMap.java
@@ -16,7 +16,7 @@
 
   private static final String DEFAULT_MAP_FILE_NAME = "third_party/gmscore/v5/proguard.map";
 
-  final Timing timing = new Timing("ReadProguardMap");
+  final Timing timing = Timing.empty();
 
   private void readProguardMapFile(String fileName) {
     try {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index 0ae7685..e124a0b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -28,7 +28,7 @@
 
   @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
-    // Intentionally left empty.
+    builder.addNop();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Const.java b/src/main/java/com/android/tools/r8/code/Const.java
index 1f5c1f1..9026684 100644
--- a/src/main/java/com/android/tools/r8/code/Const.java
+++ b/src/main/java/com/android/tools/r8/code/Const.java
@@ -59,7 +59,7 @@
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
     TypeLatticeElement typeLattice =
-        value == 0 ? TypeLatticeElement.TOP : TypeLatticeElement.SINGLE;
+        value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
     builder.addConst(typeLattice, AA, value);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Const16.java b/src/main/java/com/android/tools/r8/code/Const16.java
index 5bee254..f859228 100644
--- a/src/main/java/com/android/tools/r8/code/Const16.java
+++ b/src/main/java/com/android/tools/r8/code/Const16.java
@@ -53,7 +53,7 @@
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
     TypeLatticeElement typeLattice =
-        value == 0 ? TypeLatticeElement.TOP : TypeLatticeElement.SINGLE;
+        value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
     builder.addConst(typeLattice, AA, value);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Const4.java b/src/main/java/com/android/tools/r8/code/Const4.java
index 4f73b29..a036d57 100644
--- a/src/main/java/com/android/tools/r8/code/Const4.java
+++ b/src/main/java/com/android/tools/r8/code/Const4.java
@@ -59,7 +59,7 @@
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
     TypeLatticeElement typeLattice =
-        value == 0 ? TypeLatticeElement.TOP : TypeLatticeElement.SINGLE;
+        value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
     builder.addConst(typeLattice, A, value);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/ConstHigh16.java b/src/main/java/com/android/tools/r8/code/ConstHigh16.java
index 28d1c73..f3821a6 100644
--- a/src/main/java/com/android/tools/r8/code/ConstHigh16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstHigh16.java
@@ -59,7 +59,7 @@
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
     TypeLatticeElement typeLattice =
-        value == 0 ? TypeLatticeElement.TOP : TypeLatticeElement.SINGLE;
+        value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
     builder.addConst(typeLattice, AA, value);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide.java b/src/main/java/com/android/tools/r8/code/ConstWide.java
index 1820172..31c1089 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide.java
@@ -57,6 +57,6 @@
 
   @Override
   public void buildIR(IRBuilder builder) {
-    builder.addConst(TypeLatticeElement.WIDE, AA, decodedValue());
+    builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide16.java b/src/main/java/com/android/tools/r8/code/ConstWide16.java
index ad7e84f..2f7634d 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide16.java
@@ -57,6 +57,6 @@
 
   @Override
   public void buildIR(IRBuilder builder) {
-    builder.addConst(TypeLatticeElement.WIDE, AA, decodedValue());
+    builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide32.java b/src/main/java/com/android/tools/r8/code/ConstWide32.java
index 0b75213..51ef097 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide32.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide32.java
@@ -57,6 +57,6 @@
 
   @Override
   public void buildIR(IRBuilder builder) {
-    builder.addConst(TypeLatticeElement.WIDE, AA, decodedValue());
+    builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java b/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
index e139b35..8437a35 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
@@ -57,6 +57,6 @@
 
   @Override
   public void buildIR(IRBuilder builder) {
-    builder.addConst(TypeLatticeElement.WIDE, AA, decodedValue());
+    builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index 61def68..c709a31 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -225,6 +225,10 @@
     unset(Constants.ACC_SYNTHETIC);
   }
 
+  public void demoteFromSynthetic() {
+    demote(Constants.ACC_SYNTHETIC);
+  }
+
   public void promoteToFinal() {
     promote(Constants.ACC_FINAL);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 5c0e98c..17e63e6 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -459,10 +459,6 @@
     return getTypeInfo(type).isUnknown();
   }
 
-  public boolean isMarkedAsInterface(DexType type) {
-    return getTypeInfo(type).isInterface();
-  }
-
   public boolean hasSubtypes(DexType type) {
     return !getTypeInfo(type).directSubtypes.isEmpty();
   }
@@ -566,64 +562,6 @@
     }
   }
 
-  // TODO(b/130636783): inconsistent location
-  public DexType computeLeastUpperBoundOfClasses(DexType subtype, DexType other) {
-    if (subtype == other) {
-      return subtype;
-    }
-    DexType objectType = dexItemFactory().objectType;
-    TypeInfo subInfo = getTypeInfo(subtype);
-    TypeInfo superInfo = getTypeInfo(other);
-    // If we have no definition for either class, stop proceeding.
-    if (subInfo.hierarchyLevel == UNKNOWN_LEVEL || superInfo.hierarchyLevel == UNKNOWN_LEVEL) {
-      return objectType;
-    }
-    if (subtype == objectType || other == objectType) {
-      return objectType;
-    }
-    TypeInfo t1;
-    TypeInfo t2;
-    if (superInfo.hierarchyLevel < subInfo.hierarchyLevel) {
-      t1 = superInfo;
-      t2 = subInfo;
-    } else {
-      t1 = subInfo;
-      t2 = superInfo;
-    }
-    // From now on, t2.hierarchyLevel >= t1.hierarchyLevel
-    DexClass dexClass;
-    // Make both of other and this in the same level.
-    while (t2.hierarchyLevel > t1.hierarchyLevel) {
-      dexClass = definitionFor(t2.type);
-      if (dexClass == null || dexClass.superType == null) {
-        return objectType;
-      }
-      t2 = getTypeInfo(dexClass.superType);
-    }
-    // At this point, they are at the same level.
-    // lubType starts from t1, and will move up; t2 starts from itself, and will move up, too.
-    // They move up in their own hierarchy tree, and will repeat the process until they meet.
-    // (It will stop at anytime when either one's definition is not found.)
-    DexType lubType = t1.type;
-    DexType t2Type = t2.type;
-    while (t2Type != lubType) {
-      assert getTypeInfo(t2Type).hierarchyLevel == getTypeInfo(lubType).hierarchyLevel;
-      dexClass = definitionFor(t2Type);
-      if (dexClass == null) {
-        lubType = objectType;
-        break;
-      }
-      t2Type = dexClass.superType;
-      dexClass = definitionFor(lubType);
-      if (dexClass == null) {
-        lubType = objectType;
-        break;
-      }
-      lubType = dexClass.superType;
-    }
-    return lubType;
-  }
-
   public boolean inDifferentHierarchy(DexType type1, DexType type2) {
     return !isSubtype(type1, type2) && !isSubtype(type2, type1);
   }
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 1388051..9c23fd1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -196,16 +196,15 @@
   }
 
   public OptionalBool isInterface(DexType type) {
+    assert type.isClassType();
     // Without whole program information we should not assume anything about any other class than
     // the current holder in a given context.
     if (enableWholeProgramOptimizations()) {
-      assert appInfo().hasSubtyping();
-      if (appInfo().hasSubtyping()) {
-        AppInfoWithSubtyping appInfo = appInfo().withSubtyping();
-        return appInfo.isUnknown(type)
-            ? OptionalBool.unknown()
-            : OptionalBool.of(appInfo.isMarkedAsInterface(type));
+      DexClass clazz = definitionFor(type);
+      if (clazz == null) {
+        return OptionalBool.unknown();
       }
+      return OptionalBool.of(clazz.isInterface());
     }
     return OptionalBool.unknown();
   }
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 5402e3a..cbc8303 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -95,7 +95,7 @@
   private final DexType originalHolder;
 
   private final int maxStack;
-  private final int maxLocals;
+  private int maxLocals;
   public List<CfInstruction> instructions;
   private final List<CfTryCatch> tryCatchRanges;
   private final List<LocalVariableInfo> localVariables;
@@ -127,6 +127,10 @@
     return maxLocals;
   }
 
+  public void setMaxLocals(int newMaxLocals) {
+    maxLocals = newMaxLocals;
+  }
+
   public List<CfTryCatch> getTryCatchRanges() {
     return tryCatchRanges;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 358af04..d86a90c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -186,6 +186,19 @@
     directMethods = newMethods;
   }
 
+  public void removeDirectMethod(DexMethod method) {
+    int index = -1;
+    for (int i = 0; i < directMethods.length; i++) {
+      if (method.match(directMethods[i])) {
+        index = i;
+        break;
+      }
+    }
+    if (index >= 0) {
+      removeDirectMethod(index);
+    }
+  }
+
   public void setDirectMethod(int index, DexEncodedMethod method) {
     cachedClassInitializer = null;
     directMethods[index] = method;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index ff31e35..7b529da 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -47,6 +47,7 @@
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -1237,15 +1238,18 @@
   public void copyMetadata(DexEncodedMethod from) {
     checkIfObsolete();
     setKotlinMemberInfo(from.kotlinMemberInfo);
-    // Record that the current method uses identifier name string if the inlinee did so.
-    if (from.getOptimizationInfo().useIdentifierNameString()) {
-      getMutableOptimizationInfo().markUseIdentifierNameString();
-    }
     if (from.classFileVersion > classFileVersion) {
       upgradeClassFileVersion(from.getClassFileVersion());
     }
   }
 
+  public void copyMetadata(DexEncodedMethod from, OptimizationFeedback feedback) {
+    if (from.getOptimizationInfo().useIdentifierNameString()) {
+      feedback.markUseIdentifierNameString(this);
+    }
+    copyMetadata(from);
+  }
+
   private static Builder syntheticBuilder(DexEncodedMethod from) {
     return new Builder(from, true);
   }
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 ab96c0d..193fd91 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -158,6 +158,7 @@
   public final DexString trimName = createString("trim");
 
   public final DexString valueOfMethodName = createString("valueOf");
+  public final DexString valuesMethodName = createString("values");
   public final DexString toStringMethodName = createString("toString");
   public final DexString internMethodName = createString("intern");
 
@@ -481,6 +482,9 @@
           createProto(callSiteType, lookupType, stringType, methodTypeType, objectArrayType),
           createString(METAFACTORY_ALT_METHOD_NAME));
 
+  public final DexMethod deserializeLambdaMethod =
+      createMethod(objectType, deserializeLambdaMethodProto, deserializeLambdaMethodName);
+
   public final DexType stringConcatFactoryType =
       createType("Ljava/lang/invoke/StringConcatFactory;");
 
@@ -561,7 +565,7 @@
           .addAll(primitiveToBoxed.values())
           .build();
 
-  public final Set<DexType> libraryClassesWithoutStaticInitialization =
+  public Set<DexType> libraryClassesWithoutStaticInitialization =
       ImmutableSet.of(boxedBooleanType, enumType, objectType, stringBufferType, stringBuilderType);
 
   private boolean skipNameValidationForTesting = false;
@@ -775,6 +779,7 @@
     public final DexMethod ordinal;
     public final DexMethod name;
     public final DexMethod toString;
+    public final DexMethod compareTo;
 
     public final DexMethod constructor =
         createMethod(enumType, createProto(voidType, stringType, intType), constructorMethodName);
@@ -806,6 +811,29 @@
               toStringMethodName,
               stringDescriptor,
               DexString.EMPTY_ARRAY);
+      compareTo =
+          createMethod(
+              enumDescriptor,
+              compareToMethodName,
+              stringDescriptor,
+              new DexString[] {enumDescriptor});
+    }
+
+    public boolean isValuesMethod(DexMethod method, DexClass enumClass) {
+      assert enumClass.isEnum();
+      return method.holder == enumClass.type
+          && method.proto.returnType == enumClass.type.toArrayType(1, DexItemFactory.this)
+          && method.proto.parameters.size() == 0
+          && method.name == valuesMethodName;
+    }
+
+    public boolean isValueOfMethod(DexMethod method, DexClass enumClass) {
+      assert enumClass.isEnum();
+      return method.holder == enumClass.type
+          && method.proto.returnType == enumClass.type
+          && method.proto.parameters.size() == 1
+          && method.proto.parameters.values[0] == stringType
+          && method.name == valueOfMethodName;
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index ca16abe..652228c 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.Unreachable;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.function.BooleanSupplier;
@@ -80,7 +79,7 @@
 
   @Override
   public int getAsKotlinFlags() {
-    throw new Unreachable("Kotlin property is not directly mapped to JVM field.");
+    return super.getAsKotlinFlags();
   }
 
   public boolean isVolatile() {
diff --git a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
index 024c0c9..c632f75 100644
--- a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
@@ -26,9 +26,8 @@
   public static String smali(AndroidApp application, InternalOptions options) {
     ByteArrayOutputStream os = new ByteArrayOutputStream();
     try (PrintStream ps = new PrintStream(os)) {
-      DexApplication dexApplication = new ApplicationReader(application, options,
-          new Timing("SmaliWriter"))
-          .read();
+      DexApplication dexApplication =
+          new ApplicationReader(application, options, Timing.empty()).read();
       SmaliWriter writer = new SmaliWriter(dexApplication, options);
       writer.write(ps);
     } catch (IOException | ExecutionException e) {
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 1d0b4fb..fb41239 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -99,8 +99,10 @@
     boolean isLambdaMetaFactory =
         factory.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
 
-    registerMethodHandle(
-        callSite.bootstrapMethod, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
+    if (!isLambdaMetaFactory) {
+      registerMethodHandle(
+          callSite.bootstrapMethod, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
+    }
 
     // Lambda metafactory will use this type as the main SAM
     // interface for the dynamically created lambda class.
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
index e7c6418..7df903b 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import java.util.IdentityHashMap;
 import java.util.Map;
 
@@ -78,8 +79,8 @@
         mapping.getOrDefault(key, guaranteedToBeInitialized);
     mapping.put(
         key,
-        appInfo.computeLeastUpperBoundOfClasses(
-            guaranteedToBeInitialized, existingGuaranteedToBeInitialized));
+        ClassTypeLatticeElement.computeLeastUpperBoundOfClasses(
+            appInfo, guaranteedToBeInitialized, existingGuaranteedToBeInitialized));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index e9060a4..35bbf2f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DequeUtils;
@@ -68,7 +69,11 @@
   }
 
   public static void run(
-      AppView<?> appView, IRCode code, OptimizationFeedback feedback, DexEncodedMethod method) {
+      AppView<?> appView,
+      IRCode code,
+      ClassInitializerDefaultsResult classInitializerDefaultsResult,
+      OptimizationFeedback feedback,
+      DexEncodedMethod method) {
     if (!appView.enableWholeProgramOptimizations()) {
       return;
     }
@@ -91,7 +96,7 @@
     }
 
     new FieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method)
-        .computeFieldOptimizationInfo();
+        .computeFieldOptimizationInfo(classInitializerDefaultsResult);
   }
 
   private Map<BasicBlock, AbstractFieldSet> getOrCreateFieldsMaybeReadBeforeBlockInclusive() {
@@ -102,7 +107,8 @@
   }
 
   /** This method analyzes initializers with the purpose of computing field optimization info. */
-  private void computeFieldOptimizationInfo() {
+  private void computeFieldOptimizationInfo(
+      ClassInitializerDefaultsResult classInitializerDefaultsResult) {
     AppInfoWithLiveness appInfo = appView.appInfo();
     DominatorTree dominatorTree = null;
 
@@ -112,20 +118,21 @@
     // guaranteed to be assigned only in the current initializer.
     boolean isStraightLineCode = true;
     Map<DexEncodedField, LinkedList<FieldInstruction>> putsPerField = new IdentityHashMap<>();
-    for (Instruction instruction : code.instructions()) {
-      if (instruction.isFieldPut()) {
-        FieldInstruction fieldPut = instruction.asFieldInstruction();
-        DexField field = fieldPut.getField();
-        DexEncodedField encodedField = appInfo.resolveField(field);
-        if (encodedField != null
-            && encodedField.field.holder == context
-            && appInfo.isFieldOnlyWrittenInMethod(encodedField, method)) {
-          putsPerField.computeIfAbsent(encodedField, ignore -> new LinkedList<>()).add(fieldPut);
-        }
+    for (BasicBlock block : code.blocks) {
+      if (block.getSuccessors().size() >= 2) {
+        isStraightLineCode = false;
       }
-      if (instruction.isJumpInstruction()) {
-        if (!instruction.isGoto() && !instruction.isReturn()) {
-          isStraightLineCode = false;
+      for (Instruction instruction : block.getInstructions()) {
+        if (instruction.isFieldPut()) {
+          FieldInstruction fieldPut = instruction.asFieldInstruction();
+          DexField field = fieldPut.getField();
+          DexEncodedField encodedField = appInfo.resolveField(field);
+          if (encodedField != null
+              && encodedField.field.holder == context
+              && encodedField.isStatic() == method.isStatic()
+              && appInfo.isFieldOnlyWrittenInMethod(encodedField, method)) {
+            putsPerField.computeIfAbsent(encodedField, ignore -> new LinkedList<>()).add(fieldPut);
+          }
         }
       }
     }
@@ -146,7 +153,9 @@
           continue;
         }
       }
-      if (fieldMaybeReadBeforeInstruction(encodedField, fieldPut)) {
+      boolean priorReadsWillReadSameValue =
+          !classInitializerDefaultsResult.hasStaticValue(encodedField) && fieldPut.value().isZero();
+      if (!priorReadsWillReadSameValue && fieldMaybeReadBeforeInstruction(encodedField, fieldPut)) {
         continue;
       }
       updateFieldOptimizationInfo(encodedField, fieldPut.value());
@@ -239,20 +248,24 @@
 
       if (!blockOrPredecessorMaybeReadAnyField) {
         // Finally, we update the read set with the fields that are read by the instructions in the
-        // current block.
-        for (Instruction instruction : block.getInstructions()) {
-          AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
-          if (instructionReadSet.isBottom()) {
-            continue;
+        // current block. This can be skipped if the block has already been processed.
+        if (seenBefore) {
+          assert verifyFieldSetContainsAllFieldReadsInBlock(knownReadSet, block, context);
+        } else {
+          for (Instruction instruction : block.getInstructions()) {
+            AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
+            if (instructionReadSet.isBottom()) {
+              continue;
+            }
+            if (instructionReadSet.isTop()) {
+              blockOrPredecessorMaybeReadAnyField = true;
+              break;
+            }
+            if (!knownReadSet.isConcreteFieldSet()) {
+              knownReadSet = new ConcreteMutableFieldSet();
+            }
+            knownReadSet.asConcreteFieldSet().addAll(instructionReadSet.asConcreteFieldSet());
           }
-          if (instructionReadSet.isTop()) {
-            blockOrPredecessorMaybeReadAnyField = true;
-            break;
-          }
-          if (!knownReadSet.isConcreteFieldSet()) {
-            knownReadSet = new ConcreteMutableFieldSet();
-          }
-          knownReadSet.asConcreteFieldSet().addAll(instructionReadSet.asConcreteFieldSet());
         }
       }
 
@@ -279,6 +292,21 @@
     return result;
   }
 
+  private boolean verifyFieldSetContainsAllFieldReadsInBlock(
+      KnownFieldSet readSet, BasicBlock block, DexType context) {
+    for (Instruction instruction : block.getInstructions()) {
+      AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
+      assert !instructionReadSet.isTop();
+      if (instructionReadSet.isBottom()) {
+        continue;
+      }
+      for (DexEncodedField field : instructionReadSet.asConcreteFieldSet().getFields()) {
+        assert readSet.contains(field);
+      }
+    }
+    return true;
+  }
+
   private void updateFieldOptimizationInfo(DexEncodedField field, Value value) {
     // Abstract value.
     Value root = value.getAliasedValue();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/KnownFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/KnownFieldSet.java
index b96918e..263101b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/KnownFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/KnownFieldSet.java
@@ -4,8 +4,12 @@
 
 package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
 
+import com.android.tools.r8.graph.DexEncodedField;
+
 public interface KnownFieldSet {
 
+  boolean contains(DexEncodedField field);
+
   default boolean isConcreteFieldSet() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 31b6588..cc7307f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -163,7 +163,6 @@
         new NewArrayEmpty(newObjectsValue, sizeValue, appView.dexItemFactory().objectArrayType));
 
     // Populate the `objects` array.
-    boolean hasIntroducedIdentifierNameString = false;
     for (int i = 0; i < objects.size(); i++) {
       Value indexValue = instructionIterator.insertConstIntInstruction(code, appView.options(), i);
       Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
@@ -171,20 +170,12 @@
       instructionIterator.add(
           new ArrayPut(
               MemberType.OBJECT, newObjectsValue, indexValue, materializingInstruction.outValue()));
-
-      if (materializingInstruction.isDexItemBasedConstString()) {
-        hasIntroducedIdentifierNameString = true;
-      }
     }
 
     // Pass the newly created `objects` array to RawMessageInfo.<init>(...) or
     // GeneratedMessageLite.newMessageInfo().
     setObjectsValueForMessageInfoConstructionInvoke(
         newMessageInfoInvoke, newObjectsValue, references);
-
-    if (hasIntroducedIdentifierNameString) {
-      method.getMutableOptimizationInfo().markUseIdentifierNameString();
-    }
   }
 
   public static InvokeMethod getNewMessageInfoInvoke(IRCode code, ProtoReferences references) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
index aacb2a0..014ab85 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
@@ -16,7 +16,9 @@
 
 public class ProtoReferences {
 
+  public final DexType enumLiteMapType;
   public final DexType extendableMessageType;
+  public final DexType extensionDescriptorType;
   public final DexType extensionRegistryLiteType;
   public final DexType generatedExtensionType;
   public final DexType generatedMessageLiteType;
@@ -25,7 +27,9 @@
   public final DexType rawMessageInfoType;
   public final DexType messageLiteType;
   public final DexType methodToInvokeType;
+  public final DexType wireFormatFieldType;
 
+  public final GeneratedExtensionMethods generatedExtensionMethods;
   public final GeneratedMessageLiteMethods generatedMessageLiteMethods;
   public final GeneratedMessageLiteBuilderMethods generatedMessageLiteBuilderMethods;
   public final GeneratedMessageLiteExtendableBuilderMethods
@@ -44,28 +48,24 @@
 
   public ProtoReferences(DexItemFactory factory) {
     // Types.
+    enumLiteMapType = factory.createType("Lcom/google/protobuf/Internal$EnumLiteMap;");
     extendableMessageType =
-        factory.createType(
-            factory.createString("Lcom/google/protobuf/GeneratedMessageLite$ExtendableMessage;"));
-    extensionRegistryLiteType =
-        factory.createType(factory.createString("Lcom/google/protobuf/ExtensionRegistryLite;"));
+        factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtendableMessage;");
+    extensionDescriptorType =
+        factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtensionDescriptor;");
+    extensionRegistryLiteType = factory.createType("Lcom/google/protobuf/ExtensionRegistryLite;");
     generatedExtensionType =
-        factory.createType(
-            factory.createString("Lcom/google/protobuf/GeneratedMessageLite$GeneratedExtension;"));
-    generatedMessageLiteType =
-        factory.createType(factory.createString("Lcom/google/protobuf/GeneratedMessageLite;"));
+        factory.createType("Lcom/google/protobuf/GeneratedMessageLite$GeneratedExtension;");
+    generatedMessageLiteType = factory.createType("Lcom/google/protobuf/GeneratedMessageLite;");
     generatedMessageLiteBuilderType =
-        factory.createType(
-            factory.createString("Lcom/google/protobuf/GeneratedMessageLite$Builder;"));
+        factory.createType("Lcom/google/protobuf/GeneratedMessageLite$Builder;");
     generatedMessageLiteExtendableBuilderType =
-        factory.createType(
-            factory.createString("Lcom/google/protobuf/GeneratedMessageLite$ExtendableBuilder;"));
-    rawMessageInfoType =
-        factory.createType(factory.createString("Lcom/google/protobuf/RawMessageInfo;"));
-    messageLiteType = factory.createType(factory.createString("Lcom/google/protobuf/MessageLite;"));
+        factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtendableBuilder;");
+    rawMessageInfoType = factory.createType("Lcom/google/protobuf/RawMessageInfo;");
+    messageLiteType = factory.createType("Lcom/google/protobuf/MessageLite;");
     methodToInvokeType =
-        factory.createType(
-            factory.createString("Lcom/google/protobuf/GeneratedMessageLite$MethodToInvoke;"));
+        factory.createType("Lcom/google/protobuf/GeneratedMessageLite$MethodToInvoke;");
+    wireFormatFieldType = factory.createType("Lcom/google/protobuf/WireFormat$FieldType;");
 
     // Names.
     dynamicMethodName = factory.createString("dynamicMethod");
@@ -93,6 +93,7 @@
                 factory.voidType, messageLiteType, factory.stringType, factory.objectArrayType),
             factory.constructorMethodName);
 
+    generatedExtensionMethods = new GeneratedExtensionMethods(factory);
     generatedMessageLiteMethods = new GeneratedMessageLiteMethods(factory);
     generatedMessageLiteBuilderMethods = new GeneratedMessageLiteBuilderMethods(factory);
     generatedMessageLiteExtendableBuilderMethods =
@@ -123,7 +124,8 @@
 
   public boolean isFindLiteExtensionByNumberMethod(DexMethod method) {
     return method.proto == findLiteExtensionByNumberProto
-        && method.name.startsWith(findLiteExtensionByNumberName);
+        && method.name.startsWith(findLiteExtensionByNumberName)
+        && method.holder != extensionRegistryLiteType;
   }
 
   public boolean isGeneratedMessageLiteBuilder(DexProgramClass clazz) {
@@ -136,11 +138,47 @@
     return method.match(newMessageInfoMethod) || method == rawMessageInfoConstructor;
   }
 
-  class GeneratedMessageLiteMethods {
+  public class GeneratedExtensionMethods {
+
+    public final DexMethod constructor;
+    public final DexMethod constructorWithClass;
+
+    private GeneratedExtensionMethods(DexItemFactory dexItemFactory) {
+      constructor =
+          dexItemFactory.createMethod(
+              generatedExtensionType,
+              dexItemFactory.createProto(
+                  dexItemFactory.voidType,
+                  messageLiteType,
+                  dexItemFactory.objectType,
+                  messageLiteType,
+                  extensionDescriptorType),
+              dexItemFactory.constructorMethodName);
+      constructorWithClass =
+          dexItemFactory.createMethod(
+              generatedExtensionType,
+              dexItemFactory.createProto(
+                  dexItemFactory.voidType,
+                  messageLiteType,
+                  dexItemFactory.objectType,
+                  messageLiteType,
+                  extensionDescriptorType,
+                  dexItemFactory.classType),
+              dexItemFactory.constructorMethodName);
+    }
+
+    public boolean isConstructor(DexMethod method) {
+      return method == constructor || method == constructorWithClass;
+    }
+  }
+
+  public class GeneratedMessageLiteMethods {
 
     public final DexMethod createBuilderMethod;
     public final DexMethod dynamicMethodBridgeMethod;
     public final DexMethod isInitializedMethod;
+    public final DexMethod newRepeatedGeneratedExtension;
+    public final DexMethod newSingularGeneratedExtension;
 
     private GeneratedMessageLiteMethods(DexItemFactory dexItemFactory) {
       createBuilderMethod =
@@ -158,6 +196,32 @@
               generatedMessageLiteType,
               dexItemFactory.createProto(dexItemFactory.booleanType),
               "isInitialized");
+      newRepeatedGeneratedExtension =
+          dexItemFactory.createMethod(
+              generatedMessageLiteType,
+              dexItemFactory.createProto(
+                  generatedExtensionType,
+                  messageLiteType,
+                  messageLiteType,
+                  enumLiteMapType,
+                  dexItemFactory.intType,
+                  wireFormatFieldType,
+                  dexItemFactory.booleanType,
+                  dexItemFactory.classType),
+              "newRepeatedGeneratedExtension");
+      newSingularGeneratedExtension =
+          dexItemFactory.createMethod(
+              generatedMessageLiteType,
+              dexItemFactory.createProto(
+                  generatedExtensionType,
+                  messageLiteType,
+                  dexItemFactory.objectType,
+                  messageLiteType,
+                  enumLiteMapType,
+                  dexItemFactory.intType,
+                  wireFormatFieldType,
+                  dexItemFactory.classType),
+              "newSingularGeneratedExtension");
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index 92954e4..820789a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -4,11 +4,14 @@
 
 package com.android.tools.r8.ir.analysis.proto.schema;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
+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.analysis.EnqueuerAnalysis;
@@ -17,14 +20,22 @@
 import com.android.tools.r8.ir.analysis.proto.ProtoReferences;
 import com.android.tools.r8.ir.analysis.proto.ProtoShrinker;
 import com.android.tools.r8.ir.analysis.proto.RawMessageInfoDecoder;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRCodeUtils;
+import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
+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.FieldOptimizationInfo;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
 import com.android.tools.r8.shaking.KeepReason;
 import com.android.tools.r8.utils.BitUtils;
 import com.android.tools.r8.utils.OptionalBool;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -36,10 +47,6 @@
 //  implementation. The current caching mechanism is unsafe, because we may mark a message as not
 //  containing a map/required field in presence of cycles, although it does.
 
-// TODO(b/112437944): Handle extensions in the map/required field detection + add a test that fails
-//  with the current implementation. If there is a field whose type is an extension, then we should
-//  look if any of the applicable extensions could contain a map/required field.
-
 // TODO(b/112437944): Handle incomplete information about extensions + add a test that fails with
 //  the current implementation. If there are some extensions that cannot be resolved, then we should
 //  keep fields that could reach extensions to be conservative.
@@ -68,6 +75,12 @@
   private final Set<DexEncodedMethod> dynamicMethodsWithTracedProtoObjects =
       Sets.newIdentityHashSet();
 
+  // The findLiteExtensionByNumber() methods that have become live since the last fixpoint.
+  private final Set<DexEncodedMethod> findLiteExtensionByNumberMethods = Sets.newIdentityHashSet();
+
+  // Mapping from extension container types to the extensions for that type.
+  private final Map<DexType, Set<DexType>> extensionGraph = new IdentityHashMap<>();
+
   public ProtoEnqueuerExtension(AppView<?> appView) {
     ProtoShrinker protoShrinker = appView.protoShrinker();
     this.appView = appView;
@@ -82,6 +95,11 @@
    */
   @Override
   public void processNewlyLiveMethod(DexEncodedMethod encodedMethod) {
+    if (references.isFindLiteExtensionByNumberMethod(encodedMethod.method)) {
+      findLiteExtensionByNumberMethods.add(encodedMethod);
+      return;
+    }
+
     if (!references.isDynamicMethod(encodedMethod)) {
       return;
     }
@@ -124,6 +142,8 @@
 
   @Override
   public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist) {
+    populateExtensionGraph(enqueuer);
+
     markMapOrRequiredFieldsAsReachable(enqueuer, worklist);
 
     // The ProtoEnqueuerUseRegistry does not trace the const-class instructions in dynamicMethod().
@@ -139,6 +159,166 @@
     }
   }
 
+  /**
+   * For each extension field referenced from any of the methods in {@link
+   * #findLiteExtensionByNumberMethods}, this method finds the definition of the field, and then
+   * adds an edge to the proto extension graph {@link #extensionGraph} based on the definition of
+   * the field.
+   *
+   * <p>Example: If the field is defined as below, then an edge is added from the container type
+   * MyProtoMessage (argument #0) to the extension type MyProtoMessage$Ext (argument #2).
+   *
+   * <pre>
+   *   GeneratedMessageLite.newSingularGeneratedExtension(
+   *       MyProtoMessage.getDefaultInstance(),
+   *       MyProtoMessage.Ext.getDefaultInstance(),
+   *       MyProtoMessage.Ext.getDefaultInstance(),
+   *       null,
+   *       10,
+   *       WireFormat.FieldType.MESSAGE,
+   *       MyProtoMessage.Ext.class);
+   * </pre>
+   *
+   * In addition to {@code newSingularGeneratedExtension()} we also model {@code
+   * newRepeatedGeneratedExtension()}. Both of these methods forward to {@code
+   * GeneratedExtension.<init>()}, hence we also model this constructor for robustness against
+   * inlining.
+   */
+  private void populateExtensionGraph(Enqueuer enqueuer) {
+    collectExtensionFields()
+        .forEach(
+            (clazz, extensionFields) -> {
+              DexEncodedMethod clinit = clazz.getClassInitializer();
+              if (clinit == null) {
+                assert false; // Should generally not happen.
+                return;
+              }
+
+              IRCode code = clinit.buildIR(appView, appView.appInfo().originFor(clazz.type));
+              Map<DexEncodedField, StaticPut> uniqueStaticPuts =
+                  IRCodeUtils.findUniqueStaticPuts(appView, code, extensionFields);
+              for (DexEncodedField extensionField : extensionFields) {
+                StaticPut staticPut = uniqueStaticPuts.get(extensionField);
+                if (staticPut == null) {
+                  // Could happen after we have optimized the code.
+                  assert enqueuer.getMode().isFinalTreeShaking();
+                  continue;
+                }
+                populateExtensionGraphWithExtensionFieldDefinition(staticPut);
+              }
+            });
+
+    // Clear the set of methods such that we don't re-analyze these methods upon the next fixpoint.
+    findLiteExtensionByNumberMethods.clear();
+  }
+
+  /**
+   * Finds the extension fields referenced in the methods in {@link
+   * #findLiteExtensionByNumberMethods}.
+   */
+  private Map<DexProgramClass, Set<DexEncodedField>> collectExtensionFields() {
+    Map<DexProgramClass, Set<DexEncodedField>> extensionFieldsByClass = new IdentityHashMap<>();
+    for (DexEncodedMethod findLiteExtensionByNumberMethod : findLiteExtensionByNumberMethods) {
+      IRCode code =
+          findLiteExtensionByNumberMethod.buildIR(
+              appView, appView.appInfo().originFor(findLiteExtensionByNumberMethod.method.holder));
+      for (BasicBlock block : code.blocks(BasicBlock::isReturnBlock)) {
+        Value returnValue = block.exit().asReturn().returnValue().getAliasedValue();
+        if (returnValue.isPhi()) {
+          assert false;
+          continue;
+        }
+
+        if (returnValue.isZero()) {
+          continue; // OK.
+        }
+
+        Instruction definition = returnValue.definition;
+        if (definition.isStaticGet()) {
+          StaticGet staticGet = definition.asStaticGet();
+          DexEncodedField field = appView.appInfo().resolveField(staticGet.getField());
+          if (field == null) {
+            assert false;
+            continue;
+          }
+
+          DexProgramClass holder = asProgramClassOrNull(appView.definitionFor(field.field.holder));
+          if (holder == null) {
+            assert false;
+            continue;
+          }
+
+          extensionFieldsByClass
+              .computeIfAbsent(holder, ignore -> Sets.newIdentityHashSet())
+              .add(field);
+          continue;
+        }
+
+        assert definition.isInvokeMethodWithReceiver()
+            && references.isFindLiteExtensionByNumberMethod(
+                definition.asInvokeMethodWithReceiver().getInvokedMethod());
+      }
+    }
+    return extensionFieldsByClass;
+  }
+
+  /**
+   * Updates {@link #extensionGraph} based on the definition of {@param staticPut}.
+   *
+   * <p>See also {@link #populateExtensionGraph}.
+   */
+  private void populateExtensionGraphWithExtensionFieldDefinition(StaticPut staticPut) {
+    Value value = staticPut.value().getAliasedValue();
+    if (value.isPhi()) {
+      return;
+    }
+
+    Instruction extensionFactory = value.definition;
+    if (extensionFactory.isNewInstance()) {
+      extensionFactory =
+          extensionFactory.asNewInstance().getUniqueConstructorInvoke(appView.dexItemFactory());
+      if (extensionFactory == null) {
+        assert false;
+        return;
+      }
+    }
+
+    if (extensionFactory.isInvokeDirect() || extensionFactory.isInvokeStatic()) {
+      InvokeMethod invoke = extensionFactory.asInvokeMethod();
+      DexMethod invokedMethod = invoke.getInvokedMethod();
+
+      TypeLatticeElement containerType, extensionType;
+      if (invokedMethod == references.generatedMessageLiteMethods.newRepeatedGeneratedExtension) {
+        containerType = invoke.arguments().get(0).getTypeLattice();
+        extensionType = invoke.arguments().get(1).getTypeLattice();
+      } else if (invokedMethod
+          == references.generatedMessageLiteMethods.newSingularGeneratedExtension) {
+        containerType = invoke.arguments().get(0).getTypeLattice();
+        extensionType = invoke.arguments().get(2).getTypeLattice();
+      } else if (references.generatedExtensionMethods.isConstructor(invokedMethod)) {
+        containerType = invoke.arguments().get(1).getTypeLattice();
+        extensionType = invoke.arguments().get(3).getTypeLattice();
+      } else {
+        return;
+      }
+
+      if (extensionType.isNullType()) {
+        return; // Extension is a primitive type.
+      }
+
+      if (!containerType.isClassType() || !extensionType.isClassType()) {
+        assert false; // Should generally not happen.
+        return;
+      }
+
+      extensionGraph
+          .computeIfAbsent(
+              containerType.asClassTypeLatticeElement().getClassType(),
+              ignore -> Sets.newIdentityHashSet())
+          .add(extensionType.asClassTypeLatticeElement().getClassType());
+    }
+  }
+
   private void markMapOrRequiredFieldsAsReachable(Enqueuer enqueuer, EnqueuerWorklist worklist) {
     // TODO(b/112437944): We only need to check if a given field can reach a map/required field
     //  once. Maybe maintain a map `newlyLiveProtos` that store the set of proto messages that have
@@ -388,9 +568,10 @@
    * @return true if this proto message contains a map/required field directly or indirectly.
    */
   private boolean reachesMapOrRequiredField(ProtoMessageInfo protoMessageInfo) {
-    if (!protoMessageInfo.hasFields()) {
+    if (!protoMessageInfo.hasFields() && !extensionGraph.containsKey(protoMessageInfo.getType())) {
       return false;
     }
+
     OptionalBool cache =
         reachesMapOrRequiredFieldFromMessageCache.getOrDefault(
             protoMessageInfo, OptionalBool.unknown());
@@ -403,8 +584,21 @@
     reachesMapOrRequiredFieldFromMessageCache.put(protoMessageInfo, OptionalBool.of(false));
 
     // Check if any of the fields contains a map/required field.
-    for (ProtoFieldInfo protoFieldInfo : protoMessageInfo.getFields()) {
-      if (reachesMapOrRequiredField(protoFieldInfo)) {
+    if (protoMessageInfo.hasFields()) {
+      for (ProtoFieldInfo protoFieldInfo : protoMessageInfo.getFields()) {
+        if (reachesMapOrRequiredField(protoFieldInfo)) {
+          reachesMapOrRequiredFieldFromMessageCache.put(protoMessageInfo, OptionalBool.of(true));
+          return true;
+        }
+      }
+    }
+
+    Iterable<DexType> extensionTypes =
+        extensionGraph.getOrDefault(protoMessageInfo.getType(), ImmutableSet.of());
+    for (DexType extensionType : extensionTypes) {
+      ProtoMessageInfo protoExtensionMessageInfo = getOrCreateProtoMessageInfo(extensionType);
+      assert protoExtensionMessageInfo != null;
+      if (reachesMapOrRequiredField(protoExtensionMessageInfo)) {
         reachesMapOrRequiredFieldFromMessageCache.put(protoMessageInfo, OptionalBool.of(true));
         return true;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
index 663d9f6..ac88305 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.proto.schema;
 
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.proto.ProtoUtils;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -218,6 +219,10 @@
     return oneOfObjects;
   }
 
+  public DexType getType() {
+    return dynamicMethod.method.holder;
+  }
+
   public boolean hasFields() {
     return fields != null && !fields.isEmpty();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
index e2af564..39ebff8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
@@ -63,7 +63,7 @@
   }
 
   public TypeLatticeElement getArrayMemberTypeAsValueType() {
-    return memberTypeLattice.isFineGrainedType() ? INT : memberTypeLattice;
+    return memberTypeLattice.isFineGrainedType() ? getInt() : memberTypeLattice;
   }
 
   public TypeLatticeElement getArrayBaseTypeLattice() {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
index 3ad41d4..eff9681 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
@@ -8,8 +8,11 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.ImmutableSet;
 import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -203,10 +206,8 @@
           Collections.emptySet());
     }
     DexType lubType =
-        appView
-            .appInfo()
-            .withSubtyping()
-            .computeLeastUpperBoundOfClasses(getClassType(), other.getClassType());
+        computeLeastUpperBoundOfClasses(
+            appView.appInfo().withSubtyping(), getClassType(), other.getClassType());
     Set<DexType> c1lubItfs = getInterfaces();
     Set<DexType> c2lubItfs = other.getInterfaces();
     Set<DexType> lubItfs = null;
@@ -234,7 +235,50 @@
     }
   }
 
-  // TODO(b/130636783): inconsistent location
+  public static DexType computeLeastUpperBoundOfClasses(
+      AppInfoWithSubtyping appInfo, DexType type1, DexType type2) {
+    // Compiling R8 with R8, this hits more than 1/3 of cases.
+    if (type1 == type2) {
+      return type1;
+    }
+    // Compiling R8 with R8, this hits more than 1/3 of cases.
+    DexType objectType = appInfo.dexItemFactory().objectType;
+    if (type1 == objectType || type2 == objectType) {
+      return objectType;
+    }
+    // Compiling R8 with R8, there are no hierarchies above height 10.
+    // The overhead of a hash map likely outweighs the speed of scanning an array.
+    Collection<DexType> types = new ArrayList<>(10);
+    DexType type = type1;
+    while (true) {
+      if (type == type2) {
+        return type;
+      }
+      types.add(type);
+      DexClass clazz = appInfo.definitionFor(type);
+      if (clazz == null || clazz.superType == null || clazz.superType == objectType) {
+        break;
+      }
+      type = clazz.superType;
+    }
+    // In pathological cases, realloc to a set if large.
+    if (types.size() > 20) {
+      types = SetUtils.newIdentityHashSet(types);
+    }
+    type = type2;
+    while (true) {
+      if (types.contains(type)) {
+        return type;
+      }
+      DexClass clazz = appInfo.definitionFor(type);
+      if (clazz == null || clazz.superType == null || clazz.superType == objectType) {
+        break;
+      }
+      type = clazz.superType;
+    }
+    return objectType;
+  }
+
   public static Set<DexType> computeLeastUpperBoundOfInterfaces(
       AppView<? extends AppInfoWithSubtyping> appView, Set<DexType> s1, Set<DexType> s2) {
     if (s1.isEmpty() || s2.isEmpty()) {
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 10f2d3e..3af208a 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
@@ -40,7 +40,7 @@
     Deque<Phi> worklist = new ArrayDeque<>(affectedPhis);
     while (!worklist.isEmpty()) {
       Phi phi = worklist.poll();
-      phi.setTypeLattice(TypeLatticeElement.BOTTOM);
+      phi.setTypeLattice(TypeLatticeElement.getBottom());
       for (Phi affectedPhi : phi.uniquePhiUsers()) {
         if (affectedPhis.add(affectedPhi)) {
           worklist.add(affectedPhi);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
index f43ebab..6a16fdc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
@@ -78,32 +78,32 @@
     switch (descriptor) {
       case 'Z':
         if (asArrayElementType) {
-          return TypeLatticeElement.BOOLEAN;
+          return TypeLatticeElement.getBoolean();
         }
         // fall through
       case 'B':
         if (asArrayElementType) {
-          return TypeLatticeElement.BYTE;
+          return TypeLatticeElement.getByte();
         }
         // fall through
       case 'S':
         if (asArrayElementType) {
-          return TypeLatticeElement.SHORT;
+          return TypeLatticeElement.getShort();
         }
         // fall through
       case 'C':
         if (asArrayElementType) {
-          return TypeLatticeElement.CHAR;
+          return TypeLatticeElement.getChar();
         }
         // fall through
       case 'I':
-        return TypeLatticeElement.INT;
+        return TypeLatticeElement.getInt();
       case 'F':
-        return TypeLatticeElement.FLOAT;
+        return TypeLatticeElement.getFloat();
       case 'J':
-        return TypeLatticeElement.LONG;
+        return TypeLatticeElement.getLong();
       case 'D':
-        return TypeLatticeElement.DOUBLE;
+        return TypeLatticeElement.getDouble();
       case 'V':
         throw new InternalCompilerError("No value type for void type.");
       default:
@@ -117,13 +117,13 @@
       case CHAR:
       case SHORT:
       case INT:
-        return TypeLatticeElement.INT;
+        return TypeLatticeElement.getInt();
       case FLOAT:
-        return TypeLatticeElement.FLOAT;
+        return TypeLatticeElement.getFloat();
       case LONG:
-        return TypeLatticeElement.LONG;
+        return TypeLatticeElement.getLong();
       case DOUBLE:
-        return TypeLatticeElement.DOUBLE;
+        return TypeLatticeElement.getDouble();
       default:
         throw new Unreachable("Invalid numeric type '" + numericType + "'");
     }
@@ -135,16 +135,16 @@
     }
     if (isSinglePrimitive()) {
       if (other.isSinglePrimitive()) {
-        return TypeLatticeElement.SINGLE;
+        return TypeLatticeElement.getSingle();
       }
       assert other.isWidePrimitive();
-      return TypeLatticeElement.TOP;
+      return TypeLatticeElement.getTop();
     }
     assert isWidePrimitive();
     if (other.isWidePrimitive()) {
-      return TypeLatticeElement.WIDE;
+      return TypeLatticeElement.getWide();
     }
     assert other.isSinglePrimitive();
-    return TypeLatticeElement.TOP;
+    return TypeLatticeElement.getTop();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
index 09a9fad..de7b894 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
@@ -16,22 +16,58 @@
  * The base abstraction of lattice elements for local type analysis.
  */
 public abstract class TypeLatticeElement {
-  public static final BottomTypeLatticeElement BOTTOM = BottomTypeLatticeElement.getInstance();
-  public static final TopTypeLatticeElement TOP = TopTypeLatticeElement.getInstance();
-  public static final BooleanTypeLatticeElement BOOLEAN = BooleanTypeLatticeElement.getInstance();
-  public static final ByteTypeLatticeElement BYTE = ByteTypeLatticeElement.getInstance();
-  static final ShortTypeLatticeElement SHORT = ShortTypeLatticeElement.getInstance();
-  static final CharTypeLatticeElement CHAR = CharTypeLatticeElement.getInstance();
-  public static final IntTypeLatticeElement INT = IntTypeLatticeElement.getInstance();
-  public static final FloatTypeLatticeElement FLOAT = FloatTypeLatticeElement.getInstance();
-  public static final SinglePrimitiveTypeLatticeElement SINGLE =
-      SinglePrimitiveTypeLatticeElement.getInstance();
-  public static final LongTypeLatticeElement LONG = LongTypeLatticeElement.getInstance();
-  public static final DoubleTypeLatticeElement DOUBLE = DoubleTypeLatticeElement.getInstance();
-  public static final WidePrimitiveTypeLatticeElement WIDE =
-      WidePrimitiveTypeLatticeElement.getInstance();
-  public static final ReferenceTypeLatticeElement NULL =
-      ReferenceTypeLatticeElement.getNullTypeLatticeElement();
+
+  public static BottomTypeLatticeElement getBottom() {
+    return BottomTypeLatticeElement.getInstance();
+  }
+
+  public static TopTypeLatticeElement getTop() {
+    return TopTypeLatticeElement.getInstance();
+  }
+
+  public static BooleanTypeLatticeElement getBoolean() {
+    return BooleanTypeLatticeElement.getInstance();
+  }
+
+  public static ByteTypeLatticeElement getByte() {
+    return ByteTypeLatticeElement.getInstance();
+  }
+
+  public static ShortTypeLatticeElement getShort() {
+    return ShortTypeLatticeElement.getInstance();
+  }
+
+  public static CharTypeLatticeElement getChar() {
+    return CharTypeLatticeElement.getInstance();
+  }
+
+  public static IntTypeLatticeElement getInt() {
+    return IntTypeLatticeElement.getInstance();
+  }
+
+  public static FloatTypeLatticeElement getFloat() {
+    return FloatTypeLatticeElement.getInstance();
+  }
+
+  public static SinglePrimitiveTypeLatticeElement getSingle() {
+    return SinglePrimitiveTypeLatticeElement.getInstance();
+  }
+
+  public static LongTypeLatticeElement getLong() {
+    return LongTypeLatticeElement.getInstance();
+  }
+
+  public static DoubleTypeLatticeElement getDouble() {
+    return DoubleTypeLatticeElement.getInstance();
+  }
+
+  public static WidePrimitiveTypeLatticeElement getWide() {
+    return WidePrimitiveTypeLatticeElement.getInstance();
+  }
+
+  public static ReferenceTypeLatticeElement getNull() {
+    return ReferenceTypeLatticeElement.getNullTypeLatticeElement();
+  }
 
   public TypeLatticeElement fixupClassTypeReferences(
       Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
@@ -62,16 +98,16 @@
       return this;
     }
     if (isTop() || other.isTop()) {
-      return TOP;
+      return getTop();
     }
     if (isPrimitive()) {
       return other.isPrimitive()
           ? asPrimitiveTypeLatticeElement().join(other.asPrimitiveTypeLatticeElement())
-          : TOP;
+          : getTop();
     }
     if (other.isPrimitive()) {
       // By the above case, !(isPrimitive())
-      return TOP;
+      return getTop();
     }
     // From now on, this and other are precise reference types, i.e., either ArrayType or ClassType.
     assert isReference() && other.isReference();
@@ -100,7 +136,7 @@
 
   public static TypeLatticeElement join(
       Iterable<TypeLatticeElement> typeLattices, AppView<?> appView) {
-    TypeLatticeElement result = BOTTOM;
+    TypeLatticeElement result = getBottom();
     for (TypeLatticeElement other : typeLattices) {
       result = result.join(other, appView);
     }
@@ -373,7 +409,7 @@
       DexType type, Nullability nullability, AppView<?> appView, boolean asArrayElementType) {
     if (type == DexItemFactory.nullValueType) {
       assert !nullability.isDefinitelyNotNull();
-      return NULL;
+      return getNull();
     }
     if (type.isPrimitiveType()) {
       return PrimitiveTypeLatticeElement.fromDexType(type, asArrayElementType);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index 64401fd..9a43d23 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -66,7 +66,7 @@
     assert !typeLattice.isReference() || value == 0;
     Value returnedValue =
         code.createValue(
-            typeLattice.isReference() ? TypeLatticeElement.NULL : typeLattice, debugLocalInfo);
+            typeLattice.isReference() ? TypeLatticeElement.getNull() : typeLattice, debugLocalInfo);
     return new ConstNumber(returnedValue, value);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
index cc154e9..5d97e99 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
@@ -140,20 +140,20 @@
       ConstNumber newConst;
       if (type == NumericType.INT) {
         int result = foldIntegers(leftConst.getIntValue(), rightConst.getIntValue());
-        Value value = code.createValue(TypeLatticeElement.INT, getLocalInfo());
+        Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
         newConst = new ConstNumber(value, result);
       } else if (type == NumericType.LONG) {
         long result = foldLongs(leftConst.getLongValue(), rightConst.getLongValue());
-        Value value = code.createValue(TypeLatticeElement.LONG, getLocalInfo());
+        Value value = code.createValue(TypeLatticeElement.getLong(), getLocalInfo());
         newConst = new ConstNumber(value, result);
       } else if (type == NumericType.FLOAT) {
         float result = foldFloat(leftConst.getFloatValue(), rightConst.getFloatValue());
-        Value value = code.createValue(TypeLatticeElement.FLOAT, getLocalInfo());
+        Value value = code.createValue(TypeLatticeElement.getFloat(), getLocalInfo());
         newConst = new ConstNumber(value, Float.floatToIntBits(result));
       } else {
         assert type == NumericType.DOUBLE;
         double result = foldDouble(leftConst.getDoubleValue(), rightConst.getDoubleValue());
-        Value value = code.createValue(TypeLatticeElement.DOUBLE, getLocalInfo());
+        Value value = code.createValue(TypeLatticeElement.getDouble(), getLocalInfo());
         newConst = new ConstNumber(value, Double.doubleToLongBits(result));
       }
       return new ConstLatticeElement(newConst);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index d90bf21..0c118fb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -96,11 +96,11 @@
       case BOOLEAN_OR_BYTE:
         ArrayTypeLatticeElement arrayType = array().getTypeLattice().asArrayTypeLatticeElement();
         if (arrayType != null
-            && arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.BOOLEAN) {
+            && arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getBoolean()) {
           instruction = new AgetBoolean(dest, array, index);
         } else {
           assert array().getTypeLattice().isDefinitelyNull()
-              || arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.BYTE;
+              || arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getByte();
           instruction = new AgetByte(dest, array, index);
         }
         break;
@@ -203,9 +203,10 @@
         // the instruction cannot return. For now we return NULL as the type to ensure we have a
         // type consistent witness for the out-value type. We could consider returning bottom in
         // this case as the value is indeed empty, i.e., the instruction will always fail.
-        TypeLatticeElement valueType = arrayTypeLattice == null
-            ? TypeLatticeElement.NULL
-            : arrayTypeLattice.getArrayMemberTypeAsValueType();
+        TypeLatticeElement valueType =
+            arrayTypeLattice == null
+                ? TypeLatticeElement.getNull()
+                : arrayTypeLattice.getArrayMemberTypeAsValueType();
         assert valueType.isReference();
         return valueType;
       case BOOLEAN_OR_BYTE:
@@ -214,19 +215,19 @@
       case INT:
         assert arrayTypeLattice == null
             || arrayTypeLattice.getArrayMemberTypeAsValueType().isInt();
-        return TypeLatticeElement.INT;
+        return TypeLatticeElement.getInt();
       case FLOAT:
         assert arrayTypeLattice == null
             || arrayTypeLattice.getArrayMemberTypeAsValueType().isFloat();
-        return TypeLatticeElement.FLOAT;
+        return TypeLatticeElement.getFloat();
       case LONG:
         assert arrayTypeLattice == null
             || arrayTypeLattice.getArrayMemberTypeAsValueType().isLong();
-        return TypeLatticeElement.LONG;
+        return TypeLatticeElement.getLong();
       case DOUBLE:
         assert arrayTypeLattice == null
             || arrayTypeLattice.getArrayMemberTypeAsValueType().isDouble();
-        return TypeLatticeElement.DOUBLE;
+        return TypeLatticeElement.getDouble();
       case INT_OR_FLOAT:
         assert arrayTypeLattice == null
             || arrayTypeLattice.getArrayMemberTypeAsValueType().isSinglePrimitive();
@@ -268,7 +269,7 @@
   public boolean outTypeKnownToBeBoolean(Set<Phi> seen) {
     return array().getTypeLattice().isArrayType()
         && array().getTypeLattice().asArrayTypeLatticeElement().getArrayMemberTypeAsMemberType()
-        == TypeLatticeElement.BOOLEAN;
+            == TypeLatticeElement.getBoolean();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index f767de8..b2f908c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -131,7 +131,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.INT;
+    return TypeLatticeElement.getInt();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 8917cc0..b8d8526 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -92,11 +92,11 @@
       case BOOLEAN_OR_BYTE:
         ArrayTypeLatticeElement arrayType = array().getTypeLattice().asArrayTypeLatticeElement();
         if (arrayType != null
-            && arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.BOOLEAN) {
+            && arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getBoolean()) {
           instruction = new AputBoolean(value, array, index);
         } else {
           assert array().getTypeLattice().isDefinitelyNull()
-              || arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.BYTE;
+              || arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getByte();
           instruction = new AputByte(value, array, index);
         }
         break;
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 5203cb9..4c3822a 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
@@ -612,6 +612,10 @@
     return instructions.isEmpty();
   }
 
+  public boolean isReturnBlock() {
+    return exit().isReturn();
+  }
+
   public Instruction entry() {
     return instructions.get(0);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 301be93..3cae82a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -719,7 +719,7 @@
             new Phi(
                 code.valueNumberGenerator.next(),
                 newExitBlock,
-                TypeLatticeElement.BOTTOM,
+                TypeLatticeElement.getBottom(),
                 null,
                 RegisterReadType.NORMAL);
         phi.addOperands(operands);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Cmp.java b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
index 97c76ce..acccf31 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Cmp.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
@@ -186,7 +186,7 @@
           result = (int) Math.signum(left - right);
         }
       }
-      Value value = code.createValue(TypeLatticeElement.INT, getLocalInfo());
+      Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
       ConstNumber newConst = new ConstNumber(value, result);
       return new ConstLatticeElement(newConst);
     } else if (leftLattice.isValueRange() && rightLattice.isConst()) {
@@ -214,7 +214,7 @@
       return Bottom.getInstance();
     }
     int result = Integer.signum(Long.compare(leftRange.getMin(), rightRange.getMin()));
-    Value value = code.createValue(TypeLatticeElement.INT, getLocalInfo());
+    Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
     ConstNumber newConst = new ConstNumber(value, result);
     return new ConstLatticeElement(newConst);
   }
@@ -236,7 +236,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.INT;
+    return TypeLatticeElement.getInt();
   }
 
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 3cae1e4..39b6fb8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -99,7 +99,7 @@
     }
     // * IllegalAccessError (not visible from the access context).
     if (!isMemberVisibleFromOriginalContext(
-        appView, context, resolvedField.field.holder, resolvedField.accessFlags)) {
+        appView, context, field.holder, resolvedField.accessFlags)) {
       return AbstractError.specific(appView.dexItemFactory().illegalAccessErrorType);
     }
     // TODO(b/137168535): Without non-null tracking, only locally created receiver is allowed in D8.
@@ -209,6 +209,10 @@
     assert isFieldGet();
     DexEncodedField field = appView.appInfo().resolveField(getField());
     if (field != null) {
+      DexClass holder = appView.definitionFor(field.field.holder);
+      if (holder != null && holder.isLibraryClass() && field.isStatic() && field.isFinal()) {
+        return appView.abstractValueFactory().createSingleFieldValue(field.field);
+      }
       return field.getOptimizationInfo().getAbstractValue();
     }
     return UnknownValue.getInstance();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 9594d10..39c3d39 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -949,6 +949,10 @@
     return true;
   }
 
+  public Iterable<BasicBlock> blocks(Predicate<BasicBlock> predicate) {
+    return () -> IteratorUtils.filter(listIterator(), predicate);
+  }
+
   public Iterable<Instruction> instructions() {
     return this::instructionIterator;
   }
@@ -1046,12 +1050,12 @@
   }
 
   public ConstNumber createDoubleConstant(double value, DebugLocalInfo local) {
-    Value out = createValue(TypeLatticeElement.DOUBLE, local);
+    Value out = createValue(TypeLatticeElement.getDouble(), local);
     return new ConstNumber(out, Double.doubleToLongBits(value));
   }
 
   public ConstNumber createFloatConstant(float value, DebugLocalInfo local) {
-    Value out = createValue(TypeLatticeElement.FLOAT, local);
+    Value out = createValue(TypeLatticeElement.getFloat(), local);
     return new ConstNumber(out, Float.floatToIntBits(value));
   }
 
@@ -1060,12 +1064,12 @@
   }
 
   public ConstNumber createIntConstant(int value, DebugLocalInfo local) {
-    Value out = createValue(TypeLatticeElement.INT, local);
+    Value out = createValue(TypeLatticeElement.getInt(), local);
     return new ConstNumber(out, value);
   }
 
   public ConstNumber createLongConstant(long value, DebugLocalInfo local) {
-    Value out = createValue(TypeLatticeElement.LONG, local);
+    Value out = createValue(TypeLatticeElement.getLong(), local);
     return new ConstNumber(out, value);
   }
 
@@ -1084,12 +1088,12 @@
   }
 
   public ConstNumber createConstNull() {
-    Value out = createValue(TypeLatticeElement.NULL);
+    Value out = createValue(TypeLatticeElement.getNull());
     return new ConstNumber(out, 0);
   }
 
   public ConstNumber createConstNull(DebugLocalInfo local) {
-    Value out = createValue(TypeLatticeElement.NULL, local);
+    Value out = createValue(TypeLatticeElement.getNull(), local);
     return new ConstNumber(out, 0);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
index a394ce5..f6348ac 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
@@ -4,15 +4,40 @@
 
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.utils.DequeUtils;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.Map;
 import java.util.Set;
 
 public class IRCodeUtils {
 
   /**
+   * Finds the single assignment to the fields in {@param fields} in {@param code}. Note that this
+   * does not guarantee that the assignments found dominate all the normal exits.
+   */
+  public static Map<DexEncodedField, StaticPut> findUniqueStaticPuts(
+      AppView<?> appView, IRCode code, Set<DexEncodedField> fields) {
+    Set<DexEncodedField> writtenMoreThanOnce = Sets.newIdentityHashSet();
+    Map<DexEncodedField, StaticPut> uniqueStaticPuts = new IdentityHashMap<>();
+    for (StaticPut staticPut : code.<StaticPut>instructions(Instruction::isStaticPut)) {
+      DexEncodedField field = appView.appInfo().resolveField(staticPut.getField());
+      if (field == null || !fields.contains(field) || writtenMoreThanOnce.contains(field)) {
+        continue;
+      }
+      if (uniqueStaticPuts.put(field, staticPut) != null) {
+        writtenMoreThanOnce.add(field);
+        uniqueStaticPuts.remove(field);
+      }
+    }
+    return uniqueStaticPuts;
+  }
+
+  /**
    * Removes {@param instruction} if it is a {@link NewArrayEmpty} instruction, which only has
    * array-put users. Also removes all instructions that contribute to the computation of the
    * indices and the elements, if they end up being unused, even if the instructions may have side
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index d59316f..de4fa66 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -92,7 +92,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.INT;
+    return TypeLatticeElement.getInt();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
index ad3d344..7f87253 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
@@ -117,7 +117,7 @@
       ConstNumber newConst;
       if (type == NumericType.INT) {
         int result = foldIntegers(leftConst.getIntValue(), rightConst.getIntValue());
-        Value value = code.createValue(TypeLatticeElement.INT, getLocalInfo());
+        Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
         newConst = new ConstNumber(value, result);
       } else {
         assert type == NumericType.LONG;
@@ -129,7 +129,7 @@
           right = rightConst.getLongValue();
         }
         long result = foldLongs(leftConst.getLongValue(), right);
-        Value value = code.createValue(TypeLatticeElement.LONG, getLocalInfo());
+        Value value = code.createValue(TypeLatticeElement.getLong(), getLocalInfo());
         newConst = new ConstNumber(value, result);
       }
       return new ConstLatticeElement(newConst);
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 407742e..aed4b9e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -36,6 +36,27 @@
     this.clazz = clazz;
   }
 
+  public InvokeDirect getUniqueConstructorInvoke(DexItemFactory dexItemFactory) {
+    InvokeDirect result = null;
+    for (Instruction user : outValue().uniqueUsers()) {
+      if (user.isInvokeDirect()) {
+        InvokeDirect invoke = user.asInvokeDirect();
+        if (!dexItemFactory.isConstructor(invoke.getInvokedMethod())) {
+          continue;
+        }
+        if (invoke.getReceiver() != outValue()) {
+          continue;
+        }
+        if (result != null) {
+          // Does not have a unique constructor invoke.
+          return null;
+        }
+        result = invoke;
+      }
+    }
+    return result;
+  }
+
   @Override
   public int opcode() {
     return Opcodes.NEW_INSTANCE;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 16fc789..eecf8c3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -393,7 +393,7 @@
 
   // Type of phi(v1, v2, ..., vn) is the least upper bound of all those n operands.
   public TypeLatticeElement computePhiType(AppView<?> appView) {
-    TypeLatticeElement result = TypeLatticeElement.BOTTOM;
+    TypeLatticeElement result = TypeLatticeElement.getBottom();
     for (Value operand : getOperands()) {
       result = result.join(operand.getTypeLattice(), appView);
     }
@@ -416,7 +416,7 @@
       }
     }
     Set<Value> visitedOperands = Sets.newIdentityHashSet();
-    TypeLatticeElement result = TypeLatticeElement.BOTTOM;
+    TypeLatticeElement result = TypeLatticeElement.getBottom();
     for (Phi phi : reachablePhis) {
       for (Value operand : phi.getOperands()) {
         if (!operand.getAliasedValue().isPhi() && visitedOperands.add(operand)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/StackValues.java b/src/main/java/com/android/tools/r8/ir/code/StackValues.java
index 1ce52f9..528cddd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StackValues.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StackValues.java
@@ -16,7 +16,7 @@
   private final StackValue[] stackValues;
 
   public StackValues(StackValue... stackValues) {
-    super(Value.UNDEFINED_NUMBER, TypeLatticeElement.BOTTOM, null);
+    super(Value.UNDEFINED_NUMBER, TypeLatticeElement.getBottom(), null);
     this.stackValues = stackValues;
     assert stackValues.length >= 2;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index bba9148..8e75d62 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -79,9 +79,9 @@
         if (typeLattice.isTop()) {
           if (definition != null && definition.isConstNumber()) {
             assert definition.asConstNumber().isZero();
-            return TypeLatticeElement.NULL;
+            return TypeLatticeElement.getNull();
           } else {
-            return TypeLatticeElement.BOTTOM;
+            return TypeLatticeElement.getBottom();
           }
         }
         if (typeLattice.isReference()) {
@@ -98,17 +98,17 @@
         break;
       case INT:
         if (typeLattice.isTop() || (typeLattice.isSinglePrimitive() && !typeLattice.isFloat())) {
-          return TypeLatticeElement.INT;
+          return TypeLatticeElement.getInt();
         }
         break;
       case FLOAT:
         if (typeLattice.isTop() || (typeLattice.isSinglePrimitive() && !typeLattice.isInt())) {
-          return TypeLatticeElement.FLOAT;
+          return TypeLatticeElement.getFloat();
         }
         break;
       case INT_OR_FLOAT:
         if (typeLattice.isTop()) {
-          return TypeLatticeElement.SINGLE;
+          return TypeLatticeElement.getSingle();
         }
         if (typeLattice.isSinglePrimitive()) {
           return typeLattice;
@@ -116,12 +116,12 @@
         break;
       case LONG:
         if (typeLattice.isWidePrimitive() && !typeLattice.isDouble()) {
-          return TypeLatticeElement.LONG;
+          return TypeLatticeElement.getLong();
         }
         break;
       case DOUBLE:
         if (typeLattice.isWidePrimitive() && !typeLattice.isLong()) {
-          return TypeLatticeElement.DOUBLE;
+          return TypeLatticeElement.getDouble();
         }
         break;
       case LONG_OR_DOUBLE:
@@ -213,7 +213,7 @@
   public static final int UNDEFINED_NUMBER = -1;
 
   public static final Value UNDEFINED =
-      new Value(UNDEFINED_NUMBER, TypeLatticeElement.BOTTOM, null);
+      new Value(UNDEFINED_NUMBER, TypeLatticeElement.getBottom(), null);
 
   protected final int number;
   public Instruction definition = null;
@@ -232,6 +232,7 @@
   protected TypeLatticeElement typeLattice;
 
   public Value(int number, TypeLatticeElement typeLattice, DebugLocalInfo local) {
+    assert typeLattice != null;
     this.number = number;
     this.debugData = local == null ? null : new DebugData(local);
     this.typeLattice = typeLattice;
@@ -1101,6 +1102,7 @@
    * @param newType The new type lattice element
    */
   public void setTypeLattice(TypeLatticeElement newType) {
+    assert newType != null;
     typeLattice = newType;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueType.java b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
index e3926c5..865c7d4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
@@ -124,13 +124,13 @@
   public PrimitiveTypeLatticeElement toPrimitiveTypeLattice() {
     switch (this) {
       case INT:
-        return TypeLatticeElement.INT;
+        return TypeLatticeElement.getInt();
       case FLOAT:
-        return TypeLatticeElement.FLOAT;
+        return TypeLatticeElement.getFloat();
       case LONG:
-        return TypeLatticeElement.LONG;
+        return TypeLatticeElement.getLong();
       case DOUBLE:
-        return TypeLatticeElement.DOUBLE;
+        return TypeLatticeElement.getDouble();
       default:
         throw new Unreachable("Unexpected type in conversion to primitive: " + this);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java b/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java
index eb0ba83..1f7f97b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java
@@ -160,13 +160,13 @@
   public PrimitiveTypeLatticeElement toPrimitiveTypeLattice() {
     switch (this) {
       case INT:
-        return TypeLatticeElement.INT;
+        return TypeLatticeElement.getInt();
       case FLOAT:
-        return TypeLatticeElement.FLOAT;
+        return TypeLatticeElement.getFloat();
       case LONG:
-        return TypeLatticeElement.LONG;
+        return TypeLatticeElement.getLong();
       case DOUBLE:
-        return TypeLatticeElement.DOUBLE;
+        return TypeLatticeElement.getDouble();
       default:
         throw new Unreachable("Unexpected type in conversion to primitive: " + this);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 682804e..83ffaeb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -385,7 +385,7 @@
             || constNumber == null
             || add == null
             || store == null
-            || constNumber.outValue().getTypeLattice() != TypeLatticeElement.INT) {
+            || constNumber.outValue().getTypeLattice() != TypeLatticeElement.getInt()) {
           it.next();
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index a8b0e60..8af9977 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -606,9 +606,6 @@
 
   public void add(com.android.tools.r8.ir.code.Instruction instr, Instruction dex) {
     assert !instr.isGoto();
-    assert isBuildingForComparison()
-        || !instr.isDexItemBasedConstString()
-        || ir.method.getOptimizationInfo().useIdentifierNameString();
     add(instr, new FixedSizeInfo(instr, dex));
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 6233a57..42728d4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -6,6 +6,14 @@
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getBottom;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getDouble;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getFloat;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getInt;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getLong;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getNull;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getSingle;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getWide;
 
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.CompilationError;
@@ -139,12 +147,6 @@
  */
 public class IRBuilder {
 
-  private static final TypeLatticeElement INT = TypeLatticeElement.INT;
-  private static final TypeLatticeElement FLOAT = TypeLatticeElement.FLOAT;
-  private static final TypeLatticeElement LONG = TypeLatticeElement.LONG;
-  private static final TypeLatticeElement DOUBLE = TypeLatticeElement.DOUBLE;
-  private static final TypeLatticeElement NULL = TypeLatticeElement.NULL;
-
   public static final int INITIAL_BLOCK_OFFSET = -1;
 
   private static TypeLatticeElement fromMemberType(MemberType type) {
@@ -153,20 +155,20 @@
       case CHAR:
       case SHORT:
       case INT:
-        return INT;
+        return getInt();
       case FLOAT:
-        return FLOAT;
+        return getFloat();
       case INT_OR_FLOAT:
-        return TypeLatticeElement.SINGLE;
+        return getSingle();
       case LONG:
-        return LONG;
+        return getLong();
       case DOUBLE:
-        return DOUBLE;
+        return getDouble();
       case LONG_OR_DOUBLE:
-        return TypeLatticeElement.WIDE;
+        return getWide();
       case OBJECT:
         // For object types, we delay the exact type computation until done building.
-        return TypeLatticeElement.BOTTOM;
+        return getBottom();
       default:
         throw new Unreachable("Unexpected member type: " + type);
     }
@@ -895,7 +897,7 @@
     RemovedArgumentInfo removedArgumentInfo = getRemovedArgumentInfo();
     if (removedArgumentInfo == null) {
       DebugLocalInfo local = getOutgoingLocal(register);
-      Value value = writeRegister(register, INT, ThrowingInfo.NO_THROW, local);
+      Value value = writeRegister(register, getInt(), ThrowingInfo.NO_THROW, local);
       addNonThisArgument(new Argument(value, true));
     } else {
       assert removedArgumentInfo.isNeverUsed();
@@ -922,7 +924,7 @@
         pendingArgumentInstructions = new ArrayList<>();
       }
       DebugLocalInfo local = getOutgoingLocal(register);
-      Value value = writeRegister(register, TypeLatticeElement.NULL, ThrowingInfo.NO_THROW, local);
+      Value value = writeRegister(register, getNull(), ThrowingInfo.NO_THROW, local);
       pendingArgumentInstructions.add(new ConstNumber(value, 0));
     } else {
       assert removedArgumentInfo.isNeverUsed();
@@ -982,6 +984,12 @@
         if (currentBlock.getInstructions().isEmpty()) {
           addInstruction(new DebugLocalRead());
         } else {
+          // We do not want to add the out value of an instructions as a debug value for
+          // the same instruction. Debug values are there to keep values alive until that
+          // instruction. Therefore, they make no sense on the instruction that defines
+          // the value. There should always be a DebugLocalRead in the IR for situations
+          // where an introduced local's scope ends immediately.
+          assert !debugLocalEnds.contains(currentBlock.getInstructions().getLast().outValue());
           attachLocalValues(currentBlock.getInstructions().getLast());
         }
       }
@@ -1043,7 +1051,7 @@
 
   public void addArrayLength(int dest, int array) {
     Value in = readRegister(array, ValueTypeConstraint.OBJECT);
-    Value out = writeRegister(dest, INT, ThrowingInfo.CAN_THROW);
+    Value out = writeRegister(dest, getInt(), ThrowingInfo.CAN_THROW);
     ArrayLength instruction = new ArrayLength(out, in);
     assert instruction.instructionTypeCanThrow();
     add(instruction);
@@ -1073,7 +1081,7 @@
   public void addCmp(NumericType type, Bias bias, int dest, int left, int right) {
     Value in1 = readNumericRegister(left, type);
     Value in2 = readNumericRegister(right, type);
-    Value out = writeRegister(dest, INT, ThrowingInfo.NO_THROW);
+    Value out = writeRegister(dest, getInt(), ThrowingInfo.NO_THROW);
     Cmp instruction = new Cmp(type, bias, out, in1, in2);
     assert !instruction.instructionTypeCanThrow();
     add(instruction);
@@ -1087,23 +1095,23 @@
   }
 
   public void addLongConst(int dest, long value) {
-    add(new ConstNumber(writeRegister(dest, LONG, ThrowingInfo.NO_THROW), value));
+    add(new ConstNumber(writeRegister(dest, getLong(), ThrowingInfo.NO_THROW), value));
   }
 
   public void addDoubleConst(int dest, long value) {
-    add(new ConstNumber(writeRegister(dest, DOUBLE, ThrowingInfo.NO_THROW), value));
+    add(new ConstNumber(writeRegister(dest, getDouble(), ThrowingInfo.NO_THROW), value));
   }
 
   public void addIntConst(int dest, long value) {
-    add(new ConstNumber(writeRegister(dest, INT, ThrowingInfo.NO_THROW), value));
+    add(new ConstNumber(writeRegister(dest, getInt(), ThrowingInfo.NO_THROW), value));
   }
 
   public void addFloatConst(int dest, long value) {
-    add(new ConstNumber(writeRegister(dest, FLOAT, ThrowingInfo.NO_THROW), value));
+    add(new ConstNumber(writeRegister(dest, getFloat(), ThrowingInfo.NO_THROW), value));
   }
 
   public void addNullConst(int dest) {
-    add(new ConstNumber(writeRegister(dest, NULL, ThrowingInfo.NO_THROW), 0L));
+    add(new ConstNumber(writeRegister(dest, getNull(), ThrowingInfo.NO_THROW), 0L));
   }
 
   public void addConstClass(int dest, DexType type) {
@@ -1160,7 +1168,6 @@
 
   public void addDexItemBasedConstString(
       int dest, DexReference item, NameComputationInfo<?> nameComputationInfo) {
-    assert method.getOptimizationInfo().useIdentifierNameString();
     TypeLatticeElement typeLattice =
         TypeLatticeElement.stringClassType(appView, definitelyNotNull());
     ThrowingInfo throwingInfo = throwingInfoForConstStrings();
@@ -1212,6 +1219,11 @@
         addInstruction(new DebugLocalWrite(out, in));
         return;
       }
+      // If this move ends locals, add a DebugLocalRead to make sure the end point
+      // is registered in the right place.
+      if (!debugLocalEnds.isEmpty()) {
+        addInstruction(new DebugLocalRead());
+      }
     }
     currentBlock.writeCurrentDefinition(dest, in, ThrowingInfo.NO_THROW);
   }
@@ -1235,6 +1247,15 @@
     addInstruction(instruction);
   }
 
+  public void addNop() {
+    // If locals end on a nop, insert a debug local read as the ending point.
+    // This avoids situations where the end of the local could be the instruction
+    // that introduced it when the local only spans a nop in the input.
+    if (!debugLocalEnds.isEmpty()) {
+      addInstruction(new DebugLocalRead());
+    }
+  }
+
   public void addRem(NumericType type, int dest, int left, int right) {
     boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
     Value in1 = readNumericRegister(left, type);
@@ -1358,7 +1379,7 @@
 
   public void addInstanceOf(int dest, int value, DexType type) {
     Value in = readRegister(value, ValueTypeConstraint.OBJECT);
-    Value out = writeRegister(dest, INT, ThrowingInfo.CAN_THROW);
+    Value out = writeRegister(dest, getInt(), ThrowingInfo.CAN_THROW);
     InstanceOf instruction = new InstanceOf(out, in, type);
     assert instruction.instructionTypeCanThrow();
     addInstruction(instruction);
@@ -2098,9 +2119,7 @@
     // A debug initiated value must have a precise type constraint.
     assert typeConstraint.isPrecise();
     TypeLatticeElement type =
-        typeConstraint.isObject()
-            ? TypeLatticeElement.NULL
-            : typeConstraint.toPrimitiveTypeLattice();
+        typeConstraint.isObject() ? getNull() : typeConstraint.toPrimitiveTypeLattice();
     if (uninitializedDebugLocalValues == null) {
       uninitializedDebugLocalValues = new Int2ReferenceOpenHashMap<>();
     }
@@ -2137,14 +2156,14 @@
   }
 
   private Value readLongLiteral(long constant) {
-    Value value = new Value(valueNumberGenerator.next(), LONG, null);
+    Value value = new Value(valueNumberGenerator.next(), getLong(), null);
     ConstNumber number = new ConstNumber(value, constant);
     add(number);
     return number.outValue();
   }
 
   private Value readIntLiteral(long constant) {
-    Value value = new Value(valueNumberGenerator.next(), INT, null);
+    Value value = new Value(valueNumberGenerator.next(), getInt(), null);
     ConstNumber number = new ConstNumber(value, constant);
     add(number);
     return number.outValue();
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 028a397..2c9a53c 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
@@ -15,7 +15,6 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexApplication.Builder;
-import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -44,6 +43,7 @@
 import com.android.tools.r8.ir.desugar.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.ir.desugar.StringConcatRewriter;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
@@ -51,11 +51,13 @@
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.Assumer;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
+import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
 import com.android.tools.r8.ir.optimize.Devirtualizer;
 import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
+import com.android.tools.r8.ir.optimize.EnumUnboxer;
 import com.android.tools.r8.ir.optimize.IdempotentFunctionCallCanonicalizer;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -100,10 +102,10 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Sets;
-import com.google.common.collect.Streams;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -156,6 +158,7 @@
   private final TypeChecker typeChecker;
   private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
   private final ServiceLoaderRewriter serviceLoaderRewriter;
+  private final EnumUnboxer enumUnboxer;
 
   // Assumers that will insert Assume instructions.
   public final Collection<Assumer> assumers = new ArrayList<>();
@@ -187,7 +190,8 @@
     assert appView.appInfo().hasLiveness() || appView.graphLense().isIdentityLense();
     assert appView.options() != null;
     assert appView.options().programConsumer != null;
-    this.timing = timing != null ? timing : new Timing("internal");
+    assert timing != null;
+    this.timing = timing;
     this.appView = appView;
     this.options = appView.options();
     this.printer = printer;
@@ -195,7 +199,7 @@
     this.codeRewriter = new CodeRewriter(appView, this);
     this.constantCanonicalizer = new ConstantCanonicalizer(codeRewriter);
     this.classInitializerDefaultsOptimization =
-        options.debug ? null : new ClassInitializerDefaultsOptimization(appView, this);
+        new ClassInitializerDefaultsOptimization(appView, this);
     this.stringConcatRewriter = new StringConcatRewriter(appView);
     this.stringOptimizer = new StringOptimizer(appView);
     this.stringBuilderOptimizer = new StringBuilderOptimizer(appView);
@@ -243,10 +247,13 @@
       this.stringSwitchRemover = null;
       this.serviceLoaderRewriter = null;
       this.methodOptimizationInfoCollector = null;
+      this.enumUnboxer = null;
       return;
     }
     this.lambdaRewriter =
-        options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
+        (options.desugarState == DesugarState.ON && !appView.enableWholeProgramOptimizations())
+            ? new LambdaRewriter(appView)
+            : null;
     this.interfaceMethodRewriter =
         options.isInterfaceMethodDesugaringEnabled()
             ? new InterfaceMethodRewriter(appView, this)
@@ -313,12 +320,13 @@
           options.enableUninstantiatedTypeOptimization
               ? new UninstantiatedTypeOptimization(appViewWithLiveness)
               : null;
-      this.typeChecker = new TypeChecker(appView.withLiveness());
+      this.typeChecker = new TypeChecker(appViewWithLiveness);
       this.d8NestBasedAccessDesugaring = null;
       this.serviceLoaderRewriter =
           options.enableServiceLoaderRewriting
-              ? new ServiceLoaderRewriter(appView.withLiveness())
+              ? new ServiceLoaderRewriter(appViewWithLiveness)
               : null;
+      this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
     } else {
       this.classInliner = null;
       this.classStaticizer = null;
@@ -339,6 +347,7 @@
           options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
       this.serviceLoaderRewriter = null;
       this.methodOptimizationInfoCollector = null;
+      this.enumUnboxer = null;
     }
     this.stringSwitchRemover =
         options.isStringSwitchConversionEnabled()
@@ -346,14 +355,6 @@
             : null;
   }
 
-  public Set<DexCallSite> getDesugaredCallSites() {
-    if (lambdaRewriter != null) {
-      return lambdaRewriter.getDesugaredCallSites();
-    } else {
-      return Collections.emptySet();
-    }
-  }
-
   /** Create an IR converter for processing methods with full program optimization disabled. */
   public IRConverter(AppView<?> appView, Timing timing) {
     this(appView, timing, null, MainDexClasses.NONE);
@@ -399,11 +400,8 @@
   private void synthesizeLambdaClasses(Builder<?> builder, ExecutorService executorService)
       throws ExecutionException {
     if (lambdaRewriter != null) {
-      if (appView.enableWholeProgramOptimizations()) {
-        lambdaRewriter.finalizeLambdaDesugaringForR8(builder);
-      } else {
-        lambdaRewriter.finalizeLambdaDesugaringForD8(builder, this, executorService);
-      }
+      assert !appView.enableWholeProgramOptimizations();
+      lambdaRewriter.finalizeLambdaDesugaringForD8(builder, this, executorService);
     }
   }
 
@@ -422,12 +420,13 @@
 
   private void desugarInterfaceMethods(
       Builder<?> builder,
-      InterfaceMethodRewriter.Flavor includeAllResources,
+      OptimizationFeedback feedback,
+      Flavor includeAllResources,
       ExecutorService executorService)
       throws ExecutionException {
     if (interfaceMethodRewriter != null) {
       interfaceMethodRewriter.desugarInterfaceMethods(
-          builder, includeAllResources, executorService);
+          builder, feedback, includeAllResources, executorService);
     }
   }
 
@@ -463,7 +462,7 @@
 
     desugarNestBasedAccess(builder, executor);
     synthesizeLambdaClasses(builder, executor);
-    desugarInterfaceMethods(builder, ExcludeDexResources, executor);
+    desugarInterfaceMethods(builder, simpleOptimizationFeedback, ExcludeDexResources, executor);
     synthesizeTwrCloseResourceUtilityClass(builder, executor);
     synthesizeJava8UtilityClass(builder, executor);
     processCovariantReturnTypeAnnotations(builder);
@@ -622,22 +621,12 @@
   }
 
   public DexApplication optimize(ExecutorService executorService) throws ExecutionException {
-    if (options.isShrinking()) {
-      assert !removeLambdaDeserializationMethods();
-    } else {
-      removeLambdaDeserializationMethods();
-    }
-
     DexApplication application = appView.appInfo().app();
 
     computeReachabilitySensitivity(application);
     collectLambdaMergingCandidates(application);
     collectStaticizerCandidates(application);
 
-    if (lambdaRewriter != null) {
-      lambdaRewriter.installGraphLens();
-    }
-
     // The process is in two phases in general.
     // 1) Subject all DexEncodedMethods to optimization, except some optimizations that require
     //    reprocessing IR code of methods, e.g., outlining, double-inlining, class staticizer, etc.
@@ -665,6 +654,7 @@
           method -> processMethod(method, feedback, primaryMethodProcessor),
           this::waveStart,
           this::waveDone,
+          timing,
           executorService);
       timing.end();
       assert graphLenseForIR == appView.graphLense();
@@ -678,6 +668,10 @@
       libraryMethodOverrideAnalysis.finish();
     }
 
+    if (enumUnboxer != null) {
+      enumUnboxer.finishEnumAnalysis();
+    }
+
     // Post processing:
     //   1) Second pass for methods whose collected call site information become more precise.
     //   2) Second inlining pass for dealing with double inline callers.
@@ -715,7 +709,8 @@
     synthesizeLambdaClasses(builder, executorService);
 
     printPhase("Interface method desugaring");
-    desugarInterfaceMethods(builder, IncludeAllResources, executorService);
+    desugarInterfaceMethods(builder, feedback, IncludeAllResources, executorService);
+    feedback.updateVisibleOptimizationInfo();
 
     printPhase("Twr close resource utility class synthesis");
     synthesizeTwrCloseResourceUtilityClass(builder, executorService);
@@ -756,7 +751,8 @@
             code -> {
               outliner.applyOutliningCandidate(code);
               printMethod(code, "IR after outlining (SSA)", null);
-              finalizeIR(code.method, code, OptimizationFeedbackIgnore.getInstance());
+              finalizeIR(
+                  code.method, code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
             },
             executorService);
         feedback.updateVisibleOptimizationInfo();
@@ -812,11 +808,6 @@
   private void waveStart(Collection<DexEncodedMethod> wave, ExecutorService executorService)
       throws ExecutionException {
     onWaveDoneActions = Collections.synchronizedList(new ArrayList<>());
-
-    if (lambdaRewriter != null) {
-      lambdaRewriter.synthesizeLambdaClassesForWave(
-          wave, this, executorService, delayedOptimizationFeedback, lensCodeRewriter);
-    }
   }
 
   private void waveDone() {
@@ -882,7 +873,7 @@
     IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.method.holder));
     assert code != null;
     codeRewriter.rewriteMoveResult(code);
-    finalizeIR(method, code, OptimizationFeedbackIgnore.getInstance());
+    finalizeIR(method, code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
   }
 
   private void collectLambdaMergingCandidates(DexApplication application) {
@@ -934,7 +925,8 @@
     }
     assert code.isConsistentSSA();
     code.traceBlocks();
-    RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
+    Timing timing = Timing.empty();
+    RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
     method.setCode(code, registerAllocator, appView);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Resulting dex code for %s:\n%s",
@@ -982,22 +974,6 @@
     processMethodsConcurrently(methods, executorService);
   }
 
-  public void optimizeSynthesizedLambdaClasses(
-      Collection<DexProgramClass> classes, ExecutorService executorService)
-      throws ExecutionException {
-    assert appView.enableWholeProgramOptimizations();
-    Set<DexEncodedMethod> methods = Sets.newIdentityHashSet();
-    for (DexProgramClass clazz : classes) {
-      clazz.forEachMethod(methods::add);
-    }
-    LambdaMethodProcessor processor =
-        new LambdaMethodProcessor(appView.withLiveness(), methods, executorService, timing);
-    processor.forEachMethod(
-        method -> processMethod(method, delayedOptimizationFeedback, processor),
-        delayedOptimizationFeedback::updateVisibleOptimizationInfo,
-        executorService);
-  }
-
   public void optimizeSynthesizedMethod(DexEncodedMethod method) {
     if (!method.isProcessed()) {
       // Process the generated method, but don't apply any outlining.
@@ -1031,18 +1007,17 @@
   }
 
   // TODO(b/140766440): Make this receive a list of CodeOptimizations to conduct.
-  public void processMethod(
-      DexEncodedMethod method,
-      OptimizationFeedback feedback,
-      MethodProcessor methodProcessor) {
+  public Timing processMethod(
+      DexEncodedMethod method, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
     Code code = method.getCode();
     boolean matchesMethodFilter = options.methodMatchesFilter(method);
     if (code != null && matchesMethodFilter) {
-      rewriteCode(method, feedback, methodProcessor);
+      return rewriteCode(method, feedback, methodProcessor);
     } else {
       // Mark abstract methods as processed as well.
       method.markProcessed(ConstraintWithTarget.NEVER);
     }
+    return Timing.empty();
   }
 
   private static void invertConditionalsForTesting(IRCode code) {
@@ -1053,11 +1028,11 @@
     }
   }
 
-  private void rewriteCode(
+  private Timing rewriteCode(
       DexEncodedMethod method, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
     Origin origin = appView.appInfo().originFor(method.method.holder);
     try {
-      rewriteCodeInternal(method, feedback, methodProcessor, origin);
+      return rewriteCodeInternal(method, feedback, methodProcessor, origin);
     } catch (CompilationError e) {
       // If rewriting throws a compilation error, attach the origin and method if missing.
       throw e.withAdditionalOriginAndPositionInfo(origin, new MethodPosition(method.method));
@@ -1070,7 +1045,7 @@
     }
   }
 
-  private void rewriteCodeInternal(
+  private Timing rewriteCodeInternal(
       DexEncodedMethod method,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
@@ -1086,22 +1061,21 @@
     }
     if (options.skipIR) {
       feedback.markProcessed(method, ConstraintWithTarget.NEVER);
-      return;
+      return Timing.empty();
     }
     IRCode code = method.buildIR(appView, origin);
     if (code == null) {
       feedback.markProcessed(method, ConstraintWithTarget.NEVER);
-      return;
+      return Timing.empty();
     }
-    optimize(code, feedback, methodProcessor);
+    return optimize(code, feedback, methodProcessor);
   }
 
   // TODO(b/140766440): Convert all sub steps an implementer of CodeOptimization
-  private void optimize(
-      IRCode code,
-      OptimizationFeedback feedback,
-      MethodProcessor methodProcessor) {
+  private Timing optimize(
+      IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
     DexEncodedMethod method = code.method;
+    Timing timing = Timing.create(method.qualifiedName(), options);
 
     if (Log.ENABLED) {
       Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s", method.toSourceString(), code);
@@ -1115,7 +1089,9 @@
     }
 
     if (options.canHaveArtStringNewInitBug()) {
+      timing.begin("Check for new-init issue");
       CodeRewriter.ensureDirectStringNewToInit(code, appView.dexItemFactory());
+      timing.end();
     }
 
     boolean isDebugMode = options.debug || method.getOptimizationInfo().isReachabilitySensitive();
@@ -1126,19 +1102,25 @@
 
     if (!method.isProcessed()) {
       if (lensCodeRewriter != null) {
+        timing.begin("Lens rewrite");
         lensCodeRewriter.rewrite(code, method);
+        timing.end();
       } else {
         assert appView.graphLense().isIdentityLense();
       }
 
       if (lambdaRewriter != null) {
+        timing.begin("Desugar lambdas");
         lambdaRewriter.desugarLambdas(method, code);
+        timing.end();
         assert code.isConsistentSSA();
       }
     }
 
     if (lambdaMerger != null) {
+      timing.begin("Merge lambdas");
       lambdaMerger.rewriteCode(method, code, inliner);
+      timing.end();
       assert code.isConsistentSSA();
     }
 
@@ -1152,7 +1134,7 @@
                   + "` does not type check and will be assumed to be unreachable.");
       options.reporter.warning(warning);
       finalizeEmptyThrowingCode(method, feedback);
-      return;
+      return timing;
     }
 
     // This is the first point in time where we can assert that the types are sound. If this
@@ -1163,41 +1145,55 @@
 
     if (serviceLoaderRewriter != null) {
       assert appView.appInfo().hasLiveness();
+      timing.begin("Rewrite service loaders");
       serviceLoaderRewriter.rewrite(code);
+      timing.end();
     }
 
     if (identifierNameStringMarker != null) {
+      timing.begin("Decouple identifier-name strings");
       identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(method, code);
+      timing.end();
       assert code.isConsistentSSA();
     }
 
     if (memberValuePropagation != null) {
+      timing.begin("Propagate member values");
       memberValuePropagation.rewriteWithConstantValues(code, method.method.holder);
+      timing.end();
     }
 
     if (options.enableEnumValueOptimization) {
       assert appView.enableWholeProgramOptimizations();
+      timing.begin("Remove switch maps");
       codeRewriter.removeSwitchMaps(code);
+      timing.end();
     }
 
-    assertionsRewriter.run(method, code);
+    assertionsRewriter.run(method, code, timing);
 
     previous = printMethod(code, "IR after disable assertions (SSA)", previous);
 
+    timing.begin("Insert assume instructions");
     CodeRewriter.insertAssumeInstructions(code, assumers);
+    timing.end();
 
     previous = printMethod(code, "IR after inserting assume instructions (SSA)", previous);
 
+    timing.begin("Run proto shrinking tasks");
     appView.withGeneratedExtensionRegistryShrinker(shrinker -> shrinker.rewriteCode(method, code));
 
     previous = printMethod(code, "IR after generated extension registry shrinking (SSA)", previous);
 
     appView.withGeneratedMessageLiteShrinker(shrinker -> shrinker.run(method, code));
+    timing.end();
 
     previous = printMethod(code, "IR after generated message lite shrinking (SSA)", previous);
 
     if (!isDebugMode && options.enableInlining && inliner != null) {
+      timing.begin("Inlining");
       inliner.performInlining(method, code, feedback, methodProcessor);
+      timing.end();
       assert code.verifyTypes(appView);
     }
 
@@ -1205,77 +1201,124 @@
 
     if (appView.appInfo().hasLiveness()) {
       // Reflection optimization 1. getClass() / forName() -> const-class
+      timing.begin("Rewrite to const class");
       ReflectionOptimizer.rewriteGetClassOrForNameToConstClass(appView.withLiveness(), code);
+      timing.end();
     }
 
     if (!isDebugMode) {
       // Reflection optimization 2. get*Name() with const-class -> const-string
       if (options.enableNameReflectionOptimization
           || options.testing.forceNameReflectionOptimization) {
+        timing.begin("Rewrite Class.getName");
         stringOptimizer.rewriteClassGetName(appView, code);
+        timing.end();
       }
       // Reflection/string optimization 3. trivial conversion/computation on const-string
+      timing.begin("Optimize const strings");
       stringOptimizer.computeTrivialOperationsOnConstString(code);
       stringOptimizer.removeTrivialConversions(code);
+      timing.end();
       if (libraryMethodOptimizer != null) {
+        timing.begin("Optimize library methods");
         libraryMethodOptimizer.optimize(code, feedback, methodProcessor);
+        timing.end();
       }
       assert code.isConsistentSSA();
     }
 
     if (devirtualizer != null) {
       assert code.verifyTypes(appView);
+      timing.begin("Devirtualize invoke interface");
       devirtualizer.devirtualizeInvokeInterface(code, method.method.holder);
+      timing.end();
     }
     if (uninstantiatedTypeOptimization != null) {
+      timing.begin("Rewrite uninstantiated types");
       uninstantiatedTypeOptimization.rewrite(code);
+      timing.end();
     }
 
     assert code.verifyTypes(appView);
+    timing.begin("Remove trivial type checks/casts");
     codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
+    timing.end();
 
     if (options.enableEnumValueOptimization) {
       assert appView.enableWholeProgramOptimizations();
+      timing.begin("Rewrite constant enum methods");
       codeRewriter.rewriteConstantEnumMethodCalls(code);
+      timing.end();
     }
 
+    timing.begin("Rewrite array length");
     codeRewriter.rewriteKnownArrayLengthCalls(code);
+    timing.end();
+    timing.begin("Rewrite AssertionError");
     codeRewriter.rewriteAssertionErrorTwoArgumentConstructor(code, options);
+    timing.end();
+    timing.begin("Run CSE");
     codeRewriter.commonSubexpressionElimination(code);
+    timing.end();
+    timing.begin("Simplify arrays");
     codeRewriter.simplifyArrayConstruction(code);
+    timing.end();
+    timing.begin("Rewrite move result");
     codeRewriter.rewriteMoveResult(code);
+    timing.end();
     // TODO(b/114002137): for now, string concatenation depends on rewriteMoveResult.
     if (options.enableStringConcatenationOptimization
         && !isDebugMode
         && options.isGeneratingDex()) {
+      timing.begin("Rewrite string concat");
       stringBuilderOptimizer.computeTrivialStringConcatenation(code);
+      timing.end();
     }
 
+    timing.begin("Split range invokes");
     codeRewriter.splitRangeInvokeConstants(code);
+    timing.end();
+    timing.begin("Propogate sparse conditionals");
     new SparseConditionalConstantPropagation(code).run();
+    timing.end();
     if (stringSwitchRemover != null) {
+      timing.begin("Remove string switch");
       stringSwitchRemover.run(method, code);
+      timing.end();
     }
+    timing.begin("Rewrite always throwing invokes");
     codeRewriter.processMethodsNeverReturningNormally(code);
+    timing.end();
+    timing.begin("Simplify control flow");
     if (codeRewriter.simplifyControlFlow(code)) {
+      timing.begin("Remove trivial type checks/casts");
       codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
+      timing.end();
     }
+    timing.end();
     if (options.enableRedundantConstNumberOptimization) {
+      timing.begin("Remove const numbers");
       codeRewriter.redundantConstNumberRemoval(code);
+      timing.end();
     }
     if (RedundantFieldLoadElimination.shouldRun(appView, code)) {
+      timing.begin("Remove field loads");
       new RedundantFieldLoadElimination(appView, code).run();
+      timing.end();
     }
 
     if (options.testing.invertConditionals) {
       invertConditionalsForTesting(code);
     }
 
+    timing.begin("Rewrite throw NPE");
     codeRewriter.rewriteThrowNullPointerException(code);
+    timing.end();
 
-    if (classInitializerDefaultsOptimization != null && !isDebugMode) {
-      classInitializerDefaultsOptimization.optimize(method, code);
-    }
+    timing.begin("Optimize class initializers");
+    ClassInitializerDefaultsResult classInitializerDefaultsResult =
+        classInitializerDefaultsOptimization.optimize(method, code);
+    timing.end();
 
     if (Log.ENABLED) {
       Log.debug(getClass(), "Intermediate (SSA) flow graph for %s:\n%s",
@@ -1284,15 +1327,23 @@
     // Dead code removal. Performed after simplifications to remove code that becomes dead
     // as a result of those simplifications. The following optimizations could reveal more
     // dead code which is removed right before register allocation in performRegisterAllocation.
+    timing.begin("Remove dead code");
     deadCodeRemover.run(code);
+    timing.end();
     assert code.isConsistentSSA();
 
     if (options.desugarState == DesugarState.ON && enableTryWithResourcesDesugaring()) {
+      timing.begin("Rewrite Throwable suppresed methods");
       codeRewriter.rewriteThrowableAddAndGetSuppressed(code);
+      timing.end();
     }
+    timing.begin("Rewrite backport methods");
     backportedMethodRewriter.desugar(code);
+    timing.end();
 
+    timing.begin("Desugar string concat");
     stringConcatRewriter.desugarStringConcats(method.method, code);
+    timing.end();
 
     previous = printMethod(code, "IR after lambda desugaring (SSA)", previous);
 
@@ -1301,6 +1352,7 @@
     previous = printMethod(code, "IR before class inlining (SSA)", previous);
 
     if (classInliner != null) {
+      timing.begin("Inline classes");
       // Class inliner should work before lambda merger, so if it inlines the
       // lambda, it does not get collected by merger.
       assert options.enableInlining && inliner != null;
@@ -1323,6 +1375,7 @@
                       // Inlining instruction allowance is not needed for the class inliner since it
                       // always uses a force inlining oracle for inlining.
                       -1)));
+      timing.end();
       assert code.isConsistentSSA();
       assert code.verifyTypes(appView);
     }
@@ -1330,30 +1383,42 @@
     previous = printMethod(code, "IR after class inlining (SSA)", previous);
 
     if (d8NestBasedAccessDesugaring != null) {
+      timing.begin("Desugar nest access");
       d8NestBasedAccessDesugaring.rewriteNestBasedAccesses(method, code, appView);
+      timing.end();
       assert code.isConsistentSSA();
     }
 
     previous = printMethod(code, "IR after nest based access desugaring (SSA)", previous);
 
     if (interfaceMethodRewriter != null) {
+      timing.begin("Rewrite interface methods");
       interfaceMethodRewriter.rewriteMethodReferences(method, code);
+      timing.end();
       assert code.isConsistentSSA();
     }
 
     previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
 
+    if (enumUnboxer != null && methodProcessor.isPost()) {
+      enumUnboxer.unboxEnums(code);
+    }
+
     // This pass has to be after interfaceMethodRewriter and BackportedMethodRewriter.
     if (desugaredLibraryAPIConverter != null
         && (!appView.enableWholeProgramOptimizations() || methodProcessor.isPrimary())) {
+      timing.begin("Desugar library API");
       desugaredLibraryAPIConverter.desugar(code);
+      timing.end();
       assert code.isConsistentSSA();
     }
 
     previous = printMethod(code, "IR after desugared library API Conversion (SSA)", previous);
 
     if (twrCloseResourceRewriter != null) {
+      timing.begin("Rewrite TWR close");
       twrCloseResourceRewriter.rewriteMethodCode(code);
+      timing.end();
     }
 
     assert code.verifyTypes(appView);
@@ -1361,7 +1426,9 @@
     previous = printMethod(code, "IR after twr close resource rewriter (SSA)", previous);
 
     if (lambdaMerger != null) {
+      timing.begin("Analyze lambda merging");
       lambdaMerger.analyzeCode(method, code);
+      timing.end();
       assert code.isConsistentSSA();
     }
 
@@ -1370,7 +1437,9 @@
     // TODO(b/140766440): an ideal solution would be puttting CodeOptimization for this into
     //  the list for primary processing only.
     if (options.outline.enabled && outliner != null && methodProcessor.isPrimary()) {
+      timing.begin("Identify outlines");
       outliner.getOutlineMethodIdentifierGenerator().accept(code);
+      timing.end();
       assert code.isConsistentSSA();
     }
 
@@ -1380,11 +1449,19 @@
 
     // TODO(mkroghj) Test if shorten live ranges is worth it.
     if (!options.isGeneratingClassFiles()) {
+      timing.begin("Canonicalize constants");
       constantCanonicalizer.canonicalize(appView, code);
+      timing.end();
+      timing.begin("Create constants for literal instructions");
       codeRewriter.useDedicatedConstantForLitInstruction(code);
+      timing.end();
+      timing.begin("Shorten live ranges");
       codeRewriter.shortenLiveRanges(code);
+      timing.end();
     }
+    timing.begin("Canonicalize idempotent calls");
     idempotentFunctionCallCanonicalizer.canonicalize(code);
+    timing.end();
 
     previous =
         printMethod(code, "IR after idempotent function call canonicalization (SSA)", previous);
@@ -1398,30 +1475,46 @@
     previous = printMethod(code, "IR after argument type logging (SSA)", previous);
 
     if (classStaticizer != null) {
+      timing.begin("Identify staticizing candidates");
       classStaticizer.examineMethodCode(method, code);
+      timing.end();
+    }
+
+    if (enumUnboxer != null && methodProcessor.isPrimary()) {
+      enumUnboxer.analyzeEnums(code);
     }
 
     assert code.verifyTypes(appView);
 
     if (appView.enableWholeProgramOptimizations()) {
       if (libraryMethodOverrideAnalysis != null) {
+        timing.begin("Analyze library method overrides");
         libraryMethodOverrideAnalysis.analyze(code);
+        timing.end();
       }
 
       if (fieldBitAccessAnalysis != null) {
+        timing.begin("Record field access");
         fieldBitAccessAnalysis.recordFieldAccesses(code, feedback);
+        timing.end();
       }
 
       // Arguments can be changed during the debug mode.
       if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) {
+        timing.begin("Collect call-site info");
         appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
+        timing.end();
       }
 
-      collectOptimizationInfo(code, feedback);
+      timing.begin("Collect optimization info");
+      collectOptimizationInfo(code, classInitializerDefaultsResult, feedback);
+      timing.end();
     }
 
     if (!assumers.isEmpty()) {
+      timing.begin("Remove assume instructions");
       CodeRewriter.removeAssumeInstructions(appView, code);
+      timing.end();
       assert code.isConsistentSSA();
     }
 
@@ -1434,47 +1527,58 @@
         printMethod(code, "IR after computation of optimization info summary (SSA)", previous);
 
     if (options.canHaveNumberConversionRegisterAllocationBug()) {
+      timing.begin("Check number conversion issue");
       codeRewriter.workaroundNumberConversionRegisterAllocationBug(code);
+      timing.end();
     }
 
-    // Either marked by IdentifierNameStringMarker or name reflection, or propagated from inlinee,
-    // Then, make it visible to IdentifierMinifier.
-    // Note that we place this at the end of IR processing because inlinee can be inlined by
-    // Inliner, ClassInliner, or future optimizations that use the inlining machinery.
-    if (method.getOptimizationInfo().useIdentifierNameString()) {
-      // If it is optimized, e.g., moved to default values of static fields or even removed by dead
-      // code remover, we can save future computation in IdentifierMinifier.
-      if (Streams.stream(code.instructionIterator())
-          .anyMatch(Instruction::isDexItemBasedConstString)) {
-        feedback.markUseIdentifierNameString(method);
-      }
-    } else {
-      assert Streams.stream(code.instructionIterator())
-          .noneMatch(Instruction::isDexItemBasedConstString);
+    // IR processing should not need to handle identifier name strings, so after the first round
+    // there should be no information for that.
+    if (methodProcessor.isPrimary()) {
+      assert !method.getOptimizationInfo().useIdentifierNameString();
     }
 
     printMethod(code, "Optimized IR (SSA)", previous);
-    finalizeIR(method, code, feedback);
+    timing.begin("Finalize IR");
+    finalizeIR(method, code, feedback, timing);
+    timing.end();
+    return timing;
+  }
+
+  public void collectIdentifierNameStringUse(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+    Iterator<Instruction> iterator = code.instructionIterator();
+    while (iterator.hasNext()) {
+      if (iterator.next().isDexItemBasedConstString()) {
+        feedback.markUseIdentifierNameString(method);
+        break;
+      }
+    }
   }
 
   // Compute optimization info summary for the current method unless it is pinned
   // (in that case we should not be making any assumptions about the behavior of the method).
-  public void collectOptimizationInfo(IRCode code, OptimizationFeedback feedback) {
+  public void collectOptimizationInfo(
+      IRCode code,
+      ClassInitializerDefaultsResult classInitializerDefaultsResult,
+      OptimizationFeedback feedback) {
     if (appView.appInfo().withLiveness().isPinned(code.method.method)) {
       return;
     }
     methodOptimizationInfoCollector
         .collectMethodOptimizationInfo(code.method, code, feedback, dynamicTypeOptimization);
-    FieldValueAnalysis.run(appView, code, feedback, code.method);
+    FieldValueAnalysis.run(appView, code, classInitializerDefaultsResult, feedback, code.method);
   }
 
-  public void finalizeIR(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+  public void finalizeIR(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+    collectIdentifierNameStringUse(method, code, feedback);
     code.traceBlocks();
     if (options.isGeneratingClassFiles()) {
       finalizeToCf(method, code, feedback);
     } else {
       assert options.isGeneratingDex();
-      finalizeToDex(method, code, feedback);
+      finalizeToDex(method, code, feedback, timing);
     }
   }
 
@@ -1496,19 +1600,24 @@
     markProcessed(method, code, feedback);
   }
 
-  private void finalizeToDex(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+  private void finalizeToDex(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
     // Workaround massive dex2oat memory use for self-recursive methods.
     CodeRewriter.disableDex2OatInliningForSelfRecursiveMethods(appView, code);
     // Perform register allocation.
-    RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
+    RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
+    timing.begin("Build DEX code");
     method.setCode(code, registerAllocator, appView);
+    timing.end();
     updateHighestSortingStrings(method);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Resulting dex code for %s:\n%s",
           method.toSourceString(), logCode(options, method));
     }
     printMethod(code, "Final IR (non-SSA)", null);
+    timing.begin("Marking processed");
     markProcessed(method, code, feedback);
+    timing.end();
   }
 
   private void markProcessed(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
@@ -1534,24 +1643,33 @@
     }
   }
 
-  private RegisterAllocator performRegisterAllocation(IRCode code, DexEncodedMethod method) {
+  private RegisterAllocator performRegisterAllocation(
+      IRCode code, DexEncodedMethod method, Timing timing) {
     // Always perform dead code elimination before register allocation. The register allocator
     // does not allow dead code (to make sure that we do not waste registers for unneeded values).
+    timing.begin("Remove dead code");
     deadCodeRemover.run(code);
+    timing.end();
     materializeInstructionBeforeLongOperationsWorkaround(code);
     workaroundForwardingInitializerBug(code);
+    timing.begin("Allocate registers");
     LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(appView, code);
     registerAllocator.allocateRegisters();
+    timing.end();
     if (options.canHaveExceptionTargetingLoopHeaderBug()) {
       codeRewriter.workaroundExceptionTargetingLoopHeaderBug(code);
     }
     printMethod(code, "After register allocation (non-SSA)", null);
+    timing.begin("Peephole optimize");
     for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
       CodeRewriter.collapseTrivialGotos(code);
       PeepholeOptimizer.optimize(code, registerAllocator);
     }
+    timing.end();
+    timing.begin("Clean up");
     CodeRewriter.removeUnneededMovesOnExitingPaths(code, registerAllocator);
     CodeRewriter.collapseTrivialGotos(code);
+    timing.end();
     if (Log.ENABLED) {
       Log.debug(getClass(), "Final (non-SSA) flow graph for %s:\n%s",
           method.toSourceString(), code);
@@ -1713,7 +1831,7 @@
     Instruction check = it.previous();
     assert addBefore == check;
     // Forced definition of const-zero
-    Value fixitValue = code.createValue(TypeLatticeElement.INT);
+    Value fixitValue = code.createValue(TypeLatticeElement.getInt());
     Instruction fixitDefinition = new AlwaysMaterializingDefinition(fixitValue);
     fixitDefinition.setBlock(addBefore.getBlock());
     fixitDefinition.setPosition(addBefore.getPosition());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LambdaMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/LambdaMethodProcessor.java
deleted file mode 100644
index a6a9b94..0000000
--- a/src/main/java/com/android/tools/r8/ir/conversion/LambdaMethodProcessor.java
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.conversion;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Action;
-import com.android.tools.r8.utils.IROrdering;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
-import com.android.tools.r8.utils.Timing;
-import java.util.ArrayDeque;
-import java.util.Collection;
-import java.util.Deque;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-
-class LambdaMethodProcessor implements MethodProcessor {
-
-  private final Deque<Collection<DexEncodedMethod>> waves;
-  private Collection<DexEncodedMethod> wave;
-
-  LambdaMethodProcessor(
-      AppView<AppInfoWithLiveness> appView,
-      Set<DexEncodedMethod> methods,
-      ExecutorService executorService,
-      Timing timing)
-      throws ExecutionException {
-    CallGraph callGraph =
-        new PartialCallGraphBuilder(appView, methods).build(executorService, timing);
-    this.waves = createWaves(appView, callGraph);
-  }
-
-  @Override
-  public Phase getPhase() {
-    return Phase.LAMBDA_PROCESSING;
-  }
-
-  private Deque<Collection<DexEncodedMethod>> createWaves(AppView<?> appView, CallGraph callGraph) {
-    IROrdering shuffle = appView.options().testing.irOrdering;
-    Deque<Collection<DexEncodedMethod>> waves = new ArrayDeque<>();
-    while (!callGraph.isEmpty()) {
-      waves.addLast(shuffle.order(callGraph.extractLeaves()));
-    }
-    return waves;
-  }
-
-  @Override
-  public boolean isProcessedConcurrently(DexEncodedMethod method) {
-    return wave.contains(method);
-  }
-
-  <E extends Exception> void forEachMethod(
-      ThrowingConsumer<DexEncodedMethod, E> consumer,
-      Action waveDone,
-      ExecutorService executorService)
-      throws ExecutionException {
-    while (!waves.isEmpty()) {
-      wave = waves.removeFirst();
-      assert wave.size() > 0;
-      ThreadUtils.processItems(wave, consumer, executorService);
-      waveDone.execute();
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index d6e2ff3..34609c9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -9,7 +9,6 @@
 
   enum Phase {
     ONE_TIME,
-    LAMBDA_PROCESSING,
     PRIMARY,
     POST
   }
@@ -20,6 +19,10 @@
     return getPhase() == Phase.PRIMARY;
   }
 
+  default boolean isPost() {
+    return getPhase() == Phase.POST;
+  }
+
   default CallSiteInformation getCallSiteInformation() {
     return CallSiteInformation.empty();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index be529f4..6c42599 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -12,9 +12,11 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.IROrdering;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.ThrowingFunction;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.Timing.TimingMerger;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.Collection;
@@ -73,7 +75,8 @@
 
   private Deque<Collection<DexEncodedMethod>> createWaves(
       AppView<?> appView, CallGraph callGraph, CallSiteInformation callSiteInformation) {
-    IROrdering shuffle = appView.options().testing.irOrdering;
+    InternalOptions options = appView.options();
+    IROrdering shuffle = options.testing.irOrdering;
     Deque<Collection<DexEncodedMethod>> waves = new ArrayDeque<>();
 
     Set<Node> nodes = callGraph.nodes;
@@ -100,6 +103,7 @@
     if (!reprocessing.isEmpty()) {
       postMethodProcessorBuilder.put(reprocessing);
     }
+    options.testing.waveModifier.accept(waves);
     return waves;
   }
 
@@ -135,17 +139,29 @@
    * processed at the same time is passed. This can be used to avoid races in concurrent processing.
    */
   <E extends Exception> void forEachMethod(
-      ThrowingConsumer<DexEncodedMethod, E> consumer,
+      ThrowingFunction<DexEncodedMethod, Timing, E> consumer,
       WaveStartAction waveStartAction,
       Action waveDone,
+      Timing timing,
       ExecutorService executorService)
       throws ExecutionException {
+    TimingMerger merger =
+        timing.beginMerger("primary-processor", ThreadUtils.getNumberOfThreads(executorService));
     while (!waves.isEmpty()) {
       wave = waves.removeFirst();
       assert wave.size() > 0;
       waveStartAction.notifyWaveStart(wave, executorService);
-      ThreadUtils.processItems(wave, consumer, executorService);
+      merger.add(
+          ThreadUtils.processItemsWithResults(
+              wave,
+              method -> {
+                Timing time = consumer.apply(method);
+                time.end();
+                return time;
+              },
+              executorService));
       waveDone.execute();
     }
+    merger.end();
   }
 }
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 b54b406..41f22a4 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
@@ -107,7 +107,7 @@
       InvokeVirtual invokeInstruction =
           new InvokeVirtual(
               appView.dexItemFactory().stringMethods.equals,
-              code.createValue(PrimitiveTypeLatticeElement.INT),
+              code.createValue(PrimitiveTypeLatticeElement.getInt()),
               ImmutableList.of(theSwitch.value(), constStringInstruction.outValue()));
       invokeInstruction.setPosition(Position.syntheticNone());
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
index 6f01dd7..2c41a2a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
@@ -68,25 +68,25 @@
   public static TypeLatticeElement typeForConstraint(ValueTypeConstraint constraint) {
     switch (constraint) {
       case INT_OR_FLOAT_OR_OBJECT:
-        return TypeLatticeElement.TOP;
+        return TypeLatticeElement.getTop();
       case OBJECT:
         // If the constraint is object the concrete lattice type will need to be computed.
         // We mark the object type as bottom for now, with the implication that it is of type
         // reference but that it should not contribute to the computation of its join
         // (in potentially self-referencing phis).
-        return TypeLatticeElement.BOTTOM;
+        return TypeLatticeElement.getBottom();
       case INT:
-        return TypeLatticeElement.INT;
+        return TypeLatticeElement.getInt();
       case FLOAT:
-        return TypeLatticeElement.FLOAT;
+        return TypeLatticeElement.getFloat();
       case INT_OR_FLOAT:
-        return TypeLatticeElement.SINGLE;
+        return TypeLatticeElement.getSingle();
       case LONG:
-        return TypeLatticeElement.LONG;
+        return TypeLatticeElement.getLong();
       case DOUBLE:
-        return TypeLatticeElement.DOUBLE;
+        return TypeLatticeElement.getDouble();
       case LONG_OR_DOUBLE:
-        return TypeLatticeElement.WIDE;
+        return TypeLatticeElement.getWide();
       default:
         throw new Unreachable("Unexpected constraint type: " + constraint);
     }
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 09cd2aa..21b182e 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
@@ -38,6 +38,7 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.Collection;
 import com.android.tools.r8.ir.desugar.InterfaceProcessor.InterfaceProcessorNestedGraphLense;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.position.MethodPosition;
@@ -913,6 +914,7 @@
    */
   public void desugarInterfaceMethods(
       Builder<?> builder,
+      OptimizationFeedback feedback,
       Flavor flavour,
       ExecutorService executorService)
       throws ExecutionException {
@@ -930,7 +932,8 @@
     // make original default methods abstract, remove bridge methods, create dispatch
     // classes if needed.
     AppInfo appInfo = appView.appInfo();
-    for (Entry<DexType, DexProgramClass> entry : processInterfaces(builder, flavour).entrySet()) {
+    for (Entry<DexType, DexProgramClass> entry :
+        processInterfaces(builder, feedback, flavour).entrySet()) {
       // Don't need to optimize synthesized class since all of its methods
       // are just moved from interfaces and don't need to be re-processed.
       DexProgramClass synthesizedClass = entry.getValue();
@@ -960,9 +963,10 @@
         && clazz.isInterface() == mustBeInterface;
   }
 
-  private Map<DexType, DexProgramClass> processInterfaces(Builder<?> builder, Flavor flavour) {
+  private Map<DexType, DexProgramClass> processInterfaces(
+      Builder<?> builder, OptimizationFeedback feedback, Flavor flavour) {
     NestedGraphLense.Builder graphLensBuilder = InterfaceProcessorNestedGraphLense.builder();
-    InterfaceProcessor processor = new InterfaceProcessor(appView, this);
+    InterfaceProcessor processor = new InterfaceProcessor(appView, this, feedback);
     for (DexProgramClass clazz : builder.getProgramClasses()) {
       if (shouldProcess(clazz, flavour, true)) {
         processor.process(clazz, graphLensBuilder);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index ccb0f3c..ef9b9c9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
@@ -56,13 +57,16 @@
 
   private final AppView<?> appView;
   private final InterfaceMethodRewriter rewriter;
+  private final OptimizationFeedback feedback;
 
   // All created companion and dispatch classes indexed by interface type.
   final Map<DexType, DexProgramClass> syntheticClasses = new IdentityHashMap<>();
 
-  InterfaceProcessor(AppView<?> appView, InterfaceMethodRewriter rewriter) {
+  InterfaceProcessor(
+      AppView<?> appView, InterfaceMethodRewriter rewriter, OptimizationFeedback feedback) {
     this.appView = appView;
     this.rewriter = rewriter;
+    this.feedback = feedback;
   }
 
   void process(DexProgramClass iface, NestedGraphLense.Builder graphLensBuilder) {
@@ -95,6 +99,7 @@
             code, companionMethod.getArity(), appView);
         DexEncodedMethod implMethod = new DexEncodedMethod(
             companionMethod, newFlags, virtual.annotations, virtual.parameterAnnotationsList, code, true);
+        implMethod.copyMetadata(virtual, feedback);
         virtual.setDefaultInterfaceMethodImplementation(implMethod);
         companionMethods.add(implMethod);
         graphLensBuilder.move(virtual.method, implMethod.method);
@@ -127,8 +132,17 @@
             : "Static interface method " + direct.toSourceString() + " is expected to "
             + "either be public or private in " + iface.origin;
         DexMethod companionMethod = rewriter.staticAsMethodOfCompanionClass(oldMethod);
-        companionMethods.add(new DexEncodedMethod(companionMethod, newFlags,
-            direct.annotations, direct.parameterAnnotationsList, direct.getCode(),true));
+        Code code = direct.getCode();
+        DexEncodedMethod implMethod =
+            new DexEncodedMethod(
+                companionMethod,
+                newFlags,
+                direct.annotations,
+                direct.parameterAnnotationsList,
+                direct.getCode(),
+                true);
+        implMethod.copyMetadata(direct, feedback);
+        companionMethods.add(implMethod);
         graphLensBuilder.move(oldMethod, companionMethod);
       } else {
         if (originalFlags.isPrivate()) {
@@ -146,8 +160,16 @@
           }
           DexEncodedMethod.setDebugInfoWithFakeThisParameter(
               code, companionMethod.getArity(), appView);
-          companionMethods.add(new DexEncodedMethod(companionMethod,
-              newFlags, direct.annotations, direct.parameterAnnotationsList, code,true));
+          DexEncodedMethod implMethod =
+              new DexEncodedMethod(
+                  companionMethod,
+                  newFlags,
+                  direct.annotations,
+                  direct.parameterAnnotationsList,
+                  code,
+                  true);
+          implMethod.copyMetadata(direct, feedback);
+          companionMethods.add(implMethod);
           graphLensBuilder.move(oldMethod, companionMethod);
         } else {
           // Since there are no interface constructors at this point,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaAccessorMethodWithSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaAccessorMethodWithSynthesizedCode.java
new file mode 100644
index 0000000..a733540
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaAccessorMethodWithSynthesizedCode.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.function.Consumer;
+
+public class LambdaAccessorMethodWithSynthesizedCode extends LambdaSynthesizedCode {
+
+  public LambdaAccessorMethodWithSynthesizedCode(LambdaClass lambda) {
+    super(lambda);
+  }
+
+  @Override
+  public SourceCodeProvider getSourceCodeProvider() {
+    return callerPosition -> new AccessorMethodSourceCode(lambda, callerPosition);
+  }
+
+  @Override
+  public Consumer<UseRegistry> getRegistryCallback() {
+    return registry -> {
+      DexMethodHandle handle = lambda.descriptor.implHandle;
+      DexMethod target = handle.asMethod();
+      switch (handle.type) {
+        case INVOKE_STATIC:
+          registry.registerInvokeStatic(target);
+          break;
+        case INVOKE_INSTANCE:
+          registry.registerInvokeVirtual(target);
+          break;
+        case INVOKE_CONSTRUCTOR:
+        case INVOKE_DIRECT:
+          registry.registerInvokeDirect(target);
+          break;
+        default:
+          throw new Unreachable("Unexpected handle type: " + handle.type);
+      }
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index e7d2f34..330d72a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.InternalOptions;
@@ -58,16 +59,16 @@
  * <p>Another reason is that if we generate an accessor, we generate it in the class referencing the
  * call site, and thus two such classes will require two separate lambda classes.
  */
-final class LambdaClass {
+public final class LambdaClass {
 
   final LambdaRewriter rewriter;
-  final DexType type;
-  final LambdaDescriptor descriptor;
-  final DexMethod constructor;
+  public final DexType type;
+  public LambdaDescriptor descriptor;
+  public final DexMethod constructor;
   final DexMethod classConstructor;
-  final DexField lambdaField;
-  final Target target;
-  final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
+  public final DexField lambdaField;
+  public final Target target;
+  public final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
   private final Collection<DexProgramClass> synthesizedFrom = new ArrayList<>(1);
   private final Supplier<DexProgramClass> lazyDexClass =
       Suppliers.memoize(this::synthesizeLambdaClass); // NOTE: thread-safe.
@@ -136,7 +137,7 @@
     return factory.createType(lambdaClassDescriptor.toString());
   }
 
-  final DexProgramClass getOrCreateLambdaClass() {
+  public final DexProgramClass getOrCreateLambdaClass() {
     return lazyDexClass.get();
   }
 
@@ -204,7 +205,7 @@
             rewriter.getFactory().createString("f$" + index));
   }
 
-  final boolean isStateless() {
+  public final boolean isStateless() {
     return descriptor.isStateless();
   }
 
@@ -505,7 +506,7 @@
   // Represents information about the method lambda class need to delegate the call to. It may
   // be the same method as specified in lambda descriptor or a newly synthesized accessor.
   // Also provides action for ensuring accessibility of the referenced symbols.
-  abstract class Target {
+  public abstract class Target {
 
     final DexMethod callTarget;
     final Invoke.Type invokeType;
@@ -521,12 +522,12 @@
     }
 
     // Ensure access of the referenced symbol(s).
-    abstract DexEncodedMethod ensureAccessibility();
+    abstract DexEncodedMethod ensureAccessibility(boolean allowMethodModification);
 
     // Ensure access of the referenced symbol(s).
-    DexEncodedMethod ensureAccessibilityIfNeeded() {
+    public DexEncodedMethod ensureAccessibilityIfNeeded(boolean allowMethodModification) {
       if (!hasEnsuredAccessibility) {
-        accessibilityBridge = ensureAccessibility();
+        accessibilityBridge = ensureAccessibility(allowMethodModification);
         hasEnsuredAccessibility = true;
       }
       return accessibilityBridge;
@@ -573,7 +574,7 @@
     }
 
     @Override
-    DexEncodedMethod ensureAccessibility() {
+    DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
       return null;
     }
   }
@@ -589,7 +590,7 @@
     }
 
     @Override
-    DexEncodedMethod ensureAccessibility() {
+    DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
       // We already found the static method to be called, just relax its accessibility.
       target.method.accessFlags.unsetPrivate();
       if (target.holder.isInterface()) {
@@ -608,7 +609,7 @@
     }
 
     @Override
-    DexEncodedMethod ensureAccessibility() {
+    DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
       // For all instantiation points for which the compiler creates lambda$
       // methods, it creates these methods in the same class/interface.
       DexMethod implMethod = descriptor.implHandle.asMethod();
@@ -658,12 +659,20 @@
     }
 
     @Override
-    DexEncodedMethod ensureAccessibility() {
+    DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
+      // When compiling with whole program optimization, check that we are not inplace modifying.
+      assert !(rewriter.getAppView().enableWholeProgramOptimizations() && allowMethodModification);
       // For all instantiation points for which the compiler creates lambda$
       // methods, it creates these methods in the same class/interface.
       DexMethod implMethod = descriptor.implHandle.asMethod();
       DexClass implMethodHolder = definitionFor(implMethod.holder);
+      return allowMethodModification
+          ? modifyLambdaImplementationMethod(implMethod, implMethodHolder)
+          : createSyntheticAccessor(implMethod, implMethodHolder);
+    }
 
+    private DexEncodedMethod modifyLambdaImplementationMethod(
+        DexMethod implMethod, DexClass implMethodHolder) {
       List<DexEncodedMethod> oldDirectMethods = implMethodHolder.directMethods();
       for (int i = 0; i < oldDirectMethods.size(); i++) {
         DexEncodedMethod encodedMethod = oldDirectMethods.get(i);
@@ -692,6 +701,35 @@
       }
       return null;
     }
+
+    private DexEncodedMethod createSyntheticAccessor(
+        DexMethod implMethod, DexClass implMethodHolder) {
+      MethodAccessFlags accessorFlags =
+          MethodAccessFlags.fromSharedAccessFlags(
+              Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC, false);
+
+      ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
+          ForwardMethodSourceCode.builder(callTarget)
+              .setReceiver(implMethod.holder)
+              .setTargetReceiver(implMethod.holder)
+              .setTarget(implMethod)
+              .setInvokeType(Type.DIRECT)
+              .setIsInterface(false);
+
+      DexEncodedMethod accessorEncodedMethod =
+          new DexEncodedMethod(
+              callTarget,
+              accessorFlags,
+              DexAnnotationSet.empty(),
+              ParameterAnnotationsList.empty(),
+              new SynthesizedCode(
+                  forwardSourceCodeBuilder::build,
+                  registry -> registry.registerInvokeDirect(implMethod)),
+              true);
+
+      implMethodHolder.appendVirtualMethod(accessorEncodedMethod);
+      return accessorEncodedMethod;
+    }
   }
 
   // Used for instance/static methods or constructors accessed via
@@ -703,7 +741,7 @@
     }
 
     @Override
-    DexEncodedMethod ensureAccessibility() {
+    DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
       // Create a static accessor with proper accessibility.
       DexProgramClass accessorClass = programDefinitionFor(callTarget.holder);
       assert accessorClass != null;
@@ -720,9 +758,7 @@
               accessorFlags,
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
-              new SynthesizedCode(
-                  callerPosition ->
-                      new AccessorMethodSourceCode(LambdaClass.this, callerPosition)),
+              new LambdaAccessorMethodWithSynthesizedCode(LambdaClass.this),
               true);
 
       // We may arrive here concurrently so we need must update the methods of the class atomically.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 2eb7b03..56270d5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -40,9 +40,9 @@
   final DexProto enforcedProto;
   public final DexMethodHandle implHandle;
 
-  final List<DexType> interfaces = new ArrayList<>();
+  public final List<DexType> interfaces = new ArrayList<>();
   final Set<DexProto> bridges = Sets.newIdentityHashSet();
-  final DexTypeList captures;
+  public final DexTypeList captures;
 
   // Used for accessibility analysis and few assertions only.
   private final MethodAccessFlags targetAccessFlags;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java
index 3e7e3f5..7c24b63 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java
@@ -9,9 +9,10 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Invoke.Type;
 import java.util.function.Consumer;
 
-class LambdaMainMethodSynthesizedCode extends LambdaSynthesizedCode {
+public class LambdaMainMethodSynthesizedCode extends LambdaSynthesizedCode {
 
   private final DexMethod mainMethod;
 
@@ -34,7 +35,10 @@
           || target.invokeType == Invoke.Type.DIRECT
           || target.invokeType == Invoke.Type.INTERFACE;
 
-      registry.registerNewInstance(target.callTarget.holder);
+      boolean constructorTarget = target.invokeType == Type.DIRECT;
+      if (constructorTarget) {
+        registry.registerNewInstance(target.callTarget.holder);
+      }
 
       DexType[] capturedTypes = captures();
       for (int i = 0; i < capturedTypes.length; i++) {
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 6ce1f5f..608fd4a 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
@@ -6,8 +6,6 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DefaultUseRegistry;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -30,9 +28,6 @@
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.LensCodeRewriter;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
@@ -47,7 +42,6 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
 
 /**
  * Lambda desugaring rewriter.
@@ -67,7 +61,6 @@
 
   final DexString instanceFieldName;
 
-  private final LambdaRewriterGraphLense graphLens;
   final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
 
   // Maps call sites seen so far to inferred lambda descriptor. It is intended
@@ -90,7 +83,6 @@
     assert appView.appInfo().hasClassHierarchy()
         : "Lambda desugaring is not available without class hierarchy.";
     this.appView = appView.withClassHierarchy();
-    this.graphLens = new LambdaRewriterGraphLense(appView);
     this.instanceFieldName = getFactory().createString(LAMBDA_INSTANCE_FIELD_NAME);
   }
 
@@ -106,125 +98,36 @@
     return getAppView().dexItemFactory();
   }
 
-  public void installGraphLens() {
-    appView.setGraphLense(graphLens);
-  }
-
-  public void synthesizeLambdaClassesForWave(
-      Collection<DexEncodedMethod> wave,
-      IRConverter converter,
-      ExecutorService executorService,
-      OptimizationFeedbackDelayed feedback,
-      LensCodeRewriter lensCodeRewriter)
-      throws ExecutionException {
-    Set<LambdaClass> synthesizedLambdaClasses = Sets.newIdentityHashSet();
-    Set<DexProgramClass> synthesizedLambdaProgramClasses = Sets.newIdentityHashSet();
-    for (DexEncodedMethod method : wave) {
-      synthesizeLambdaClassesForMethod(
-          method,
-          lambdaClass -> {
-            synthesizedLambdaClasses.add(lambdaClass);
-            synthesizedLambdaProgramClasses.add(lambdaClass.getOrCreateLambdaClass());
-          },
-          lensCodeRewriter);
-    }
-
-    if (synthesizedLambdaClasses.isEmpty()) {
-      return;
-    }
-
-    synthesizeAccessibilityBridgesForLambdaClasses(
-        synthesizedLambdaClasses, converter, executorService, feedback, lensCodeRewriter);
-
+  public Map<DexEncodedField, Set<DexEncodedMethod>> getWritesWithContexts(
+      DexProgramClass synthesizedLambdaClass) {
     // Record that the static fields on each lambda class are only written inside the static
     // initializer of the lambdas.
     Map<DexEncodedField, Set<DexEncodedMethod>> writesWithContexts = new IdentityHashMap<>();
-    for (DexProgramClass synthesizedLambdaClass : synthesizedLambdaProgramClasses) {
       DexEncodedMethod clinit = synthesizedLambdaClass.getClassInitializer();
       if (clinit != null) {
         for (DexEncodedField field : synthesizedLambdaClass.staticFields()) {
           writesWithContexts.put(field, ImmutableSet.of(clinit));
         }
       }
-    }
-
-    AppView<AppInfoWithLiveness> appViewWithLiveness = getAppView().withLiveness();
-    appViewWithLiveness.setAppInfo(
-        appViewWithLiveness.appInfo().withStaticFieldWrites(writesWithContexts));
-
-    converter.optimizeSynthesizedLambdaClasses(synthesizedLambdaProgramClasses, executorService);
-    feedback.updateVisibleOptimizationInfo();
+    return writesWithContexts;
   }
 
-  private void synthesizeAccessibilityBridgesForLambdaClasses(
+  private void synthesizeAccessibilityBridgesForLambdaClassesD8(
       Collection<LambdaClass> lambdaClasses, IRConverter converter, ExecutorService executorService)
       throws ExecutionException {
-    synthesizeAccessibilityBridgesForLambdaClasses(
-        lambdaClasses, converter, executorService, null, null);
-  }
-
-  private void synthesizeAccessibilityBridgesForLambdaClasses(
-      Collection<LambdaClass> lambdaClasses,
-      IRConverter converter,
-      ExecutorService executorService,
-      OptimizationFeedbackDelayed feedback,
-      LensCodeRewriter lensCodeRewriter)
-      throws ExecutionException {
     Set<DexEncodedMethod> nonDexAccessibilityBridges = Sets.newIdentityHashSet();
     for (LambdaClass lambdaClass : lambdaClasses) {
       // This call may cause originalMethodSignatures to be updated.
-      DexEncodedMethod accessibilityBridge = lambdaClass.target.ensureAccessibilityIfNeeded();
+      DexEncodedMethod accessibilityBridge = lambdaClass.target.ensureAccessibilityIfNeeded(true);
       if (accessibilityBridge != null && !accessibilityBridge.getCode().isDexCode()) {
         nonDexAccessibilityBridges.add(accessibilityBridge);
       }
     }
-    if (appView.enableWholeProgramOptimizations()) {
-      if (!originalMethodSignatures.isEmpty()) {
-        graphLens.addOriginalMethodSignatures(originalMethodSignatures);
-        originalMethodSignatures.clear();
-      }
-
-      // Ensure that all lambda classes referenced from accessibility bridges are synthesized
-      // prior to the IR processing of these accessibility bridges.
-      synthesizeLambdaClassesForWave(
-          nonDexAccessibilityBridges, converter, executorService, feedback, lensCodeRewriter);
-    }
     if (!nonDexAccessibilityBridges.isEmpty()) {
       converter.processMethodsConcurrently(nonDexAccessibilityBridges, executorService);
     }
   }
 
-  public void synthesizeLambdaClassesForMethod(
-      DexEncodedMethod method, Consumer<LambdaClass> consumer, LensCodeRewriter lensCodeRewriter) {
-    if (!method.hasCode() || method.isProcessed()) {
-      // Nothing to desugar.
-      return;
-    }
-
-    Code code = method.getCode();
-    if (!code.isCfCode()) {
-      // Nothing to desugar.
-      return;
-    }
-
-    // Introduce a lambda class in AppInfo for each call site such that we do not modify the
-    // application (and, in particular, the class hierarchy) during wave processing.
-    code.registerCodeReferences(
-        method,
-        new DefaultUseRegistry(getFactory()) {
-
-          @Override
-          public void registerCallSite(DexCallSite callSite) {
-            LambdaDescriptor descriptor =
-                inferLambdaDescriptor(
-                    lensCodeRewriter.rewriteCallSite(callSite, method), method.method.holder);
-            if (descriptor != LambdaDescriptor.MATCH_FAILED) {
-              consumer.accept(getOrCreateLambdaClass(descriptor, method.method.holder));
-            }
-          }
-        });
-  }
-
   /**
    * Detect and desugar lambdas and method references found in the code.
    *
@@ -296,7 +199,7 @@
   public void finalizeLambdaDesugaringForD8(
       Builder<?> builder, IRConverter converter, ExecutorService executorService)
       throws ExecutionException {
-    synthesizeAccessibilityBridgesForLambdaClasses(
+    synthesizeAccessibilityBridgesForLambdaClassesD8(
         knownLambdaClasses.values(), converter, executorService);
     for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
       DexProgramClass synthesizedClass = lambdaClass.getOrCreateLambdaClass();
@@ -315,18 +218,6 @@
         executorService);
   }
 
-  /** Generates lambda classes and adds them to the builder. */
-  public void finalizeLambdaDesugaringForR8(Builder<?> builder) {
-    // Verify that all mappings have been published to the LambdaRewriterGraphLense.
-    assert originalMethodSignatures.isEmpty();
-    graphLens.markShouldMapInvocationType();
-    for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
-      DexProgramClass synthesizedClass = lambdaClass.getOrCreateLambdaClass();
-      getAppInfo().addSynthesizedClass(synthesizedClass);
-      builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
-    }
-  }
-
   public Set<DexCallSite> getDesugaredCallSites() {
     synchronized (knownCallSites) {
       return knownCallSites.keySet();
@@ -357,7 +248,7 @@
 
   // Returns a lambda class corresponding to the lambda descriptor and context,
   // creates the class if it does not yet exist.
-  private LambdaClass getOrCreateLambdaClass(LambdaDescriptor descriptor, DexType accessedFrom) {
+  public LambdaClass getOrCreateLambdaClass(LambdaDescriptor descriptor, DexType accessedFrom) {
     DexType lambdaClassType = LambdaClass.createLambdaClassType(this, accessedFrom, descriptor);
     // We check the map twice to to minimize time spent in synchronized block.
     LambdaClass lambdaClass = getKnown(knownLambdaClasses, lambdaClassType);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
deleted file mode 100644
index 9903607..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.desugar;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
-import com.android.tools.r8.ir.code.Invoke;
-import com.google.common.collect.HashBiMap;
-import com.google.common.collect.ImmutableBiMap;
-import com.google.common.collect.ImmutableMap;
-import java.util.Map;
-
-class LambdaRewriterGraphLense extends NestedGraphLense {
-
-  // We don't map the invocation type in the lens code rewriter for lambda accessibility bridges to
-  // ensure that we always compute the same unique id for invoke-custom instructions.
-  //
-  // TODO(b/129458850): It might be possible to avoid this by performing lambda desugaring prior to
-  //  lens code rewriting in the IR converter.
-  private boolean shouldMapInvocationType = false;
-
-  LambdaRewriterGraphLense(AppView<?> appView) {
-    super(
-        ImmutableMap.of(),
-        ImmutableMap.of(),
-        ImmutableMap.of(),
-        ImmutableBiMap.of(),
-        HashBiMap.create(),
-        appView.graphLense(),
-        appView.dexItemFactory());
-  }
-
-  @Override
-  protected boolean isLegitimateToHaveEmptyMappings() {
-    return true;
-  }
-
-  void addOriginalMethodSignatures(Map<DexMethod, DexMethod> originalMethodSignatures) {
-    this.originalMethodSignatures.putAll(originalMethodSignatures);
-  }
-
-  void markShouldMapInvocationType() {
-    shouldMapInvocationType = true;
-  }
-
-  @Override
-  protected Invoke.Type mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod, Invoke.Type type) {
-    if (shouldMapInvocationType && methodMap.get(originalMethod) == newMethod) {
-      assert type == Invoke.Type.VIRTUAL || type == Invoke.Type.DIRECT;
-      return Invoke.Type.STATIC;
-    }
-    return super.mapInvocationType(newMethod, originalMethod, type);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
index a344095..b19e557 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThrowingCharIterator;
+import com.android.tools.r8.utils.Timing;
 import java.io.UTFDataFormatException;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -216,10 +217,15 @@
    * }
    * </pre>
    */
-  public void run(DexEncodedMethod method, IRCode code) {
-    if (!enabled) {
-      return;
+  public void run(DexEncodedMethod method, IRCode code, Timing timing) {
+    if (enabled) {
+      timing.begin("Rewrite assertions");
+      runInternal(method, code);
+      timing.end();
     }
+  }
+
+  private void runInternal(DexEncodedMethod method, IRCode code) {
     AssertionTransformation transformation = getTransformationForMethod(method);
     if (transformation == AssertionTransformation.PASSTHROUGH) {
       return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index fb75832..52d047e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -60,6 +60,30 @@
 
 public class ClassInitializerDefaultsOptimization {
 
+  public static class ClassInitializerDefaultsResult {
+
+    private static final ClassInitializerDefaultsResult EMPTY =
+        new ClassInitializerDefaultsResult(null);
+
+    private final Map<DexEncodedField, DexValue> fieldsWithStaticValues;
+
+    ClassInitializerDefaultsResult(Map<DexEncodedField, DexValue> fieldsWithStaticValues) {
+      this.fieldsWithStaticValues = fieldsWithStaticValues;
+    }
+
+    public static ClassInitializerDefaultsResult empty() {
+      return EMPTY;
+    }
+
+    public boolean hasStaticValue(DexEncodedField field) {
+      if (field.isStatic()) {
+        return (fieldsWithStaticValues != null && fieldsWithStaticValues.containsKey(field))
+            || field.getStaticValue() != null;
+      }
+      return false;
+    }
+  }
+
   private class WaveDoneAction implements Action {
 
     private final Map<DexEncodedField, DexValue> fieldsWithStaticValues;
@@ -103,14 +127,18 @@
     this.dexItemFactory = appView.dexItemFactory();
   }
 
-  public void optimize(DexEncodedMethod method, IRCode code) {
+  public ClassInitializerDefaultsResult optimize(DexEncodedMethod method, IRCode code) {
+    if (appView.options().debug || method.getOptimizationInfo().isReachabilitySensitive()) {
+      return ClassInitializerDefaultsResult.empty();
+    }
+
     if (!method.isClassInitializer()) {
-      return;
+      return ClassInitializerDefaultsResult.empty();
     }
 
     DexClass clazz = appView.definitionFor(method.method.holder);
     if (clazz == null) {
-      return;
+      return ClassInitializerDefaultsResult.empty();
     }
 
     // Collect straight-line static puts up to the first side-effect that is not
@@ -123,7 +151,7 @@
     // Return eagerly if there is nothing to optimize.
     if (finalFieldPuts.isEmpty()) {
       assert unnecessaryStaticPuts.isEmpty();
-      return;
+      return ClassInitializerDefaultsResult.empty();
     }
 
     Map<DexEncodedField, DexValue> fieldsWithStaticValues = new IdentityHashMap<>();
@@ -253,6 +281,8 @@
     } else {
       fieldsWithStaticValues.forEach(DexEncodedField::setStaticValue);
     }
+
+    return new ClassInitializerDefaultsResult(fieldsWithStaticValues);
   }
 
   private DexValue getDexStringValue(Value inValue, DexType holder) {
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 65e86b7..51fe0fe 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
@@ -1477,7 +1477,7 @@
           new ConstNumber(
               new Value(
                   code.valueNumberGenerator.next(),
-                  TypeLatticeElement.INT,
+                  TypeLatticeElement.getInt(),
                   instanceOf.outValue().getLocalInfo()),
               result == InstanceOfResult.TRUE ? 1 : 0);
       it.replaceCurrentInstruction(newInstruction);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
new file mode 100644
index 0000000..88166e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
@@ -0,0 +1,323 @@
+// 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;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class EnumUnboxer {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final Set<DexType> enumsToUnbox;
+
+  private final boolean debugLogEnabled;
+  private final Map<DexType, Reason> debugLogs;
+  private final DexItemFactory factory;
+
+  public EnumUnboxer(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+    this.factory = appView.dexItemFactory();
+    if (appView.options().testing.enableEnumUnboxingDebugLogs) {
+      debugLogEnabled = true;
+      debugLogs = new ConcurrentHashMap<>();
+    } else {
+      debugLogEnabled = false;
+      debugLogs = null;
+    }
+    enumsToUnbox = new EnumUnboxingCandidateAnalysis(appView, this).findCandidates();
+  }
+
+  public void unboxEnums(IRCode code) {
+    // TODO(b/147860220): To implement.
+    // Do not forget static get, which is implicitly valid (no inValue).
+  }
+
+  public void analyzeEnums(IRCode code) {
+    // Enum <clinit> and <init> are analyzed in between the two processing phases using optimization
+    // feedback.
+    DexClass dexClass = appView.definitionFor(code.method.method.holder);
+    if (dexClass.isEnum() && code.method.isInitializer()) {
+      return;
+    }
+    analyzeEnumsInMethod(code);
+  }
+
+  private void markEnumAsUnboxable(Reason reason, DexProgramClass enumClass) {
+    assert enumClass.isEnum();
+    reportFailure(enumClass.type, reason);
+    enumsToUnbox.remove(enumClass.type);
+  }
+
+  private DexProgramClass getEnumUnboxingCandidateOrNull(TypeLatticeElement lattice) {
+    if (lattice.isClassType()) {
+      DexType classType = lattice.asClassTypeLatticeElement().getClassType();
+      return getEnumUnboxingCandidateOrNull(classType);
+    }
+    if (lattice.isArrayType()) {
+      ArrayTypeLatticeElement arrayLattice = lattice.asArrayTypeLatticeElement();
+      if (arrayLattice.getArrayBaseTypeLattice().isClassType()) {
+        DexType classType =
+            arrayLattice.getArrayBaseTypeLattice().asClassTypeLatticeElement().getClassType();
+        return getEnumUnboxingCandidateOrNull(classType);
+      }
+    }
+    return null;
+  }
+
+  private DexProgramClass getEnumUnboxingCandidateOrNull(DexType anyType) {
+    if (!enumsToUnbox.contains(anyType)) {
+      return null;
+    }
+    return appView.definitionForProgramType(anyType);
+  }
+
+  private void analyzeEnumsInMethod(IRCode code) {
+    for (BasicBlock block : code.blocks) {
+      for (Instruction instruction : block.getInstructions()) {
+        Value outValue = instruction.outValue();
+        DexProgramClass enumClass =
+            outValue == null ? null : getEnumUnboxingCandidateOrNull(outValue.getTypeLattice());
+        if (enumClass != null) {
+          validateEnumUsages(code, outValue.uniqueUsers(), outValue.uniquePhiUsers(), enumClass);
+        }
+      }
+      for (Phi phi : block.getPhis()) {
+        DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(phi.getTypeLattice());
+        if (enumClass != null) {
+          validateEnumUsages(code, phi.uniqueUsers(), phi.uniquePhiUsers(), enumClass);
+        }
+      }
+    }
+  }
+
+  private Reason validateEnumUsages(
+      IRCode code, Set<Instruction> uses, Set<Phi> phiUses, DexProgramClass enumClass) {
+    for (Instruction user : uses) {
+      Reason reason = instructionAllowEnumUnboxing(user, code, enumClass);
+      if (reason != Reason.ELIGIBLE) {
+        markEnumAsUnboxable(reason, enumClass);
+        return reason;
+      }
+    }
+    for (Phi phi : phiUses) {
+      for (Value operand : phi.getOperands()) {
+        if (getEnumUnboxingCandidateOrNull(operand.getTypeLattice()) != enumClass) {
+          markEnumAsUnboxable(Reason.INVALID_PHI, enumClass);
+          return Reason.INVALID_PHI;
+        }
+      }
+    }
+    return Reason.ELIGIBLE;
+  }
+
+  public void finishEnumAnalysis() {
+    for (DexType toUnbox : enumsToUnbox) {
+      DexProgramClass enumClass = appView.definitionForProgramType(toUnbox);
+      assert enumClass != null;
+
+      DexEncodedMethod initializer = enumClass.lookupDirectMethod(factory.enumMethods.constructor);
+      assert initializer != null;
+      if (initializer.getOptimizationInfo().mayHaveSideEffects()) {
+        markEnumAsUnboxable(Reason.INVALID_INIT, enumClass);
+        continue;
+      }
+
+      if (enumClass.classInitializationMayHaveSideEffects(appView)) {
+        markEnumAsUnboxable(Reason.INVALID_CLINIT, enumClass);
+        continue;
+      }
+
+      Map<DexField, EnumValueInfo> enumValueInfoMapFor =
+          appView.appInfo().withLiveness().getEnumValueInfoMapFor(enumClass.type);
+      if (enumValueInfoMapFor == null) {
+        markEnumAsUnboxable(Reason.MISSING_INFO_MAP, enumClass);
+        continue;
+      }
+      if (enumValueInfoMapFor.size() != enumClass.staticFields().size() - 1) {
+        markEnumAsUnboxable(Reason.UNEXPECTED_STATIC_FIELD, enumClass);
+      }
+    }
+    if (debugLogEnabled) {
+      reportEnumsAnalysis();
+    }
+  }
+
+  private Reason instructionAllowEnumUnboxing(
+      Instruction instruction, IRCode code, DexProgramClass enumClass) {
+
+    // All invokes in the library are invalid, besides a few cherry picked cases such as ordinal().
+    if (instruction.isInvokeMethod()) {
+      InvokeMethod invokeMethod = instruction.asInvokeMethod();
+      if (invokeMethod.getInvokedMethod().holder.isArrayType()) {
+        // The only valid methods is clone for values() to be correct.
+        if (invokeMethod.getInvokedMethod().name == factory.cloneMethodName) {
+          return Reason.ELIGIBLE;
+        }
+        return Reason.INVALID_INVOKE_ON_ARRAY;
+      }
+      DexEncodedMethod invokedEncodedMethod =
+          invokeMethod.lookupSingleTarget(appView, code.method.method.holder);
+      if (invokedEncodedMethod == null) {
+        return Reason.INVALID_INVOKE;
+      }
+      DexMethod invokedMethod = invokedEncodedMethod.method;
+      DexClass dexClass = appView.definitionFor(invokedMethod.holder);
+      if (dexClass == null) {
+        return Reason.INVALID_INVOKE;
+      }
+      if (dexClass.isProgramClass()) {
+        // All invokes in the program are generally valid, but specific care is required
+        // for values() and valueOf().
+        if (dexClass.isEnum() && factory.enumMethods.isValuesMethod(invokedMethod, dexClass)) {
+          return Reason.VALUES_INVOKE;
+        }
+        if (dexClass.isEnum() && factory.enumMethods.isValueOfMethod(invokedMethod, dexClass)) {
+          return Reason.VALUE_OF_INVOKE;
+        }
+        return Reason.ELIGIBLE;
+      }
+      if (dexClass.isClasspathClass()) {
+        return Reason.INVALID_INVOKE;
+      }
+      assert dexClass.isLibraryClass();
+      if (dexClass.type != factory.enumType) {
+        return Reason.UNSUPPORTED_LIBRARY_CALL;
+      }
+      // TODO(b/147860220): Methods toString(), name(), compareTo(), EnumSet and EnumMap may be
+      // interesting to model. A the moment rewrite only Enum#ordinal().
+      if (debugLogEnabled) {
+        if (invokedMethod == factory.enumMethods.compareTo) {
+          return Reason.COMPARE_TO_INVOKE;
+        }
+        if (invokedMethod == factory.enumMethods.name) {
+          return Reason.NAME_INVOKE;
+        }
+        if (invokedMethod == factory.enumMethods.toString) {
+          return Reason.TO_STRING_INVOKE;
+        }
+      }
+      if (invokedMethod != factory.enumMethods.ordinal) {
+        return Reason.UNSUPPORTED_LIBRARY_CALL;
+      }
+      return Reason.ELIGIBLE;
+    }
+
+    // A field put is valid only if the field is not on an enum, and the field type and the valuePut
+    // have identical enum type.
+    if (instruction.isFieldPut()) {
+      FieldInstruction fieldInstruction = instruction.asFieldInstruction();
+      DexEncodedField field = appView.appInfo().resolveField(fieldInstruction.getField());
+      if (field == null) {
+        return Reason.INVALID_FIELD_PUT;
+      }
+      DexProgramClass dexClass = appView.definitionForProgramType(field.field.holder);
+      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;
+      }
+      return Reason.ELIGIBLE;
+    }
+
+    if (instruction.isAssume()) {
+      Value outValue = instruction.outValue();
+      return validateEnumUsages(code, outValue.uniqueUsers(), outValue.uniquePhiUsers(), enumClass);
+    }
+
+    // Return is used for valueOf methods.
+    if (instruction.isReturn()) {
+      DexType returnType = code.method.method.proto.returnType;
+      if (returnType != enumClass.type && returnType.toBaseType(factory) != enumClass.type) {
+        return Reason.IMPLICIT_UP_CAST_IN_RETURN;
+      }
+      return Reason.ELIGIBLE;
+    }
+
+    return Reason.OTHER_UNSUPPORTED_INSTRUCTION;
+  }
+
+  private void reportEnumsAnalysis() {
+    assert debugLogEnabled;
+    Reporter reporter = appView.options().reporter;
+    reporter.info(
+        new StringDiagnostic(
+            "Unboxed enums (Unboxing succeeded "
+                + enumsToUnbox.size()
+                + "): "
+                + Arrays.toString(enumsToUnbox.toArray())));
+    StringBuilder sb = new StringBuilder();
+    sb.append("Boxed enums (Unboxing failed ").append(debugLogs.size()).append("):\n");
+    for (DexType enumType : debugLogs.keySet()) {
+      sb.append("- ")
+          .append(enumType)
+          .append(": ")
+          .append(debugLogs.get(enumType).toString())
+          .append('\n');
+    }
+    reporter.info(new StringDiagnostic(sb.toString()));
+  }
+
+  void reportFailure(DexType enumType, Reason reason) {
+    if (debugLogEnabled) {
+      debugLogs.put(enumType, reason);
+    }
+  }
+
+  public enum Reason {
+    ELIGIBLE,
+    SUBTYPES,
+    INTERFACE,
+    INSTANCE_FIELD,
+    UNEXPECTED_STATIC_FIELD,
+    VIRTUAL_METHOD,
+    UNEXPECTED_DIRECT_METHOD,
+    INVALID_PHI,
+    INVALID_INIT,
+    INVALID_CLINIT,
+    INVALID_INVOKE,
+    INVALID_INVOKE_ON_ARRAY,
+    IMPLICIT_UP_CAST_IN_RETURN,
+    VALUE_OF_INVOKE,
+    VALUES_INVOKE,
+    COMPARE_TO_INVOKE,
+    TO_STRING_INVOKE,
+    NAME_INVOKE,
+    UNSUPPORTED_LIBRARY_CALL,
+    MISSING_INFO_MAP,
+    INVALID_FIELD_PUT,
+    FIELD_PUT_ON_ENUM,
+    TYPE_MISSMATCH_FIELD_PUT,
+    OTHER_UNSUPPORTED_INSTRUCTION;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java
new file mode 100644
index 0000000..3fb84e1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java
@@ -0,0 +1,114 @@
+// 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;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.EnumUnboxer.Reason;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+class EnumUnboxingCandidateAnalysis {
+
+  private final AppView<?> appView;
+  private final EnumUnboxer enumUnboxer;
+  private final DexItemFactory factory;
+
+  EnumUnboxingCandidateAnalysis(AppView<?> appView, EnumUnboxer enumUnboxer) {
+    this.appView = appView;
+    this.enumUnboxer = enumUnboxer;
+    factory = appView.dexItemFactory();
+  }
+
+  Set<DexType> findCandidates() {
+    Set<DexType> enums = Sets.newConcurrentHashSet();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (isEnumUnboxingCandidate(clazz)) {
+        enums.add(clazz.type);
+      }
+    }
+    return enums;
+  }
+
+  private boolean isEnumUnboxingCandidate(DexProgramClass clazz) {
+    if (!clazz.isEnum()) {
+      return false;
+    }
+    if (!clazz.isEffectivelyFinal(appView)) {
+      enumUnboxer.reportFailure(clazz.type, Reason.SUBTYPES);
+      return false;
+    }
+    // TODO(b/147860220): interfaces without default methods should be acceptable if the build setup
+    // is correct (all abstract methods are implemented).
+    if (!clazz.interfaces.isEmpty()) {
+      enumUnboxer.reportFailure(clazz.type, Reason.INTERFACE);
+      return false;
+    }
+    if (!clazz.instanceFields().isEmpty()) {
+      enumUnboxer.reportFailure(clazz.type, Reason.INSTANCE_FIELD);
+      return false;
+    }
+    if (!enumHasBasicStaticFields(clazz)) {
+      enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_STATIC_FIELD);
+      return false;
+    }
+    if (!clazz.virtualMethods().isEmpty()) {
+      enumUnboxer.reportFailure(clazz.type, Reason.VIRTUAL_METHOD);
+      return false;
+    }
+    // Methods values, valueOf, init, clinit are present on each enum.
+    // Methods init and clinit are required if the enum is used.
+    // Methods valueOf and values are normally kept by the commonly used/recommended enum keep rule
+    // -keepclassmembers,allowoptimization enum * {
+    //     public static **[] values();
+    //     public static ** valueOf(java.lang.String);
+    // }
+    // In general there will be 4 methods, unless the enum keep rule is not present.
+    if (clazz.directMethods().size() > 4) {
+      enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_DIRECT_METHOD);
+      return false;
+    }
+    for (DexEncodedMethod directMethod : clazz.directMethods()) {
+      if (!(factory.enumMethods.isValuesMethod(directMethod.method, clazz)
+          || factory.enumMethods.isValueOfMethod(directMethod.method, clazz)
+          || isStandardEnumInitializer(directMethod)
+          || directMethod.isClassInitializer())) {
+        enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_DIRECT_METHOD);
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private boolean isStandardEnumInitializer(DexEncodedMethod method) {
+    return method.isInstanceInitializer()
+        && method.method.proto == factory.enumMethods.constructor.proto;
+  }
+
+  // The enum should have the $VALUES static field and only fields directly referencing the enum
+  // instances.
+  private boolean enumHasBasicStaticFields(DexProgramClass clazz) {
+    for (DexEncodedField staticField : clazz.staticFields()) {
+      if (staticField.field.type == clazz.type
+          && staticField.accessFlags.isEnum()
+          && staticField.accessFlags.isFinal()) {
+        // Enum field, valid, do nothing.
+      } else if (staticField.field.type.isArrayType()
+          && staticField.field.type.toArrayElementType(factory) == clazz.type
+          && staticField.accessFlags.isSynthetic()
+          && staticField.accessFlags.isFinal()
+          && staticField.field.name == factory.enumValuesFieldName) {
+        // Field $VALUES, valid, do nothing.
+      } else {
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 5df7550..722625f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -17,13 +17,12 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.IRMetadata;
-import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
@@ -161,9 +160,6 @@
                 code.origin));
         return null;
       }
-      if (replacement.isDexItemBasedConstString()) {
-        code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
-      }
       return replacement;
     }
 
@@ -294,16 +290,16 @@
     }
   }
 
-  private void rewriteStaticGetWithConstantValues(
+  private void rewriteFieldGetWithConstantValues(
       IRCode code,
       Set<Value> affectedValues,
       ListIterator<BasicBlock> blocks,
       InstructionListIterator iterator,
-      StaticGet current) {
+      FieldInstruction current) {
     DexField field = current.getField();
 
     // TODO(b/123857022): Should be able to use definitionFor().
-    DexEncodedField target = appView.appInfo().lookupStaticTarget(field.holder, field);
+    DexEncodedField target = appView.appInfo().resolveField(field);
     if (target == null) {
       boolean replaceCurrentInstructionWithConstNull =
           appView.withGeneratedExtensionRegistryShrinker(
@@ -314,6 +310,10 @@
       return;
     }
 
+    if (target.isStatic() != current.isStaticGet()) {
+      return;
+    }
+
     if (!mayPropagateValueFor(target)) {
       return;
     }
@@ -336,9 +336,9 @@
         target.valueAsConstInstruction(code, current.outValue().getLocalInfo(), appView);
     if (replacement != null) {
       affectedValues.addAll(current.outValue().affectedValues());
-      if (target.mayTriggerClassInitializationSideEffects(appView, code.method.method.holder)) {
-        // To preserve class initialization side effects, original static-get remains as-is, but its
-        // value is replaced with constant.
+      if (current.instructionMayHaveSideEffects(appView, code.method.method.holder)) {
+        // To preserve class initialization/NPE side effects, original field-get remains as-is,
+        // but its value is replaced with constant.
         replacement.setPosition(current.getPosition());
         current.outValue().replaceUsers(replacement.outValue());
         if (current.getBlock().hasCatchHandlers()) {
@@ -349,39 +349,6 @@
       } else {
         iterator.replaceCurrentInstruction(replacement);
       }
-      if (replacement.isDexItemBasedConstString()) {
-        code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
-      }
-      feedback.markFieldAsPropagated(target);
-    }
-  }
-
-  private void rewriteInstanceGetWithConstantValues(
-      IRCode code,
-      Set<Value> affectedValues,
-      InstructionListIterator iterator,
-      InstanceGet current) {
-    if (current.object().getTypeLattice().isNullable()) {
-      return;
-    }
-
-    DexField field = current.getField();
-
-    // TODO(b/123857022): Should be able to use definitionFor().
-    DexEncodedField target = appView.appInfo().lookupInstanceTarget(field.holder, field);
-    if (target == null || !mayPropagateValueFor(target)) {
-      return;
-    }
-
-    // Check if a this value is known const.
-    Instruction replacement =
-        target.valueAsConstInstruction(code, current.outValue().getLocalInfo(), appView);
-    if (replacement != null) {
-      affectedValues.add(replacement.outValue());
-      iterator.replaceCurrentInstruction(replacement);
-      if (replacement.isDexItemBasedConstString()) {
-        code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
-      }
       feedback.markFieldAsPropagated(target);
     }
   }
@@ -407,16 +374,9 @@
         if (current.isInvokeMethod()) {
           rewriteInvokeMethodWithConstantValues(
               code, callingContext, affectedValues, blocks, iterator, current.asInvokeMethod());
-        } else if (current.isStaticGet()) {
-          rewriteStaticGetWithConstantValues(
-              code,
-              affectedValues,
-              blocks,
-              iterator,
-              current.asStaticGet());
-        } else if (current.isInstanceGet()) {
-          rewriteInstanceGetWithConstantValues(
-              code, affectedValues, iterator, current.asInstanceGet());
+        } else if (current.isFieldGet()) {
+          rewriteFieldGetWithConstantValues(
+              code, affectedValues, blocks, iterator, current.asFieldInstruction());
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
index c6aecbb..b679383 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
@@ -47,7 +47,7 @@
           if (group.isSingletonLambda(lambda)) {
             int id = group.lambdaId(lambda);
             add(builder -> builder.addNewInstance(instance, groupClassType));
-            add(builder -> builder.addConst(TypeLatticeElement.INT, lambdaId, id));
+            add(builder -> builder.addConst(TypeLatticeElement.getInt(), lambdaId, id));
             add(
                 builder ->
                     builder.addInvoke(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
index 9e07c44..49eaa46 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
@@ -232,7 +232,7 @@
     @Override
     void prepareSuperConstructorCall(int receiverRegister) {
       int arityRegister = nextRegister(ValueType.INT);
-      add(builder -> builder.addConst(TypeLatticeElement.INT, arityRegister, arity));
+      add(builder -> builder.addConst(TypeLatticeElement.getInt(), arityRegister, arity));
       add(
           builder ->
               builder.addInvoke(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
index f17535a..e54d241 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
@@ -213,7 +213,7 @@
     DexType lambda = method.holder;
 
     // Create constant with lambda id.
-    Value lambdaIdValue = context.code.createValue(TypeLatticeElement.INT);
+    Value lambdaIdValue = context.code.createValue(TypeLatticeElement.getInt());
     ConstNumber lambdaId = new ConstNumber(lambdaIdValue, group.lambdaId(lambda));
     lambdaId.setPosition(invoke.getPosition());
     context.instructions().previous();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
index 9c5a8d1..91e952a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
@@ -32,6 +32,10 @@
   public LibraryMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
     this.appView = appView;
     register(new BooleanMethodOptimizer(appView));
+
+    if (LogMethodOptimizer.isEnabled(appView)) {
+      register(new LogMethodOptimizer(appView));
+    }
   }
 
   private void register(LibraryMethodModelCollection optimizer) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
new file mode 100644
index 0000000..f65b57a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
@@ -0,0 +1,154 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.library;
+
+import 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;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
+
+public class LogMethodOptimizer implements LibraryMethodModelCollection {
+
+  private static final int VERBOSE = 2;
+  private static final int DEBUG = 3;
+  private static final int INFO = 4;
+  private static final int WARN = 5;
+  private static final int ERROR = 6;
+  private static final int ASSERT = 7;
+
+  private final AppView<? extends AppInfoWithSubtyping> appView;
+
+  private final DexType logType;
+
+  private final DexMethod isLoggableMethod;
+  private final DexMethod vMethod;
+  private final DexMethod dMethod;
+  private final DexMethod iMethod;
+  private final DexMethod wMethod;
+  private final DexMethod eMethod;
+  private final DexMethod wtfMethod;
+
+  LogMethodOptimizer(AppView<? extends AppInfoWithSubtyping> appView) {
+    this.appView = appView;
+
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexType logType = dexItemFactory.createType("Landroid/util/Log;");
+    this.logType = logType;
+    this.isLoggableMethod =
+        dexItemFactory.createMethod(
+            logType,
+            dexItemFactory.createProto(
+                dexItemFactory.booleanType, dexItemFactory.stringType, dexItemFactory.intType),
+            "isLoggable");
+    this.vMethod =
+        dexItemFactory.createMethod(
+            logType,
+            dexItemFactory.createProto(
+                dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+            "v");
+    this.dMethod =
+        dexItemFactory.createMethod(
+            logType,
+            dexItemFactory.createProto(
+                dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+            "d");
+    this.iMethod =
+        dexItemFactory.createMethod(
+            logType,
+            dexItemFactory.createProto(
+                dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+            "i");
+    this.wMethod =
+        dexItemFactory.createMethod(
+            logType,
+            dexItemFactory.createProto(
+                dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+            "w");
+    this.eMethod =
+        dexItemFactory.createMethod(
+            logType,
+            dexItemFactory.createProto(
+                dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+            "e");
+    this.wtfMethod =
+        dexItemFactory.createMethod(
+            logType,
+            dexItemFactory.createProto(
+                dexItemFactory.intType, dexItemFactory.stringType, dexItemFactory.stringType),
+            "wtf");
+  }
+
+  public static boolean isEnabled(AppView<?> appView) {
+    return appView.options().getProguardConfiguration().getMaxRemovedAndroidLogLevel() >= VERBOSE;
+  }
+
+  @Override
+  public DexType getType() {
+    return logType;
+  }
+
+  @Override
+  public void optimize(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      DexEncodedMethod singleTarget,
+      Set<Value> affectedValues) {
+    int maxRemovedAndroidLogLevel =
+        appView.options().getProguardConfiguration().getMaxRemovedAndroidLogLevel();
+    if (singleTarget.method == isLoggableMethod) {
+      Value logLevelValue = invoke.arguments().get(1).getAliasedValue();
+      if (!logLevelValue.isPhi() && !logLevelValue.hasLocalInfo()) {
+        Instruction definition = logLevelValue.definition;
+        if (definition.isConstNumber()) {
+          int logLevel = definition.asConstNumber().getIntValue();
+          replaceInvokeWithConstNumber(
+              code, instructionIterator, invoke, maxRemovedAndroidLogLevel >= logLevel ? 0 : 1);
+        }
+      }
+    } else if (singleTarget.method == vMethod) {
+      if (maxRemovedAndroidLogLevel >= VERBOSE) {
+        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+      }
+    } else if (singleTarget.method == dMethod) {
+      if (maxRemovedAndroidLogLevel >= DEBUG) {
+        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+      }
+    } else if (singleTarget.method == iMethod) {
+      if (maxRemovedAndroidLogLevel >= INFO) {
+        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+      }
+    } else if (singleTarget.method == wMethod) {
+      if (maxRemovedAndroidLogLevel >= WARN) {
+        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+      }
+    } else if (singleTarget.method == eMethod) {
+      if (maxRemovedAndroidLogLevel >= ERROR) {
+        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+      }
+    } else if (singleTarget.method == wtfMethod) {
+      if (maxRemovedAndroidLogLevel >= ASSERT) {
+        replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
+      }
+    }
+  }
+
+  private void replaceInvokeWithConstNumber(
+      IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, int value) {
+    if (invoke.hasOutValue() && invoke.outValue().hasAnyUsers()) {
+      instructionIterator.replaceCurrentInstructionWithConstInt(appView, code, value);
+    } else {
+      instructionIterator.removeOrReplaceByDebugLocalRead();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java
index c50791e..49a74d1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java
@@ -113,7 +113,7 @@
     Instruction current = it.next();
     if (position != current.getPosition()
         || !current.isConstNumber()
-        || current.outValue().getTypeLattice() != TypeLatticeElement.INT
+        || current.outValue().getTypeLattice() != TypeLatticeElement.getInt()
         || current.asConstNumber().getIntValue() < -128
         || current.asConstNumber().getIntValue() > 127
         || !it.hasNext()) {
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 e019acd..247f13e 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
@@ -25,6 +25,7 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer.CandidateInfo;
@@ -32,6 +33,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableList;
@@ -298,7 +300,7 @@
     IRCode code = method.buildIR(appView, origin);
     codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code));
     CodeRewriter.removeAssumeInstructions(appView, code);
-    converter.finalizeIR(method, code, feedback);
+    converter.finalizeIR(method, code, feedback, Timing.empty());
   }
 
   private void insertAssumeInstructions(IRCode code) {
@@ -306,7 +308,8 @@
   }
 
   private Consumer<IRCode> collectOptimizationInfo(OptimizationFeedback feedback) {
-    return code -> converter.collectOptimizationInfo(code, feedback);
+    return code ->
+        converter.collectOptimizationInfo(code, ClassInitializerDefaultsResult.empty(), feedback);
   }
 
   private void removeCandidateInstantiation(IRCode code) {
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 41e1264..072f762 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
@@ -305,7 +305,6 @@
       return;
     }
     Set<Value> affectedValues = Sets.newIdentityHashSet();
-    boolean markUseIdentifierNameString = false;
     InstructionListIterator it = code.instructionListIterator();
     while (it.hasNext()) {
       Instruction instr = it.next();
@@ -452,7 +451,6 @@
       } else if (deferred != null) {
         affectedValues.addAll(invoke.outValue().affectedValues());
         it.replaceCurrentInstruction(deferred);
-        markUseIdentifierNameString = true;
         logHistogramOfNames(deferred);
       }
     }
@@ -461,9 +459,6 @@
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
-    if (markUseIdentifierNameString) {
-      code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
-    }
   }
 
   private void logNameComputation(ClassNameMapping kind) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
index 25a35ad..f9da7c6 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
@@ -85,6 +85,6 @@
 
   @Override
   public final String toString(DexEncodedMethod method, ClassNameMapper naming) {
-    return "SynthesizedCode";
+    return this.getClass().getSimpleName();
   }
 }
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 c63f22e..0edb554 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -6,8 +6,10 @@
 
 import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toKmType;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedClassifier;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmConstructor;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmType;
+import static kotlinx.metadata.Flag.IS_SEALED;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -80,6 +82,17 @@
     }
 
     rewriteDeclarationContainer(kmClass, appView, lens);
+
+    List<String> sealedSubclasses = kmClass.getSealedSubclasses();
+    sealedSubclasses.clear();
+    if (IS_SEALED.invoke(kmClass.getFlags())) {
+      for (DexType subtype : appView.appInfo().allImmediateSubtypes(clazz.type)) {
+        String classifier = toRenamedClassifier(subtype, appView, lens);
+        if (classifier != null) {
+          sealedSubclasses.add(classifier);
+        }
+      }
+    }
   }
 
   @Override
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 ebd5524..7a1a343 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -7,14 +7,20 @@
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunction;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunctionAsExtension;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.KmPropertyGroup;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import kotlinx.metadata.KmDeclarationContainer;
 import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
@@ -105,9 +111,9 @@
       NamingLens lens) {
     assert clazz != null;
 
+    Map<String, KmPropertyGroup.Builder> propertyGroupBuilderMap = new HashMap<>();
     List<KmFunction> functions = kmDeclarationContainer.getFunctions();
     functions.clear();
-    // TODO(b/70169921): clear property
     for (DexEncodedMethod method : clazz.methods()) {
       if (method.isInitializer()) {
         continue;
@@ -127,17 +133,62 @@
         }
         continue;
       }
-      if (method.isKotlinExtensionProperty()) {
-        // TODO(b/70169921): (extension) property
-        continue;
-      }
       if (method.isKotlinProperty()) {
-        // TODO(b/70169921): (extension) property
+        String name = method.getKotlinMemberInfo().propertyName;
+        assert name != null;
+        KmPropertyGroup.Builder builder =
+            propertyGroupBuilderMap.computeIfAbsent(
+                name, k -> KmPropertyGroup.builder(method.getKotlinMemberInfo().flag, name));
+        switch (method.getKotlinMemberInfo().memberKind) {
+          case EXTENSION_PROPERTY_GETTER:
+            builder.isExtensionGetter();
+            // fallthrough;
+          case PROPERTY_GETTER:
+            builder.foundGetter(method);
+            break;
+          case EXTENSION_PROPERTY_SETTER:
+            builder.isExtensionSetter();
+            // fallthrough;
+          case PROPERTY_SETTER:
+            builder.foundSetter(method);
+            break;
+          case EXTENSION_PROPERTY_ANNOTATIONS:
+            builder.isExtensionAnnotations();
+            // fallthrough;
+          case PROPERTY_ANNOTATIONS:
+            builder.foundAnnotations(method);
+            break;
+          default:
+            throw new Unreachable("Not a Kotlin property: " + method.getKotlinMemberInfo());
+        }
         continue;
       }
 
       // TODO(b/70169921): What should we do for methods that fall into this category---no mark?
     }
-    // TODO(b/70169921): (extension) companion
+
+    for (DexEncodedField field : clazz.fields()) {
+      if (field.isKotlinBackingField()) {
+        String name = field.getKotlinMemberInfo().propertyName;
+        assert name != null;
+        KmPropertyGroup.Builder builder =
+            propertyGroupBuilderMap.computeIfAbsent(
+                name, k -> KmPropertyGroup.builder(field.getKotlinMemberInfo().flag, name));
+        builder.foundBackingField(field);
+      }
+    }
+
+    List<KmProperty> properties = kmDeclarationContainer.getProperties();
+    properties.clear();
+    for (KmPropertyGroup.Builder builder : propertyGroupBuilderMap.values()) {
+      KmPropertyGroup group = builder.build();
+      if (group == null) {
+        continue;
+      }
+      KmProperty property = group.toRenamedKmProperty(appView, lens);
+      if (property != null) {
+        properties.add(property);
+      }
+    }
   }
 }
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 82bafe6..5382e44 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
@@ -28,13 +28,19 @@
   }
 
   public final MemberKind memberKind;
-  public final int flag;
-  // TODO(b/70169921): for (extension) property, we may need to group/track the original shape of
-  //  the property. Then, at then end, metadata for only live ones will be synthesized.
+  // Original flag. May be necessary to keep Kotlin-specific flag, e.g., inline function.
+  final int flag;
+  // Original property name for (extension) property. Otherwise, null.
+  final String propertyName;
 
   private KotlinMemberInfo(MemberKind memberKind, int flag) {
+    this(memberKind, flag, null);
+  }
+
+  private KotlinMemberInfo(MemberKind memberKind, int flag, String propertyName) {
     this.memberKind = memberKind;
     this.flag = flag;
+    this.propertyName = propertyName;
   }
 
   public enum MemberKind {
@@ -53,7 +59,7 @@
     EXTENSION_PROPERTY_SETTER,
     EXTENSION_PROPERTY_ANNOTATIONS;
 
-    // TODO(b/70169921): (extension) companion
+    // TODO(b/70169921): companion
 
     public boolean isFunction() {
       return this == FUNCTION || isExtensionFunction();
@@ -129,7 +135,8 @@
       if (kmPropertyFieldMap.containsKey(key)) {
         KmProperty kmProperty = kmPropertyFieldMap.get(key);
         field.setKotlinMemberInfo(
-            new KotlinMemberInfo(MemberKind.PROPERTY_BACKING_FIELD, kmProperty.getFlags()));
+            new KotlinMemberInfo(
+                MemberKind.PROPERTY_BACKING_FIELD, kmProperty.getFlags(), kmProperty.getName()));
       }
     }
 
@@ -151,19 +158,27 @@
         KmProperty kmProperty = kmPropertyGetterMap.get(key);
         if (isExtension(kmProperty)) {
           method.setKotlinMemberInfo(
-              new KotlinMemberInfo(MemberKind.EXTENSION_PROPERTY_GETTER, kmProperty.getFlags()));
+              new KotlinMemberInfo(
+                  MemberKind.EXTENSION_PROPERTY_GETTER,
+                  kmProperty.getFlags(),
+                  kmProperty.getName()));
         } else {
           method.setKotlinMemberInfo(
-              new KotlinMemberInfo(MemberKind.PROPERTY_GETTER, kmProperty.getFlags()));
+              new KotlinMemberInfo(
+                  MemberKind.PROPERTY_GETTER, kmProperty.getFlags(), kmProperty.getName()));
         }
       } else if (kmPropertySetterMap.containsKey(key)) {
         KmProperty kmProperty = kmPropertySetterMap.get(key);
         if (isExtension(kmProperty)) {
           method.setKotlinMemberInfo(
-              new KotlinMemberInfo(MemberKind.EXTENSION_PROPERTY_SETTER, kmProperty.getFlags()));
+              new KotlinMemberInfo(
+                  MemberKind.EXTENSION_PROPERTY_SETTER,
+                  kmProperty.getFlags(),
+                  kmProperty.getName()));
         } else {
           method.setKotlinMemberInfo(
-              new KotlinMemberInfo(MemberKind.PROPERTY_SETTER, kmProperty.getFlags()));
+              new KotlinMemberInfo(
+                  MemberKind.PROPERTY_SETTER, kmProperty.getFlags(), kmProperty.getName()));
         }
       }
     }
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 a3e62c5..8a8773a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -3,15 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
+import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmFieldSignature;
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
-import static com.android.tools.r8.utils.DescriptorUtils.descriptorToInternalName;
+import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType;
+import static com.android.tools.r8.kotlin.Kotlin.NAME;
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier;
 import static kotlinx.metadata.FlagsKt.flagsOf;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
@@ -36,23 +40,21 @@
 
   static KmType toKmType(String descriptor) {
     KmType kmType = new KmType(flagsOf());
-    kmType.visitClass(descriptorToInternalName(descriptor));
+    kmType.visitClass(descriptorToKotlinClassifier(descriptor));
     return kmType;
   }
 
-  static KmType toRenamedKmType(
+  static String toRenamedClassifier(
       DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    // E.g., [Ljava/lang/String; -> Lkotlin/Array;
+    // E.g., [Ljava/lang/String; -> kotlin/Array
     if (type.isArrayType()) {
-      return toKmType(addKotlinPrefix("Array;"));
+      return NAME + "/Array";
     }
-    // E.g., void -> Lkotlin/Unit;
+    // E.g., void -> kotlin/Unit
     if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) {
-      KmType kmType = new KmType(flagsOf());
       DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type);
       assert convertedType != null;
-      kmType.visitClass(descriptorToInternalName(convertedType.toDescriptorString()));
-      return kmType;
+      return descriptorToKotlinClassifier(convertedType.toDescriptorString());
     }
     // For library or classpath class, synthesize @Metadata always.
     // For a program class, make sure it is live.
@@ -64,10 +66,19 @@
     DexClass clazz = appView.definitionFor(type);
     assert clazz == null || clazz.isProgramClass() || renamedType == type
         : type.toSourceString() + " -> " + renamedType.toSourceString();
+    return descriptorToKotlinClassifier(renamedType.toDescriptorString());
+  }
+
+  static KmType toRenamedKmType(
+      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    String classifier = toRenamedClassifier(type, appView, lens);
+    if (classifier == null) {
+      return null;
+    }
     // TODO(b/70169921): Mysterious, why attempts to properly set flags bothers kotlinc?
     //   and/or why wiping out flags works for KmType but not KmFunction?!
     KmType kmType = new KmType(flagsOf());
-    kmType.visitClass(descriptorToInternalName(renamedType.toDescriptorString()));
+    kmType.visitClass(classifier);
     return kmType;
   }
 
@@ -109,8 +120,9 @@
       NamingLens lens,
       boolean isExtension) {
     // For library overrides, synthesize @Metadata always.
-    // For regular methods, make sure it is live.
+    // For regular methods, make sure it is live or pinned.
     if (!method.isLibraryMethodOverride().isTrue()
+        && !appView.appInfo().isPinned(method.method)
         && !appView.appInfo().liveMethods.contains(method.method)) {
       return null;
     }
@@ -118,8 +130,13 @@
     // For a library method override, we should not have renamed it.
     assert !method.isLibraryMethodOverride().isTrue() || renamedMethod == method.method
         : method.toSourceString() + " -> " + renamedMethod.toSourceString();
-    KmFunction kmFunction =
-        new KmFunction(method.accessFlags.getAsKotlinFlags(), renamedMethod.name.toString());
+    // TODO(b/70169921): Should we keep kotlin-specific flags only while synthesizing the base
+    //  value from general JVM flags?
+    int flag =
+        appView.appInfo().isPinned(method.method) && method.getKotlinMemberInfo() != null
+            ? method.getKotlinMemberInfo().flag
+            : method.accessFlags.getAsKotlinFlags();
+    KmFunction kmFunction = new KmFunction(flag, renamedMethod.name.toString());
     JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(method.method));
     KmType kmReturnType = toRenamedKmType(method.method.proto.returnType, appView, lens);
     if (kmReturnType == null) {
@@ -164,4 +181,327 @@
     }
     return true;
   }
+
+  /**
+   * A group of a field, and methods that correspond to a Kotlin property.
+   *
+   * <p>
+   * va(l|r) prop: T
+   *
+   * is converted to a backing field with signature `T prop` if necessary. If the property is a pure
+   * computation, e.g., return `this` or access to the status of other properties, a field is not
+   * needed for the property.
+   * <p>
+   * Depending on property visibility, getter is defined with signature `T getProp()` (or `isProp`
+   * for boolean property).
+   * <p>
+   * If it's editable, i.e., declared as `var`, setter is defined with signature `void setProp(T)`.
+   * <p>
+   * Yet another addition, `void prop$annotations()`, seems(?) to be used to store associated
+   * annotations.
+   *
+   * <p>
+   * Currently, it's unclear how users can selectively keep each. Assuming every piece is kept by
+   * more general rules, such as keeping non-private members, we expect to find those as a whole.
+   * When rewriting a corresponding property, each information is used to update corresponding part,
+   * e.g., field to update the return type of the property, getter to update jvmMethodSignature of
+   * getter, and so on.
+   */
+  static class KmPropertyGroup {
+    final int flag;
+    final String name;
+    final DexEncodedField field;
+    final DexEncodedMethod getter;
+    final DexEncodedMethod setter;
+    final DexEncodedMethod annotations;
+    final boolean isExtension;
+
+    private KmPropertyGroup(
+        int flag,
+        String name,
+        DexEncodedField field,
+        DexEncodedMethod getter,
+        DexEncodedMethod setter,
+        DexEncodedMethod annotations,
+        boolean isExtension) {
+      this.flag = flag;
+      this.name = name;
+      this.field = field;
+      this.getter = getter;
+      this.setter = setter;
+      this.annotations = annotations;
+      this.isExtension = isExtension;
+    }
+
+    static Builder builder(int flag, String name) {
+      return new Builder(flag, name);
+    }
+
+    static class Builder {
+      private final int flag;
+      private final String name;
+      private DexEncodedField field;
+      private DexEncodedMethod getter;
+      private DexEncodedMethod setter;
+      private DexEncodedMethod annotations;
+
+      private boolean isExtensionGetter;
+      private boolean isExtensionSetter;
+      private boolean isExtensionAnnotations;
+
+      private Builder(int flag, String name) {
+        this.flag = flag;
+        this.name = name;
+      }
+
+      Builder foundBackingField(DexEncodedField field) {
+        this.field = field;
+        return this;
+      }
+
+      Builder foundGetter(DexEncodedMethod getter) {
+        this.getter = getter;
+        return this;
+      }
+
+      Builder foundSetter(DexEncodedMethod setter) {
+        this.setter = setter;
+        return this;
+      }
+
+      Builder foundAnnotations(DexEncodedMethod annotations) {
+        this.annotations = annotations;
+        return this;
+      }
+
+      Builder isExtensionGetter() {
+        this.isExtensionGetter = true;
+        return this;
+      }
+
+      Builder isExtensionSetter() {
+        this.isExtensionSetter = true;
+        return this;
+      }
+
+      Builder isExtensionAnnotations() {
+        this.isExtensionAnnotations = true;
+        return this;
+      }
+
+      KmPropertyGroup build() {
+        boolean isExtension = isExtensionGetter || isExtensionSetter || isExtensionAnnotations;
+        // If this is an extension property, everything should be an extension.
+        if (isExtension) {
+          if (getter != null && !isExtensionGetter) {
+            return null;
+          }
+          if (setter != null && !isExtensionSetter) {
+            return null;
+          }
+          if (annotations != null && !isExtensionAnnotations) {
+            return null;
+          }
+        }
+        return new KmPropertyGroup(flag, name, field, getter, setter, annotations, isExtension);
+      }
+    }
+
+    KmProperty toRenamedKmProperty(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+      KmProperty kmProperty = new KmProperty(flag, name, flagsOf(), flagsOf());
+      KmType kmPropertyType = null;
+      KmType kmReceiverType = null;
+
+      if (field != null) {
+        DexField renamedField = lens.lookupField(field.field, appView.dexItemFactory());
+        if (renamedField == null) {
+          // Bail out if we can't find a renamed backing field.
+          return null;
+        }
+        kmProperty.setName(renamedField.name.toString());
+        kmPropertyType = toRenamedKmType(field.field.type, appView, lens);
+        if (kmPropertyType != null) {
+          kmProperty.setReturnType(kmPropertyType);
+        }
+        JvmExtensionsKt.setFieldSignature(kmProperty, toJvmFieldSignature(field.field));
+      }
+
+      GetterSetterCriteria criteria = checkGetterCriteria();
+      if (criteria == GetterSetterCriteria.VIOLATE) {
+        return null;
+      }
+
+      if (criteria == GetterSetterCriteria.MET) {
+        assert getter != null
+            : "checkGetterCriteria: " + this.toString();
+        if (isExtension) {
+          assert getter.method.proto.parameters.size() == 1
+              : "checkGetterCriteria: " + this.toString();
+          kmReceiverType = toRenamedKmType(getter.method.proto.parameters.values[0], appView, lens);
+          if (kmReceiverType != null) {
+            kmProperty.setReceiverParameterType(kmReceiverType);
+          }
+        }
+        if (kmPropertyType == null) {
+          // The property type is not set yet.
+          kmPropertyType = toRenamedKmType(getter.method.proto.returnType, appView, lens);
+          if (kmPropertyType != null) {
+            kmProperty.setReturnType(kmPropertyType);
+          }
+        } else {
+          // If property type is set already (via backing field), make sure it's consistent.
+          KmType kmPropertyTypeFromGetter =
+              toRenamedKmType(getter.method.proto.returnType, appView, lens);
+          if (!getDescriptorFromKmType(kmPropertyType)
+              .equals(getDescriptorFromKmType(kmPropertyTypeFromGetter))) {
+            return null;
+          }
+        }
+        kmProperty.setGetterFlags(getter.accessFlags.getAsKotlinFlags());
+        JvmExtensionsKt.setGetterSignature(kmProperty, toJvmMethodSignature(getter.method));
+      }
+
+      criteria = checkSetterCriteria();
+      if (criteria == GetterSetterCriteria.VIOLATE) {
+        return null;
+      }
+
+      if (criteria == GetterSetterCriteria.MET) {
+        assert setter != null && setter.method.proto.parameters.size() >= 1
+            : "checkSetterCriteria: " + this.toString();
+        if (isExtension) {
+          assert setter.method.proto.parameters.size() == 2
+              : "checkSetterCriteria: " + this.toString();
+          if (kmReceiverType == null) {
+            kmReceiverType =
+                toRenamedKmType(setter.method.proto.parameters.values[0], appView, lens);
+            if (kmReceiverType != null) {
+              kmProperty.setReceiverParameterType(kmReceiverType);
+            }
+          } else {
+            // If the receiver type for the extension property is set already (via getter),
+            // make sure it's consistent.
+            KmType kmReceiverTypeFromSetter =
+                toRenamedKmType(setter.method.proto.parameters.values[0], appView, lens);
+            if (!getDescriptorFromKmType(kmReceiverType)
+                .equals(getDescriptorFromKmType(kmReceiverTypeFromSetter))) {
+              return null;
+            }
+          }
+        }
+        int valueIndex = isExtension ? 1 : 0;
+        if (kmPropertyType == null) {
+          // The property type is not set yet.
+          kmPropertyType =
+              toRenamedKmType(setter.method.proto.parameters.values[valueIndex], appView, lens);
+          if (kmPropertyType != null) {
+            kmProperty.setReturnType(kmPropertyType);
+          }
+        } else {
+          // If property type is set already (via either backing field or getter),
+          // make sure it's consistent.
+          KmType kmPropertyTypeFromSetter =
+              toRenamedKmType(setter.method.proto.parameters.values[valueIndex], appView, lens);
+          if (!getDescriptorFromKmType(kmPropertyType)
+              .equals(getDescriptorFromKmType(kmPropertyTypeFromSetter))) {
+            return null;
+          }
+        }
+        kmProperty.setSetterFlags(setter.accessFlags.getAsKotlinFlags());
+        JvmExtensionsKt.setSetterSignature(kmProperty, toJvmMethodSignature(setter.method));
+      }
+
+      // If the property type remains null at the end, bail out to synthesize this property.
+      if (kmPropertyType == null) {
+        return null;
+      }
+      // For extension property, if the receiver type remains null at the end, bail out too.
+      if (isExtension && kmReceiverType == null) {
+        return null;
+      }
+      return kmProperty;
+    }
+
+    enum GetterSetterCriteria {
+      NOT_AVAILABLE,
+      MET,
+      VIOLATE
+    }
+
+    // Getter should look like:
+    //   1) T getProp(); for regular property, or
+    //   2) T getProp(R); for extension property, where
+    // T is a property type, and R is a receiver type.
+    private GetterSetterCriteria checkGetterCriteria() {
+      if (getter == null) {
+        return GetterSetterCriteria.NOT_AVAILABLE;
+      }
+      // Property type will be checked against a backing field type if any.
+      // And if they're different, we won't synthesize KmProperty for that.
+      // Checking parameter size.
+      if (isExtension) {
+        return getter.method.proto.parameters.size() == 1
+            ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
+      } else {
+        return getter.method.proto.parameters.size() == 0
+            ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
+      }
+    }
+
+    // Setter should look like:
+    //   1) void setProp(T); for regular property, or
+    //   2) void setProp(R, T); for extension property, where
+    // T is a property type, and R is a receiver type.
+    private GetterSetterCriteria checkSetterCriteria() {
+      if (setter == null) {
+        return GetterSetterCriteria.NOT_AVAILABLE;
+      }
+      if (!setter.method.proto.returnType.isVoidType()) {
+        return GetterSetterCriteria.VIOLATE;
+      }
+      // Property type will be checked against a backing field type if any.
+      // And if they're different, we won't synthesize KmProperty for that.
+      // Plus, receiver type will be checked, too, against a getter.
+      if (isExtension) {
+        return setter.method.proto.parameters.size() == 2
+            ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
+      } else {
+        return setter.method.proto.parameters.size() == 1
+            ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
+      }
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder builder = new StringBuilder();
+      builder.append("KmPropertyGroup {").append(System.lineSeparator());
+      builder.append("  name: ").append(name).append(System.lineSeparator());
+      builder.append("  isExtension: ").append(isExtension).append(System.lineSeparator());
+      if (field != null) {
+        builder.append("  field: ")
+            .append(field.toSourceString())
+            .append(System.lineSeparator());
+      }
+      if (getter != null) {
+        builder
+            .append("  getter: ")
+            .append(getter.toSourceString())
+            .append(System.lineSeparator());
+      }
+      if (setter != null) {
+        builder
+            .append("  setter: ")
+            .append(setter.toSourceString())
+            .append(System.lineSeparator());
+      }
+      if (annotations != null) {
+        builder
+            .append("  annotations: ")
+            .append(annotations.toSourceString())
+            .append(System.lineSeparator());
+      }
+      builder.append("}");
+      return builder.toString();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 147c251..1d3718b 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -415,7 +415,12 @@
       } else {
         // "x:y:a():u:v -> b"
         assert originalRange instanceof Range;
-        return ((Range) originalRange).from + lineNumberAfterMinification - minifiedRange.from;
+        Range originalRange = (Range) this.originalRange;
+        if (originalRange.to == originalRange.from) {
+          // This is a single line mapping which we should report as the actual line.
+          return originalRange.to;
+        }
+        return originalRange.from + lineNumberAfterMinification - minifiedRange.from;
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index de60241..47d6431 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -195,7 +195,6 @@
       InstancePut instancePut = instruction.asInstancePut();
       iterator.replaceCurrentInstruction(new InstancePut(field, instancePut.object(), newIn));
     }
-    method.getMutableOptimizationInfo().markUseIdentifierNameString();
     return iterator;
   }
 
@@ -335,7 +334,6 @@
       iterator.replaceCurrentInstruction(
           Invoke.create(
               invoke.getType(), invokedMethod, invokedMethod.proto, invoke.outValue(), newIns));
-      method.getMutableOptimizationInfo().markUseIdentifierNameString();
     }
     return iterator;
   }
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index 6c097a4..8b662b9 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.MethodNameMinifier.State;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DisjointSets;
 import com.android.tools.r8.utils.MethodJavaSignatureEquivalence;
@@ -18,7 +19,6 @@
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
 import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -359,7 +359,6 @@
   }
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final Set<DexCallSite> desugaredCallSites;
   private final Equivalence<DexMethod> equivalence;
   private final MethodNameMinifier.State minifierState;
 
@@ -371,12 +370,8 @@
   /** A map for caching all interface states. */
   private final Map<DexType, InterfaceReservationState> interfaceStateMap = new HashMap<>();
 
-  InterfaceMethodNameMinifier(
-      AppView<AppInfoWithLiveness> appView,
-      Set<DexCallSite> desugaredCallSites,
-      MethodNameMinifier.State minifierState) {
+  InterfaceMethodNameMinifier(AppView<AppInfoWithLiveness> appView, State minifierState) {
     this.appView = appView;
-    this.desugaredCallSites = desugaredCallSites;
     this.minifierState = minifierState;
     this.equivalence =
         appView.options().getProguardConfiguration().isOverloadAggressively()
@@ -433,7 +428,7 @@
     // desugared lambdas this is a conservative estimate, as we don't track if the generated
     // lambda classes survive into the output. As multi-interface lambda expressions are rare
     // this is not a big deal.
-    Set<DexCallSite> liveCallSites = Sets.union(desugaredCallSites, appView.appInfo().callSites);
+    Set<DexCallSite> liveCallSites = appView.appInfo().callSites;
     // If the input program contains a multi-interface lambda expression that implements
     // interface methods with different protos, we need to make sure tha the implemented lambda
     // methods are renamed to the same name.
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 8d2e79f..d506d84 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -21,7 +21,6 @@
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
-import java.util.Set;
 import java.util.function.Function;
 
 /**
@@ -174,8 +173,7 @@
     }
   }
 
-  MethodRenaming computeRenaming(
-      Collection<DexClass> interfaces, Set<DexCallSite> desugaredCallSites, Timing timing) {
+  MethodRenaming computeRenaming(Collection<DexClass> interfaces, Timing timing) {
     // Phase 1: Reserve all the names that need to be kept and allocate linked state in the
     //          library part.
     timing.begin("Phase 1");
@@ -186,7 +184,7 @@
     //          states that may hold an implementation.
     timing.begin("Phase 2");
     InterfaceMethodNameMinifier interfaceMethodNameMinifier =
-        new InterfaceMethodNameMinifier(appView, desugaredCallSites, minifierState);
+        new InterfaceMethodNameMinifier(appView, minifierState);
     timing.end();
     timing.begin("Phase 3");
     interfaceMethodNameMinifier.assignNamesToInterfaceMethods(timing, interfaces);
@@ -244,7 +242,7 @@
 
   private void reserveNamesInClasses(
       DexType type, DexType libraryFrontier, MethodReservationState<?> parentReservationState) {
-    assert appView.isInterface(type).isFalse();
+    assert !appView.isInterface(type).isTrue();
 
     MethodReservationState<?> reservationState =
         allocateReservationStateAndReserve(type, libraryFrontier, parentReservationState);
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index eb3a614..e768589 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -39,18 +38,16 @@
 public class Minifier {
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final Set<DexCallSite> desugaredCallSites;
 
-  public Minifier(AppView<AppInfoWithLiveness> appView, Set<DexCallSite> desugaredCallSites) {
+  public Minifier(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
-    this.desugaredCallSites = desugaredCallSites;
   }
 
   public NamingLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
     assert appView.options().isMinifying();
     timing.begin("ComputeInterfaces");
     Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
-    interfaces.addAll(appView.appInfo().computeReachableInterfaces(desugaredCallSites));
+    interfaces.addAll(appView.appInfo().computeReachableInterfaces());
     timing.end();
     timing.begin("MinifyClasses");
     ClassNameMinifier classNameMinifier =
@@ -70,8 +67,7 @@
     MemberNamingStrategy minifyMembers = new MinifierMemberNamingStrategy(appView);
     timing.begin("MinifyMethods");
     MethodRenaming methodRenaming =
-        new MethodNameMinifier(appView, minifyMembers)
-            .computeRenaming(interfaces, desugaredCallSites, timing);
+        new MethodNameMinifier(appView, minifyMembers).computeRenaming(interfaces, timing);
     timing.end();
 
     assert new MinifiedRenaming(appView, classRenaming, methodRenaming, FieldRenaming.empty())
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 4fd6274..814ef4c 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -68,7 +67,6 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory factory;
   private final SeedMapper seedMapper;
-  private final Set<DexCallSite> desugaredCallSites;
   private final BiMap<DexType, DexString> mappedNames = HashBiMap.create();
   // To keep the order deterministic, we sort the classes by their type, which is a unique key.
   private final Set<DexClass> mappedClasses = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
@@ -79,14 +77,10 @@
   private final Map<DexMethod, DexString> additionalMethodNamings = Maps.newIdentityHashMap();
   private final Map<DexField, DexString> additionalFieldNamings = Maps.newIdentityHashMap();
 
-  public ProguardMapMinifier(
-      AppView<AppInfoWithLiveness> appView,
-      SeedMapper seedMapper,
-      Set<DexCallSite> desugaredCallSites) {
+  public ProguardMapMinifier(AppView<AppInfoWithLiveness> appView, SeedMapper seedMapper) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
     this.seedMapper = seedMapper;
-    this.desugaredCallSites = desugaredCallSites;
   }
 
   public NamingLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
@@ -145,8 +139,7 @@
         new ApplyMappingMemberNamingStrategy(appView, memberNames);
     timing.begin("MinifyMethods");
     MethodRenaming methodRenaming =
-        new MethodNameMinifier(appView, nameStrategy)
-            .computeRenaming(interfaces, desugaredCallSites, timing);
+        new MethodNameMinifier(appView, nameStrategy).computeRenaming(interfaces, timing);
     // Amend the method renamings with the default interface methods.
     methodRenaming.renaming.putAll(defaultInterfaceMethodImplementationNames);
     methodRenaming.renaming.putAll(additionalMethodNamings);
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 af3d41e..317cb3b 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.optimize.PublicizerLense.PublicizedLenseBuilder;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
-import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -64,7 +63,7 @@
 
     // Phase 2: Visit classes and promote class/member to public if possible.
     timing.begin("Phase 2: promoteToPublic");
-    for (DexClass iface : appView.appInfo().computeReachableInterfaces(Collections.emptySet())) {
+    for (DexClass iface : appView.appInfo().computeReachableInterfaces()) {
       publicizeType(iface.type);
     }
     publicizeType(appView.dexItemFactory().objectType);
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
index 3b68d3d..8d3e956 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -458,6 +458,69 @@
     }
   }
 
+  static class CircularReferenceLine extends StackTraceLine {
+
+    private final String startWhitespace;
+    private final String exceptionClass;
+    private final String endBracketAndWhitespace;
+
+    private static final String CIRCULAR_REFERENCE = "[CIRCULAR REFERENCE:";
+
+    public CircularReferenceLine(
+        String startWhitespace, String exceptionClass, String endBracketAndWhitespace) {
+      this.startWhitespace = startWhitespace;
+      this.exceptionClass = exceptionClass;
+      this.endBracketAndWhitespace = endBracketAndWhitespace;
+    }
+
+    static StackTraceLine tryParse(String line) {
+      // Check that the line is indented with some amount of white space.
+      if (line.length() == 0 || !Character.isWhitespace(line.charAt(0))) {
+        return null;
+      }
+      // Find the first non-white space character and check that we have the sequence
+      // '[CIRCULAR REFERENCE:Exception]'.
+      int firstNonWhiteSpace = firstNonWhiteSpaceCharacterFromIndex(line, 0);
+      if (!line.startsWith(CIRCULAR_REFERENCE, firstNonWhiteSpace)) {
+        return null;
+      }
+      int exceptionStartIndex = firstNonWhiteSpace + CIRCULAR_REFERENCE.length();
+      int lastBracketPosition = firstCharFromIndex(line, exceptionStartIndex, ']');
+      if (lastBracketPosition == line.length()) {
+        return null;
+      }
+      int onlyWhitespaceFromLastBracket =
+          firstNonWhiteSpaceCharacterFromIndex(line, lastBracketPosition + 1);
+      if (onlyWhitespaceFromLastBracket != line.length()) {
+        return null;
+      }
+      return new CircularReferenceLine(
+          line.substring(0, firstNonWhiteSpace),
+          line.substring(exceptionStartIndex, lastBracketPosition),
+          line.substring(lastBracketPosition));
+    }
+
+    @Override
+    List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose) {
+      List<StackTraceLine> exceptionLines = new ArrayList<>();
+      retraceBase
+          .retrace(Reference.classFromTypeName(exceptionClass))
+          .forEach(
+              element ->
+                  exceptionLines.add(
+                      new CircularReferenceLine(
+                          startWhitespace,
+                          element.getClassReference().getTypeName(),
+                          endBracketAndWhitespace)));
+      return exceptionLines;
+    }
+
+    @Override
+    public String toString() {
+      return startWhitespace + CIRCULAR_REFERENCE + exceptionClass + endBracketAndWhitespace;
+    }
+  }
+
   static class UnknownLine extends StackTraceLine {
     private final String line;
 
@@ -490,6 +553,10 @@
     if (parsedLine != null) {
       return parsedLine;
     }
+    parsedLine = CircularReferenceLine.tryParse(line);
+    if (parsedLine != null) {
+      return parsedLine;
+    }
     parsedLine = MoreLine.tryParse(line);
     if (parsedLine == null) {
       diagnosticsHandler.warning(
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 ac4f95f..89cf63c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -179,7 +179,7 @@
   final Set<DexType> instantiatedLambdas;
 
   // TODO(zerny): Clean up the constructors so we have just one.
-  private AppInfoWithLiveness(
+  AppInfoWithLiveness(
       DexApplication application,
       Set<DexType> liveTypes,
       Set<DexType> instantiatedAnnotationTypes,
@@ -579,19 +579,13 @@
     return clazz == null || !clazz.isProgramClass();
   }
 
-  public Collection<DexClass> computeReachableInterfaces(Set<DexCallSite> desugaredCallSites) {
+  public Collection<DexClass> computeReachableInterfaces() {
     Set<DexClass> interfaces = Sets.newIdentityHashSet();
     Set<DexType> seen = Sets.newIdentityHashSet();
     Deque<DexType> worklist = new ArrayDeque<>();
     for (DexProgramClass clazz : classes()) {
       worklist.add(clazz.type);
     }
-    // TODO(b/129458850): Remove this once desugared classes are made part of the program classes.
-    for (DexCallSite callSite : desugaredCallSites) {
-      for (DexEncodedMethod method : lookupLambdaImplementedMethods(callSite)) {
-        worklist.add(method.method.holder);
-      }
-    }
     for (DexCallSite callSite : callSites) {
       for (DexEncodedMethod method : lookupLambdaImplementedMethods(callSite)) {
         worklist.add(method.method.holder);
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 4a8cdf3..b90d37e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -10,14 +10,26 @@
 import static com.android.tools.r8.shaking.EnqueuerUtils.toImmutableSortedMap;
 
 import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Descriptor;
 import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
@@ -54,8 +66,11 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.LambdaClass;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
@@ -67,6 +82,7 @@
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -98,6 +114,7 @@
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import org.objectweb.asm.Opcodes;
 
 /**
  * Approximates the runtime dependencies for the given set of roots.
@@ -247,7 +264,9 @@
    * Set of interface types for which there may be instantiations, such as lambda expressions or
    * explicit keep rules.
    */
-  private final SetWithReason<DexProgramClass> instantiatedInterfaceTypes;
+  private final Set<DexProgramClass> instantiatedInterfaceTypes;
+  /** Subset of the above that are marked instantiated by usages that are not desugared lambdas. */
+  private final SetWithReason<DexProgramClass> unknownInstantiatedInterfaceTypes;
 
   /** A queue of items that need processing. Different items trigger different actions. */
   private final EnqueuerWorklist workList;
@@ -297,6 +316,12 @@
 
   private final GraphReporter graphReporter;
 
+  private final LambdaRewriter lambdaRewriter;
+  private final Map<DexType, LambdaClass> lambdaClasses = new IdentityHashMap<>();
+  private final Map<DexEncodedMethod, Map<DexCallSite, LambdaClass>> lambdaCallSites =
+      new IdentityHashMap<>();
+  private final Set<DexProgramClass> classesWithSerializableLambdas = Sets.newIdentityHashSet();
+
   Enqueuer(
       AppView<? extends AppInfoWithSubtyping> appView,
       GraphConsumer keptGraphConsumer,
@@ -328,7 +353,9 @@
     failedResolutionTargets = SetUtils.newIdentityHashSet(2);
     liveMethods = new LiveMethodsSet(graphReporter::registerMethod);
     liveFields = new SetWithReason<>(graphReporter::registerField);
-    instantiatedInterfaceTypes = new SetWithReason<>(graphReporter::registerInterface);
+    unknownInstantiatedInterfaceTypes = new SetWithReason<>(graphReporter::registerInterface);
+    instantiatedInterfaceTypes = Sets.newIdentityHashSet();
+    lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
   }
 
   public Mode getMode() {
@@ -483,7 +510,8 @@
   private void markInterfaceAsInstantiated(DexProgramClass clazz, KeepReasonWitness witness) {
     assert clazz.isInterface() && !clazz.accessFlags.isAnnotation();
 
-    if (!instantiatedInterfaceTypes.add(clazz, witness)) {
+    unknownInstantiatedInterfaceTypes.add(clazz, witness);
+    if (!instantiatedInterfaceTypes.add(clazz)) {
       return;
     }
     populateInstantiatedTypesCache(clazz);
@@ -588,24 +616,6 @@
   }
 
   void traceCallSite(DexCallSite callSite, ProgramMethod context) {
-    callSites.add(callSite);
-
-    List<DexType> directInterfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
-    if (directInterfaces != null) {
-      for (DexType lambdaInstantiatedInterface : directInterfaces) {
-        markLambdaInstantiated(lambdaInstantiatedInterface, context.method);
-      }
-    } else {
-      if (!appInfo.isStringConcat(callSite.bootstrapMethod)) {
-        if (options.reporter != null) {
-          Diagnostic message =
-              new StringDiagnostic(
-                  "Unknown bootstrap method " + callSite.bootstrapMethod, context.holder.origin);
-          options.reporter.warning(message);
-        }
-      }
-    }
-
     DexProgramClass bootstrapClass =
         getProgramClassOrNull(callSite.bootstrapMethod.asMethod().holder);
     if (bootstrapClass != null) {
@@ -614,9 +624,41 @@
 
     LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo, context.holder);
     if (descriptor == null) {
+      if (!appInfo.isStringConcat(callSite.bootstrapMethod)) {
+        if (options.reporter != null) {
+          Diagnostic message =
+              new StringDiagnostic(
+                  "Unknown bootstrap method " + callSite.bootstrapMethod, context.holder.origin);
+          options.reporter.warning(message);
+        }
+      }
+
+      callSites.add(callSite);
       return;
     }
 
+    for (DexType lambdaInstantiatedInterface : descriptor.interfaces) {
+      markLambdaInstantiated(lambdaInstantiatedInterface, context.method);
+    }
+
+    if (lambdaRewriter != null) {
+      assert context.method.getCode().isCfCode() : "Unexpected input type with lambdas";
+      CfCode code = context.method.getCode().asCfCode();
+      if (code != null) {
+        LambdaClass lambdaClass =
+            lambdaRewriter.getOrCreateLambdaClass(descriptor, context.method.method.holder);
+        lambdaClasses.put(lambdaClass.type, lambdaClass);
+        lambdaCallSites
+            .computeIfAbsent(context.method, k -> new IdentityHashMap<>())
+            .put(callSite, lambdaClass);
+        if (lambdaClass.descriptor.interfaces.contains(appView.dexItemFactory().serializableType)) {
+          classesWithSerializableLambdas.add(context.holder);
+        }
+      }
+    } else {
+      callSites.add(callSite);
+    }
+
     // For call sites representing a lambda, we link the targeted method
     // or field as if it were referenced from the current method.
 
@@ -657,10 +699,6 @@
     // We make an assumption that such classes are inherited directly from java.lang.Object
     // and implement all lambda interfaces.
 
-    if (directInterfaces == null) {
-      return;
-    }
-
     // The set now contains all virtual methods on the type and its supertype that are reachable.
     // In a second step, we now look at interfaces. We have to do this in this order due to JVM
     // semantics for default methods. A default method is only reachable if it is not overridden
@@ -670,7 +708,7 @@
     // the shadowing of other interface chains into account.
     // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3
     ScopedDexMethodSet seen = new ScopedDexMethodSet();
-    for (DexType iface : directInterfaces) {
+    for (DexType iface : descriptor.interfaces) {
       DexProgramClass ifaceClazz = getProgramClassOrNull(iface);
       if (ifaceClazz != null) {
         transitionDefaultMethodsForInstantiatedClass(iface, seen);
@@ -1856,10 +1894,14 @@
       options.reporter.warning(message);
       return;
     }
-    if (clazz.isProgramClass()) {
-      KeepReason reason = KeepReason.instantiatedIn(method);
-      if (instantiatedInterfaceTypes.add(clazz.asProgramClass(), reason)) {
-        populateInstantiatedTypesCache(clazz.asProgramClass());
+    DexProgramClass programClass = clazz.asProgramClass();
+    if (programClass != null) {
+      // When not desugaring, we need to mark the instantiation point as unknown for now.
+      if (lambdaRewriter == null) {
+        unknownInstantiatedInterfaceTypes.add(programClass, KeepReason.instantiatedIn(method));
+      }
+      if (instantiatedInterfaceTypes.add(programClass)) {
+        populateInstantiatedTypesCache(programClass);
       }
     }
   }
@@ -2270,9 +2312,11 @@
       liveMethods.add(bridge.holder, bridge.method, graphReporter.fakeReportShouldNotBeUsed());
     }
 
+    DexApplication app = postProcessLambdaDesugaring(appInfo);
+
     AppInfoWithLiveness appInfoWithLiveness =
         new AppInfoWithLiveness(
-            appInfo,
+            app,
             SetUtils.mapIdentityHashSet(liveTypes.getItems(), DexProgramClass::getType),
             SetUtils.mapIdentityHashSet(
                 liveAnnotations.getItems(), DexAnnotation::getAnnotationType),
@@ -2313,12 +2357,114 @@
             Collections.emptyMap(),
             Collections.emptyMap(),
             SetUtils.mapIdentityHashSet(
-                instantiatedInterfaceTypes.getItems(), DexProgramClass::getType),
+                unknownInstantiatedInterfaceTypes.getItems(), DexProgramClass::getType),
             constClassReferences);
     appInfo.markObsolete();
     return appInfoWithLiveness;
   }
 
+  private DexApplication postProcessLambdaDesugaring(AppInfoWithSubtyping appInfo) {
+    if (lambdaRewriter == null || lambdaClasses.isEmpty()) {
+      return appInfo.app();
+    }
+    Builder<?> appBuilder = appInfo.app().builder();
+    for (LambdaClass lambdaClass : lambdaClasses.values()) {
+      // Add all desugared classes to the application, main-dex list, and mark them instantiated.
+      DexProgramClass programClass = lambdaClass.getOrCreateLambdaClass();
+      appBuilder.addProgramClass(programClass);
+      if (lambdaClass.addToMainDexList.get()) {
+        appBuilder.addToMainDexList(Collections.singletonList(programClass.type));
+      }
+      liveTypes.add(programClass, graphReporter.fakeReportShouldNotBeUsed());
+      instantiatedTypes.add(programClass, graphReporter.fakeReportShouldNotBeUsed());
+
+      // Register all of the field writes in the lambda constructors.
+      // This is needed to ensure that the initializers can be optimized.
+      Map<DexEncodedField, Set<DexEncodedMethod>> writes =
+          lambdaRewriter.getWritesWithContexts(programClass);
+      writes.forEach(
+          (field, contexts) -> {
+            for (DexEncodedMethod context : contexts) {
+              registerFieldWrite(field.field, context);
+            }
+          });
+
+      // Mark all methods on the desugared lambda classes as live.
+      for (DexEncodedMethod method : programClass.methods()) {
+        targetedMethods.add(method, graphReporter.fakeReportShouldNotBeUsed());
+        liveMethods.add(programClass, method, graphReporter.fakeReportShouldNotBeUsed());
+      }
+
+      // Ensure accessors if needed and mark them live too.
+      DexEncodedMethod accessor = lambdaClass.target.ensureAccessibilityIfNeeded(false);
+      if (accessor != null) {
+        DexProgramClass clazz = getProgramClassOrNull(accessor.method.holder);
+        targetedMethods.add(accessor, graphReporter.fakeReportShouldNotBeUsed());
+        liveMethods.add(clazz, accessor, graphReporter.fakeReportShouldNotBeUsed());
+      }
+    }
+
+    // Rewrite all of the invoke-dynamic instructions to lambda class instantiations.
+    lambdaCallSites.forEach(this::rewriteLambdaCallSites);
+
+    // Remove all '$deserializeLambda$' methods which are not supported by desugaring.
+    for (DexProgramClass clazz : classesWithSerializableLambdas) {
+      clazz.removeDirectMethod(appView.dexItemFactory().deserializeLambdaMethod);
+    }
+
+    return appBuilder.build();
+  }
+
+  private void rewriteLambdaCallSites(
+      DexEncodedMethod method, Map<DexCallSite, LambdaClass> callSites) {
+    assert !callSites.isEmpty();
+    CfCode code = method.getCode().asCfCode();
+    List<CfInstruction> instructions = code.instructions;
+    int replaced = 0;
+    int maxTemp = 0;
+    for (int i = 0; i < instructions.size(); i++) {
+      CfInstruction instruction = instructions.get(i);
+      if (instruction instanceof CfInvokeDynamic) {
+        LambdaClass lambdaClass = callSites.get(((CfInvokeDynamic) instruction).getCallSite());
+        if (lambdaClass == null) {
+          continue;
+        }
+        if (lambdaClass.isStateless()) {
+          CfFieldInstruction getStaticLambdaInstance =
+              new CfFieldInstruction(
+                  Opcodes.GETSTATIC, lambdaClass.lambdaField, lambdaClass.lambdaField);
+          instructions.set(i, getStaticLambdaInstance);
+        } else {
+          List<CfInstruction> replacement = new ArrayList<>();
+          int arguments = lambdaClass.descriptor.captures.size();
+          int temp = code.getMaxLocals();
+          for (int j = arguments - 1; j >= 0; j--) {
+            ValueType type = ValueType.fromDexType(lambdaClass.descriptor.captures.values[j]);
+            replacement.add(new CfStore(type, temp));
+            temp += type.requiredRegisters();
+          }
+          maxTemp = Math.max(temp, maxTemp);
+          replacement.add(new CfNew(lambdaClass.type));
+          replacement.add(new CfStackInstruction(Opcode.Dup));
+          for (int j = 0; j < arguments; j++) {
+            ValueType type = ValueType.fromDexType(lambdaClass.descriptor.captures.values[j]);
+            temp -= type.requiredRegisters();
+            replacement.add(new CfLoad(type, temp));
+          }
+          replacement.add(new CfInvoke(Opcodes.INVOKESPECIAL, lambdaClass.constructor, false));
+          instructions.remove(i);
+          instructions.addAll(i, replacement);
+        }
+        ++replaced;
+      }
+    }
+    if (maxTemp > 0) {
+      assert maxTemp > code.getMaxLocals();
+      code.setMaxLocals(maxTemp);
+    }
+    assert replaced == callSites.size();
+  }
+
   private static <T extends PresortedComparable<T>> SortedSet<T> toSortedDescriptorSet(
       Set<? extends KeyedDexItem<T>> set) {
     ImmutableSortedSet.Builder<T> builder =
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index bfad990..3c91bc8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -70,6 +70,7 @@
     private boolean keepRuleSynthesisForRecompilation = false;
     private boolean configurationDebugging = false;
     private boolean dontUseMixedCaseClassnames = false;
+    private int maxRemovedAndroidLogLevel = 1;
 
     private Builder(DexItemFactory dexItemFactory, Reporter reporter) {
       this.dexItemFactory = dexItemFactory;
@@ -288,6 +289,14 @@
       this.dontUseMixedCaseClassnames = dontUseMixedCaseClassnames;
     }
 
+    public int getMaxRemovedAndroidLogLevel() {
+      return maxRemovedAndroidLogLevel;
+    }
+
+    public void setMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel) {
+      this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
+    }
+
     /**
      * This synthesizes a set of keep rules that are necessary in order to be able to successfully
      * recompile the generated dex files with the same keep rules.
@@ -346,7 +355,8 @@
               adaptResourceFileContents.build(),
               keepDirectories.build(),
               configurationDebugging,
-              dontUseMixedCaseClassnames);
+              dontUseMixedCaseClassnames,
+              maxRemovedAndroidLogLevel);
 
       reporter.failIfPendingErrors();
 
@@ -415,6 +425,7 @@
   private final ProguardPathFilter keepDirectories;
   private final boolean configurationDebugging;
   private final boolean dontUseMixedCaseClassnames;
+  private final int maxRemovedAndroidLogLevel;
 
   private ProguardConfiguration(
       String parsedConfiguration,
@@ -454,7 +465,8 @@
       ProguardPathFilter adaptResourceFileContents,
       ProguardPathFilter keepDirectories,
       boolean configurationDebugging,
-      boolean dontUseMixedCaseClassnames) {
+      boolean dontUseMixedCaseClassnames,
+      int maxRemovedAndroidLogLevel) {
     this.parsedConfiguration = parsedConfiguration;
     this.dexItemFactory = factory;
     this.injars = ImmutableList.copyOf(injars);
@@ -493,6 +505,7 @@
     this.keepDirectories = keepDirectories;
     this.configurationDebugging = configurationDebugging;
     this.dontUseMixedCaseClassnames = dontUseMixedCaseClassnames;
+    this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
   }
 
   /**
@@ -659,6 +672,10 @@
     return dontUseMixedCaseClassnames;
   }
 
+  public int getMaxRemovedAndroidLogLevel() {
+    return maxRemovedAndroidLogLevel;
+  }
+
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index a589f9a..cc05e23 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -58,7 +58,7 @@
       "maximuminlinedcodelength");
 
   private static final List<String> IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS =
-      ImmutableList.of("runtype", "laststageoutput", "maximumremovedandroidloglevel");
+      ImmutableList.of("runtype", "laststageoutput");
 
   private static final List<String> IGNORED_FLAG_OPTIONS = ImmutableList.of(
       "forceprocessing",
@@ -398,6 +398,14 @@
         configurationBuilder.setConfigurationDebugging(true);
       } else if (acceptString("dontusemixedcaseclassnames")) {
         configurationBuilder.setDontUseMixedCaseClassnames(true);
+      } else if (acceptString("maximumremovedandroidloglevel")) {
+        skipWhitespace();
+        Integer maxRemovedAndroidLogLevel = acceptInteger();
+        if (maxRemovedAndroidLogLevel != null) {
+          configurationBuilder.setMaxRemovedAndroidLogLevel(maxRemovedAndroidLogLevel);
+        } else {
+          throw parseError("Expected integer", getPosition());
+        }
       } else {
         String unknownOption = acceptString();
         String devMessage = "";
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 93f36a2..4be540a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -74,8 +74,9 @@
       return this;
     }
 
-    public void setArguments(List<ProguardTypeMatcher> arguments) {
+    public Builder setArguments(List<ProguardTypeMatcher> arguments) {
       this.arguments = arguments;
+      return this;
     }
 
     public Builder setReturnValue(ProguardMemberRuleReturnValue value) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 62241d5..6ab0836 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -923,6 +923,12 @@
           if (virtualMethod.accessFlags.isAbstract()) {
             // Remove abstract/interface methods that are shadowed.
             deferredRenamings.map(virtualMethod.method, shadowedBy.method);
+
+            // The override now corresponds to the method in the parent, so unset its synthetic flag
+            // if the method in the parent is not synthetic.
+            if (!virtualMethod.isSyntheticMethod() && shadowedBy.isSyntheticMethod()) {
+              shadowedBy.accessFlags.demoteFromSynthetic();
+            }
             continue;
           }
         } else {
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 fd51973..70d6df0 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -178,6 +178,16 @@
   }
 
   /**
+   * Convert a descriptor to a classifier in Kotlin metadata
+   * @param descriptor like "Lorg/foo/bar/Baz$Nested;"
+   * @return className "org/foo/bar/Baz.Nested"
+   */
+  public static String descriptorToKotlinClassifier(String descriptor) {
+    return getBinaryNameFromDescriptor(descriptor)
+        .replace(INNER_CLASS_SEPARATOR, JAVA_PACKAGE_SEPARATOR);
+  }
+
+  /**
    * Convert a type descriptor to a Java type name. Will also deobfuscate class names if a
    * class mapper is provided.
    *
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 d168a64..9eb8b6c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -53,7 +53,9 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Comparator;
+import java.util.Deque;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -202,6 +204,7 @@
   public boolean enableInlining =
       !Version.isDevelopmentVersion()
           || System.getProperty("com.android.tools.r8.disableinlining") == null;
+  public boolean enableEnumUnboxing = false;
   // TODO(b/141451716): Evaluate the effect of allowing inlining in the inlinee.
   public boolean applyInliningToInlinee =
       System.getProperty("com.android.tools.r8.applyInliningToInlinee") != null;
@@ -979,6 +982,8 @@
             ? NondeterministicIROrdering.getInstance()
             : IdentityIROrdering.getInstance();
 
+    public Consumer<Deque<Collection<DexEncodedMethod>>> waveModifier = waves -> {};
+
     /**
      * If this flag is enabled, we will also compute the set of possible targets for invoke-
      * interface and invoke-virtual instructions that target a library method, and add the
@@ -1006,6 +1011,7 @@
     public boolean enableCheckCastAndInstanceOfRemoval = true;
     public boolean enableDeadSwitchCaseElimination = true;
     public boolean enableSwitchToIfRewriting = true;
+    public boolean enableEnumUnboxingDebugLogs = false;
     public boolean forceRedundantConstNumberRemoval = false;
     public boolean forceAssumeNoneInsertion = false;
     public boolean invertConditionals = false;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index df13ac6..e9eb31f 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -378,11 +378,23 @@
             for (; j < mappedPositions.size(); j++) {
               // Break if this position cannot be merged with lastPosition.
               MappedPosition mp = mappedPositions.get(j);
+              // We allow for ranges being mapped to the same line but not to other ranges:
+              //   1:10:void foo():42:42 -> a
+              // is OK since retrace(a(:7)) = 42, however, the following is not OK:
+              //   1:10:void foo():42:43 -> a
+              // since retrace(a(:7)) = 49, which is not correct.
+              boolean isSingleLine = mp.originalLine == firstPosition.originalLine;
+              boolean differentDelta =
+                  mp.originalLine - lastPosition.originalLine
+                      != mp.obfuscatedLine - lastPosition.obfuscatedLine;
+              boolean isMappingRangeToSingleLine =
+                  firstPosition.obfuscatedLine != lastPosition.obfuscatedLine
+                      && firstPosition.originalLine == lastPosition.originalLine;
               // Note that mp.caller and lastPosition.class must be deep-compared since multiple
               // inlining passes lose the canonical property of the positions.
-              if ((mp.method != lastPosition.method)
-                  || (mp.originalLine - lastPosition.originalLine
-                      != mp.obfuscatedLine - lastPosition.obfuscatedLine)
+              if (mp.method != lastPosition.method
+                  || (!isSingleLine && differentDelta)
+                  || (!isSingleLine && isMappingRangeToSingleLine)
                   || !Objects.equals(mp.caller, lastPosition.caller)) {
                 break;
               }
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index 02b5fbb..40232ca 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -4,29 +4,39 @@
 package com.android.tools.r8.utils;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.Future;
 
 public class ThreadUtils {
 
   public static final int NOT_SPECIFIED = -1;
 
+  public static <T, R, E extends Exception> Collection<R> processItemsWithResults(
+      Iterable<T> items, ThrowingFunction<T, R, E> consumer, ExecutorService executorService)
+      throws ExecutionException {
+    List<Future<R>> futures = new ArrayList<>();
+    for (T item : items) {
+      futures.add(executorService.submit(() -> consumer.apply(item)));
+    }
+    return awaitFuturesWithResults(futures);
+  }
+
   public static <T, E extends Exception> void processItems(
       Iterable<T> items, ThrowingConsumer<T, E> consumer, ExecutorService executorService)
       throws ExecutionException {
-    List<Future<?>> futures = new ArrayList<>();
-    for (T item : items) {
-      futures.add(executorService.submit(
-          () -> {
-            consumer.accept(item);
-            return null; // we want a Callable not a Runnable to be able to throw
-          }));
-    }
-    awaitFutures(futures);
+    processItemsWithResults(
+        items,
+        arg -> {
+          consumer.accept(arg);
+          return null;
+        },
+        executorService);
   }
 
   public static void awaitFutures(Iterable<? extends Future<?>> futures)
@@ -52,6 +62,31 @@
     }
   }
 
+  public static <R> Collection<R> awaitFuturesWithResults(Collection<? extends Future<R>> futures)
+      throws ExecutionException {
+    List<R> results = new ArrayList<>(futures.size());
+    Iterator<? extends Future<R>> futureIterator = futures.iterator();
+    try {
+      while (futureIterator.hasNext()) {
+        results.add(futureIterator.next().get());
+      }
+    } catch (InterruptedException e) {
+      throw new RuntimeException("Interrupted while waiting for future.", e);
+    } finally {
+      // In case we get interrupted or one of the threads throws an exception, still wait for all
+      // further work to make sure synchronization guarantees are met. Calling cancel unfortunately
+      // does not guarantee that the task at hand actually terminates before cancel returns.
+      while (futureIterator.hasNext()) {
+        try {
+          futureIterator.next().get();
+        } catch (Throwable t) {
+          // Ignore any new Exception.
+        }
+      }
+    }
+    return results;
+  }
+
   static ExecutorService getExecutorServiceForProcessors(int processors) {
     // This heuristic is based on measurements on a 32 core (hyper-threaded) machine.
     int threads = processors <= 2 ? processors : (int) Math.ceil(Integer.min(processors, 16) / 2.0);
@@ -68,4 +103,11 @@
   public static ExecutorService getExecutorService(InternalOptions options) {
     return getExecutorService(options.numberOfThreads);
   }
+
+  public static int getNumberOfThreads(ExecutorService service) {
+    if (service instanceof ForkJoinPool) {
+      return ((ForkJoinPool) service).getParallelism();
+    }
+    return -1;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingFunction.java b/src/main/java/com/android/tools/r8/utils/ThrowingFunction.java
new file mode 100644
index 0000000..e65d42c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingFunction.java
@@ -0,0 +1,17 @@
+// 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;
+
+import java.util.function.Function;
+
+/**
+ * Similar to a {@link Function} but throws a single {@link Throwable}.
+ *
+ * @param <T> the type of the input
+ * @param <E> the type of the {@link Throwable}
+ */
+@FunctionalInterface
+public interface ThrowingFunction<T, R, E extends Throwable> {
+  R apply(T t) throws E;
+}
diff --git a/src/main/java/com/android/tools/r8/utils/Timing.java b/src/main/java/com/android/tools/r8/utils/Timing.java
index 7d68954..78c1b44 100644
--- a/src/main/java/com/android/tools/r8/utils/Timing.java
+++ b/src/main/java/com/android/tools/r8/utils/Timing.java
@@ -13,6 +13,10 @@
 // Finally a report is printed by:
 //     t.report();
 
+import com.google.common.base.Strings;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -20,21 +24,71 @@
 
 public class Timing {
 
+  private static final int MINIMUM_REPORT_PERCENTAGE = 2;
+
+  private static final Timing EMPTY =
+      new Timing("<empty>", false) {
+        @Override
+        public TimingMerger beginMerger(String title, int numberOfThreads) {
+          return new TimingMerger(null, -1, this) {
+            @Override
+            public void add(Collection<Timing> timings) {
+              // Ignore.
+            }
+
+            @Override
+            public void end() {
+              // Ignore.
+            }
+          };
+        }
+
+        @Override
+        public void begin(String title) {
+          // Ignore.
+        }
+
+        @Override
+        public void end() {
+          // Ignore.
+        }
+
+        @Override
+        public void report() {
+          // Ignore.
+        }
+
+        @Override
+        public void scope(String title, TimingScope fn) {
+          // Ignore.
+        }
+      };
+
+  public static Timing empty() {
+    return Timing.EMPTY;
+  }
+
+  public static Timing create(String title, InternalOptions options) {
+    // We also create a timer when running assertions to validate wellformedness of the node stack.
+    return options.printTimes || InternalOptions.assertionsEnabled()
+        ? new Timing(title, options.printMemory)
+        : Timing.empty();
+  }
+
+  private final Node top;
   private final Stack<Node> stack;
   private final boolean trackMemory;
 
-  public Timing() {
-    this("<no title>");
-  }
-
+  @Deprecated
   public Timing(String title) {
     this(title, false);
   }
 
-  public Timing(String title, boolean trackMemory) {
+  private Timing(String title, boolean trackMemory) {
     this.trackMemory = trackMemory;
     stack = new Stack<>();
-    stack.push(new Node("Recorded timings for " + title));
+    top = new Node(title, trackMemory);
+    stack.push(top);
   }
 
   private static class MemInfo {
@@ -53,8 +107,9 @@
     }
   }
 
-  class Node {
+  static class Node {
     final String title;
+    final boolean trackMemory;
 
     final Map<String, Node> children = new LinkedHashMap<>();
     long duration = 0;
@@ -62,8 +117,9 @@
     Map<String, MemInfo> startMemory;
     Map<String, MemInfo> endMemory;
 
-    Node(String title) {
+    Node(String title, boolean trackMemory) {
       this.title = title;
+      this.trackMemory = trackMemory;
       if (trackMemory) {
         startMemory = computeMemoryInformation();
       }
@@ -98,25 +154,50 @@
 
     public String toString(Node top) {
       if (this == top) return toString();
-      return toString() + " (" + prettyPercentage(duration(), top.duration()) + ")";
+      return "(" + prettyPercentage(duration(), top.duration()) + ") " + toString();
     }
 
     public void report(int depth, Node top) {
       assert duration() >= 0;
-      if (depth > 0) {
-        for (int i = 0; i < depth; i++) {
-          System.out.print("  ");
-        }
-        System.out.print("- ");
+      if (percentage(duration(), top.duration()) < MINIMUM_REPORT_PERCENTAGE) {
+        return;
       }
+      printPrefix(depth);
       System.out.println(toString(top));
       if (trackMemory) {
         printMemory(depth);
       }
-      children.values().forEach(p -> p.report(depth + 1, top));
+      if (children.isEmpty()) {
+        return;
+      }
+      Collection<Node> childNodes = children.values();
+      long childTime = 0;
+      for (Node childNode : childNodes) {
+        childTime += childNode.duration();
+      }
+      if (childTime < duration()) {
+        long unaccounted = duration() - childTime;
+        if (percentage(unaccounted, top.duration()) >= MINIMUM_REPORT_PERCENTAGE) {
+          printPrefix(depth + 1);
+          System.out.println(
+              "("
+                  + prettyPercentage(unaccounted, top.duration())
+                  + ") Unaccounted: "
+                  + prettyTime(unaccounted));
+        }
+      }
+      childNodes.forEach(p -> p.report(depth + 1, top));
+
     }
 
-    private void printMemory(int depth) {
+    void printPrefix(int depth) {
+      if (depth > 0) {
+        System.out.print(Strings.repeat("  ", depth));
+        System.out.print("- ");
+      }
+    }
+
+    void printMemory(int depth) {
       for (Entry<String, MemInfo> start : startMemory.entrySet()) {
         if (start.getKey().equals("Memory")) {
           for (int i = 0; i <= depth; i++) {
@@ -137,8 +218,111 @@
     }
   }
 
+  public static class TimingMerger {
+    final Node parent;
+    final Node merged;
+
+    private int taskCount = 0;
+    private Node slowest = new Node("<zero>", false);
+
+    private TimingMerger(String title, int numberOfThreads, Timing timing) {
+      parent = timing.stack.peek();
+      merged =
+          new Node(title, timing.trackMemory) {
+            @Override
+            public void report(int depth, Node top) {
+              assert duration() >= 0;
+              printPrefix(depth);
+              System.out.print(toString());
+              if (numberOfThreads <= 0) {
+                System.out.println(" (unknown thread count)");
+              } else {
+                long walltime = parent.duration();
+                long perThreadTime = duration() / numberOfThreads;
+                System.out.println(
+                    ", tasks: "
+                        + taskCount
+                        + ", threads: "
+                        + numberOfThreads
+                        + ", utilization: "
+                        + prettyPercentage(perThreadTime, walltime));
+              }
+              if (trackMemory) {
+                printMemory(depth);
+              }
+              // Report children with this merge node as "top" so times are relative to the total
+              // merge.
+              children.forEach((title, node) -> node.report(depth + 1, this));
+              // Print the slowest entry if one was found.
+              if (slowest.duration > 0) {
+                printPrefix(depth);
+                System.out.println("SLOWEST " + slowest.toString(this));
+                slowest.children.forEach((title, node) -> node.report(depth + 1, this));
+              }
+            }
+
+            @Override
+            public String toString() {
+              return "MERGE " + super.toString();
+            }
+          };
+    }
+
+    private static class Item {
+      final Node mergeTarget;
+      final Node mergeSource;
+
+      public Item(Node mergeTarget, Node mergeSource) {
+        this.mergeTarget = mergeTarget;
+        this.mergeSource = mergeSource;
+      }
+    }
+
+    public void add(Collection<Timing> timings) {
+      final boolean trackMemory = merged.trackMemory;
+      Deque<Item> worklist = new ArrayDeque<>();
+      for (Timing timing : timings) {
+        if (timing == empty()) {
+          continue;
+        }
+        assert timing.stack.isEmpty() : "Expected sub-timing to have completed prior to merge";
+        ++taskCount;
+        merged.duration += timing.top.duration;
+        if (timing.top.duration > slowest.duration) {
+          slowest = timing.top;
+        }
+        worklist.addLast(new Item(merged, timing.top));
+      }
+      while (!worklist.isEmpty()) {
+        Item item = worklist.pollFirst();
+        item.mergeSource.children.forEach(
+            (title, child) -> {
+              Node mergeTarget =
+                  item.mergeTarget.children.computeIfAbsent(title, t -> new Node(t, trackMemory));
+              mergeTarget.duration += child.duration;
+              if (!child.children.isEmpty()) {
+                worklist.addLast(new Item(mergeTarget, child));
+              }
+            });
+      }
+    }
+
+    public void end() {
+      assert !parent.children.containsKey(merged.title);
+      parent.children.put(merged.title, merged);
+    }
+  }
+
+  public TimingMerger beginMerger(String title, int numberOfThreads) {
+    return new TimingMerger(title, numberOfThreads, this);
+  }
+
+  private static long percentage(long part, long total) {
+    return part * 100 / total;
+  }
+
   private static String prettyPercentage(long part, long total) {
-    return (part * 100 / total) + "%";
+    return percentage(part, total) + "%";
   }
 
   private static String prettyTime(long value) {
@@ -176,7 +360,7 @@
       child = parent.children.get(title);
       child.restart();
     } else {
-      child = new Node(title);
+      child = new Node(title, trackMemory);
       parent.children.put(title, child);
     }
     stack.push(child);
@@ -188,9 +372,11 @@
   }
 
   public void report() {
+    assert stack.size() == 1;
     Node top = stack.peek();
+    assert top == this.top;
     top.end();
-    System.out.println();
+    System.out.println("Recorded timings:");
     top.report(0, top);
   }
 
@@ -207,7 +393,7 @@
     void apply();
   }
 
-  private Map<String, MemInfo> computeMemoryInformation() {
+  private static Map<String, MemInfo> computeMemoryInformation() {
     System.gc();
     Map<String, MemInfo> info = new LinkedHashMap<>();
     info.put(
diff --git a/src/test/examplesAndroidO/classmerging/LambdaRewritingTest.java b/src/test/examplesAndroidO/classmerging/LambdaRewritingTest.java
index 8357eee..621ae07 100644
--- a/src/test/examplesAndroidO/classmerging/LambdaRewritingTest.java
+++ b/src/test/examplesAndroidO/classmerging/LambdaRewritingTest.java
@@ -11,13 +11,19 @@
 
     // Leads to an invoke-custom instruction that mentions the type of `obj` since it is captured.
     invoke(() -> obj.foo());
+
+    FunctionImpl functionImpl = new FunctionImpl();
+    if (System.currentTimeMillis() < 0) {
+      System.out.println(functionImpl);
+    }
   }
 
+  @NeverInline
   private static void invoke(Function f) {
     f.accept();
   }
 
-  // Must not be merged into FunctionImpl as it is instantiated by a lambda.
+  // Cannot be merged as it has two subtypes: FunctionImpl and a lambda.
   public interface Function {
 
     void accept();
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index d740e54..d6732bd 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import java.nio.file.Path;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
@@ -35,11 +36,17 @@
     return builder;
   }
 
-  public D8TestBuilder addProgramResourceProvider(ProgramResourceProvider provider) {
-    builder.addProgramResourceProvider(provider);
+  public D8TestBuilder addProgramResourceProviders(Collection<ProgramResourceProvider> providers) {
+    for (ProgramResourceProvider provider : providers) {
+      builder.addProgramResourceProvider(provider);
+    }
     return self();
   }
 
+  public D8TestBuilder addProgramResourceProviders(ProgramResourceProvider... providers) {
+    return addProgramResourceProviders(Arrays.asList(providers));
+  }
+
   @Override
   public D8TestBuilder addClasspathClasses(Collection<Class<?>> classes) {
     builder.addClasspathResourceProvider(ClassFileResourceProviderFromClasses(classes));
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 92d02ee..39ac985 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, 137, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 119, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -110,7 +110,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 8, "lambdadesugaring"))
         .run();
   }
 
@@ -142,7 +142,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 137, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 119, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -150,7 +150,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 8, "lambdadesugaring"))
         .run();
   }
 
@@ -164,7 +164,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 31, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 33, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -174,7 +174,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaringnplus"))
         .run();
   }
 
@@ -188,7 +188,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 31, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 33, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -198,7 +198,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaringnplus"))
         .run();
   }
 
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 59ae602..1dca05c 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -119,11 +119,17 @@
     return builder;
   }
 
-  public T addProgramResourceProvider(ProgramResourceProvider provider) {
-    builder.addProgramResourceProvider(provider);
+  public T addProgramResourceProviders(Collection<ProgramResourceProvider> providers) {
+    for (ProgramResourceProvider provider : providers) {
+      builder.addProgramResourceProvider(provider);
+    }
     return self();
   }
 
+  public T addProgramResourceProviders(ProgramResourceProvider... providers) {
+    return addProgramResourceProviders(Arrays.asList(providers));
+  }
+
   @Override
   public T addClasspathClasses(Collection<Class<?>> classes) {
     builder.addClasspathResourceProvider(ClassFileResourceProviderFromClasses(classes));
diff --git a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
index 80fe207..9cb139c 100644
--- a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
+++ b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
@@ -38,7 +38,7 @@
             .addProgramFiles(SMALI_DIR.resolve(name).resolve(name + ".dex"))
             .build();
     ExecutorService executorService = Executors.newSingleThreadExecutor();
-    Timing timing = new Timing("R8UnreachableCodeTest");
+    Timing timing = Timing.empty();
     InternalOptions options = new InternalOptions();
     options.programConsumer = DexIndexedConsumer.emptyConsumer();
     DirectMappedDexApplication application =
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 203a07b..c1a612f 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -48,6 +48,8 @@
 import com.android.tools.r8.shaking.ProguardKeepRule;
 import com.android.tools.r8.shaking.ProguardKeepRule.Builder;
 import com.android.tools.r8.shaking.ProguardKeepRuleType;
+import com.android.tools.r8.shaking.ProguardMemberRule;
+import com.android.tools.r8.shaking.ProguardMemberType;
 import com.android.tools.r8.shaking.ProguardTypeMatcher;
 import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -423,6 +425,10 @@
     return buildClasses(programClasses, libraryClasses).build();
   }
 
+  protected static AndroidApp.Builder buildClasses(Class<?>... programClasses) throws IOException {
+    return buildClasses(Arrays.asList(programClasses), Collections.emptyList());
+  }
+
   protected static AndroidApp.Builder buildClasses(Collection<Class<?>> programClasses)
       throws IOException {
     return buildClasses(programClasses, Collections.emptyList());
@@ -559,9 +565,7 @@
   protected static AppInfo computeAppInfo(AndroidApp application) {
     try {
       DexApplication dexApplication =
-          new ApplicationReader(
-                  application, new InternalOptions(), new Timing("TestBase.computeAppInfo"))
-              .read();
+          new ApplicationReader(application, new InternalOptions(), Timing.empty()).read();
       return new AppInfo(dexApplication);
     } catch (IOException | ExecutionException e) {
       throw new RuntimeException(e);
@@ -572,7 +576,7 @@
       AndroidApp application) {
     try {
       DexApplication dexApplication =
-          new ApplicationReader(application, new InternalOptions(), new Timing()).read();
+          new ApplicationReader(application, new InternalOptions(), Timing.empty()).read();
       return new AppInfoWithClassHierarchy(dexApplication);
     } catch (IOException | ExecutionException e) {
       throw new RuntimeException(e);
@@ -581,7 +585,7 @@
 
   protected static AppView<AppInfoWithSubtyping> computeAppViewWithSubtyping(AndroidApp app)
       throws Exception {
-    Timing timing = new Timing();
+    Timing timing = Timing.empty();
     InternalOptions options = new InternalOptions();
     DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
     AppView<AppInfoWithSubtyping> appView =
@@ -598,7 +602,9 @@
     DexApplication application = appView.appInfo().app();
     RootSet rootSet =
         new RootSetBuilder(
-                appView, application, buildKeepRuleForClass(mainClass, application.dexItemFactory))
+                appView,
+                application,
+                buildKeepRuleForClassAndMethods(mainClass, application.dexItemFactory))
             .run(executor);
     AppInfoWithLiveness appInfoWithLiveness =
         EnqueuerFactory.createForInitialTreeShaking(appView)
@@ -638,6 +644,13 @@
         method.getMethodName());
   }
 
+  protected static DexMethod buildNullaryVoidMethod(
+      Class clazz, String name, DexItemFactory factory) {
+    return buildMethod(
+        Reference.method(Reference.classFromClass(clazz), name, Collections.emptyList(), null),
+        factory);
+  }
+
   protected static DexProto buildProto(
       TypeReference returnType, List<TypeReference> formalTypes, DexItemFactory factory) {
     return factory.createProto(
@@ -657,6 +670,21 @@
     return Collections.singletonList(keepRuleBuilder.build());
   }
 
+  private static List<ProguardConfigurationRule> buildKeepRuleForClassAndMethods(
+      Class clazz, DexItemFactory factory) {
+    Builder keepRuleBuilder = ProguardKeepRule.builder();
+    keepRuleBuilder.setSource("buildKeepRuleForClass " + clazz.getTypeName());
+    keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
+    keepRuleBuilder.setClassNames(
+        ProguardClassNameList.singletonList(
+            ProguardTypeMatcher.create(
+                factory.createType(DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName())))));
+    keepRuleBuilder.setMemberRules(
+        Lists.newArrayList(
+            ProguardMemberRule.builder().setRuleType(ProguardMemberType.ALL_METHODS).build()));
+    return Collections.singletonList(keepRuleBuilder.build());
+  }
+
   /** Returns a list containing all the data resources in the given app. */
   public static List<DataEntryResource> getDataResources(AndroidApp app) throws ResourceException {
     List<DataEntryResource> dataResources = new ArrayList<>();
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index fc9c8db..d494ea2 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -49,6 +49,10 @@
     return runtime;
   }
 
+  public boolean useRuntimeAsNoneRuntime() {
+    return isNoneRuntime() || (runtime != null && runtime.equals(TestRuntime.getCheckedInJdk9()));
+  }
+
   // Helper function to get the "backend" for a given runtime target.
   public Backend getBackend() {
     return runtime.getBackend();
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 19c6edd..2defd19 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -150,6 +150,13 @@
     return addKeepMainRule(mainClass.getTypeName());
   }
 
+  public T addKeepMainRules(Class<?>[] mainClasses) {
+    for (Class<?> mainClass : mainClasses) {
+      this.addKeepMainRule(mainClass);
+    }
+    return self();
+  }
+
   public T addKeepMainRule(String mainClass) {
     return addKeepRules(
         "-keep class " + mainClass + " { public static void main(java.lang.String[]); }");
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 87b2806..effe9d9 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1066,9 +1066,7 @@
             .addProgramFiles(ListUtils.map(fileNames, Paths::get))
             .addLibraryFiles(androidJar)
             .build();
-    return new ApplicationReader(
-        input, new InternalOptions(), new Timing("ToolHelper buildApplication"))
-        .read().toDirect();
+    return new ApplicationReader(input, new InternalOptions(), Timing.empty()).read().toDirect();
   }
 
   public static ProguardConfiguration loadProguardConfiguration(
@@ -2085,7 +2083,7 @@
   public static void disassemble(AndroidApp app, PrintStream ps)
       throws IOException, ExecutionException {
     DexApplication application =
-        new ApplicationReader(app, new InternalOptions(), new Timing()).read().toDirect();
+        new ApplicationReader(app, new InternalOptions(), Timing.empty()).read().toDirect();
     new AssemblyWriter(application, new InternalOptions(), true, false).write(ps);
   }
 
diff --git a/src/test/java/com/android/tools/r8/bisect/BisectTest.java b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
index 02f6b35..28ecca3 100644
--- a/src/test/java/com/android/tools/r8/bisect/BisectTest.java
+++ b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
@@ -89,7 +89,7 @@
       Result lastResult = Result.UNKNOWN;
       while (clazz == null) {
         InternalOptions options = new InternalOptions();
-        Timing timing = new Timing("bisect-test");
+        Timing timing = Timing.empty();
         DexApplication appGood = new ApplicationReader(goodInput, options, timing).read();
         DexApplication appBad = new ApplicationReader(badInput, options, timing).read();
         BisectState state = new BisectState(appGood, appBad, stateFile);
@@ -113,7 +113,7 @@
   @Test
   public void bisectWithInternalCommand() throws Exception {
     InternalOptions options = new InternalOptions();
-    Timing timing = new Timing("bisect-test");
+    Timing timing = Timing.empty();
     DexApplication appGood = new ApplicationReader(buildGood(), options, timing).read();
     DexApplication appBad = new ApplicationReader(buildBad(), options, timing).read();
     ExecutorService executor = Executors.newWorkStealingPool();
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index cf013d1..09489c9 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -13,7 +13,8 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.StringConsumer.FileConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
@@ -34,6 +35,7 @@
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AndroidApp.Builder;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -337,6 +339,7 @@
         ImmutableSet.of(
             "classmerging.LambdaRewritingTest",
             "classmerging.LambdaRewritingTest$Function",
+            "classmerging.LambdaRewritingTest$FunctionImpl",
             "classmerging.LambdaRewritingTest$InterfaceImpl");
     runTestOnInput(
         main,
@@ -1223,21 +1226,31 @@
       VerticalClassMergerDebugTest debugTestRunner)
       throws Throwable {
     Path proguardMapPath = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
-    R8Command.Builder commandBuilder =
-        ToolHelper.prepareR8CommandBuilder(input)
-            .setDisableMinification(true)
-            .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
-    ToolHelper.allowTestProguardOptions(commandBuilder);
-    AndroidApp output =
-        ToolHelper.runR8(
-            commandBuilder.build(),
-            options -> {
-              optionsConsumer.accept(options);
-              options.proguardMapConsumer =
-                  new FileConsumer(proguardMapPath, options.proguardMapConsumer);
-            });
+    R8TestCompileResult compileResult =
+        testForR8(Backend.DEX)
+            .apply(
+                b -> {
+                  // Some tests add DEX inputs, so circumvent the check by adding directly to the
+                  // app.
+                  Builder appBuilder = ToolHelper.getAppBuilder(b.getBuilder());
+                  for (ProgramResourceProvider provider : input.getProgramResourceProviders()) {
+                    appBuilder.addProgramResourceProvider(provider);
+                  }
+                })
+            .noMinification()
+            .addKeepRules(proguardConfig)
+            .enableProguardTestOptions()
+            .addOptionsModification(
+                o -> {
+                  optionsConsumer.accept(o);
+                  o.testing.allowUnusedProguardConfigurationRules = true;
+                  o.proguardMapConsumer = new FileConsumer(proguardMapPath, o.proguardMapConsumer);
+                })
+            .compile();
+
     CodeInspector inputInspector = new CodeInspector(input);
-    CodeInspector outputInspector = new CodeInspector(output);
+    CodeInspector outputInspector = compileResult.inspector();
+
     // Check that all classes in [preservedClassNames] are in fact preserved.
     for (FoundClassSubject classSubject : inputInspector.allClasses()) {
       String className = classSubject.getOriginalName();
@@ -1247,12 +1260,19 @@
           shouldBePresent,
           outputInspector.clazz(className).isPresent());
     }
+
     // Check that the R8-generated code produces the same result as D8-generated code.
-    assertEquals(runOnArt(compileWithD8(input), main), runOnArt(output, main));
+    String d8Result =
+        testForD8()
+            .addProgramResourceProviders(input.getProgramResourceProviders())
+            .run(main)
+            .assertSuccess()
+            .getStdOut();
+    compileResult.run(main).assertSuccessWithOutput(d8Result);
     // Check that we never come across a method that has a name with "$classmerging$" in it during
     // debugging.
     if (debugTestRunner != null) {
-      debugTestRunner.test(output, proguardMapPath);
+      debugTestRunner.test(compileResult.app, proguardMapPath);
     }
     return outputInspector;
   }
diff --git a/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java b/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
index 866dcf5..e17ac14 100644
--- a/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
@@ -67,10 +67,7 @@
     Marker selfie = Marker.parse(markerDexString);
     assert marker.equals(selfie);
     AndroidApp app = ToolHelper.runD8(command, opts -> opts.setMarker(marker));
-    DexApplication dexApp =
-        new ApplicationReader(
-                app, options, new Timing("D8FrameworkDexPassthroughMarkerTest"))
-            .read();
+    DexApplication dexApp = new ApplicationReader(app, options, Timing.empty()).read();
     Collection<Marker> markers = dexApp.dexItemFactory.extractMarkers();
     assertEquals(1, markers.size());
     Marker readMarker = markers.iterator().next();
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java b/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
new file mode 100644
index 0000000..211050e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
@@ -0,0 +1,112 @@
+// 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 static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DefaultInterfaceWithIdentifierNameString extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    // Test expect that default interface method desugaring is enabled.
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(AndroidApiLevel.N)
+        .build();
+  }
+
+  public DefaultInterfaceWithIdentifierNameString(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private boolean isInvokeClassForName(InstructionSubject instruction) {
+    if (!instruction.isInvokeStatic()) {
+      return false;
+    }
+    return ((InvokeInstructionSubject) instruction)
+        .invokedMethod()
+        .name
+        .toString()
+        .equals("forName");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(A.class);
+    assertThat(classSubject, isRenamed());
+    ClassSubject companionClassSubject = inspector.companionClassFor(I.class);
+    assertThat(companionClassSubject, isRenamed());
+    companionClassSubject
+        .allMethods()
+        .forEach(
+            method -> assertTrue(method.streamInstructions().anyMatch(this::isInvokeClassForName)));
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(DefaultInterfaceWithIdentifierNameString.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("new A", "new A", "DONE");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      I.newInstance().antoherInstance();
+      System.out.println("DONE");
+    }
+  }
+
+  @NeverMerge
+  interface I {
+    @NeverInline
+    static I newInstance() throws Exception {
+      return (I)
+          Class.forName("com.android.tools.r8.desugar.DefaultInterfaceWithIdentifierNameString$A")
+              .newInstance();
+    }
+
+    @NeverInline
+    default I antoherInstance() throws Exception {
+      return (I)
+          Class.forName("com.android.tools.r8.desugar.DefaultInterfaceWithIdentifierNameString$A")
+              .newInstance();
+    }
+  }
+
+  @NeverClassInline
+  static class A implements I {
+    A() {
+      System.out.println("new A");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarInstanceLambdaWithReadsTest.java b/src/test/java/com/android/tools/r8/desugar/DesugarInstanceLambdaWithReadsTest.java
new file mode 100644
index 0000000..07e4f62
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarInstanceLambdaWithReadsTest.java
@@ -0,0 +1,112 @@
+// 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 static org.junit.Assert.assertEquals;
+
+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 com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugarInstanceLambdaWithReadsTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("false");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public DesugarInstanceLambdaWithReadsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class, A.class, B.class, Consumer.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkJustOneLambdaImplementationMethod);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, A.class, B.class, Consumer.class)
+        .addKeepClassRules(Consumer.class)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkJustOneLambdaImplementationMethod);
+  }
+
+  private void checkJustOneLambdaImplementationMethod(CodeInspector inspector) {
+    List<FoundMethodSubject> lambdaImplementationMethods =
+        inspector.clazz(Main.class).allMethods(m -> m.getOriginalName().startsWith("lambda$"));
+    assertEquals(1, lambdaImplementationMethods.size());
+  }
+
+  private interface Consumer {
+    void accept(String arg);
+  }
+
+  abstract static class A {
+    abstract boolean contains(String item);
+  }
+
+  // A is expected to be merged into B.
+  static class B extends A {
+    final List<String> items;
+
+    public B(List<String> items) {
+      this.items = items;
+    }
+
+    @NeverInline
+    @Override
+    boolean contains(String item) {
+      return items.contains(item);
+    }
+  }
+
+  static class Main {
+    // Field that is read from the lambda$ method.
+    A filter;
+
+    public Main(A filter) {
+      this.filter = filter;
+    }
+
+    @NeverInline
+    public static void forEach(List<String> args, Consumer fn) {
+      for (String arg : args) {
+        fn.accept(arg);
+      }
+    }
+
+    public void foo(List<String> args) {
+      forEach(args, arg -> System.out.println(filter.contains(arg)));
+    }
+
+    public static void main(String[] args) {
+      new Main(new B(Arrays.asList(args))).foo(Collections.singletonList("hello!"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugaredLambdaImplementationInliningTest.java b/src/test/java/com/android/tools/r8/desugar/DesugaredLambdaImplementationInliningTest.java
index 9453575..cb1b96e 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugaredLambdaImplementationInliningTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugaredLambdaImplementationInliningTest.java
@@ -6,40 +6,59 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 // Test if the static method of the lambda implementation (SINGLE_CALLER) is inlined into the
 // interface method of the lambda class.
+@RunWith(Parameterized.class)
 public class DesugaredLambdaImplementationInliningTest extends TestBase {
+
+  static final String EXPECTED =
+      StringUtils.lines(
+          "Running desugared stateless lambda.",
+          "Running desugared stateful lambda: lambda-state.");
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  final TestParameters parameters;
+
+  public DesugaredLambdaImplementationInliningTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
   @Test
   public void test() throws Exception {
     class Counter {
       private int i = 0;
     }
     Counter counter = new Counter();
-    R8TestRunResult result =
-        testForR8Compat(Backend.DEX)
-            .addInnerClasses(DesugaredLambdaImplementationInliningTest.class)
-            .addKeepMainRule(DesugaredLambdaImplementationInliningTest.TestClass.class)
-            .noMinification()
-            .run(TestClass.class)
-            .assertSuccess()
-            .inspect(
-                inspector -> {
-                  inspector
-                      .clazz(DesugaredLambdaImplementationInliningTest.TestClass.class)
-                      .forAllMethods(
-                          fms -> {
-                            if (fms.isStatic() && !fms.getOriginalName().equals("main")) {
-                              ++counter.i;
-                            }
-                          });
-                });
-
-    // TODO(b/126323172) Change expected value to zero after fixed.
-    assertEquals(2, counter.i);
+    testForR8Compat(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspector()
+        .clazz(TestClass.class)
+        .forAllMethods(
+            fms -> {
+              if (fms.isStatic() && !fms.getOriginalName().equals("main")) {
+                ++counter.i;
+              }
+            });
+    // On CF the lambdas remain.
+    assertEquals(parameters.isDexRuntime() ? 0 : 2, counter.i);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DumpCoreLibUsage.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DumpCoreLibUsage.java
index 7513e50..62d2d3a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DumpCoreLibUsage.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DumpCoreLibUsage.java
@@ -66,7 +66,7 @@
     AndroidApp input =
         AndroidApp.builder().addLibraryFiles(ToolHelper.getAndroidJar(apiLevel)).build();
     DirectMappedDexApplication dexApplication =
-        new ApplicationReader(input, new InternalOptions(factory, new Reporter()), new Timing())
+        new ApplicationReader(input, new InternalOptions(factory, new Reporter()), Timing.empty())
             .read()
             .toDirect();
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java
index b161386..2801eb9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
-import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
@@ -31,8 +30,7 @@
 
   private static final Path ATOMIC_TESTS_FOLDER =
       Paths.get(ToolHelper.JDK_11_TESTS_DIR + "java/util/concurrent/atomic/");
-  private static final Path ATOMIC_COMPILED_TESTS_FOLDER =
-      Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "Atomic/");
+  private static Path ATOMIC_COMPILED_TESTS_FOLDER;
   private static final String ATOMIC_REFERENCE_TEST = "AtomicReferenceTest";
   private static final String ATOMIC_UPDATERS = "AtomicUpdaters";
   private static final Path[] ATOMIC_TESTS_FILES =
@@ -63,8 +61,7 @@
 
   @BeforeClass
   public static void compileAtomicClasses() throws Exception {
-    File atomicClassesDir = new File(ATOMIC_COMPILED_TESTS_FOLDER.toString());
-    assert atomicClassesDir.exists() || atomicClassesDir.mkdirs();
+    ATOMIC_COMPILED_TESTS_FOLDER = getStaticTemp().newFolder("atomic").toPath();
     javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
         .addClasspathFiles(
             Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java
index fa595cb..7d0fc6b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
-import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -37,10 +36,6 @@
       Paths.get(ToolHelper.JDK_11_TESTS_DIR + "java/util/concurrent/ConcurrentMap/");
   private static final Path CONCURRENT_HASH_TESTS_FOLDER =
       Paths.get(ToolHelper.JDK_11_TESTS_DIR + "java/util/concurrent/ConcurrentHashMap/");
-  private static final Path CONCURRENT_COMPILED_TESTS_FOLDER =
-      Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "ConcurrentMap/");
-  private static final Path CONCURRENT_HASH_COMPILED_TESTS_FOLDER =
-      Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "ConcurrentHashMap/");
   private static Path[] CONCURRENT_COMPILED_TESTS_FILES;
   private static Path[] CONCURRENT_HASH_COMPILED_TESTS_FILES;
   private static final Path[] SUPPORT_LIBS =
@@ -71,16 +66,15 @@
 
   @BeforeClass
   public static void compileConcurrentClasses() throws Exception {
-    File concurrentClassesDir = new File(CONCURRENT_COMPILED_TESTS_FOLDER.toString());
-    assert concurrentClassesDir.exists() || concurrentClassesDir.mkdirs();
+    Path concurrentCompiledTestsFolder = getStaticTemp().newFolder("concurrentmap").toPath();
     javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
         .addClasspathFiles(
             Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
         .addSourceFiles(getAllFilesWithSuffixInDirectory(CONCURRENT_TESTS_FOLDER, JAVA_EXTENSION))
-        .setOutputPath(CONCURRENT_COMPILED_TESTS_FOLDER)
+        .setOutputPath(concurrentCompiledTestsFolder)
         .compile();
     CONCURRENT_COMPILED_TESTS_FILES =
-        getAllFilesWithSuffixInDirectory(CONCURRENT_COMPILED_TESTS_FOLDER, CLASS_EXTENSION);
+        getAllFilesWithSuffixInDirectory(concurrentCompiledTestsFolder, CLASS_EXTENSION);
     assert CONCURRENT_COMPILED_TESTS_FILES.length > 0;
 
     List<Path> concurrentHashFilesAndDependencies = new ArrayList<>();
@@ -89,16 +83,16 @@
         getAllFilesWithSuffixInDirectory(CONCURRENT_HASH_TESTS_FOLDER, JAVA_EXTENSION));
     Collections.addAll(concurrentHashFilesAndDependencies, SUPPORT_LIBS);
     Path[] classesToCompile = concurrentHashFilesAndDependencies.toArray(new Path[0]);
-    File concurrentHashClassesDir = new File(CONCURRENT_HASH_COMPILED_TESTS_FOLDER.toString());
-    assert concurrentHashClassesDir.exists() || concurrentHashClassesDir.mkdirs();
+    Path concurrentHashCompiledTestsFolder =
+        getStaticTemp().newFolder("concurrenthashmap").toPath();
     javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
         .addClasspathFiles(
             Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
         .addSourceFiles(classesToCompile)
-        .setOutputPath(CONCURRENT_HASH_COMPILED_TESTS_FOLDER)
+        .setOutputPath(concurrentHashCompiledTestsFolder)
         .compile();
     CONCURRENT_HASH_COMPILED_TESTS_FILES =
-        getAllFilesWithSuffixInDirectory(CONCURRENT_HASH_COMPILED_TESTS_FOLDER, CLASS_EXTENSION);
+        getAllFilesWithSuffixInDirectory(concurrentHashCompiledTestsFolder, CLASS_EXTENSION);
     assert CONCURRENT_HASH_COMPILED_TESTS_FILES.length > 0;
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11DesugaredLibraryTestBase.java
index 94c128e..248302a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11DesugaredLibraryTestBase.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
-import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
@@ -27,8 +26,7 @@
 public class Jdk11DesugaredLibraryTestBase extends DesugaredLibraryTestBase {
 
   protected static Path[] JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES;
-  protected static final Path JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR =
-      Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "Bootlib");
+  static Path JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR;
   private static final Path JDK_11_JAVA_BASE_EXTENSION_FILES_DIR =
       Paths.get("third_party/openjdk/jdk-11-test/lib/testlibrary/bootlib/java.base");
 
@@ -72,8 +70,7 @@
 
   @BeforeClass
   public static void compileJavaBaseExtensions() throws Exception {
-    File extensionClassesDir = new File(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR.toString());
-    assert extensionClassesDir.exists() || extensionClassesDir.mkdirs();
+    JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR = getStaticTemp().newFolder("jdk11JavaBaseExt").toPath();
     List<String> options =
         Arrays.asList(
             "--add-reads",
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11MathTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11MathTests.java
index 713061e..8a07522 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11MathTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11MathTests.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
-import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.junit.Assume;
@@ -28,18 +27,8 @@
   private static final String DIVMOD = "DivModTests";
   private static final String EXACTARITH = "ExactArithTests";
 
-  // Build test constants.
-  private static final Path JDK_11_MATH_TESTS_DIR =
-      Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "Math");
-  private static final Path JDK_11_STRICT_MATH_TESTS_DIR =
-      Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "StrictMath");
-  private static final Path[] JDK_11_MATH_TEST_CLASS_FILES =
-      new Path[] {
-        JDK_11_MATH_TESTS_DIR.resolve(DIVMOD + CLASS_EXTENSION),
-        JDK_11_MATH_TESTS_DIR.resolve(EXACTARITH + CLASS_EXTENSION)
-      };
-  private static final Path[] JDK_11_STRICT_MATH_TEST_CLASS_FILES =
-      new Path[] {JDK_11_STRICT_MATH_TESTS_DIR.resolve(EXACTARITH + CLASS_EXTENSION)};
+  private static Path[] JDK_11_MATH_TEST_CLASS_FILES;
+  private static Path[] JDK_11_STRICT_MATH_TEST_CLASS_FILES;
 
   // JDK 11 test constants.
   private static final Path JDK_11_MATH_JAVA_DIR =
@@ -67,19 +56,25 @@
 
   @BeforeClass
   public static void compileMathClasses() throws Exception {
-    File mathClassesDir = new File(JDK_11_MATH_TESTS_DIR.toString());
-    assert mathClassesDir.exists() || mathClassesDir.mkdirs();
+    // Build test constants.
+    Path jdk11MathTestsDir = getStaticTemp().newFolder("math").toPath();
     javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
         .addSourceFiles(JDK_11_MATH_JAVA_FILES)
-        .setOutputPath(JDK_11_MATH_TESTS_DIR)
+        .setOutputPath(jdk11MathTestsDir)
         .compile();
+    JDK_11_MATH_TEST_CLASS_FILES =
+        new Path[] {
+          jdk11MathTestsDir.resolve(DIVMOD + CLASS_EXTENSION),
+          jdk11MathTestsDir.resolve(EXACTARITH + CLASS_EXTENSION)
+        };
 
-    File strictMathClassesDir = new File(JDK_11_STRICT_MATH_TESTS_DIR.toString());
-    assert strictMathClassesDir.exists() || strictMathClassesDir.mkdirs();
+    Path jdk11StrictMathTestsDir = getStaticTemp().newFolder("strictmath").toPath();
     javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
         .addSourceFiles(JDK_11_STRICT_MATH_JAVA_FILES)
-        .setOutputPath(JDK_11_STRICT_MATH_TESTS_DIR)
+        .setOutputPath(jdk11StrictMathTestsDir)
         .compile();
+    JDK_11_STRICT_MATH_TEST_CLASS_FILES =
+        new Path[] {jdk11StrictMathTestsDir.resolve(EXACTARITH + CLASS_EXTENSION)};
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ObjectsTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ObjectsTests.java
index c24839f..c49e10c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ObjectsTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ObjectsTests.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
-import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.junit.Assume;
@@ -26,13 +25,8 @@
 @RunWith(Parameterized.class)
 public class Jdk11ObjectsTests extends TestBase {
 
-  private static final Path JDK_11_OBJECTS_TESTS_DIR =
-      Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "Objects");
   private static final String BASIC_OBJECTS_TEST = "BasicObjectsTest";
-  private static final Path[] JDK_11_OBJECTS_TEST_CLASS_FILES =
-      new Path[] {
-        JDK_11_OBJECTS_TESTS_DIR.resolve(BASIC_OBJECTS_TEST + CLASS_EXTENSION),
-      };
+  private static Path[] JDK_11_OBJECTS_TEST_CLASS_FILES;
   private static final Path JDK_11_OBJECTS_JAVA_DIR =
       Paths.get(ToolHelper.JDK_11_TESTS_DIR + "java/util/Objects");
 
@@ -54,12 +48,15 @@
 
   @BeforeClass
   public static void compileObjectsClass() throws Exception {
-    File objectsDir = new File(JDK_11_OBJECTS_TESTS_DIR.toString());
-    assert objectsDir.exists() || objectsDir.mkdirs();
+    Path jdk11ObjectsTestsDir = getStaticTemp().newFolder("objects").toPath();
     javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
         .addSourceFiles(JDK_11_OBJECTS_JAVA_DIR.resolve(BASIC_OBJECTS_TEST + JAVA_EXTENSION))
-        .setOutputPath(JDK_11_OBJECTS_TESTS_DIR)
+        .setOutputPath(jdk11ObjectsTestsDir)
         .compile();
+    JDK_11_OBJECTS_TEST_CLASS_FILES =
+        new Path[] {
+          jdk11ObjectsTestsDir.resolve(BASIC_OBJECTS_TEST + CLASS_EXTENSION),
+        };
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
index 3f4a1ee..9d9b5cb 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
-import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
@@ -58,8 +57,7 @@
     this.parameters = parameters;
   }
 
-  private static final Path JDK_11_STREAM_TEST_CLASSES_DIR =
-      Paths.get(ToolHelper.JDK_11_TESTS_CLASSES_DIR + "Stream");
+  private static Path JDK_11_STREAM_TEST_CLASSES_DIR;
   private static final Path JDK_11_STREAM_TEST_FILES_DIR =
       Paths.get("third_party/openjdk/jdk-11-test/java/util/stream/test");
   private static Path[] JDK_11_STREAM_TEST_COMPILED_FILES;
@@ -162,8 +160,7 @@
 
   @BeforeClass
   public static void compileJdk11StreamTests() throws Exception {
-    File streamClassesDir = new File(JDK_11_STREAM_TEST_CLASSES_DIR.toString());
-    assert streamClassesDir.exists() || streamClassesDir.mkdirs();
+    JDK_11_STREAM_TEST_CLASSES_DIR = getStaticTemp().newFolder("stream").toPath();
     List<String> options =
         Arrays.asList(
             "--add-reads",
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 5b94aac..8542a76 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -141,8 +141,7 @@
     DexProgramClass sharedSynthesizedClass =
         makeClass(options, "SharedSynthesized", 100, Constants.MAX_NON_JUMBO_INDEX - 1, classes);
 
-    DexApplication.Builder builder =
-        DirectMappedDexApplication.builder(options, new Timing("SharedClassWritingTest"));
+    DexApplication.Builder builder = DirectMappedDexApplication.builder(options, Timing.empty());
     builder.addSynthesizedClass(sharedSynthesizedClass, false);
     classes.forEach(builder::addProgramClass);
     DexApplication application = builder.build();
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
new file mode 100644
index 0000000..332ad2e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -0,0 +1,66 @@
+// 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 static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+
+public class EnumUnboxingTestBase extends TestBase {
+
+  static final String KEEP_ENUM =
+      "-keepclassmembers enum * { public static **[] values(); public static **"
+          + " valueOf(java.lang.String); }";
+
+  public void assertLines2By2Correct(String string) {
+    List<String> lines = StringUtils.splitLines(string);
+    assert lines.size() % 2 == 0;
+    for (int i = 0; i < lines.size(); i += 2) {
+      assertEquals(
+          "Different lines: " + lines.get(i) + " || " + lines.get(i + 1) + "\n" + string,
+          lines.get(i),
+          lines.get(i + 1));
+    }
+  }
+
+  void enableEnumOptions(InternalOptions options) {
+    options.enableEnumUnboxing = true;
+    options.testing.enableEnumUnboxingDebugLogs = true;
+  }
+
+  void assertEnumIsUnboxed(Class<?> enumClass, String testName, TestDiagnosticMessages m) {
+    Diagnostic diagnostic = m.getInfos().get(0);
+    assertTrue(diagnostic.getDiagnosticMessage().startsWith("Unboxed enums"));
+    assertTrue(
+        "Expected enum to be removed (" + testName + "): ",
+        diagnostic.getDiagnosticMessage().contains(enumClass.getSimpleName()));
+  }
+
+  void assertEnumIsBoxed(Class<?> enumClass, String testName, TestDiagnosticMessages m) {
+    Diagnostic diagnostic = m.getInfos().get(1);
+    assertTrue(diagnostic.getDiagnosticMessage().startsWith("Boxed enums"));
+    assertTrue(
+        "Expected enum NOT to be removed (" + testName + "): ",
+        diagnostic.getDiagnosticMessage().contains(enumClass.getSimpleName()));
+  }
+
+  static TestParametersCollection enumUnboxingTestParameters() {
+    return getTestParameters()
+        .withCfRuntime(CfVm.JDK9)
+        .withDexRuntime(DexVm.Version.first())
+        .withDexRuntime(DexVm.Version.last())
+        .withAllApiLevels()
+        .build();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..247e8a1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
@@ -0,0 +1,191 @@
+// 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 static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumInstanceFieldMain.EnumInstanceField;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumInterfaceMain.EnumInterface;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumStaticFieldMain.EnumStaticField;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumStaticMethodMain.EnumStaticMethod;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumVirtualMethodMain.EnumVirtualMethod;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FailingEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+  private static final Class<?>[] FAILURES = {
+    EnumInterface.class,
+    EnumStaticField.class,
+    EnumInstanceField.class,
+    EnumStaticMethod.class,
+    EnumVirtualMethod.class
+  };
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public FailingEnumUnboxingAnalysisTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testEnumUnboxingFailure() throws Exception {
+    R8FullTestBuilder r8FullTestBuilder =
+        testForR8(parameters.getBackend()).addInnerClasses(FailingEnumUnboxingAnalysisTest.class);
+    for (Class<?> failure : FAILURES) {
+      r8FullTestBuilder.addKeepMainRule(failure.getEnclosingClass());
+    }
+    R8TestCompileResult compile =
+        r8FullTestBuilder
+            .noTreeShaking() // Disabled to avoid merging Itf into EnumInterface.
+            .enableInliningAnnotations()
+            .addKeepRules(KEEP_ENUM)
+            .addOptionsModification(this::enableEnumOptions)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(this::assertEnumsAsExpected);
+    for (Class<?> failure : FAILURES) {
+      R8TestRunResult run =
+          compile
+              .inspectDiagnosticMessages(
+                  m -> assertEnumIsBoxed(failure, failure.getSimpleName(), m))
+              .run(parameters.getRuntime(), failure.getEnclosingClass())
+              .assertSuccess();
+      assertLines2By2Correct(run.getStdOut());
+    }
+  }
+
+  private void assertEnumsAsExpected(CodeInspector inspector) {
+    assertEquals(1, inspector.clazz(EnumInterface.class).getDexClass().interfaces.size());
+
+    assertTrue(inspector.clazz(EnumStaticField.class).uniqueFieldWithName("X").isPresent());
+    assertTrue(inspector.clazz(EnumInstanceField.class).uniqueFieldWithName("a").isPresent());
+
+    assertEquals(5, inspector.clazz(EnumStaticMethod.class).getDexClass().directMethods().size());
+    assertEquals(1, inspector.clazz(EnumVirtualMethod.class).virtualMethods().size());
+  }
+
+  static class EnumInterfaceMain {
+
+    public static void main(String[] args) {
+      System.out.println(EnumInterface.A.ordinal());
+      System.out.println(0);
+    }
+
+    enum EnumInterface implements Itf {
+      A,
+      B,
+      C
+    }
+
+    interface Itf {
+
+      default int ordinal() {
+        return -1;
+      }
+    }
+  }
+
+  static class EnumStaticFieldMain {
+
+    public static void main(String[] args) {
+      System.out.println(EnumStaticField.A.ordinal());
+      System.out.println(0);
+      System.out.println(EnumStaticField.X.ordinal());
+      System.out.println(0);
+    }
+
+    enum EnumStaticField {
+      A,
+      B,
+      C;
+      static EnumStaticField X = A;
+    }
+  }
+
+  static class EnumInstanceFieldMain {
+
+    enum EnumInstanceField {
+      A(10),
+      B(20),
+      C(30);
+      private int a;
+
+      EnumInstanceField(int i) {
+        this.a = i;
+      }
+    }
+
+    public static void main(String[] args) {
+      System.out.println(EnumInstanceField.A.ordinal());
+      System.out.println(0);
+      System.out.println(EnumInstanceField.A.a);
+      System.out.println(10);
+    }
+  }
+
+  static class EnumStaticMethodMain {
+
+    enum EnumStaticMethod {
+      A,
+      B,
+      C;
+
+      // Enum cannot be unboxed if it has a static method, we do not inline so the method is
+      // present.
+      @NeverInline
+      static int foo() {
+        return Math.addExact(-1, 0);
+      }
+    }
+
+    public static void main(String[] args) {
+      System.out.println(EnumStaticMethod.A.ordinal());
+      System.out.println(0);
+      System.out.println(EnumStaticMethod.foo());
+      System.out.println(-1);
+    }
+  }
+
+  static class EnumVirtualMethodMain {
+
+    public static void main(String[] args) {
+      EnumVirtualMethod e1 = EnumVirtualMethod.A;
+      System.out.println(e1.ordinal());
+      System.out.println(0);
+      System.out.println(e1.valueOf());
+      System.out.println(-1);
+    }
+
+    enum EnumVirtualMethod {
+      A,
+      B,
+      C;
+
+      // Enum cannot be unboxed if it has a virtual method, we do not inline so the method is
+      // present.
+      @NeverInline
+      int valueOf() {
+        return Math.addExact(-1, 0);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..762380a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
@@ -0,0 +1,241 @@
+// 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 static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.EnumSet;
+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 FailingMethodEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+  private static final Class<?>[] FAILURES = {
+    NullCheck.class,
+    Check.class,
+    InstanceFieldPutObject.class,
+    StaticFieldPutObject.class,
+    ToString.class,
+    EnumSetTest.class,
+    FailingPhi.class
+  };
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public FailingMethodEnumUnboxingAnalysisTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testEnumUnboxingFailure() throws Exception {
+    R8FullTestBuilder r8FullTestBuilder =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(FailingMethodEnumUnboxingAnalysisTest.class);
+    for (Class<?> failure : FAILURES) {
+      r8FullTestBuilder.addKeepMainRule(failure);
+    }
+    R8TestCompileResult compile =
+        r8FullTestBuilder
+            .addKeepRules(KEEP_ENUM)
+            .addOptionsModification(this::enableEnumOptions)
+            .enableInliningAnnotations()
+            .addOptionsModification(
+                // Disabled to avoid toString() being removed.
+                opt -> opt.enableEnumValueOptimization = false)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(this::assertEnumsAsExpected);
+    for (Class<?> failure : FAILURES) {
+      R8TestRunResult run =
+          compile
+              .inspectDiagnosticMessages(
+                  m ->
+                      assertEnumIsBoxed(
+                          failure.getDeclaredClasses()[0], failure.getSimpleName(), m))
+              .run(parameters.getRuntime(), failure)
+              .assertSuccess();
+      assertLines2By2Correct(run.getStdOut());
+    }
+  }
+
+  private void assertEnumsAsExpected(CodeInspector inspector) {
+    // Check all as expected (else we test nothing)
+    assertTrue(inspector.clazz(NullCheck.class).uniqueMethodWithName("nullCheck").isPresent());
+    assertTrue(inspector.clazz(Check.class).uniqueMethodWithName("check").isPresent());
+
+    assertEquals(
+        1, inspector.clazz(InstanceFieldPutObject.class).getDexClass().instanceFields().size());
+    assertEquals(
+        1, inspector.clazz(StaticFieldPutObject.class).getDexClass().staticFields().size());
+
+    assertTrue(inspector.clazz(FailingPhi.class).uniqueMethodWithName("switchOn").isPresent());
+  }
+
+  @SuppressWarnings("ConstantConditions")
+  static class NullCheck {
+
+    enum MyEnum {
+      A,
+      B,
+      C
+    }
+
+    public static void main(String[] args) {
+      System.out.println(nullCheck(MyEnum.A));
+      System.out.println(false);
+      System.out.println(nullCheck(null));
+      System.out.println(true);
+    }
+
+    // Do not resolve the == with constants after inlining.
+    @NeverInline
+    static boolean nullCheck(MyEnum e) {
+      return e == null;
+    }
+  }
+
+  static class Check {
+
+    enum MyEnum {
+      A,
+      B,
+      C
+    }
+
+    public static void main(String[] args) {
+      MyEnum e1 = MyEnum.A;
+      System.out.println(check(e1));
+      System.out.println(false);
+    }
+
+    // Do not resolve the == with constants after inlining.
+    @NeverInline
+    static boolean check(MyEnum e) {
+      return e == MyEnum.B;
+    }
+  }
+
+  static class InstanceFieldPutObject {
+
+    enum MyEnum {
+      A,
+      B,
+      C
+    }
+
+    Object e;
+
+    public static void main(String[] args) {
+      InstanceFieldPutObject fieldPut = new InstanceFieldPutObject();
+      fieldPut.setA();
+      Object obj = new Object();
+      fieldPut.e = obj;
+      System.out.println(fieldPut.e);
+      System.out.println(obj);
+    }
+
+    void setA() {
+      e = MyEnum.A;
+    }
+  }
+
+  static class StaticFieldPutObject {
+
+    enum MyEnum {
+      A,
+      B,
+      C
+    }
+
+    static Object e;
+
+    public static void main(String[] args) {
+      setA();
+      Object obj = new Object();
+      e = obj;
+      System.out.println(e);
+      System.out.println(obj);
+    }
+
+    static void setA() {
+      e = MyEnum.A;
+    }
+  }
+
+  static class ToString {
+
+    enum MyEnum {
+      A,
+      B,
+      C
+    }
+
+    public static void main(String[] args) {
+      MyEnum e1 = MyEnum.A;
+      System.out.println(e1.toString());
+      System.out.println("A");
+    }
+  }
+
+  static class EnumSetTest {
+
+    enum MyEnum {
+      A,
+      B,
+      C
+    }
+
+    public static void main(String[] args) {
+      EnumSet<MyEnum> es = EnumSet.allOf(MyEnum.class);
+      System.out.println(es.size());
+      System.out.println("3");
+    }
+  }
+
+  static class FailingPhi {
+
+    enum MyEnum {
+      A,
+      B,
+      C
+    }
+
+    public static void main(String[] args) {
+      System.out.println(switchOn(1));
+      System.out.println("B");
+      System.out.println(switchOn(2));
+      System.out.println("class java.lang.Object");
+    }
+
+    // Avoid removing the switch entirely.
+    @NeverInline
+    static Object switchOn(int i) {
+      switch (i) {
+        case 0:
+          return MyEnum.A;
+        case 1:
+          return MyEnum.B;
+        default:
+          return Object.class;
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..2841689
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
@@ -0,0 +1,124 @@
+// 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 static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+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 FieldPutEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private static final Class<?>[] INPUTS =
+      new Class<?>[] {InstanceFieldPut.class, StaticFieldPut.class};
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public FieldPutEnumUnboxingAnalysisTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    R8TestCompileResult compile =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(FieldPutEnumUnboxingAnalysisTest.class)
+            .addKeepMainRules(INPUTS)
+            .addKeepRules(KEEP_ENUM)
+            .addOptionsModification(this::enableEnumOptions)
+            .enableInliningAnnotations()
+            .setMinApi(parameters.getApiLevel())
+            .noMinification()
+            .compile()
+            .inspect(
+                i -> {
+                  assertEquals(
+                      1, i.clazz(InstanceFieldPut.class).getDexClass().instanceFields().size());
+                  assertEquals(
+                      1, i.clazz(StaticFieldPut.class).getDexClass().staticFields().size());
+                });
+
+    for (Class<?> input : INPUTS) {
+      R8TestRunResult run =
+          compile
+              .inspectDiagnosticMessages(
+                  m -> assertEnumIsUnboxed(input.getDeclaredClasses()[0], input.getSimpleName(), m))
+              .run(parameters.getRuntime(), input)
+              .assertSuccess();
+      assertLines2By2Correct(run.getStdOut());
+    }
+  }
+
+  static class InstanceFieldPut {
+
+    enum MyEnum {
+      A,
+      B,
+      C
+    }
+
+    MyEnum e;
+
+    public static void main(String[] args) {
+      InstanceFieldPut fieldPut = new InstanceFieldPut();
+      fieldPut.setA();
+      System.out.println(fieldPut.e.ordinal());
+      System.out.println(0);
+      fieldPut.setB();
+      System.out.println(fieldPut.e.ordinal());
+      System.out.println(1);
+    }
+
+    void setA() {
+      e = MyEnum.A;
+    }
+
+    void setB() {
+      e = MyEnum.B;
+    }
+  }
+
+  static class StaticFieldPut {
+
+    enum MyEnum {
+      A,
+      B,
+      C
+    }
+
+    static MyEnum e;
+
+    public static void main(String[] args) {
+      setA();
+      System.out.println(StaticFieldPut.e.ordinal());
+      System.out.println(0);
+      setB();
+      System.out.println(StaticFieldPut.e.ordinal());
+      System.out.println(1);
+    }
+
+    @NeverInline
+    static void setA() {
+      StaticFieldPut.e = MyEnum.A;
+    }
+
+    @NeverInline
+    static void setB() {
+      StaticFieldPut.e = MyEnum.B;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..079c9f7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
@@ -0,0 +1,62 @@
+// 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.R8TestRunResult;
+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 OrdinalEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public OrdinalEnumUnboxingAnalysisTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static final Class<?> ENUM_CLASS = MyEnum.class;
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<Ordinal> classToTest = Ordinal.class;
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(classToTest, ENUM_CLASS)
+            .addKeepMainRule(classToTest)
+            .addKeepRules(KEEP_ENUM)
+            .addOptionsModification(this::enableEnumOptions)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspectDiagnosticMessages(
+                m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
+            .run(parameters.getRuntime(), classToTest)
+            .assertSuccess();
+    assertLines2By2Correct(run.getStdOut());
+  }
+
+  enum MyEnum {
+    A,
+    B,
+    C
+  }
+
+  static class Ordinal {
+
+    public static void main(String[] args) {
+      System.out.println(MyEnum.A.ordinal());
+      System.out.println(0);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..87cacd5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
@@ -0,0 +1,79 @@
+// 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.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+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 PhiEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public PhiEnumUnboxingAnalysisTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static final Class<?> ENUM_CLASS = MyEnum.class;
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<?> classToTest = Phi.class;
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(classToTest, ENUM_CLASS)
+            .addKeepMainRule(classToTest)
+            .addKeepRules(KEEP_ENUM)
+            .enableInliningAnnotations()
+            .addOptionsModification(this::enableEnumOptions)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspectDiagnosticMessages(
+                m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
+            .run(parameters.getRuntime(), classToTest)
+            .assertSuccess();
+    assertLines2By2Correct(run.getStdOut());
+  }
+
+  enum MyEnum {
+    A,
+    B,
+    C
+  }
+
+  static class Phi {
+
+    public static void main(String[] args) {
+      System.out.println(switchOn(1).ordinal());
+      System.out.println(1);
+      System.out.println(switchOn(2).ordinal());
+      System.out.println(2);
+    }
+
+    // Avoid removing the switch entirely.
+    @NeverInline
+    static MyEnum switchOn(int i) {
+      switch (i) {
+        case 0:
+          return MyEnum.A;
+        case 1:
+          return MyEnum.B;
+        default:
+          return MyEnum.C;
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..699c72d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
@@ -0,0 +1,79 @@
+// 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.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+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 SwitchEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public SwitchEnumUnboxingAnalysisTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static final Class<?> ENUM_CLASS = MyEnum.class;
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<Switch> classToTest = Switch.class;
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(SwitchEnumUnboxingAnalysisTest.class)
+            .addKeepMainRule(classToTest)
+            .addKeepRules(KEEP_ENUM)
+            .enableInliningAnnotations()
+            .addOptionsModification(this::enableEnumOptions)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspectDiagnosticMessages(
+                m -> assertEnumIsBoxed(ENUM_CLASS, classToTest.getSimpleName(), m))
+            .run(parameters.getRuntime(), classToTest)
+            .assertSuccess();
+    assertLines2By2Correct(run.getStdOut());
+  }
+
+  enum MyEnum {
+    A,
+    B,
+    C
+  }
+
+  static class Switch {
+
+    public static void main(String[] args) {
+      System.out.println(switchOnEnum(MyEnum.A));
+      System.out.println(0xC0FFEE);
+      System.out.println(switchOnEnum(MyEnum.B));
+      System.out.println(0xBABE);
+    }
+
+    // Avoid removing the switch entirely.
+    @NeverInline
+    static int switchOnEnum(MyEnum e) {
+      switch (e) {
+        case A:
+          return 0xC0FFEE;
+        case B:
+          return 0xBABE;
+        default:
+          return 0xDEADBEEF;
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
index f482d5c..90218e0 100644
--- a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
@@ -26,14 +26,14 @@
     InternalOptions options = new InternalOptions();
     DexApplication application =
         new ApplicationReader(
-            AndroidApp.builder()
-                .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
-                .addLibraryFiles(ToolHelper.getKotlinStdlibJar())
-                .build(),
-            options,
-            new Timing(DexType.class.getName()))
-        .read()
-        .toDirect();
+                AndroidApp.builder()
+                    .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+                    .addLibraryFiles(ToolHelper.getKotlinStdlibJar())
+                    .build(),
+                options,
+                Timing.empty())
+            .read()
+            .toDirect();
     factory = options.itemFactory;
     appInfo = new AppInfoWithSubtyping(application);
   }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index cffd09c..78585b8 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -45,7 +45,7 @@
       proguardMap = StringResource.fromFile(mapFile);
     }
     ExecutorService executorService = Executors.newSingleThreadExecutor();
-    Timing timing = new Timing("ReadGMSCore");
+    Timing timing = Timing.empty();
     program =
         new ApplicationReader(app, new InternalOptions(), timing)
             .read(proguardMap, executorService)
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index eb62734..558664f 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -179,14 +179,11 @@
           usedRootClassSubject.uniqueFieldWithName("recursiveWithRequiredField_"), isPresent());
       assertThat(usedRootClassSubject.uniqueFieldWithName("hasMapField_"), isPresent());
 
-      // TODO(b/112437944): Should be present.
       assertThat(
-          usedRootClassSubject.uniqueFieldWithName("isExtendedWithRequiredField_"),
-          not(isPresent()));
-      // TODO(b/112437944): Should be present.
+          usedRootClassSubject.uniqueFieldWithName("isExtendedWithRequiredField_"), isPresent());
       assertThat(
           usedRootClassSubject.uniqueFieldWithName("isRepeatedlyExtendedWithRequiredField_"),
-          not(isPresent()));
+          isPresent());
 
       ClassSubject hasRequiredFieldClassSubject = outputInspector.clazz(HAS_REQUIRED_FIELD);
       assertThat(hasRequiredFieldClassSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
index 9394934..586129b 100644
--- a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
@@ -54,7 +54,7 @@
     AndroidApp application = buildApplication(builder);
     InternalOptions options = new InternalOptions();
     DexApplication dexApplication =
-        new ApplicationReader(application, options, new Timing("BasicBlockIteratorTest")).read();
+        new ApplicationReader(application, options, Timing.empty()).read();
     AppView<?> appView = AppView.createForD8(new AppInfo(dexApplication), options);
 
     // Build the code, and split the code into three blocks.
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index e20e882..34d7c61 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -17,11 +18,13 @@
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
 import com.android.tools.r8.shaking.ProguardClassFilter;
+import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardKeepRule;
 import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -54,7 +57,7 @@
                 application,
                 ImmutableList.of(ProguardKeepRule.defaultKeepAllRule(unused -> {})))
             .run(executorService));
-    Timing timing = new Timing(getClass().getSimpleName());
+    Timing timing = Timing.empty();
     Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView);
     appView.setAppInfo(
         enqueuer.traceApplication(
@@ -62,6 +65,13 @@
     return new TestApplication(appView, method, additionalCode);
   }
 
+  private static InternalOptions createOptions() {
+    Reporter reporter = new Reporter();
+    ProguardConfiguration proguardConfiguration =
+        ProguardConfiguration.builder(new DexItemFactory(), reporter).build();
+    return new InternalOptions(proguardConfiguration, reporter);
+  }
+
   private TestApplication codeForMethodReplaceTest(int a, int b) throws ExecutionException {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
@@ -108,7 +118,7 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -190,7 +200,7 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -267,7 +277,7 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -400,7 +410,7 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -513,7 +523,7 @@
         "    goto :print_result"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -624,7 +634,7 @@
         "    goto :print_result"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -737,7 +747,7 @@
         "    goto :print_result"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -893,7 +903,7 @@
         "    goto :print_result"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
@@ -1139,7 +1149,7 @@
         "    goto :print_result"
     );
 
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = createOptions();
     DexApplication application = buildApplication(builder, options).toDirect();
 
     // Return the processed method for inspection.
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index fc61691..8d73fad 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -45,7 +45,7 @@
   protected DexApplication buildApplication(AndroidApp input, InternalOptions options) {
     try {
       options.itemFactory.resetSortedIndices();
-      return new ApplicationReader(input, options, new Timing("IrInjectionTest")).read();
+      return new ApplicationReader(input, options, Timing.empty()).read();
     } catch (IOException | ExecutionException e) {
       throw new RuntimeException(e);
     }
@@ -123,7 +123,7 @@
     }
 
     public String run() throws IOException {
-      Timing timing = new Timing(getClass().getSimpleName());
+      Timing timing = Timing.empty();
       IRConverter converter = new IRConverter(appView, timing, null, MainDexClasses.NONE);
       converter.replaceCodeForTesting(method, code);
       AndroidApp app = writeDex(application, appView.options());
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index 4b0d1ed..e53db5e 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -364,9 +364,9 @@
       // Modify the code to make the inserted block add the constant 10 to the original return
       // value.
       Value newConstValue =
-          new Value(test.valueNumberGenerator.next(), TypeLatticeElement.INT, null);
+          new Value(test.valueNumberGenerator.next(), TypeLatticeElement.getInt(), null);
       Value newReturnValue =
-          new Value(test.valueNumberGenerator.next(), TypeLatticeElement.INT, null);
+          new Value(test.valueNumberGenerator.next(), TypeLatticeElement.getInt(), null);
       Value oldReturnValue = newReturnBlock.iterator().next().asReturn().returnValue();
       newReturnBlock.iterator().next().asReturn().returnValue().replaceUsers(newReturnValue);
       Instruction constInstruction = new ConstNumber(newConstValue, 10);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java b/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
index c7e8270..7321e79 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
@@ -65,9 +65,7 @@
   @Before
   public void setup() throws Exception {
     DexApplication application =
-        new ApplicationReader(app, options, new Timing("AnalysisTestBase.appReader"))
-            .read()
-            .toDirect();
+        new ApplicationReader(app, options, Timing.empty()).read().toDirect();
     appView = AppView.createForR8(new AppInfoWithSubtyping(application), options);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index d1e44d0..02395ef 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -118,7 +118,7 @@
         parameters.isCfRuntime()
             ? ClassFileConsumer.emptyConsumer()
             : DexIndexedConsumer.emptyConsumer();
-    Timing timing = new Timing("FieldBitAccessInfoTest");
+    Timing timing = Timing.empty();
     DexApplication application =
         new ApplicationReader(
                 AndroidApp.builder()
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
index e5e0a86..74a49ed 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.ir.analysis.type;
 
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.FLOAT;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.INT;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getFloat;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getInt;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -74,8 +74,8 @@
           ArrayTypeLatticeElement arrayType = array.getTypeLattice().asArrayTypeLatticeElement();
           TypeLatticeElement elementType = arrayType.getArrayMemberTypeAsMemberType();
 
-          assertEquals(FLOAT, elementType);
-          assertEquals(FLOAT, value.getTypeLattice());
+          assertEquals(getFloat(), elementType);
+          assertEquals(getFloat(), value.getTypeLattice());
         }
       }
     };
@@ -94,8 +94,8 @@
         ArrayTypeLatticeElement arrayType = array.getTypeLattice().asArrayTypeLatticeElement();
         TypeLatticeElement elementType = arrayType.getArrayMemberTypeAsMemberType();
 
-        assertEquals(FLOAT, elementType);
-        assertEquals(FLOAT, value.getTypeLattice());
+        assertEquals(getFloat(), elementType);
+        assertEquals(getFloat(), value.getTypeLattice());
       }
 
       {
@@ -104,7 +104,7 @@
                 code,
                 instruction ->
                     instruction.isConstNumber() && instruction.asConstNumber().getRawValue() != 0);
-        assertEquals(FLOAT, constNumberInstruction.outValue().getTypeLattice());
+        assertEquals(getFloat(), constNumberInstruction.outValue().getTypeLattice());
       }
     };
   }
@@ -115,7 +115,7 @@
       for (BasicBlock block : code.blocks) {
         for (Phi phi : block.getPhis()) {
           phiCount++;
-          assertEquals(INT, phi.getTypeLattice());
+          assertEquals(getInt(), phi.getTypeLattice());
         }
       }
       assertEquals(2, phiCount);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
index dd0b5e2..9471d46 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
@@ -4,10 +4,10 @@
 
 package com.android.tools.r8.ir.analysis.type;
 
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.DOUBLE;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.FLOAT;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.INT;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.LONG;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getDouble;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getFloat;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getInt;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getLong;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
@@ -54,42 +54,42 @@
 
   @Test
   public void testIntWithInvokeUser() throws Exception {
-    buildAndCheckIR("intWithInvokeUserTest", testInspector(INT, 1));
+    buildAndCheckIR("intWithInvokeUserTest", testInspector(getInt(), 1));
   }
 
   @Test
   public void testIntWithIndirectInvokeUser() throws Exception {
-    buildAndCheckIR("intWithIndirectInvokeUserTest", testInspector(INT, 2));
+    buildAndCheckIR("intWithIndirectInvokeUserTest", testInspector(getInt(), 2));
   }
 
   @Test
   public void testFloatWithInvokeUser() throws Exception {
-    buildAndCheckIR("floatWithInvokeUserTest", testInspector(FLOAT, 1));
+    buildAndCheckIR("floatWithInvokeUserTest", testInspector(getFloat(), 1));
   }
 
   @Test
   public void testFloatWithIndirectInvokeUser() throws Exception {
-    buildAndCheckIR("floatWithIndirectInvokeUserTest", testInspector(FLOAT, 2));
+    buildAndCheckIR("floatWithIndirectInvokeUserTest", testInspector(getFloat(), 2));
   }
 
   @Test
   public void testLongWithInvokeUser() throws Exception {
-    buildAndCheckIR("longWithInvokeUserTest", testInspector(LONG, 1));
+    buildAndCheckIR("longWithInvokeUserTest", testInspector(getLong(), 1));
   }
 
   @Test
   public void testLongWithIndirectInvokeUser() throws Exception {
-    buildAndCheckIR("longWithIndirectInvokeUserTest", testInspector(LONG, 2));
+    buildAndCheckIR("longWithIndirectInvokeUserTest", testInspector(getLong(), 2));
   }
 
   @Test
   public void testDoubleWithInvokeUser() throws Exception {
-    buildAndCheckIR("doubleWithInvokeUserTest", testInspector(DOUBLE, 1));
+    buildAndCheckIR("doubleWithInvokeUserTest", testInspector(getDouble(), 1));
   }
 
   @Test
   public void testDoubleWithIndirectInvokeUser() throws Exception {
-    buildAndCheckIR("doubleWithIndirectInvokeUserTest", testInspector(DOUBLE, 2));
+    buildAndCheckIR("doubleWithIndirectInvokeUserTest", testInspector(getDouble(), 2));
   }
 
   private static Consumer<IRCode> testInspector(
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
index 91fdee5..198ffa3 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
@@ -58,10 +58,10 @@
 @RunWith(Parameterized.class)
 public class TypeAnalysisTest extends SmaliTestBase {
   private static final InternalOptions TEST_OPTIONS = new InternalOptions();
-  private static final TypeLatticeElement NULL = TypeLatticeElement.NULL;
-  private static final TypeLatticeElement SINGLE = TypeLatticeElement.SINGLE;
-  private static final TypeLatticeElement INT = TypeLatticeElement.INT;
-  private static final TypeLatticeElement LONG = TypeLatticeElement.LONG;
+  private static final TypeLatticeElement NULL = TypeLatticeElement.getNull();
+  private static final TypeLatticeElement SINGLE = TypeLatticeElement.getSingle();
+  private static final TypeLatticeElement INT = TypeLatticeElement.getInt();
+  private static final TypeLatticeElement LONG = TypeLatticeElement.getLong();
 
   private final String dirName;
   private final String smaliFileName;
@@ -113,8 +113,7 @@
     byte[] content = Smali.compile(smaliStringBuilder.toString());
     AndroidApp app = AndroidApp.builder().addDexProgramData(content, Origin.unknown()).build();
     DexApplication dexApplication =
-        new ApplicationReader(app, TEST_OPTIONS, new Timing("TypeAnalysisTest.appReader"))
-            .read().toDirect();
+        new ApplicationReader(app, TEST_OPTIONS, Timing.empty()).read().toDirect();
     inspection.accept(
         AppView.createForD8(new AppInfo(dexApplication), TEST_OPTIONS),
         new CodeInspector(dexApplication));
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java
index ad428dd..80d752e 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java
@@ -4,10 +4,6 @@
 
 package com.android.tools.r8.ir.analysis.type;
 
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.DOUBLE;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.FLOAT;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.INT;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.LONG;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestParameters;
@@ -94,22 +90,24 @@
 
   @Test
   public void testIntConstraintOnTrivialPhi() throws Exception {
-    buildAndCheckIR("intConstraintOnTrivialPhiTest", testInspector(INT));
+    buildAndCheckIR("intConstraintOnTrivialPhiTest", testInspector(TypeLatticeElement.getInt()));
   }
 
   @Test
   public void testFloatConstraintOnTrivialPhi() throws Exception {
-    buildAndCheckIR("floatConstraintOnTrivialPhiTest", testInspector(FLOAT));
+    buildAndCheckIR(
+        "floatConstraintOnTrivialPhiTest", testInspector(TypeLatticeElement.getFloat()));
   }
 
   @Test
   public void testLongConstraintOnTrivialPhi() throws Exception {
-    buildAndCheckIR("longConstraintOnTrivialPhiTest", testInspector(LONG));
+    buildAndCheckIR("longConstraintOnTrivialPhiTest", testInspector(TypeLatticeElement.getLong()));
   }
 
   @Test
   public void testDoubleConstraintOnTrivialPhi() throws Exception {
-    buildAndCheckIR("doubleConstraintOnTrivialPhiTest", testInspector(DOUBLE));
+    buildAndCheckIR(
+        "doubleConstraintOnTrivialPhiTest", testInspector(TypeLatticeElement.getDouble()));
   }
 
   private static Consumer<IRCode> testInspector(TypeLatticeElement expectedType) {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElementWidthTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElementWidthTest.java
index d6b9598..f2a20a6 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElementWidthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElementWidthTest.java
@@ -18,8 +18,7 @@
   @Test
   public void testArrayWidth() {
     ArrayTypeLatticeElement arrayType =
-        ArrayTypeLatticeElement.create(
-            IntTypeLatticeElement.getInstance(), Nullability.maybeNull());
+        ArrayTypeLatticeElement.create(TypeLatticeElement.getInt(), Nullability.maybeNull());
     assertFalse(arrayType.isSinglePrimitive());
     assertFalse(arrayType.isWidePrimitive());
     assertEquals(1, arrayType.requiredRegisters());
@@ -27,51 +26,51 @@
 
   @Test
   public void testBooleanWidth() {
-    assertTrue(BooleanTypeLatticeElement.getInstance().isSinglePrimitive());
-    assertFalse(BooleanTypeLatticeElement.getInstance().isWidePrimitive());
-    assertEquals(1, BooleanTypeLatticeElement.getInstance().requiredRegisters());
+    assertTrue(TypeLatticeElement.getBoolean().isSinglePrimitive());
+    assertFalse(TypeLatticeElement.getBoolean().isWidePrimitive());
+    assertEquals(1, TypeLatticeElement.getBoolean().requiredRegisters());
   }
 
   @Test
   public void testByteWidth() {
-    assertTrue(ByteTypeLatticeElement.getInstance().isSinglePrimitive());
-    assertFalse(ByteTypeLatticeElement.getInstance().isWidePrimitive());
-    assertEquals(1, ByteTypeLatticeElement.getInstance().requiredRegisters());
+    assertTrue(TypeLatticeElement.getByte().isSinglePrimitive());
+    assertFalse(TypeLatticeElement.getByte().isWidePrimitive());
+    assertEquals(1, TypeLatticeElement.getByte().requiredRegisters());
   }
 
   @Test
   public void testCharWidth() {
-    assertTrue(CharTypeLatticeElement.getInstance().isSinglePrimitive());
-    assertFalse(CharTypeLatticeElement.getInstance().isWidePrimitive());
-    assertEquals(1, CharTypeLatticeElement.getInstance().requiredRegisters());
+    assertTrue(TypeLatticeElement.getChar().isSinglePrimitive());
+    assertFalse(TypeLatticeElement.getChar().isWidePrimitive());
+    assertEquals(1, TypeLatticeElement.getChar().requiredRegisters());
   }
 
   @Test
   public void testDoubleWidth() {
-    assertTrue(DoubleTypeLatticeElement.getInstance().isWidePrimitive());
-    assertFalse(DoubleTypeLatticeElement.getInstance().isSinglePrimitive());
-    assertEquals(2, DoubleTypeLatticeElement.getInstance().requiredRegisters());
+    assertTrue(TypeLatticeElement.getDouble().isWidePrimitive());
+    assertFalse(TypeLatticeElement.getDouble().isSinglePrimitive());
+    assertEquals(2, TypeLatticeElement.getDouble().requiredRegisters());
   }
 
   @Test
   public void testFloatWidth() {
-    assertTrue(FloatTypeLatticeElement.getInstance().isSinglePrimitive());
-    assertFalse(FloatTypeLatticeElement.getInstance().isWidePrimitive());
-    assertEquals(1, FloatTypeLatticeElement.getInstance().requiredRegisters());
+    assertTrue(TypeLatticeElement.getFloat().isSinglePrimitive());
+    assertFalse(TypeLatticeElement.getFloat().isWidePrimitive());
+    assertEquals(1, TypeLatticeElement.getFloat().requiredRegisters());
   }
 
   @Test
   public void testIntWidth() {
-    assertTrue(IntTypeLatticeElement.getInstance().isSinglePrimitive());
-    assertFalse(IntTypeLatticeElement.getInstance().isWidePrimitive());
-    assertEquals(1, IntTypeLatticeElement.getInstance().requiredRegisters());
+    assertTrue(TypeLatticeElement.getInt().isSinglePrimitive());
+    assertFalse(TypeLatticeElement.getInt().isWidePrimitive());
+    assertEquals(1, TypeLatticeElement.getInt().requiredRegisters());
   }
 
   @Test
   public void testLongWidth() {
-    assertTrue(LongTypeLatticeElement.getInstance().isWidePrimitive());
-    assertFalse(LongTypeLatticeElement.getInstance().isSinglePrimitive());
-    assertEquals(2, LongTypeLatticeElement.getInstance().requiredRegisters());
+    assertTrue(TypeLatticeElement.getLong().isWidePrimitive());
+    assertFalse(TypeLatticeElement.getLong().isSinglePrimitive());
+    assertEquals(2, TypeLatticeElement.getLong().requiredRegisters());
   }
 
   @Test
@@ -87,8 +86,8 @@
 
   @Test
   public void testShortWidth() {
-    assertTrue(ShortTypeLatticeElement.getInstance().isSinglePrimitive());
-    assertFalse(ShortTypeLatticeElement.getInstance().isWidePrimitive());
-    assertEquals(1, ShortTypeLatticeElement.getInstance().requiredRegisters());
+    assertTrue(TypeLatticeElement.getShort().isSinglePrimitive());
+    assertFalse(TypeLatticeElement.getShort().isWidePrimitive());
+    assertEquals(1, TypeLatticeElement.getShort().requiredRegisters());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 3c47534..e0465b3 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -4,9 +4,9 @@
 package com.android.tools.r8.ir.analysis.type;
 
 import static com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement.computeLeastUpperBoundOfInterfaces;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.BOTTOM;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.TOP;
 import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.fromDexType;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getBottom;
+import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getTop;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -56,7 +56,7 @@
                     .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
                     .build(),
                 options,
-                new Timing(TypeLatticeTest.class.getName()))
+                Timing.empty())
             .read()
             .toDirect();
     factory = options.itemFactory;
@@ -64,19 +64,19 @@
   }
 
   private TopTypeLatticeElement top() {
-    return TypeLatticeElement.TOP;
+    return getTop();
   }
 
   private BottomTypeLatticeElement bottom() {
-    return TypeLatticeElement.BOTTOM;
+    return getBottom();
   }
 
   private SinglePrimitiveTypeLatticeElement single() {
-    return TypeLatticeElement.SINGLE;
+    return TypeLatticeElement.getSingle();
   }
 
   private WidePrimitiveTypeLatticeElement wide() {
-    return TypeLatticeElement.WIDE;
+    return TypeLatticeElement.getWide();
   }
 
   private TypeLatticeElement element(DexType type) {
@@ -510,9 +510,7 @@
     assertTrue(strictlyLessThan(
         array(2, factory.objectType),
         array(1, factory.objectType)));
-    assertTrue(strictlyLessThan(
-        ReferenceTypeLatticeElement.getNullTypeLatticeElement(),
-        array(1, factory.classType)));
+    assertTrue(strictlyLessThan(TypeLatticeElement.getNull(), array(1, factory.classType)));
   }
 
   @Test
@@ -527,10 +525,10 @@
             element(factory.objectType, Nullability.maybeNull())));
     assertFalse(
         lessThanOrEqualUpToNullability(array(3, factory.stringType), array(4, factory.stringType)));
-    assertTrue(lessThanOrEqualUpToNullability(BOTTOM, element(factory.objectType)));
-    assertFalse(lessThanOrEqualUpToNullability(element(factory.objectType), BOTTOM));
-    assertFalse(lessThanOrEqualUpToNullability(TOP, element(factory.objectType)));
-    assertTrue(lessThanOrEqualUpToNullability(element(factory.objectType), TOP));
+    assertTrue(lessThanOrEqualUpToNullability(getBottom(), element(factory.objectType)));
+    assertFalse(lessThanOrEqualUpToNullability(element(factory.objectType), getBottom()));
+    assertFalse(lessThanOrEqualUpToNullability(getTop(), element(factory.objectType)));
+    assertTrue(lessThanOrEqualUpToNullability(element(factory.objectType), getTop()));
   }
 
   @Test
@@ -545,10 +543,10 @@
     assertFalse(lessThanOrEqual(nullableType, nonNullType));
 
     // Check that the class-type null is also more specific than nullableType.
-    assertTrue(strictlyLessThan(TypeLatticeElement.NULL, nullableType));
+    assertTrue(strictlyLessThan(TypeLatticeElement.getNull(), nullableType));
     assertTrue(
         strictlyLessThan(
-            TypeLatticeElement.NULL,
+            TypeLatticeElement.getNull(),
             nullableType.getOrCreateVariant(Nullability.definitelyNull())));
   }
 
@@ -556,7 +554,7 @@
   public void testNotNullOfNullGivesBottom() {
     assertEquals(
         Nullability.bottom(),
-        ReferenceTypeLatticeElement.NULL.asMeetWithNotNull().nullability());
+        ReferenceTypeLatticeElement.getNull().asMeetWithNotNull().nullability());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java
index 09c27ea..d08b323 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.analysis.type;
 
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.INT;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.LONG;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestParameters;
@@ -88,27 +86,32 @@
 
   @Test
   public void testUnconstrainedSingleWithNoUsers() throws Exception {
-    buildAndCheckIR("unconstrainedSingleWithNoUsersTest", testInspector(INT, 1));
+    buildAndCheckIR(
+        "unconstrainedSingleWithNoUsersTest", testInspector(TypeLatticeElement.getInt(), 1));
   }
 
   @Test
   public void testUnconstrainedSingleWithIfUser() throws Exception {
-    buildAndCheckIR("unconstrainedSingleWithIfUserTest", testInspector(INT, 2));
+    buildAndCheckIR(
+        "unconstrainedSingleWithIfUserTest", testInspector(TypeLatticeElement.getInt(), 2));
   }
 
   @Test
   public void testUnconstrainedSingleWithIfZeroUser() throws Exception {
-    buildAndCheckIR("unconstrainedSingleWithIfZeroUserTest", testInspector(INT, 1));
+    buildAndCheckIR(
+        "unconstrainedSingleWithIfZeroUserTest", testInspector(IntTypeLatticeElement.getInt(), 1));
   }
 
   @Test
   public void testUnconstrainedWideWithNoUsers() throws Exception {
-    buildAndCheckIR("unconstrainedWideWithNoUsersTest", testInspector(LONG, 1));
+    buildAndCheckIR(
+        "unconstrainedWideWithNoUsersTest", testInspector(TypeLatticeElement.getLong(), 1));
   }
 
   @Test
   public void testUnconstrainedWideWithIfUser() throws Exception {
-    buildAndCheckIR("unconstrainedWideWithIfUserTest", testInspector(LONG, 2));
+    buildAndCheckIR(
+        "unconstrainedWideWithIfUserTest", testInspector(TypeLatticeElement.getLong(), 2));
   }
 
   private static Consumer<IRCode> testInspector(
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
index 8b027e4..6b18b6b 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
@@ -40,7 +40,7 @@
   private final ExecutorService executorService = ThreadUtils.getExecutorService(options);
 
   public PartialCallGraphTest() throws Exception {
-    Timing timing = new Timing("PartialCallGraphTest.setup");
+    Timing timing = Timing.empty();
     AndroidApp app = testForD8().addProgramClasses(TestClass.class).compile().app;
     DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
     AppView<AppInfoWithSubtyping> appView =
@@ -66,8 +66,7 @@
 
   @Test
   public void testFullGraph() throws Exception {
-    CallGraph cg =
-        new CallGraphBuilder(appView).build(executorService, new Timing("testFullGraph"));
+    CallGraph cg = new CallGraphBuilder(appView).build(executorService, Timing.empty());
     Node m1 = findNode(cg.nodes, "m1");
     Node m2 = findNode(cg.nodes, "m2");
     Node m3 = findNode(cg.nodes, "m3");
@@ -115,7 +114,7 @@
 
     CallGraph pg =
         new PartialCallGraphBuilder(appView, ImmutableSet.of(em1, em2, em4, em5))
-            .build(executorService, new Timing("tetPartialGraph"));
+            .build(executorService, Timing.empty());
 
     Node m1 = findNode(pg.nodes, "m1");
     Node m2 = findNode(pg.nodes, "m2");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 2d626a2..607839e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -76,14 +76,14 @@
     IRMetadata metadata = IRMetadata.unknown();
     Position position = Position.testingPosition();
 
-    Value v3 = new Value(3, TypeLatticeElement.LONG, null);
+    Value v3 = new Value(3, TypeLatticeElement.getLong(), null);
     v3.setNeedsRegister(true);
     new MockLiveIntervals(v3);
     Instruction instruction = new ConstNumber(v3, 0);
     instruction.setPosition(position);
     block.add(instruction, metadata);
 
-    Value v0 = new Value(0, TypeLatticeElement.LONG, null);
+    Value v0 = new Value(0, TypeLatticeElement.getLong(), null);
     v0.setNeedsRegister(true);
     new MockLiveIntervals(v0);
     instruction = new ConstNumber(v0, 10);
@@ -94,14 +94,14 @@
     instruction.setPosition(position);
     block.add(instruction, metadata);
 
-    Value v2 = new Value(2, TypeLatticeElement.INT, null);
+    Value v2 = new Value(2, TypeLatticeElement.getInt(), null);
     v2.setNeedsRegister(true);
     new MockLiveIntervals(v2);
     instruction = new ConstNumber(v2, 10);
     instruction.setPosition(position);
     block.add(instruction, metadata);
 
-    Value v1 = new Value(1, TypeLatticeElement.INT, null);
+    Value v1 = new Value(1, TypeLatticeElement.getInt(), null);
     v1.setNeedsRegister(true);
     new MockLiveIntervals(v1);
     instruction = new Move(v1 ,v2);
@@ -112,7 +112,7 @@
     instruction.setPosition(position);
     block.add(instruction, metadata);
 
-    Value v0_2 = new Value(0, TypeLatticeElement.LONG, null);
+    Value v0_2 = new Value(0, TypeLatticeElement.getLong(), null);
     v0_2.setNeedsRegister(true);
     new MockLiveIntervals(v0_2);
     instruction = new ConstNumber(v0_2, 10);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
index ee19ffd..a24de2f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
@@ -29,11 +29,9 @@
     InternalOptions options = new InternalOptions();
     DexApplication application =
         new ApplicationReader(
-                AndroidApp.builder()
-                    .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
-                    .build(),
+                AndroidApp.builder().addLibraryFiles(ToolHelper.getDefaultAndroidJar()).build(),
                 options,
-                new Timing(ConstraintWithTargetTest.class.getName()))
+                Timing.empty())
             .read()
             .toDirect();
     factory = options.itemFactory;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
index 26535f1..838a681 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
@@ -17,7 +17,7 @@
 public abstract class NonNullTrackerTestBase extends TestBase {
 
   protected AppView<?> build(Class<?> mainClass) throws Exception {
-    Timing timing = new Timing(getClass().getSimpleName());
+    Timing timing = Timing.empty();
     AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(mainClass));
     InternalOptions options = new InternalOptions();
     DexApplication dexApplication = new ApplicationReader(app, options, timing).read().toDirect();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index fdb982f..5d48fa6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -59,7 +59,7 @@
     block2.setFilledForTesting();
     BasicBlock block1 = new BasicBlock();
     block1.setNumber(1);
-    Value value = new Value(0, TypeLatticeElement.INT, null);
+    Value value = new Value(0, TypeLatticeElement.getInt(), null);
     Instruction number = new ConstNumber(value, 0);
     number.setPosition(position);
     block1.add(number, metadata);
@@ -94,7 +94,7 @@
   @Test
   public void trivialGotoLoopAsFallthrough() {
     InternalOptions options = new InternalOptions();
-    DexApplication app = DexApplication.builder(new InternalOptions(), new Timing("")).build();
+    DexApplication app = DexApplication.builder(new InternalOptions(), Timing.empty()).build();
     AppView<AppInfo> appView = AppView.createForD8(new AppInfo(app), options);
     // Setup block structure:
     // block0:
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StaticFinalLibraryFieldCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StaticFinalLibraryFieldCanonicalizationTest.java
new file mode 100644
index 0000000..7da9e81
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StaticFinalLibraryFieldCanonicalizationTest.java
@@ -0,0 +1,80 @@
+// 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.canonicalization;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableSet;
+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 StaticFinalLibraryFieldCanonicalizationTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean systemHasClassInitializationSideEffects;
+
+  @Parameters(name = "{0}, System has class initialization side effects: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public StaticFinalLibraryFieldCanonicalizationTest(
+      TestParameters parameters, boolean systemHasClassInitializationSideEffects) {
+    this.systemHasClassInitializationSideEffects = systemHasClassInitializationSideEffects;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> {
+              if (!systemHasClassInitializationSideEffects) {
+                options.itemFactory.libraryClassesWithoutStaticInitialization =
+                    ImmutableSet.of(options.itemFactory.createType("Ljava/lang/System;"));
+              }
+            })
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+
+    MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertEquals(
+        systemHasClassInitializationSideEffects || parameters.isCfRuntime() ? 2 : 1,
+        mainMethodSubject.streamInstructions().filter(InstructionSubject::isStaticGet).count());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.print("Hello");
+      System.out.println(" world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/NoLongerSyntheticAfterVerticalClassMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/NoLongerSyntheticAfterVerticalClassMergingTest.java
new file mode 100644
index 0000000..6fe9d09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/NoLongerSyntheticAfterVerticalClassMergingTest.java
@@ -0,0 +1,87 @@
+// 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.classmerger.vertical;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+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 com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NoLongerSyntheticAfterVerticalClassMergingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public NoLongerSyntheticAfterVerticalClassMergingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, A.class)
+        .addProgramClassFileData(
+            transformer(B.class).setSynthetic(B.class.getDeclaredMethod("m")).transform())
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("B.m()");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(A.class), not(isPresent()));
+
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isPresent());
+
+    MethodSubject mMethodSubject = bClassSubject.uniqueMethodWithName("m");
+    assertThat(mMethodSubject, isPresent());
+    assertFalse(mMethodSubject.getMethod().accessFlags.isSynthetic());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A a = new B();
+      a.m();
+    }
+  }
+
+  abstract static class A {
+
+    public abstract void m();
+  }
+
+  @NeverClassInline
+  static class B extends A {
+
+    @NeverInline
+    @Override
+    public void m() {
+      System.out.println("B.m()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java
new file mode 100644
index 0000000..c90173e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.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.ir.optimize.lambda;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LambdaMethodInliningTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public LambdaMethodInliningTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(LambdaMethodInliningTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .addOptionsModification(options -> options.enableClassInlining = false)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello", "Hello");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    assertThat(classSubject, isPresent());
+
+    MethodSubject testClassMethodSubject = classSubject.uniqueMethodWithName("testClass");
+    assertThat(testClassMethodSubject, isPresent());
+    assertTrue(
+        testClassMethodSubject
+            .streamInstructions()
+            .noneMatch(InstructionSubject::isInvokeInterface));
+    assertTrue(
+        testClassMethodSubject
+            .streamInstructions()
+            .anyMatch(
+                instruction ->
+                    instruction.isInvokeVirtual()
+                        && instruction.getMethod().toSourceString().contains("println")));
+
+    MethodSubject testLambdaMethodSubject = classSubject.uniqueMethodWithName("testLambda");
+    assertThat(testLambdaMethodSubject, isPresent());
+    assertTrue(
+        testLambdaMethodSubject
+            .streamInstructions()
+            .noneMatch(InstructionSubject::isInvokeInterface));
+    assertTrue(
+        testLambdaMethodSubject
+            .streamInstructions()
+            .anyMatch(
+                instruction ->
+                    instruction.isInvokeVirtual()
+                        && instruction.getMethod().toSourceString().contains("println")));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      testClass(new A());
+      testLambda(
+          () -> {
+            System.out.print("H");
+            System.out.print("e");
+            System.out.print("l");
+            System.out.print("l");
+            System.out.println("o");
+          });
+    }
+
+    @NeverInline
+    static void testClass(ImplementedByClass obj) {
+      obj.m();
+    }
+
+    @NeverInline
+    static void testLambda(ImplementedByLambda obj) {
+      obj.m();
+    }
+  }
+
+  @NeverMerge
+  interface ImplementedByClass {
+
+    void m();
+  }
+
+  interface ImplementedByLambda {
+
+    void m();
+  }
+
+  static class A implements ImplementedByClass {
+
+    @Override
+    public void m() {
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.println("o");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/logging/AndroidLogRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/logging/AndroidLogRemovalTest.java
new file mode 100644
index 0000000..1314274
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/logging/AndroidLogRemovalTest.java
@@ -0,0 +1,195 @@
+// 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.logging;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+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 AndroidLogRemovalTest extends TestBase {
+
+  private final int maxRemovedAndroidLogLevel;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{1}, log level: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(1, 2, 3, 4, 5, 6, 7),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public AndroidLogRemovalTest(int maxRemovedAndroidLogLevel, TestParameters parameters) {
+    this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path libraryFile =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(
+                transformer(Log.class).setClassDescriptor("Landroid/util/Log;").transform())
+            .addKeepAllClassesRule()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .writeToZip();
+
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(
+            transformer(TestClass.class)
+                .transformMethodInsnInMethod(
+                    "main",
+                    (opcode, owner, name, descriptor, isInterface, continuation) ->
+                        continuation.apply(
+                            opcode,
+                            owner.endsWith("$Log") ? "android/util/Log" : owner,
+                            name,
+                            descriptor,
+                            isInterface))
+                .transform())
+        .addLibraryFiles(libraryFile, runtimeJar(parameters.getBackend()))
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-maximumremovedandroidloglevel " + maxRemovedAndroidLogLevel)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathFiles(libraryFile)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(getExpectedOutputForMaxLogLevel());
+  }
+
+  private String getExpectedOutputForMaxLogLevel() {
+    switch (maxRemovedAndroidLogLevel) {
+      case 1:
+        return StringUtils.join(
+            "",
+            StringUtils.times(StringUtils.lines("[R8] VERBOSE."), 2),
+            StringUtils.times(StringUtils.lines("[R8] DEBUG."), 2),
+            StringUtils.times(StringUtils.lines("[R8] INFO."), 2),
+            StringUtils.times(StringUtils.lines("[R8] WARN."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ERROR."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+      case 2:
+        return StringUtils.join(
+            "",
+            StringUtils.times(StringUtils.lines("[R8] DEBUG."), 2),
+            StringUtils.times(StringUtils.lines("[R8] INFO."), 2),
+            StringUtils.times(StringUtils.lines("[R8] WARN."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ERROR."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+      case 3:
+        return StringUtils.join(
+            "",
+            StringUtils.times(StringUtils.lines("[R8] INFO."), 2),
+            StringUtils.times(StringUtils.lines("[R8] WARN."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ERROR."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+      case 4:
+        return StringUtils.join(
+            "",
+            StringUtils.times(StringUtils.lines("[R8] WARN."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ERROR."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+      case 5:
+        return StringUtils.join(
+            "",
+            StringUtils.times(StringUtils.lines("[R8] ERROR."), 2),
+            StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+      case 6:
+        return StringUtils.join("", StringUtils.times(StringUtils.lines("[R8] ASSERT."), 2));
+      case 7:
+        return "";
+      default:
+        throw new Unreachable();
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Log.v("R8", "VERBOSE.");
+      if (Log.isLoggable("R8", Log.VERBOSE)) {
+        System.out.println("[R8] VERBOSE.");
+      }
+
+      Log.d("R8", "DEBUG.");
+      if (Log.isLoggable("R8", Log.DEBUG)) {
+        System.out.println("[R8] DEBUG.");
+      }
+
+      Log.i("R8", "INFO.");
+      if (Log.isLoggable("R8", Log.INFO)) {
+        System.out.println("[R8] INFO.");
+      }
+
+      Log.w("R8", "WARN.");
+      if (Log.isLoggable("R8", Log.WARN)) {
+        System.out.println("[R8] WARN.");
+      }
+
+      Log.e("R8", "ERROR.");
+      if (Log.isLoggable("R8", Log.ERROR)) {
+        System.out.println("[R8] ERROR.");
+      }
+
+      Log.wtf("R8", "ASSERT.");
+      if (Log.isLoggable("R8", Log.ASSERT)) {
+        System.out.println("[R8] ASSERT.");
+      }
+    }
+  }
+
+  public static class Log {
+
+    public static final int VERBOSE = 2;
+    public static final int DEBUG = 3;
+    public static final int INFO = 4;
+    public static final int WARN = 5;
+    public static final int ERROR = 6;
+    public static final int ASSERT = 7;
+
+    public static boolean isLoggable(String tag, int level) {
+      return true;
+    }
+
+    public static int v(String tag, String message) {
+      System.out.println("[" + tag + "] " + message);
+      return 42;
+    }
+
+    public static int d(String tag, String message) {
+      System.out.println("[" + tag + "] " + message);
+      return 42;
+    }
+
+    public static int i(String tag, String message) {
+      System.out.println("[" + tag + "] " + message);
+      return 42;
+    }
+
+    public static int w(String tag, String message) {
+      System.out.println("[" + tag + "] " + message);
+      return 42;
+    }
+
+    public static int e(String tag, String message) {
+      System.out.println("[" + tag + "] " + message);
+      return 42;
+    }
+
+    public static int wtf(String tag, String message) {
+      System.out.println("[" + tag + "] " + message);
+      return 42;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
index 32d6962..e1e6b4b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.ir.optimize.membervaluepropagation;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -51,8 +51,7 @@
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
-        // TODO(b/147835946): Should be "Hello world!".
-        .assertSuccessWithOutput("");
+        .assertSuccessWithOutputLines("Hello world!");
   }
 
   private void inspect(CodeInspector inspector) {
@@ -64,7 +63,7 @@
 
     FieldSubject alwaysTrueNoSideEffectsFieldSubject =
         configClassSubject.uniqueFieldWithName("alwaysTrueNoSideEffects");
-    assertThat(alwaysTrueNoSideEffectsFieldSubject, isPresent());
+    assertThat(alwaysTrueNoSideEffectsFieldSubject, not(isPresent()));
 
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
@@ -79,13 +78,6 @@
             .map(InstructionSubject::getField)
             .filter(alwaysTrueFieldSubject.getField().field::equals)
             .count());
-    // TODO(b/147835946): Should be true.
-    assertFalse(
-        mainMethodSubject
-            .streamInstructions()
-            .filter(InstructionSubject::isInstanceGet)
-            .map(InstructionSubject::getField)
-            .noneMatch(alwaysTrueNoSideEffectsFieldSubject.getField().field::equals));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/ConstructorWithExceptionalControlFlowTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/ConstructorWithExceptionalControlFlowTest.java
new file mode 100644
index 0000000..e662d29
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/ConstructorWithExceptionalControlFlowTest.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.membervaluepropagation;
+
+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 ConstructorWithExceptionalControlFlowTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ConstructorWithExceptionalControlFlowTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ConstructorWithExceptionalControlFlowTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("false");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new A().alwaysFalse);
+    }
+  }
+
+  static class A {
+
+    boolean alwaysFalse;
+
+    A() {
+      try {
+        throwRuntimeException();
+      } catch (Exception e) {
+        return;
+      }
+      alwaysFalse = true;
+    }
+
+    @NeverInline
+    static void throwRuntimeException() {
+      if (System.currentTimeMillis() >= 0) {
+        throw new RuntimeException();
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.java
new file mode 100644
index 0000000..df33e2b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.java
@@ -0,0 +1,79 @@
+// 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.membervaluepropagation;
+
+import static org.junit.Assert.assertTrue;
+
+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.graph.DexEncodedMethod;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(options -> options.testing.waveModifier = this::waveModifier)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("42");
+  }
+
+  private void waveModifier(Deque<Collection<DexEncodedMethod>> waves) {
+    Collection<DexEncodedMethod> initialWave = waves.getFirst();
+    Optional<DexEncodedMethod> printFieldMethod =
+        initialWave.stream()
+            .filter(method -> method.method.name.toSourceString().equals("printField"))
+            .findFirst();
+    assertTrue(printFieldMethod.isPresent());
+    initialWave.remove(printFieldMethod.get());
+
+    ArrayList<DexEncodedMethod> lastWave = new ArrayList<>();
+    lastWave.add(printFieldMethod.get());
+    waves.addLast(lastWave);
+  }
+
+  static class TestClass {
+
+    static int field = 42;
+
+    static {
+      printField();
+      field = 0;
+    }
+
+    @NeverInline
+    static void printField() {
+      System.out.println(field); // Should print 42.
+    }
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithDefaultValueAssignmentPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithDefaultValueAssignmentPropagationTest.java
index 0f714ef..8710a6a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithDefaultValueAssignmentPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FinalFieldWithDefaultValueAssignmentPropagationTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.membervaluepropagation;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertFalse;
 
@@ -51,8 +52,7 @@
   private void inspect(CodeInspector inspector) {
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
-    // TODO(b/147799637): Should be absent.
-    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
 
     ClassSubject configClassSubject = inspector.clazz(Config.class);
     assertThat(configClassSubject, isPresent());
@@ -67,7 +67,8 @@
   static class TestClass {
 
     public static void main(String[] args) {
-      if (new Config().alwaysFalse) {
+      Config nullableConfig = System.currentTimeMillis() >= 0 ? new Config() : null;
+      if (nullableConfig.alwaysFalse) {
         dead();
       }
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java
index 2a37168..f68a131 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonFinalFieldWithDefaultValueAssignmentPropagationTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.membervaluepropagation;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertFalse;
 
@@ -53,8 +54,7 @@
   private void inspect(CodeInspector inspector) {
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
-    // TODO(b/147799637): Should be absent.
-    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
 
     ClassSubject configClassSubject = inspector.clazz(Config.class);
     assertThat(configClassSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldAssignedOnlyInInstanceConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldAssignedOnlyInInstanceConstructorTest.java
new file mode 100644
index 0000000..6b69ae8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldAssignedOnlyInInstanceConstructorTest.java
@@ -0,0 +1,53 @@
+// 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.membervaluepropagation;
+
+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 StaticFieldAssignedOnlyInInstanceConstructorTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public StaticFieldAssignedOnlyInInstanceConstructorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(StaticFieldAssignedOnlyInInstanceConstructorTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("0");
+  }
+
+  static class TestClass {
+
+    static int FIELD;
+
+    public static void main(String[] args) {
+      System.out.println(FIELD); // Should print 0.
+      new TestClass();
+    }
+
+    public TestClass() {
+      FIELD = 42;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/NestedStringBuilderTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/NestedStringBuilderTest.java
index 4c4951d..38ddc76 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/NestedStringBuilderTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/NestedStringBuilderTest.java
@@ -4,34 +4,19 @@
 package com.android.tools.r8.ir.optimize.string;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.ForceInline;
-import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.Streams;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
-class NestedStringBuilders {
-
-  public static void main(String... args) {
-    System.out.println(concat("one", args[0]) + "two");
-  }
-
-  @ForceInline
-  public static String concat(String one, String two) {
-    return one + two;
-  }
-}
-
 @RunWith(Parameterized.class)
 public class NestedStringBuilderTest extends TestBase {
   private static final Class<?> MAIN = NestedStringBuilders.class;
@@ -39,7 +24,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -48,29 +33,37 @@
     this.parameters = parameters;
   }
 
-  private void test(TestCompileResult result) throws Exception {
-    result
-        .run(parameters.getRuntime(), MAIN.getTypeName(), "$")
-        .assertSuccessWithOutput(EXPECTED);
-    CodeInspector codeInspector = result.inspector();
-    ClassSubject mainClass = codeInspector.clazz(MAIN);
-    MethodSubject main = mainClass.mainMethod();
-    long count = Streams.stream(main.iterateInstructions(instructionSubject ->
-        instructionSubject.isNewInstance(StringBuilder.class.getTypeName()))).count();
-    // TODO(b/113859361): should be 1 after merging StringBuilder's
-    assertEquals(2, count);
-  }
-
   @Test
   public void b113859361() throws Exception {
-    R8TestCompileResult result =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(MAIN)
-            .enableForceInliningAnnotations()
-            .addKeepMainRule(MAIN)
-            .setMinApi(parameters.getRuntime())
-            .compile();
-    test(result);
+    assumeTrue("CF does not rewrite move results.", parameters.isDexRuntime());
+
+    testForR8(parameters.getBackend())
+        .addProgramClasses(MAIN)
+        .enableForceInliningAnnotations()
+        .addKeepMainRule(MAIN)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN.getTypeName(), "$")
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(codeInspector -> {
+          ClassSubject mainClass = codeInspector.clazz(MAIN);
+          MethodSubject main = mainClass.mainMethod();
+          assertEquals(
+              // TODO(b/113859361): should be 1 after merging StringBuilder's
+              2,
+              main.streamInstructions().filter(
+                  i -> i.isNewInstance(StringBuilder.class.getTypeName())).count());
+          });
   }
 
+  static class NestedStringBuilders {
+
+    public static void main(String... args) {
+      System.out.println(concat("one", args[0]) + "two");
+    }
+
+    @ForceInline
+    public static String concat(String one, String two) {
+      return one + two;
+    }
+  }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
index e40b4e0..761856f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
@@ -4,6 +4,8 @@
 package com.android.tools.r8.ir.optimize.string;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -14,7 +16,9 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer.BuilderState;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.Consumer;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -152,26 +156,104 @@
           for (Value builder : optimizer.analysis.builderStates.keySet()) {
             Map<Instruction, BuilderState> perBuilderState =
                 optimizer.analysis.builderStates.get(builder);
-            checkBuilderState(optimizer, perBuilderState, null, true);
+            checkBuilderState(optimizer, perBuilderState, "Hello,R8", true);
           }
-          assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
+          assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
+          // TODO(b/113859361): check # of merged builders.
+          // assertEquals(2, optimizer.analysis.mergedBuilders.size());
         }));
   }
 
+  @Ignore("TODO(b/113859361): merge builder.")
   @Test
   public void testNestedBuilders_appendBuilderResult() throws Exception {
     buildAndCheckIR(
         "nestedBuilders_appendBuilderResult",
         checkOptimizerStates(appView, optimizer -> {
+          assertEquals(1, optimizer.analysis.builderStates.size());
+          for (Value builder : optimizer.analysis.builderStates.keySet()) {
+            Map<Instruction, BuilderState> perBuilderState =
+                optimizer.analysis.builderStates.get(builder);
+            checkBuilderState(optimizer, perBuilderState, "Hello,R8", true);
+          }
+          assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
+          // TODO(b/113859361): check # of merged builders.
+          // assertEquals(2, optimizer.analysis.mergedBuilders.size());
+        }));
+  }
+
+  @Ignore("TODO(b/113859361): merge builder.")
+  @Test
+  public void testNestedBuilders_conditional() throws Exception {
+    buildAndCheckIR(
+        "nestedBuilders_conditional",
+        checkOptimizerStates(appView, optimizer -> {
+          assertEquals(1, optimizer.analysis.builderStates.size());
+          for (Value builder : optimizer.analysis.builderStates.keySet()) {
+            Map<Instruction, BuilderState> perBuilderState =
+                optimizer.analysis.builderStates.get(builder);
+            checkBuilderState(optimizer, perBuilderState, null, true);
+          }
+          assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
+          // TODO(b/113859361): check # of merged builders.
+          // assertEquals(3, optimizer.analysis.mergedBuilders.size());
+        }));
+  }
+
+  @Ignore("TODO(b/113859361): merge builder.")
+  @Test
+  public void testConcatenatedBuilders_init() throws Exception {
+    buildAndCheckIR(
+        "concatenatedBuilders_init",
+        checkOptimizerStates(appView, optimizer -> {
+          assertEquals(1, optimizer.analysis.builderStates.size());
+          for (Value builder : optimizer.analysis.builderStates.keySet()) {
+            Map<Instruction, BuilderState> perBuilderState =
+                optimizer.analysis.builderStates.get(builder);
+            checkBuilderState(optimizer, perBuilderState, "Hello,R8", true);
+          }
+          assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
+          // TODO(b/113859361): check # of merged builders.
+          // assertEquals(2, optimizer.analysis.mergedBuilders.size());
+        }));
+  }
+
+  @Ignore("TODO(b/113859361): merge builder.")
+  @Test
+  public void testConcatenatedBuilders_append() throws Exception {
+    buildAndCheckIR(
+        "concatenatedBuilders_append",
+        checkOptimizerStates(appView, optimizer -> {
+          assertEquals(1, optimizer.analysis.builderStates.size());
+          for (Value builder : optimizer.analysis.builderStates.keySet()) {
+            Map<Instruction, BuilderState> perBuilderState =
+                optimizer.analysis.builderStates.get(builder);
+            checkBuilderState(optimizer, perBuilderState, "Hello,R8", true);
+          }
+          assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
+          // TODO(b/113859361): check # of merged builders.
+          // assertEquals(2, optimizer.analysis.mergedBuilders.size());
+        }));
+  }
+
+  @Ignore("TODO(b/113859361): merge builder.")
+  @Test
+  public void testConcatenatedBuilders_conditional() throws Exception {
+    final Set<String> expectedConstStrings = new HashSet<>();
+    expectedConstStrings.add("Hello,R8");
+    expectedConstStrings.add("D8");
+    buildAndCheckIR(
+        "concatenatedBuilders_conditional",
+        checkOptimizerStates(appView, optimizer -> {
           assertEquals(2, optimizer.analysis.builderStates.size());
           for (Value builder : optimizer.analysis.builderStates.keySet()) {
             Map<Instruction, BuilderState> perBuilderState =
                 optimizer.analysis.builderStates.get(builder);
-            String expectedResult =
-                optimizer.analysis.simplifiedBuilders.contains(builder) ? "R8" : null;
-            checkBuilderState(optimizer, perBuilderState, expectedResult, true);
+            checkBuilderStates(optimizer, perBuilderState, expectedConstStrings, true);
           }
-          assertEquals(1, optimizer.analysis.simplifiedBuilders.size());
+          assertEquals(2, optimizer.analysis.simplifiedBuilders.size());
+          // TODO(b/113859361): check # of merged builders.
+          // assertEquals(2, optimizer.analysis.mergedBuilders.size());
         }));
   }
 
@@ -282,16 +364,36 @@
       StringBuilderOptimizer optimizer,
       Map<Instruction, BuilderState> perBuilderState,
       String expectedConstString,
-      boolean expectToSeeToString) {
-    boolean metToString = false;
+      boolean expectToSeeMethodToString) {
+    boolean seenMethodToString = false;
     for (Map.Entry<Instruction, BuilderState> entry : perBuilderState.entrySet()) {
       if (entry.getKey().isInvokeMethod()
         && optimizer.optimizationConfiguration.isToStringMethod(
             entry.getKey().asInvokeMethod().getInvokedMethod())) {
-        metToString = true;
+        seenMethodToString = true;
         assertEquals(expectedConstString, optimizer.analysis.toCompileTimeString(entry.getValue()));
       }
     }
-    assertEquals(expectToSeeToString, metToString);
+    assertEquals(expectToSeeMethodToString, seenMethodToString);
+  }
+
+  static void checkBuilderStates(
+      StringBuilderOptimizer optimizer,
+      Map<Instruction, BuilderState> perBuilderState,
+      Set<String> expectedConstStrings,
+      boolean expectToSeeMethodToString) {
+    boolean seenMethodToString = false;
+    for (Map.Entry<Instruction, BuilderState> entry : perBuilderState.entrySet()) {
+      if (entry.getKey().isInvokeMethod()
+          && optimizer.optimizationConfiguration.isToStringMethod(
+              entry.getKey().asInvokeMethod().getInvokedMethod())) {
+        seenMethodToString = true;
+        String computedString = optimizer.analysis.toCompileTimeString(entry.getValue());
+        assertNotNull(computedString);
+        assertTrue(expectedConstStrings.contains(computedString));
+        expectedConstStrings.remove(computedString);
+      }
+    }
+    assertEquals(expectToSeeMethodToString, seenMethodToString);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
index 0efe652..69c6355 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
@@ -44,6 +44,14 @@
       "Hello,R8",
       // nestedBuilders_appendBuilderResult
       "Hello,R8",
+      // nestedBuilders_conditional
+      "Hello,R8",
+      // concatenatedBuilders_init
+      "Hello,R8",
+      // concatenatedBuilders_append
+      "Hello,R8",
+      // concatenatedBuilders_conditional
+      "Hello,R8",
       // simplePhi
       "Hello,",
       "Hello,D8",
@@ -142,6 +150,28 @@
     expectedCount = 3;
     assertEquals(expectedCount, countConstString(method));
 
+    method = mainClass.uniqueMethodWithName("nestedBuilders_conditional");
+    assertThat(method, isPresent());
+    assertEquals(4, countConstString(method));
+
+    method = mainClass.uniqueMethodWithName("concatenatedBuilders_init");
+    assertThat(method, isPresent());
+    // TODO(b/113859361): merge builders
+    expectedCount = 2;
+    assertEquals(expectedCount, countConstString(method));
+
+    method = mainClass.uniqueMethodWithName("concatenatedBuilders_append");
+    assertThat(method, isPresent());
+    // TODO(b/113859361): merge builders
+    expectedCount = 2;
+    assertEquals(expectedCount, countConstString(method));
+
+    method = mainClass.uniqueMethodWithName("concatenatedBuilders_conditional");
+    assertThat(method, isPresent());
+    // TODO(b/113859361): merge builders
+    expectedCount = 4;
+    assertEquals(expectedCount, countConstString(method));
+
     method = mainClass.uniqueMethodWithName("simplePhi");
     assertThat(method, isPresent());
     assertEquals(4, countConstString(method));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java
index 535fee9..88bb9d4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java
@@ -105,6 +105,67 @@
   }
 
   @NeverInline
+  public static void nestedBuilders_conditional() {
+    StringBuilder b1 = new StringBuilder();
+    StringBuilder b2 = new StringBuilder();
+    StringBuilder b3 = new StringBuilder();
+    b1.append("Hello,");
+    if (System.currentTimeMillis() > 0) {
+      b2.append("R");
+      b2.append("8");
+      b1.append(b2);
+    } else {
+      // To avoid canonicalization
+      b3.append("D");
+      b3.append(8);
+      b1.append(b3.toString());
+    }
+    System.out.println(b1.toString());
+  }
+
+  @NeverInline
+  public static void concatenatedBuilders_init() {
+    StringBuilder b1 = new StringBuilder();
+    b1.append("Hello,");
+    b1.append("R");
+    StringBuilder b2 = new StringBuilder(b1);
+    b2.append(8);
+    System.out.println(b2.toString());
+  }
+
+  @NeverInline
+  public static void concatenatedBuilders_append() {
+    StringBuilder b1 = new StringBuilder();
+    b1.append("Hello,");
+    b1.append("R");
+    StringBuilder b2 = new StringBuilder();
+    b2.append(b1);
+    b2.append(8);
+    System.out.println(b2.toString());
+  }
+
+  @NeverInline
+  public static void concatenatedBuilders_conditional() {
+    String result;
+    StringBuilder b1 = new StringBuilder();
+    b1.append("Hello,");
+    if (System.currentTimeMillis() > 0) {
+      StringBuilder b2 = new StringBuilder();
+      b2.append(b1);
+      b2.append("R");
+      b2.append("8");
+      result = b2.toString();
+    } else {
+      StringBuilder b3 = new StringBuilder();
+      // To avoid canonicalization
+      b3.append("D");
+      b3.append(8);
+      result = b3.toString();
+    }
+    System.out.println(result);
+  }
+
+  @NeverInline
   public static void simplePhi() {
     StringBuilder builder = new StringBuilder();
     builder.append("Hello");
@@ -176,6 +237,10 @@
     typeConversion_withPhis();
     nestedBuilders_appendBuilderItself();
     nestedBuilders_appendBuilderResult();
+    nestedBuilders_conditional();
+    concatenatedBuilders_init();
+    concatenatedBuilders_append();
+    concatenatedBuilders_conditional();
     simplePhi();
     phiAtInit();
     phiWithDifferentInits();
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index d5a89c8..00ee4e7 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -65,13 +65,13 @@
   @Test
   public void equalityOfConstantOperands() {
     RegisterAllocator allocator = new MockRegisterAllocator();
-    Value value0 = new Value(0, TypeLatticeElement.INT, null);
+    Value value0 = new Value(0, TypeLatticeElement.getInt(), null);
     ConstNumber const0 = new ConstNumber(value0, 0);
-    Value value1 = new Value(1, TypeLatticeElement.INT, null);
+    Value value1 = new Value(1, TypeLatticeElement.getInt(), null);
     ConstNumber const1 = new ConstNumber(value1, 1);
-    Value value2 = new Value(2, TypeLatticeElement.INT, null);
+    Value value2 = new Value(2, TypeLatticeElement.getInt(), null);
     ConstNumber const2 = new ConstNumber(value2, 2);
-    Value value3 = new Value(2, TypeLatticeElement.INT, null);
+    Value value3 = new Value(2, TypeLatticeElement.getInt(), null);
     Add add0 = new Add(NumericType.INT, value3, value0, value1);
     add0.setPosition(Position.none());
     Add add1 = new Add(NumericType.INT, value3, value0, value2);
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index a449c5d..f88783d 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -161,8 +161,8 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.INT));
-    scheduler.addMove(new RegisterMove(1, 0, TypeLatticeElement.INT));
+    scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.getInt()));
+    scheduler.addMove(new RegisterMove(1, 0, TypeLatticeElement.getInt()));
     scheduler.schedule();
     assertEquals(3, moves.size());
     Move tempMove = moves.get(0);
@@ -186,8 +186,8 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(0, 2, TypeLatticeElement.LONG));
-    scheduler.addMove(new RegisterMove(2, 0, TypeLatticeElement.LONG));
+    scheduler.addMove(new RegisterMove(0, 2, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(2, 0, TypeLatticeElement.getLong()));
     scheduler.schedule();
     assertEquals(3, moves.size());
     Move tempMove = moves.get(0);
@@ -211,8 +211,8 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(1, 0, TypeLatticeElement.LONG));
-    scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.INT));
+    scheduler.addMove(new RegisterMove(1, 0, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.getInt()));
     scheduler.schedule();
     assertEquals(3, moves.size());
     Move tempMove = moves.get(0).asMove();
@@ -236,8 +236,8 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.INT));
-    scheduler.addMove(new RegisterMove(1, 0, TypeLatticeElement.LONG));
+    scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.getInt()));
+    scheduler.addMove(new RegisterMove(1, 0, TypeLatticeElement.getLong()));
     scheduler.schedule();
     assertEquals(3, moves.size());
     Move tempMove = moves.get(0).asMove();
@@ -261,8 +261,8 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.LONG));
-    scheduler.addMove(new RegisterMove(2, 3, TypeLatticeElement.LONG));
+    scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(2, 3, TypeLatticeElement.getLong()));
     scheduler.schedule();
     assertEquals(2, moves.size());
     Move firstMove = moves.get(0).asMove();
@@ -280,8 +280,8 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(2, 1, TypeLatticeElement.LONG));
-    scheduler.addMove(new RegisterMove(0, 3, TypeLatticeElement.LONG));
+    scheduler.addMove(new RegisterMove(2, 1, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(0, 3, TypeLatticeElement.getLong()));
     scheduler.schedule();
     assertEquals(3, moves.size());
     Move firstMove = moves.get(0).asMove();
@@ -303,9 +303,9 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(2, 0, TypeLatticeElement.LONG));
-    scheduler.addMove(new RegisterMove(0, 2, TypeLatticeElement.INT));
-    scheduler.addMove(new RegisterMove(1, 3, TypeLatticeElement.INT));
+    scheduler.addMove(new RegisterMove(2, 0, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(0, 2, TypeLatticeElement.getInt()));
+    scheduler.addMove(new RegisterMove(1, 3, TypeLatticeElement.getInt()));
     scheduler.schedule();
     assertEquals(4, moves.size());
     Move firstMove = moves.get(0).asMove();
@@ -327,8 +327,8 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(0, 2, TypeLatticeElement.LONG));
-    scheduler.addMove(new RegisterMove(3, 0, TypeLatticeElement.INT));
+    scheduler.addMove(new RegisterMove(0, 2, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(3, 0, TypeLatticeElement.getInt()));
     scheduler.schedule();
     assertEquals(3, moves.size());
     Move firstMove = moves.get(0).asMove();
@@ -350,10 +350,10 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(14, 11, TypeLatticeElement.LONG));
-    scheduler.addMove(new RegisterMove(16, 13, TypeLatticeElement.LONG));
-    scheduler.addMove(new RegisterMove(10, 17, TypeLatticeElement.LONG));
-    scheduler.addMove(new RegisterMove(12, 19, TypeLatticeElement.LONG));
+    scheduler.addMove(new RegisterMove(14, 11, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(16, 13, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(10, 17, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(12, 19, TypeLatticeElement.getLong()));
     scheduler.schedule();
     // In order to resolve these moves, we need to use two temporary register pairs.
     assertEquals(6, moves.size());
@@ -375,10 +375,10 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(26, 22, TypeLatticeElement.INT));
-    scheduler.addMove(new RegisterMove(29, 24, TypeLatticeElement.LONG));
+    scheduler.addMove(new RegisterMove(26, 22, TypeLatticeElement.getInt()));
+    scheduler.addMove(new RegisterMove(29, 24, TypeLatticeElement.getLong()));
     scheduler.addMove(new RegisterMove(28, 26, objectType));
-    scheduler.addMove(new RegisterMove(23, 28, TypeLatticeElement.LONG));
+    scheduler.addMove(new RegisterMove(23, 28, TypeLatticeElement.getLong()));
     scheduler.schedule();
     // For this example we need recursive unblocking.
     assertEquals(6, moves.size());
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
index a5183d3..4700a44 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -63,20 +63,19 @@
     // Setup live an inactive live interval with ranges [0, 10[ and [20, 30[ with only
     // uses in the first interval and which is linked to another interval.
     LiveIntervals inactiveIntervals =
-        new LiveIntervals(new Value(0, TypeLatticeElement.INT, null));
+        new LiveIntervals(new Value(0, TypeLatticeElement.getInt(), null));
     inactiveIntervals.addRange(new LiveRange(0, 10));
     inactiveIntervals.addUse(new LiveIntervalsUse(0, 10));
     inactiveIntervals.addUse(new LiveIntervalsUse(4, 10));
     inactiveIntervals.addRange(new LiveRange(20, 30));
     inactiveIntervals.setRegister(0);
-    LiveIntervals linked =
-        new LiveIntervals(new Value(1, TypeLatticeElement.INT, null));
+    LiveIntervals linked = new LiveIntervals(new Value(1, TypeLatticeElement.getInt(), null));
     linked.setRegister(1);
     inactiveIntervals.link(linked);
     allocator.addInactiveIntervals(inactiveIntervals);
     // Setup an unhandled interval that overlaps the inactive interval.
     LiveIntervals unhandledIntervals =
-        new LiveIntervals(new Value(2, TypeLatticeElement.INT, null));
+        new LiveIntervals(new Value(2, TypeLatticeElement.getInt(), null));
     unhandledIntervals.addRange(new LiveRange(12, 24));
     // Split the overlapping inactive intervals and check that after the split, the second
     // part of the inactive interval is unhandled and will therefore get a new register
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 26f2030..74d62e0 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -517,7 +517,7 @@
   }
 
   public DexApplication read(InternalOptions options) throws Exception {
-    Timing timing = new Timing("JasminTest");
+    Timing timing = Timing.empty();
     return new ApplicationReader(build(), options, timing).read();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInCompanionTest.java
new file mode 100644
index 0000000..a5c01a5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInCompanionTest.java
@@ -0,0 +1,126 @@
+// 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.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+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.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRenameInCompanionTest extends KotlinMetadataTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRenameInCompanionTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Path companionLibJar;
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    String companionLibFolder = PKG_PREFIX + "/companion_lib";
+    companionLibJar =
+        kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addSourceFiles(getKotlinFileInTest(companionLibFolder, "lib"))
+            .compile();
+  }
+
+  @Test
+  public void testMetadataInCompanion_renamed() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(companionLibJar)
+            // Keep the B class and its interface (which has the doStuff method).
+            .addKeepRules("-keep class **.B")
+            .addKeepRules("-keep class **.I { <methods>; }")
+            // Keep getters for B$Companion.(singleton|foo) which will be referenced at the app.
+            .addKeepRules("-keepclassmembers class **.B$* { *** get*(...); }")
+            // No rule for Super, but will be kept and renamed.
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .compile();
+    String pkg = getClass().getPackage().getName();
+    final String superClassName = pkg + ".companion_lib.Super";
+    final String bClassName = pkg + ".companion_lib.B";
+    final String companionClassName = pkg + ".companion_lib.B$Companion";
+    compileResult.inspect(inspector -> {
+      ClassSubject sup = inspector.clazz(superClassName);
+      assertThat(sup, isRenamed());
+
+      ClassSubject impl = inspector.clazz(bClassName);
+      assertThat(impl, isPresent());
+      assertThat(impl, not(isRenamed()));
+      // API entry is kept, hence the presence of Metadata.
+      KmClassSubject kmClass = impl.getKmClass();
+      assertThat(kmClass, isPresent());
+      List<ClassSubject> superTypes = kmClass.getSuperTypes();
+      assertTrue(superTypes.stream().noneMatch(
+          supertype -> supertype.getFinalDescriptor().contains("Super")));
+      assertTrue(superTypes.stream().anyMatch(
+          supertype -> supertype.getFinalDescriptor().equals(sup.getFinalDescriptor())));
+
+      // Bridge for the property in the companion that needs a backing field.
+      MethodSubject singletonBridge = impl.uniqueMethodWithName("access$getSingleton$cp");
+      assertThat(singletonBridge, isRenamed());
+
+      // For B$Companion.foo, no backing field needed, hence no bridge.
+      MethodSubject fooBridge = impl.uniqueMethodWithName("access$getFoo$cp");
+      assertThat(fooBridge, not(isPresent()));
+
+      ClassSubject companion = inspector.clazz(companionClassName);
+      assertThat(companion, isRenamed());
+
+      MethodSubject singletonGetter = companion.uniqueMethodWithName("getSingleton");
+      assertThat(singletonGetter, isPresent());
+
+      MethodSubject fooGetter = companion.uniqueMethodWithName("getFoo");
+      assertThat(fooGetter, isPresent());
+    });
+
+    Path libJar = compileResult.writeToZip();
+
+    String appFolder = PKG_PREFIX + "/companion_app";
+    ProcessResult kotlinTestCompileResult =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            // TODO(b/143687784): update to just .compile() once fixed.
+            .compileRaw();
+
+    // TODO(b/70169921): should be able to compile!
+    assertNotEquals(0, kotlinTestCompileResult.exitCode);
+    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: singleton"));
+    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: foo"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.java
index 7ffa9ac..84d9dd2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInExtensionPropertyTest.java
@@ -4,22 +4,23 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionProperty;
 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.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 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 java.nio.file.Path;
@@ -87,6 +88,9 @@
       List<ClassSubject> superTypes = kmClass.getSuperTypes();
       assertTrue(superTypes.stream().noneMatch(
           supertype -> supertype.getFinalDescriptor().contains("Super")));
+      KmFunctionSubject kmFunction = kmClass.kmFunctionWithUniqueName("doStuff");
+      assertThat(kmFunction, isPresent());
+      assertThat(kmFunction, not(isExtensionFunction()));
 
       ClassSubject bKt = inspector.clazz(bKtClassName);
       assertThat(bKt, isPresent());
@@ -97,20 +101,24 @@
 
       KmPropertySubject kmProperty = kmPackage.kmPropertyExtensionWithUniqueName("asI");
       assertThat(kmProperty, isExtensionProperty());
+      assertNotNull(kmProperty.getterSignature());
     });
 
     Path libJar = compileResult.writeToZip();
 
     String appFolder = PKG_PREFIX + "/extension_property_app";
-    ProcessResult processResult =
+    Path output =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
             .setOutputPath(temp.newFolder().toPath())
-            .compileRaw();
-    // TODO(b/70169921): should be able to compile!
-    assertNotEquals(0, processResult.exitCode);
-    assertThat(processResult.stderr, containsString("unresolved reference: doStuff"));
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".extension_property_app.MainKt")
+        .assertSuccessWithOutputLines("do stuff", "do stuff");
   }
 
   @Test
@@ -134,7 +142,6 @@
     final String bKtClassName = pkg + ".extension_property_lib.BKt";
     compileResult.inspect(inspector -> {
       ClassSubject sup = inspector.clazz(superClassName);
-      assertThat(sup, isPresent());
       assertThat(sup, isRenamed());
 
       ClassSubject impl = inspector.clazz(bClassName);
@@ -158,19 +165,23 @@
 
       KmPropertySubject kmProperty = kmPackage.kmPropertyExtensionWithUniqueName("asI");
       assertThat(kmProperty, isExtensionProperty());
+      assertNotNull(kmProperty.getterSignature());
     });
 
     Path libJar = compileResult.writeToZip();
 
     String appFolder = PKG_PREFIX + "/extension_property_app";
-    ProcessResult processResult =
+    Path output =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
             .setOutputPath(temp.newFolder().toPath())
-            .compileRaw();
-    // TODO(b/70169921): should be able to compile!
-    assertNotEquals(0, processResult.exitCode);
-    assertThat(processResult.stderr, containsString("unresolved reference: doStuff"));
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".extension_property_app.MainKt")
+        .assertSuccessWithOutputLines("do stuff", "do stuff");
   }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
index d0708f8..556780c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTest.java
@@ -81,8 +81,7 @@
       assertThat(name, not(isExtensionProperty()));
       assertNotNull(name.fieldSignature());
       assertNotNull(name.getterSignature());
-      // TODO(b/70169921): Can remove setter.
-      assertNotNull(name.setterSignature());
+      assertNull(name.setterSignature());
 
       KmPropertySubject familyName = kmClass.kmPropertyWithUniqueName("familyName");
       assertThat(familyName, isPresent());
@@ -98,8 +97,7 @@
       assertThat(age, not(isExtensionProperty()));
       assertNotNull(age.fieldSignature());
       assertNotNull(age.getterSignature());
-      // TODO(b/70169921): Can remove setter.
-      assertNotNull(name.setterSignature());
+      assertNull(name.setterSignature());
     });
 
     Path libJar = compileResult.writeToZip();
@@ -146,27 +144,19 @@
       KmPropertySubject name = kmClass.kmPropertyWithUniqueName("name");
       assertThat(name, isPresent());
       assertThat(name, not(isExtensionProperty()));
-      assertNotNull(name.fieldSignature());
-      // TODO(b/70169921): Either remove getter or rewrite renamed signature.
-      assertNotNull(name.getterSignature());
+      assertNull(name.fieldSignature());
+      assertNull(name.getterSignature());
       assertNotNull(name.setterSignature());
 
       KmPropertySubject familyName = kmClass.kmPropertyWithUniqueName("familyName");
-      assertThat(familyName, isPresent());
-      assertThat(familyName, not(isExtensionProperty()));
-      // No backing field for property `familyName`
-      assertNull(familyName.fieldSignature());
-      assertNotNull(familyName.getterSignature());
-      // No setter for property `familyName`
-      assertNull(familyName.setterSignature());
+      assertThat(familyName, not(isPresent()));
 
       KmPropertySubject age = kmClass.kmPropertyWithUniqueName("age");
       assertThat(age, isPresent());
       assertThat(age, not(isExtensionProperty()));
       assertNotNull(age.fieldSignature());
-      // TODO(b/70169921): Either remove getter or rewrite renamed signature.
-      assertNotNull(age.getterSignature());
-      assertNotNull(name.setterSignature());
+      assertNull(age.getterSignature());
+      assertNotNull(age.setterSignature());
     });
 
     Path libJar = compileResult.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java
index 60a9732..78a1514 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInPropertyTypeTest.java
@@ -6,16 +6,14 @@
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 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.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -82,25 +80,25 @@
           supertype -> supertype.getFinalDescriptor().contains("Itf")));
       assertTrue(superTypes.stream().anyMatch(
           supertype -> supertype.getFinalDescriptor().equals(itf.getFinalDescriptor())));
-      // TODO(b/70169921): should not refer to Itf
       List<ClassSubject> propertyReturnTypes = kmClass.getReturnTypesInProperties();
       assertTrue(propertyReturnTypes.stream().anyMatch(
-          propertyType -> propertyType.getOriginalDescriptor().contains("Itf")));
+          propertyType -> propertyType.getFinalDescriptor().equals(itf.getFinalDescriptor())));
     });
 
     Path libJar = compileResult.writeToZip();
 
     String appFolder = PKG_PREFIX + "/propertytype_app";
-    ProcessResult processResult =
+    Path output =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
             .setOutputPath(temp.newFolder().toPath())
-            .compileRaw();
-    // TODO(b/70169921): should be able to compile!
-    assertNotEquals(0, processResult.exitCode);
-    assertThat(
-        processResult.stderr,
-        containsString("cannot access class '" + pkg + ".propertytype_lib.Itf'"));
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".propertytype_app.MainKt")
+        .assertSuccessWithOutputLines("Impl::8");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java
new file mode 100644
index 0000000..a2d4fa1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java
@@ -0,0 +1,183 @@
+// 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.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
+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.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 java.nio.file.Path;
+import java.util.Collection;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRenameInSealedClassTest extends KotlinMetadataTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRenameInSealedClassTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Path sealedLibJar;
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    String sealedLibFolder = PKG_PREFIX + "/sealed_lib";
+    sealedLibJar =
+        kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addSourceFiles(getKotlinFileInTest(sealedLibFolder, "lib"))
+            .compile();
+  }
+
+  @Test
+  public void testMetadataInSealedClass_valid() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(sealedLibJar)
+            // Keep the Expr class
+            .addKeepRules("-keep class **.Expr")
+            // Keep the extension function
+            .addKeepRules("-keep class **.LibKt { <methods>; }")
+            // Keep the factory object and utils
+            .addKeepRules("-keep class **.ExprFactory { *; }")
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .compile();
+    String pkg = getClass().getPackage().getName();
+    final String numClassName = pkg + ".sealed_lib.Num";
+    final String exprClassName = pkg + ".sealed_lib.Expr";
+    final String libClassName = pkg + ".sealed_lib.LibKt";
+    compileResult.inspect(inspector -> {
+      ClassSubject num = inspector.clazz(numClassName);
+      assertThat(num, isRenamed());
+
+      ClassSubject expr = inspector.clazz(exprClassName);
+      assertThat(expr, isPresent());
+      assertThat(expr, not(isRenamed()));
+
+      KmClassSubject kmClass = expr.getKmClass();
+      assertThat(kmClass, isPresent());
+
+      kmClass.getSealedSubclassDescriptors().forEach(sealedSubclassDescriptor -> {
+        ClassSubject sealedSubclass =
+            inspector.clazz(descriptorToJavaType(sealedSubclassDescriptor));
+        assertThat(sealedSubclass, isRenamed());
+        assertEquals(sealedSubclassDescriptor, sealedSubclass.getFinalDescriptor());
+      });
+
+      ClassSubject libKt = inspector.clazz(libClassName);
+      assertThat(expr, isPresent());
+      assertThat(expr, not(isRenamed()));
+
+      KmPackageSubject kmPackage = libKt.getKmPackage();
+      assertThat(kmPackage, isPresent());
+
+      KmFunctionSubject eval = kmPackage.kmFunctionExtensionWithUniqueName("eval");
+      assertThat(eval, isPresent());
+      assertThat(eval, isExtensionFunction());
+    });
+
+    Path libJar = compileResult.writeToZip();
+
+    String appFolder = PKG_PREFIX + "/sealed_app";
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(appFolder, "valid"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".sealed_app.ValidKt")
+        .assertSuccessWithOutputLines("6");
+  }
+
+  @Test
+  public void testMetadataInSealedClass_invalid() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(sealedLibJar)
+            // Keep the Expr class
+            .addKeepRules("-keep class **.Expr")
+            // Keep the extension function
+            .addKeepRules("-keep class **.LibKt { <methods>; }")
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .compile();
+    String pkg = getClass().getPackage().getName();
+    final String exprClassName = pkg + ".sealed_lib.Expr";
+    final String numClassName = pkg + ".sealed_lib.Num";
+    final String libClassName = pkg + ".sealed_lib.LibKt";
+    compileResult.inspect(inspector -> {
+      // Without any specific keep rule and no instantiation point, it's not necessary to keep
+      // sub classes of Expr.
+      ClassSubject num = inspector.clazz(numClassName);
+      assertThat(num, not(isPresent()));
+
+      ClassSubject expr = inspector.clazz(exprClassName);
+      assertThat(expr, isPresent());
+      assertThat(expr, not(isRenamed()));
+
+      KmClassSubject kmClass = expr.getKmClass();
+      assertThat(kmClass, isPresent());
+
+      assertTrue(kmClass.getSealedSubclassDescriptors().isEmpty());
+
+      ClassSubject libKt = inspector.clazz(libClassName);
+      assertThat(expr, isPresent());
+      assertThat(expr, not(isRenamed()));
+
+      KmPackageSubject kmPackage = libKt.getKmPackage();
+      assertThat(kmPackage, isPresent());
+
+      KmFunctionSubject eval = kmPackage.kmFunctionExtensionWithUniqueName("eval");
+      assertThat(eval, isPresent());
+      assertThat(eval, isExtensionFunction());
+    });
+
+    Path libJar = compileResult.writeToZip();
+
+    String appFolder = PKG_PREFIX + "/sealed_app";
+    ProcessResult kotlinTestCompileResult =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(appFolder, "invalid"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compileRaw();
+
+    assertNotEquals(0, kotlinTestCompileResult.exitCode);
+    assertThat(kotlinTestCompileResult.stderr, containsString("cannot access"));
+    assertThat(kotlinTestCompileResult.stderr, containsString("private in 'Expr'"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt
new file mode 100644
index 0000000..55c61e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_app/main.kt
@@ -0,0 +1,12 @@
+// 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.metadata.companion_app
+
+import com.android.tools.r8.kotlin.metadata.companion_lib.B
+
+fun main() {
+  B().doStuff()
+  B.singleton.doStuff()
+  println(B.foo)
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
new file mode 100644
index 0000000..9e9f2eb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
@@ -0,0 +1,26 @@
+// 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.metadata.companion_lib
+
+interface I {
+  fun doStuff()
+}
+
+open class Super : I {
+  override fun doStuff() {
+    println("do stuff")
+  }
+}
+
+class B : Super() {
+  override fun doStuff() {
+    println(foo)
+  }
+
+  companion object {
+    val singleton: Super = B()
+    val foo: String
+      get() = "B.Companion:foo"
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/invalid.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/invalid.kt
new file mode 100644
index 0000000..88f401c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/invalid.kt
@@ -0,0 +1,14 @@
+// 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.metadata.sealed_app
+
+import com.android.tools.r8.kotlin.metadata.sealed_lib.Expr
+import com.android.tools.r8.kotlin.metadata.sealed_lib.eval
+
+// A sealed class can only be extended within that file.
+class MyExpr : Expr()
+
+fun main() {
+  println(MyExpr().eval())
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/valid.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/valid.kt
new file mode 100644
index 0000000..379c17e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/valid.kt
@@ -0,0 +1,11 @@
+// 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.metadata.sealed_app
+
+import com.android.tools.r8.kotlin.metadata.sealed_lib.eval
+import com.android.tools.r8.kotlin.metadata.sealed_lib.ExprFactory
+
+fun main() {
+  println(ExprFactory.createSum(ExprFactory.createNum(4), ExprFactory.createNum(2)).eval())
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_lib/lib.kt
new file mode 100644
index 0000000..ea64d13
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_lib/lib.kt
@@ -0,0 +1,26 @@
+// 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.metadata.sealed_lib
+
+sealed class Expr
+
+sealed class BinOp(val e1: Expr, val e2: Expr, val op: String) : Expr() {
+  override fun toString() = "$e1 $op $e2"
+}
+
+data class Num(val num: Int) : Expr()
+
+data class Sum(val left: Expr, val right: Expr) : BinOp(left, right, "+")
+
+object ExprFactory {
+  fun createNum(i: Int): Expr = Num(i)
+  fun createSum(e1: Expr, e2: Expr): Expr = Sum(e1, e2)
+}
+
+fun Expr.eval(): Int =
+  when(this) {
+    is Num -> num
+    is Sum -> e1.eval() + e2.eval()
+    else -> throw IllegalArgumentException("Unknown Expr: $this")
+  }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 6ebd77c..3f8c11f 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -798,7 +798,7 @@
       int methodCount,
       DiagnosticsHandler diagnosticsHandler)
       throws IOException, ExecutionException {
-    Timing timing = new Timing("MainDexListTests");
+    Timing timing = Timing.empty();
     InternalOptions options =
         new InternalOptions(new DexItemFactory(), new Reporter(diagnosticsHandler));
     options.minApiLevel = minApi;
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierTest.java b/src/test/java/com/android/tools/r8/naming/MinifierTest.java
index 473ca53..1baffde 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierTest.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.Timing;
 import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.Collection;
@@ -31,7 +30,7 @@
       String test,
       List<String> keepRulesFiles,
       BiConsumer<DexItemFactory, NamingLens> inspection) {
-    super(test, keepRulesFiles, inspection, new Timing("MinifierTest"));
+    super(test, keepRulesFiles, inspection);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index b6db30f..b03aeaa 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -24,7 +24,6 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -46,14 +45,11 @@
   protected DexItemFactory dexItemFactory;
 
   protected NamingTestBase(
-      String test,
-      List<String> keepRulesFiles,
-      BiConsumer<DexItemFactory, NamingLens> inspection,
-      Timing timing) {
+      String test, List<String> keepRulesFiles, BiConsumer<DexItemFactory, NamingLens> inspection) {
     appFileName = ToolHelper.EXAMPLES_BUILD_DIR + test + "/classes.dex";
     this.keepRulesFiles = keepRulesFiles;
     this.inspection = inspection;
-    this.timing = timing;
+    this.timing = Timing.empty();
   }
 
   @Before
@@ -80,7 +76,7 @@
     appView.setAppInfo(
         enqueuer.traceApplication(
             appView.rootSet(), configuration.getDontWarnPatterns(), executor, timing));
-    return new Minifier(appView.withLiveness(), Collections.emptySet()).run(executor, timing);
+    return new Minifier(appView.withLiveness()).run(executor, timing);
   }
 
   protected static <T> Collection<Object[]> createTests(
diff --git a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
index 4209cb6..549b0e1 100644
--- a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.Timing;
 import com.google.common.base.CharMatcher;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
@@ -39,7 +38,7 @@
       String test,
       List<String> keepRulesFiles,
       BiConsumer<DexItemFactory, NamingLens> inspection) {
-    super(test, keepRulesFiles, inspection, new Timing("PackageNamingTest"));
+    super(test, keepRulesFiles, inspection);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
index fbe1f34..9b2c7c4 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
@@ -44,9 +44,12 @@
   }
 
   private int expectedActualStackTraceHeight() {
-    // In debug mode the expected stack trace height differs since there is no lambda desugaring
-    // for CF.
-    return mode == CompilationMode.RELEASE ? 2 : (parameters.isCfRuntime() ? 4 : 5);
+    // In DEX release the entire lambda is inlined.
+    if (parameters.isDexRuntime()) {
+      return mode == CompilationMode.RELEASE ? 1 : 5;
+    }
+    // In CF release it is not and in debug there is no lambda desugaring thus the shorter stack.
+    return mode == CompilationMode.RELEASE ? 2 : 4;
   }
 
   private boolean isSynthesizedLambdaFrame(StackTraceLine line) {
diff --git a/src/test/java/com/android/tools/r8/regress/b147865212/Flaf2Dump.java b/src/test/java/com/android/tools/r8/regress/b147865212/Flaf2Dump.java
new file mode 100644
index 0000000..aa2ac6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b147865212/Flaf2Dump.java
@@ -0,0 +1,203 @@
+// 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.b147865212;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generate a class file with the following code, line number table,
+// and local variable table.
+//
+//  public static final java.lang.String box(int);
+//    descriptor: (I)Ljava/lang/String;
+//    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+//    Code:
+//      stack=1, locals=3, args_size=1
+//         0: nop
+//         1: ldc           #9                  // String OK
+//         3: astore_1
+//         4: nop
+//         5: iload_0
+//         6: istore_2
+//         7: nop
+//         8: nop
+//         9: iload_0
+//        10: istore_2
+//        11: nop
+//        12: aload_1
+//        13: areturn
+//        14: astore_1
+//        15: nop
+//        16: iload_0
+//        17: istore_2
+//        18: nop
+//        19: nop
+//        20: iload_0
+//        21: istore_2
+//        22: nop
+//        23: aload_1
+//        24: athrow
+//      Exception table:
+//         from    to  target type
+//             0     4    14   any
+//            14    15    14   any
+//      StackMapTable: number_of_entries = 1
+//        frame_type = 78 /* same_locals_1_stack_item */
+//          stack = [ class java/lang/Throwable ]
+//      LineNumberTable:
+//        line 2: 0
+//        line 3: 1
+//        line 5: 4
+//        line 6: 5
+//        line 8: 8
+//        line 9: 9
+//        line 10: 11
+//        line 3: 13
+//        line 12: 14
+//        line 5: 15
+//        line 6: 16
+//        line 8: 19
+//        line 9: 20
+//        line 10: 22
+//      LocalVariableTable:
+//        Start  Length  Slot  Name   Signature
+//            7       1     2     z   I
+//           11       1     2     z   I
+//           18       1     2     z   I
+//           22       1     2     z   I
+//            0      25     0     x   I
+public class Flaf2Dump implements Opcodes {
+
+  public static byte[] dump() throws Exception {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+    AnnotationVisitor annotationVisitor0;
+
+    classWriter.visit(
+        V1_6, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, "FlafKt", null, "java/lang/Object", null);
+
+    classWriter.visitSource("Flaf.kt", null);
+
+    {
+      annotationVisitor0 = classWriter.visitAnnotation("Lkotlin/Metadata;", true);
+      annotationVisitor0.visit("mv", new int[] {1, 1, 17});
+      annotationVisitor0.visit("bv", new int[] {1, 0, 3});
+      annotationVisitor0.visit("k", new Integer(2));
+      {
+        AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d1");
+        annotationVisitor1.visit(
+            null,
+            "\u0000\u0006\n\u0000\n\u0002\u0010\u000e\u001a\u0006\u0010\u0000\u001a\u00020\u0001");
+        annotationVisitor1.visitEnd();
+      }
+      {
+        AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d2");
+        annotationVisitor1.visit(null, "box");
+        annotationVisitor1.visit(null, "");
+        annotationVisitor1.visitEnd();
+      }
+      annotationVisitor0.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "box", "()Ljava/lang/String;", null, null);
+      {
+        annotationVisitor0 =
+            methodVisitor.visitAnnotation("Lorg/jetbrains/annotations/NotNull;", false);
+        annotationVisitor0.visitEnd();
+      }
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      Label label1 = new Label();
+      Label label2 = new Label();
+      methodVisitor.visitTryCatchBlock(label0, label1, label2, null);
+      Label label3 = new Label();
+      methodVisitor.visitTryCatchBlock(label2, label3, label2, null);
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(2, label0);
+      methodVisitor.visitInsn(NOP);
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(3, label4);
+      methodVisitor.visitLdcInsn("OK");
+      methodVisitor.visitVarInsn(ASTORE, 0);
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(5, label1);
+      methodVisitor.visitInsn(NOP);
+      Label label5 = new Label();
+      methodVisitor.visitLabel(label5);
+      methodVisitor.visitLineNumber(6, label5);
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitVarInsn(ISTORE, 1);
+      Label label6 = new Label();
+      methodVisitor.visitLabel(label6);
+      methodVisitor.visitInsn(NOP);
+      Label label7 = new Label();
+      methodVisitor.visitLabel(label7);
+      methodVisitor.visitLineNumber(8, label7);
+      methodVisitor.visitInsn(NOP);
+      Label label8 = new Label();
+      methodVisitor.visitLabel(label8);
+      methodVisitor.visitLineNumber(9, label8);
+      methodVisitor.visitInsn(ICONST_4);
+      methodVisitor.visitVarInsn(ISTORE, 1);
+      Label label9 = new Label();
+      methodVisitor.visitLabel(label9);
+      methodVisitor.visitInsn(NOP);
+      Label label10 = new Label();
+      methodVisitor.visitLabel(label10);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      Label label11 = new Label();
+      methodVisitor.visitLabel(label11);
+      methodVisitor.visitLineNumber(3, label11);
+      methodVisitor.visitInsn(ARETURN);
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(11, label2);
+      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
+      methodVisitor.visitVarInsn(ASTORE, 0);
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(5, label3);
+      methodVisitor.visitInsn(NOP);
+      Label label12 = new Label();
+      methodVisitor.visitLabel(label12);
+      methodVisitor.visitLineNumber(6, label12);
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitVarInsn(ISTORE, 1);
+      Label label13 = new Label();
+      methodVisitor.visitLabel(label13);
+      methodVisitor.visitInsn(NOP);
+      Label label14 = new Label();
+      methodVisitor.visitLabel(label14);
+      methodVisitor.visitLineNumber(8, label14);
+      methodVisitor.visitInsn(NOP);
+      Label label15 = new Label();
+      methodVisitor.visitLabel(label15);
+      methodVisitor.visitLineNumber(9, label15);
+      methodVisitor.visitInsn(ICONST_4);
+      methodVisitor.visitVarInsn(ISTORE, 1);
+      Label label16 = new Label();
+      methodVisitor.visitLabel(label16);
+      methodVisitor.visitInsn(NOP);
+      Label label17 = new Label();
+      methodVisitor.visitLabel(label17);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitInsn(ATHROW);
+      methodVisitor.visitLocalVariable("z", "I", null, label6, label7, 1);
+      methodVisitor.visitLocalVariable("z", "I", null, label9, label10, 1);
+      methodVisitor.visitLocalVariable("z", "I", null, label13, label14, 1);
+      methodVisitor.visitLocalVariable("z", "I", null, label16, label17, 1);
+      methodVisitor.visitMaxs(1, 2);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b147865212/FlafDump.java b/src/test/java/com/android/tools/r8/regress/b147865212/FlafDump.java
new file mode 100644
index 0000000..d0a4f6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b147865212/FlafDump.java
@@ -0,0 +1,124 @@
+// 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.b147865212;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generate a class file containing the following method, line number table,
+// and local variable table. Conditionally add an extra entry to the line
+// number table for the nop at instruction 5. When generateLineNumberForLocal
+// is true, a `line 7: 5` entry is generated in the line number table
+// making the local observable in the debugger.
+//
+//  public static final java.lang.String box();
+//    descriptor: ()Ljava/lang/String;
+//    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+//    Code:
+//      stack=1, locals=1, args_size=0
+//         0: nop
+//         1: ldc           #11                 // String A
+//         3: areturn
+//         4: astore_0
+//         5: nop
+//         6: ldc           #13                 // String B
+//         8: areturn
+//      Exception table:
+//         from    to  target type
+//             0     4     4   Class java/lang/IllegalStateException
+//      StackMapTable: number_of_entries = 1
+//        frame_type = 68 /* same_locals_1_stack_item */
+//          stack = [ class java/lang/IllegalStateException ]
+//      LineNumberTable:
+//        line 2: 0
+//        line 3: 1
+//        line 4: 4
+//        line 5: 6
+//        line 6: 6
+//      LocalVariableTable:
+//        Start  Length  Slot  Name   Signature
+//            5       1     0     e   Ljava/lang/IllegalStateException;
+public class FlafDump implements Opcodes {
+  public static byte[] dump(boolean generateLineNumberForLocal) throws Exception {
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+    AnnotationVisitor annotationVisitor0;
+
+    classWriter.visit(
+        V1_6, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, "FlafKt", null, "java/lang/Object", null);
+
+    classWriter.visitSource("Flaf.kt", null);
+
+    {
+      annotationVisitor0 = classWriter.visitAnnotation("Lkotlin/Metadata;", true);
+      annotationVisitor0.visit("mv", new int[] {1, 1, 16});
+      annotationVisitor0.visit("bv", new int[] {1, 0, 3});
+      annotationVisitor0.visit("k", new Integer(2));
+      {
+        AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d1");
+        annotationVisitor1.visit(
+            null,
+            "\u0000\u0006\n\u0000\n\u0002\u0010\u000e\u001a\u0006\u0010\u0000\u001a\u00020\u0001");
+        annotationVisitor1.visitEnd();
+      }
+      {
+        AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d2");
+        annotationVisitor1.visit(null, "box");
+        annotationVisitor1.visit(null, "");
+        annotationVisitor1.visitEnd();
+      }
+      annotationVisitor0.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "box", "()Ljava/lang/String;", null, null);
+      {
+        annotationVisitor0 =
+            methodVisitor.visitAnnotation("Lorg/jetbrains/annotations/NotNull;", false);
+        annotationVisitor0.visitEnd();
+      }
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      Label label1 = new Label();
+      methodVisitor.visitTryCatchBlock(label0, label1, label1, "java/lang/IllegalStateException");
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(2, label0);
+      methodVisitor.visitInsn(NOP);
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(3, label2);
+      methodVisitor.visitLdcInsn("A");
+      methodVisitor.visitInsn(ARETURN);
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(4, label1);
+      methodVisitor.visitFrame(
+          Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/IllegalStateException"});
+      methodVisitor.visitVarInsn(ASTORE, 0);
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      if (generateLineNumberForLocal) {
+        methodVisitor.visitLineNumber(7, label3);
+      }
+      methodVisitor.visitInsn(NOP);
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(5, label4);
+      methodVisitor.visitLineNumber(6, label4);
+      methodVisitor.visitLdcInsn("B");
+      methodVisitor.visitInsn(ARETURN);
+      methodVisitor.visitLocalVariable(
+          "e", "Ljava/lang/IllegalStateException;", null, label3, label4, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java b/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java
new file mode 100644
index 0000000..d7133e7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java
@@ -0,0 +1,80 @@
+// 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.b147865212;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Arrays;
+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 Regress147865212 extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public Regress147865212(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private boolean hasLocal(MethodSubject method) {
+    return Arrays.stream(method.getMethod().getCode().asDexCode().getDebugInfo().events)
+        .anyMatch(event -> event instanceof StartLocal);
+  }
+
+  private MethodSubject build(byte[] classFile) throws Exception {
+    CodeInspector inspector =
+        testForD8()
+            .addProgramClassFileData(classFile)
+            .setMinApi(parameters.getApiLevel())
+            .debug()
+            .compile()
+            .inspector();
+    ClassSubject clazz = inspector.clazz("FlafKt");
+    assertTrue(clazz.isPresent());
+    MethodSubject method = clazz.uniqueMethodWithName("box");
+    assertTrue(method.isPresent());
+    return method;
+  }
+
+  @Test
+  public void testFlafWithLineNumberForLocal() throws Exception {
+    // Generate the FlafKt class file adding a line number for the single nop instruction
+    // that a local spans. That will make the local observable in the debugger as there
+    // is now a breakpoint on the nop. Therefore, the local stays in the output.
+    MethodSubject method = build(FlafDump.dump(true));
+    assertTrue(hasLocal(method));
+  }
+
+  @Test
+  public void testFlafWithoutLineNumberForLocal() throws Exception {
+    // Generate the FlafKt class file where all locals are live only on a nop instruction
+    // with no line number. Therefore, the locals are not observable in the debugger and
+    // are removed.
+    MethodSubject method = build(FlafDump.dump(false));
+    assertFalse(hasLocal(method));
+  }
+
+  @Test
+  public void testFlaf2() throws Exception {
+    MethodSubject method = build(Flaf2Dump.dump());
+    assertFalse(hasLocal(method));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
index 6162b15..c7f8ef9 100644
--- a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
@@ -29,7 +29,7 @@
 
   @Test
   public void testArrays() throws IOException, ExecutionException {
-    Timing timing = new Timing(ArrayTargetLookupTest.class.getCanonicalName());
+    Timing timing = Timing.empty();
     InternalOptions options = new InternalOptions();
     AndroidApp app =
         AndroidApp.builder()
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index e290d51..b473a40 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.resolution.singletarget.Main;
 import com.android.tools.r8.resolution.singletarget.one.AbstractSubClass;
 import com.android.tools.r8.resolution.singletarget.one.AbstractTopClass;
@@ -192,12 +191,6 @@
         });
   }
 
-  public static DexMethod buildNullaryVoidMethod(Class clazz, String name, AppInfo appInfo) {
-    return buildMethod(
-        Reference.method(Reference.classFromClass(clazz), name, Collections.emptyList(), null),
-        appInfo.dexItemFactory());
-  }
-
   private static DexType toType(Class clazz, AppInfo appInfo) {
     return buildType(clazz, appInfo.dexItemFactory());
   }
@@ -209,7 +202,7 @@
 
   @Test
   public void lookupSingleTarget() {
-    DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo);
+    DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo.dexItemFactory());
     Assert.assertNotNull(
         appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
     DexEncodedMethod singleVirtualTarget = appInfo.lookupSingleVirtualTarget(method, method.holder);
@@ -224,7 +217,7 @@
 
   @Test
   public void lookupVirtualTargets() {
-    DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo);
+    DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo.dexItemFactory());
     Assert.assertNotNull(
         appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
index eb4cd97..28b468e 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -61,7 +61,7 @@
   }
 
   private static DexMethod buildMethod(Class clazz, String name) {
-    return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
+    return buildNullaryVoidMethod(clazz, name, appInfo.dexItemFactory());
   }
 
   @Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
index 85ace0b..1da4ff7 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -82,7 +82,7 @@
   }
 
   private static DexMethod buildMethod(Class clazz, String name) {
-    return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
+    return buildNullaryVoidMethod(clazz, name, appInfo.dexItemFactory());
   }
 
   @Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
index c90b6e4..674c090 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
@@ -99,7 +99,7 @@
   }
 
   private static DexMethod buildMethod(Class clazz, String name) {
-    return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
+    return buildNullaryVoidMethod(clazz, name, appInfo.dexItemFactory());
   }
 
   @Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
index f41a60f..f9fb653 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -146,7 +146,7 @@
   }
 
   private static DexMethod buildMethod(Class clazz, String name) {
-    return SingleTargetLookupTest.buildNullaryVoidMethod(clazz, name, appInfo);
+    return buildNullaryVoidMethod(clazz, name, appInfo.dexItemFactory());
   }
 
   @Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
index 2117ed6..005f292 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
@@ -9,11 +9,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.AndroidApp;
 import com.google.common.collect.ImmutableList;
@@ -42,10 +40,10 @@
   @Test
   public void testResolution() throws Exception {
     // The resolution is runtime independent, so just run it on the default CF VM.
-    assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AndroidApp app = readClasses(CLASSES);
     AppInfoWithLiveness appInfo = computeAppViewWithLiveness(app, Main.class).appInfo();
-    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+    DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     // Currently R8 will resolve to L::f as that is the first in the topological search.
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
index a9c79fc..0c3ed4c 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
@@ -9,11 +9,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -42,7 +40,7 @@
   @Test
   public void testResolution() throws Exception {
     // The resolution is runtime independent, so just run it on the default CF VM.
-    assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppInfoWithLiveness appInfo =
         computeAppViewWithLiveness(
                 buildClasses(CLASSES)
@@ -50,7 +48,7 @@
                     .build(),
                 Main.class)
             .appInfo();
-    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+    DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
index a65c188..42f7600 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
@@ -9,11 +9,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -42,7 +40,7 @@
   @Test
   public void testResolution() throws Exception {
     // The resolution is runtime independent, so just run it on the default CF VM.
-    assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppInfoWithLiveness appInfo =
         computeAppViewWithLiveness(
                 buildClasses(CLASSES)
@@ -50,7 +48,7 @@
                     .build(),
                 Main.class)
             .appInfo();
-    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+    DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
index 053fba0..c29509d 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
@@ -10,12 +10,10 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -45,7 +43,7 @@
   @Test
   public void testResolution() throws Exception {
     // The resolution is runtime independent, so just run it on the default CF VM.
-    assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppInfoWithLiveness appInfo =
         computeAppViewWithLiveness(
                 buildClasses(CLASSES)
@@ -53,7 +51,7 @@
                     .build(),
                 Main.class)
             .appInfo();
-    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+    DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
index 5163bc2..e7c5ab8 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
@@ -10,12 +10,10 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -45,7 +43,7 @@
   @Test
   public void testResolution() throws Exception {
     // The resolution is runtime independent, so just run it on the default CF VM.
-    assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppInfoWithLiveness appInfo =
         computeAppViewWithLiveness(
                 buildClasses(CLASSES)
@@ -53,7 +51,7 @@
                     .build(),
                 Main.class)
             .appInfo();
-    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+    DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
index 75f689f..3d7071d 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
@@ -10,10 +10,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -44,7 +42,7 @@
   @Test
   public void testResolution() throws Exception {
     // The resolution is runtime independent, so just run it on the default CF VM.
-    assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppInfoWithLiveness appInfo =
         computeAppViewWithLiveness(
                 buildClasses(CLASSES)
@@ -52,7 +50,7 @@
                     .build(),
                 Main.class)
             .appInfo();
-    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+    DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     Set<String> holders = new HashSet<>();
     resolutionResult
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
index 5772bdc..b5c2018 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
@@ -9,11 +9,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -41,10 +39,10 @@
   @Test
   public void testResolution() throws Exception {
     // The resolution is runtime independent, so just run it on the default CF VM.
-    assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppInfoWithLiveness appInfo =
         computeAppViewWithLiveness(readClasses(CLASSES), Main.class).appInfo();
-    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+    DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
index 50e0f8d..867cfe9 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
@@ -9,11 +9,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -41,10 +39,10 @@
   @Test
   public void testResolution() throws Exception {
     // The resolution is runtime independent, so just run it on the default CF VM.
-    assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppInfoWithLiveness appInfo =
         computeAppViewWithLiveness(readClasses(CLASSES), Main.class).appInfo();
-    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+    DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
index bb81c51..b2a4ce2 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
@@ -10,10 +10,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -45,7 +43,7 @@
   @Test
   public void testResolution() throws Exception {
     // The resolution is runtime independent, so just run it on the default CF VM.
-    assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppInfoWithLiveness appInfo =
         computeAppViewWithLiveness(
                 buildClasses(CLASSES)
@@ -53,7 +51,7 @@
                     .build(),
                 Main.class)
             .appInfo();
-    DexMethod method = SingleTargetLookupTest.buildNullaryVoidMethod(B.class, "f", appInfo);
+    DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     Set<String> holders = new HashSet<>();
     resolutionResult
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
new file mode 100644
index 0000000..d2a1ee6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
@@ -0,0 +1,138 @@
+// 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.resolution.interfacetargets;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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/148168065 */
+@RunWith(Parameterized.class)
+public class DefaultMethodAsOverrideWithLambdaTest extends TestBase {
+
+  private static final String[] EXPECTED = new String[] {"Lambda.foo", "J.bar"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DefaultMethodAsOverrideWithLambdaTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(buildClasses(I.class, A.class, Main.class).build(), Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    Set<String> targets =
+        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected =
+        ImmutableSet.of(A.class.getTypeName() + ".bar", J.class.getTypeName() + ".bar");
+    // TODO(b/148168065): Correct incorrect target lookup.
+    // assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addInnerClasses(DefaultMethodAsOverrideWithLambdaTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(DefaultMethodAsOverrideWithLambdaTest.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
+    // TODO(b/148168065): Correct incorrect target lookup.
+    //    .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @FunctionalInterface
+  @NeverMerge
+  public interface I {
+    void foo();
+
+    default void bar() {
+      System.out.println("I.bar");
+    }
+  }
+
+  @NeverMerge
+  public interface J extends I {
+
+    @Override
+    default void bar() {
+      System.out.println("J.bar");
+    }
+  }
+
+  @NeverClassInline
+  public static class A implements I {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("A.foo");
+    }
+
+    @Override
+    public void bar() {
+      System.out.println("A.bar");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      if (args.length == 0) {
+        callI((J) () -> System.out.println("Lambda.foo"));
+      } else {
+        callI(new A());
+      }
+    }
+
+    private static void callI(I i) {
+      i.foo();
+      i.bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
new file mode 100644
index 0000000..37f346b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
@@ -0,0 +1,125 @@
+// 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.resolution.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 DefaultMethodLambdaTest extends TestBase {
+
+  private static final String[] EXPECTED = new String[] {"Lambda.foo", "I.bar"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DefaultMethodLambdaTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(buildClasses(I.class, A.class, Main.class).build(), Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    Set<String> targets =
+        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected =
+        ImmutableSet.of(A.class.getTypeName() + ".bar", I.class.getTypeName() + ".bar");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addInnerClasses(DefaultMethodLambdaTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(DefaultMethodLambdaTest.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @FunctionalInterface
+  @NeverMerge
+  public interface I {
+    void foo();
+
+    default void bar() {
+      System.out.println("I.bar");
+    }
+  }
+
+  @NeverClassInline
+  public static class A implements I {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("A.foo");
+    }
+
+    @Override
+    public void bar() {
+      System.out.println("A.bar");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      if (args.length == 0) {
+        callI(() -> System.out.println("Lambda.foo"));
+      } else {
+        callI(new A());
+      }
+    }
+
+    private static void callI(I i) {
+      i.foo();
+      i.bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
new file mode 100644
index 0000000..3a455cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
@@ -0,0 +1,135 @@
+// 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.resolution.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 LambdaMultipleInterfacesTest extends TestBase {
+
+  private static final String[] EXPECTED = new String[] {"Lambda.foo", "J.bar"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public LambdaMultipleInterfacesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(
+                buildClasses(I.class, J.class, A.class, Main.class).build(), Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(J.class, "bar", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    Set<String> targets =
+        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected =
+        ImmutableSet.of(A.class.getTypeName() + ".bar", J.class.getTypeName() + ".bar");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addInnerClasses(LambdaMultipleInterfacesTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(LambdaMultipleInterfacesTest.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @FunctionalInterface
+  @NeverMerge
+  public interface I {
+    void foo();
+  }
+
+  public interface J {
+    default void bar() {
+      System.out.println("J.bar");
+    }
+  }
+
+  @NeverClassInline
+  public static class A implements I, J {
+
+    @Override
+    public void foo() {
+      System.out.println("A.foo");
+    }
+
+    @Override
+    public void bar() {
+      System.out.println("A.bar");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      I i;
+      if (args.length == 0) {
+        i =
+            (I & J)
+                () -> {
+                  System.out.println("Lambda.foo");
+                };
+      } else {
+        i = new A();
+      }
+      callIJ(i);
+    }
+
+    @NeverInline
+    private static void callIJ(I i) {
+      i.foo();
+      ((I & J) i).bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
new file mode 100644
index 0000000..8b96a84
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
@@ -0,0 +1,130 @@
+// 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.resolution.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 MultipleImplementsTest extends TestBase {
+
+  private static final String[] EXPECTED = new String[] {"B.foo"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MultipleImplementsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(
+                buildClasses(I.class, A.class, B.class, C.class, Main.class).build(), Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    Set<String> targets =
+        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected =
+        ImmutableSet.of(B.class.getTypeName() + ".foo", C.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addInnerClasses(MultipleImplementsTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(MultipleImplementsTest.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @NeverMerge
+  public interface I {
+    void foo();
+  }
+
+  @NeverMerge
+  public interface J {
+    void foo();
+  }
+
+  @NeverMerge
+  public abstract static class A implements I, J {}
+
+  @NeverClassInline
+  public static class B extends A {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("B.foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class C extends A {
+
+    @Override
+    public void foo() {
+      System.out.println("C.foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A a = args.length == 0 ? new B() : new C();
+      callJ(a);
+    }
+
+    @NeverInline
+    private static void callJ(J j) {
+      j.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
new file mode 100644
index 0000000..60ed217
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
@@ -0,0 +1,125 @@
+// 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.resolution.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 SimpleInterfaceInvokeTest extends TestBase {
+
+  private static final String[] EXPECTED = new String[] {"A.foo", "B.foo"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SimpleInterfaceInvokeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    // The resolution is runtime independent, so just run it on the default CF VM.
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(
+                buildClasses(I.class, A.class, B.class, Main.class).build(), Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    Set<String> targets =
+        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected =
+        ImmutableSet.of(A.class.getTypeName() + ".foo", B.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addInnerClasses(SimpleInterfaceInvokeTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(SimpleInterfaceInvokeTest.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @NeverMerge
+  public interface I {
+    void foo();
+  }
+
+  @NeverClassInline
+  public static class A implements I {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("A.foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class B implements I {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("B.foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      I a = args.length == 0 ? new A() : new B();
+      I b = args.length != 0 ? new A() : new B();
+      invokeI(a);
+      invokeI(b);
+    }
+
+    private static void invokeI(I i) {
+      i.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
new file mode 100644
index 0000000..4986840
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
@@ -0,0 +1,138 @@
+// 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.resolution.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 SubInterfaceOverridesTest extends TestBase {
+
+  private static final String[] EXPECTED = new String[] {"A.foo"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SubInterfaceOverridesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(
+                buildClasses(I.class, J.class, A.class, B.class, C.class, Main.class).build(),
+                Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    Set<String> targets =
+        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected =
+        ImmutableSet.of(
+            J.class.getTypeName() + ".foo",
+            A.class.getTypeName() + ".foo",
+            C.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addInnerClasses(SubInterfaceOverridesTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(SubInterfaceOverridesTest.class)
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @NeverMerge
+  public interface I {
+
+    void foo();
+  }
+
+  @NeverMerge
+  public interface J extends I {
+
+    @Override
+    default void foo() {
+      System.out.println("J.foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class A implements I {
+
+    @Override
+    public void foo() {
+      System.out.println("A.foo");
+      ;
+    }
+  }
+
+  @NeverClassInline
+  public static class B implements J {}
+
+  @NeverClassInline
+  public static class C implements J {
+
+    @Override
+    public void foo() {
+      System.out.println("C.foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      I i;
+      if (args.length == 0) {
+        i = new A();
+      } else {
+        i = new B();
+      }
+      i.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
new file mode 100644
index 0000000..3ccb04b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
@@ -0,0 +1,124 @@
+// 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.resolution.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 SubTypeMissingOverridesTest extends TestBase {
+
+  private static final String[] EXPECTED = new String[] {"B.foo"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SubTypeMissingOverridesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(
+                buildClasses(I.class, A.class, B.class, C.class, Main.class).build(), Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    Set<String> targets =
+        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected =
+        ImmutableSet.of(B.class.getTypeName() + ".foo", C.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addInnerClasses(SubTypeMissingOverridesTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(SubTypeMissingOverridesTest.class)
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @NeverMerge
+  public interface I {
+
+    void foo();
+  }
+
+  @NeverClassInline
+  public abstract static class A implements I {}
+
+  @NeverClassInline
+  public static class B extends A {
+
+    @Override
+    public void foo() {
+      System.out.println("B.foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class C extends A {
+
+    @Override
+    public void foo() {
+      System.out.println("C.foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      I i;
+      if (args.length == 0) {
+        i = new B();
+      } else {
+        i = new C();
+      }
+      i.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
new file mode 100644
index 0000000..4e4a494
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
@@ -0,0 +1,122 @@
+// 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.resolution.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+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 SubTypeOverridesTest extends TestBase {
+
+  private static final String[] EXPECTED = new String[] {"A.foo"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SubTypeOverridesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(
+                buildClasses(I.class, A.class, B.class, Main.class).build(), Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    Set<String> targets =
+        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected =
+        ImmutableSet.of(A.class.getTypeName() + ".foo", B.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addInnerClasses(SubTypeOverridesTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(SubTypeOverridesTest.class)
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @NeverMerge
+  public interface I {
+
+    void foo();
+  }
+
+  @NeverClassInline
+  public static class A implements I {
+
+    @Override
+    public void foo() {
+      System.out.println("A.foo");
+      ;
+    }
+  }
+
+  @NeverClassInline
+  public static class B extends A {
+
+    @Override
+    public void foo() {
+      System.out.println("B.foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      I i;
+      if (args.length == 0) {
+        i = new A();
+      } else {
+        i = new B();
+      }
+      i.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index e29c928..cfc4fba 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
@@ -18,6 +19,7 @@
 import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTrace;
 import com.android.tools.r8.retrace.stacktraces.AmbiguousMissingLineStackTrace;
 import com.android.tools.r8.retrace.stacktraces.AmbiguousStackTrace;
+import com.android.tools.r8.retrace.stacktraces.CircularReferenceStackTrace;
 import com.android.tools.r8.retrace.stacktraces.FileNameExtensionStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineNoLineNumberStackTrace;
@@ -25,6 +27,7 @@
 import com.android.tools.r8.retrace.stacktraces.InvalidStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NullStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ObfucatedExceptionClassStackTrace;
+import com.android.tools.r8.retrace.stacktraces.ObfuscatedRangeToSingleLineStackTrace;
 import com.android.tools.r8.retrace.stacktraces.RetraceAssertionErrorStackTrace;
 import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
 import com.android.tools.r8.retrace.stacktraces.SuppressedStackTrace;
@@ -149,6 +152,19 @@
     runRetraceTest(new InlineNoLineNumberStackTrace());
   }
 
+  @Test
+  public void testCircularReferenceStackTrace() {
+    // Proguard retrace (and therefore the default regular expression) will not retrace circular
+    // reference exceptions.
+    assumeFalse(useRegExpParsing);
+    runRetraceTest(new CircularReferenceStackTrace());
+  }
+
+  @Test
+  public void testObfuscatedRangeToSingleLine() {
+    runRetraceTest(new ObfuscatedRangeToSingleLineStackTrace());
+  }
+
   private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     RetraceCommand retraceCommand =
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/CircularReferenceStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/CircularReferenceStackTrace.java
new file mode 100644
index 0000000..5df37f8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/CircularReferenceStackTrace.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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class CircularReferenceStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "        [CIRCULAR REFERENCE:A.A]",
+        " [CIRCULAR REFERENCE:A.B]",
+        "        [CIRCULAR REFERENCE:None.existing.class]",
+        "        [CIRCULAR REFERENCE:A.A] ",
+        // Invalid Circular Reference lines.
+        "        [CIRCU:AA]",
+        "        [CIRCULAR REFERENCE:A.A",
+        "        [CIRCULAR REFERENCE:]",
+        "        [CIRCULAR REFERENCE:None existing class]");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines("foo.bar.Baz -> A.A:", "foo.bar.Qux -> A.B:");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "        [CIRCULAR REFERENCE:foo.bar.Baz]",
+        " [CIRCULAR REFERENCE:foo.bar.Qux]",
+        "        [CIRCULAR REFERENCE:None.existing.class]",
+        "        [CIRCULAR REFERENCE:foo.bar.Baz] ",
+        "        [CIRCU:AA]",
+        "        [CIRCULAR REFERENCE:A.A",
+        "        [CIRCULAR REFERENCE:]",
+        "        [CIRCULAR REFERENCE:None existing class]");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 5;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ObfuscatedRangeToSingleLineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ObfuscatedRangeToSingleLineStackTrace.java
new file mode 100644
index 0000000..1579756
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ObfuscatedRangeToSingleLineStackTrace.java
@@ -0,0 +1,46 @@
+// 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class ObfuscatedRangeToSingleLineStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "UnknownException: This is just a fake exception",
+        "  at a.a(:8)",
+        "  at a.a(:13)",
+        "  at a.b(:1399)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "foo.bar.Baz -> a:",
+        "  1:10:void qux():27:27 -> a",
+        "  11:15:void qux():42 -> a",
+        "  1337:1400:void foo.bar.Baz.quux():113:113 -> b",
+        "  1337:1400:void quuz():72 -> b");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "UnknownException: This is just a fake exception",
+        "  at foo.bar.Baz.qux(Baz.java:27)",
+        "  at foo.bar.Baz.qux(Baz.java:42)",
+        "  at foo.bar.Baz.quux(Baz.java:113)",
+        "  at foo.bar.Baz.quuz(Baz.java:72)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index 06ccf5a..2d85c6b 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -76,9 +76,7 @@
     options.programConsumer = DexIndexedConsumer.emptyConsumer();
 
     DexApplication application =
-        new ApplicationReader(
-                originalApplication, options, new Timing("CatchSuccessorFallthroughTest"))
-            .read();
+        new ApplicationReader(originalApplication, options, Timing.empty()).read();
 
     DexEncodedMethod method = getMethod(originalApplication, methodSig);
     // Get the IR pre-optimization.
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index c528eb0..8ecb463 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -63,7 +63,7 @@
   protected DexApplication buildApplication(AndroidApp input, InternalOptions options) {
     try {
       options.itemFactory.resetSortedIndices();
-      return new ApplicationReader(input, options, new Timing("SmaliTest")).read();
+      return new ApplicationReader(input, options, Timing.empty()).read();
     } catch (IOException | ExecutionException e) {
       throw new RuntimeException(e);
     }
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 598f6f1..0c2358a 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 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.MethodAccessFlags;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.references.ClassReference;
@@ -284,6 +285,10 @@
         });
   }
 
+  public ClassFileTransformer setSynthetic(Method method) {
+    return setAccessFlags(method, AccessFlags::setSynthetic);
+  }
+
   public ClassFileTransformer setAccessFlags(Method method, Consumer<MethodAccessFlags> setter) {
     return addClassTransformer(
         new ClassTransformer() {
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index 0092be2..02aa682 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -109,8 +109,7 @@
     options.programConsumer = consumer;
     ExecutorService executor = ThreadUtils.getExecutorService(1);
     try {
-      DexApplication dexApp = new ApplicationReader(
-          app, options, new Timing("smali")).read(executor);
+      DexApplication dexApp = new ApplicationReader(app, options, Timing.empty()).read(executor);
       ApplicationWriter writer =
           new ApplicationWriter(
               dexApp,
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 6a2f504..1a640ed 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
@@ -97,4 +97,14 @@
   public List<ClassSubject> getSuperTypes() {
     return null;
   }
+
+  @Override
+  public List<String> getSealedSubclassDescriptors() {
+    return null;
+  }
+
+  @Override
+  public List<ClassSubject> getSealedSubclasses() {
+    return null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 1a4dcc16..5e1e668 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -102,7 +103,7 @@
       originalToObfuscatedMapping = null;
       obfuscatedToOriginalMapping = null;
     }
-    Timing timing = new Timing("CodeInspector");
+    Timing timing = Timing.empty();
     InternalOptions options = runOptionsConsumer(optionsConsumer);
     dexItemFactory = options.itemFactory;
     AndroidApp input = AndroidApp.builder().addProgramFiles(files).build();
@@ -111,14 +112,14 @@
 
   public CodeInspector(AndroidApp app) throws IOException, ExecutionException {
     this(
-        new ApplicationReader(app, runOptionsConsumer(null), new Timing("CodeInspector"))
+        new ApplicationReader(app, runOptionsConsumer(null), Timing.empty())
             .read(app.getProguardMapOutputData()));
   }
 
   public CodeInspector(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
       throws IOException, ExecutionException {
     this(
-        new ApplicationReader(app, runOptionsConsumer(optionsConsumer), new Timing("CodeInspector"))
+        new ApplicationReader(app, runOptionsConsumer(optionsConsumer), Timing.empty())
             .read(app.getProguardMapOutputData()));
   }
 
@@ -133,14 +134,14 @@
   public CodeInspector(AndroidApp app, Path proguardMapFile)
       throws IOException, ExecutionException {
     this(
-        new ApplicationReader(app, runOptionsConsumer(null), new Timing("CodeInspector"))
+        new ApplicationReader(app, runOptionsConsumer(null), Timing.empty())
             .read(StringResource.fromFile(proguardMapFile)));
   }
 
   public CodeInspector(AndroidApp app, String proguardMapContent)
       throws IOException, ExecutionException {
     this(
-        new ApplicationReader(app, runOptionsConsumer(null), new Timing("CodeInspector"))
+        new ApplicationReader(app, runOptionsConsumer(null), Timing.empty())
             .read(StringResource.fromString(proguardMapContent, Origin.unknown())));
   }
 
@@ -251,7 +252,7 @@
     return rewriter.getSignature();
   }
 
-  public ClassSubject clazz(Class clazz) {
+  public ClassSubject clazz(Class<?> clazz) {
     return clazz(Reference.classFromClass(clazz));
   }
 
@@ -284,6 +285,12 @@
     return new FoundClassSubject(this, clazz, naming);
   }
 
+  public ClassSubject companionClassFor(Class<?> clazz) {
+    return clazz(
+        Reference.classFromTypeName(
+            clazz.getTypeName() + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX));
+  }
+
   public void forAllClasses(Consumer<FoundClassSubject> inspection) {
     forAll(
         application.classes(),
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 e60f9f3..6c65c2d 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
@@ -4,6 +4,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.utils.DescriptorUtils;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
@@ -69,4 +70,19 @@
         .filter(ClassSubject::isPresent)
         .collect(Collectors.toList());
   }
+
+  @Override
+  public List<String> getSealedSubclassDescriptors() {
+    return kmClass.getSealedSubclasses().stream()
+        .map(DescriptorUtils::getDescriptorFromKotlinClassifier)
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  public List<ClassSubject> getSealedSubclasses() {
+    return kmClass.getSealedSubclasses().stream()
+        .map(DescriptorUtils::getDescriptorFromKotlinClassifier)
+        .map(this::getClassSubjectFromDescriptor)
+        .collect(Collectors.toList());
+  }
 }
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 34bc771..9fece50 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
@@ -140,12 +140,16 @@
         .collect(Collectors.toList());
   }
 
+  default ClassSubject getClassSubjectFromDescriptor(String descriptor) {
+    return codeInspector().clazz(Reference.classFromDescriptor(descriptor));
+  }
+
   default ClassSubject getClassSubjectFromKmType(KmType kmType) {
     String descriptor = getDescriptorFromKmType(kmType);
     if (descriptor == null) {
       return new AbsentClassSubject();
     }
-    return codeInspector().clazz(Reference.classFromDescriptor(descriptor));
+    return getClassSubjectFromDescriptor(descriptor);
   }
 
   @Override
@@ -167,7 +171,7 @@
   }
 
   // TODO(b/145824437): This is a dup of KotlinMetadataJvmExtensionUtils$KmPropertyProcessor
-  static class KmPropertyProcessor {
+  class KmPropertyProcessor {
     JvmFieldSignature fieldSignature = null;
     // Custom getter via @get:JvmName("..."). Otherwise, null.
     JvmMethodSignature getterSignature = null;
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 d36f233..97b233d 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
@@ -12,4 +12,8 @@
   public abstract List<String> getSuperTypeDescriptors();
 
   public abstract List<ClassSubject> getSuperTypes();
+
+  public abstract List<String> getSealedSubclassDescriptors();
+
+  public abstract List<ClassSubject> getSealedSubclasses();
 }
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 182ccf8..81ccd98 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -334,8 +334,14 @@
       log('Running with hash: %s' % git_hash)
       exitcode = run_once(archive=True)
       log('Running finished with exit code %s' % exitcode)
-      put_magic_file(TESTING_COMPLETE, git_hash)
-      delete_magic_file(TESTING)
+      # If the bot timed out or something else triggered the bot to fail, don't
+      # put up the result (it will not be displayed anywhere, and we can't
+      # remove the magic file if the bot cleaned up).
+      if get_magic_file_exists(TESTING):
+        put_magic_file(TESTING_COMPLETE, git_hash)
+        # There is still a potential race here (we check, bot deletes, we try to
+        # delete) - this is unlikely and we ignore it (restart if it happens).
+        delete_magic_file(TESTING)
     time.sleep(PULL_DELAY)
 
 def handle_output(archive, stderr, stdout, exitcode, timed_out, cmd):