Merge commit '4b1e8d39e2fc46d3275b81fff407a41ad7d9b1e1' into dev-release
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 6e3764a..7c4ac49 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -34,6 +34,7 @@
       private Path proguardMapFile = null;
       private boolean useSmali = false;
       private boolean allInfo = false;
+      private boolean noCode = false;
       private boolean useIr;
 
       @Override
@@ -70,6 +71,11 @@
         return this;
       }
 
+      public Builder setNoCode(boolean noCode) {
+        this.noCode = noCode;
+        return this;
+      }
+
       @Override
       protected DisassembleCommand makeCommand() {
         // If printing versions ignore everything else.
@@ -82,7 +88,8 @@
             proguardMapFile == null ? null : StringResource.fromFile(proguardMapFile),
             allInfo,
             useSmali,
-            useIr);
+            useIr,
+            noCode);
       }
     }
 
@@ -93,6 +100,7 @@
             + "  --all                       # Include all information in disassembly.\n"
             + "  --smali                     # Disassemble using smali syntax.\n"
             + "  --ir                        # Print IR before and after optimization.\n"
+            + "  --nocode                    # No printing of code objects.\n"
             + "  --pg-map <file>             # Proguard map <file> for mapping names.\n"
             + "  --pg-map-charset <charset>  # Charset for Proguard map file.\n"
             + "  --output                    # Specify a file or directory to write to.\n"
@@ -102,6 +110,7 @@
     private final boolean allInfo;
     private final boolean useSmali;
     private final boolean useIr;
+    private final boolean noCode;
 
     public static Builder builder() {
       return new Builder();
@@ -128,6 +137,8 @@
           builder.setUseSmali(true);
         } else if (arg.equals("--ir")) {
           builder.setUseIr(true);
+        } else if (arg.equals("--nocode")) {
+          builder.setNoCode(true);
         } else if (arg.equals("--pg-map")) {
           builder.setProguardMapFile(Paths.get(args[++i]));
         } else if (arg.equals("--pg-map-charset")) {
@@ -161,13 +172,15 @@
         StringResource proguardMap,
         boolean allInfo,
         boolean useSmali,
-        boolean useIr) {
+        boolean useIr,
+        boolean noCode) {
       super(inputApp);
       this.outputPath = outputPath;
       this.proguardMap = proguardMap;
       this.allInfo = allInfo;
       this.useSmali = useSmali;
       this.useIr = useIr;
+      this.noCode = noCode;
     }
 
     private DisassembleCommand(boolean printHelp, boolean printVersion) {
@@ -177,6 +190,7 @@
       allInfo = false;
       useSmali = false;
       useIr = false;
+      noCode = false;
     }
 
     public Path getOutputPath() {
@@ -191,6 +205,10 @@
       return useIr;
     }
 
+    public boolean noCode() {
+      return noCode;
+    }
+
     @Override
     InternalOptions getInternalOptions() {
       InternalOptions internal = new InternalOptions();
@@ -226,7 +244,8 @@
       DexByteCodeWriter writer =
           command.useSmali()
               ? new SmaliWriter(application, options)
-              : new AssemblyWriter(application, options, command.allInfo, command.useIr());
+              : new AssemblyWriter(
+                  application, options, command.allInfo, command.useIr(), !command.noCode());
       if (command.getOutputPath() != null) {
         writer.write(command.getOutputPath());
       } else {
diff --git a/src/main/java/com/android/tools/r8/FeatureSplit.java b/src/main/java/com/android/tools/r8/FeatureSplit.java
index 5947786..9bb637e 100644
--- a/src/main/java/com/android/tools/r8/FeatureSplit.java
+++ b/src/main/java/com/android/tools/r8/FeatureSplit.java
@@ -29,6 +29,9 @@
  */
 @Keep
 public final class FeatureSplit {
+
+  public static final FeatureSplit BASE = new FeatureSplit(null, null);
+
   private final ProgramConsumer programConsumer;
   private final List<ProgramResourceProvider> programResourceProviders;
 
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 86abac6..9d93b40 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -183,6 +183,14 @@
     return null;
   }
 
+  public boolean isInvokeVirtualRange() {
+    return false;
+  }
+
+  public InvokeVirtualRange asInvokeVirtualRange() {
+    return null;
+  }
+
   public boolean isSimpleNop() {
     return !isPayload() && this instanceof Nop;
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
index 64e9b6d..cd78458 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
@@ -39,6 +39,16 @@
   }
 
   @Override
+  public boolean isInvokeVirtualRange() {
+    return true;
+  }
+
+  @Override
+  public InvokeVirtualRange asInvokeVirtualRange() {
+    return this;
+  }
+
+  @Override
   public void registerUse(UseRegistry registry) {
     registry.registerInvokeVirtual(getMethod());
   }
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index 9eb7caa..f4e85a8 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
@@ -36,6 +37,8 @@
 
   abstract void recordClassAllAccesses(DexType type);
 
+  abstract void recordHierarchyOf(DexProgramClass clazz);
+
   abstract boolean isNop();
 
   abstract void generateKeepRules(InternalOptions options);
@@ -116,6 +119,14 @@
       }
     }
 
