Merge "Cache synthesized classes at AppInfo level."
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 e1be490..db83f6a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -10,6 +10,7 @@
 import com.google.common.collect.ImmutableMap.Builder;
 import com.google.common.collect.ImmutableSet;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -22,6 +23,10 @@
   public final DexItemFactory dexItemFactory;
   private final ConcurrentHashMap<DexType, Map<Descriptor<?,?>, KeyedDexItem<?>>> definitions =
       new ConcurrentHashMap<>();
+  // For some optimizations, e.g. optimizing synthetic classes, we may need to resolve the current
+  // class being optimized.
+  private ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses =
+      new ConcurrentHashMap<>();
 
   public AppInfo(DexApplication application) {
     this.app = application;
@@ -42,6 +47,16 @@
     this(application);
   }
 
+  public void addSynthesizedClass(DexProgramClass clazz) {
+    assert clazz.type.isD8R8SynthesizedClassType();
+    DexProgramClass previous = synthesizedClasses.put(clazz.type, clazz);
+    assert previous == null || previous == clazz;
+  }
+
+  public Collection<DexProgramClass> getSynthesizedClassesForSanityCheck() {
+    return Collections.unmodifiableCollection(synthesizedClasses.values());
+  }
+
   private Map<Descriptor<?,?>, KeyedDexItem<?>> computeDefinitions(DexType type) {
     Builder<Descriptor<?,?>, KeyedDexItem<?>> builder = ImmutableMap.builder();
     DexClass clazz = app.definitionFor(type);
@@ -72,6 +87,11 @@
   }
 
   public DexClass definitionFor(DexType type) {
+    DexProgramClass cached = synthesizedClasses.get(type);
+    if (cached != null) {
+      assert app.definitionFor(type) == null;
+      return cached;
+    }
     return app.definitionFor(type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 0efc380..96f34bc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -114,7 +114,7 @@
     // The only way to figure out whether the DexValue contains the final value
     // is ensure the value is not the default or check <clinit> is not present.
     boolean isEffectivelyFinal =
-        (accessFlags.isFinal() || !appInfo.fieldsWritten.contains(field))
+        (accessFlags.isFinal() || !appInfo.isFieldWritten(field))
             && !appInfo.isPinned(field);
     if (!isEffectivelyFinal) {
       return null;
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 2ebccfe..249a2b5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -10,7 +10,8 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.ir.desugar.Java8MethodRewriter;
+import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
@@ -221,14 +222,6 @@
     }
     while (other.hierarchyLevel < self.hierarchyLevel) {
       DexClass holder = appInfo.definitionFor(self);
-      // TODO(b/113374256): even synthesized class should be available ATM.
-      if (holder == null) {
-        assert self.isD8R8SynthesizedClassType();
-        if (Log.ENABLED) {
-          Log.debug(getClass(), "%s is not in AppInfo yet.", self.toSourceString());
-        }
-        return orElse;
-      }
       assert holder != null && !holder.isInterface();
       self = holder.superType;
     }
@@ -483,7 +476,9 @@
         || name.contains(DISPATCH_CLASS_NAME_SUFFIX)
         || name.contains(LAMBDA_CLASS_NAME_PREFIX)
         || name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX)
-        || name.contains(OutlineOptions.CLASS_NAME);
+        || name.contains(OutlineOptions.CLASS_NAME)
+        || name.contains(TwrCloseResourceRewriter.UTILITY_CLASS_NAME)
+        || name.contains(Java8MethodRewriter.UTILITY_CLASS_NAME_PREFIX);
   }
 
   public int elementSizeForPrimitiveArrayType() {
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 5fd473f..b964dd3 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -388,9 +388,7 @@
   public abstract DexMethod getRenamedMethodSignature(DexMethod originalMethod);
 
   public DexEncodedMethod mapDexEncodedMethod(
-      DexEncodedMethod originalEncodedMethod,
-      AppInfo appInfo,
-      Map<DexType, DexProgramClass> synthesizedClasses) {
+      DexEncodedMethod originalEncodedMethod, AppInfo appInfo) {
     DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method);
     // Note that:
     // * Even if `newMethod` is the same as `originalEncodedMethod.method`, we still need to look it
@@ -398,14 +396,7 @@
     // * We can't directly use AppInfo#definitionFor(DexMethod) since definitions may not be
     //   updated either yet.
     DexClass newHolder = appInfo.definitionFor(newMethod.holder);
-
-    // TODO(b/120130831): Need to ensure that all synthesized classes are part of the application.
-    if (newHolder == null) {
-      newHolder = synthesizedClasses.get(newMethod.holder);
-    }
-
     assert newHolder != null;
-
     DexEncodedMethod newEncodedMethod = newHolder.lookupMethod(newMethod);
     assert newEncodedMethod != null;
     return newEncodedMethod;
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 cb7a5d5..ff6abd3 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
@@ -89,12 +89,10 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -149,10 +147,6 @@
   private final OptimizationFeedback simpleOptimizationFeedback = new OptimizationFeedbackSimple();
   private DexString highestSortingString;
 
-  // For some optimizations, e.g. optimizing synthetic classes, we may need to resolve
-  // the current class being optimized.
-  private ConcurrentHashMap<DexType, DexProgramClass> cachedClasses = new ConcurrentHashMap<>();
-
   // The argument `appView` is only available when full program optimizations are allowed
   // (i.e., when running R8).
   private IRConverter(
@@ -352,30 +346,24 @@
       InterfaceMethodRewriter.Flavor includeAllResources,
       ExecutorService executorService)
       throws ExecutionException {
-    desugarInterfaceMethods(builder, includeAllResources, executorService, null);
-  }
-
-  private void desugarInterfaceMethods(
-      Builder<?> builder,
-      InterfaceMethodRewriter.Flavor includeAllResources,
-      ExecutorService executorService,
-      Map<DexType, DexProgramClass> synthesizedClasses)
-      throws ExecutionException {
     if (interfaceMethodRewriter != null) {
       interfaceMethodRewriter.desugarInterfaceMethods(
-          builder, includeAllResources, executorService, synthesizedClasses);
+          builder, includeAllResources, executorService);
     }
   }
 
