Remove ClassInitializerInfo

This removes the need for ClassInitializerInfo by leveraging the fact that we already compute dynamic lower bound type information for static final singleton fields.

A prerequisite for class inlining singleton fields is that the corresponding static-get instruction cannot trigger observable side effects. Therefore, this CL optimizes the methods of synthesized lambda classes and publishes the gathered optimization feedback before starting a wave. This ensures that we will mark the class initializers of lambdas as not having observable side effects, such that we can class inline the INSTANCE fields of these lambdas.

(This was not previously needed because the class inliner implicitly assumed that these class initializers did not have observable side effects.)

Bug: 145642327, 120814598
Change-Id: If72c86442c69d5d1d23fead04dc69768aaa7e189
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 76b7195..270e91e 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -88,6 +88,7 @@
     assert checkIfObsolete();
     assert clazz.type.isD8R8SynthesizedClassType();
     DexProgramClass previous = synthesizedClasses.put(clazz.type, clazz);
+    invalidateTypeCacheFor(clazz.type);
     assert previous == null || previous == clazz;
   }
 
@@ -98,7 +99,7 @@
 
   private Map<Descriptor<?,?>, KeyedDexItem<?>> computeDefinitions(DexType type) {
     Builder<Descriptor<?,?>, KeyedDexItem<?>> builder = ImmutableMap.builder();
-    DexClass clazz = app.definitionFor(type);
+    DexClass clazz = definitionFor(type);
     if (clazz != null) {
       clazz.forEachMethod(method -> builder.put(method.getKey(), method));
       clazz.forEachField(field -> builder.put(field.getKey(), field));
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 78ba461..358af04 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -968,6 +968,21 @@
     return getKotlinInfo() != null;
   }
 
+  public boolean hasInstanceFields() {
+    return instanceFields.length > 0;
+  }
+
+  public boolean hasInstanceFieldsDirectlyOrIndirectly(AppView<?> appView) {
+    if (superType == null || type == appView.dexItemFactory().objectType) {
+      return false;
+    }
+    if (hasInstanceFields()) {
+      return true;
+    }
+    DexClass superClass = appView.definitionFor(superType);
+    return superClass == null || superClass.hasInstanceFieldsDirectlyOrIndirectly(appView);
+  }
+
   public boolean isValid(InternalOptions options) {
     assert verifyNoAbstractMethodsOnNonAbstractClasses(virtualMethods(), options);
     assert !isInterface() || virtualMethods().stream().noneMatch(DexEncodedMethod::isFinal);
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index e3d78cf..cc39610 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -246,6 +246,11 @@
     return isDoubleType() || isLongType();
   }
 
+  public boolean isD8R8SynthesizedLambdaClassType() {
+    String name = toSourceString();
+    return name.contains(LAMBDA_CLASS_NAME_PREFIX);
+  }
+
   public boolean isD8R8SynthesizedClassType() {
     String name = toSourceString();
     return name.contains(COMPANION_CLASS_NAME_SUFFIX)
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index 0605623..9fcee4a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -4,9 +4,12 @@
 
 package com.android.tools.r8.ir.analysis;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 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.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.IRCode;
@@ -16,6 +19,7 @@
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
+import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
@@ -284,6 +288,12 @@
       return false;
     }
 
+    NewInstance newInstance = value.definition.asNewInstance();
+    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(newInstance.clazz));
+    if (clazz == null) {
+      return false;
+    }
+
     // Find the single constructor invocation.
     InvokeMethod constructorInvoke = null;
     for (Instruction instruction : value.uniqueUsers()) {
@@ -319,24 +329,26 @@
       return false;
     }
 
