Merge "Revert "Revert "Ensure lookupDescriptor do not introduce duplicate minified types"""
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index 364a794..e124571 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -18,6 +18,7 @@
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
 import java.util.List;
+import java.util.Set;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
@@ -150,7 +151,8 @@
       return DescriptorUtils.getClassBinaryNameFromDescriptor(classDescriptor) + CLASS_EXTENSION;
     }
 
-    public static void writeResources(Path archive, List<ProgramResource> resources)
+    public static void writeResources(
+        Path archive, List<ProgramResource> resources, Set<DataEntryResource> dataResources)
         throws IOException, ResourceException {
       OpenOption[] options =
           new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
@@ -163,6 +165,11 @@
             byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
             ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
           }
+          for (DataEntryResource dataResource : dataResources) {
+            String entryName = dataResource.getName();
+            byte[] bytes = ByteStreams.toByteArray(closer.register(dataResource.getByteStream()));
+            ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
+          }
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 466a26f..2273c4d 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -204,6 +204,7 @@
 
       new ApplicationWriter(
               app,
+              null,
               options,
               marker == null ? null : ImmutableList.copyOf(markers),
               null,
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index b6bb4e0..6974d46 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -95,6 +95,7 @@
         ApplicationWriter writer =
             new ApplicationWriter(
                 app,
+                null,
                 options,
                 markers,
                 null,
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index 8731d2a..3ca4bf2 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.utils.OutputBuilder;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.Closer;
 import java.io.IOException;
@@ -180,13 +179,8 @@
       outputBuilder.close(handler);
     }
 
-    public static void writeResources(Path archive, List<ProgramResource> resources)
-        throws IOException, ResourceException {
-      writeResources(archive, resources, ImmutableList.of());
-    }
-
     public static void writeResources(
-        Path archive, List<ProgramResource> resources, List<DataEntryResource> dataResources)
+        Path archive, List<ProgramResource> resources, Set<DataEntryResource> dataResources)
         throws IOException, ResourceException {
       OpenOption[] options =
           new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index 27c564a..6826f63 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -96,6 +96,7 @@
         try {
           new ApplicationWriter(
                   featureApp,
+                  null,
                   options,
                   markers,
                   null,
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 4f472c6..065ad10 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -11,7 +11,9 @@
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.AppliedGraphLens;
 import com.android.tools.r8.graph.DexApplication;
@@ -184,6 +186,7 @@
   static void writeApplication(
       ExecutorService executorService,
       DexApplication application,
+      AppView<? extends AppInfo> appView,
       String deadCode,
       GraphLense graphLense,
       NamingLens namingLens,
@@ -197,6 +200,7 @@
       if (options.isGeneratingClassFiles()) {
         new CfApplicationWriter(
                 application,
+                appView,
                 options,
                 marker,
                 deadCode,
@@ -208,6 +212,7 @@
       } else {
         new ApplicationWriter(
                 application,
+                appView,
                 options,
                 Collections.singletonList(marker),
                 deadCode,
@@ -263,6 +268,8 @@
       AppView<AppInfoWithSubtyping> appView =
           new AppView<>(
               new AppInfoWithSubtyping(application), GraphLense.getIdentityLense(), options);
+      appView.setAppServices(AppServices.builder(appView).build());
+
       List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
       RootSet rootSet;
       String proguardSeedsData = null;
@@ -483,6 +490,8 @@
             new EnumOrdinalMapCollector(appViewWithLiveness, options).run());
       }
 
+      appView.setAppServices(appView.appServices().rewrittenWithLens(appView.graphLense()));
+
       timing.begin("Create IR");
       Set<DexCallSite> desugaredCallSites;
       CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
@@ -619,9 +628,7 @@
       NamingLens namingLens;
       if (options.enableMinification) {
         timing.begin("Minification");
-        namingLens =
-            new Minifier(appView.appInfo().withLiveness(), rootSet, desugaredCallSites, options)
-                .run(timing);
+        namingLens = new Minifier(appView.withLiveness(), rootSet, desugaredCallSites).run(timing);
         timing.end();
       } else {
         namingLens = NamingLens.getIdentityLens();
@@ -665,6 +672,7 @@
       writeApplication(
           executorService,
           application,
+          appView,
           application.deadCode,
           appView.graphLense(),
           namingLens,
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index 60167c5..2b661d0 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -178,7 +178,7 @@
     InternalOptions options = new InternalOptions();
     AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
     ApplicationWriter writer =
-        new ApplicationWriter(app, options, null, null, null, null, null, null);
+        new ApplicationWriter(app, null, options, null, null, null, null, null, null);
     writer.write(executor);
     compatSink.build().writeToDirectory(output, OutputMode.DexIndexed);
   }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 26398d8..7b2c688 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -17,6 +17,8 @@
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.dex.FileWriter.ByteBufferResult;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -60,6 +62,7 @@
 public class ApplicationWriter {
 
   public final DexApplication application;
+  public final AppView<? extends AppInfo> appView;
   public final String deadCode;
   public final GraphLense graphLense;
   public final NamingLens namingLens;
@@ -129,6 +132,7 @@
 
   public ApplicationWriter(
       DexApplication application,
+      AppView<? extends AppInfo> appView,
       InternalOptions options,
       List<Marker> markers,
       String deadCode,
@@ -138,6 +142,7 @@
       ProguardMapSupplier proguardMapSupplier) {
     this(
         application,
+        appView,
         options,
         markers,
         deadCode,
@@ -150,6 +155,7 @@
 
   public ApplicationWriter(
       DexApplication application,
+      AppView<? extends AppInfo> appView,
       InternalOptions options,
       List<Marker> markers,
       String deadCode,
@@ -160,6 +166,7 @@
       DexIndexedConsumer consumer) {
     assert application != null;
     this.application = application;
+    this.appView = appView;
     assert options != null;
     this.options = options;
     this.markers = markers;
@@ -298,6 +305,7 @@
       // Supply info to all additional resource consumers.
       supplyAdditionalConsumers(
           application,
+          appView,
           graphLense,
           namingLens,
           options,
@@ -311,6 +319,7 @@
 
   public static void supplyAdditionalConsumers(
       DexApplication application,
+      AppView<? extends AppInfo> appView,
       GraphLense graphLense,
       NamingLens namingLens,
       InternalOptions options,
@@ -349,7 +358,7 @@
           .collect(Collectors.toList());
 
       ResourceAdapter resourceAdapter =
-          new ResourceAdapter(application.dexItemFactory, graphLense, namingLens, options);
+          new ResourceAdapter(appView, application.dexItemFactory, graphLense, namingLens, options);
       Set<String> generatedResourceNames = new HashSet<>();
 
       for (DataResourceProvider dataResourceProvider : dataResourceProviders) {
@@ -367,6 +376,10 @@
 
                 @Override
                 public void visit(DataEntryResource file) {
+                  if (resourceAdapter.shouldBeDeleted(file)) {
+                    return;
+                  }
+
                   DataEntryResource adapted = resourceAdapter.adaptIfNeeded(file);
                   if (generatedResourceNames.add(adapted.getName())) {
                     dataResourceConsumer.accept(adapted, options.reporter);
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
index ae59576..630f25d 100644
--- a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -8,11 +8,15 @@
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardPathFilter;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
@@ -27,16 +31,19 @@
 
 public class ResourceAdapter {
 
+  private final AppView<? extends AppInfo> appView;
   private final DexItemFactory dexItemFactory;
   private final GraphLense graphLense;
   private final NamingLens namingLense;
   private final InternalOptions options;
 
   public ResourceAdapter(
+      AppView<? extends AppInfo> appView,
       DexItemFactory dexItemFactory,
       GraphLense graphLense,
       NamingLens namingLense,
       InternalOptions options) {
+    this.appView = appView;
     this.dexItemFactory = dexItemFactory;
     this.graphLense = graphLense;
     this.namingLense = namingLense;
@@ -88,8 +95,40 @@
     return DataDirectoryResource.fromName(adaptDirectoryName(directory), directory.getOrigin());
   }
 
+  // Returns true for files in META-INF/services/ that are never used by the application.
+  public boolean shouldBeDeleted(DataEntryResource file) {
+    if (appView != null && appView.appInfo().hasLiveness()) {
+      AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
+      if (file.getName().startsWith(AppServices.SERVICE_DIRECTORY_NAME)) {
+        String serviceName = file.getName().substring(AppServices.SERVICE_DIRECTORY_NAME.length());
+        if (!DescriptorUtils.isValidJavaType(serviceName)) {
+          return false;
+        }
+
+        DexString serviceDescriptor =
+            dexItemFactory.lookupString(DescriptorUtils.javaTypeToDescriptor(serviceName));
+        if (serviceDescriptor == null) {
+          return false;
+        }
+
+        DexType serviceType = appView.dexItemFactory().lookupType(serviceDescriptor);
+        if (serviceType == null) {
+          return false;
+        }
+
+        DexType rewrittenServiceType = appView.graphLense().lookupType(serviceType);
+        assert appView.appServices().allServiceTypes().contains(rewrittenServiceType);
+        return !appInfo.instantiatedAppServices.contains(rewrittenServiceType);
+      }
+    }
+    return false;
+  }
+
   private String adaptFileName(DataEntryResource file) {
-    FileNameAdapter adapter = new FileNameAdapter(file.getName());
+    FileNameAdapter adapter =
+        file.getName().startsWith(AppServices.SERVICE_DIRECTORY_NAME)
+            ? new ServiceFileNameAdapter(file.getName())
+            : new DefaultFileNameAdapter(file.getName());
     if (adapter.run()) {
       return adapter.getResult();
     }
@@ -307,7 +346,7 @@
 
     private void outputRangeFromInput(int from, int toExclusive) {
       if (from < toExclusive) {
-        result.append(contents.substring(from, toExclusive));
+        result.append(contents, from, toExclusive);
       }
     }
 
@@ -362,8 +401,8 @@
     }
   }
 
-  private abstract class FileNameAdapterBase extends StringAdapter {
-    public FileNameAdapterBase(String filename) {
+  private abstract class FileNameAdapter extends StringAdapter {
+    public FileNameAdapter(String filename) {
       super(filename);
     }
 
@@ -391,8 +430,8 @@
     }
   }
 
-  private class FileNameAdapter extends FileNameAdapterBase {
-    public FileNameAdapter(String filename) {
+  private class DefaultFileNameAdapter extends FileNameAdapter {
+    public DefaultFileNameAdapter(String filename) {
       super(filename);
     }
 
@@ -402,7 +441,28 @@
     }
   }
 
-  private class DirectoryNameAdapter extends FileNameAdapterBase {
+  private class ServiceFileNameAdapter extends FileNameAdapter {
+    public ServiceFileNameAdapter(String filename) {
+      super(filename);
+    }
+
+    @Override
+    public char getClassNameSeparator() {
+      return '.';
+    }
+
+    @Override
+    public boolean allowRenamingOfPrefixes() {
+      return false;
+    }
+
+    @Override
+    public boolean isRenamingCandidate(int from, int toExclusive) {
+      return from == AppServices.SERVICE_DIRECTORY_NAME.length() && eof(toExclusive);
+    }
+  }
+
+  private class DirectoryNameAdapter extends FileNameAdapter {
     public DirectoryNameAdapter(String filename) {
       super(filename);
     }
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/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
new file mode 100644
index 0000000..ec22d85
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -0,0 +1,178 @@
+// 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.graph;
+
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/** A description of the services and their implementations found in META-INF/services/. */
+public class AppServices {
+
+  public static final String SERVICE_DIRECTORY_NAME = "META-INF/services/";
+
+  private final AppView<? extends AppInfo> appView;
+
+  // Mapping from service types to service implementation types.
+  private final Map<DexType, Set<DexType>> services;
+
+  private AppServices(AppView<? extends AppInfo> appView, Map<DexType, Set<DexType>> services) {
+    this.appView = appView;
+    this.services = services;
+  }
+
+  public Set<DexType> allServiceTypes() {
+    assert verifyRewrittenWithLens();
+    return services.keySet();
+  }
+
+  public Set<DexType> serviceImplementationsFor(DexType serviceType) {
+    assert verifyRewrittenWithLens();
+    assert services.containsKey(serviceType);
+    Set<DexType> serviceImplementationTypes = services.get(serviceType);
+    if (serviceImplementationTypes == null) {
+      assert false
+          : "Unexpected attempt to get service implementations for non-service type `"
+              + serviceType.toSourceString()
+              + "`";
+      return ImmutableSet.of();
+    }
+    return serviceImplementationTypes;
+  }
+
+  public AppServices rewrittenWithLens(GraphLense graphLens) {
+    ImmutableMap.Builder<DexType, Set<DexType>> rewrittenServices = ImmutableMap.builder();
+    for (Entry<DexType, Set<DexType>> entry : services.entrySet()) {
+      DexType rewrittenServiceType = graphLens.lookupType(entry.getKey());
+      ImmutableSet.Builder<DexType> rewrittenServiceImplementationTypes = ImmutableSet.builder();
+      for (DexType serviceImplementationType : entry.getValue()) {
+        rewrittenServiceImplementationTypes.add(graphLens.lookupType(serviceImplementationType));
+      }
+      rewrittenServices.put(rewrittenServiceType, rewrittenServiceImplementationTypes.build());
+    }
+    return new AppServices(appView, rewrittenServices.build());
+  }
+
+  private boolean verifyRewrittenWithLens() {
+    for (Entry<DexType, Set<DexType>> entry : services.entrySet()) {
+      assert entry.getKey() == appView.graphLense().lookupType(entry.getKey());
+      for (DexType type : entry.getValue()) {
+        assert type == appView.graphLense().lookupType(type);
+      }
+    }
+    return true;
+  }
+
+  public static Builder builder(AppView<? extends AppInfo> appView) {
+    return new Builder(appView);
+  }
+
+  public static class Builder {
+
+    private final AppView<? extends AppInfo> appView;
+    private final Map<DexType, Set<DexType>> services = new IdentityHashMap<>();
+
+    private Builder(AppView<? extends AppInfo> appView) {
+      this.appView = appView;
+    }
+
+    public AppServices build() {
+      Iterable<ProgramResourceProvider> programResourceProviders =
+          appView.appInfo().app.programResourceProviders;
+      for (ProgramResourceProvider programResourceProvider : programResourceProviders) {
+        DataResourceProvider dataResourceProvider =
+            programResourceProvider.getDataResourceProvider();
+        if (dataResourceProvider != null) {
+          readServices(dataResourceProvider);
+        }
+      }
+      return new AppServices(appView, services);
+    }
+
+    private void readServices(DataResourceProvider dataResourceProvider) {
+      try {
+        dataResourceProvider.accept(new DataResourceProviderVisitor());
+      } catch (ResourceException e) {
+        throw new CompilationError(e.getMessage(), e);
+      }
+    }
+
+    private class DataResourceProviderVisitor implements Visitor {
+
+      @Override
+      public void visit(DataDirectoryResource directory) {
+        // Ignore.
+      }
+
+      @Override
+      public void visit(DataEntryResource file) {
+        try {
+          String name = file.getName();
+          if (name.startsWith(SERVICE_DIRECTORY_NAME)) {
+            String serviceName = name.substring(SERVICE_DIRECTORY_NAME.length());
+            if (DescriptorUtils.isValidJavaType(serviceName)) {
+              String serviceDescriptor = DescriptorUtils.javaTypeToDescriptor(serviceName);
+              DexType serviceType = appView.dexItemFactory().createType(serviceDescriptor);
+              byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+              String contents = new String(bytes, Charset.defaultCharset());
+              services.put(
+                  serviceType, readServiceImplementationsForService(contents, file.getOrigin()));
+            }
+          }
+        } catch (IOException | ResourceException e) {
+          throw new CompilationError(e.getMessage(), e);
+        }
+      }
+
+      private Set<DexType> readServiceImplementationsForService(String contents, Origin origin) {
+        if (contents != null) {
+          return Arrays.stream(contents.split(System.lineSeparator()))
+              .map(String::trim)
+              .filter(line -> !line.isEmpty())
+              .filter(DescriptorUtils::isValidJavaType)
+              .map(DescriptorUtils::javaTypeToDescriptor)
+              .map(appView.dexItemFactory()::createType)
+              .filter(
+                  serviceImplementationType -> {
+                    if (!serviceImplementationType.isClassType()) {
+                      // Should never happen.
+                      appView
+                          .options()
+                          .reporter
+                          .warning(
+                              new StringDiagnostic(
+                                  "Unexpected service implementation found in META-INF/services/: `"
+                                      + serviceImplementationType.toSourceString()
+                                      + "`.",
+                                  origin));
+                      return false;
+                    }
+                    return true;
+                  })
+              .collect(Collectors.toSet());
+        }
+        return ImmutableSet.of();
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index e232cb9..084ad80 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -11,6 +11,7 @@
 public class AppView<T extends AppInfo> {
 
   private T appInfo;
+  private AppServices appServices;
   private final DexItemFactory dexItemFactory;
   private GraphLense graphLense;
   private final InternalOptions options;
@@ -31,6 +32,14 @@
     this.appInfo = appInfo;
   }
 
+  public AppServices appServices() {
+    return appServices;
+  }
+
+  public void setAppServices(AppServices appServices) {
+    this.appServices = appServices;
+  }
+
   public DexItemFactory dexItemFactory() {
     return dexItemFactory;
   }
@@ -86,6 +95,16 @@
     }
 
     @Override
+    public AppServices appServices() {
+      return AppView.this.appServices();
+    }
+
+    @Override
+    public void setAppServices(AppServices appServices) {
+      AppView.this.setAppServices(appServices);
+    }
+
+    @Override
     public DexItemFactory dexItemFactory() {
       return AppView.this.dexItemFactory();
     }
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/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index e7b3967..3ce4a75 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -170,6 +170,7 @@
   public final DexString objectDescriptor = createString("Ljava/lang/Object;");
   public final DexString objectArrayDescriptor = createString("[Ljava/lang/Object;");
   public final DexString classDescriptor = createString("Ljava/lang/Class;");
+  public final DexString classLoaderDescriptor = createString("Ljava/lang/ClassLoader;");
   public final DexString autoCloseableDescriptor = createString("Ljava/lang/AutoCloseable;");
   public final DexString classArrayDescriptor = createString("[Ljava/lang/Class;");
   public final DexString fieldDescriptor = createString("Ljava/lang/reflect/Field;");
@@ -187,6 +188,7 @@
   public final DexString methodTypeDescriptor = createString("Ljava/lang/invoke/MethodType;");
 
   public final DexString npeDescriptor = createString("Ljava/lang/NullPointerException;");
+  public final DexString serviceLoaderDescriptor = createString("Ljava/util/ServiceLoader;");
 
   public final DexString intFieldUpdaterDescriptor =
       createString("Ljava/util/concurrent/atomic/AtomicIntegerFieldUpdater;");
@@ -237,6 +239,7 @@
   public final DexType exceptionInInitializerErrorType =
       createType(exceptionInInitializerErrorDescriptor);
   public final DexType classType = createType(classDescriptor);
+  public final DexType classLoaderType = createType(classLoaderDescriptor);
   public final DexType autoCloseableType = createType(autoCloseableDescriptor);
 
   public final DexType stringBuilderType = createType(stringBuilderDescriptor);
@@ -247,6 +250,7 @@
   public final DexType methodTypeType = createType(methodTypeDescriptor);
 
   public final DexType npeType = createType(npeDescriptor);
+  public final DexType serviceLoaderType = createType(serviceLoaderDescriptor);
 
   public final StringBuildingMethods stringBuilderMethods =
       new StringBuildingMethods(stringBuilderType);
@@ -266,6 +270,7 @@
       new AtomicFieldUpdaterMethods();
   public final Kotlin kotlin;
   public final PolymorphicMethods polymorphicMethods = new PolymorphicMethods();
+  public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
 
   public final DexString twrCloseResourceMethodName = createString("$closeResource");
   public final DexProto twrCloseResourceMethodProto =
@@ -790,6 +795,32 @@
     }
   }
 
+  public class ServiceLoaderMethods {
+
+    public final DexMethod load;
+    public final DexMethod loadWithClassLoader;
+    public final DexMethod loadInstalled;
+
+    private ServiceLoaderMethods() {
+      DexString loadName = createString("load");
+      load = createMethod(serviceLoaderType, createProto(serviceLoaderType, classType), loadName);
+      loadWithClassLoader =
+          createMethod(
+              serviceLoaderType,
+              createProto(serviceLoaderType, classType, classLoaderType),
+              loadName);
+      loadInstalled =
+          createMethod(
+              serviceLoaderType,
+              createProto(serviceLoaderType, classType),
+              createString("loadInstalled"));
+    }
+
+    public boolean isLoadMethod(DexMethod method) {
+      return method == load || method == loadWithClassLoader || method == loadInstalled;
+    }
+  }
+
   private static <T extends DexItem> T canonicalize(ConcurrentHashMap<T, T> map, T item) {
     assert item != null;
     assert !DexItemFactory.isInternalSentinel(item);
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/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
index b4ff421..ad95b05 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
@@ -45,7 +45,7 @@
 
   public void run() {
 
-    BasicBlock firstBlock = code.blocks.get(0);
+    BasicBlock firstBlock = code.entryBlock();
     visitInstructions(firstBlock);
 
     while (!flowEdges.isEmpty() || !ssaEdges.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index 04f56f5..1ab0670 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -303,7 +303,7 @@
     for (int i = 0; i < inlinee.blocks.size(); i++) {
       blocksIterator.previous();
     }
-    assert IteratorUtils.peekNext(blocksIterator) == inlinee.blocks.getFirst();
+    assert IteratorUtils.peekNext(blocksIterator) == inlinee.entryBlock();
     // Iterate through the inlined blocks.
     for (BasicBlock inlinedBlock : inlinee.blocks) {
       BasicBlock expected = blocksIterator.next();
@@ -364,7 +364,7 @@
     List<Value> arguments = inlinee.collectArguments();
     assert invoke.inValues().size() == arguments.size();
 
-    BasicBlock entryBlock = inlinee.blocks.getFirst();
+    BasicBlock entryBlock = inlinee.entryBlock();
     InstructionListIterator entryBlockIterator;
 
     int i = 0;
@@ -425,7 +425,7 @@
     new TypeAnalysis(appInfo, inlinee.method).narrowing(argumentUsers);
 
     // The inline entry is the first block now the argument instructions are gone.
-    BasicBlock inlineEntry = inlinee.blocks.getFirst();
+    BasicBlock inlineEntry = inlinee.entryBlock();
 
     BasicBlock inlineExit = null;
     List<BasicBlock> normalExits = inlinee.computeNormalExitBlocks();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 870ae6f..0d96f0d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -136,6 +136,10 @@
     this.hasConstString |= inlinee.hasConstString;
   }
 
+  public BasicBlock entryBlock() {
+    return blocks.getFirst();
+  }
+
   /**
    * Compute the set of live values at the entry to each block using a backwards data-flow analysis.
    */
@@ -416,7 +420,7 @@
     ArrayList<BasicBlock> reverseOrdered = new ArrayList<>(blocks.size());
     Set<BasicBlock> visitedBlocks = new HashSet<>(blocks.size());
     Deque<Object> worklist = new ArrayDeque<>(blocks.size());
-    worklist.addLast(blocks.getFirst());
+    worklist.addLast(entryBlock());
     while (!worklist.isEmpty()) {
       Object item = worklist.removeLast();
       if (item instanceof BlockMarker) {
@@ -768,7 +772,7 @@
 
   public List<Value> collectArguments(boolean ignoreReceiver) {
     final List<Value> arguments = new ArrayList<>();
-    Iterator<Instruction> iterator = blocks.get(0).iterator();
+    Iterator<Instruction> iterator = entryBlock().iterator();
     while (iterator.hasNext()) {
       Instruction instruction = iterator.next();
       if (instruction.isArgument()) {
@@ -787,7 +791,7 @@
     if (method.accessFlags.isStatic()) {
       return null;
     }
-    Instruction firstArg = blocks.getFirst().listIterator().nextUntil(Instruction::isArgument);
+    Instruction firstArg = entryBlock().listIterator().nextUntil(Instruction::isArgument);
     assert firstArg != null;
     Value thisValue = firstArg.asArgument().outValue();
     assert thisValue.isThis();
@@ -910,7 +914,7 @@
   public Set<BasicBlock> getUnreachableBlocks() {
     Set<BasicBlock> unreachableBlocks = Sets.newIdentityHashSet();
     int color = reserveMarkingColor();
-    markTransitiveSuccessors(blocks.getFirst(), color);
+    markTransitiveSuccessors(entryBlock(), color);
     for (BasicBlock block : blocks) {
       if (!block.isMarked(color)) {
         unreachableBlocks.add(block);
@@ -923,7 +927,7 @@
   public Set<Value> removeUnreachableBlocks() {
     ImmutableSet.Builder<Value> affectedValueBuilder = ImmutableSet.builder();
     int color = reserveMarkingColor();
-    markTransitiveSuccessors(blocks.getFirst(), color);
+    markTransitiveSuccessors(entryBlock(), color);
     ListIterator<BasicBlock> blockIterator = listIterator();
     while (blockIterator.hasNext()) {
       BasicBlock current = blockIterator.next();
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..b5f3a2d 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
@@ -201,7 +201,7 @@
       BasicBlock blk = block;  // Additional local for lambda below.
       assert !block.isTrivialGoto()
           || block.exit().asGoto().getTarget() == block
-          || code.blocks.get(0) == block
+          || code.entryBlock() == block
           || block.getPredecessors().stream().anyMatch((b) -> b.exit().fallthroughBlock() == blk);
       // Trivial goto blocks never target the next block (in that case there should just be a
       // fallthrough).
@@ -242,7 +242,7 @@
       // Not targeting the fallthrough block, determine if we need this goto. We need it if
       // a fallthrough can hit this block. That is the case if the block is the entry block
       // or if one of the predecessors fall through to the block.
-      needed = code.blocks.get(0) == block || isFallthroughBlock(block);
+      needed = code.entryBlock() == block || isFallthroughBlock(block);
     }
 
     if (!needed) {
@@ -1535,7 +1535,7 @@
     final int color = code.reserveMarkingColor();
     try {
       ArrayDeque<BasicBlock> worklist = new ArrayDeque<>();
-      final BasicBlock entry = code.blocks.getFirst();
+      final BasicBlock entry = code.entryBlock();
       worklist.add(entry);
       entry.mark(color);
 
@@ -1886,7 +1886,7 @@
       return;
     }
 
-    DexClass clazz = definitionFor(method.method.getHolder());
+    DexClass clazz = appInfo.definitionFor(method.method.getHolder());
     if (clazz == null) {
       return;
     }
@@ -2032,7 +2032,7 @@
       Set<StaticPut> puts, Map<DexField, StaticPut> finalFieldPut) {
     final int color = code.reserveMarkingColor();
     try {
-      BasicBlock block = code.blocks.getFirst();
+      BasicBlock block = code.entryBlock();
       while (!block.isMarked(color) && block.getPredecessors().size() <= 1) {
         block.mark(color);
         InstructionListIterator it = block.listIterator();
@@ -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.");
@@ -3901,7 +3897,7 @@
    */
   public void logArgumentTypes(DexEncodedMethod method, IRCode code) {
     List<Value> arguments = code.collectArguments();
-    BasicBlock block = code.blocks.getFirst();
+    BasicBlock block = code.entryBlock();
     InstructionListIterator iterator = block.listIterator();
 
     // Attach some synthetic position to all inserted code.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index 4c437bd..53da564 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -90,7 +90,7 @@
 
     // Double-check the entry block does not have catch handlers.
     // Otherwise, we need to split it before moving canonicalized const-string, which may throw.
-    assert !code.blocks.getFirst().hasCatchHandlers();
+    assert !code.entryBlock().hasCatchHandlers();
     Position firstNonNonePosition = findFirstNonNonePosition(code);
     FastSortedEntrySet<ConstInstruction, List<Value>> entries =
         valuesDefinedByConstant.object2ObjectEntrySet();
@@ -124,7 +124,7 @@
 
   private static void insertCanonicalizedConstant(
       IRCode code, ConstInstruction canonicalizedConstant) {
-    BasicBlock entryBlock = code.blocks.get(0);
+    BasicBlock entryBlock = code.entryBlock();
     // Insert the constant instruction at the start of the block right after the argument
     // instructions. It is important that the const instruction is put before any instruction
     // that can throw exceptions (since the value could be used on the exceptional edge).
@@ -139,7 +139,7 @@
   }
 
   private static Position findFirstNonNonePosition(IRCode code) {
-    BasicBlock entryBlock = code.blocks.get(0);
+    BasicBlock entryBlock = code.entryBlock();
     Instruction rightAfterArguments =
         entryBlock.listIterator().nextUntil(instr -> !instr.isArgument());
     Position firstNonArgumentPosition = rightAfterArguments.getPosition();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
index 229611d..2e31219 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -183,7 +183,7 @@
   }
 
   private static void insertCanonicalizedInvoke(IRCode code, Invoke canonicalizedInvoke) {
-    BasicBlock entryBlock = code.blocks.get(0);
+    BasicBlock entryBlock = code.entryBlock();
     // Insert the canonicalized invoke after in values.
     int numberOfInValuePassed = 0;
     InstructionListIterator it = entryBlock.listIterator();
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/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index cbdeaac..dd84122 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
@@ -422,8 +422,7 @@
       // Stop traversing upwards if we hit the entry block: if the entry block has an non-null,
       // this case should be handled already by A) because the entry block surely dominates all
       // normal exits.
-      // TODO(b/120787963): code.entryBlock()
-      if (uncoveredPath == code.blocks.getFirst()) {
+      if (uncoveredPath == code.entryBlock()) {
         return false;
       }
       // Make sure we're not visiting the same block over and over again.
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/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 170c39e..51f9de0 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -189,7 +189,7 @@
     this.code = code;
     this.options = options;
     int argumentRegisters = 0;
-    for (Instruction instruction : code.blocks.getFirst().getInstructions()) {
+    for (Instruction instruction : code.entryBlock().getInstructions()) {
       if (instruction.isArgument()) {
         argumentRegisters += instruction.outValue().requiredRegisters();
       }
@@ -2506,7 +2506,7 @@
     // VMs we block the receiver register throughout the method.
     if ((options.canHaveThisTypeVerifierBug() || options.canHaveThisJitCodeDebuggingBug())
         && !code.method.accessFlags.isStatic()) {
-      for (Instruction instruction : code.blocks.get(0).getInstructions()) {
+      for (Instruction instruction : code.entryBlock().getInstructions()) {
         if (instruction.isArgument() && instruction.outValue().isThis()) {
           Value thisValue = instruction.outValue();
           LiveIntervals thisIntervals = thisValue.getLiveIntervals();
@@ -2674,8 +2674,8 @@
   // be original, consecutive arguments of the enclosing method (and importantly, not values that
   // have been defined by a check-cast instruction).
   private void transformBridgeMethod() {
-    assert implementationIsBridge(this.code);
-    BasicBlock entry = this.code.blocks.getFirst();
+    assert implementationIsBridge(code);
+    BasicBlock entry = code.entryBlock();
     InstructionListIterator iterator = entry.listIterator();
     // Create a mapping from argument values to their index, while scanning over the arguments.
     Reference2IntMap<Value> argumentIndices = new Reference2IntArrayMap<>();
@@ -2733,7 +2733,7 @@
     if (code.blocks.size() > 1) {
       return false;
     }
-    InstructionListIterator iterator = code.blocks.getFirst().listIterator();
+    InstructionListIterator iterator = code.entryBlock().listIterator();
     // Move forward to the first instruction after the definition of the arguments.
     while (iterator.hasNext() && iterator.peekNext().isArgument()) {
       iterator.next();
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index 8489866..0d25746 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -166,7 +166,7 @@
   public int scheduleAndInsertMoves(int tempRegister) {
     for (BasicBlock block : code.blocks) {
       InstructionListIterator insertAt = block.listIterator();
-      if (block == code.blocks.getFirst()) {
+      if (block == code.entryBlock()) {
         // Move insertAt iterator to the first non-argument, such that moves for the arguments will
         // be inserted after the last argument.
         while (insertAt.hasNext() && insertAt.peekNext().isArgument()) {
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 8d55a59..6a9e056 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -11,6 +11,8 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
@@ -42,7 +44,6 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.concurrent.ExecutorService;
@@ -67,6 +68,7 @@
   public static final int MARKER_STRING_CONSTANT_POOL_INDEX = 2;
 
   private final DexApplication application;
+  private final AppView<? extends AppInfo> appView;
   private final GraphLense graphLense;
   private final NamingLens namingLens;
   private final InternalOptions options;
@@ -78,6 +80,7 @@
 
   public CfApplicationWriter(
       DexApplication application,
+      AppView<? extends AppInfo> appView,
       InternalOptions options,
       Marker marker,
       String deadCode,
@@ -86,6 +89,7 @@
       String proguardSeedsData,
       ProguardMapSupplier proguardMapSupplier) {
     this.application = application;
+    this.appView = appView;
     this.graphLense = graphLense;
     this.namingLens = namingLens;
     this.options = options;
@@ -96,7 +100,7 @@
     this.proguardSeedsData = proguardSeedsData;
   }
 
-  public void write(ClassFileConsumer consumer, ExecutorService executor) throws IOException {
+  public void write(ClassFileConsumer consumer, ExecutorService executor) {
     application.timing.begin("CfApplicationWriter.write");
     try {
       writeApplication(consumer, executor);
@@ -105,8 +109,7 @@
     }
   }
 
-  private void writeApplication(ClassFileConsumer consumer, ExecutorService executor)
-      throws IOException {
+  private void writeApplication(ClassFileConsumer consumer, ExecutorService executor) {
     ProguardMapSupplier.ProguardMapAndId proguardMapAndId = null;
     if (proguardMapSupplier != null && options.proguardMapConsumer != null) {
       proguardMapAndId = proguardMapSupplier.getProguardMapAndId();
@@ -124,6 +127,7 @@
     }
     ApplicationWriter.supplyAdditionalConsumers(
         application,
+        appView,
         graphLense,
         namingLens,
         options,
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index bc81538..b59b79b 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -9,6 +9,7 @@
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromClassBinaryName;
 import static com.android.tools.r8.utils.DescriptorUtils.getPackageBinaryNameFromJavaType;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
@@ -50,6 +51,7 @@
 
 class ClassNameMinifier {
 
+  private final AppView<AppInfoWithLiveness> appView;
   private final AppInfoWithLiveness appInfo;
   private final Reporter reporter;
   private final PackageObfuscationMode packageObfuscationMode;
@@ -74,11 +76,10 @@
   private final GenericSignatureParser<DexType> genericSignatureParser =
       new GenericSignatureParser<>(genericSignatureRewriter);
 
-  ClassNameMinifier(
-      AppInfoWithLiveness appInfo,
-      RootSet rootSet,
-      InternalOptions options) {
-    this.appInfo = appInfo;
+  ClassNameMinifier(AppView<AppInfoWithLiveness> appView, RootSet rootSet) {
+    this.appView = appView;
+    this.appInfo = appView.appInfo();
+    InternalOptions options = appView.options();
     this.reporter = options.reporter;
     this.packageObfuscationMode = options.getProguardConfiguration().getPackageObfuscationMode();
     this.isAccessModificationAllowed =
@@ -546,6 +547,7 @@
     @Override
     public DexType parsedTypeName(String name) {
       DexType type = appInfo.dexItemFactory.createType(getDescriptorFromClassBinaryName(name));
+      type = appView.graphLense().lookupType(type);
       DexString renamedDescriptor = renaming.getOrDefault(type, type.descriptor);
       renamedSignature.append(getClassBinaryNameFromDescriptor(renamedDescriptor.toString()));
       return type;
@@ -564,6 +566,7 @@
       String enclosingRenamedBinaryName =
           getClassBinaryNameFromDescriptor(
               renaming.getOrDefault(enclosingType, enclosingType.descriptor).toString());
+      type = appView.graphLense().lookupType(type);
       DexString renamedDescriptor = renaming.get(type);
       if (renamedDescriptor != null) {
         // Pick the renamed inner class from the fully renamed binary name.
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index be339b6..e2127dd 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+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.DexField;
@@ -10,7 +11,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
@@ -19,8 +19,8 @@
 
 class FieldNameMinifier extends MemberNameMinifier<DexField, DexType> {
 
-  FieldNameMinifier(AppInfoWithLiveness appInfo, RootSet rootSet, InternalOptions options) {
-    super(appInfo, rootSet, options);
+  FieldNameMinifier(AppView<AppInfoWithLiveness> appView, RootSet rootSet) {
+    super(appView, rootSet);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
index 419bfbb..53957e4 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CachedHashValueDexItem;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -18,6 +19,7 @@
 
 abstract class MemberNameMinifier<MemberType, StateType extends CachedHashValueDexItem> {
 
+  protected final AppView<AppInfoWithLiveness> appView;
   protected final AppInfoWithLiveness appInfo;
   protected final RootSet rootSet;
   protected final InternalOptions options;
@@ -34,10 +36,11 @@
   // which is useful for debugging.
   private final BiMap<DexType, NamingState<StateType, ?>> states = HashBiMap.create();
 
-  MemberNameMinifier(AppInfoWithLiveness appInfo, RootSet rootSet, InternalOptions options) {
-    this.appInfo = appInfo;
+  MemberNameMinifier(AppView<AppInfoWithLiveness> appView, RootSet rootSet) {
+    this.appView = appView;
+    this.appInfo = appView.appInfo();
     this.rootSet = rootSet;
-    this.options = options;
+    this.options = appView.options();
     this.dictionary = options.getProguardConfiguration().getObfuscationDictionary();
     this.useUniqueMemberNames = options.getProguardConfiguration().isUseUniqueClassMemberNames();
     this.overloadAggressively =
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 8864271..2cd8712 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -91,11 +92,8 @@
 
   private final FrontierState frontierState = new FrontierState();
 
-  MethodNameMinifier(
-      AppInfoWithLiveness appInfo,
-      RootSet rootSet,
-      InternalOptions options) {
-    super(appInfo, rootSet, options);
+  MethodNameMinifier(AppView<AppInfoWithLiveness> appView, RootSet rootSet) {
+    super(appView, rootSet);
     equivalence =
         overloadAggressively
             ? MethodSignatureEquivalence.get()
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 5699d03..7f389a4 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -36,26 +37,27 @@
 
   static final char INNER_CLASS_SEPARATOR = '$';
 
+  private final AppView<AppInfoWithLiveness> appView;
   private final AppInfoWithLiveness appInfo;
   private final RootSet rootSet;
   private final Set<DexCallSite> desugaredCallSites;
   private final InternalOptions options;
 
   public Minifier(
-      AppInfoWithLiveness appInfo,
+      AppView<AppInfoWithLiveness> appView,
       RootSet rootSet,
-      Set<DexCallSite> desugaredCallSites,
-      InternalOptions options) {
-    this.appInfo = appInfo;
+      Set<DexCallSite> desugaredCallSites) {
+    this.appView = appView;
+    this.appInfo = appView.appInfo();
     this.rootSet = rootSet;
     this.desugaredCallSites = desugaredCallSites;
-    this.options = options;
+    this.options = appView.options();
   }
 
   public NamingLens run(Timing timing) {
     assert options.enableMinification;
     timing.begin("MinifyClasses");
-    ClassNameMinifier classNameMinifier = new ClassNameMinifier(appInfo, rootSet, options);
+    ClassNameMinifier classNameMinifier = new ClassNameMinifier(appView, rootSet);
     ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing);
     timing.end();
 
@@ -65,16 +67,14 @@
 
     timing.begin("MinifyMethods");
     MethodRenaming methodRenaming =
-        new MethodNameMinifier(appInfo, rootSet, options)
-            .computeRenaming(desugaredCallSites, timing);
+        new MethodNameMinifier(appView, rootSet).computeRenaming(desugaredCallSites, timing);
     timing.end();
 
     assert new MinifiedRenaming(classRenaming, methodRenaming, FieldRenaming.empty(), appInfo)
         .verifyNoCollisions(appInfo.classes(), appInfo.dexItemFactory);
 
     timing.begin("MinifyFields");
-    FieldRenaming fieldRenaming =
-        new FieldNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
+    FieldRenaming fieldRenaming = new FieldNameMinifier(appView, rootSet).computeRenaming(timing);
     timing.end();
 
     NamingLens lens = new MinifiedRenaming(classRenaming, methodRenaming, fieldRenaming, appInfo);
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 1a19a39..8de2695 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -51,6 +51,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
@@ -220,6 +221,12 @@
       new SetWithReason<>(this::registerField);
 
   /**
+   * Set of service types (from META-INF/services/) that may have been instantiated reflectively via
+   * ServiceLoader.load() or ServiceLoader.loadInstalled().
+   */
+  private final Set<DexType> instantiatedAppServices = Sets.newIdentityHashSet();
+
+  /**
    * Set of interface types for which a lambda expression can be reached. These never have a single
    * interface implementation.
    */
@@ -492,6 +499,10 @@
       if (method == appInfo.dexItemFactory.enumMethods.valueOf) {
         pendingReflectiveUses.add(currentMethod);
       }
+      // Handling of application services.
+      if (appInfo.dexItemFactory.serviceLoaderMethods.isLoadMethod(method)) {
+        pendingReflectiveUses.add(currentMethod);
+      }
       if (!registerItemWithTargetAndContext(staticInvokes, method, currentMethod)) {
         return false;
       }
@@ -829,8 +840,8 @@
         // If this type has deferred annotations, we have to process those now, too.
         Set<DexAnnotation> annotations = deferredAnnotations.remove(type);
         if (annotations != null && !annotations.isEmpty()) {
-          assert !holder.accessFlags.isAnnotation()
-              || annotations.stream().allMatch(a -> a.annotation.type == holder.type);
+          assert holder.accessFlags.isAnnotation();
+          assert annotations.stream().allMatch(a -> a.annotation.type == holder.type);
           annotations.forEach(annotation -> handleAnnotation(holder, annotation));
         }
       } else {
@@ -1688,6 +1699,13 @@
             collectReachedFields(staticFields, this::tryLookupStaticField)));
   }
 
+  private void markClassAsInstantiatedWithReason(DexClass clazz, KeepReason reason) {
+    workList.add(Action.markInstantiated(clazz, reason));
+    if (clazz.hasDefaultInitializer()) {
+      workList.add(Action.markMethodLive(clazz.getDefaultInitializer(), reason));
+    }
+  }
+
   private void markClassAsInstantiatedWithCompatRule(DexClass clazz) {
     ProguardKeepRule rule = ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
     proguardCompatibilityWorkList.add(
@@ -1728,6 +1746,10 @@
       handleJavaLangEnumValueOf(method, invoke);
       return;
     }
+    if (appInfo.dexItemFactory.serviceLoaderMethods.isLoadMethod(invokedMethod)) {
+      handleServiceLoaderInvocation(method, invoke);
+      return;
+    }
     if (!isReflectionMethod(appInfo.dexItemFactory, invokedMethod)) {
       return;
     }
@@ -1797,6 +1819,55 @@
     }
   }
 
+  private void handleServiceLoaderInvocation(DexEncodedMethod method, InvokeMethod invoke) {
+    if (invoke.inValues().size() == 0) {
+      // Should never happen.
+      return;
+    }
+
+    Value argument = invoke.inValues().get(0).getAliasedValue();
+    if (!argument.isPhi() && argument.definition.isConstClass()) {
+      DexType serviceType = argument.definition.asConstClass().getValue();
+      if (!appView.appServices().allServiceTypes().contains(serviceType)) {
+        // Should never happen.
+        options.reporter.warning(
+            new StringDiagnostic(
+                "The type `"
+                    + serviceType.toSourceString()
+                    + "` is being passed to the method `"
+                    + invoke.getInvokedMethod()
+                    + "`, but could was found in `META-INF/services/`.",
+                appInfo.originFor(method.method.holder)));
+        return;
+      }
+
+      handleServiceInstantiation(serviceType, KeepReason.reflectiveUseIn(method));
+    } else {
+      KeepReason reason = KeepReason.reflectiveUseIn(method);
+      for (DexType serviceType : appView.appServices().allServiceTypes()) {
+        handleServiceInstantiation(serviceType, reason);
+      }
+    }
+  }
+
+  private void handleServiceInstantiation(DexType serviceType, KeepReason reason) {
+    instantiatedAppServices.add(serviceType);
+
+    Set<DexType> serviceImplementationTypes =
+        appView.appServices().serviceImplementationsFor(serviceType);
+    for (DexType serviceImplementationType : serviceImplementationTypes) {
+      if (!serviceImplementationType.isClassType()) {
+        // Should never happen.
+        continue;
+      }
+
+      DexClass serviceImplementationClass = appInfo.definitionFor(serviceImplementationType);
+      if (serviceImplementationClass != null && serviceImplementationClass.isProgramClass()) {
+        markClassAsInstantiatedWithReason(serviceImplementationClass, reason);
+      }
+    }
+  }
+
   private static class Action {
 
     final Kind kind;
@@ -1868,6 +1939,11 @@
     /** Set of annotation types that are instantiated. */
     final SortedSet<DexType> instantiatedAnnotationTypes;
     /**
+     * Set of service types (from META-INF/services/) that may have been instantiated reflectively
+     * via ServiceLoader.load() or ServiceLoader.loadInstalled().
+     */
+    public final SortedSet<DexType> instantiatedAppServices;
+    /**
      * Set of types that are actually instantiated. These cannot be abstract.
      */
     final SortedSet<DexType> instantiatedTypes;
@@ -1909,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.
      */
@@ -2029,6 +2105,9 @@
           ImmutableSortedSet.orderedBy(PresortedComparable<DexType>::slowCompareTo);
       enqueuer.liveAnnotations.items.forEach(annotation -> builder.add(annotation.annotation.type));
       this.instantiatedAnnotationTypes = builder.build();
+      this.instantiatedAppServices =
+          ImmutableSortedSet.copyOf(
+              PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedAppServices);
       this.instantiatedTypes = ImmutableSortedSet.copyOf(
           PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedTypes.getItems());
       this.instantiatedLambdas =
@@ -2087,6 +2166,7 @@
       super(application);
       this.liveTypes = previous.liveTypes;
       this.instantiatedAnnotationTypes = previous.instantiatedAnnotationTypes;
+      this.instantiatedAppServices = previous.instantiatedAppServices;
       this.instantiatedTypes = previous.instantiatedTypes;
       this.instantiatedLambdas = previous.instantiatedLambdas;
       this.targetedMethods = previous.targetedMethods;
@@ -2136,6 +2216,8 @@
       this.liveTypes = rewriteItems(previous.liveTypes, lense::lookupType);
       this.instantiatedAnnotationTypes =
           rewriteItems(previous.instantiatedAnnotationTypes, lense::lookupType);
+      this.instantiatedAppServices =
+          rewriteItems(previous.instantiatedAppServices, lense::lookupType);
       this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
       this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
       this.targetedMethods = lense.rewriteMethodsConservatively(previous.targetedMethods);
@@ -2214,6 +2296,7 @@
       super(previous);
       this.liveTypes = previous.liveTypes;
       this.instantiatedAnnotationTypes = previous.instantiatedAnnotationTypes;
+      this.instantiatedAppServices = previous.instantiatedAppServices;
       this.instantiatedTypes = previous.instantiatedTypes;
       this.instantiatedLambdas = previous.instantiatedLambdas;
       this.targetedMethods = previous.targetedMethods;
@@ -2276,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);
     }
@@ -2303,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/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index eb9916e..a767c86 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -57,9 +57,11 @@
     private Position keepParameterNamesOptionPosition;
     private final ProguardClassFilter.Builder adaptClassStrings = ProguardClassFilter.builder();
     private final ProguardPathFilter.Builder adaptResourceFilenames =
-        ProguardPathFilter.builder().disable();
+        ProguardPathFilter.builder()
+            .addPattern(ProguardPathList.builder().addFileName("META-INF/services/*").build());
     private final ProguardPathFilter.Builder adaptResourceFileContents =
-        ProguardPathFilter.builder().disable();
+        ProguardPathFilter.builder()
+            .addPattern(ProguardPathList.builder().addFileName("META-INF/services/*").build());
     private final ProguardPathFilter.Builder keepDirectories =
         ProguardPathFilter.builder().disable();
     private boolean forceProguardCompatibility = false;
@@ -243,18 +245,10 @@
       adaptClassStrings.addPattern(pattern);
     }
 
-    public void enableAdaptResourceFilenames() {
-      adaptResourceFilenames.enable();
-    }
-
     public void addAdaptResourceFilenames(ProguardPathList pattern) {
       adaptResourceFilenames.addPattern(pattern);
     }
 
-    public void enableAdaptResourceFileContents() {
-      adaptResourceFileContents.enable();
-    }
-
     public void addAdaptResourceFileContents(ProguardPathList pattern) {
       adaptResourceFileContents.addPattern(pattern);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 5acd2a8..eaad9a4 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -384,10 +384,8 @@
       } else if (acceptString("adaptclassstrings")) {
         parseClassFilter(configurationBuilder::addAdaptClassStringsPattern);
       } else if (acceptString("adaptresourcefilenames")) {
-        configurationBuilder.enableAdaptResourceFilenames();
         parsePathFilter(configurationBuilder::addAdaptResourceFilenames);
       } else if (acceptString("adaptresourcefilecontents")) {
-        configurationBuilder.enableAdaptResourceFileContents();
         parsePathFilter(configurationBuilder::addAdaptResourceFileContents);
       } else if (acceptString("identifiernamestring")) {
         configurationBuilder.addRule(parseIdentifierNameStringRule(optionStart));
@@ -1710,7 +1708,7 @@
       if (fileFilter == null) {
         throw parseError("Path filter expected");
       }
-      builder.addFileName(negated, fileFilter);
+      builder.addFileName(fileFilter, negated);
       skipWhitespace();
       while (acceptChar(',')) {
         skipWhitespace();
@@ -1720,7 +1718,7 @@
         if (fileFilter == null) {
           throw parseError("Path filter expected");
         }
-        builder.addFileName(negated, fileFilter);
+        builder.addFileName(fileFilter, negated);
         skipWhitespace();
       }
       return builder.build();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java b/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java
index 6766128..edcc4f6 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java
@@ -26,7 +26,11 @@
     private Builder() {
     }
 
-    public Builder addFileName(boolean isNegated, String path) {
+    public Builder addFileName(String path) {
+      return addFileName(path, false);
+    }
+
+    public Builder addFileName(String path, boolean isNegated) {
       matchers.add(new FileNameMatcher(isNegated, path));
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
index dc8925a..a59a08c 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
@@ -386,24 +386,43 @@
             wildcard = wildcards.get(wildcardIndex);
             assert wildcard.isPattern();
             wildcardPattern = wildcard.asPattern();
+
             boolean includeSeparators = pattern.length() > (i + 1) && pattern.charAt(i + 1) == '*';
-            int nextPatternIndex = i + (includeSeparators ? 2 : 1);
-            // Fast cases for the common case where a pattern ends with '**' or '*'.
+            boolean includeAll = pattern.length() > (i + 2) && pattern.charAt(i + 2) == '*';
+            int nextPatternIndex = i + 1;
+            if (includeAll) {
+              nextPatternIndex += 2;
+            } else if (includeSeparators) {
+              nextPatternIndex += 1;
+            }
+
+            // Fast cases for the common case where a pattern ends with  '*', '**', or '***'.
             if (nextPatternIndex == pattern.length()) {
-              wildcardPattern.setCaptured(name.substring(nameIndex, name.length()));
+              wildcardPattern.setCaptured(name.substring(nameIndex));
+              if (includeAll) {
+                return true;
+              }
               if (includeSeparators) {
                 return kind == ClassOrType.CLASS || !isArrayType(name);
               }
               boolean hasSeparators = containsSeparatorsStartingAt(name, nameIndex);
               return !hasSeparators && (kind == ClassOrType.CLASS || !isArrayType(name));
             }
+
             // Match the rest of the pattern against the (non-empty) rest of the class name.
             for (int nextNameIndex = nameIndex; nextNameIndex < name.length(); nextNameIndex++) {
               wildcardPattern.setCaptured(name.substring(nameIndex, nextNameIndex));
-              if (!includeSeparators && name.charAt(nextNameIndex) == '.') {
-                return matchClassOrTypeNameImpl(
-                    pattern, nextPatternIndex, name, nextNameIndex, wildcards, wildcardIndex + 1,
-                    kind);
+              if (!includeSeparators) {
+                if (name.charAt(nextNameIndex) == '.') {
+                  return matchClassOrTypeNameImpl(
+                      pattern,
+                      nextPatternIndex,
+                      name,
+                      nextNameIndex,
+                      wildcards,
+                      wildcardIndex + 1,
+                      kind);
+                }
               }
               if (kind == ClassOrType.TYPE && name.charAt(nextNameIndex) == '[') {
                 return matchClassOrTypeNameImpl(
@@ -416,11 +435,12 @@
                 return true;
               }
             }
-            // Finally, check the case where the '*' or '**' eats all of the class name.
-            wildcardPattern.setCaptured(name.substring(nameIndex, name.length()));
+
+            // Finally, check the case where the '*', '**', or '***' eats all of the class name.
+            wildcardPattern.setCaptured(name.substring(nameIndex));
             return matchClassOrTypeNameImpl(
-                pattern, nextPatternIndex, name, name.length(), wildcards, wildcardIndex + 1,
-                kind);
+                pattern, nextPatternIndex, name, name.length(), wildcards, wildcardIndex + 1, kind);
+
           case '?':
             wildcard = wildcards.get(wildcardIndex);
             assert wildcard.isPattern();
@@ -432,6 +452,7 @@
             nameIndex++;
             wildcardIndex++;
             break;
+
           case '<':
             wildcard = wildcards.get(wildcardIndex);
             assert wildcard.isBackReference();
@@ -446,6 +467,7 @@
             wildcardIndex++;
             i = pattern.indexOf(">", i);
             break;
+
           default:
             if (nameIndex == name.length() || patternChar != name.charAt(nameIndex++)) {
               return false;
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.
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 369367e..4c15524 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
@@ -39,10 +40,12 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 
 /**
  * Collection of program files needed for processing.
@@ -214,8 +217,8 @@
     }
   }
 
-  public List<DataEntryResource> getDataEntryResourcesForTesting() throws ResourceException {
-    List<DataEntryResource> out = new ArrayList<>();
+  public Set<DataEntryResource> getDataEntryResourcesForTesting() throws ResourceException {
+    Set<DataEntryResource> out = new TreeSet<>(Comparator.comparing(DataResource::getName));
     for (ProgramResourceProvider programResourceProvider : getProgramResourceProviders()) {
       DataResourceProvider dataResourceProvider = programResourceProvider.getDataResourceProvider();
       if (dataResourceProvider != null) {
@@ -229,7 +232,14 @@
 
               @Override
               public void visit(DataEntryResource file) {
-                out.add(file);
+                try {
+                  byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+                  DataEntryResource copy =
+                      DataEntryResource.fromBytes(bytes, file.getName(), file.getOrigin());
+                  out.add(copy);
+                } catch (IOException | ResourceException e) {
+                  throw new RuntimeException(e);
+                }
               }
             });
       }
@@ -355,8 +365,8 @@
         DexFilePerClassFileConsumer.ArchiveConsumer.writeResources(
             archive, resources, programResourcesMainDescriptor);
       } else if (outputMode == OutputMode.ClassFile) {
-        List<ProgramResource> resources = getClassProgramResourcesForTesting();
-        ClassFileConsumer.ArchiveConsumer.writeResources(archive, resources);
+        ClassFileConsumer.ArchiveConsumer.writeResources(
+            archive, getClassProgramResourcesForTesting(), getDataEntryResourcesForTesting());
       } else {
         throw new Unreachable("Unsupported output-mode for writing: " + outputMode);
       }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
index c7078ec..b7beb82 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
@@ -14,10 +14,13 @@
 import com.android.tools.r8.DexIndexedConsumer.ForwardingConsumer;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.origin.Origin;
+import com.google.common.io.ByteStreams;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -110,23 +113,38 @@
 
           @Override
           public DataResourceConsumer getDataResourceConsumer() {
-            assert consumer.getDataResourceConsumer() == null;
+            DataResourceConsumer dataResourceConsumer =
+                consumer != null ? consumer.getDataResourceConsumer() : null;
             return new DataResourceConsumer() {
 
               @Override
               public void accept(
                   DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
-                // Ignore.
+                if (dataResourceConsumer != null) {
+                  dataResourceConsumer.accept(directory, diagnosticsHandler);
+                }
               }
 
               @Override
               public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
-                builder.addDataResource(file);
+                try {
+                  byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+                  DataEntryResource copy =
+                      DataEntryResource.fromBytes(bytes, file.getName(), file.getOrigin());
+                  builder.addDataResource(copy);
+                  if (dataResourceConsumer != null) {
+                    dataResourceConsumer.accept(copy, diagnosticsHandler);
+                  }
+                } catch (IOException | ResourceException e) {
+                  throw new RuntimeException(e);
+                }
               }
 
               @Override
               public void finished(DiagnosticsHandler handler) {
-                // Ignore.
+                if (dataResourceConsumer != null) {
+                  dataResourceConsumer.finished(handler);
+                }
               }
             };
           }
@@ -174,6 +192,44 @@
               assert getDataResourceConsumer() != null;
             }
           }
+
+          @Override
+          public DataResourceConsumer getDataResourceConsumer() {
+            DataResourceConsumer dataResourceConsumer =
+                consumer != null ? consumer.getDataResourceConsumer() : null;
+            return new DataResourceConsumer() {
+
+              @Override
+              public void accept(
+                  DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+                if (dataResourceConsumer != null) {
+                  dataResourceConsumer.accept(directory, diagnosticsHandler);
+                }
+              }
+
+              @Override
+              public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+                try {
+                  byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+                  DataEntryResource copy =
+                      DataEntryResource.fromBytes(bytes, file.getName(), file.getOrigin());
+                  builder.addDataResource(copy);
+                  if (dataResourceConsumer != null) {
+                    dataResourceConsumer.accept(copy, diagnosticsHandler);
+                  }
+                } catch (IOException | ResourceException e) {
+                  throw new RuntimeException(e);
+                }
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {
+                if (dataResourceConsumer != null) {
+                  dataResourceConsumer.finished(handler);
+                }
+              }
+            };
+          }
         };
     programConsumer = wrapped;
     return wrapped;
@@ -208,6 +264,44 @@
               assert getDataResourceConsumer() != null;
             }
           }
+
+          @Override
+          public DataResourceConsumer getDataResourceConsumer() {
+            DataResourceConsumer dataResourceConsumer =
+                consumer != null ? consumer.getDataResourceConsumer() : null;
+            return new DataResourceConsumer() {
+
+              @Override
+              public void accept(
+                  DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+                if (dataResourceConsumer != null) {
+                  dataResourceConsumer.accept(directory, diagnosticsHandler);
+                }
+              }
+
+              @Override
+              public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+                try {
+                  byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+                  DataEntryResource copy =
+                      DataEntryResource.fromBytes(bytes, file.getName(), file.getOrigin());
+                  builder.addDataResource(copy);
+                  if (dataResourceConsumer != null) {
+                    dataResourceConsumer.accept(copy, diagnosticsHandler);
+                  }
+                } catch (IOException | ResourceException e) {
+                  throw new RuntimeException(e);
+                }
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {
+                if (dataResourceConsumer != null) {
+                  dataResourceConsumer.finished(handler);
+                }
+              }
+            };
+          }
         };
     programConsumer = wrapped;
     return wrapped;
diff --git a/src/test/java/com/android/tools/r8/TestBaseResult.java b/src/test/java/com/android/tools/r8/TestBaseResult.java
index e7623a3..390e017 100644
--- a/src/test/java/com/android/tools/r8/TestBaseResult.java
+++ b/src/test/java/com/android/tools/r8/TestBaseResult.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8;
 
+import java.util.function.Consumer;
+import java.util.function.Function;
+
 public abstract class TestBaseResult<CR extends TestBaseResult<CR, RR>, RR extends TestRunResult> {
   final TestState state;
 
@@ -12,4 +15,13 @@
   }
 
   public abstract CR self();
+
+  public <S> S map(Function<CR, S> fn) {
+    return fn.apply(self());
+  }
+
+  public CR apply(Consumer<CR> fn) {
+    fn.accept(self());
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index c67c05c..9ba822d 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -9,6 +9,8 @@
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 public abstract class TestBuilder<RR extends TestRunResult, T extends TestBuilder<RR, T>> {
 
@@ -24,6 +26,15 @@
 
   abstract T self();
 
+  public <S> S map(Function<T, S> fn) {
+    return fn.apply(self());
+  }
+
+  public T apply(Consumer<T> fn) {
+    fn.accept(self());
+    return self();
+  }
+
   public abstract RR run(String mainClass)
       throws IOException, CompilationFailedException;
 
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 89d0fff..e2f4239 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -9,12 +9,12 @@
 import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import org.hamcrest.Matcher;
 
@@ -29,6 +29,15 @@
 
   abstract RR self();
 
+  public <S> S map(Function<RR, S> fn) {
+    return fn.apply(self());
+  }
+
+  public RR apply(Consumer<RR> fn) {
+    fn.accept(self());
+    return self();
+  }
+
   public AndroidApp app() {
     return app;
   }
@@ -74,10 +83,6 @@
     return self();
   }
 
-  public <R> R map(Function<RR, R> mapper) {
-    return mapper.apply(self());
-  }
-
   public CodeInspector inspector() throws IOException, ExecutionException {
     // Inspection post run implies success. If inspection of an invalid program is needed it should
     // be done on the compilation result or on the input.
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 4f80d5e..c0b06f4 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1784,6 +1784,7 @@
         Executors.newSingleThreadExecutor(),
         application,
         null,
+        null,
         GraphLense.getIdentityLense(),
         NamingLens.getIdentityLens(),
         null,
diff --git a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
index 926a22d..b730618 100644
--- a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
@@ -91,7 +91,7 @@
     if (!code.method.qualifiedName().equals(TryRangeTestLimitRange.class.getName() + ".main")) {
       return;
     }
-    BasicBlock entryBlock = code.blocks.get(0);
+    BasicBlock entryBlock = code.entryBlock();
     BasicBlock tryBlock = code.blocks.get(1);
     assertTrue(tryBlock.hasCatchHandlers());
     ListIterator<Instruction> it = entryBlock.getInstructions().listIterator();
diff --git a/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java b/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
index f8faf9c..e72cf0a 100644
--- a/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
@@ -58,7 +58,7 @@
       // the source file or the desguared interface is to make it an inner class.
       assertEquals('$', InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX.charAt(0));
       defaultMethodName = InterfaceMethodRewriter.DEFAULT_METHOD_PREFIX + "doSomething";
-      defaultMethodThisName = "-this";
+      defaultMethodThisName = "_this";
     }
 
 
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 8d6953b..298ffe1 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -139,6 +139,7 @@
     ApplicationWriter writer =
         new ApplicationWriter(
             application,
+            null,
             options,
             null,
             null,
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index a459a9c..68b466a 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -101,7 +101,7 @@
 
     // Run code inlining a.
     test = codeForMethodReplaceTest(a, b);
-    iterator = test.code.blocks.get(0).listIterator();
+    iterator = test.code.entryBlock().listIterator();
     iterator.nextUntil(instruction -> instruction.isInvoke());
     iterator.previous();
     iterator.inlineInvoke(test.appInfo, test.code, test.additionalCode.get(0));
@@ -110,7 +110,7 @@
 
     // Run code inlining b (where a is actually called).
     test = codeForMethodReplaceTest(a, b);
-    iterator = test.code.blocks.get(0).listIterator();
+    iterator = test.code.entryBlock().listIterator();
     iterator.nextUntil(instruction -> instruction.isInvoke());
     iterator.previous();
     iterator.inlineInvoke(test.appInfo, test.code, test.additionalCode.get(1));
@@ -183,7 +183,7 @@
 
     // Run code inlining a.
     test = codeForMethodReplaceReturnVoidTest(1, 2);
-    iterator = test.code.blocks.get(0).listIterator();
+    iterator = test.code.entryBlock().listIterator();
     iterator.nextUntil(instruction -> instruction.isInvoke());
     iterator.previous();
     iterator.inlineInvoke(test.appInfo, test.code, test.additionalCode.get(0));
@@ -519,7 +519,7 @@
 
     // Run code inlining a.
     test = codeForInlineCanThrow(a, b, twoGuards);
-    iterator = test.code.blocks.get(0).listIterator();
+    iterator = test.code.entryBlock().listIterator();
     iterator.nextUntil(instruction -> instruction.isInvoke());
     iterator.previous();
     iterator.inlineInvoke(test.appInfo, test.code, test.additionalCode.get(0));
@@ -528,7 +528,7 @@
 
     // Run code inlining b (where a is actually called).
     test = codeForInlineCanThrow(a, b, twoGuards);
-    iterator = test.code.blocks.get(0).listIterator();
+    iterator = test.code.entryBlock().listIterator();
     iterator.nextUntil(instruction -> instruction.isInvoke());
     iterator.previous();
     iterator.inlineInvoke(test.appInfo, test.code, test.additionalCode.get(1));
@@ -633,7 +633,7 @@
 
     // Run code inlining a.
     test = codeForInlineAlwaysThrows(twoGuards);
-    iterator = test.code.blocks.get(0).listIterator();
+    iterator = test.code.entryBlock().listIterator();
     iterator.nextUntil(Instruction::isInvoke);
     iterator.previous();
     iterator.inlineInvoke(test.appInfo, test.code, test.additionalCode.get(0));
@@ -643,7 +643,7 @@
 
     // Run code inlining b (where a is actually called).
     test = codeForInlineAlwaysThrows(twoGuards);
-    iterator = test.code.blocks.get(0).listIterator();
+    iterator = test.code.entryBlock().listIterator();
     iterator.nextUntil(Instruction::isInvoke);
     iterator.previous();
     iterator.inlineInvoke(test.appInfo, test.code, test.additionalCode.get(1));
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index 1152903..066c51a 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -104,7 +104,7 @@
 
     public int countArgumentInstructions() {
       int count = 0;
-      ListIterator<Instruction> iterator = code.blocks.get(0).listIterator();
+      ListIterator<Instruction> iterator = code.entryBlock().listIterator();
       while (iterator.next().isArgument()) {
         count++;
       }
diff --git a/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java b/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java
index 3001b98..0a11f41 100644
--- a/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java
@@ -107,7 +107,7 @@
   @Test
   public void hasNextWillCheckNextBlock() throws Exception {
     IRCode code = simpleCode();
-    InstructionListIterator it = new LinearFlowInstructionIterator(code.blocks.get(0));
+    InstructionListIterator it = new LinearFlowInstructionIterator(code.entryBlock());
     Instruction current = it.next();
     current = it.next();
     assert it.hasNext();
@@ -116,7 +116,7 @@
   @Test
   public void nextWillContinueThroughGotoBlocks() throws Exception {
     IRCode code = simpleCode();
-    InstructionListIterator it = new LinearFlowInstructionIterator(code.blocks.get(0));
+    InstructionListIterator it = new LinearFlowInstructionIterator(code.entryBlock());
     Instruction current = it.next();
     current = it.next();
     current = it.next();
@@ -142,7 +142,7 @@
   public void GoToFrontAndBackIsSameAmountOfInstructions() throws Exception {
     IRCode code = simpleCode();
     int moves = 0;
-    InstructionListIterator it = new LinearFlowInstructionIterator(code.blocks.get(0));
+    InstructionListIterator it = new LinearFlowInstructionIterator(code.entryBlock());
     while (it.hasNext()) {
       it.next();
       moves++;
@@ -169,7 +169,7 @@
   @Test
   public void doNotChangeToNextBlockWhenNotLinearFlow() throws Exception {
     IRCode code = branchingCode();
-    InstructionListIterator it = new LinearFlowInstructionIterator(code.blocks.get(0));
+    InstructionListIterator it = new LinearFlowInstructionIterator(code.entryBlock());
     it.nextUntil((i) -> !i.isArgument());
     Instruction current = it.next();
     assert !it.hasNext();
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index c416c6e..2a6e8c8 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -83,7 +83,7 @@
       IRCode code = test.code;
       assertEquals(initialBlockCount, code.blocks.size());
 
-      BasicBlock block = code.blocks.get(0);
+      BasicBlock block = code.entryBlock();
       int instructionCount = block.getInstructions().size();
       assertEquals(firstBlockInstructions, instructionCount);
 
@@ -96,7 +96,7 @@
       assertTrue(code.isConsistentSSA());
 
       assertEquals(initialBlockCount + 1, code.blocks.size());
-      assertEquals(i + 1, code.blocks.get(0).getInstructions().size());
+      assertEquals(i + 1, code.entryBlock().getInstructions().size());
       assertEquals(instructionCount - i, code.blocks.get(1).getInstructions().size());
       assertSame(newBlock, code.blocks.get(1));
 
@@ -117,7 +117,7 @@
       IRCode code = test.code;
       assertEquals(initialBlockCount, code.blocks.size());
 
-      BasicBlock block = code.blocks.get(0);
+      BasicBlock block = code.entryBlock();
       int instructionCount = block.getInstructions().size();
       assertEquals(firstBlockInstructions, instructionCount);
 
@@ -130,7 +130,7 @@
       assertTrue(code.isConsistentSSA());
 
       assertEquals(initialBlockCount + 2, code.blocks.size());
-      assertEquals(i + 1, code.blocks.get(0).getInstructions().size());
+      assertEquals(i + 1, code.entryBlock().getInstructions().size());
       assertEquals(2, code.blocks.get(1).getInstructions().size());
       assertEquals(instructionCount - i - 1, code.blocks.get(2).getInstructions().size());
       assertSame(newBlock, code.blocks.get(1));
@@ -329,7 +329,7 @@
       IRCode code = test.code;
       assertEquals(initialBlockCount, code.blocks.size());
 
-      BasicBlock block = code.blocks.get(0);
+      BasicBlock block = code.entryBlock();
       int instructionCount = block.getInstructions().size();
       assertEquals(firstBlockInstructions, instructionCount);
 
@@ -342,7 +342,7 @@
       assertTrue(code.isConsistentSSA());
 
       assertEquals(initialBlockCount + 1, code.blocks.size());
-      assertEquals(i + 1, code.blocks.get(0).getInstructions().size());
+      assertEquals(i + 1, code.entryBlock().getInstructions().size());
       assertEquals(instructionCount - i, newBlock.getInstructions().size());
       assertSame(newBlock, code.blocks.get(1));
 
@@ -457,7 +457,7 @@
       IRCode code = test.code;
       assertEquals(initialBlockCount, code.blocks.size());
 
-      BasicBlock block = code.blocks.get(0);
+      BasicBlock block = code.entryBlock();
       int instructionCount = block.getInstructions().size();
       assertEquals(firstBlockInstructions, instructionCount);
 
@@ -470,7 +470,7 @@
       assertTrue(code.isConsistentSSA());
 
       assertEquals(initialBlockCount + 1, code.blocks.size());
-      assertEquals(i + 1, code.blocks.get(0).getInstructions().size());
+      assertEquals(i + 1, code.entryBlock().getInstructions().size());
       assertEquals(instructionCount - i, newBlock.getInstructions().size());
       assertSame(newBlock, code.blocks.get(1));
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 927bff2..0df0f97 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -145,6 +145,6 @@
     // Check that all four constant number instructions remain.
     assertEquals(
         4,
-        code.blocks.get(0).getInstructions().stream().filter(Instruction::isConstNumber).count());
+        code.entryBlock().getInstructions().stream().filter(Instruction::isConstNumber).count());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
index 6b281cb..e2f47cd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
@@ -102,7 +102,7 @@
       // Only a single basic block.
       assertEquals(1, code.blocks.size());
       // The block only has three instructions.
-      BasicBlock entryBlock = code.blocks.get(0);
+      BasicBlock entryBlock = code.entryBlock();
       assertEquals(3, entryBlock.getInstructions().size());
       // The first one is the `argument` instruction.
       Instruction argument = entryBlock.getInstructions().getFirst();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 3582403..a9e2157 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -83,7 +83,7 @@
             false,
             Origin.unknown());
     CodeRewriter.collapseTrivialGotos(null, code);
-    assertTrue(code.blocks.get(0).isTrivialGoto());
+    assertTrue(code.entryBlock().isTrivialGoto());
     assertTrue(blocks.contains(block0));
     assertTrue(blocks.contains(block1));
     assertTrue(blocks.contains(block2));
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 5103951..30db56e 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -779,6 +779,7 @@
     ApplicationWriter writer =
         new ApplicationWriter(
             application,
+            null,
             options,
             null,
             null,
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
index 0f3accb..15599ef 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.DataResourceProvider.Visitor;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestCompileResult;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
@@ -60,13 +59,24 @@
     this.backend = backend;
   }
 
-  protected static class CustomDataResourceConsumer implements DataResourceConsumer {
+  public static class DataResourceConsumerForTesting implements DataResourceConsumer {
 
+    private final DataResourceConsumer inner;
     private final Map<String, ImmutableList<String>> resources = new HashMap<>();
 
+    public DataResourceConsumerForTesting() {
+      this(null);
+    }
+
+    public DataResourceConsumerForTesting(DataResourceConsumer inner) {
+      this.inner = inner;
+    }
+
     @Override
     public void accept(DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
-      throw new Unreachable();
+      if (inner != null) {
+        inner.accept(directory, diagnosticsHandler);
+      }
     }
 
     @Override
@@ -79,6 +89,9 @@
       } catch (Exception e) {
         throw new RuntimeException(e);
       }
+      if (inner != null) {
+        inner.accept(file, diagnosticsHandler);
+      }
     }
 
     @Override
@@ -88,6 +101,10 @@
       return resources.get(name);
     }
 
+    public boolean isEmpty() {
+      return size() == 0;
+    }
+
     public int size() {
       return resources.size();
     }
@@ -175,7 +192,7 @@
   @Test
   public void testEnabled() throws Exception {
     String pgConf = getProguardConfigWithNeverInline(true, null);
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     CodeInspector inspector = compileWithR8(pgConf, dataResourceConsumer).inspector();
 
     // Check that the data resources have changed as expected.
@@ -257,7 +274,7 @@
 
   @Test
   public void testEnabledWithFilter() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(getProguardConfigWithNeverInline(true, "*.md"), dataResourceConsumer);
 
     // Check that the file matching the filter has changed as expected.
@@ -279,7 +296,7 @@
 
   @Test
   public void testDisabled() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(getProguardConfigWithNeverInline(false, null), dataResourceConsumer);
 
     // Check that all data resources are unchanged.
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
index 437f2dd..40f4249 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.naming.AdaptResourceFileContentsTest.CustomDataResourceConsumer;
+import com.android.tools.r8.naming.AdaptResourceFileContentsTest.DataResourceConsumerForTesting;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.utils.AndroidApp;
@@ -107,7 +107,7 @@
 
   @Test
   public void testEnabled() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(
         getProguardConfigWithNeverInline(true, null), dataResourceConsumer, this::checkR8Renamings);
     // Check that the generated resources have the expected names.
@@ -120,7 +120,7 @@
 
   @Test
   public void testEnabledWithFilter() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(
         getProguardConfigWithNeverInline(true, "**.md"),
         dataResourceConsumer,
@@ -138,7 +138,7 @@
 
   @Test
   public void testDisabled() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(getProguardConfigWithNeverInline(false, null), dataResourceConsumer);
     // Check that none of the resources were renamed.
     for (DataEntryResource dataResource : getOriginalDataResources()) {
@@ -150,7 +150,7 @@
 
   @Test
   public void testCollisionBehavior() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(
         getProguardConfigWithNeverInline(true, null),
         dataResourceConsumer,
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index 7b6052a..4dcb52f 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -74,9 +75,12 @@
         new RootSetBuilder(appView, program, configuration.getRules(), options).run(executor);
 
     Enqueuer enqueuer = new Enqueuer(appView, options, null, options.forceProguardCompatibility);
-    AppInfoWithSubtyping appInfo =
+    AppInfoWithLiveness appInfo =
         enqueuer.traceApplication(rootSet, configuration.getDontWarnPatterns(), executor, timing);
-    return new Minifier(appInfo.withLiveness(), rootSet, Collections.emptySet(), options)
+    return new Minifier(
+        new AppView<>(appInfo, GraphLense.getIdentityLense(), options),
+        rootSet,
+        Collections.emptySet())
         .run(timing);
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
index 3112ae7..659a8fc 100644
--- a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
+++ b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
@@ -7,19 +7,21 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.allOf;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.lang.reflect.Method;
 import java.lang.reflect.ParameterizedType;
@@ -27,7 +29,7 @@
 
 public class B124357885Test extends TestBase {
 
-  private void checkSignatureAnnotation(AnnotationSubject signature) {
+  private void checkSignatureAnnotation(CodeInspector inspector, AnnotationSubject signature) {
     DexAnnotationElement[] elements = signature.getAnnotation().elements;
     assertEquals(1, elements.length);
     assertEquals("value", elements[0].name.toString());
@@ -38,20 +40,22 @@
       assertTrue(value instanceof DexValueString);
       builder.append(((DexValueString) value).value);
     }
-    // TODO(124357885): This should be the minified name for FooImpl instead of Foo.
-    String fooDescriptor = DescriptorUtils.javaTypeToDescriptor(Foo.class.getTypeName());
+    String fooImplFinalDescriptor =
+        DescriptorUtils.javaTypeToDescriptor(inspector.clazz(FooImpl.class).getFinalName());
     StringBuilder expected =
         new StringBuilder()
             .append("()")
-            .append(fooDescriptor.substring(0, fooDescriptor.length() - 1))  // Remove the ;.
+            // Remove the final ; from the descriptor to add the generic type.
+            .append(fooImplFinalDescriptor.substring(0, fooImplFinalDescriptor.length() - 1))
             .append("<Ljava/lang/String;>")
-            .append(";");  // Add the ; here.
+            // Add the ; after the generic type.
+            .append(";");
     assertEquals(expected.toString(), builder.toString());
   }
 
   @Test
   public void test() throws Exception {
-    testForR8(Backend.DEX)
+    R8TestCompileResult compileResult = testForR8(Backend.DEX)
         .addProgramClasses(Main.class, Service.class, Foo.class, FooImpl.class)
         .addKeepMainRule(Main.class)
         .addKeepRules("-keepattributes Signature,InnerClasses,EnclosingMethod")
@@ -65,13 +69,14 @@
           assertEquals(1, inspector.clazz(Service.class).allMethods().size());
           MethodSubject fooList = inspector.clazz(Service.class).allMethods().get(0);
           AnnotationSubject signature = fooList.annotation("dalvik.annotation.Signature");
-          checkSignatureAnnotation(signature);
-        })
-        .run(Main.class)
-        .assertFailureWithErrorThatMatches(
-            containsString(
-                "java.lang.ClassNotFoundException: "
-                    + "Didn't find class \"com.android.tools.r8.naming.b124357885.Foo\""));
+          checkSignatureAnnotation(inspector, signature);
+        });
+
+        String fooImplFinalName = compileResult.inspector().clazz(FooImpl.class).getFinalName();
+
+        compileResult
+            .run(Main.class)
+            .assertSuccessWithOutput(StringUtils.lines(fooImplFinalName, fooImplFinalName));
   }
 }
 
@@ -84,7 +89,7 @@
 
     // Convince R8 we only use subtypes to get class merging of Foo into FooImpl.
     Foo<String> foo = new FooImpl<>();
-    System.out.println(foo);
+    System.out.println(foo.getClass().getTypeName());
   }
 }
 
@@ -94,4 +99,4 @@
 
 interface Foo<T> {}
 
-class FooImpl<T> implements Foo<T> {}
\ No newline at end of file
+class FooImpl<T> implements Foo<T> {}
diff --git a/src/test/java/com/android/tools/r8/proguard/rules/ProguardMatchAllRuleWithPrefixTest.java b/src/test/java/com/android/tools/r8/proguard/rules/ProguardMatchAllRuleWithPrefixTest.java
new file mode 100644
index 0000000..d5f456b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/proguard/rules/ProguardMatchAllRuleWithPrefixTest.java
@@ -0,0 +1,59 @@
+// 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.proguard.rules;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+
+/** Regression test for b/124584385. */
+public class ProguardMatchAllRuleWithPrefixTest extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addProgramClasses(TestClass.class)
+            .addKeepRules(
+                "-keep,allowobfuscation class com.android.tools.r8.*** {",
+                "  com.android.tools.r8.*** methodA();",
+                "  com.android.tools.r8.***Class methodB();",
+                "  com.android.tools.r8.***[] methodC();",
+                "  com.android.tools.r8.***Class[] methodD();",
+                "}")
+            .compile()
+            .inspector();
+
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject.uniqueMethodWithName("methodA"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("methodB"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("methodC"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("methodD"), isPresent());
+  }
+
+  static class TestClass {
+
+    TestClass methodA() {
+      return new TestClass();
+    }
+
+    TestClass methodB() {
+      return new TestClass();
+    }
+
+    TestClass[] methodC() {
+      return new TestClass[0];
+    }
+
+    TestClass[] methodD() {
+      return new TestClass[0];
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
index e005a2c..29e5bcd 100644
--- a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -4,11 +4,20 @@
 
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.naming.AdaptResourceFileContentsTest.DataResourceConsumerForTesting;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.Lists;
 import java.util.List;
@@ -21,14 +30,18 @@
 @RunWith(Parameterized.class)
 public class ServiceLoaderTest extends TestBase {
 
+  private final Backend backend;
   private final boolean includeWorldGreeter;
 
-  @Parameters(name = "Include WorldGreeter: {0}")
-  public static Boolean[] data() {
-    return BooleanUtils.values();
+  private DataResourceConsumerForTesting dataResourceConsumer;
+
+  @Parameters(name = "Backend: {0}, include WorldGreeter: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(Backend.values(), BooleanUtils.values());
   }
 
-  public ServiceLoaderTest(boolean includeWorldGreeter) {
+  public ServiceLoaderTest(Backend backend, boolean includeWorldGreeter) {
+    this.backend = backend;
     this.includeWorldGreeter = includeWorldGreeter;
   }
 
@@ -42,26 +55,84 @@
     }
 
     CodeInspector inspector =
-        testForR8(Backend.DEX)
+        testForR8(backend)
             .addInnerClasses(ServiceLoaderTest.class)
             .addKeepMainRule(TestClass.class)
-            // TODO(b/124181030): Test should work without the following keep-all-rules.
-            .addKeepAllClassesRule()
-            .addKeepAllInterfacesRule()
             .addDataEntryResources(
                 DataEntryResource.fromBytes(
                     StringUtils.lines(serviceImplementations).getBytes(),
                     "META-INF/services/" + Greeter.class.getTypeName(),
                     Origin.unknown()))
+            .addOptionsModification(
+                options -> {
+                  dataResourceConsumer =
+                      new DataResourceConsumerForTesting(options.dataResourceConsumer);
+                  options.dataResourceConsumer = dataResourceConsumer;
+                })
             .run(TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
 
-    // TODO(b/124181030): Verify that Greeter is merged into HelloGreeter when `includeWorldGreeter`
-    //  is false.
+    ClassSubject greeterSubject = inspector.clazz(Greeter.class);
+    assertEquals(includeWorldGreeter, greeterSubject.isPresent());
 
-    // TODO(b/124181030): Verify that META-INF/services/...WorldGreeter is removed when
-    //  `includeWorldGreeter` is false.
+    ClassSubject helloGreeterSubject = inspector.clazz(HelloGreeter.class);
+    assertThat(helloGreeterSubject, isPresent());
+
+    ClassSubject worldGreeterSubject = inspector.clazz(WorldGreeter.class);
+    assertEquals(includeWorldGreeter, worldGreeterSubject.isPresent());
+
+    String serviceFileName =
+        includeWorldGreeter ? greeterSubject.getFinalName() : helloGreeterSubject.getFinalName();
+    List<String> lines =
+        dataResourceConsumer.get(AppServices.SERVICE_DIRECTORY_NAME + serviceFileName);
+    assertEquals(includeWorldGreeter ? 2 : 1, lines.size());
+    assertEquals(helloGreeterSubject.getFinalName(), lines.get(0));
+    if (includeWorldGreeter) {
+      assertEquals(worldGreeterSubject.getFinalName(), lines.get(1));
+    }
+
+    // TODO(b/124181030): Verify that -whyareyoukeeping works as intended.
+  }
+
+  @Test
+  public void testResourceElimination() throws Exception {
+    String expectedOutput = "Hello world!";
+
+    List<String> serviceImplementations = Lists.newArrayList(HelloGreeter.class.getTypeName());
+    if (includeWorldGreeter) {
+      serviceImplementations.add(WorldGreeter.class.getTypeName());
+    }
+
+    CodeInspector inspector =
+        testForR8(backend)
+            .addInnerClasses(ServiceLoaderTest.class)
+            .addKeepMainRule(OtherTestClass.class)
+            .addDataEntryResources(
+                DataEntryResource.fromBytes(
+                    StringUtils.lines(serviceImplementations).getBytes(),
+                    "META-INF/services/" + Greeter.class.getTypeName(),
+                    Origin.unknown()))
+            .addOptionsModification(
+                options -> {
+                  dataResourceConsumer =
+                      new DataResourceConsumerForTesting(options.dataResourceConsumer);
+                  options.dataResourceConsumer = dataResourceConsumer;
+                })
+            .run(OtherTestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject greeterSubject = inspector.clazz(Greeter.class);
+    assertThat(greeterSubject, not(isPresent()));
+
+    ClassSubject helloGreeterSubject = inspector.clazz(HelloGreeter.class);
+    assertThat(helloGreeterSubject, not(isPresent()));
+
+    ClassSubject worldGreeterSubject = inspector.clazz(WorldGreeter.class);
+    assertThat(worldGreeterSubject, not(isPresent()));
+
+    assertTrue(dataResourceConsumer.isEmpty());
   }
 
   static class TestClass {
@@ -73,6 +144,13 @@
     }
   }
 
+  static class OtherTestClass {
+
+    public static void main(String[] args) {
+      System.out.print("Hello world!");
+    }
+  }
+
   public interface Greeter {
 
     String greeting();
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index fb61a57..b99cbdc 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -113,6 +113,7 @@
       ApplicationWriter writer =
           new ApplicationWriter(
               dexApp,
+              null,
               options,
               null,
               null,
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index aa083da..f585226 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -321,6 +321,14 @@
     return obfuscatedTypeName != null ? obfuscatedTypeName : originalTypeName;
   }
 
+  String getOriginalTypeName(String minifiedTypeName) {
+    String originalTypeName = null;
+    if (mapping != null) {
+      originalTypeName = mapType(obfuscatedToOriginalMapping, minifiedTypeName);
+    }
+    return originalTypeName != null ? originalTypeName : minifiedTypeName;
+  }
+
   InstructionSubject createInstructionSubject(Instruction instruction) {
     DexInstructionSubject dexInst = new DexInstructionSubject(instruction);
     if (dexInst.isInvoke()) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 6581a47..dd7cd85 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -143,18 +143,14 @@
     //     X method(X) -> a
     //
     // whereas the final signature is for X.a is "a (a)"
-    String[] OriginalParameters = new String[signature.parameters.length];
-    for (int i = 0; i < OriginalParameters.length; i++) {
-      String obfuscated = signature.parameters[i];
-      String original = codeInspector.obfuscatedToOriginalMapping.get(obfuscated);
-      OriginalParameters[i] = original != null ? original : obfuscated;
+    String[] originalParameters = new String[signature.parameters.length];
+    for (int i = 0; i < originalParameters.length; i++) {
+      originalParameters[i] = codeInspector.getOriginalTypeName(signature.parameters[i]);
     }
-    String obfuscatedReturnType = signature.type;
-    String originalReturnType = codeInspector.obfuscatedToOriginalMapping.get(obfuscatedReturnType);
-    String returnType = originalReturnType != null ? originalReturnType : obfuscatedReturnType;
+    String returnType = codeInspector.getOriginalTypeName(signature.type);
 
     MethodSignature lookupSignature =
-        new MethodSignature(signature.name, returnType, OriginalParameters);
+        new MethodSignature(signature.name, returnType, originalParameters);
 
     MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
     return memberNaming != null ? (MethodSignature) memberNaming.getOriginalSignature() : signature;