-  private void synthesizeTwrCloseResourceUtilityClass(Builder<?> builder) {
+  private void synthesizeTwrCloseResourceUtilityClass(
+      Builder<?> builder, ExecutorService executorService)
+      throws ExecutionException {
     if (twrCloseResourceRewriter != null) {
-      twrCloseResourceRewriter.synthesizeUtilityClass(builder, options);
+      twrCloseResourceRewriter.synthesizeUtilityClass(builder, executorService, options);
     }
   }
 
-  private void synthesizeJava8UtilityClass(Builder<?> builder) {
+  private void synthesizeJava8UtilityClass(
+      Builder<?> builder, ExecutorService executorService) throws ExecutionException {
     if (java8MethodRewriter != null) {
-      java8MethodRewriter.synthesizeUtilityClass(builder, options);
+      java8MethodRewriter.synthesizeUtilityClass(builder, executorService, options);
     }
   }
 
@@ -398,8 +386,8 @@
 
     synthesizeLambdaClasses(builder, executor);
     desugarInterfaceMethods(builder, ExcludeDexResources, executor);
-    synthesizeTwrCloseResourceUtilityClass(builder);
-    synthesizeJava8UtilityClass(builder);
+    synthesizeTwrCloseResourceUtilityClass(builder, executor);
+    synthesizeJava8UtilityClass(builder, executor);
     processCovariantReturnTypeAnnotations(builder);
 
     handleSynthesizedClassMapping(builder);
@@ -590,21 +578,20 @@
     synthesizeLambdaClasses(builder, executorService);
 
     printPhase("Interface method desugaring");
-    Map<DexType, DexProgramClass> synthesizedClasses = new IdentityHashMap<>();
-    desugarInterfaceMethods(builder, IncludeAllResources, executorService, synthesizedClasses);
+    desugarInterfaceMethods(builder, IncludeAllResources, executorService);
 
     printPhase("Twr close resource utility class synthesis");
-    synthesizeTwrCloseResourceUtilityClass(builder);
-    synthesizeJava8UtilityClass(builder);
+    synthesizeTwrCloseResourceUtilityClass(builder, executorService);
+    synthesizeJava8UtilityClass(builder, executorService);
     handleSynthesizedClassMapping(builder);
 
     printPhase("Lambda merging finalization");
-    finalizeLambdaMerging(application, feedback, builder, executorService, synthesizedClasses);
+    finalizeLambdaMerging(application, feedback, builder, executorService);
 
     if (outliner != null) {
       printPhase("Outlining");
       timing.begin("IR conversion phase 2");
-      if (outliner.selectMethodsForOutlining(synthesizedClasses)) {
+      if (outliner.selectMethodsForOutlining()) {
         forEachSelectedOutliningMethod(
             executorService,
             (code, method) -> {
@@ -612,7 +599,8 @@
               outliner.identifyOutlineSites(code, method);
             });
         DexProgramClass outlineClass = outliner.buildOutlinerClass(computeOutlineClassType());
-        optimizeSynthesizedClass(outlineClass);
+        appInfo.addSynthesizedClass(outlineClass);
+        optimizeSynthesizedClass(outlineClass, executorService);
         forEachSelectedOutliningMethod(
             executorService,
             (code, method) -> {
@@ -636,6 +624,12 @@
       uninstantiatedTypeOptimization.logResults();
     }
 
+    // Check if what we've added to the application builder as synthesized classes are same as
+    // what we've added and used through AppInfo.
+    assert appInfo.getSynthesizedClassesForSanityCheck()
+            .containsAll(builder.getSynthesizedClasses())
+        && builder.getSynthesizedClasses()
+            .containsAll(appInfo.getSynthesizedClassesForSanityCheck());
     return builder.build();
   }
 
@@ -687,14 +681,13 @@
 
   private void finalizeLambdaMerging(
       DexApplication application,
-      OptimizationFeedback directFeedback,
+      OptimizationFeedback feedback,
       Builder<?> builder,
-      ExecutorService executorService,
-      Map<DexType, DexProgramClass> synthesizedClasses)
+      ExecutorService executorService)
       throws ExecutionException {
     if (lambdaMerger != null) {
       lambdaMerger.applyLambdaClassMapping(
-          application, this, directFeedback, builder, executorService, synthesizedClasses);
+          application, this, feedback, builder, executorService);
     }
   }
 
@@ -744,49 +737,24 @@
     return result;
   }
 
-  public DexClass definitionFor(DexType type) {
-    DexProgramClass cached = cachedClasses.get(type);
-    return cached != null ? cached : appInfo.definitionFor(type);
-  }
-
-  public void optimizeSynthesizedClass(DexProgramClass clazz) {
-    try {
-      enterCachedClass(clazz);
-      // Process the generated class, but don't apply any outlining.
-      clazz.forEachMethod(this::optimizeSynthesizedMethod);
-    } finally {
-      leaveCachedClass(clazz);
-    }
+  public void optimizeSynthesizedClass(
+      DexProgramClass clazz, ExecutorService executorService)
+      throws ExecutionException {
+    Set<DexEncodedMethod> methods = Sets.newIdentityHashSet();
+    clazz.forEachMethod(methods::add);
+    // Process the generated class, but don't apply any outlining.
+    optimizeSynthesizedMethodsConcurrently(methods, executorService);
   }
 
   public void optimizeSynthesizedClasses(
       Collection<DexProgramClass> classes, ExecutorService executorService)
       throws ExecutionException {
     Set<DexEncodedMethod> methods = Sets.newIdentityHashSet();
-    try {
-      for (DexProgramClass clazz : classes) {
-        enterCachedClass(clazz);
-        clazz.forEachMethod(methods::add);
-      }
-      // Process the generated class, but don't apply any outlining.
-      optimizeSynthesizedMethods(methods, executorService);
-    } finally {
-      for (DexProgramClass clazz : classes) {
-        leaveCachedClass(clazz);
-      }
+    for (DexProgramClass clazz : classes) {
+      clazz.forEachMethod(methods::add);
     }
-  }
-
-  public void optimizeMethodOnSynthesizedClass(DexProgramClass clazz, DexEncodedMethod method) {
-    if (!method.isProcessed()) {
-      try {
-        enterCachedClass(clazz);
-        // Process the generated method, but don't apply any outlining.
-        optimizeSynthesizedMethod(method);
-      } finally {
-        leaveCachedClass(clazz);
-      }
-    }
+    // Process the generated class, but don't apply any outlining.
+    optimizeSynthesizedMethodsConcurrently(methods, executorService);
   }
 
   public void optimizeSynthesizedMethod(DexEncodedMethod method) {
@@ -801,7 +769,7 @@
     }
   }
 
-  public void optimizeSynthesizedMethods(
+  public void optimizeSynthesizedMethodsConcurrently(
       Collection<DexEncodedMethod> methods, ExecutorService executorService)
       throws ExecutionException {
     List<Future<?>> futures = new ArrayList<>();
@@ -821,16 +789,6 @@
     ThreadUtils.awaitFutures(futures);
   }
 
-  private void enterCachedClass(DexProgramClass clazz) {
-    DexProgramClass previous = cachedClasses.put(clazz.type, clazz);
-    assert previous == null;
-  }
-
-  private void leaveCachedClass(DexProgramClass clazz) {
-    DexProgramClass existing = cachedClasses.remove(clazz.type);
-    assert existing == clazz;
-  }
-
   private String logCode(InternalOptions options, DexEncodedMethod method) {
     return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString();
   }
@@ -1198,7 +1156,8 @@
       // original method signature (this could have changed as a result of, for example, class
       // merging). Then, we find the type that now corresponds to the the original holder.
       DexMethod originalSignature = graphLense().getOriginalMethodSignature(method.method);
-      DexClass originalHolder = definitionFor(graphLense().lookupType(originalSignature.holder));
+      DexClass originalHolder = appInfo.definitionFor(
+          graphLense().lookupType(originalSignature.holder));
       if (originalHolder.hasKotlinInfo()) {
         KotlinInfo kotlinInfo = originalHolder.getKotlinInfo();
         if (kotlinInfo.hasNonNullParameterHints()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index ec788a4..401feaa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -60,7 +60,7 @@
     DexType superType = clazz.superType;
     // If superClass definition is missing, just skip this part and let real processing of its
     // subclasses report the error if it is required.
-    DexClass superClass = superType == null ? null : rewriter.findDefinitionFor(superType);
+    DexClass superClass = superType == null ? null : rewriter.appInfo.definitionFor(superType);
     if (superClass != null && superType != rewriter.factory.objectType) {
       if (superClass.isInterface()) {
         throw new CompilationError("Interface `" + superClass.toSourceString()
@@ -96,7 +96,7 @@
 
   private DexEncodedMethod addForwardingMethod(DexEncodedMethod defaultMethod, DexClass clazz) {
     DexMethod method = defaultMethod.method;
-    DexClass target = rewriter.findDefinitionFor(method.holder);
+    DexClass target = rewriter.appInfo.definitionFor(method.holder);
     // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
     // even if this results in invalid code, these classes are never desugared.
     assert target != null && !target.isLibraryClass();
@@ -168,7 +168,7 @@
       if (current.superType == null) {
         break;
       } else {
-        DexClass superClass = rewriter.findDefinitionFor(current.superType);
+        DexClass superClass = rewriter.appInfo.definitionFor(current.superType);
         if (superClass != null) {
           current = superClass;
         } else {
@@ -206,7 +206,7 @@
       DexType superType = current.superType;
       DexClass superClass = null;
       if (superType != null) {
-        superClass = rewriter.findDefinitionFor(superType);
+        superClass = rewriter.appInfo.definitionFor(superType);
         // It's available or we would have failed while analyzing the hierarchy for interfaces.
         assert superClass != null;
       }
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 e79017d..abd38a5 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
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication.Builder;
@@ -80,6 +81,7 @@
   public static final String PRIVATE_METHOD_PREFIX = "$private$";
 
   private final AppView<? extends AppInfoWithLiveness> appView;
+  final AppInfo appInfo;
   private final IRConverter converter;
   private final InternalOptions options;
   final DexItemFactory factory;
@@ -121,6 +123,7 @@
     assert converter != null;
     this.appView = appView;
     this.converter = converter;
+    this.appInfo = converter.appInfo;
     this.options = options;
     this.factory = options.itemFactory;
   }
@@ -156,7 +159,7 @@
         if (instruction.isInvokeStatic()) {
           InvokeStatic invokeStatic = instruction.asInvokeStatic();
           DexMethod method = invokeStatic.getInvokedMethod();
-          DexClass clazz = findDefinitionFor(method.holder);
+          DexClass clazz = appInfo.definitionFor(method.holder);
           if (Java8MethodRewriter.hasJava8MethodRewritePrefix(method.holder)) {
             // We did not create this code yet, but it will not require rewriting.
             continue;
@@ -187,7 +190,7 @@
                         invokeStatic.outValue(), invokeStatic.arguments()));
                 requiredDispatchClasses
                     .computeIfAbsent(clazz.asLibraryClass(), k -> Sets.newConcurrentHashSet())
-                    .add(findDefinitionFor(encodedMethod.method.holder).asProgramClass());
+                    .add(appInfo.definitionFor(encodedMethod.method.holder).asProgramClass());
               }
             } else {
               instructions.replaceCurrentInstruction(
@@ -201,7 +204,7 @@
         if (instruction.isInvokeSuper()) {
           InvokeSuper invokeSuper = instruction.asInvokeSuper();
           DexMethod method = invokeSuper.getInvokedMethod();
-          DexClass clazz = findDefinitionFor(method.holder);
+          DexClass clazz = appInfo.definitionFor(method.holder);
           if (clazz == null) {
             // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
             // exception but we can not report it as error since it can also be the intended
@@ -218,7 +221,7 @@
             // WARNING: This may result in incorrect code on older platforms!
             // Retarget call to an appropriate method of companion class.
             DexMethod amendedMethod = amendDefaultMethod(
-                findDefinitionFor(encodedMethod.method.holder), method);
+                appInfo.definitionFor(encodedMethod.method.holder), method);
             instructions.replaceCurrentInstruction(
                 new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
                     invokeSuper.outValue(), invokeSuper.arguments()));
@@ -233,7 +236,7 @@
             continue;
           }
 
-          DexClass clazz = findDefinitionFor(method.holder);
+          DexClass clazz = appInfo.definitionFor(method.holder);
           if (clazz == null) {
             // Report missing class since we don't know if it is an interface.
             warnMissingType(encodedMethod.method, method.holder);
@@ -279,7 +282,7 @@
 
   private void reportStaticInterfaceMethodHandle(DexMethod referencedFrom, DexMethodHandle handle) {
     if (handle.type.isInvokeStatic()) {
-      DexClass holderClass = findDefinitionFor(handle.asMethod().holder);
+      DexClass holderClass = appInfo.definitionFor(handle.asMethod().holder);
       // NOTE: If the class definition is missing we can't check. Let it be handled as any other
       // missing call target.
       if (holderClass == null) {
@@ -292,15 +295,6 @@
     }
   }
 
-  /**
-   * Returns the class definition for the specified type.
-   *
-   * @return may return null if no definition for the given type is available.
-   */
-  final DexClass findDefinitionFor(DexType type) {
-    return converter.definitionFor(type);
-  }
-
   // Gets the companion class for the interface `type`.
   final DexType getCompanionClassType(DexType type) {
     assert type.isClassType();
@@ -334,7 +328,7 @@
   }
 
   private boolean isInMainDexList(DexType iface) {
-    return converter.appInfo.isInMainDexList(iface);
+    return appInfo.isInMainDexList(iface);
   }
 
   // Represent a static interface method as a method of companion class.
@@ -393,8 +387,7 @@
   public void desugarInterfaceMethods(
       Builder<?> builder,
       Flavor flavour,
-      ExecutorService executorService,
-      Map<DexType, DexProgramClass> synthesizedClasses)
+      ExecutorService executorService)
       throws ExecutionException {
     // Process all classes first. Add missing forwarding methods to
     // replace desugared default interface methods.
@@ -409,13 +402,10 @@
       // are just moved from interfaces and don't need to be re-processed.
       DexProgramClass synthesizedClass = entry.getValue();
       builder.addSynthesizedClass(synthesizedClass, isInMainDexList(entry.getKey()));
-
-      if (synthesizedClasses != null) {
-        synthesizedClasses.put(synthesizedClass.type, synthesizedClass);
-      }
+      appInfo.addSynthesizedClass(synthesizedClass);
     }
 
-    converter.optimizeSynthesizedMethods(synthesizedMethods, executorService);
+    converter.optimizeSynthesizedMethodsConcurrently(synthesizedMethods, executorService);
 
     // Cached data is not needed any more.
     clear();
@@ -528,7 +518,7 @@
     if (isCompanionClassType(holder)) {
       holder = getInterfaceClassType(holder);
     }
-    DexClass clazz = converter.appInfo.definitionFor(holder);
+    DexClass clazz = appInfo.definitionFor(holder);
     return clazz == null ? Origin.unknown() : clazz.getOrigin();
   }
 
@@ -550,7 +540,7 @@
       DexClass implementing,
       DexType iface) {
     DefaultMethodsHelper helper = new DefaultMethodsHelper();
-    DexClass definedInterface = findDefinitionFor(iface);
+    DexClass definedInterface = appInfo.definitionFor(iface);
     if (definedInterface == null) {
       warnMissingInterface(classToDesugar, implementing, iface);
       return helper.wrapInCollection();
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 054b950..e4d4b01 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
@@ -322,7 +322,7 @@
         if (!seenBefore.add(superType)) {
           continue;
         }
-        DexClass clazz = rewriter.findDefinitionFor(superType);
+        DexClass clazz = rewriter.appInfo.definitionFor(superType);
         if (clazz != null) {
           if (clazz.lookupVirtualMethod(method.method) != null) {
             return false;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
index 9e2e9ab..e930bcf 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
@@ -37,9 +37,12 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.BiFunction;
 
 public final class Java8MethodRewriter {
+  public static final String UTILITY_CLASS_NAME_PREFIX = "$r8$java8methods$utility";
   private static final String UTILITY_CLASS_DESCRIPTOR_PREFIX = "L$r8$java8methods$utility";
   private final Set<DexType> holders = Sets.newConcurrentHashSet();
   private final IRConverter converter;
@@ -88,7 +91,9 @@
     return clazz.descriptor.toString().startsWith(UTILITY_CLASS_DESCRIPTOR_PREFIX);
   }
 
-  public void synthesizeUtilityClass(Builder<?> builder, InternalOptions options) {
+  public void synthesizeUtilityClass(
+      Builder<?> builder, ExecutorService executorService, InternalOptions options)
+      throws ExecutionException {
     if (holders.isEmpty()) {
       return;
     }
@@ -134,7 +139,8 @@
       code.setUpContext(utilityClass);
       boolean addToMainDexList = referencingClasses.stream()
           .anyMatch(clazz -> converter.appInfo.isInMainDexList(clazz.type));
-      converter.optimizeSynthesizedClass(utilityClass);
+      converter.appInfo.addSynthesizedClass(utilityClass);
+      converter.optimizeSynthesizedClass(utilityClass, executorService);
       builder.addSynthesizedClass(utilityClass, addToMainDexList);
     }
   }
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 865a62c..cff5392 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
@@ -153,8 +153,9 @@
             synthesizeVirtualMethods(mainMethod),
             rewriter.factory.getSkipNameValidationForTesting());
     // Optimize main method.
-    rewriter.converter.optimizeMethodOnSynthesizedClass(
-        clazz, clazz.lookupVirtualMethod(mainMethod));
+    rewriter.converter.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.
     synchronized (synthesizedFrom) {
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 4e29e6f..a1bb373 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
@@ -181,14 +181,15 @@
   /** Generates lambda classes and adds them to the builder. */
   public void synthesizeLambdaClasses(Builder<?> builder, ExecutorService executorService)
       throws ExecutionException {
+    for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
+      DexProgramClass synthesizedClass = lambdaClass.getLambdaClass();
+      appInfo.addSynthesizedClass(synthesizedClass);
+      builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
+    }
     converter.optimizeSynthesizedClasses(
         knownLambdaClasses.values().stream()
             .map(LambdaClass::getLambdaClass).collect(ImmutableSet.toImmutableSet()),
         executorService);
-    for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
-      DexProgramClass synthesizedClass = lambdaClass.getLambdaClass();
-      builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
-    }
   }
 
   public Set<DexCallSite> getDesugaredCallSites() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index 568e021..965d44e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -31,6 +31,8 @@
 import java.lang.reflect.Method;
 import java.util.Collections;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 
 // Try with resources outlining processor. Handles $closeResource methods
 // synthesized by java 9 compiler.
@@ -45,6 +47,7 @@
 // tree shaking to remove them since now they should not be referenced.
 //
 public final class TwrCloseResourceRewriter {
+  public static final String UTILITY_CLASS_NAME = "$r8$twr$utility";
   public static final String UTILITY_CLASS_DESCRIPTOR = "L$r8$twr$utility;";
 
   private final IRConverter converter;
@@ -107,7 +110,9 @@
         && original.proto == converter.appInfo.dexItemFactory.twrCloseResourceMethodProto;
   }
 
-  public void synthesizeUtilityClass(Builder<?> builder, InternalOptions options) {
+  public void synthesizeUtilityClass(
+      Builder<?> builder, ExecutorService executorService, InternalOptions options)
+      throws ExecutionException {
     if (referencingClasses.isEmpty()) {
       return;
     }
@@ -144,7 +149,8 @@
     // Process created class and method.
     boolean addToMainDexList = referencingClasses.stream()
         .anyMatch(clazz -> converter.appInfo.isInMainDexList(clazz.type));
-    converter.optimizeSynthesizedClass(utilityClass);
+    converter.appInfo.addSynthesizedClass(utilityClass);
+    converter.optimizeSynthesizedClass(utilityClass, executorService);
     builder.addSynthesizedClass(utilityClass, addToMainDexList);
   }
 
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 f7baf3e..b1bb84e 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
@@ -1886,7 +1886,7 @@
       return;
     }
 
-    DexClass clazz = definitionFor(method.method.getHolder());
+    DexClass clazz = appInfo.definitionFor(method.method.getHolder());
     if (clazz == null) {
       return;
     }
@@ -2087,10 +2087,6 @@
     }
   }
 
-  DexClass definitionFor(DexType type) {
-    return converter.definitionFor(type);
-  }
-
   public void removeTrivialCheckCastAndInstanceOfInstructions(
       IRCode code, boolean enableWholeProgramOptimizations) {
     if (!enableWholeProgramOptimizations) {
@@ -2196,7 +2192,7 @@
     if (baseType.isPrimitiveType()) {
       return false;
     }
-    DexClass clazz = definitionFor(baseType);
+    DexClass clazz = appInfo.definitionFor(baseType);
     if (clazz == null) {
       // Conservatively say yes.
       return true;
@@ -3874,7 +3870,7 @@
       if (type == dexItemFactory.throwableType) {
         return true;
       }
-      DexClass dexClass = definitionFor(type);
+      DexClass dexClass = appInfo.definitionFor(type);
       if (dexClass == null) {
         throw new CompilationError("Class or interface " + type.toSourceString() +
             " required for desugaring of try-with-resources is not found.");
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 2c14153..22c3da8 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
@@ -286,7 +286,7 @@
             ? appInfo.lookupInstanceTarget(field.getHolder(), field)
             : appInfo.lookupStaticTarget(field.getHolder(), field);
     // TODO(b/123857022): Should be possible to use `!isFieldRead(field)`.
-    if (target != null && !isFieldRead(target.field)) {
+    if (target != null && !appInfo.isFieldRead(target.field)) {
       // Remove writes to dead (i.e. never read) fields.
       iterator.removeOrReplaceByDebugLocalRead();
     }
@@ -327,17 +327,4 @@
     }
     assert code.isConsistentSSA();
   }
-
-  private boolean isFieldRead(DexField field) {
-    return appInfo.fieldsRead.contains(field)
-        // TODO(b/121354886): Pinned fields should be in `fieldsRead`.
-        || appInfo.isPinned(field)
-        // For library classes we don't know whether a field is read.
-        || isLibraryField(field);
-  }
-
-  private boolean isLibraryField(DexField field) {
-    DexClass holder = appInfo.definitionFor(field.clazz);
-    return holder == null || holder.isLibraryClass();
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 73f2a91..27ec7dc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -1232,7 +1232,7 @@
     }
   }
 
-  public boolean selectMethodsForOutlining(Map<DexType, DexProgramClass> synthesizedClasses) {
+  public boolean selectMethodsForOutlining() {
     assert methodsSelectedForOutlining.size() == 0;
     assert outlineSites.size() == 0;
     for (List<DexEncodedMethod> outlineMethods : candidateMethodLists) {
@@ -1241,7 +1241,7 @@
           methodsSelectedForOutlining.add(
               converter
                   .graphLense()
-                  .mapDexEncodedMethod(outlineMethod, appInfo, synthesizedClasses));
+                  .mapDexEncodedMethod(outlineMethod, appInfo));
         }
       }
     }
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 1f036b6..a0ba213 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
@@ -117,12 +117,14 @@
     if (!eligibleClass.isClassType()) {
       return false;
     }
-    eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
-    if (eligibleClassDefinition == null && lambdaRewriter != null) {
+    if (lambdaRewriter != null) {
       // Check if the class is synthesized for a desugared lambda
       eligibleClassDefinition = lambdaRewriter.getLambdaClass(eligibleClass);
       isDesugaredLambda = eligibleClassDefinition != null;
     }
+    if (eligibleClassDefinition == null) {
+      eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
+    }
     return eligibleClassDefinition != null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index b988a89..ab6f635 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.ir.conversion.CallSiteInformation;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.Outliner;
 import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
@@ -36,6 +37,7 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import com.google.common.base.Predicates;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
@@ -194,8 +196,7 @@
       IRConverter converter,
       OptimizationFeedback feedback,
       Builder<?> builder,
-      ExecutorService executorService,
-      Map<DexType, DexProgramClass> synthesizedClasses)
+      ExecutorService executorService)
       throws ExecutionException {
     if (lambdas.isEmpty()) {
       return;
@@ -218,18 +219,29 @@
     this.strategyFactory = ApplyStrategy::new;
 
     // Add synthesized lambda group classes to the builder.
-    converter.optimizeSynthesizedClasses(lambdaGroupsClasses.values(), executorService);
 
     for (Entry<LambdaGroup, DexProgramClass> entry : lambdaGroupsClasses.entrySet()) {
       DexProgramClass synthesizedClass = entry.getValue();
-      synthesizedClasses.put(synthesizedClass.type, synthesizedClass);
+      converter.appInfo.addSynthesizedClass(synthesizedClass);
       builder.addSynthesizedClass(
           synthesizedClass, entry.getKey().shouldAddToMainDex(converter.appInfo));
+      // Eventually, we need to process synthesized methods in the lambda group.
+      // Otherwise, abstract SynthesizedCode will be flown to Enqueuer.
+      // But that process should not see the holder. Otherwise, lambda calls in the main dispatch
+      // method became recursive calls via the lense rewriter. They should remain, then inliner
+      // will inline methods from mergee lambdas to the main dispatch method.
+      // Then, there is a dilemma: other sub optimizations trigger subtype lookup that will throw
+      // NPE if it cannot find the holder for this synthesized lambda group.
+      // One hack here is to mark those methods `processed` so that the lense rewriter is skipped.
+      synthesizedClass.forEachMethod(encodedMethod -> {
+        encodedMethod.markProcessed(ConstraintWithTarget.NEVER);
+      });
     }
+    converter.optimizeSynthesizedClasses(lambdaGroupsClasses.values(), executorService);
 
     // Rewrite lambda class references into lambda group class
     // references inside methods from the processing queue.
-    rewriteLambdaReferences(converter, synthesizedClasses, feedback);
+    rewriteLambdaReferences(converter, feedback);
     this.strategyFactory = null;
   }
 
@@ -304,10 +316,7 @@
     }
   }
 
-  private void rewriteLambdaReferences(
-      IRConverter converter,
-      Map<DexType, DexProgramClass> synthesizedClasses,
-      OptimizationFeedback feedback) {
+  private void rewriteLambdaReferences(IRConverter converter, OptimizationFeedback feedback) {
     List<DexEncodedMethod> methods =
         methodsToReprocess
             .stream()
@@ -315,9 +324,9 @@
             .collect(Collectors.toList());
     for (DexEncodedMethod method : methods) {
       DexEncodedMethod mappedMethod =
-          converter.graphLense().mapDexEncodedMethod(method, converter.appInfo, synthesizedClasses);
+          converter.graphLense().mapDexEncodedMethod(method, converter.appInfo);
       converter.processMethod(mappedMethod, feedback,
-          x -> false, CallSiteInformation.empty(), Outliner::noProcessing);
+          Predicates.alwaysFalse(), CallSiteInformation.empty(), Outliner::noProcessing);
       assert mappedMethod.isProcessed();
     }
   }
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 f40cb1d..8de2695 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1985,11 +1985,11 @@
     /**
      * Set of all fields which may be touched by a get operation. This is actual field definitions.
      */
-    public final SortedSet<DexField> fieldsRead;
+    private final SortedSet<DexField> fieldsRead;
     /**
      * Set of all fields which may be touched by a put operation. This is actual field definitions.
      */
-    public final SortedSet<DexField> fieldsWritten;
+    private final SortedSet<DexField> fieldsWritten;
     /**
      * Set of all field ids used in instance field reads, along with access context.
      */
@@ -2359,7 +2359,8 @@
 
     public boolean isInstantiatedDirectly(DexType type) {
       assert type.isClassType();
-      return instantiatedTypes.contains(type)
+      return type.isD8R8SynthesizedClassType()
+          || instantiatedTypes.contains(type)
           || instantiatedLambdas.contains(type)
           || instantiatedAnnotationTypes.contains(type);
     }
@@ -2386,6 +2387,31 @@
       return isInstantiatedDirectly(type) || isInstantiatedIndirectly(type);
     }
 
+    public boolean isFieldRead(DexField field) {
+      return fieldsRead.contains(field)
+          // TODO(b/121354886): Pinned fields should be in `fieldsRead`.
+          || isPinned(field)
+          // Fields in the class that is synthesized by D8/R8 would be used soon.
+          || field.getHolder().isD8R8SynthesizedClassType()
+          // For library classes we don't know whether a field is read.
+          || isLibraryField(field);
+    }
+
+    public boolean isFieldWritten(DexField field) {
+      return fieldsWritten.contains(field)
+          // TODO(b/121354886): Pinned fields should be in `fieldsWritten`.
+          || isPinned(field)
+          // Fields in the class that is synthesized by D8/R8 would be used soon.
+          || field.clazz.isD8R8SynthesizedClassType()
+          // For library classes we don't know whether a field is rewritten.
+          || isLibraryField(field);
+    }
+
+    private boolean isLibraryField(DexField field) {
+      DexClass holder = definitionFor(field.clazz);
+      return holder == null || holder.isLibraryClass();
+    }
+
     private Object2BooleanMap<DexReference> joinIdentifierNameStrings(
         Set<DexReference> explicit, Set<DexReference> implicit) {
       Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index dd8bcd6..786d026 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -228,8 +228,8 @@
     Predicate<DexField> isReachableOrReferencedField =
         field ->
             appInfo.liveFields.contains(field)
-                || appInfo.fieldsRead.contains(field)
-                || appInfo.fieldsWritten.contains(field);
+                || appInfo.isFieldRead(field)
+                || appInfo.isFieldWritten(field);
     int firstUnreachable =
         firstUnreachableIndex(Arrays.asList(fields), isReachableOrReferencedField);
     // Return the original array if all fields are used.