-    InstanceInitializerInfo initializerInfo =
-        constructor.getOptimizationInfo().getInstanceInitializerInfo();
-    if (initializerInfo.instanceFieldInitializationMayDependOnEnvironment()) {
-      return false;
-    }
-
-    // Check that none of the arguments to the constructor depend on the environment.
-    for (int i = 1; i < constructorInvoke.arguments().size(); i++) {
-      Value argument = constructorInvoke.arguments().get(i);
-      if (valueMayDependOnEnvironment(argument, assumedNotToDependOnEnvironment)) {
+    if (clazz.hasInstanceFieldsDirectlyOrIndirectly(appView)) {
+      InstanceInitializerInfo initializerInfo =
+          constructor.getOptimizationInfo().getInstanceInitializerInfo();
+      if (initializerInfo.instanceFieldInitializationMayDependOnEnvironment()) {
         return false;
       }
-    }
 
-    // Finally, check that the object does not escape.
-    if (valueMayBeMutatedBeforeMethodExit(
-        value, assumedNotToDependOnEnvironment, ImmutableSet.of(constructorInvoke))) {
-      return false;
+      // Check that none of the arguments to the constructor depend on the environment.
+      for (int i = 1; i < constructorInvoke.arguments().size(); i++) {
+        Value argument = constructorInvoke.arguments().get(i);
+        if (valueMayDependOnEnvironment(argument, assumedNotToDependOnEnvironment)) {
+          return false;
+        }
+      }
+
+      // Finally, check that the object does not escape.
+      if (valueMayBeMutatedBeforeMethodExit(
+          value, assumedNotToDependOnEnvironment, ImmutableSet.of(constructorInvoke))) {
+        return false;
+      }
     }
 
     if (assumedNotToDependOnEnvironment.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index c4f9072..06f1f58 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -9,8 +9,12 @@
 import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator.CycleEliminationResult;
 import com.android.tools.r8.ir.conversion.CallSiteInformation.CallGraphBasedCallSiteInformation;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
+import java.util.Iterator;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /**
  * Call graph representation.
@@ -172,4 +176,32 @@
         ? new CallGraphBasedCallSiteInformation(appView, this)
         : CallSiteInformation.empty();
   }
+
+  public boolean isEmpty() {
+    return nodes.isEmpty();
+  }
+
+  public Set<DexEncodedMethod> extractLeaves() {
+    return extractNodes(Node::isLeaf, Node::cleanCallersForRemoval);
+  }
+
+  public Set<DexEncodedMethod> extractRoots() {
+    return extractNodes(Node::isRoot, Node::cleanCalleesForRemoval);
+  }
+
+  private Set<DexEncodedMethod> extractNodes(Predicate<Node> predicate, Consumer<Node> clean) {
+    Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+    Set<Node> removed = Sets.newIdentityHashSet();
+    Iterator<Node> nodeIterator = nodes.iterator();
+    while (nodeIterator.hasNext()) {
+      Node node = nodeIterator.next();
+      if (predicate.test(node)) {
+        result.add(node.method);
+        nodeIterator.remove();
+        removed.add(node);
+      }
+    }
+    removed.forEach(clean);
+    return result;
+  }
 }
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 9f5a62a..9ccc4c2 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
@@ -807,11 +807,13 @@
     return builder.build();
   }
 
-  private void waveStart(Collection<DexEncodedMethod> wave) {
+  private void waveStart(Collection<DexEncodedMethod> wave, ExecutorService executorService)
+      throws ExecutionException {
     onWaveDoneActions = Collections.synchronizedList(new ArrayList<>());
 
     if (lambdaRewriter != null) {
-      wave.forEach(method -> lambdaRewriter.synthesizeLambdaClassesFor(method, lensCodeRewriter));
+      lambdaRewriter.synthesizeLambdaClassesForWave(
+          wave, executorService, delayedOptimizationFeedback, lensCodeRewriter);
     }
   }
 
@@ -975,10 +977,25 @@
     for (DexProgramClass clazz : classes) {
       clazz.forEachMethod(methods::add);
     }
-    // Process the generated class, but don't apply any outlining.
     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.
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
new file mode 100644
index 0000000..a6a9b94
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LambdaMethodProcessor.java
@@ -0,0 +1,69 @@
+// 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/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 0f6bd4f..7d4a203 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
 import java.util.Set;
@@ -59,7 +59,8 @@
 
   void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility);
 
-  void setInitializerInfo(DexEncodedMethod method, InitializerInfo info);
+  void setInstanceInitializerInfo(
+      DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo);
 
   void setInitializerEnablingJavaAssertions(DexEncodedMethod method);
 
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 b2bf648..d6e2ff3 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,6 +9,7 @@
 
   enum Phase {
     ONE_TIME,
+    LAMBDA_PROCESSING,
     PRIMARY,
     POST
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 5107b18..9ecc62d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.logging.Log;
@@ -17,17 +16,14 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
-import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
 
 class PostMethodProcessor implements MethodProcessor {
 
@@ -106,11 +102,9 @@
     IROrdering shuffle = appView.options().testing.irOrdering;
     Deque<Collection<DexEncodedMethod>> waves = new ArrayDeque<>();
 
-    Set<Node> nodes = callGraph.nodes;
     int waveCount = 1;
-    while (!nodes.isEmpty()) {
-      Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
-      extractRoots(nodes, n -> wave.add(n.method));
+    while (!callGraph.isEmpty()) {
+      Set<DexEncodedMethod> wave = callGraph.extractRoots();
       waves.addLast(shuffle.order(wave));
       if (Log.ENABLED && Log.isLoggingEnabledFor(PostMethodProcessor.class)) {
         Log.info(getClass(), "Wave #%d: %d", waveCount++, wave.size());
@@ -120,25 +114,6 @@
     return waves;
   }
 
-  /**
-   * Extract the next set of roots (nodes with an incoming call degree of 0) if any.
-   *
-   * <p>All nodes in the graph are extracted if called repeatedly until null is returned.
-   */
-  static void extractRoots(Set<Node> nodes, Consumer<Node> fn) {
-    Set<Node> removed = Sets.newIdentityHashSet();
-    Iterator<Node> nodeIterator = nodes.iterator();
-    while (nodeIterator.hasNext()) {
-      Node node = nodeIterator.next();
-      if (node.isRoot()) {
-        fn.accept(node);
-        nodeIterator.remove();
-        removed.add(node);
-      }
-    }
-    removed.forEach(Node::cleanCalleesForRemoval);
-  }
-
   @Override
   public boolean isProcessedConcurrently(DexEncodedMethod method) {
     return wave != null && wave.contains(method);
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 1787587..be529f4 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
@@ -31,6 +31,12 @@
  */
 class PrimaryMethodProcessor implements MethodProcessor {
 
+  interface WaveStartAction {
+
+    void notifyWaveStart(Collection<DexEncodedMethod> wave, ExecutorService executorService)
+        throws ExecutionException;
+  }
+
   private final CallSiteInformation callSiteInformation;
   private final PostMethodProcessor.Builder postMethodProcessorBuilder;
   private final Deque<Collection<DexEncodedMethod>> waves;
@@ -130,14 +136,14 @@
    */
   <E extends Exception> void forEachMethod(
       ThrowingConsumer<DexEncodedMethod, E> consumer,
-      Consumer<Collection<DexEncodedMethod>> waveStart,
+      WaveStartAction waveStartAction,
       Action waveDone,
       ExecutorService executorService)
       throws ExecutionException {
     while (!waves.isEmpty()) {
       wave = waves.removeFirst();
       assert wave.size() > 0;
-      waveStart.accept(wave);
+      waveStartAction.notifyWaveStart(wave, executorService);
       ThreadUtils.processItems(wave, consumer, executorService);
       waveDone.execute();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSynthesizedCode.java
new file mode 100644
index 0000000..6e109dd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSynthesizedCode.java
@@ -0,0 +1,43 @@
+// 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.desugar;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.function.Consumer;
+
+class LambdaBridgeMethodSynthesizedCode extends LambdaSynthesizedCode {
+
+  private final DexMethod mainMethod;
+  private final DexMethod bridgeMethod;
+
+  LambdaBridgeMethodSynthesizedCode(
+      LambdaClass lambda, DexMethod mainMethod, DexMethod bridgeMethod) {
+    super(lambda);
+    this.mainMethod = mainMethod;
+    this.bridgeMethod = bridgeMethod;
+  }
+
+  @Override
+  public SourceCodeProvider getSourceCodeProvider() {
+    return callerPosition ->
+        new LambdaBridgeMethodSourceCode(lambda, mainMethod, bridgeMethod, callerPosition);
+  }
+
+  @Override
+  public Consumer<UseRegistry> getRegistryCallback() {
+    return registry -> {
+      registry.registerInvokeVirtual(mainMethod);
+
+      DexType bridgeMethodReturnType = bridgeMethod.proto.returnType;
+      if (!bridgeMethodReturnType.isVoidType()
+          && bridgeMethodReturnType != mainMethod.proto.returnType
+          && bridgeMethodReturnType != dexItemFactory().objectType) {
+        registry.registerCheckCast(bridgeMethodReturnType);
+      }
+    };
+  }
+}
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 025ecb0..28a70b2 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
@@ -156,9 +156,7 @@
             synthesizeVirtualMethods(mainMethod),
             rewriter.factory.getSkipNameValidationForTesting(),
             LambdaClass::computeChecksumForSynthesizedClass);
-    // Optimize main method.
     rewriter.converter.appView.appInfo().addSynthesizedClass(clazz);
-    rewriter.converter.optimizeSynthesizedMethod(clazz.lookupVirtualMethod(mainMethod));
 
     // The method addSynthesizedFrom() may be called concurrently. To avoid a Concurrent-
     // ModificationException we must use synchronization.
@@ -219,9 +217,7 @@
                 Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
-            new SynthesizedCode(
-                callerPosition ->
-                    new LambdaMainMethodSourceCode(this, mainMethod, callerPosition)));
+            new LambdaMainMethodSynthesizedCode(this, mainMethod));
 
     // Synthesize bridge methods.
     for (DexProto bridgeProto : descriptor.bridges) {
@@ -237,10 +233,7 @@
                   false),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
-              new SynthesizedCode(
-                  callerPosition ->
-                      new LambdaBridgeMethodSourceCode(
-                          this, mainMethod, bridgeMethod, callerPosition)));
+              new LambdaBridgeMethodSynthesizedCode(this, mainMethod, bridgeMethod));
     }
     return methods;
   }