+    @Override
+    void recordHierarchyOf(DexProgramClass clazz) {
+      recordClassAllAccesses(clazz.superType);
+      for (DexType itf : clazz.interfaces.values) {
+        recordClassAllAccesses(itf);
+      }
+    }
+
     private void keepClass(DexType type) {
       DexType baseType = type.lookupBaseType(options.itemFactory);
       toKeep.putIfAbsent(baseType, new KeepStruct());
@@ -193,6 +204,9 @@
     void recordClassAllAccesses(DexType type) {}
 
     @Override
+    void recordHierarchyOf(DexProgramClass clazz) {}
+
+    @Override
     boolean isNop() {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 0047bf8..8d1eac7 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -467,6 +467,8 @@
   }
 
   private void writeClassDefItem(DexProgramClass clazz) {
+    desugaredLibraryCodeToKeep.recordHierarchyOf(clazz);
+
     dest.putInt(mapping.getOffsetFor(clazz.type));
     dest.putInt(clazz.accessFlags.getAsDexAccessFlags());
     dest.putInt(
@@ -659,10 +661,6 @@
 
   private void writeClassData(DexProgramClass clazz) {
     assert clazz.hasMethodsOrFields();
-    desugaredLibraryCodeToKeep.recordClassAllAccesses(clazz.superType);
-    for (DexType itf : clazz.interfaces.values) {
-      desugaredLibraryCodeToKeep.recordClassAllAccesses(itf);
-    }
     mixedSectionOffsets.setOffsetFor(clazz, dest.position());
     dest.putUleb128(clazz.staticFields().size());
     dest.putUleb128(clazz.instanceFields().size());
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index 5c1c7ad..0dbaa30 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.FeatureSplit;
+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;
@@ -21,7 +23,7 @@
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -36,9 +38,9 @@
   private final AppView<?> appView;
 
   // Mapping from service types to service implementation types.
-  private final Map<DexType, List<DexType>> services;
+  private final Map<DexType, Map<FeatureSplit, List<DexType>>> services;
 
-  private AppServices(AppView<?> appView, Map<DexType, List<DexType>> services) {
+  private AppServices(AppView<?> appView, Map<DexType, Map<FeatureSplit, List<DexType>>> services) {
     this.appView = appView;
     this.services = services;
   }
@@ -54,66 +56,124 @@
 
   public List<DexType> serviceImplementationsFor(DexType serviceType) {
     assert verifyRewrittenWithLens();
-    assert services.containsKey(serviceType);
-    List<DexType> serviceImplementationTypes = services.get(serviceType);
-    if (serviceImplementationTypes == null) {
+    Map<FeatureSplit, List<DexType>> featureSplitListMap = services.get(serviceType);
+    if (featureSplitListMap == null) {
       assert false
           : "Unexpected attempt to get service implementations for non-service type `"
               + serviceType.toSourceString()
               + "`";
       return ImmutableList.of();
     }
-    return serviceImplementationTypes;
+    ImmutableList.Builder<DexType> builder = ImmutableList.builder();
+    for (List<DexType> implementations : featureSplitListMap.values()) {
+      builder.addAll(implementations);
+    }
+    return builder.build();
+  }
+
+  public boolean hasServiceImplementationsInFeature(DexType serviceType) {
+    if (appView.options().featureSplitConfiguration == null) {
+      return false;
+    }
+    Map<FeatureSplit, List<DexType>> featureImplementations = services.get(serviceType);
+    if (featureImplementations == null || featureImplementations.isEmpty()) {
+      assert false
+          : "Unexpected attempt to get service implementations for non-service type `"
+              + serviceType.toSourceString()
+              + "`";
+      return true;
+    }
+    if (featureImplementations.size() > 1
+        || !featureImplementations.containsKey(FeatureSplit.BASE)) {
+      return true;
+    }
+    // Check if service is defined feature
+    DexProgramClass serviceClass = appView.definitionForProgramType(serviceType);
+    if (appView.options().featureSplitConfiguration.isInFeature(serviceClass)) {
+      return true;
+    }
+    for (DexType dexType : featureImplementations.get(FeatureSplit.BASE)) {
+      DexProgramClass implementationClass = appView.definitionForProgramType(dexType);
+      if (appView.options().featureSplitConfiguration.isInFeature(implementationClass)) {
+        return true;
+      }
+    }
+    return false;
   }
 
   public AppServices rewrittenWithLens(GraphLense graphLens) {
-    ImmutableMap.Builder<DexType, List<DexType>> rewrittenServices = ImmutableMap.builder();
-    for (Entry<DexType, List<DexType>> entry : services.entrySet()) {
+    ImmutableMap.Builder<DexType, Map<FeatureSplit, List<DexType>>> rewrittenFeatureMappings =
+        ImmutableMap.builder();
+    for (Entry<DexType, Map<FeatureSplit, List<DexType>>> entry : services.entrySet()) {
       DexType rewrittenServiceType = graphLens.lookupType(entry.getKey());
-      ImmutableList.Builder<DexType> rewrittenServiceImplementationTypes = ImmutableList.builder();
-      for (DexType serviceImplementationType : entry.getValue()) {
-        rewrittenServiceImplementationTypes.add(graphLens.lookupType(serviceImplementationType));
+      ImmutableMap.Builder<FeatureSplit, List<DexType>> rewrittenFeatureImplementations =
+          ImmutableMap.builder();
+      for (Entry<FeatureSplit, List<DexType>> featureSplitImpls : entry.getValue().entrySet()) {
+        ImmutableList.Builder<DexType> rewrittenServiceImplementationTypes =
+            ImmutableList.builder();
+        for (DexType serviceImplementationType : featureSplitImpls.getValue()) {
+          rewrittenServiceImplementationTypes.add(graphLens.lookupType(serviceImplementationType));
+        }
+        rewrittenFeatureImplementations.put(
+            featureSplitImpls.getKey(), rewrittenServiceImplementationTypes.build());
       }
-      rewrittenServices.put(rewrittenServiceType, rewrittenServiceImplementationTypes.build());
+      rewrittenFeatureMappings.put(rewrittenServiceType, rewrittenFeatureImplementations.build());
     }
-    return new AppServices(appView, rewrittenServices.build());
+    return new AppServices(appView, rewrittenFeatureMappings.build());
   }
 
   public AppServices prunedCopy(Collection<DexType> removedClasses) {
-    ImmutableMap.Builder<DexType, List<DexType>> rewrittenServicesBuilder = ImmutableMap.builder();
-    for (Entry<DexType, List<DexType>> entry : services.entrySet()) {
-      if (!removedClasses.contains(entry.getKey())) {
-        DexType serviceType = entry.getKey();
+    ImmutableMap.Builder<DexType, Map<FeatureSplit, List<DexType>>> rewrittenServicesBuilder =
+        ImmutableMap.builder();
+    for (Entry<DexType, Map<FeatureSplit, List<DexType>>> entry : services.entrySet()) {
+      if (removedClasses.contains(entry.getKey())) {
+        continue;
+      }
+      ImmutableMap.Builder<FeatureSplit, List<DexType>> prunedFeatureSplitImpls =
+          ImmutableMap.builder();
+      for (Entry<FeatureSplit, List<DexType>> featureSplitEntry : entry.getValue().entrySet()) {
         ImmutableList.Builder<DexType> rewrittenServiceImplementationTypesBuilder =
             ImmutableList.builder();
-        for (DexType serviceImplementationType : entry.getValue()) {
+        for (DexType serviceImplementationType : featureSplitEntry.getValue()) {
           if (!removedClasses.contains(serviceImplementationType)) {
             rewrittenServiceImplementationTypesBuilder.add(serviceImplementationType);
           }
         }
-        List<DexType> rewrittenServiceImplementationTypes =
+        List<DexType> prunedFeatureSplitImplementations =
             rewrittenServiceImplementationTypesBuilder.build();
-        if (rewrittenServiceImplementationTypes.size() > 0) {
-          rewrittenServicesBuilder.put(
-              serviceType, rewrittenServiceImplementationTypesBuilder.build());
+        if (prunedFeatureSplitImplementations.size() > 0) {
+          prunedFeatureSplitImpls.put(
+              featureSplitEntry.getKey(), rewrittenServiceImplementationTypesBuilder.build());
         }
       }
+      ImmutableMap<FeatureSplit, List<DexType>> prunedServiceImplementations =
+          prunedFeatureSplitImpls.build();
+      if (prunedServiceImplementations.size() > 0) {
+        rewrittenServicesBuilder.put(entry.getKey(), prunedServiceImplementations);
+      }
     }
     return new AppServices(appView, rewrittenServicesBuilder.build());
   }
 
   private boolean verifyRewrittenWithLens() {
-    for (Entry<DexType, List<DexType>> entry : services.entrySet()) {
+    for (Entry<DexType, Map<FeatureSplit, List<DexType>>> entry : services.entrySet()) {
       assert entry.getKey() == appView.graphLense().lookupType(entry.getKey());
-      for (DexType type : entry.getValue()) {
-        assert type == appView.graphLense().lookupType(type);
+      for (Entry<FeatureSplit, List<DexType>> featureEntry : entry.getValue().entrySet()) {
+        for (DexType type : featureEntry.getValue()) {
+          assert type == appView.graphLense().lookupType(type);
+        }
       }
     }
     return true;
   }
 
   public void visit(BiConsumer<DexType, List<DexType>> consumer) {
-    services.forEach(consumer);
+    services.forEach(
+        (type, featureImpls) -> {
+          ImmutableList.Builder<DexType> builder = ImmutableList.builder();
+          featureImpls.values().forEach(builder::addAll);
+          consumer.accept(type, builder.build());
+        });
   }
 
   public static Builder builder(AppView<?> appView) {
@@ -123,7 +183,7 @@
   public static class Builder {
 
     private final AppView<?> appView;
-    private final Map<DexType, List<DexType>> services = new IdentityHashMap<>();
+    private final Map<DexType, Map<FeatureSplit, List<DexType>>> services = new LinkedHashMap<>();
 
     private Builder(AppView<?> appView) {
       this.appView = appView;
@@ -131,14 +191,27 @@
 
     public AppServices build() {
       for (DataResourceProvider provider : appView.appInfo().app().dataResourceProviders) {
-        readServices(provider);
+        readServices(provider, FeatureSplit.BASE);
+      }
+      if (appView.options().featureSplitConfiguration != null) {
+        List<FeatureSplit> featureSplits =
+            appView.options().featureSplitConfiguration.getFeatureSplits();
+        for (FeatureSplit featureSplit : featureSplits) {
+          for (ProgramResourceProvider provider : featureSplit.getProgramResourceProviders()) {
+            DataResourceProvider dataResourceProvider = provider.getDataResourceProvider();
+            if (dataResourceProvider != null) {
+              readServices(dataResourceProvider, featureSplit);
+            }
+          }
+        }
       }
       return new AppServices(appView, services);
     }
 
-    private void readServices(DataResourceProvider dataResourceProvider) {
+    private void readServices(
+        DataResourceProvider dataResourceProvider, FeatureSplit featureSplit) {
       try {
-        dataResourceProvider.accept(new DataResourceProviderVisitor());
+        dataResourceProvider.accept(new DataResourceProviderVisitor(featureSplit));
       } catch (ResourceException e) {
         throw new CompilationError(e.getMessage(), e);
       }
@@ -146,6 +219,12 @@
 
     private class DataResourceProviderVisitor implements Visitor {
 
+      private final FeatureSplit featureSplit;
+
+      public DataResourceProviderVisitor(FeatureSplit featureSplit) {
+        this.featureSplit = featureSplit;
+      }
+
       @Override
       public void visit(DataDirectoryResource directory) {
         // Ignore.
@@ -162,8 +241,10 @@
               DexType serviceType = appView.dexItemFactory().createType(serviceDescriptor);
               byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
               String contents = new String(bytes, Charset.defaultCharset());
+              Map<FeatureSplit, List<DexType>> featureSplitImplementations =
+                  services.computeIfAbsent(serviceType, k -> new LinkedHashMap<>());
               List<DexType> serviceImplementations =
-                  services.computeIfAbsent(serviceType, (key) -> new ArrayList<>());
+                  featureSplitImplementations.computeIfAbsent(featureSplit, f -> new ArrayList<>());
               readServiceImplementationsForService(
                   contents, file.getOrigin(), serviceImplementations);
             }
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 3cd5fc5..d10c69f 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -30,17 +30,23 @@
   private final boolean writeFields;
   private final boolean writeAnnotations;
   private final boolean writeIR;
+  private final boolean writeCode;
   private final AppInfoWithClassHierarchy appInfo;
   private final Kotlin kotlin;
   private final Timing timing = new Timing("AssemblyWriter");
 
   public AssemblyWriter(
-      DexApplication application, InternalOptions options, boolean allInfo, boolean writeIR) {
+      DexApplication application,
+      InternalOptions options,
+      boolean allInfo,
+      boolean writeIR,
+      boolean writeCode) {
     super(application, options);
     this.writeAllClassInfo = allInfo;
     this.writeFields = allInfo;
     this.writeAnnotations = allInfo;
     this.writeIR = writeIR;
+    this.writeCode = writeCode;
     if (writeIR) {
       this.appInfo = new AppInfoWithClassHierarchy(application.toDirect());
       if (options.programConsumer == null) {
@@ -128,6 +134,9 @@
     ps.println("# " + definition.accessFlags);
     ps.println("#");
     ps.println();
+    if (!writeCode) {
+      return;
+    }
     Code code = definition.getCode();
     if (code != null) {
       if (writeIR) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 1825fb3..ae39752 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -542,8 +542,10 @@
     return false;
   }
 
-  public synchronized DexEncodedMethod getClassInitializer() {
-    return methodCollection.getClassInitializer();
+  public DexEncodedMethod getClassInitializer() {
+    DexEncodedMethod classInitializer = methodCollection.getClassInitializer();
+    assert classInitializer != DexEncodedMethod.SENTINEL;
+    return classInitializer;
   }
 
   public Origin getOrigin() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 2269517..4c0c7c5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -120,7 +120,11 @@
   public static final DexEncodedMethod[] EMPTY_ARRAY = {};
   public static final DexEncodedMethod SENTINEL =
       new DexEncodedMethod(
-          null, null, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null);
+          null,
+          MethodAccessFlags.fromDexAccessFlags(0),
+          DexAnnotationSet.empty(),
+          ParameterAnnotationsList.empty(),
+          null);
   public static final Int2ReferenceMap<DebugLocalInfo> NO_PARAMETER_INFO =
       new Int2ReferenceArrayMap<>(0);
 
@@ -247,6 +251,7 @@
     this.code = code;
     this.classFileVersion = classFileVersion;
     this.d8R8Synthesized = d8R8Synthesized;
+    assert accessFlags != null;
     assert code == null || !shouldNotHaveCode();
     assert parameterAnnotationsList != null;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 423acef..0a5014a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -230,8 +230,8 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems,
-      DexMethod method, int instructionOffset) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
     if (indexedItems.addClass(this)) {
       type.collectIndexedItems(indexedItems, method, instructionOffset);
       if (superType != null) {
@@ -260,8 +260,8 @@
     }
   }
 
-  private static <T extends DexItem> void synchronizedCollectAll(IndexedItemCollection collection,
-      T[] items) {
+  private static <T extends DexItem> void synchronizedCollectAll(
+      IndexedItemCollection collection, T[] items) {
     synchronized (items) {
       collectAll(collection, items);
     }
@@ -447,6 +447,50 @@
     methodCollection.addDirectMethod(directMethod);
   }
 
+  public void addExtraInterfaces(List<DexType> extraInterfaces, DexItemFactory factory) {
+    if (extraInterfaces.isEmpty()) {
+      return;
+    }
+    addExtraInterfacesToInterfacesArray(extraInterfaces);
+    addExtraInterfacesToSignatureAnnotationIfPresent(extraInterfaces, factory);
+  }
+
+  private void addExtraInterfacesToInterfacesArray(List<DexType> extraInterfaces) {
+    DexType[] newInterfaces =
+        Arrays.copyOf(interfaces.values, interfaces.size() + extraInterfaces.size());
+    for (int i = interfaces.size(); i < newInterfaces.length; i++) {
+      newInterfaces[i] = extraInterfaces.get(i - interfaces.size());
+    }
+    interfaces = new DexTypeList(newInterfaces);
+  }
+
+  private void addExtraInterfacesToSignatureAnnotationIfPresent(
+      List<DexType> extraInterfaces, DexItemFactory factory) {
+    // We need to introduce in the dalvik.annotation.Signature annotation the extra interfaces.
+    // At this point we cheat and pretend the extraInterfaces simply don't use any generic types.
+    DexAnnotation[] annotations = annotations().annotations;
+    for (int i = 0; i < annotations.length; i++) {
+      DexAnnotation annotation = annotations[i];
+      if (DexAnnotation.isSignatureAnnotation(annotation, factory)) {
+        DexAnnotation[] rewrittenAnnotations = annotations.clone();
+        rewrittenAnnotations[i] = rewriteSignatureAnnotation(annotation, extraInterfaces, factory);
+        setAnnotations(new DexAnnotationSet(rewrittenAnnotations));
+        // There is at most one signature annotation, so we can return here.
+        return;
+      }
+    }
+  }
+
+  private DexAnnotation rewriteSignatureAnnotation(
+      DexAnnotation annotation, List<DexType> extraInterfaces, DexItemFactory factory) {
+    String signature = DexAnnotation.getSignature(annotation);
+    StringBuilder newSignatureBuilder = new StringBuilder(signature);
+    for (DexType extraInterface : extraInterfaces) {
+      newSignatureBuilder.append(extraInterface.descriptor.toString());
+    }
+    return DexAnnotation.createSignatureAnnotation(newSignatureBuilder.toString(), factory);
+  }
+
   @Override
   public DexProgramClass get() {
     return this;
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index c93d366..b1a8f8e 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -178,7 +178,7 @@
     cachedClassInitializer = DexEncodedMethod.SENTINEL;
   }
 
-  public DexEncodedMethod getClassInitializer() {
+  public synchronized DexEncodedMethod getClassInitializer() {
     if (cachedClassInitializer == DexEncodedMethod.SENTINEL) {
       cachedClassInitializer = null;
       for (DexEncodedMethod directMethod : directMethods()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index 14dee6f..7bdcc84 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -109,12 +109,14 @@
   @Override
   public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense lens) {
     AbstractValueFactory factory = appView.abstractValueFactory();
-    EnumValueInfoMap unboxedEnumInfo = appView.unboxedEnums().getEnumValueInfoMap(field.type);
-    if (unboxedEnumInfo != null) {
-      // Return the ordinal of the unboxed enum.
-      assert unboxedEnumInfo.hasEnumValueInfo(field);
-      return factory.createSingleNumberValue(
-          unboxedEnumInfo.getEnumValueInfo(field).convertToInt());
+    if (field.holder == field.type) {
+      EnumValueInfoMap unboxedEnumInfo = appView.unboxedEnums().getEnumValueInfoMap(field.type);
+      if (unboxedEnumInfo != null) {
+        // Return the ordinal of the unboxed enum.
+        assert unboxedEnumInfo.hasEnumValueInfo(field);
+        return factory.createSingleNumberValue(
+            unboxedEnumInfo.getEnumValueInfo(field).convertToInt());
+      }
     }
     return factory.createSingleFieldValue(
         lens.lookupField(field), getState().rewrittenWithLens(appView, lens));
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 3971c88..7dc7daa 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
@@ -320,8 +320,10 @@
       return;
     }
 
-    // If target is a non-interface library class it may be an emulated interface.
-    if (!libraryHolder.isInterface()) {
+    // If target is a non-interface library class it may be an emulated interface,
+    // except on a rewritten type, where L8 has already dealt with the desugaring.
+    if (!libraryHolder.isInterface()
+        && !appView.rewritePrefix.hasRewrittenType(libraryHolder.type, appView)) {
       // Here we use step-3 of resolution to find a maximally specific default interface method.
       DexClassAndMethod result = appInfo.lookupMaximallySpecificMethod(libraryHolder, method);
       if (result != null && rewriter.isEmulatedInterface(result.getHolder().type)) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index be1bbfd..5ce5bb8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -33,7 +32,6 @@
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -325,10 +323,8 @@
       // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
       // applies up to 24.
       for (DexEncodedMethod method : methods) {
-        DexType[] newInterfaces =
-            Arrays.copyOf(clazz.interfaces.values, clazz.interfaces.size() + 1);
-        newInterfaces[newInterfaces.length - 1] = dispatchInterfaceTypeFor(method);
-        clazz.interfaces = new DexTypeList(newInterfaces);
+        clazz.addExtraInterfaces(
+            Collections.singletonList(dispatchInterfaceTypeFor(method)), appView.dexItemFactory());
         if (clazz.lookupVirtualMethod(method.getReference()) == null) {
           DexEncodedMethod newMethod = createForwardingMethod(method, clazz);
           clazz.addVirtualMethod(newMethod);
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 27a5224..f537a6f 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
@@ -920,7 +920,7 @@
   private void duplicateEmulatedInterfaces() {
     // All classes implementing an emulated interface now implements the interface and the
     // emulated one, as well as hidden overrides, for correct emulated dispatch.
-    for (DexClass clazz : appView.appInfo().classes()) {
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
       if (clazz.type == appView.dexItemFactory().objectType) {
         continue;
       }
@@ -940,17 +940,11 @@
           }
         }
         // Remove duplicates.
-        extraInterfaces = new ArrayList<>(new LinkedHashSet<>(extraInterfaces));
-      }
-      if (!extraInterfaces.isEmpty()) {
-        DexType[] newInterfaces =
-            Arrays.copyOf(
-                clazz.interfaces.values, clazz.interfaces.size() + extraInterfaces.size());
-        for (int i = clazz.interfaces.size(); i < newInterfaces.length; i++) {
-          newInterfaces[i] = extraInterfaces.get(i - clazz.interfaces.size());
+        if (extraInterfaces.size() > 1) {
+          extraInterfaces = new ArrayList<>(new LinkedHashSet<>(extraInterfaces));
         }
-        clazz.interfaces = new DexTypeList(newInterfaces);
       }
+      clazz.addExtraInterfaces(extraInterfaces, appView.dexItemFactory());
     }
   }
 
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 8f4650a..4cb2fab 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
@@ -1432,7 +1432,7 @@
     // c = ...        // Even though we know c is of type A,
     // a' = (B) c;    // (this could be removed, since chained below.)
     // a'' = (A) a';  // this should remain for runtime verification.
-    assert !inTypeLattice.isDefinitelyNull();
+    assert !inTypeLattice.isDefinitelyNull() || (inValue.isPhi() && !inTypeLattice.isNullType());
     assert outTypeLattice.equalUpToNullability(castTypeLattice);
     return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 263cadd..71a2a18 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -132,6 +132,11 @@
         continue;
       }
 
+      // Check that we are not service loading anything from a feature into base.
+      if (appView.appServices().hasServiceImplementationsInFeature(constClass.getValue())) {
+        continue;
+      }
+
       // Check that ClassLoader used is the ClassLoader defined for the the service configuration
       // that we are instantiating or NULL.
       InvokeVirtual classLoaderInvoke =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
index f80e1d7..3feac87 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
@@ -12,11 +12,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
@@ -26,7 +23,6 @@
 import java.util.Map;
 import java.util.Set;
 
-// TODO(b/160634549): Rename or refactor this to reflect its non-cost related analysis.
 /** Analysis that estimates the cost of class inlining an object allocation. */
 class ClassInlinerCostAnalysis {
 
@@ -77,28 +73,6 @@
         continue;
       }
       IRCode inliningIR = inliningIRProvider.getAndCacheInliningIR(invoke, inlinee);
-
-      // If the instance is part of a phi, then inlining will invalidate the inliner assumptions.
-      // TODO(b/160634549): This is not a budget miss but a hard requirement.
-      InstructionIterator iterator = inliningIR.entryBlock().iterator();
-      while (iterator.hasNext()) {
-        Instruction next = iterator.next();
-        if (!next.isArgument()) {
-          break;
-        }
-        Value argumentValue = next.outValue();
-        TypeElement argumentType = argumentValue.getType();
-        if (argumentType.isClassType()
-            && argumentType.asClassType().getClassType() == eligibleClass.type) {
-          assert argumentValue.uniqueUsers().stream()
-              .noneMatch(
-                  AssumeAndCheckCastAliasedValueConfiguration.getInstance()::isIntroducingAnAlias);
-          if (argumentValue.hasPhiUsers()) {
-            return true;
-          }
-        }
-      }
-
       int increment =
           inlinee.getDefinition().getCode().estimatedSizeForInlining()
               - estimateNumberOfNonMaterializingInstructions(invoke, inliningIR);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
index 8d178a3..35eaaf5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
@@ -22,13 +22,16 @@
   final OptionalBool returnsReceiver;
 
   final boolean hasMonitorOnReceiver;
+  final boolean modifiesInstanceFields;
 
   public ClassInlinerEligibilityInfo(
       List<Pair<Invoke.Type, DexMethod>> callsReceiver,
       OptionalBool returnsReceiver,
-      boolean hasMonitorOnReceiver) {
+      boolean hasMonitorOnReceiver,
+      boolean modifiesInstanceFields) {
     this.callsReceiver = callsReceiver;
     this.returnsReceiver = returnsReceiver;
     this.hasMonitorOnReceiver = hasMonitorOnReceiver;
+    this.modifiesInstanceFields = modifiesInstanceFields;
   }
 }
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 4e52743..3824962 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
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.google.common.base.Predicates.alwaysFalse;
 
+import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
@@ -41,6 +42,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
@@ -58,11 +60,11 @@
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.WorkList;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -581,11 +583,35 @@
     while (!currentUsers.isEmpty()) {
       Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet();
       for (Instruction instruction : currentUsers) {
-        if (instruction.isAssume() || instruction.isCheckCast()) {
-          Value src = ListUtils.first(instruction.inValues());
+        if (aliasesThroughAssumeAndCheckCasts.isIntroducingAnAlias(instruction)) {
+          Value src = aliasesThroughAssumeAndCheckCasts.getAliasForOutValue(instruction);
           Value dest = instruction.outValue();
-          indirectOutValueUsers.addAll(dest.uniqueUsers());
+          if (dest.hasPhiUsers()) {
+            // It is possible that a trivial phi is constructed upon IR building for the eligible
+            // value. It must actually be trivial so verify that it is indeed trivial and replace
+            // all of the phis involved with the value.
+            WorkList<Phi> worklist = WorkList.newIdentityWorkList(dest.uniquePhiUsers());
+            while (worklist.hasNext()) {
+              Phi phi = worklist.next();
+              for (Value operand : phi.getOperands()) {
+                operand = operand.getAliasedValue(aliasesThroughAssumeAndCheckCasts);
+                if (operand.isPhi()) {
+                  worklist.addIfNotSeen(operand.asPhi());
+                } else if (src != operand) {
+                  throw new InternalCompilerError(
+                      "Unexpected non-trivial phi in method eligible for class inlining");
+                }
+              }
+            }
+            // The only value flowing into any of the phis is src, so replace all phis by src.
+            for (Phi phi : worklist.getSeenSet()) {
+              indirectOutValueUsers.addAll(phi.uniqueUsers());
+              phi.replaceUsers(src);
+              phi.removeDeadPhi();
+            }
+          }
           assert !dest.hasPhiUsers();
+          indirectOutValueUsers.addAll(dest.uniqueUsers());
           dest.replaceUsers(src);
           removeInstruction(instruction);
         }
@@ -1039,6 +1065,12 @@
         // We will not be able to remove the monitor instruction afterwards.
         return false;
       }
+      if (eligibility.modifiesInstanceFields) {
+        // The static instance could be accessed from elsewhere. Therefore, we cannot
+        // allow side-effects to be removed and therefore cannot class inline method
+        // calls that modifies the instance.
+        return false;
+      }
     }
 
     // If the method returns receiver and the return value is actually
@@ -1189,8 +1221,8 @@
     }
 
     if (root.isStaticGet()) {
-      // If we are class inlining a singleton instance from a static-get, then we don't the value of
-      // the fields.
+      // If we are class inlining a singleton instance from a static-get, then we don't know
+      // the value of the fields.
       if (parameterUsage.hasFieldRead) {
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 1ffca3d..3be53b2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -771,7 +771,7 @@
           factory.createString(
               enumUnboxerRewriter.compatibleName(method.holder)
                   + "$"
-                  + (encodedMethod.isDirectMethod() ? "d" : "v")
+                  + (encodedMethod.isStatic() ? "s" : "v")
                   + "$"
                   + method.name.toString());
       DexProto proto =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 8a25765..d88f9d2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -192,6 +192,7 @@
     List<Pair<Invoke.Type, DexMethod>> callsReceiver = new ArrayList<>();
     boolean seenSuperInitCall = false;
     boolean seenMonitor = false;
+    boolean modifiesInstanceFields = false;
 
     AliasedValueConfiguration configuration =
         AssumeAndCheckCastAliasedValueConfiguration.getInstance();
@@ -220,6 +221,7 @@
               if (isReceiverAlias.test(instancePutInstruction.value())) {
                 return;
               }
+              modifiesInstanceFields = true;
             }
             DexField field = insn.asFieldInstruction().getField();
             if (appView.appInfo().resolveField(field).isFailedOrUnknownResolution()) {
@@ -293,7 +295,8 @@
         new ClassInlinerEligibilityInfo(
             callsReceiver,
             new ClassInlinerReceiverAnalysis(appView, definition, code).computeReturnsReceiver(),
-            seenMonitor || synchronizedVirtualMethod));
+            seenMonitor || synchronizedVirtualMethod,
+            modifiesInstanceFields));
   }
 
   private void identifyParameterUsages(
@@ -540,7 +543,7 @@
               if (!value.onlyDependsOnArgument()) {
                 builder.setInstanceFieldInitializationMayDependOnEnvironment();
               }
-              if (value == receiver) {
+              if (couldBeReceiverValue(value, receiver, aliasesThroughAssumeAndCheckCasts)) {
                 builder.setReceiverMayEscapeOutsideConstructorChain();
               }
             }
@@ -569,7 +572,7 @@
                 for (int i = 1; i < invoke.arguments().size(); i++) {
                   Value argument =
                       invoke.arguments().get(i).getAliasedValue(aliasesThroughAssumeAndCheckCasts);
-                  if (argument == receiver) {
+                  if (couldBeReceiverValue(argument, receiver, aliasesThroughAssumeAndCheckCasts)) {
                     // In the analysis of the parent constructor, we don't consider the non-receiver
                     // arguments as being aliases of the receiver. Therefore, we explicitly mark
                     // that the receiver escapes from this constructor.
@@ -587,7 +590,7 @@
                     .markAllFieldsAsRead()
                     .setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
                 for (Value inValue : invoke.inValues()) {
-                  if (inValue.getAliasedValue(aliasesThroughAssumeAndCheckCasts) == receiver) {
+                  if (couldBeReceiverValue(inValue, receiver, aliasesThroughAssumeAndCheckCasts)) {
                     builder.setReceiverMayEscapeOutsideConstructorChain();
                     break;
                   }
@@ -603,7 +606,7 @@
                 builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
               }
               for (Value argument : invoke.arguments()) {
-                if (argument.getAliasedValue(aliasesThroughAssumeAndCheckCasts) == receiver) {
+                if (couldBeReceiverValue(argument, receiver, aliasesThroughAssumeAndCheckCasts)) {
                   builder.setReceiverMayEscapeOutsideConstructorChain();
                   break;
                 }
@@ -620,7 +623,7 @@
                   .markAllFieldsAsRead()
                   .setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
               for (Value argument : invoke.arguments()) {
-                if (argument.getAliasedValue(aliasesThroughAssumeAndCheckCasts) == receiver) {
+                if (couldBeReceiverValue(argument, receiver, aliasesThroughAssumeAndCheckCasts)) {
                   builder.setReceiverMayEscapeOutsideConstructorChain();
                   break;
                 }
@@ -669,6 +672,18 @@
     return builder.build();
   }
 
+  private static boolean couldBeReceiverValue(
+      Value value, Value receiver, AliasedValueConfiguration aliasing) {
+    if (value.isPhi() && receiver.hasPhiUsers()) {
+      // Conservatively assume that the receiver might be an input dependency of the phi value.
+      return true;
+    }
+    if (value.getAliasedValue(aliasing) == receiver) {
+      return true;
+    }
+    return false;
+  }
+
   private void identifyInvokeSemanticsForInlining(
       DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
     timing.begin("Identify invoke semantics for inlining");
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
index 54decce..1cfe21f 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.code.InvokeVirtualRange;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
@@ -304,49 +305,66 @@
 
   private CfCode createCfCodeForVirtualBridge(CfCode code, DexMethod methodToInvoke) {
     List<CfInstruction> newInstructions = new ArrayList<>();
+    boolean modified = false;
     for (CfInstruction instruction : code.getInstructions()) {
-      if (instruction.isInvoke()) {
+      if (instruction.isInvoke() && instruction.asInvoke().getMethod() != methodToInvoke) {
         CfInvoke invoke = instruction.asInvoke();
         assert invoke.isInvokeVirtual();
         assert !invoke.isInterface();
         assert invoke.getMethod().match(methodToInvoke);
         newInstructions.add(new CfInvoke(invoke.getOpcode(), methodToInvoke, false));
+        modified = true;
       } else {
         newInstructions.add(instruction);
       }
     }
-    return new CfCode(
-        methodToInvoke.holder,
-        code.getMaxStack(),
-        code.getMaxLocals(),
-        newInstructions,
-        code.getTryCatchRanges(),
-        code.getLocalVariables());
+    return modified
+        ? new CfCode(
+            methodToInvoke.holder,
+            code.getMaxStack(),
+            code.getMaxLocals(),
+            newInstructions,
+            code.getTryCatchRanges(),
+            code.getLocalVariables())
+        : code;
   }
 
   private DexCode createDexCodeForVirtualBridge(DexCode code, DexMethod methodToInvoke) {
     Instruction[] newInstructions = new Instruction[code.instructions.length];
+    boolean modified = false;
     for (int i = 0; i < code.instructions.length; i++) {
       Instruction instruction = code.instructions[i];
-      if (instruction.isInvokeVirtual()) {
+      if (instruction.isInvokeVirtual()
+          && instruction.asInvokeVirtual().getMethod() != methodToInvoke) {
         InvokeVirtual invoke = instruction.asInvokeVirtual();
         InvokeVirtual newInvoke =
             new InvokeVirtual(
                 invoke.A, methodToInvoke, invoke.C, invoke.D, invoke.E, invoke.F, invoke.G);
         newInvoke.setOffset(invoke.getOffset());
         newInstructions[i] = newInvoke;
+        modified = true;
+      } else if (instruction.isInvokeVirtualRange()
+          && instruction.asInvokeVirtualRange().getMethod() != methodToInvoke) {
+        InvokeVirtualRange invoke = instruction.asInvokeVirtualRange();
+        InvokeVirtualRange newInvoke =
+            new InvokeVirtualRange(invoke.CCCC, invoke.AA, methodToInvoke);
+        newInvoke.setOffset(invoke.getOffset());
+        modified = true;
+        newInstructions[i] = newInvoke;
       } else {
         newInstructions[i] = instruction;
       }
     }
-    return new DexCode(
-        code.registerSize,
-        code.incomingRegisterSize,
-        code.outgoingRegisterSize,
-        newInstructions,
-        code.tries,
-        code.handlers,
-        code.getDebugInfo());
+    return modified
+        ? new DexCode(
+            code.registerSize,
+            code.incomingRegisterSize,
+            code.outgoingRegisterSize,
+            newInstructions,
+            code.tries,
+            code.handlers,
+            code.getDebugInfo())
+        : code;
   }
 
   static class BridgeHoistingLens extends NestedGraphLense {
diff --git a/src/main/java/com/android/tools/r8/references/Reference.java b/src/main/java/com/android/tools/r8/references/Reference.java
index f2c5442..444eee3 100644
--- a/src/main/java/com/android/tools/r8/references/Reference.java
+++ b/src/main/java/com/android/tools/r8/references/Reference.java
@@ -60,7 +60,11 @@
 
   private static Reference getInstance() {
     if (instance == null) {
-      instance = new Reference();
+      synchronized (Reference.class) {
+        if (instance == null) {
+          instance = new Reference();
+        }
+      }
     }
     return instance;
   }
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 9dd4865..f0340a3 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -646,7 +646,7 @@
       ZipOutputStream dexArchiveOutputStream,
       ProgramResource programResource)
       throws ResourceException, IOException {
-    byte[] bytes = ByteStreams.toByteArray(programResource.getByteStream());
+    byte[] bytes = StreamUtils.StreamToByteArrayClose(programResource.getByteStream());
     if (programResource.getKind() == Kind.CF) {
       Set<String> classDescriptors = programResource.getClassDescriptors();
       String classDescriptor =
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index f7353d9..6cc73f4 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -225,7 +225,8 @@
   public boolean enableInlining =
       !Version.isDevelopmentVersion()
           || System.getProperty("com.android.tools.r8.disableinlining") == null;
-  public boolean enableEnumUnboxing = true;
+  // TODO(b/160854837): re-enable enum unboxing.
+  public boolean enableEnumUnboxing = false;
   // TODO(b/141451716): Evaluate the effect of allowing inlining in the inlinee.
   public boolean applyInliningToInlinee =
       System.getProperty("com.android.tools.r8.applyInliningToInlinee") != null;
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index d8bd420..092a4fc 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -6,6 +6,7 @@
 
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.HashSet;
 import java.util.Set;
@@ -78,6 +79,10 @@
     return workingList.removeFirst();
   }
 
+  public Set<T> getSeenSet() {
+    return Collections.unmodifiableSet(seen);
+  }
+
   public enum EqualityTest {
     HASH,
     IDENTITY
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 9ad7a70..31150c4 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -2129,7 +2129,7 @@
       throws IOException, ExecutionException {
     DexApplication application =
         new ApplicationReader(app, new InternalOptions(), Timing.empty()).read().toDirect();
-    new AssemblyWriter(application, new InternalOptions(), true, false).write(ps);
+    new AssemblyWriter(application, new InternalOptions(), true, false, true).write(ps);
   }
 
   public static Path getTestFolderForClass(Class<?> clazz) {
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
index 16db7d8..410d418 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
@@ -13,7 +13,9 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.bridgeremoval.hoisting.testclasses.BridgeHoistingAccessibilityTestClasses;
+import com.android.tools.r8.bridgeremoval.hoisting.testclasses.BridgeHoistingAccessibilityTestClasses.AWithRangedInvoke;
 import com.android.tools.r8.bridgeremoval.hoisting.testclasses.BridgeHoistingAccessibilityTestClasses.User;
+import com.android.tools.r8.bridgeremoval.hoisting.testclasses.BridgeHoistingAccessibilityTestClasses.UserWithRangedInvoke;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
@@ -45,6 +47,28 @@
                 .transform(),
             transformer(C.class)
                 .setBridge(C.class.getDeclaredMethod("bridgeC", Object.class))
+                .transform(),
+            transformer(BWithRangedInvoke.class)
+                .setBridge(
+                    BWithRangedInvoke.class.getDeclaredMethod(
+                        "bridgeB",
+                        Object.class,
+                        int.class,
+                        int.class,
+                        int.class,
+                        int.class,
+                        int.class))
+                .transform(),
+            transformer(CWithRangedInvoke.class)
+                .setBridge(
+                    CWithRangedInvoke.class.getDeclaredMethod(
+                        "bridgeC",
+                        Object.class,
+                        int.class,
+                        int.class,
+                        int.class,
+                        int.class,
+                        int.class))
                 .transform())
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
@@ -54,7 +78,7 @@
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("Hello world!");
+        .assertSuccessWithOutputLines("Hello world!", "Hello 12345 world! 12345");
   }
 
   private void inspect(CodeInspector inspector) {
@@ -69,6 +93,11 @@
 
     ClassSubject cClassSubject = inspector.clazz(C.class);
     assertThat(cClassSubject, isPresent());
+
+    ClassSubject axClassSubject = inspector.clazz(AWithRangedInvoke.class);
+    assertThat(axClassSubject, isPresent());
+    assertThat(axClassSubject.uniqueMethodWithName("m"), isPresent());
+    assertThat(axClassSubject.uniqueMethodWithName("bridgeC"), isPresent());
   }
 
   static class TestClass {
@@ -77,6 +106,10 @@
       C instance = new C();
       System.out.print(instance.bridgeB("Hello"));
       System.out.println(User.invokeBridgeC(instance));
+
+      CWithRangedInvoke instanceWithRangedInvoke = new CWithRangedInvoke();
+      System.out.print(instanceWithRangedInvoke.bridgeB("Hello ", 1, 2, 3, 4, 5));
+      System.out.println(UserWithRangedInvoke.invokeBridgeC(instanceWithRangedInvoke));
     }
   }
 
@@ -102,4 +135,27 @@
       return (String) m((String) o);
     }
   }
+
+  @NeverMerge
+  static class BWithRangedInvoke extends AWithRangedInvoke {
+
+    // This bridge cannot be hoisted to A, since it would then become inaccessible to the call site
+    // in TestClass.main().
+    @NeverInline
+    /*bridge*/ String bridgeB(Object o, int a, int b, int c, int d, int e) {
+      return (String) m((String) o, a, b, c, d, e);
+    }
+  }
+
+  @NeverClassInline
+  public static class CWithRangedInvoke extends BWithRangedInvoke {
+
+    // This bridge is invoked from another package. However, this does not prevent us from hoisting
+    // the bridge to B, although B is not public, since users from outside this package can still
+    // access bridgeC() via class C. From B, the bridge can be hoisted again to A.
+    @NeverInline
+    public /*bridge*/ String bridgeC(Object o, int a, int b, int c, int d, int e) {
+      return (String) m((String) o, a, b, c, d, e);
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
index bc91e00..55daf92 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.bridgeremoval.hoisting.BridgeHoistingAccessibilityTest;
+import com.android.tools.r8.bridgeremoval.hoisting.BridgeHoistingAccessibilityTest.CWithRangedInvoke;
 
 public class BridgeHoistingAccessibilityTestClasses {
 
@@ -26,4 +27,21 @@
       return instance.bridgeC(" world!");
     }
   }
+
+  @NeverMerge
+  public static class AWithRangedInvoke {
+
+    @NeverInline
+    public Object m(String arg, int a, int b, int c, int d, int e) {
+      return System.currentTimeMillis() > 0 ? arg + a + b + c + d + e : null;
+    }
+  }
+
+  public static class UserWithRangedInvoke {
+
+    @NeverInline
+    public static String invokeBridgeC(CWithRangedInvoke instance) {
+      return instance.bridgeC(" world! ", 1, 2, 3, 4, 5);
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
index f13eddb..ae709f3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -18,6 +19,9 @@
 @RunWith(Parameterized.class)
 public class ConcurrentHashMapSubclassTest extends DesugaredLibraryTestBase {
 
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines("1.0", "2.0", "10.0", "1.0", "2.0", "10.0", "1.0", "2.0", "10.0");
+
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
 
@@ -46,7 +50,7 @@
             keepRuleConsumer.get(),
             shrinkDesugaredLibrary)
         .run(parameters.getRuntime(), Executor.class)
-        .assertSuccessWithOutputLines("1.0", "10.0", "1.0", "10.0", "1.0", "10.0");
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
@@ -64,7 +68,7 @@
             keepRuleConsumer.get(),
             shrinkDesugaredLibrary)
         .run(parameters.getRuntime(), Executor.class)
-        .assertSuccessWithOutputLines("1.0", "10.0", "1.0", "10.0", "1.0", "10.0");
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @SuppressWarnings("unchecked")
@@ -78,24 +82,33 @@
     static void itfType() {
       Map map = new NullableConcurrentHashMap<Integer, Double>();
       map.put(1, 1.0);
+      map.putIfAbsent(2, 2.0);
+      map.putIfAbsent(2, 3.0);
       map.putAll(example());
       System.out.println(map.get(1));
+      System.out.println(map.get(2));
       System.out.println(map.get(10));
     }
 
     static void classType() {
       ConcurrentHashMap map = new NullableConcurrentHashMap<Integer, Double>();
       map.put(1, 1.0);
+      map.putIfAbsent(2, 2.0);
+      map.putIfAbsent(2, 3.0);
       map.putAll(example());
       System.out.println(map.get(1));
+      System.out.println(map.get(2));
       System.out.println(map.get(10));
     }
 
     static void directType() {
       NullableConcurrentHashMap map = new NullableConcurrentHashMap<Integer, Double>();
       map.put(1, 1.0);
+      map.putIfAbsent(2, 2.0);
+      map.putIfAbsent(2, 3.0);
       map.putAll(example());
       System.out.println(map.get(1));
+      System.out.println(map.get(2));
       System.out.println(map.get(10));
     }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
new file mode 100644
index 0000000..0eab37e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+//  for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LibraryEmptySubclassInterfaceTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public LibraryEmptySubclassInterfaceTest(
+      boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(LibraryEmptySubclassInterfaceTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutputLines(getResult());
+    assertExpectedKeepRules(keepRuleConsumer);
+  }
+
+  private void assertExpectedKeepRules(KeepRuleConsumer keepRuleConsumer) {
+    if (!requiresEmulatedInterfaceCoreLibDesugaring(parameters)) {
+      return;
+    }
+    String keepRules = keepRuleConsumer.get();
+    assertThat(keepRules, containsString("-keep class j$.util.Map"));
+    assertThat(keepRules, containsString("-keep class j$.util.concurrent.ConcurrentHashMap"));
+    assertThat(keepRules, containsString("-keep class j$.util.concurrent.ConcurrentMap"));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(Backend.DEX)
+        .addInnerClasses(LibraryEmptySubclassInterfaceTest.class)
+        .addKeepMainRule(Executor.class)
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutputLines(getResult());
+    assertExpectedKeepRules(keepRuleConsumer);
+  }
+
+  private String getResult() {
+    return requiresEmulatedInterfaceCoreLibDesugaring(parameters)
+        ? "class j$.util.concurrent.ConcurrentHashMap"
+        : "class java.util.concurrent.ConcurrentHashMap";
+  }
+
+  @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
+  static class Executor {
+
+    public static void main(String[] args) {
+      System.out.println(NullableConcurrentHashMap.class.getSuperclass());
+    }
+  }
+
+  static class NullableConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {
+    NullableConcurrentHashMap() {
+      super();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java
new file mode 100644
index 0000000..6e300ba
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java
@@ -0,0 +1,1012 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+//  for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import dalvik.system.PathClassLoader;
+import java.sql.SQLDataException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LibrarySubclassInterfaceTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public LibrarySubclassInterfaceTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testCustomCollectionD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    String stdOut =
+        testForD8()
+            .addInnerClasses(LibrarySubclassInterfaceTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .addDesugaredCoreLibraryRunClassPath(
+                this::buildDesugaredLibrary,
+                parameters.getApiLevel(),
+                keepRuleConsumer.get(),
+                shrinkDesugaredLibrary)
+            .run(parameters.getRuntime(), Executor.class)
+            .assertSuccess()
+            .getStdOut();
+    assertValidInterfaces(stdOut);
+  }
+
+  @Test
+  public void testCustomCollectionR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    String stdOut =
+        testForR8(Backend.DEX)
+            .addInnerClasses(LibrarySubclassInterfaceTest.class)
+            .addKeepMainRule(Executor.class)
+            .noMinification()
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .addDesugaredCoreLibraryRunClassPath(
+                this::buildDesugaredLibrary,
+                parameters.getApiLevel(),
+                keepRuleConsumer.get(),
+                shrinkDesugaredLibrary)
+            .run(parameters.getRuntime(), Executor.class)
+            .assertSuccess()
+            .getStdOut();
+    assertValidInterfaces(stdOut);
+  }
+
+  private void assertValidInterfaces(String stdOut) {
+    // The value of getGenericInterfaces has to be the value of getInterfaces with generic types.
+    // Here are two examples:
+    //  - class A implements I {}
+    //    getInterfaces -> [interface I]
+    //    getGenericInterfaces -> [interface I]
+    //  - class B<E> implements J<E> {}
+    //    getInterfaces -> [interface J]
+    //    getGenericInterfaces -> [J<E>]
+    // Both arrays have to be of the same size and each class has to be present in the same order.
+    String[] lines = stdOut.split("\n");
+    for (int i = 0; i < lines.length; i += 4) {
+      String className = lines[i];
+      String[] interfaces1 = lines[i + 1].split("(, com|, interface|, j)");
+      String[] interfaces2 = lines[i + 2].split("(, com|, interface|, j)");
+      assertEquals(
+          "Invalid number of interfaces in "
+              + className
+              + "\n "
+              + Arrays.toString(interfaces1)
+              + "\n "
+              + Arrays.toString(interfaces2),
+          interfaces1.length,
+          interfaces2.length);
+      // Ignore the empty list of interface case.
+      if (!interfaces1[0].equals("[]")) {
+        for (int j = 0; j < interfaces1.length; j++) {
+          String interfaceName = interfaces1[j].substring("interface ".length()).trim();
+          while (interfaceName.charAt(interfaceName.length() - 1) == ']') {
+            interfaceName = interfaceName.substring(0, interfaceName.length() - 2).trim();
+          }
+          assertTrue(
+              "Invalid interface in " + className + "\n " + interfaces1[j] + "\n " + interfaces2[j],
+              interfaces2[j].contains(interfaceName));
+        }
+      }
+    }
+  }
+
+  @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
+  static class Executor {
+
+    // The output of the test is, in stdOut, composed of 4 lines entries:
+    // line 1: class name
+    // line 2: getInterfaces() for the class
+    // line 3: getGenericInterfaces() for the class
+    // line 4: empty.
+    public static void main(String[] args) {
+      mapTest();
+      collectionTest();
+      collectionMapTest();
+      sqlDateTest();
+    }
+
+    private static void mapTest() {
+      System.out.println(NullableConcurrentHashMapExtendDifferentLetters.class);
+      System.out.println(
+          Arrays.toString(NullableConcurrentHashMapExtendDifferentLetters.class.getInterfaces()));
+      System.out.println(
+          Arrays.toString(
+              NullableConcurrentHashMapExtendDifferentLetters.class.getGenericInterfaces()));
+      System.out.println();
+
+      System.out.println(NullableConcurrentHashMapExtend.class);
+      System.out.println(Arrays.toString(NullableConcurrentHashMapExtend.class.getInterfaces()));
+      System.out.println(
+          Arrays.toString(NullableConcurrentHashMapExtend.class.getGenericInterfaces()));
+      System.out.println();
+
+      System.out.println(NullableConcurrentHashMapExtendZ.class);
+      System.out.println(Arrays.toString(NullableConcurrentHashMapExtendZ.class.getInterfaces()));
+      System.out.println(
+          Arrays.toString(NullableConcurrentHashMapExtendZ.class.getGenericInterfaces()));
+      System.out.println();
+
+      System.out.println(NullableConcurrentHashMapImplement.class);
+      System.out.println(Arrays.toString(NullableConcurrentHashMapImplement.class.getInterfaces()));
+      System.out.println(
+          Arrays.toString(NullableConcurrentHashMapImplement.class.getGenericInterfaces()));
+      System.out.println();
+
+      System.out.println(NullableConcurrentHashMapImplementZ.class);
+      System.out.println(
+          Arrays.toString(NullableConcurrentHashMapImplementZ.class.getInterfaces()));
+      System.out.println(
+          Arrays.toString(NullableConcurrentHashMapImplementZ.class.getGenericInterfaces()));
+      System.out.println();
+    }
+
+    private static void collectionTest() {
+      System.out.println(NullableArrayListExtend.class);
+      System.out.println(Arrays.toString(NullableArrayListExtend.class.getInterfaces()));
+      System.out.println(Arrays.toString(NullableArrayListExtend.class.getGenericInterfaces()));
+      System.out.println();
+
+      System.out.println(NullableArrayListExtendZ.class);
+      System.out.println(Arrays.toString(NullableArrayListExtendZ.class.getInterfaces()));
+      System.out.println(Arrays.toString(NullableArrayListExtendZ.class.getGenericInterfaces()));
+      System.out.println();
+
+      System.out.println(NullableArrayListImplement.class);
+      System.out.println(Arrays.toString(NullableArrayListImplement.class.getInterfaces()));
+      System.out.println(Arrays.toString(NullableArrayListImplement.class.getGenericInterfaces()));
+      System.out.println();
+
+      System.out.println(NullableArrayListImplementZ.class);
+      System.out.println(Arrays.toString(NullableArrayListImplementZ.class.getInterfaces()));
+      System.out.println(Arrays.toString(NullableArrayListImplementZ.class.getGenericInterfaces()));
+      System.out.println();
+    }
+
+    private static void collectionMapTest() {
+      System.out.println(CollectionMapImplements2.class);
+      System.out.println(Arrays.toString(CollectionMapImplements2.class.getInterfaces()));
+      System.out.println(Arrays.toString(CollectionMapImplements2.class.getGenericInterfaces()));
+      System.out.println();
+
+      System.out.println(CollectionMapExtendImplement.class);
+      System.out.println(Arrays.toString(CollectionMapExtendImplement.class.getInterfaces()));
+      System.out.println(
+          Arrays.toString(CollectionMapExtendImplement.class.getGenericInterfaces()));
+      System.out.println();
+
+      System.out.println(CollectionMapImplements2Integer1.class);
+      System.out.println(Arrays.toString(CollectionMapImplements2Integer1.class.getInterfaces()));
+      System.out.println(
+          Arrays.toString(CollectionMapImplements2Integer1.class.getGenericInterfaces()));
+      System.out.println();
+
+      System.out.println(CollectionMapExtendImplementInteger1.class);
+      System.out.println(
+          Arrays.toString(CollectionMapExtendImplementInteger1.class.getInterfaces()));
+      System.out.println(
+          Arrays.toString(CollectionMapExtendImplementInteger1.class.getGenericInterfaces()));
+      System.out.println();
+
+      System.out.println(CollectionMapImplements2Integer2.class);
+      System.out.println(Arrays.toString(CollectionMapImplements2Integer2.class.getInterfaces()));
+      System.out.println(
+          Arrays.toString(CollectionMapImplements2Integer2.class.getGenericInterfaces()));
+      System.out.println();
+
+      System.out.println(CollectionMapExtendImplementInteger2.class);
+      System.out.println(
+          Arrays.toString(CollectionMapExtendImplementInteger2.class.getInterfaces()));
+      System.out.println(
+          Arrays.toString(CollectionMapExtendImplementInteger2.class.getGenericInterfaces()));
+      System.out.println();
+    }
+
+    private static void sqlDateTest() {
+      System.out.println(MySQLDataException.class);
+      System.out.println(Arrays.toString(MySQLDataException.class.getInterfaces()));
+      System.out.println(Arrays.toString(MySQLDataException.class.getGenericInterfaces()));
+      System.out.println();
+
+      System.out.println(MyDate.class);
+      System.out.println(Arrays.toString(MyDate.class.getInterfaces()));
+      System.out.println(Arrays.toString(MyDate.class.getGenericInterfaces()));
+      System.out.println();
+
+      System.out.println(MyDateZ.class);
+      System.out.println(Arrays.toString(MyDateZ.class.getInterfaces()));
+      System.out.println(Arrays.toString(MyDateZ.class.getGenericInterfaces()));
+      System.out.println();
+    }
+  }
+
+  interface MyInterface<Z> {
+    void print(Z z);
+  }
+
+  static class NullableConcurrentHashMapExtendDifferentLetters<R, T>
+      extends ConcurrentHashMap<R, T> {
+    NullableConcurrentHashMapExtendDifferentLetters() {
+      super();
+    }
+  }
+
+  static class NullableConcurrentHashMapExtend<K, V> extends ConcurrentHashMap<K, V> {
+    NullableConcurrentHashMapExtend() {
+      super();
+    }
+  }
+
+  static class NullableConcurrentHashMapImplement<K, V> implements ConcurrentMap<K, V> {
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean containsKey(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean containsValue(Object o) {
+      return false;
+    }
+
+    @Override
+    public V get(Object o) {
+      return null;
+    }
+
+    @Nullable
+    @Override
+    public V put(K k, V v) {
+      return null;
+    }
+
+    @Override
+    public V remove(Object o) {
+      return null;
+    }
+
+    @Override
+    public void putAll(@NotNull Map<? extends K, ? extends V> map) {}
+
+    @Override
+    public void clear() {}
+
+    @NotNull
+    @Override
+    public Set<K> keySet() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Collection<V> values() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Set<Entry<K, V>> entrySet() {
+      return null;
+    }
+
+    @Override
+    public V putIfAbsent(@NotNull K k, V v) {
+      return null;
+    }
+
+    @Override
+    public boolean remove(@NotNull Object o, Object o1) {
+      return false;
+    }
+
+    @Override
+    public boolean replace(@NotNull K k, @NotNull V v, @NotNull V v1) {
+      return false;
+    }
+
+    @Override
+    public V replace(@NotNull K k, @NotNull V v) {
+      return null;
+    }
+  }
+
+  static class NullableConcurrentHashMapExtendZ<K, V> extends ConcurrentHashMap<K, V>
+      implements MyInterface<K> {
+    NullableConcurrentHashMapExtendZ() {
+      super();
+    }
+
+    @Override
+    public void print(K k) {}
+  }
+
+  static class NullableConcurrentHashMapImplementZ<K, V>
+      implements ConcurrentMap<K, V>, MyInterface<K> {
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean containsKey(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean containsValue(Object o) {
+      return false;
+    }
+
+    @Override
+    public V get(Object o) {
+      return null;
+    }
+
+    @Nullable
+    @Override
+    public V put(K k, V v) {
+      return null;
+    }
+
+    @Override
+    public V remove(Object o) {
+      return null;
+    }
+
+    @Override
+    public void putAll(@NotNull Map<? extends K, ? extends V> map) {}
+
+    @Override
+    public void clear() {}
+
+    @NotNull
+    @Override
+    public Set<K> keySet() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Collection<V> values() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Set<Entry<K, V>> entrySet() {
+      return null;
+    }
+
+    @Override
+    public V putIfAbsent(@NotNull K k, V v) {
+      return null;
+    }
+
+    @Override
+    public boolean remove(@NotNull Object o, Object o1) {
+      return false;
+    }
+
+    @Override
+    public boolean replace(@NotNull K k, @NotNull V v, @NotNull V v1) {
+      return false;
+    }
+
+    @Override
+    public V replace(@NotNull K k, @NotNull V v) {
+      return null;
+    }
+
+    @Override
+    public void print(K k) {}
+  }
+
+  static class NullableArrayListExtend<E> extends ArrayList<E> {
+    NullableArrayListExtend() {
+      super();
+    }
+  }
+
+  static class NullableArrayListImplement<E> implements List<E> {
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean contains(Object o) {
+      return false;
+    }
+
+    @NotNull
+    @Override
+    public Iterator<E> iterator() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Object[] toArray() {
+      return new Object[0];
+    }
+
+    @NotNull
+    @Override
+    public <T> T[] toArray(@NotNull T[] ts) {
+      return null;
+    }
+
+    @Override
+    public boolean add(E e) {
+      return false;
+    }
+
+    @Override
+    public boolean remove(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean containsAll(@NotNull Collection<?> collection) {
+      return false;
+    }
+
+    @Override
+    public boolean addAll(@NotNull Collection<? extends E> collection) {
+      return false;
+    }
+
+    @Override
+    public boolean addAll(int i, @NotNull Collection<? extends E> collection) {
+      return false;
+    }
+
+    @Override
+    public boolean removeAll(@NotNull Collection<?> collection) {
+      return false;
+    }
+
+    @Override
+    public boolean retainAll(@NotNull Collection<?> collection) {
+      return false;
+    }
+
+    @Override
+    public void clear() {}
+
+    @Override
+    public E get(int i) {
+      return null;
+    }
+
+    @Override
+    public E set(int i, E e) {
+      return null;
+    }
+
+    @Override
+    public void add(int i, E e) {}
+
+    @Override
+    public E remove(int i) {
+      return null;
+    }
+
+    @Override
+    public int indexOf(Object o) {
+      return 0;
+    }
+
+    @Override
+    public int lastIndexOf(Object o) {
+      return 0;
+    }
+
+    @NotNull
+    @Override
+    public ListIterator<E> listIterator() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public ListIterator<E> listIterator(int i) {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public List<E> subList(int i, int i1) {
+      return null;
+    }
+  }
+
+  static class NullableArrayListExtendZ<E> extends ArrayList<E> implements MyInterface<E> {
+    NullableArrayListExtendZ() {
+      super();
+    }
+
+    @Override
+    public void print(E e) {}
+  }
+
+  static class NullableArrayListImplementZ<E> implements List<E>, MyInterface<E> {
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean contains(Object o) {
+      return false;
+    }
+
+    @NotNull
+    @Override
+    public Iterator<E> iterator() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Object[] toArray() {
+      return new Object[0];
+    }
+
+    @NotNull
+    @Override
+    public <T> T[] toArray(@NotNull T[] ts) {
+      return null;
+    }
+
+    @Override
+    public boolean add(E e) {
+      return false;
+    }
+
+    @Override
+    public boolean remove(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean containsAll(@NotNull Collection<?> collection) {
+      return false;
+    }
+
+    @Override
+    public boolean addAll(@NotNull Collection<? extends E> collection) {
+      return false;
+    }
+
+    @Override
+    public boolean addAll(int i, @NotNull Collection<? extends E> collection) {
+      return false;
+    }
+
+    @Override
+    public boolean removeAll(@NotNull Collection<?> collection) {
+      return false;
+    }
+
+    @Override
+    public boolean retainAll(@NotNull Collection<?> collection) {
+      return false;
+    }
+
+    @Override
+    public void clear() {}
+
+    @Override
+    public E get(int i) {
+      return null;
+    }
+
+    @Override
+    public E set(int i, E e) {
+      return null;
+    }
+
+    @Override
+    public void add(int i, E e) {}
+
+    @Override
+    public E remove(int i) {
+      return null;
+    }
+
+    @Override
+    public int indexOf(Object o) {
+      return 0;
+    }
+
+    @Override
+    public int lastIndexOf(Object o) {
+      return 0;
+    }
+
+    @NotNull
+    @Override
+    public ListIterator<E> listIterator() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public ListIterator<E> listIterator(int i) {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public List<E> subList(int i, int i1) {
+      return null;
+    }
+
+    @Override
+    public void print(E e) {}
+  }
+
+  static class CollectionMapImplements2<R, C> implements Iterable<R>, Map<R, C> {
+    @NotNull
+    @Override
+    public Iterator<R> iterator() {
+      return null;
+    }
+
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean containsKey(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean containsValue(Object o) {
+      return false;
+    }
+
+    @Override
+    public C get(Object o) {
+      return null;
+    }
+
+    @Nullable
+    @Override
+    public C put(R r, C c) {
+      return null;
+    }
+
+    @Override
+    public C remove(Object o) {
+      return null;
+    }
+
+    @Override
+    public void putAll(@NotNull Map<? extends R, ? extends C> map) {}
+
+    @Override
+    public void clear() {}
+
+    @NotNull
+    @Override
+    public Set<R> keySet() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Collection<C> values() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Set<Entry<R, C>> entrySet() {
+      return null;
+    }
+  }
+
+  static class CollectionMapExtendImplement<R, C> extends HashMap<R, C> implements Iterable<R> {
+    @NotNull
+    @Override
+    public Iterator<R> iterator() {
+      return null;
+    }
+
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean containsKey(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean containsValue(Object o) {
+      return false;
+    }
+
+    @Override
+    public C get(Object o) {
+      return null;
+    }
+
+    @Nullable
+    @Override
+    public C put(R r, C c) {
+      return null;
+    }
+
+    @Override
+    public C remove(Object o) {
+      return null;
+    }
+
+    @Override
+    public void putAll(@NotNull Map<? extends R, ? extends C> map) {}
+
+    @Override
+    public void clear() {}
+
+    @NotNull
+    @Override
+    public Set<R> keySet() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Collection<C> values() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Set<Entry<R, C>> entrySet() {
+      return null;
+    }
+  }
+
+  static class CollectionMapImplements2Integer1<C>
+      implements Iterable<PathClassLoader>, Map<PathClassLoader, C> {
+    @NotNull
+    @Override
+    public Iterator<PathClassLoader> iterator() {
+      return null;
+    }
+
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean containsKey(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean containsValue(Object o) {
+      return false;
+    }
+
+    @Override
+    public C get(Object o) {
+      return null;
+    }
+
+    @Nullable
+    @Override
+    public C put(PathClassLoader unsafe, C c) {
+      return null;
+    }
+
+    @Override
+    public C remove(Object o) {
+      return null;
+    }
+
+    @Override
+    public void putAll(@NotNull Map<? extends PathClassLoader, ? extends C> map) {}
+
+    @Override
+    public void clear() {}
+
+    @NotNull
+    @Override
+    public Set<PathClassLoader> keySet() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Collection<C> values() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Set<Entry<PathClassLoader, C>> entrySet() {
+      return null;
+    }
+  }
+
+  static class CollectionMapExtendImplementInteger1<C> extends HashMap<PathClassLoader, C>
+      implements Iterable<PathClassLoader> {
+    @NotNull
+    @Override
+    public Iterator<PathClassLoader> iterator() {
+      return null;
+    }
+  }
+
+  static class CollectionMapImplements2Integer2<R> implements Iterable<R>, Map<R, PathClassLoader> {
+    @NotNull
+    @Override
+    public Iterator<R> iterator() {
+      return null;
+    }
+
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean containsKey(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean containsValue(Object o) {
+      return false;
+    }
+
+    @Override
+    public PathClassLoader get(Object o) {
+      return null;
+    }
+
+    @Nullable
+    @Override
+    public PathClassLoader put(R r, PathClassLoader unsafe) {
+      return null;
+    }
+
+    @Override
+    public PathClassLoader remove(Object o) {
+      return null;
+    }
+
+    @Override
+    public void putAll(@NotNull Map<? extends R, ? extends PathClassLoader> map) {}
+
+    @Override
+    public void clear() {}
+
+    @NotNull
+    @Override
+    public Set<R> keySet() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Collection<PathClassLoader> values() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Set<Entry<R, PathClassLoader>> entrySet() {
+      return null;
+    }
+  }
+
+  static class CollectionMapExtendImplementInteger2<R> extends HashMap<R, PathClassLoader>
+      implements Iterable<R> {
+    @NotNull
+    @Override
+    public Iterator<R> iterator() {
+      return null;
+    }
+  }
+
+  // SQLDataException implements Iterable<Throwable>.
+  static class MySQLDataException extends SQLDataException {}
+
+  // java.util.Date for the extra dispatch case.
+  static class MyDate extends Date {}
+
+  static class MyDateZ<Z> extends Date implements MyInterface<Z> {
+    @Override
+    public void print(Z z) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
index 8f6a2d6..3e3a02c 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
@@ -38,7 +38,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection params() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -91,6 +91,7 @@
 
   @NeverMerge
   public abstract static class BaseSuperClass implements RunInterface {
+    @Override
     public void run() {
       System.out.println(getFromFeature());
     }
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
index fa3ae88..e6d0ee9 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
@@ -37,7 +37,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection params() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
index 067f920..5a0fd21 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -38,7 +38,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection params() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -99,6 +99,7 @@
   @NeverMerge
   public static class BaseClass implements RunInterface {
 
+    @Override
     @NeverInline
     public void run() {
       System.out.println(BaseWithStatic.getBase42());
@@ -119,6 +120,7 @@
 
   public static class FeatureClass extends BaseClass {
 
+    @Override
     public void run() {
       super.run();
       System.out.println(AFeatureWithStatic.getFoobar());
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitServiceLoaderTest.java b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitServiceLoaderTest.java
new file mode 100644
index 0000000..c290f26
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitServiceLoaderTest.java
@@ -0,0 +1,275 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.dexsplitter;
+
+import static com.android.tools.r8.rewrite.ServiceLoaderRewritingTest.getServiceLoaderLoads;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.ServiceLoader;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class R8FeatureSplitServiceLoaderTest extends SplitterTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public R8FeatureSplitServiceLoaderTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8AllServiceConfigurationInBaseAndNoTypesInFeatures() throws Exception {
+    Path base = temp.newFile("base.zip").toPath();
+    Path feature1Path = temp.newFile("feature1.zip").toPath();
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Base.class, I.class, Feature1I.class, Feature2I.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Base.class)
+        .addFeatureSplit(
+            builder -> simpleSplitProvider(builder, feature1Path, temp, Feature3Dummy.class))
+        .addDataEntryResources(
+            DataEntryResource.fromBytes(
+                StringUtils.lines(Feature1I.class.getTypeName(), Feature2I.class.getTypeName())
+                    .getBytes(),
+                "META-INF/services/" + I.class.getTypeName(),
+                Origin.unknown()))
+        .compile()
+        .inspect(
+            inspector -> {
+              assertEquals(0, getServiceLoaderLoads(inspector, Base.class));
+            })
+        .writeToZip(base)
+        .run(parameters.getRuntime(), Base.class)
+        .assertSuccessWithOutputLines("Feature1I.foo()", "Feature2I.foo()");
+  }
+
+  @Test
+  public void testR8AllServiceConfigurationInBase() throws Exception {
+    Path base = temp.newFile("base.zip").toPath();
+    Path feature1Path = temp.newFile("feature1.zip").toPath();
+    Path feature2Path = temp.newFile("feature2.zip").toPath();
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(Base.class, I.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(Base.class)
+            .addFeatureSplit(
+                builder -> simpleSplitProvider(builder, feature1Path, temp, Feature1I.class))
+            .addFeatureSplit(
+                builder -> simpleSplitProvider(builder, feature2Path, temp, Feature2I.class))
+            .addDataEntryResources(
+                DataEntryResource.fromBytes(
+                    StringUtils.lines(Feature1I.class.getTypeName(), Feature2I.class.getTypeName())
+                        .getBytes(),
+                    "META-INF/services/" + I.class.getTypeName(),
+                    Origin.unknown()))
+            .compile()
+            .inspect(
+                inspector -> {
+                  assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
+                })
+            .writeToZip(base)
+            .addRunClasspathFiles(feature1Path, feature2Path)
+            .run(parameters.getRuntime(), Base.class);
+    // TODO(b/160888348): This is failing on 7.0
+    if (parameters.getRuntime().isDex()
+        && parameters.getRuntime().asDex().getVm().getVersion() == Version.V7_0_0) {
+      runResult.assertFailureWithErrorThatMatches(containsString("ServiceConfigurationError"));
+    } else {
+      runResult.assertSuccessWithOutputLines("Feature1I.foo()", "Feature2I.foo()");
+    }
+  }
+
+  @Test
+  public void testR8AllLoaded() throws Exception {
+    Path base = temp.newFile("base.zip").toPath();
+    Path feature1Path = temp.newFile("feature1.zip").toPath();
+    Path feature2Path = temp.newFile("feature2.zip").toPath();
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(Base.class, I.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(Base.class)
+            .addFeatureSplit(
+                builder ->
+                    splitWithNonJavaFile(
+                        builder,
+                        feature1Path,
+                        temp,
+                        ImmutableList.of(
+                            new Pair<>(
+                                "META-INF/services/" + I.class.getTypeName(),
+                                StringUtils.lines(Feature1I.class.getTypeName()))),
+                        true,
+                        Feature1I.class))
+            .addFeatureSplit(
+                builder ->
+                    splitWithNonJavaFile(
+                        builder,
+                        feature2Path,
+                        temp,
+                        ImmutableList.of(
+                            new Pair<>(
+                                "META-INF/services/" + I.class.getTypeName(),
+                                StringUtils.lines(Feature2I.class.getTypeName()))),
+                        true,
+                        Feature2I.class))
+            .compile()
+            .inspect(
+                inspector -> {
+                  assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
+                })
+            .writeToZip(base)
+            .addRunClasspathFiles(feature1Path, feature2Path)
+            .run(parameters.getRuntime(), Base.class);
+    // TODO(b/160888348): This is failing on 7.0
+    if (parameters.getRuntime().isDex()
+        && parameters.getRuntime().asDex().getVm().getVersion() == Version.V7_0_0) {
+      runResult.assertFailureWithErrorThatMatches(containsString("ServiceConfigurationError"));
+    } else {
+      runResult.assertSuccessWithOutputLines("Feature1I.foo()", "Feature2I.foo()");
+    }
+  }
+
+  @Test
+  public void testR8WithServiceFileInSeparateFeature() throws Exception {
+    Path base = temp.newFile("base.zip").toPath();
+    Path feature1Path = temp.newFile("feature1.zip").toPath();
+    Path feature2Path = temp.newFile("feature2.zip").toPath();
+    Path feature3Path = temp.newFile("feature3.zip").toPath();
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(Base.class, I.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(Base.class)
+            .addFeatureSplit(
+                builder -> simpleSplitProvider(builder, feature1Path, temp, Feature1I.class))
+            .addFeatureSplit(
+                builder -> simpleSplitProvider(builder, feature2Path, temp, Feature2I.class))
+            .addFeatureSplit(
+                builder ->
+                    splitWithNonJavaFile(
+                        builder,
+                        feature3Path,
+                        temp,
+                        ImmutableList.of(
+                            new Pair<>(
+                                "META-INF/services/" + I.class.getTypeName(),
+                                StringUtils.lines(
+                                    Feature1I.class.getTypeName(), Feature2I.class.getTypeName()))),
+                        true,
+                        Feature3Dummy.class))
+            .compile()
+            .inspect(
+                inspector -> {
+                  assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
+                })
+            .writeToZip(base)
+            .addRunClasspathFiles(feature1Path, feature2Path, feature3Path)
+            .run(parameters.getRuntime(), Base.class);
+    // TODO(b/160888348): This is failing on 7.0
+    if (parameters.getRuntime().isDex()
+        && parameters.getRuntime().asDex().getVm().getVersion() == Version.V7_0_0) {
+      runResult.assertFailureWithErrorThatMatches(containsString("ServiceConfigurationError"));
+    } else {
+      runResult.assertSuccessWithOutputLines("Feature1I.foo()", "Feature2I.foo()");
+    }
+  }
+
+  @Test
+  public void testR8OnlyFeature2() throws Exception {
+    Path base = temp.newFile("base.zip").toPath();
+    Path feature1Path = temp.newFile("feature1.zip").toPath();
+    Path feature2Path = temp.newFile("feature2.zip").toPath();
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Base.class, I.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Base.class)
+        .addFeatureSplit(
+            builder ->
+                splitWithNonJavaFile(
+                    builder,
+                    feature1Path,
+                    temp,
+                    ImmutableList.of(
+                        new Pair<>(
+                            "META-INF/services/" + I.class.getTypeName(),
+                            StringUtils.lines(Feature1I.class.getTypeName()))),
+                    true,
+                    Feature1I.class))
+        .addFeatureSplit(
+            builder ->
+                splitWithNonJavaFile(
+                    builder,
+                    feature2Path,
+                    temp,
+                    ImmutableList.of(
+                        new Pair<>(
+                            "META-INF/services/" + I.class.getTypeName(),
+                            StringUtils.lines(Feature2I.class.getTypeName()))),
+                    true,
+                    Feature2I.class))
+        .compile()
+        .inspect(
+            inspector -> {
+              assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
+            })
+        .writeToZip(base)
+        .addRunClasspathFiles(feature2Path)
+        .run(parameters.getRuntime(), Base.class)
+        // TODO(b/160889305): This should work.
+        .assertFailureWithErrorThatMatches(containsString("java.lang.ClassNotFoundException"));
+  }
+
+  public interface I {
+    void foo();
+  }
+
+  public static class Base {
+
+    public static void main(String[] args) {
+      for (I i : ServiceLoader.load(I.class, null)) {
+        i.foo();
+      }
+    }
+  }
+
+  public static class Feature1I implements I {
+
+    @Override
+    public void foo() {
+      System.out.println("Feature1I.foo()");
+    }
+  }
+
+  public static class Feature2I implements I {
+
+    @Override
+    public void foo() {
+      System.out.println("Feature2I.foo()");
+    }
+  }
+
+  public static class Feature3Dummy {}
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
index 6f609de..60477b6 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
@@ -40,7 +41,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection params() {
-    return getTestParameters().withDexRuntimes().build();
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -122,11 +123,12 @@
     Path basePath = temp.newFile("base.zip").toPath();
     Path feature1Path = temp.newFile("feature1.zip").toPath();
     Path feature2Path = temp.newFile("feature2.zip").toPath();
-    Collection<String> nonJavaFiles = ImmutableList.of("foobar", "barfoo");
+    Collection<Pair<String, String>> nonJavaFiles =
+        ImmutableList.of(new Pair<>("foobar", "foobar"), new Pair<>("barfoo", "barfoo"));
 
     testForR8(parameters.getBackend())
         .addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .addFeatureSplit(
             builder ->
                 splitWithNonJavaFile(
@@ -140,16 +142,16 @@
         .writeToZip(basePath);
     for (Path feature : ImmutableList.of(feature1Path, feature2Path)) {
       ZipFile zipFile = new ZipFile(feature.toFile());
-      for (String nonJavaFile : nonJavaFiles) {
-        ZipEntry entry = zipFile.getEntry(nonJavaFile);
+      for (Pair<String, String> nonJavaFile : nonJavaFiles) {
+        ZipEntry entry = zipFile.getEntry(nonJavaFile.getFirst());
         assertNotNull(entry);
         String content = new String(ByteStreams.toByteArray(zipFile.getInputStream(entry)));
-        assertEquals(content, nonJavaFile);
+        assertEquals(content, nonJavaFile.getSecond());
       }
     }
     ZipFile zipFile = new ZipFile(basePath.toFile());
-    for (String nonJavaFile : nonJavaFiles) {
-      ZipEntry entry = zipFile.getEntry(nonJavaFile);
+    for (Pair<String, String> nonJavaFile : nonJavaFiles) {
+      ZipEntry entry = zipFile.getEntry(nonJavaFile.getFirst());
       assertNull(entry);
     }
   }
@@ -160,12 +162,14 @@
     Path feature1Path = temp.newFile("feature1.zip").toPath();
     Path feature2Path = temp.newFile("feature2.zip").toPath();
     // Make the content of the data resource be class names
-    Collection<String> nonJavaFiles =
-        ImmutableList.of(FeatureClass.class.getName(), FeatureClass2.class.getName());
+    Collection<Pair<String, String>> nonJavaFiles =
+        ImmutableList.of(
+            new Pair<>(FeatureClass.class.getName(), FeatureClass.class.getName()),
+            new Pair<>(FeatureClass2.class.getName(), FeatureClass2.class.getName()));
 
     testForR8(parameters.getBackend())
         .addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .addFeatureSplit(
             builder ->
                 splitWithNonJavaFile(
@@ -181,16 +185,16 @@
         .writeToZip(basePath);
     for (Path feature : ImmutableList.of(feature1Path, feature2Path)) {
       ZipFile zipFile = new ZipFile(feature.toFile());
-      for (String nonJavaFile : nonJavaFiles) {
-        ZipEntry entry = zipFile.getEntry(nonJavaFile);
+      for (Pair<String, String> nonJavaFile : nonJavaFiles) {
+        ZipEntry entry = zipFile.getEntry(nonJavaFile.getFirst());
         assertNotNull(entry);
         String content = new String(ByteStreams.toByteArray(zipFile.getInputStream(entry)));
-        assertNotEquals(content, nonJavaFile);
+        assertNotEquals(content, nonJavaFile.getSecond());
       }
     }
     ZipFile zipFile = new ZipFile(basePath.toFile());
-    for (String nonJavaFile : nonJavaFiles) {
-      ZipEntry entry = zipFile.getEntry(nonJavaFile);
+    for (Pair<String, String> nonJavaFile : nonJavaFiles) {
+      ZipEntry entry = zipFile.getEntry(nonJavaFile.getFirst());
       assertNull(entry);
     }
   }
@@ -261,7 +265,7 @@
 
       testForR8(parameters.getBackend())
           .addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
-          .setMinApi(parameters.getRuntime())
+          .setMinApi(parameters.getApiLevel())
           .addFeatureSplit(
               builder -> simpleSplitProvider(builder, feature1Path, temp, FeatureClass.class))
           .addFeatureSplit(
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
index 331403a..def7782 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
@@ -37,7 +37,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection params() {
-    return getTestParameters().withDexRuntimes().build();
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -72,6 +72,7 @@
 
   @NeverMerge
   public abstract static class BaseSuperClass implements RunInterface {
+    @Override
     public void run() {
       System.out.println(getFromFeature());
     }
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index 16291c4..536d19d 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -1,6 +1,7 @@
 package com.android.tools.r8.dexsplitter;
 
 import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.ByteDataView;
@@ -19,6 +20,7 @@
 import com.android.tools.r8.dexsplitter.DexSplitter.Options;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
@@ -42,11 +44,11 @@
 public class SplitterTestBase extends TestBase {
 
   public static FeatureSplit simpleSplitProvider(
-      FeatureSplit.Builder builder, Path outputPath, TemporaryFolder temp, Class... classes) {
+      FeatureSplit.Builder builder, Path outputPath, TemporaryFolder temp, Class<?>... classes) {
     return simpleSplitProvider(builder, outputPath, temp, Arrays.asList(classes));
   }
 
-  public static FeatureSplit simpleSplitProvider(
+  private static FeatureSplit simpleSplitProvider(
       FeatureSplit.Builder builder,
       Path outputPath,
       TemporaryFolder temp,
@@ -59,7 +61,7 @@
       FeatureSplit.Builder builder,
       Path outputPath,
       TemporaryFolder temp,
-      Collection<String> nonJavaResources,
+      Collection<Pair<String, String>> nonJavaResources,
       boolean ensureClassesInOutput,
       Collection<Class<?>> classes) {
     List<String> classNames = classes.stream().map(Class::getName).collect(Collectors.toList());
@@ -82,15 +84,18 @@
           next = inputStream.getNextEntry();
         }
 
-        for (String nonJavaResource : nonJavaResources) {
+        for (Pair<String, String> nonJavaResource : nonJavaResources) {
           ZipUtils.writeToZipStream(
-              outputStream, nonJavaResource, nonJavaResource.getBytes(), ZipEntry.STORED);
+              outputStream,
+              nonJavaResource.getFirst(),
+              nonJavaResource.getSecond().getBytes(),
+              ZipEntry.STORED);
         }
         outputStream.close();
         featureJar = newFeatureJar;
       }
     } catch (IOException e) {
-      assertTrue(false);
+      fail();
       return;
     }
 
@@ -115,18 +120,19 @@
             });
   }
 
-  protected static FeatureSplit splitWithNonJavaFile(
+  static FeatureSplit splitWithNonJavaFile(
       FeatureSplit.Builder builder,
       Path outputPath,
       TemporaryFolder temp,
-      Collection<String> nonJavaFiles,
+      Collection<Pair<String, String>> nonJavaFiles,
       boolean ensureClassesInOutput,
       Class<?>... classes) {
-    addConsumers(builder, outputPath, temp, nonJavaFiles, true, Arrays.asList(classes));
+    addConsumers(
+        builder, outputPath, temp, nonJavaFiles, ensureClassesInOutput, Arrays.asList(classes));
     return builder.build();
   }
 
-  protected <E extends Throwable> ProcessResult testR8Splitter(
+  <E extends Throwable> ProcessResult testR8Splitter(
       TestParameters parameters,
       Set<Class<?>> baseClasses,
       Set<Class<?>> featureClasses,
@@ -150,7 +156,7 @@
         .addProgramClasses(baseClasses)
         .addFeatureSplit(
             builder -> simpleSplitProvider(builder, featureOutput, temp, featureClasses))
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(SplitRunner.class)
         .addKeepClassRules(toRun);
 
@@ -165,7 +171,7 @@
 
   // Compile the passed in classes plus RunInterface and SplitRunner using R8, then split
   // based on the base/feature sets. toRun must implement the BaseRunInterface
-  protected <E extends Throwable> ProcessResult testDexSplitter(
+  <E extends Throwable> ProcessResult testDexSplitter(
       TestParameters parameters,
       Set<Class<?>> baseClasses,
       Set<Class<?>> featureClasses,
@@ -189,7 +195,7 @@
             .addClasspathClasses(baseClasses)
             .addClasspathClasses(RunInterface.class)
             .addKeepAllClassesRule()
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .compile()
             .writeToZip();
     if (parameters.isDexRuntime()) {
@@ -199,7 +205,7 @@
       testForD8()
           .addProgramClasses(SplitRunner.class, RunInterface.class)
           .addProgramClasses(baseClasses)
-          .setMinApi(parameters.getRuntime())
+          .setMinApi(parameters.getApiLevel())
           .compile()
           .run(
               parameters.getRuntime(),
@@ -220,7 +226,7 @@
 
     R8FullTestBuilder r8FullTestBuilder =
         builder
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .addProgramClasses(SplitRunner.class, RunInterface.class)
             .addProgramClasses(baseClasses)
             .addProgramClasses(featureClasses)
@@ -252,7 +258,7 @@
         toRun, splitterBaseDexFile, splitterFeatureDexFile, parameters.getRuntime());
   }
 
-  protected ProcessResult runFeatureOnArt(
+  ProcessResult runFeatureOnArt(
       Class toRun, Path splitterBaseDexFile, Path splitterFeatureDexFile, TestRuntime runtime)
       throws IOException {
     assumeTrue(runtime.isDex());
@@ -261,8 +267,7 @@
     commandBuilder.appendProgramArgument(toRun.getName());
     commandBuilder.appendProgramArgument(splitterFeatureDexFile.toString());
     commandBuilder.setMainClass(SplitRunner.class.getName());
-    ProcessResult processResult = ToolHelper.runArtRaw(commandBuilder);
-    return processResult;
+    return ToolHelper.runArtRaw(commandBuilder);
   }
 
   public interface RunInterface {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumMissingFieldsUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumMissingFieldsUnboxingTest.java
new file mode 100644
index 0000000..f6b55cf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumMissingFieldsUnboxingTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnumMissingFieldsUnboxingTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final EnumKeepRules enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public EnumMissingFieldsUnboxingTest(
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(TestClass.class)
+            .addProgramClassFileData(getEnumProgramData())
+            .addKeepMainRule(TestClass.class)
+            .addKeepRules(enumKeepRules.getKeepRules())
+            .enableNeverClassInliningAnnotations()
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+            .allowDiagnosticInfoMessages()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspectDiagnosticMessages(
+                m -> assertEnumIsBoxed(CompilationEnum.class, TestClass.class.getSimpleName(), m))
+            .run(parameters.getRuntime(), TestClass.class)
+            .assertFailureWithErrorThatMatches(containsString("NoSuchFieldError"));
+    assertLines2By2Correct(run.getStdOut());
+  }
+
+  private byte[] getEnumProgramData() throws IOException {
+    return transformer(RuntimeEnum.class)
+        .setClassDescriptor(descriptor(CompilationEnum.class))
+        .transform();
+  }
+
+  // CompilationEnum is used for the compilation of TestClass.
+  @NeverClassInline
+  public enum CompilationEnum {
+    A,
+    B,
+    C,
+    D
+  }
+
+  // CompilationEnum is used for the runtime execution of TestClass.
+  @NeverClassInline
+  public enum RuntimeEnum {
+    A,
+    D
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(CompilationEnum.A.ordinal());
+      System.out.println(0);
+      // The field C will be missing at runtime.
+      System.out.println(CompilationEnum.C.ordinal());
+      System.out.println(2);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
index 1f82de1..65aee25 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -63,6 +63,7 @@
   }
 
   void enableEnumOptions(InternalOptions options, boolean enumValueOptimization) {
+    // TODO(b/160854837): re-enable enum unboxing.
     options.enableEnumUnboxing = true;
     options.enableEnumValueOptimization = enumValueOptimization;
     options.enableEnumSwitchMapRemoval = enumValueOptimization;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
index 6323e8b..2e3466f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
@@ -4,8 +4,11 @@
 
 package com.android.tools.r8.enumunboxing;
 
+import static org.hamcrest.CoreMatchers.containsString;
+
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
@@ -36,10 +39,11 @@
   @Test
   public void testEnumUnboxing() throws Exception {
     Class<?> classToTest = VirtualMethods.class;
-    R8TestRunResult run =
+    R8TestCompileResult compile =
         testForR8(parameters.getBackend())
             .addInnerClasses(VirtualMethodsEnumUnboxingTest.class)
             .addKeepMainRule(classToTest)
+            .addKeepMainRule(VirtualMethodsFail.class)
             .addKeepRules(enumKeepRules.getKeepRules())
             .enableNeverClassInliningAnnotations()
             .enableInliningAnnotations()
@@ -51,10 +55,16 @@
                 m -> {
                   assertEnumIsUnboxed(MyEnum.class, classToTest.getSimpleName(), m);
                   assertEnumIsUnboxed(MyEnum2.class, classToTest.getSimpleName(), m);
-                })
-            .run(parameters.getRuntime(), classToTest)
-            .assertSuccess();
+                  assertEnumIsUnboxed(MyEnumWithCollisions.class, classToTest.getSimpleName(), m);
+                  assertEnumIsUnboxed(
+                      MyEnumWithPackagePrivateCall.class, classToTest.getSimpleName(), m);
+                });
+    R8TestRunResult run = compile.run(parameters.getRuntime(), classToTest).assertSuccess();
     assertLines2By2Correct(run.getStdOut());
+    // TODO(b/160854837): This test should actually be successful.
+    compile
+        .run(parameters.getRuntime(), VirtualMethodsFail.class)
+        .assertFailureWithErrorThatMatches(containsString("IllegalAccessError"));
   }
 
   @SuppressWarnings("SameParameterValue")
@@ -102,6 +112,7 @@
   }
 
   // Use two enums to test collision.
+  @NeverClassInline
   enum MyEnum2 {
     A,
     B,
@@ -123,6 +134,76 @@
     }
   }
 
+  @NeverClassInline
+  enum MyEnumWithCollisions {
+    A,
+    B,
+    C;
+
+    @NeverInline
+    public int get() {
+      return get(this);
+    }
+
+    @NeverInline
+    public static int get(MyEnumWithCollisions e) {
+      switch (e) {
+        case A:
+          return 5;
+        case B:
+          return 2;
+        case C:
+          return 1;
+      }
+      return -1;
+    }
+  }
+
+  @NeverClassInline
+  static class PackagePrivateClass {
+    @NeverInline
+    static void print() {
+      System.out.println("print");
+    }
+  }
+
+  @NeverClassInline
+  enum MyEnumWithPackagePrivateCall {
+    A,
+    B,
+    C;
+
+    @NeverInline
+    public static void callPackagePrivate() {
+      PackagePrivateClass.print();
+    }
+  }
+
+  static class VirtualMethodsFail {
+    public static void main(String[] args) {
+      testCollisions();
+      testPackagePrivate();
+    }
+
+    @NeverInline
+    private static void testPackagePrivate() {
+      System.out.println(MyEnumWithPackagePrivateCall.A.ordinal());
+      System.out.println(0);
+      MyEnumWithPackagePrivateCall.callPackagePrivate();
+      System.out.println("print");
+    }
+
+    @NeverInline
+    private static void testCollisions() {
+      System.out.println(MyEnumWithCollisions.A.get());
+      System.out.println(5);
+      System.out.println(MyEnumWithCollisions.B.get());
+      System.out.println(2);
+      System.out.println(MyEnumWithCollisions.C.get());
+      System.out.println(1);
+    }
+  }
+
   static class VirtualMethods {
 
     public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
index 2d56233..0465bc5 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
@@ -50,7 +51,11 @@
         new ProgramMethod(
             clazz,
             new DexEncodedMethod(
-                signature, null, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null));
+                signature,
+                MethodAccessFlags.fromDexAccessFlags(0),
+                DexAnnotationSet.empty(),
+                ParameterAnnotationsList.empty(),
+                null));
     return new Node(method);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 75c45d1..1d7f782 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -369,16 +370,27 @@
       InstructionSubject instruction = iterator.next();
       if (instruction.isInstanceGet()) {
         ++instanceGetCount;
-      } else if (instruction.isInvoke()
-          && !instruction
-              .getMethod()
-              .name
-              .toString()
-              .startsWith(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX)) {
+      } else if (instruction.isInvoke() && !isEnumInvoke(instruction)) {
         ++invokeCount;
       }
     }
     assertEquals(1, instanceGetCount);
-    assertEquals(BooleanUtils.intValue(parameters.isCfRuntime()), invokeCount);
+    assertEquals(0, invokeCount);
+  }
+
+  private boolean isEnumInvoke(InstructionSubject instruction) {
+    InternalOptions defaults = new InternalOptions();
+    if (parameters.isDexRuntime() && defaults.enableEnumUnboxing) {
+      return instruction
+          .getMethod()
+          .name
+          .toString()
+          .startsWith(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX);
+    } else {
+      return ((InvokeInstructionSubject) instruction)
+          .holder()
+          .toString()
+          .contains("java.lang.Enum");
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastNullForTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastNullForTypeTest.java
new file mode 100644
index 0000000..4f60021
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastNullForTypeTest.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.checkcast;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This is a reproduction of b/160856783. */
+@RunWith(Parameterized.class)
+public class CheckCastNullForTypeTest extends TestBase {
+
+  private final TestParameters parameters;
+  private static final String EXPECTED = StringUtils.lines("null", "null");
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public CheckCastNullForTypeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(A.class, Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepClassAndMembersRules(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            codeInspector -> {
+              ClassSubject main = codeInspector.clazz(Main.class);
+              assertThat(main, isPresent());
+              MethodSubject mainMethod = main.uniqueMethodWithName("main");
+              assertThat(mainMethod, isPresent());
+              // TODO(b/160856783): Investigate if this can be removed.
+              assertEquals(
+                  1,
+                  mainMethod
+                      .streamInstructions()
+                      .filter(instruction -> instruction.isCheckCast(Main.class.getTypeName()))
+                      .count());
+            });
+  }
+
+  public static class A {}
+
+  public static class Main {
+
+    private static void print(Main main) {
+      System.out.println(main);
+    }
+
+    public static void main(String[] args) {
+      A a = null;
+      Main main;
+      do {
+        main = (Main) (Object) a;
+      } while ((a = (A) null) != null);
+      System.out.println(a);
+      print(main);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerInstanceEscapeViaPhiTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerInstanceEscapeViaPhiTest.java
new file mode 100644
index 0000000..147d2ed
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerInstanceEscapeViaPhiTest.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Reproduction of b/160901582 where we inline class with escaping instance variable. */
+@RunWith(Parameterized.class)
+public class ClassInlinerInstanceEscapeViaPhiTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlinerInstanceEscapeViaPhiTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(ClassInlinerInstanceEscapeViaPhiTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("false");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlinerInstanceEscapeViaPhiTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("false");
+  }
+
+  static class A {
+
+    public A() {
+      B.foo(System.nanoTime() > 0 ? this : null);
+    }
+  }
+
+  static class B {
+
+    static void foo(A a) {
+      System.out.println((System.nanoTime() > 0 ? a : null) == null);
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new A();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
index 0c3206c..383a7ef 100644
--- a/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
+++ b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
@@ -57,15 +57,13 @@
   }
 
   private void checkJoinerIsClassInlined(CodeInspector inspector) {
-    // TODO(b/160640028): When compiling to DEX a trivial phi remains in the inline code preventing
-    //  class inlining of Joiner and the anonymous Joiner subclass.
+    assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), not(isPresent()));
+    // TODO(b/160640028): When compiling to DEX the outer Joiner class is not inlined.
     if (parameters.isDexRuntime()) {
       assertThat(inspector.clazz(Joiner.class), isPresent());
-      assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), isPresent());
-      return;
+    } else {
+      assertThat(inspector.clazz(Joiner.class), not(isPresent()));
     }
-    assertThat(inspector.clazz(Joiner.class), not(isPresent()));
-    assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), not(isPresent()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/regress/Regress160831625Test.java b/src/test/java/com/android/tools/r8/regress/Regress160831625Test.java
new file mode 100644
index 0000000..15c0c75
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/Regress160831625Test.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.regress;
+
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Regress160831625Test extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A", "0");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public Regress160831625Test(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addInnerClasses(getClass())
+        .enableMemberValuePropagationAnnotations()
+        .addKeepMainRule(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  enum MyEnum {
+    // Ensure that the enum field value is not inlined in the alias in MyClass.B
+    @NeverPropagateValue
+    A
+  }
+
+  static class MyClass {
+
+    private static final MyEnum B = MyEnum.A;
+
+    public static MyEnum getB() {
+      return B;
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(MyClass.getB().name());
+      System.out.println(MyClass.getB().ordinal());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
index 5f46a45..5e18734 100644
--- a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
@@ -326,7 +326,7 @@
     assertNotNull(zip.getEntry("META-INF/services/" + service.getFinalName()));
   }
 
-  private static long getServiceLoaderLoads(CodeInspector inspector, Class<?> clazz) {
+  public static long getServiceLoaderLoads(CodeInspector inspector, Class<?> clazz) {
     ClassSubject classSubject = inspector.clazz(clazz);
     assertTrue(classSubject.isPresent());
     return classSubject.allMethods().stream()
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 962bc23..2dcfeb6 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -208,6 +208,10 @@
       if not os.path.exists(temp):
         os.makedirs(temp)
     dump = read_dump(args, temp)
+    if not dump.program_jar():
+      error("Cannot compile dump with no program classes")
+    if not dump.library_jar():
+      print "WARNING: Unexpected lack of library classes in dump"
     build_properties = determine_build_properties(args, dump)
     version = determine_version(args, dump)
     compiler = determine_compiler(args, dump)
diff --git a/tools/r8lib_size_compare.py b/tools/r8lib_size_compare.py
deleted file mode 100755
index 568f254..0000000
--- a/tools/r8lib_size_compare.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-# for details. All rights reserved. Use of this source code is governed by a
-# BSD-style license that can be found in the LICENSE file.
-
-'''
-Build r8lib.jar with both R8 and ProGuard and print a size comparison.
-
-By default, inlining is disabled in both R8 and ProGuard to make
-method-by-method comparison much easier. Pass --inlining to enable inlining.
-
-By default, only shows methods where R8's DEX output is 5 or more instructions
-larger than ProGuard+D8's output. Pass --threshold 0 to display all methods.
-'''
-
-import argparse
-import build_r8lib
-import os
-import subprocess
-import toolhelper
-import utils
-
-
-parser = argparse.ArgumentParser(description=__doc__.strip(),
-                                 formatter_class=argparse.RawTextHelpFormatter)
-parser.add_argument('-t', '--tmpdir',
-                    help='Store auxiliary files in given directory')
-parser.add_argument('-i', '--inlining', action='store_true',
-                    help='Enable inlining')
-parser.add_argument('--threshold')
-
-R8_RELOCATIONS = [
-  ('com.google.common', 'com.android.tools.r8.com.google.common'),
-  ('com.google.gson', 'com.android.tools.r8.com.google.gson'),
-  ('com.google.thirdparty', 'com.android.tools.r8.com.google.thirdparty'),
-  ('joptsimple', 'com.android.tools.r8.joptsimple'),
-  ('org.apache.commons', 'com.android.tools.r8.org.apache.commons'),
-  ('org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm'),
-  ('it.unimi.dsi.fastutil', 'com.android.tools.r8.it.unimi.dsi.fastutil'),
-]
-
-
-def is_output_newer(input, output):
-  if not os.path.exists(output):
-    return False
-  return os.stat(input).st_mtime < os.stat(output).st_mtime
-
-
-def check_call(args, **kwargs):
-  utils.PrintCmd(args)
-  return subprocess.check_call(args, **kwargs)
-
-
-def main(tmpdir=None, inlining=True,
-         run_jarsizecompare=True, threshold=None):
-  if tmpdir is None:
-    with utils.TempDir() as tmpdir:
-      return main(tmpdir, inlining)
-
-  inline_suffix = '-inline' if inlining else '-noinline'
-
-  pg_config = utils.R8LIB_KEEP_RULES
-  r8lib_jar = os.path.join(utils.LIBS, 'r8lib%s.jar' % inline_suffix)
-  r8lib_map = os.path.join(utils.LIBS, 'r8lib%s-map.txt' % inline_suffix)
-  r8lib_args = None
-  if not inlining:
-    r8lib_args = ['-Dcom.android.tools.r8.disableinlining=1']
-    pg_config = os.path.join(tmpdir, 'keep-noinline.txt')
-    with open(pg_config, 'w') as new_config:
-      with open(utils.R8LIB_KEEP_RULES) as old_config:
-        new_config.write(old_config.read().rstrip('\n') +
-                         '\n-optimizations !method/inlining/*\n')
-
-  if not is_output_newer(utils.R8_JAR, r8lib_jar):
-    r8lib_memory = os.path.join(tmpdir, 'r8lib%s-memory.txt' % inline_suffix)
-    # TODO(b/160420801): The signature of build_r8lib has changed.
-    build_r8lib.build_r8lib(
-        output_path=r8lib_jar,
-        output_map=r8lib_map,
-        extra_args=r8lib_args,
-        track_memory_file=r8lib_memory)
-
-  pg_output = os.path.join(tmpdir, 'r8lib-pg%s.jar' % inline_suffix)
-  pg_memory = os.path.join(tmpdir, 'r8lib-pg%s-memory.txt' % inline_suffix)
-  pg_map = os.path.join(tmpdir, 'r8lib-pg%s-map.txt' % inline_suffix)
-  # TODO(b/160420801): This must use proguard.* utils once working again.
-  pg_args = ['tools/track_memory.sh', pg_memory,
-             'third_party/proguard/proguard6.0.2/bin/proguard.sh',
-             '@' + pg_config,
-             '-lib', utils.RT_JAR,
-             '-injar', utils.R8_JAR,
-             '-printmapping', pg_map,
-             '-outjar', pg_output]
-  for library_name, relocated_package in R8_RELOCATIONS:
-    pg_args.extend(['-dontwarn', relocated_package + '.**',
-                    '-dontnote', relocated_package + '.**'])
-  check_call(pg_args)
-  if threshold is None:
-    threshold = 5
-  toolhelper.run('jarsizecompare',
-                 ['--threshold', str(threshold),
-                  '--lib', utils.RT_JAR,
-                  '--input', 'input', utils.R8_JAR,
-                  '--input', 'r8', r8lib_jar, r8lib_map,
-                  '--input', 'pg', pg_output, pg_map])
-
-
-if __name__ == '__main__':
-  main(**vars(parser.parse_args()))
diff --git a/tools/test.py b/tools/test.py
index ad99485..5bc9d6c 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -143,6 +143,8 @@
       help='Test parameter runtimes to use, separated by : (eg, none:jdk9).'
           ' Special values include: all (for all runtimes)'
           ' and empty (for no runtimes).')
+  result.add_option('--print-hanging-stacks', '--print_hanging_stacks',
+      default=-1, type="int", help='Print hanging stacks after timeout in seconds')
   return result.parse_args()
 
 def archive_failures():
@@ -269,13 +271,16 @@
                                     '%s.tar.gz' % sha1)
       utils.unpack_archive('%s.tar.gz' % sha1)
 
-  if utils.is_bot() and not utils.IsWindows():
+  print_stacks_timeout = options.print_hanging_stacks
+  if (utils.is_bot() and not utils.IsWindows()) or print_stacks_timeout > -1:
     timestamp_file = os.path.join(utils.BUILD, 'last_test_time')
     if os.path.exists(timestamp_file):
       os.remove(timestamp_file)
     gradle_args.append('-Pupdate_test_timestamp=' + timestamp_file)
-    thread.start_new_thread(timeout_handler, (timestamp_file,))
-
+    print_stacks_timeout = (print_stacks_timeout
+                            if print_stacks_timeout != -1
+                            else TIMEOUT_HANDLER_PERIOD)
+    thread.start_new_thread(timeout_handler, (timestamp_file, print_stacks_timeout,))
   rotate_test_reports()
 
   if options.only_jctf:
@@ -367,10 +372,10 @@
     sys.stdout.flush()
     return None
 
-def timeout_handler(timestamp_file):
+def timeout_handler(timestamp_file, timeout_handler_period):
   last_timestamp = None
   while True:
-    time.sleep(TIMEOUT_HANDLER_PERIOD)
+    time.sleep(timeout_handler_period)
     new_timestamp = get_time_from_file(timestamp_file)
     if last_timestamp and new_timestamp == last_timestamp:
       print_jstacks()