@@ -260,8 +253,7 @@
                 true),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
-            new SynthesizedCode(
-                callerPosition -> new LambdaConstructorSourceCode(this, callerPosition)));
+            new LambdaConstructorSynthesizedCode(this));
 
     // Class constructor for stateless lambda classes.
     if (stateless) {
@@ -272,8 +264,7 @@
                   Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, true),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
-              new SynthesizedCode(
-                  callerPosition -> new LambdaClassConstructorSourceCode(this, callerPosition)));
+              new LambdaClassConstructorSynthesizedCode(this));
     }
     return methods;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSynthesizedCode.java
new file mode 100644
index 0000000..2dbac91
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSynthesizedCode.java
@@ -0,0 +1,29 @@
+// 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.desugar;
+
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.function.Consumer;
+
+class LambdaClassConstructorSynthesizedCode extends LambdaSynthesizedCode {
+
+  LambdaClassConstructorSynthesizedCode(LambdaClass lambda) {
+    super(lambda);
+  }
+
+  @Override
+  public SourceCodeProvider getSourceCodeProvider() {
+    return callerPosition -> new LambdaClassConstructorSourceCode(lambda, callerPosition);
+  }
+
+  @Override
+  public Consumer<UseRegistry> getRegistryCallback() {
+    return registry -> {
+      registry.registerNewInstance(lambda.type);
+      registry.registerInvokeDirect(lambda.constructor);
+      registry.registerStaticFieldWrite(lambda.lambdaField);
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java
new file mode 100644
index 0000000..29fb5c8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java
@@ -0,0 +1,32 @@
+// 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.desugar;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.function.Consumer;
+
+class LambdaConstructorSynthesizedCode extends LambdaSynthesizedCode {
+
+  LambdaConstructorSynthesizedCode(LambdaClass lambda) {
+    super(lambda);
+  }
+
+  @Override
+  public SourceCodeProvider getSourceCodeProvider() {
+    return callerPosition -> new LambdaConstructorSourceCode(lambda, callerPosition);
+  }
+
+  @Override
+  public Consumer<UseRegistry> getRegistryCallback() {
+    return registry -> {
+      registry.registerInvokeDirect(lambda.rewriter.objectInitMethod);
+      DexType[] capturedTypes = captures();
+      for (int i = 0; i < capturedTypes.length; i++) {
+        registry.registerInstanceFieldWrite(lambda.getCaptureField(i));
+      }
+    };
+  }
+}
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
new file mode 100644
index 0000000..3e7e3f5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java
@@ -0,0 +1,62 @@
+// 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.desugar;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.Invoke;
+import java.util.function.Consumer;
+
+class LambdaMainMethodSynthesizedCode extends LambdaSynthesizedCode {
+
+  private final DexMethod mainMethod;
+
+  LambdaMainMethodSynthesizedCode(LambdaClass lambda, DexMethod mainMethod) {
+    super(lambda);
+    this.mainMethod = mainMethod;
+  }
+
+  @Override
+  public SourceCodeProvider getSourceCodeProvider() {
+    return callerPosition -> new LambdaMainMethodSourceCode(lambda, mainMethod, callerPosition);
+  }
+
+  @Override
+  public Consumer<UseRegistry> getRegistryCallback() {
+    return registry -> {
+      LambdaClass.Target target = lambda.target;
+      assert target.invokeType == Invoke.Type.STATIC
+          || target.invokeType == Invoke.Type.VIRTUAL
+          || target.invokeType == Invoke.Type.DIRECT
+          || target.invokeType == Invoke.Type.INTERFACE;
+
+      registry.registerNewInstance(target.callTarget.holder);
+
+      DexType[] capturedTypes = captures();
+      for (int i = 0; i < capturedTypes.length; i++) {
+        registry.registerInstanceFieldRead(lambda.getCaptureField(i));
+      }
+
+      switch (target.invokeType) {
+        case DIRECT:
+          registry.registerInvokeDirect(target.callTarget);
+          break;
+        case INTERFACE:
+          registry.registerInvokeInterface(target.callTarget);
+          break;
+        case STATIC:
+          registry.registerInvokeStatic(target.callTarget);
+          break;
+        case VIRTUAL:
+          registry.registerInvokeVirtual(target.callTarget);
+          break;
+        default:
+          throw new Unreachable();
+      }
+    };
+  }
+}
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 06884b8..ed49900 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
@@ -11,6 +11,7 @@
 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;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -32,12 +33,15 @@
 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;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
@@ -45,6 +49,7 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 
 /**
  * Lambda desugaring rewriter.
@@ -101,8 +106,45 @@
     this.instanceFieldName = factory.createString(LAMBDA_INSTANCE_FIELD_NAME);
   }
 
-  public void synthesizeLambdaClassesFor(
-      DexEncodedMethod method, LensCodeRewriter lensCodeRewriter) {
+  public void synthesizeLambdaClassesForWave(
+      Collection<DexEncodedMethod> wave,
+      ExecutorService executorService,
+      OptimizationFeedbackDelayed feedback,
+      LensCodeRewriter lensCodeRewriter)
+      throws ExecutionException {
+    Set<DexProgramClass> synthesizedLambdaClasses = Sets.newIdentityHashSet();
+    for (DexEncodedMethod method : wave) {
+      synthesizeLambdaClassesForMethod(method, synthesizedLambdaClasses::add, lensCodeRewriter);
+    }
+
+    if (synthesizedLambdaClasses.isEmpty()) {
+      return;
+    }
+
+    // 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 : synthesizedLambdaClasses) {
+      DexEncodedMethod clinit = synthesizedLambdaClass.getClassInitializer();
+      if (clinit != null) {
+        for (DexEncodedField field : synthesizedLambdaClass.staticFields()) {
+          writesWithContexts.put(field, ImmutableSet.of(clinit));
+        }
+      }
+    }
+
+    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+    appViewWithLiveness.setAppInfo(
+        appViewWithLiveness.appInfo().withStaticFieldWrites(writesWithContexts));
+
+    converter.optimizeSynthesizedLambdaClasses(synthesizedLambdaClasses, executorService);
+    feedback.updateVisibleOptimizationInfo();
+  }
+
+  public void synthesizeLambdaClassesForMethod(
+      DexEncodedMethod method,
+      Consumer<DexProgramClass> consumer,
+      LensCodeRewriter lensCodeRewriter) {
     if (!method.hasCode() || method.isProcessed()) {
       // Nothing to desugar.
       return;
@@ -125,7 +167,9 @@
             LambdaDescriptor descriptor =
                 inferLambdaDescriptor(lensCodeRewriter.rewriteCallSite(callSite, method));
             if (descriptor != LambdaDescriptor.MATCH_FAILED) {
-              getOrCreateLambdaClass(descriptor, method.method.holder);
+              consumer.accept(
+                  getOrCreateLambdaClass(descriptor, method.method.holder)
+                      .getOrCreateLambdaClass());
             }
           }
         });
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaSynthesizedCode.java
new file mode 100644
index 0000000..2565687
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaSynthesizedCode.java
@@ -0,0 +1,42 @@
+// 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.desugar;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import java.util.function.Consumer;
+
+abstract class LambdaSynthesizedCode extends SynthesizedCode {
+
+  final LambdaClass lambda;
+
+  LambdaSynthesizedCode(LambdaClass lambda) {
+    super(null);
+    this.lambda = lambda;
+  }
+
+  final DexItemFactory dexItemFactory() {
+    return lambda.rewriter.factory;
+  }
+
+  final LambdaDescriptor descriptor() {
+    return lambda.descriptor;
+  }
+
+  final DexType[] captures() {
+    DexTypeList captures = descriptor().captures;
+    assert captures != null;
+    return captures.values;
+  }
+
+  @Override
+  public abstract SourceCodeProvider getSourceCodeProvider();
+
+  @Override
+  public abstract Consumer<UseRegistry> getRegistryCallback();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 6cae242..8c07d3d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
 
 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;
@@ -29,8 +28,6 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
@@ -138,9 +135,6 @@
                   && optimizationInfo.getDynamicUpperBoundType().isDefinitelyNotNull()) {
                 knownToBeNonNullValues.add(outValue);
               }
-
-              assert verifyCompanionClassInstanceIsKnownToBeNonNull(
-                  fieldInstruction, encodedField, knownToBeNonNullValues);
             }
           }
         }
@@ -246,33 +240,6 @@
     }
   }
 
-  private boolean verifyCompanionClassInstanceIsKnownToBeNonNull(
-      FieldInstruction instruction,
-      DexEncodedField encodedField,
-      Set<Value> knownToBeNonNullValues) {
-    if (!appView.appInfo().hasLiveness()) {
-      return true;
-    }
-    if (instruction.isStaticGet()) {
-      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      DexField field = encodedField.field;
-      DexClass clazz = appViewWithLiveness.definitionFor(field.holder);
-      assert clazz != null;
-      if (clazz.accessFlags.isFinal()
-          && !clazz.initializationOfParentTypesMayHaveSideEffects(appViewWithLiveness)) {
-        DexEncodedMethod classInitializer = clazz.getClassInitializer();
-        if (classInitializer != null) {
-          ClassInitializerInfo info =
-              classInitializer.getOptimizationInfo().getClassInitializerInfo();
-          boolean expectedToBeNonNull =
-              info != null && info.field == field && !appViewWithLiveness.appInfo().isPinned(field);
-          assert !expectedToBeNonNull || knownToBeNonNullValues.contains(instruction.outValue());
-        }
-      }
-    }
-    return true;
-  }
-
   private void addNonNullForValues(
       IRCode code,
       ListIterator<BasicBlock> blockIterator,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index ddb7e99..6b01c88 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -35,9 +35,11 @@
 
   enum EligibilityStatus {
     // Used by InlineCandidateProcessor#isInstanceEligible
-    UNUSED_INSTANCE,
     NON_CLASS_TYPE,
+    NOT_A_SINGLETON_FIELD,
+    RETRIEVAL_MAY_HAVE_SIDE_EFFECTS,
     UNKNOWN_TYPE,
+    UNUSED_INSTANCE,
 
     // Used by isClassEligible
     NON_PROGRAM_CLASS,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 436f81b..7f488a1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -29,6 +30,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
@@ -37,9 +39,9 @@
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInliner.EligibilityStatus;
+import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
@@ -136,9 +138,23 @@
     if (eligibleInstance == null) {
       return EligibilityStatus.UNUSED_INSTANCE;
     }
-
-    eligibleClass =
-        root.isNewInstance() ? root.asNewInstance().clazz : root.asStaticGet().getField().type;
+    if (root.isNewInstance()) {
+      eligibleClass = root.asNewInstance().clazz;
+    } else {
+      assert root.isStaticGet();
+      StaticGet staticGet = root.asStaticGet();
+      if (staticGet.instructionMayHaveSideEffects(appView, method.method.holder)) {
+        return EligibilityStatus.RETRIEVAL_MAY_HAVE_SIDE_EFFECTS;
+      }
+      DexEncodedField field = appView.appInfo().resolveField(staticGet.getField());
+      FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
+      ClassTypeLatticeElement dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType();
+      if (dynamicLowerBoundType == null
+          || !dynamicLowerBoundType.equals(optimizationInfo.getDynamicUpperBoundType())) {
+        return EligibilityStatus.NOT_A_SINGLETON_FIELD;
+      }
+      eligibleClass = dynamicLowerBoundType.getClassType();
+    }
     if (!eligibleClass.isClassType()) {
       return EligibilityStatus.NON_CLASS_TYPE;
     }
@@ -244,34 +260,10 @@
     //      of class inlining
     //
 
-    if (eligibleClassDefinition.instanceFields().size() > 0) {
+    if (!eligibleClassDefinition.instanceFields().isEmpty()) {
       return EligibilityStatus.HAS_INSTANCE_FIELDS;
     }
-    if (appView.appInfo().hasSubtypes(eligibleClassDefinition.type)) {
-      assert !eligibleClassDefinition.accessFlags.isFinal();
-      return EligibilityStatus.NON_FINAL_TYPE;
-    }
-
-    // Singleton instance must be initialized in class constructor.
-    DexEncodedMethod classInitializer = eligibleClassDefinition.getClassInitializer();
-    if (classInitializer == null || isProcessedConcurrently.test(classInitializer)) {
-      return EligibilityStatus.NOT_INITIALIZED_AT_INIT;
-    }
-
-    ClassInitializerInfo initializerInfo =
-        classInitializer.getOptimizationInfo().getClassInitializerInfo();
-    DexField instanceField = root.asStaticGet().getField();
-    // Singleton instance field must NOT be pinned.
-    AppInfoWithLiveness appInfo = appView.appInfo();
-    boolean notPinned =
-        initializerInfo != null
-            && initializerInfo.field == instanceField
-            && !appInfo.isPinned(eligibleClassDefinition.lookupStaticField(instanceField).field);
-    if (notPinned) {
-      return EligibilityStatus.ELIGIBLE;
-    } else {
-      return EligibilityStatus.PINNED_FIELD;
-    }
+    return EligibilityStatus.ELIGIBLE;
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 7ecbf18..06a94a6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.google.common.collect.ImmutableSet;
@@ -83,11 +82,6 @@
   }
 
   @Override
-  public ClassInitializerInfo getClassInitializerInfo() {
-    return null;
-  }
-
-  @Override
   public InstanceInitializerInfo getInstanceInitializerInfo() {
     return DefaultInstanceInitializerInfo.getInstance();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index d9c4083..fd8078d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import java.util.BitSet;
 import java.util.Set;
@@ -59,8 +58,6 @@
 
   Set<DexType> getInitializedClassesOnNormalExit();
 
-  ClassInitializerInfo getClassInitializerInfo();
-
   InstanceInitializerInfo getInstanceInitializerInfo();
 
   boolean isInitializerEnablingJavaAssertions();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index eac8791..fe4985a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -42,16 +42,14 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Return;
-import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerReceiverAnalysis;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.NonTrivialInstanceInitializerInfo;
 import com.android.tools.r8.kotlin.Kotlin;
@@ -94,7 +92,7 @@
     }
     computeDynamicReturnType(dynamicTypeOptimization, feedback, method, code);
     computeInitializedClassesOnNormalExit(feedback, method, code);
-    computeInitializerInfo(method, code, feedback);
+    computeInstanceInitializerInfo(method, code, feedback);
     computeMayHaveSideEffects(feedback, method, code);
     computeReturnValueOnlyDependsOnArguments(feedback, method, code);
     computeNonNullParamOrThrow(feedback, method, code);
@@ -274,11 +272,11 @@
     }
   }
 
-  private void computeInitializerInfo(
+  private void computeInstanceInitializerInfo(
       DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     assert !appView.appInfo().isPinned(method.method);
 
-    if (!method.isInitializer()) {
+    if (!method.isInstanceInitializer()) {
       return;
     }
 
@@ -296,90 +294,12 @@
       return;
     }
 
-    feedback.setInitializerInfo(
+    InstanceInitializerInfo instanceInitializerInfo = analyzeInstanceInitializer(code, clazz);
+    feedback.setInstanceInitializerInfo(
         method,
-        method.isInstanceInitializer()
-            ? computeInstanceInitializerInfo(code, clazz)
-            : computeClassInitializerInfo(code, clazz));
-  }
-
-  // This method defines trivial class initializer as follows:
-  //
-  // ** The initializer may only instantiate an instance of the same class,
-  //    initialize it with a call to a trivial constructor *without* arguments,
-  //    and assign this instance to a static final field of the same class.
-  //
-  private ClassInitializerInfo computeClassInitializerInfo(IRCode code, DexClass clazz) {
-    Value createdSingletonInstance = null;
-    DexField singletonField = null;
-    for (Instruction insn : code.instructions()) {
-      if (insn.isConstNumber()) {
-        continue;
-      }
-
-      if (insn.isConstString()) {
-        if (insn.instructionInstanceCanThrow()) {
-          return null;
-        }
-        continue;
-      }
-
-      if (insn.isReturn()) {
-        continue;
-      }
-
-      if (insn.isAssume()) {
-        continue;
-      }
-
-      if (insn.isNewInstance()) {
-        NewInstance newInstance = insn.asNewInstance();
-        if (createdSingletonInstance != null
-            || newInstance.clazz != clazz.type
-            || insn.outValue() == null) {
-          return null;
-        }
-        createdSingletonInstance = insn.outValue();
-        continue;
-      }
-
-      if (insn.isInvokeDirect()) {
-        InvokeDirect invokedDirect = insn.asInvokeDirect();
-        if (createdSingletonInstance == null
-            || invokedDirect.getReceiver() != createdSingletonInstance) {
-          return null;
-        }
-        DexEncodedMethod callTarget = clazz.lookupDirectMethod(invokedDirect.getInvokedMethod());
-        if (callTarget == null
-            || !callTarget.isInstanceInitializer()
-            || !callTarget.method.proto.parameters.isEmpty()
-            || callTarget.getOptimizationInfo().getInstanceInitializerInfo().isDefaultInfo()) {
-          return null;
-        }
-        continue;
-      }
-
-      if (insn.isStaticPut()) {
-        StaticPut staticPut = insn.asStaticPut();
-        if (singletonField != null
-            || createdSingletonInstance == null
-            || staticPut.value() != createdSingletonInstance) {
-          return null;
-        }
-        DexEncodedField field = clazz.lookupStaticField(staticPut.getField());
-        if (field == null
-            || !field.accessFlags.isStatic()
-            || !field.accessFlags.isFinal()) {
-          return null;
-        }
-        singletonField = field.field;
-        continue;
-      }
-
-      // Other instructions make the class initializer not eligible.
-      return null;
-    }
-    return singletonField == null ? null : new ClassInitializerInfo(singletonField);
+        instanceInitializerInfo != null
+            ? instanceInitializerInfo
+            : DefaultInstanceInitializerInfo.getInstance());
   }
 
   // This method defines trivial instance initializer as follows:
@@ -397,7 +317,7 @@
   // ** Assigns arguments or non-throwing constants to fields of this class.
   //
   // (Note that this initializer does not have to have zero arguments.)
-  private InstanceInitializerInfo computeInstanceInitializerInfo(IRCode code, DexClass clazz) {
+  private InstanceInitializerInfo analyzeInstanceInitializer(IRCode code, DexClass clazz) {
     if (clazz.definesFinalizer(options.itemFactory)) {
       // Defining a finalize method can observe the side-effect of Object.<init> GC registration.
       return null;
@@ -780,7 +700,7 @@
     if (!options.enableSideEffectAnalysis) {
       return;
     }
-    if (appView.appInfo().withLiveness().mayHaveSideEffects.containsKey(method.method)) {
+    if (appView.appInfo().mayHaveSideEffects.containsKey(method.method)) {
       return;
     }
     DexType context = method.method.holder;
@@ -794,6 +714,11 @@
         feedback.classInitializerMayBePostponed(method);
       } else if (classInitializerSideEffect.canBePostponed()) {
         feedback.classInitializerMayBePostponed(method);
+      } else {
+        assert !context.isD8R8SynthesizedLambdaClassType()
+                || options.debug
+                || appView.appInfo().hasPinnedInstanceInitializer(context)
+            : "Unexpected observable side effects from lambda `" + context.toSourceString() + "`";
       }
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index a0828e9..330eba6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -233,8 +233,10 @@
   }
 
   @Override
-  public synchronized void setInitializerInfo(DexEncodedMethod method, InitializerInfo info) {
-    getMethodOptimizationInfoForUpdating(method).setInitializerInfo(info);
+  public synchronized void setInstanceInitializerInfo(
+      DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo) {
+    getMethodOptimizationInfoForUpdating(method)
+        .setInstanceInitializerInfo(instanceInitializerInfo);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index e106dc8..2495e58 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
 import java.util.Set;
@@ -112,7 +112,8 @@
       DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {}
 
   @Override
-  public void setInitializerInfo(DexEncodedMethod method, InitializerInfo info) {}
+  public void setInstanceInitializerInfo(
+      DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo) {}
 
   @Override
   public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 0178a2d..34a1fb8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.BitSet;
 import java.util.Set;
@@ -161,7 +161,8 @@
   }
 
   @Override
-  public void setInitializerInfo(DexEncodedMethod method, InitializerInfo info) {
+  public void setInstanceInitializerInfo(
+      DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo) {
     // Ignored.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 74dd886..d599a38 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -12,9 +12,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import java.util.BitSet;
 import java.util.Set;
@@ -51,7 +49,8 @@
   // class inliner, null value indicates that the method is not eligible.
   private ClassInlinerEligibilityInfo classInlinerEligibility =
       DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
-  private InitializerInfo initializerInfo = null;
+  private InstanceInitializerInfo instanceInitializerInfo =
+      DefaultInstanceInitializerInfo.getInstance();
   private boolean initializerEnablingJavaAssertions =
       DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
   private ParameterUsagesInfo parametersUsages =
@@ -101,7 +100,7 @@
     checksNullReceiverBeforeAnySideEffect = template.checksNullReceiverBeforeAnySideEffect;
     triggersClassInitBeforeAnySideEffect = template.triggersClassInitBeforeAnySideEffect;
     classInlinerEligibility = template.classInlinerEligibility;
-    initializerInfo = template.initializerInfo;
+    instanceInitializerInfo = template.instanceInitializerInfo;
     initializerEnablingJavaAssertions = template.initializerEnablingJavaAssertions;
     parametersUsages = template.parametersUsages;
     nonNullParamOrThrow = template.nonNullParamOrThrow;
@@ -172,16 +171,8 @@
   }
 
   @Override
-  public ClassInitializerInfo getClassInitializerInfo() {
-    return initializerInfo != null ? initializerInfo.asClassInitializerInfo() : null;
-  }
-
-  @Override
   public InstanceInitializerInfo getInstanceInitializerInfo() {
-    if (initializerInfo != null) {
-      return initializerInfo.asInstanceInitializerInfo();
-    }
-    return DefaultInstanceInitializerInfo.getInstance();
+    return instanceInitializerInfo;
   }
 
   @Override
@@ -304,8 +295,8 @@
     this.classInlinerEligibility = eligibility;
   }
 
-  void setInitializerInfo(InitializerInfo initializerInfo) {
-    this.initializerInfo = initializerInfo;
+  void setInstanceInitializerInfo(InstanceInitializerInfo instanceInitializerInfo) {
+    this.instanceInitializerInfo = instanceInitializerInfo;
   }
 
   void setInitializerEnablingJavaAssertions() {
@@ -458,7 +449,7 @@
     // classInlinerEligibility: chances are the method is not an instance method anymore.
     classInlinerEligibility = DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
     // initializerInfo: the computed initializer info may become invalid.
-    initializerInfo = null;
+    instanceInitializerInfo = null;
     // initializerEnablingJavaAssertions: `this` could trigger <clinit> of the previous holder.
     initializerEnablingJavaAssertions =
         DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ClassInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ClassInitializerInfo.java
deleted file mode 100644
index 01d1bb0..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/ClassInitializerInfo.java
+++ /dev/null
@@ -1,26 +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.optimize.info.initializer;
-
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoCollector;
-
-/**
- * Defines class trivial initialized, see details in comments {@link
- * MethodOptimizationInfoCollector#computeClassInitializerInfo}.
- */
-public final class ClassInitializerInfo extends InitializerInfo {
-
-  public final DexField field;
-
-  public ClassInitializerInfo(DexField field) {
-    this.field = field;
-  }
-
-  @Override
-  public ClassInitializerInfo asClassInitializerInfo() {
-    return this;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InitializerInfo.java
deleted file mode 100644
index a4a2ad9..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InitializerInfo.java
+++ /dev/null
@@ -1,18 +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.optimize.info.initializer;
-
-public class InitializerInfo {
-
-  InitializerInfo() {}
-
-  public ClassInitializerInfo asClassInitializerInfo() {
-    return null;
-  }
-
-  public InstanceInitializerInfo asInstanceInitializerInfo() {
-    return null;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
index 24cb9af..76badfb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 
-public abstract class InstanceInitializerInfo extends InitializerInfo {
+public abstract class InstanceInitializerInfo {
 
   /**
    * Returns an abstraction of the set of fields that may be as a result of executing this
@@ -50,9 +50,4 @@
   public boolean isDefaultInfo() {
     return false;
   }
-
-  @Override
-  public InstanceInitializerInfo asInstanceInitializerInfo() {
-    return this;
-  }
 }
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 69e1930..708d328 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -630,6 +630,28 @@
     return constClassReferences.contains(type);
   }
 
+  public AppInfoWithLiveness withStaticFieldWrites(
+      Map<DexEncodedField, Set<DexEncodedMethod>> writesWithContexts) {
+    assert checkIfObsolete();
+    if (writesWithContexts.isEmpty()) {
+      return this;
+    }
+    AppInfoWithLiveness result = new AppInfoWithLiveness(this);
+    writesWithContexts.forEach(
+        (encodedField, contexts) -> {
+          DexField field = encodedField.field;
+          FieldAccessInfoImpl fieldAccessInfo = result.fieldAccessInfoCollection.get(field);
+          if (fieldAccessInfo == null) {
+            fieldAccessInfo = new FieldAccessInfoImpl(field);
+            result.fieldAccessInfoCollection.extend(field, fieldAccessInfo);
+          }
+          for (DexEncodedMethod context : contexts) {
+            fieldAccessInfo.recordWrite(field, context);
+          }
+        });
+    return result;
+  }
+
   public AppInfoWithLiveness withoutStaticFieldsWrites(Set<DexField> noLongerWrittenFields) {
     assert checkIfObsolete();
     if (noLongerWrittenFields.isEmpty()) {
@@ -838,6 +860,19 @@
     return pinnedItems.contains(reference);
   }
 
+  public boolean hasPinnedInstanceInitializer(DexType type) {
+    assert type.isClassType();
+    DexProgramClass clazz = asProgramClassOrNull(definitionFor(type));
+    if (clazz != null) {
+      for (DexEncodedMethod method : clazz.directMethods()) {
+        if (method.isInstanceInitializer() && isPinned(method.method)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
   private boolean canVirtualMethodBeImplementedInExtraSubclass(
       DexProgramClass clazz, DexMethod method) {
     // For functional interfaces that are instantiated by lambdas, we may not have synthesized all
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index c85098d..0084e78 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, 180, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -113,7 +113,7 @@
         // TODO(b/120814598): Should be 24. Some lambdas are not class inlined because parameter
         // usages for lambda methods are not present for the class inliner.
         // TODO(b/141719453): Also, some are not inined due to instruction limits.
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 39, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 37, "lambdadesugaring"))
         .run();
   }
 
@@ -145,7 +145,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 180, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -156,7 +156,7 @@
         // TODO(b/120814598): Should be 24. Some lambdas are not class inlined because parameter
         // usages for lambda methods are not present for the class inliner.
         // TODO(b/141719453): Also, some are not inined due to instruction limits.
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 39, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 37, "lambdadesugaring"))
         .run();
   }
 
@@ -170,7 +170,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 33, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -197,7 +197,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 33, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
index b35254e..2d1f0e5 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator;
 import com.android.tools.r8.utils.InternalOptions;
@@ -148,24 +149,25 @@
     nodes.add(n5);
     nodes.add(n6);
 
-    Set<Node> wave = Sets.newIdentityHashSet();
+    CallGraph callGraph = new CallGraph(nodes, null);
+    Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
 
-    PostMethodProcessor.extractRoots(nodes, wave::add);
+    wave.addAll(callGraph.extractRoots());
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n1));
-    assertThat(wave, hasItem(n5));
+    assertThat(wave, hasItem(n1.method));
+    assertThat(wave, hasItem(n5.method));
     wave.clear();
 
-    PostMethodProcessor.extractRoots(nodes, wave::add);
+    wave.addAll(callGraph.extractRoots());
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n2));
-    assertThat(wave, hasItem(n6));
+    assertThat(wave, hasItem(n2.method));
+    assertThat(wave, hasItem(n6.method));
     wave.clear();
 
-    PostMethodProcessor.extractRoots(nodes, wave::add);
+    wave.addAll(callGraph.extractRoots());
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n3));
-    assertThat(wave, hasItem(n4));
+    assertThat(wave, hasItem(n3.method));
+    assertThat(wave, hasItem(n4.method));
     assertTrue(nodes.isEmpty());
   }
 
@@ -200,24 +202,25 @@
     CycleEliminator cycleEliminator = new CycleEliminator(nodes, options);
     assertEquals(1, cycleEliminator.breakCycles().numberOfRemovedEdges());
 
-    Set<Node> wave = Sets.newIdentityHashSet();
+    CallGraph callGraph = new CallGraph(nodes, null);
+    Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
 
-    PostMethodProcessor.extractRoots(nodes, wave::add);
+    wave.addAll(callGraph.extractRoots());
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n1));
-    assertThat(wave, hasItem(n5));
+    assertThat(wave, hasItem(n1.method));
+    assertThat(wave, hasItem(n5.method));
     wave.clear();
 
-    PostMethodProcessor.extractRoots(nodes, wave::add);
+    wave.addAll(callGraph.extractRoots());
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n2));
-    assertThat(wave, hasItem(n6));
+    assertThat(wave, hasItem(n2.method));
+    assertThat(wave, hasItem(n6.method));
     wave.clear();
 
-    PostMethodProcessor.extractRoots(nodes, wave::add);
+    wave.addAll(callGraph.extractRoots());
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n3));
-    assertThat(wave, hasItem(n4));
+    assertThat(wave, hasItem(n3.method));
+    assertThat(wave, hasItem(n4.method));
     assertTrue(nodes.isEmpty());
   }
 }
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 880139f..8b027e4 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
@@ -126,22 +126,22 @@
     assertNotNull(m4);
     assertNotNull(m5);
 
-    Set<Node> wave = Sets.newIdentityHashSet();
+    Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
 
-    PostMethodProcessor.extractRoots(pg.nodes, wave::add);
+    wave.addAll(pg.extractRoots());
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(m1));
-    assertThat(wave, hasItem(m5));
+    assertThat(wave, hasItem(m1.method));
+    assertThat(wave, hasItem(m5.method));
     wave.clear();
 
-    PostMethodProcessor.extractRoots(pg.nodes, wave::add);
+    wave.addAll(pg.extractRoots());
     assertEquals(1, wave.size());
-    assertThat(wave, hasItem(m2));
+    assertThat(wave, hasItem(m2.method));
     wave.clear();
 
-    PostMethodProcessor.extractRoots(pg.nodes, wave::add);
+    wave.addAll(pg.extractRoots());
     assertEquals(1, wave.size());
-    assertThat(wave, hasItem(m4));
+    assertThat(wave, hasItem(m4.method));
     assertTrue(pg.nodes.isEmpty());
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
index 4fe7fde..9b424d3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
@@ -68,6 +68,7 @@
   static class Host {
     private static final Companion companion = new Companion();
 
+    @NeverClassInline
     static class Companion {
       @NeverInline
       public void foo(Object arg) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineSingletonFieldOfOtherTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineSingletonFieldOfOtherTypeTest.java
new file mode 100644
index 0000000..18739a4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineSingletonFieldOfOtherTypeTest.java
@@ -0,0 +1,76 @@
+// 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.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.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 ClassInlineSingletonFieldOfOtherTypeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlineSingletonFieldOfOtherTypeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlineSingletonFieldOfOtherTypeTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(Candidate.class), not(isPresent()));
+    assertThat(inspector.clazz(Container.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Container.getInstance().greet();
+    }
+  }
+
+  static class Candidate {
+
+    @NeverInline
+    void greet() {
+      System.out.println("Hello world!");
+    }
+  }
+
+  static class Container {
+
+    static Candidate INSTANCE = new Candidate();
+
+    static Candidate getInstance() {
+      return INSTANCE;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 325bdd5..3ad4a37 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -34,7 +34,6 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -84,9 +83,9 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(classes)
             .enableInliningAnnotations()
+            .enableSideEffectAnnotations()
             .addKeepMainRule(main)
             .addKeepAttributes("LineNumberTable")
-            .addOptionsModification(this::configure)
             .allowAccessModification()
             .noMinification()
             .run(main)
@@ -168,10 +167,7 @@
 
     AndroidApp compiled =
         compileWithR8(
-            builder.build(),
-            getProguardConfig(mainClass.name),
-            this::configure,
-            parameters.getBackend());
+            builder.build(), getProguardConfig(mainClass.name), null, parameters.getBackend());
 
     // Check that the code fails with an IncompatibleClassChangeError with Java.
     ProcessResult javaResult =
@@ -200,9 +196,9 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(classes)
             .enableInliningAnnotations()
+            .enableSideEffectAnnotations()
             .addKeepMainRule(main)
             .addKeepAttributes("LineNumberTable")
-            .addOptionsModification(this::configure)
             .allowAccessModification()
             .noMinification()
             .run(main)
@@ -244,7 +240,6 @@
                   // TODO(b/143129517, 141719453): The limit seems to only be needed for DEX...
                   o.classInliningInstructionLimit = 100;
                   o.classInliningInstructionAllowance = 1000;
-                  configure(o);
                 })
             .allowAccessModification()
             .noMinification()
@@ -299,7 +294,6 @@
                 o -> {
                   // TODO(b/141719453): Identify single instances instead of increasing the limit.
                   o.classInliningInstructionLimit = 20;
-                  configure(o);
                 })
             .allowAccessModification()
             .enableInliningAnnotations()
@@ -338,8 +332,4 @@
         "-allowaccessmodification",
         "-keepattributes LineNumberTable");
   }
-
-  private void configure(InternalOptions options) {
-    options.enableSideEffectAnalysis = false;
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java
index dcfb5eb..ae5ef86 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.code;
 
+import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.NeverInline;
 
 public class C {
@@ -27,16 +28,19 @@
     }
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
   public static int method1() {
     return new L(1).x;
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
   public static int method2() {
     return new L(1).getX();
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
   public static int method3() {
     return F.I.getX();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
index 9b7835d..86cf25e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.trivial;
 
+import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.NeverInline;
 
 public class TrivialTestClass {
@@ -51,6 +52,7 @@
     System.out.println(o.getA() + o.getB() + o.getConcat());
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
   private void testEmptyClass() {
     new EmptyClass();