Merge commit 'b7074ccebdab2c54493b49ed3fc53f8e26c2cb6c' into dev-release
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index ef8708f..782094c 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -256,7 +256,7 @@
       if (superTarget != null) {
         addMethod(superTarget.getReference());
       }
-      for (DexType type : method.getDefinition().parameters().values) {
+      for (DexType type : method.getDefinition().getParameters()) {
         registerTypeReference(type);
       }
       for (DexAnnotation annotation : method.getDefinition().annotations().annotations) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 489d0b6..2d91d71 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -125,8 +125,7 @@
       this.callSiteOptimizationInfoPropagator = null;
     }
 
-    this.libraryMethodSideEffectModelCollection =
-        new LibraryMethodSideEffectModelCollection(dexItemFactory());
+    this.libraryMethodSideEffectModelCollection = new LibraryMethodSideEffectModelCollection(this);
     this.libraryMemberOptimizer = new LibraryMemberOptimizer(this);
 
     if (enableWholeProgramOptimizations() && options().protoShrinking().isProtoShrinkingEnabled()) {
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 fc56e08..8060cbe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -394,12 +394,16 @@
     return method;
   }
 
-  public DexMethodSignature getSignature() {
-    return new DexMethodSignature(method);
+  public DexType getParameter(int index) {
+    return getReference().getParameter(index);
   }
 
-  public DexTypeList parameters() {
-    return method.proto.parameters;
+  public DexTypeList getParameters() {
+    return getReference().getParameters();
+  }
+
+  public DexMethodSignature getSignature() {
+    return new DexMethodSignature(method);
   }
 
   public DexType returnType() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index fd33483..e775ac6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -293,11 +293,6 @@
       createString(Constants.TEMPORARY_INSTANCE_INITIALIZER_PREFIX);
 
   public final DexString thisName = createString("this");
-
-  // As much as possible, R8 should rely on the content of the static enum field, using
-  // enumMembers.isValuesFieldCandidate or checking the object state in the optimization info.
-  // The field name is unrealiable since the filed can be minified prior to this compilation.
-  // We keep enumValuesFieldName as a heuristic only.
   public final DexString enumValuesFieldName = createString("$VALUES");
 
   public final DexString enabledFieldName = createString("ENABLED");
@@ -440,6 +435,7 @@
       createStaticallyKnownType("Ljava/util/logging/Logger;");
   public final DexType javaUtilSetType = createStaticallyKnownType("Ljava/util/Set;");
 
+  public final DexType androidAppActivity = createStaticallyKnownType("Landroid/app/Activity;");
   public final DexType androidOsBuildType = createStaticallyKnownType("Landroid/os/Build;");
   public final DexType androidOsBuildVersionType =
       createStaticallyKnownType("Landroid/os/Build$VERSION;");
@@ -683,6 +679,17 @@
           createString("makeConcat")
       );
 
+  public Map<DexMethod, int[]> libraryMethodsNonNullParamOrThrow =
+      buildLibraryMethodsNonNullParamOrThrow();
+
+  private Map<DexMethod, int[]> buildLibraryMethodsNonNullParamOrThrow() {
+    ImmutableMap.Builder<DexMethod, int[]> builder = ImmutableMap.builder();
+    for (DexMethod requireNonNullMethod : objectsMethods.requireNonNullMethods()) {
+      builder.put(requireNonNullMethod, new int[] {0});
+    }
+    return builder.build();
+  }
+
   public Set<DexMethod> libraryMethodsReturningReceiver =
       ImmutableSet.<DexMethod>builder()
           .addAll(stringBufferMethods.appendMethods)
@@ -719,6 +726,7 @@
   public Set<DexType> libraryTypesAssumedToBePresent =
       ImmutableSet.<DexType>builder()
           .add(
+              androidAppActivity,
               callableType,
               enumType,
               npeType,
@@ -1390,17 +1398,21 @@
       return field == nameField || field == ordinalField;
     }
 
-    public boolean isEnumField(DexEncodedField staticField, DexType enumType) {
-      assert staticField.isStatic();
-      return staticField.getType() == enumType && staticField.isEnum() && staticField.isFinal();
+    public boolean isValuesMethod(DexMethod method, DexClass enumClass) {
+      assert enumClass.isEnum();
+      return method.holder == enumClass.type
+          && method.proto.returnType == enumClass.type.toArrayType(1, DexItemFactory.this)
+          && method.proto.parameters.size() == 0
+          && method.name == valuesMethodName;
     }
 
-    public boolean isValuesFieldCandidate(DexEncodedField staticField, DexType enumType) {
-      assert staticField.isStatic();
-      return staticField.getType().isArrayType()
-          && staticField.getType().toArrayElementType(DexItemFactory.this) == enumType
-          && staticField.isSynthetic()
-          && staticField.isFinal();
+    public boolean isValueOfMethod(DexMethod method, DexClass enumClass) {
+      assert enumClass.isEnum();
+      return method.holder == enumClass.type
+          && method.proto.returnType == enumClass.type
+          && method.proto.parameters.size() == 1
+          && method.proto.parameters.values[0] == stringType
+          && method.name == valueOfMethodName;
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 56f9a92..c488caf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralMapping;
@@ -44,6 +43,7 @@
           "$r8$twr$utility",
           "$-DC",
           "$$ServiceLoaderMethods",
+          "com.android.tools.r8.GeneratedOutlineSupport",
           "-$$Lambda$");
 
   public final DexString descriptor;
@@ -338,7 +338,6 @@
     // Any entry that is removed from here must be added to OLD_SYNTHESIZED_NAMES to ensure that
     // newer releases can be used to merge previous builds.
     return name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX) // Could collide.
-        || name.contains(OutlineOptions.CLASS_NAME) // Global singleton.
         || name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME); // Global singleton.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index fc01602..281215f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -12,6 +12,9 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -23,7 +26,6 @@
 import com.android.tools.r8.ir.analysis.value.NullOrAbstractValue;
 import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
-import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
@@ -38,13 +40,10 @@
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
-import java.util.IdentityHashMap;
-import java.util.Map;
 
 public class StaticFieldValueAnalysis extends FieldValueAnalysis {
 
   private final StaticFieldValues.Builder builder;
-  private final Map<Value, AbstractValue> computedValues = new IdentityHashMap<>();
 
   private StaticFieldValueAnalysis(
       AppView<AppInfoWithLiveness> appView, IRCode code, OptimizationFeedback feedback) {
@@ -64,7 +63,7 @@
     timing.begin("Analyze class initializer");
     StaticFieldValues result =
         new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback)
-            .analyze(classInitializerDefaultsResult);
+            .analyze(classInitializerDefaultsResult, code.context().getHolderType());
     timing.end();
     return result;
   }
@@ -79,7 +78,8 @@
     return this;
   }
 
-  StaticFieldValues analyze(ClassInitializerDefaultsResult classInitializerDefaultsResult) {
+  StaticFieldValues analyze(
+      ClassInitializerDefaultsResult classInitializerDefaultsResult, DexType holderType) {
     computeFieldOptimizationInfo(classInitializerDefaultsResult);
     return builder.build();
   }
@@ -218,41 +218,16 @@
       return null;
     }
     assert !value.hasAliasedValue();
-    if (value.isPhi()) {
-      return null;
-    }
-    if (value.definition.isNewArrayEmpty()) {
+    if (isEnumValuesArray(value)) {
       return computeSingleEnumFieldValueForValuesArray(value);
     }
-    if (value.definition.isNewInstance()) {
-      return computeSingleEnumFieldValueForInstance(value);
-    }
-    return null;
+    return computeSingleEnumFieldValueForInstance(value);
   }
 
   private SingleFieldValue computeSingleEnumFieldValueForValuesArray(Value value) {
-    if (!value.definition.isNewArrayEmpty()) {
+    if (!value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty)) {
       return null;
     }
-    AbstractValue valuesValue = computedValues.get(value);
-    if (valuesValue != null) {
-      // This implicitely answers null if the value could not get computed.
-      if (valuesValue.isSingleFieldValue()) {
-        SingleFieldValue fieldValue = valuesValue.asSingleFieldValue();
-        if (fieldValue.getState().isEnumValuesObjectState()) {
-          return fieldValue;
-        }
-      }
-      return null;
-    }
-    SingleFieldValue singleFieldValue = internalComputeSingleEnumFieldValueForValuesArray(value);
-    computedValues.put(
-        value, singleFieldValue == null ? UnknownValue.getInstance() : singleFieldValue);
-    return singleFieldValue;
-  }
-
-  private SingleFieldValue internalComputeSingleEnumFieldValueForValuesArray(Value value) {
-    assert value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty);
 
     NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty();
     if (newArrayEmpty.type.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
@@ -292,9 +267,7 @@
             // We need the state of all fields for the analysis to be valuable.
             return null;
           }
-          if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
-            return null;
-          }
+          assert verifyValuesArrayIndexMatchesOrdinal(index, objectState);
           if (valuesState[index] != null) {
             return null;
           }
@@ -353,25 +326,24 @@
     return ObjectState.empty();
   }
 
-  private boolean valuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
+  private boolean verifyValuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
     DexEncodedField ordinalField =
         appView
             .appInfo()
             .resolveField(appView.dexItemFactory().enumMembers.ordinalField, context)
             .getResolvedField();
-    if (ordinalField == null) {
-      return false;
-    }
+    assert ordinalField != null;
     AbstractValue ordinalState = objectState.getAbstractFieldValue(ordinalField);
-    if (ordinalState == null || !ordinalState.isSingleNumberValue()) {
-      return false;
-    }
-    int intValue = ordinalState.asSingleNumberValue().getIntValue();
-    return intValue == ordinal;
+    assert ordinalState != null;
+    assert ordinalState.isSingleNumberValue();
+    assert ordinalState.asSingleNumberValue().getIntValue() == ordinal;
+    return true;
   }
 
   private SingleFieldValue computeSingleEnumFieldValueForInstance(Value value) {
-    assert value.isDefinedByInstructionSatisfying(Instruction::isNewInstance);
+    if (!value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
+      return null;
+    }
 
     NewInstance newInstance = value.definition.asNewInstance();
     // Some enums have direct subclasses, and the subclass is instantiated here.
@@ -487,7 +459,30 @@
   }
 
   private boolean isEnumValuesArray(Value value) {
-    SingleFieldValue singleFieldValue = computeSingleEnumFieldValueForValuesArray(value);
-    return singleFieldValue != null && singleFieldValue.getState().isEnumValuesObjectState();
+    assert context.getHolder().isEnum();
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexField valuesField =
+        dexItemFactory.createField(
+            context.getHolderType(),
+            context.getHolderType().toArrayType(1, dexItemFactory),
+            dexItemFactory.enumValuesFieldName);
+
+    Value root = value.getAliasedValue();
+    if (root.isPhi()) {
+      return false;
+    }
+
+    Instruction definition = root.definition;
+    if (definition.isNewArrayEmpty()) {
+      for (Instruction user : root.aliasedUsers()) {
+        if (user.isStaticPut() && user.asStaticPut().getField() == valuesField) {
+          return true;
+        }
+      }
+    } else if (definition.isStaticGet()) {
+      return definition.asStaticGet().getField() == valuesField;
+    }
+
+    return false;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
index 65e1ea3..b2dcdcd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
@@ -37,10 +37,17 @@
   // All the abstract values stored here may match a pinned field, using them requires therefore
   // to check the field is not pinned or prove it is no longer pinned.
   public static class EnumStaticFieldValues extends StaticFieldValues {
-    private final ImmutableMap<DexField, ObjectState> enumAbstractValues;
+    private final ImmutableMap<DexField, AbstractValue> enumAbstractValues;
+    private final DexField valuesField;
+    private final AbstractValue valuesAbstractValue;
 
-    public EnumStaticFieldValues(ImmutableMap<DexField, ObjectState> enumAbstractValues) {
+    public EnumStaticFieldValues(
+        ImmutableMap<DexField, AbstractValue> enumAbstractValues,
+        DexField valuesField,
+        AbstractValue valuesAbstractValue) {
       this.enumAbstractValues = enumAbstractValues;
+      this.valuesField = valuesField;
+      this.valuesAbstractValue = valuesAbstractValue;
     }
 
     static StaticFieldValues.Builder builder() {
@@ -48,38 +55,33 @@
     }
 
     public static class Builder extends StaticFieldValues.Builder {
-      private final ImmutableMap.Builder<DexField, ObjectState> enumObjectStateBuilder =
+      private final ImmutableMap.Builder<DexField, AbstractValue> enumAbstractValuesBuilder =
           ImmutableMap.builder();
-      private AbstractValue valuesCandidateAbstractValue;
+      private DexField valuesFields;
+      private AbstractValue valuesAbstractValue;
 
       Builder() {}
 
       @Override
       public void recordStaticField(
           DexEncodedField staticField, AbstractValue value, DexItemFactory factory) {
-        if (factory.enumMembers.isValuesFieldCandidate(staticField, staticField.getHolderType())) {
-          if (value.isSingleFieldValue()
-              && value.asSingleFieldValue().getState().isEnumValuesObjectState()) {
-            assert valuesCandidateAbstractValue == null
-                || valuesCandidateAbstractValue.equals(value);
-            valuesCandidateAbstractValue = value;
-            enumObjectStateBuilder.put(staticField.field, value.asSingleFieldValue().getState());
-          }
-        } else if (factory.enumMembers.isEnumField(staticField, staticField.getHolderType())) {
-          if (value.isSingleFieldValue() && !value.asSingleFieldValue().getState().isEmpty()) {
-            enumObjectStateBuilder.put(staticField.field, value.asSingleFieldValue().getState());
-          }
+        // TODO(b/166532388): Stop relying on the values name.
+        if (staticField.getName() == factory.enumValuesFieldName) {
+          valuesFields = staticField.field;
+          valuesAbstractValue = value;
+        } else if (staticField.isEnum()) {
+          enumAbstractValuesBuilder.put(staticField.field, value);
         }
       }
 
       @Override
       public StaticFieldValues build() {
-        ImmutableMap<DexField, ObjectState> enumAbstractValues = enumObjectStateBuilder.build();
-        if (enumAbstractValues.isEmpty()) {
+        ImmutableMap<DexField, AbstractValue> enumAbstractValues =
+            enumAbstractValuesBuilder.build();
+        if (valuesAbstractValue == null && enumAbstractValues.isEmpty()) {
           return EmptyStaticValues.getInstance();
         }
-        assert enumAbstractValues.values().stream().noneMatch(ObjectState::isEmpty);
-        return new EnumStaticFieldValues(enumAbstractValues);
+        return new EnumStaticFieldValues(enumAbstractValues, valuesFields, valuesAbstractValue);
       }
     }
 
@@ -94,7 +96,20 @@
     }
 
     public ObjectState getObjectStateForPossiblyPinnedField(DexField field) {
-      return enumAbstractValues.get(field);
+      AbstractValue fieldValue = enumAbstractValues.get(field);
+      if (fieldValue == null || fieldValue.isZero()) {
+        return null;
+      }
+      if (fieldValue.isSingleFieldValue()) {
+        return fieldValue.asSingleFieldValue().getState();
+      }
+      assert fieldValue.isUnknown();
+      return ObjectState.empty();
+    }
+
+    public AbstractValue getValuesAbstractValueForPossiblyPinnedField(DexField field) {
+      assert valuesField == field || valuesAbstractValue == null;
+      return valuesAbstractValue;
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 0f0d6e9..85d90e6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -328,6 +328,9 @@
   }
 
   public boolean onlyUsedInBlock(BasicBlock block) {
+    if (hasPhiUsers() || hasDebugUsers()) {
+      return false;
+    }
     for (Instruction user : uniqueUsers()) {
       if (user.getBlock() != block) {
         return false;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index dd9bee9..11a9d00 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -20,7 +20,6 @@
 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.graph.DexTypeList;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -101,7 +100,6 @@
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -838,9 +836,8 @@
               outliner.identifyOutlineSites(code);
             },
             executorService);
-        DexProgramClass outlineClass = outliner.buildOutlinerClass(computeOutlineClassType());
-        appView.appInfo().addSynthesizedClass(outlineClass, true);
-        optimizeSynthesizedClass(outlineClass, executorService);
+        List<ProgramMethod> outlineMethods = outliner.buildOutlineMethods();
+        optimizeSynthesizedMethods(outlineMethods, executorService);
         forEachSelectedOutliningMethod(
             methodsSelectedForOutlining,
             code -> {
@@ -852,8 +849,7 @@
             executorService);
         feedback.updateVisibleOptimizationInfo();
         assert outliner.checkAllOutlineSitesFoundAgain();
-        builder.addSynthesizedClass(outlineClass);
-        clearDexMethodCompilationState(outlineClass);
+        outlineMethods.forEach(m -> m.getDefinition().markNotProcessed());
       }
       timing.end();
     }
@@ -1037,27 +1033,11 @@
     }
   }
 
-  // Find an unused name for the outlining class. When multiple runs produces additional
-  // outlining the default outlining class might already be present.
-  private DexType computeOutlineClassType() {
-    DexType result;
-    int count = 0;
-    String prefix = appView.options().synthesizedClassPrefix.replace('/', '.');
-    do {
-      String name =
-          prefix + OutlineOptions.CLASS_NAME + (count == 0 ? "" : Integer.toString(count));
-      count++;
-      result = appView.dexItemFactory().createType(DescriptorUtils.javaTypeToDescriptor(name));
-
-    } while (appView.appInfo().definitionForWithoutExistenceAssert(result) != null);
-    return result;
-  }
-
-  public void optimizeSynthesizedClass(
-      DexProgramClass clazz, ExecutorService executorService)
+  public void optimizeSynthesizedMethods(
+      List<ProgramMethod> programMethods, ExecutorService executorService)
       throws ExecutionException {
     // Process the generated class, but don't apply any outlining.
-    SortedProgramMethodSet methods = SortedProgramMethodSet.create(clazz::forEachProgramMethod);
+    SortedProgramMethodSet methods = SortedProgramMethodSet.create(programMethods::forEach);
     processMethodsConcurrently(methods, executorService);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 994e2ba..09fd687 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -165,11 +165,9 @@
   public void processSynthesizedClasses(IRConverter converter, ExecutorService executor)
       throws ExecutionException {
     while (!synthesizedMethods.isEmpty()) {
-      List<ProgramMethod> methods = new ArrayList<>(synthesizedMethods);
+      ArrayList<ProgramMethod> methods = new ArrayList<>(synthesizedMethods);
       synthesizedMethods.clear();
-      for (ProgramMethod method : methods) {
-        converter.optimizeSynthesizedClass(method.getHolder(), executor);
-      }
+      converter.optimizeSynthesizedMethods(methods, executor);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index 23342df..b176db1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -165,7 +165,7 @@
     finalFieldPuts.forEach(
         (field, put) -> {
           DexType fieldType = field.field.type;
-          Value value = put.value();
+          Value value = put.value().getAliasedValue();
           if (unnecessaryStaticPuts.contains(put)) {
             if (fieldType == dexItemFactory.stringType) {
               fieldsWithStaticValues.put(field, getDexStringValue(value, context.getHolderType()));
@@ -404,7 +404,7 @@
             }
             DexField fieldReference = put.getField();
             DexEncodedField field = context.getHolder().lookupField(fieldReference);
-            Value value = put.value();
+            Value value = put.value().getAliasedValue();
             TypeElement valueType = value.getType();
             if (field != null) {
               if (isReadBefore.contains(field)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 11b4ddf..dbad5bd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -10,26 +10,16 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.ClasspathMethod;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DebugLocalInfo;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
 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.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GraphLens;
 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.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
@@ -65,8 +55,8 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.ListUtils;
@@ -76,7 +66,6 @@
 import com.android.tools.r8.utils.collections.ProgramMethodMultiset;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
@@ -102,9 +91,9 @@
  *       {@link OutlineOptions#threshold}). Each selected method is then converted back to IR and
  *       passed to {@link Outliner#identifyOutlineSites(IRCode)}, which then stores concrete
  *       outlining candidates in {@link Outliner#outlineSites}.
- *   <li>Third, {@link Outliner#buildOutlinerClass(DexType)} is called to construct the <em>outline
- *       support class</em> containing a static helper method for each outline candidate that occurs
- *       frequently enough. Each selected method is then converted to IR, passed to {@link
+ *   <li>Third, {@link Outliner#buildOutlineMethods()} is called to construct the <em>outline
+ *       support classes</em> containing a static helper method for each outline candidate that
+ *       occurs frequently enough. Each selected method is then converted to IR, passed to {@link
  *       Outliner#applyOutliningCandidate(IRCode)} to perform the outlining, and converted back to
  *       the output format (DEX or CF).
  * </ul>
@@ -116,7 +105,7 @@
       new ArrayList<>();
   /** Result of second step (see {@link Outliner#selectMethodsForOutlining()}. */
   private final Map<Outline, List<ProgramMethod>> outlineSites = new HashMap<>();
-  /** Result of third step (see {@link Outliner#buildOutlinerClass(DexType)}. */
+  /** Result of third step (see {@link Outliner#buildOutlineMethods()}. */
   private final Map<Outline, DexMethod> generatedOutlines = new HashMap<>();
 
   static final int MAX_IN_SIZE = 5;  // Avoid using ranged calls for outlined code.
@@ -581,11 +570,6 @@
       return proto;
     }
 
-    // Build the DexMethod for this outline.
-    DexMethod buildMethod(DexType clazz, DexString name) {
-      return appView.dexItemFactory().createMethod(clazz, buildProto(), name);
-    }
-
     @Override
     public boolean equals(Object other) {
       if (!(other instanceof Outline)) {
@@ -1355,67 +1339,40 @@
     return methodsSelectedForOutlining;
   }
 
-  public DexProgramClass buildOutlinerClass(DexType type) {
-    // Build the outlined methods.
-    // By now the candidates are the actual selected outlines. Name the generated methods in a
-    // consistent order, to provide deterministic output.
+  public List<ProgramMethod> buildOutlineMethods() {
+    List<ProgramMethod> outlineMethods = new ArrayList<>();
+    // By now the candidates are the actual selected outlines. Iterate the outlines in a
+    // consistent order, to provide deterministic naming of the internal-synthetics.
+    // The choice of 'representative' will ensure deterministic naming of the external names.
     List<Outline> outlines = selectOutlines();
     outlines.sort(Comparator.naturalOrder());
-    DexEncodedMethod[] direct = new DexEncodedMethod[outlines.size()];
-    int count = 0;
     for (Outline outline : outlines) {
-      MethodAccessFlags methodAccess =
-          MethodAccessFlags.fromSharedAccessFlags(
-              Constants.ACC_PUBLIC | Constants.ACC_STATIC, false);
-      DexString methodName =
-          appView.dexItemFactory().createString(OutlineOptions.METHOD_PREFIX + count);
-      DexMethod method = outline.buildMethod(type, methodName);
       List<ProgramMethod> sites = outlineSites.get(outline);
       assert !sites.isEmpty();
-      direct[count] =
-          new DexEncodedMethod(
-              method,
-              methodAccess,
-              MethodTypeSignature.noSignature(),
-              DexAnnotationSet.empty(),
-              ParameterAnnotationsList.empty(),
-              new OutlineCode(outline),
-              true);
-      if (appView.options().isGeneratingClassFiles()) {
-        direct[count].upgradeClassFileVersion(sites.get(0).getDefinition().getClassFileVersion());
-      }
-      generatedOutlines.put(outline, method);
-      count++;
+      ProgramMethod representative = findDeterministicRepresentative(sites);
+      ProgramMethod outlineMethod =
+          appView
+              .getSyntheticItems()
+              .createMethod(
+                  SyntheticKind.OUTLINE,
+                  representative,
+                  appView.dexItemFactory(),
+                  builder -> {
+                    builder
+                        .setAccessFlags(
+                            MethodAccessFlags.fromSharedAccessFlags(
+                                Constants.ACC_PUBLIC | Constants.ACC_STATIC, false))
+                        .setProto(outline.buildProto())
+                        .setCode(m -> new OutlineCode(outline));
+                    if (appView.options().isGeneratingClassFiles()) {
+                      builder.setClassFileVersion(
+                          representative.getDefinition().getClassFileVersion());
+                    }
+                  });
+      generatedOutlines.put(outline, outlineMethod.getReference());
+      outlineMethods.add(outlineMethod);
     }
-    // No need to sort the direct methods as they are generated in sorted order.
-
-    // Build the outliner class.
-    DexTypeList interfaces = DexTypeList.empty();
-    DexString sourceFile = appView.dexItemFactory().createString("outline");
-    ClassAccessFlags accessFlags = ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC);
-    assert !appView.options().encodeChecksums;
-    // The outliner is R8 only and checksum is not a supported part of R8 compilation.
-    ChecksumSupplier checksumSupplier = DexProgramClass::invalidChecksumRequest;
-    return new DexProgramClass(
-        type,
-        null,
-        new SynthesizedOrigin("outlining", getClass()),
-        accessFlags,
-        appView.dexItemFactory().objectType,
-        interfaces,
-        sourceFile,
-        null,
-        Collections.emptyList(),
-        null,
-        Collections.emptyList(),
-        ClassSignature.noSignature(),
-        DexAnnotationSet.empty(),
-        DexEncodedField.EMPTY_ARRAY, // Static fields.
-        DexEncodedField.EMPTY_ARRAY, // Instance fields.
-        direct,
-        DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
-        appView.dexItemFactory().getSkipNameValidationForTesting(),
-        checksumSupplier);
+    return outlineMethods;
   }
 
   private List<Outline> selectOutlines() {
@@ -1430,6 +1387,18 @@
     return result;
   }
 
+  private static ProgramMethod findDeterministicRepresentative(List<ProgramMethod> members) {
+    // Pick a deterministic member as representative.
+    ProgramMethod smallest = members.get(0);
+    for (int i = 1; i < members.size(); i++) {
+      ProgramMethod next = members.get(i);
+      if (next.getReference().compareTo(smallest.getReference()) < 0) {
+        smallest = next;
+      }
+    }
+    return smallest;
+  }
+
   public void applyOutliningCandidate(IRCode code) {
     assert !code.method().getCode().isOutlineCode();
     ListIterator<BasicBlock> blocksIterator = code.listIterator();
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 cf0ecb5..166022e 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
@@ -491,36 +491,34 @@
     // Step 1: We iterate over the field to find direct enum instance information and the values
     // fields.
     for (DexEncodedField staticField : enumClass.staticFields()) {
-      if (factory.enumMembers.isEnumField(staticField, enumClass.type)) {
+      if (EnumUnboxingCandidateAnalysis.isEnumField(staticField, enumClass.type)) {
         ObjectState enumState =
             enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.field);
-        if (enumState == null) {
-          if (!isFinalFieldInitialized(staticField, enumClass)) {
-            continue;
+        if (enumState != null) {
+          OptionalInt optionalOrdinal = getOrdinal(enumState);
+          if (!optionalOrdinal.isPresent()) {
+            return null;
           }
-          // Tracking the content of the field yielded either an empty object state, or something
-          // incoherent. We bail out.
+          int ordinal = optionalOrdinal.getAsInt();
+          unboxedValues.put(staticField.field, ordinalToUnboxedInt(ordinal));
+          ordinalToObjectState.put(ordinal, enumState);
+        }
+      } else if (EnumUnboxingCandidateAnalysis.matchesValuesField(
+          staticField, enumClass.type, factory)) {
+        AbstractValue valuesValue =
+            enumStaticFieldValues.getValuesAbstractValueForPossiblyPinnedField(staticField.field);
+        if (valuesValue == null || valuesValue.isZero()) {
+          // Unused field
+          continue;
+        }
+        if (valuesValue.isUnknown()) {
           return null;
         }
-        OptionalInt optionalOrdinal = getOrdinal(enumState);
-        if (!optionalOrdinal.isPresent()) {
+        assert valuesValue.isSingleFieldValue();
+        ObjectState valuesState = valuesValue.asSingleFieldValue().getState();
+        if (!valuesState.isEnumValuesObjectState()) {
           return null;
         }
-        int ordinal = optionalOrdinal.getAsInt();
-        unboxedValues.put(staticField.field, ordinalToUnboxedInt(ordinal));
-        ordinalToObjectState.put(ordinal, enumState);
-      } else if (factory.enumMembers.isValuesFieldCandidate(staticField, enumClass.type)) {
-        ObjectState valuesState =
-            enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.field);
-        if (valuesState == null) {
-          if (!isFinalFieldInitialized(staticField, enumClass)) {
-            continue;
-          }
-          // We could not track the content of that field, and the field could be a values field.
-          // We conservatively bail out.
-          return null;
-        }
-        assert valuesState.isEnumValuesObjectState();
         assert valuesContents == null
             || valuesContents.equals(valuesState.asEnumValuesObjectState());
         valuesContents = valuesState.asEnumValuesObjectState();
@@ -566,13 +564,6 @@
         valuesContents == null ? EnumData.INVALID_VALUES_SIZE : valuesContents.getEnumValuesSize());
   }
 
-  private boolean isFinalFieldInitialized(DexEncodedField staticField, DexProgramClass enumClass) {
-    assert staticField.isFinal();
-    return appView
-        .appInfo()
-        .isFieldOnlyWrittenInMethodIgnoringPinning(staticField, enumClass.getClassInitializer());
-  }
-
   private EnumInstanceFieldData computeEnumFieldData(
       DexField instanceField,
       DexProgramClass enumClass,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 40b0c86..88845d9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -112,7 +112,7 @@
     // Browse annotation values types in search for enum.
     // Each annotation value is represented by a virtual method.
     for (DexEncodedMethod method : clazz.virtualMethods()) {
-      assert method.parameters().isEmpty()
+      assert method.getParameters().isEmpty()
           || appView.options().testing.allowInjectedAnnotationMethods;
       DexType valueType = method.returnType().toBaseType(appView.dexItemFactory());
       if (enumToUnboxCandidates.isCandidate(valueType)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 4c7581e..6c20c4f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -434,7 +434,7 @@
     return factory.createField(
         relocator.getNewMemberLocationFor(enumType),
         factory.intArrayType,
-        "$$values$$field$" + compatibleName(enumType));
+        factory.enumValuesFieldName + "$field$" + compatibleName(enumType));
   }
 
   private DexEncodedField computeValuesEncodedField(DexField field) {
@@ -451,7 +451,7 @@
     return factory.createMethod(
         relocator.getNewMemberLocationFor(enumType),
         factory.createProto(factory.intArrayType),
-        "$$values$$method$" + compatibleName(enumType));
+        factory.enumValuesFieldName + "$method$" + compatibleName(enumType));
   }
 
   private DexEncodedMethod computeValuesEncodedMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index 33b7242..ded60a3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -85,7 +85,7 @@
     }
     for (int i = 0; i < method.method.getArity(); i++) {
       staticTypes[i + argOffset] =
-          TypeElement.fromDexType(method.parameters().values[i], maybeNull(), appView);
+          TypeElement.fromDexType(method.getParameter(i), maybeNull(), appView);
     }
     return staticTypes;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index 7f5d173..93a330e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -52,6 +52,10 @@
 
   public abstract BitSet getNonNullParamOrThrow();
 
+  public final boolean hasNonNullParamOnNormalExits() {
+    return getNonNullParamOnNormalExits() != null;
+  }
+
   public abstract BitSet getNonNullParamOnNormalExits();
 
   public abstract boolean hasBeenInlinedIntoSingleCallSite();
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 9868a64..3aaba06 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
@@ -156,8 +156,8 @@
         definition, code, feedback, instanceFieldInitializationInfos, timing);
     computeMayHaveSideEffects(feedback, definition, code, timing);
     computeReturnValueOnlyDependsOnArguments(feedback, definition, code, timing);
-    computeNonNullParamOrThrow(feedback, definition, code, timing);
-    computeNonNullParamOnNormalExits(feedback, code, timing);
+    BitSet nonNullParamOrThrow = computeNonNullParamOrThrow(feedback, definition, code, timing);
+    computeNonNullParamOnNormalExits(feedback, code, nonNullParamOrThrow, timing);
   }
 
   private void identifyBridgeInfo(
@@ -1165,18 +1165,19 @@
     }
   }
 
-  private void computeNonNullParamOrThrow(
+  private BitSet computeNonNullParamOrThrow(
       OptimizationFeedback feedback, DexEncodedMethod method, IRCode code, Timing timing) {
     timing.begin("Compute non-null-param-or-throw");
-    computeNonNullParamOrThrow(feedback, method, code);
+    BitSet nonNullParamOrThrow = computeNonNullParamOrThrow(feedback, method, code);
     timing.end();
+    return nonNullParamOrThrow;
   }
 
   // Track usage of parameters and compute their nullability and possibility of NPE.
-  private void computeNonNullParamOrThrow(
+  private BitSet computeNonNullParamOrThrow(
       OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
     if (method.getOptimizationInfo().getNonNullParamOrThrow() != null) {
-      return;
+      return null;
     }
     List<Value> arguments = code.collectArguments();
     BitSet paramsCheckedForNull = new BitSet();
@@ -1196,66 +1197,80 @@
         paramsCheckedForNull.set(index);
       }
     }
-    if (paramsCheckedForNull.length() > 0) {
+    if (!paramsCheckedForNull.isEmpty()) {
       feedback.setNonNullParamOrThrow(method, paramsCheckedForNull);
+      return paramsCheckedForNull;
     }
+    return null;
   }
 
   private void computeNonNullParamOnNormalExits(
-      OptimizationFeedback feedback, IRCode code, Timing timing) {
+      OptimizationFeedback feedback, IRCode code, BitSet nonNullParamOrThrow, Timing timing) {
     timing.begin("Compute non-null-param-on-normal-exits");
-    computeNonNullParamOnNormalExits(feedback, code);
+    computeNonNullParamOnNormalExits(feedback, code, nonNullParamOrThrow);
     timing.end();
   }
 
-  private void computeNonNullParamOnNormalExits(OptimizationFeedback feedback, IRCode code) {
+  private void computeNonNullParamOnNormalExits(
+      OptimizationFeedback feedback, IRCode code, BitSet nonNullParamOrThrow) {
     Set<BasicBlock> normalExits = Sets.newIdentityHashSet();
     normalExits.addAll(code.computeNormalExitBlocks());
     DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
     List<Value> arguments = code.collectArguments();
     BitSet facts = new BitSet();
-    Set<BasicBlock> nullCheckedBlocks = Sets.newIdentityHashSet();
+    if (nonNullParamOrThrow != null) {
+      facts.or(nonNullParamOrThrow);
+    }
     for (int index = 0; index < arguments.size(); index++) {
+      if (facts.get(index)) {
+        continue;
+      }
       Value argument = arguments.get(index);
-      // Consider reference-type parameter only.
-      if (!argument.getType().isReferenceType()) {
-        continue;
-      }
-      // The receiver is always non-null on normal exits.
-      if (argument.isThis()) {
+      if (argument.getType().isReferenceType()
+          && isNonNullOnNormalExit(code, argument, dominatorTree, normalExits)) {
         facts.set(index);
-        continue;
-      }
-      // Collect basic blocks that check nullability of the parameter.
-      nullCheckedBlocks.clear();
-      for (Instruction user : argument.uniqueUsers()) {
-        if (user.isAssumeWithNonNullAssumption()) {
-          nullCheckedBlocks.add(user.asAssume().getBlock());
-        }
-        if (user.isIf()
-            && user.asIf().isZeroTest()
-            && (user.asIf().getType() == If.Type.EQ || user.asIf().getType() == If.Type.NE)) {
-          nullCheckedBlocks.add(user.asIf().targetFromNonNullObject());
-        }
-      }
-      if (!nullCheckedBlocks.isEmpty()) {
-        boolean allExitsCovered = true;
-        for (BasicBlock normalExit : normalExits) {
-          if (!isNormalExitDominated(normalExit, code, dominatorTree, nullCheckedBlocks)) {
-            allExitsCovered = false;
-            break;
-          }
-        }
-        if (allExitsCovered) {
-          facts.set(index);
-        }
       }
     }
-    if (facts.length() > 0) {
+    if (!facts.isEmpty()) {
       feedback.setNonNullParamOnNormalExits(code.method(), facts);
     }
   }
 
+  private boolean isNonNullOnNormalExit(
+      IRCode code, Value value, DominatorTree dominatorTree, Set<BasicBlock> normalExits) {
+    assert value.getType().isReferenceType();
+
+    // The receiver is always non-null on normal exits.
+    if (value.isThis()) {
+      return true;
+    }
+
+    // Collect basic blocks that check nullability of the parameter.
+    Set<BasicBlock> nullCheckedBlocks = Sets.newIdentityHashSet();
+    for (Instruction user : value.aliasedUsers()) {
+      if (user.isAssumeWithNonNullAssumption()
+          || user.throwsNpeIfValueIsNull(value, appView, code.context())) {
+        nullCheckedBlocks.add(user.getBlock());
+      }
+      if (user.isIf()
+          && user.asIf().isZeroTest()
+          && (user.asIf().getType() == If.Type.EQ || user.asIf().getType() == If.Type.NE)) {
+        nullCheckedBlocks.add(user.asIf().targetFromNonNullObject());
+      }
+    }
+
+    if (nullCheckedBlocks.isEmpty()) {
+      return false;
+    }
+
+    for (BasicBlock normalExit : normalExits) {
+      if (!isNormalExitDominated(normalExit, code, dominatorTree, nullCheckedBlocks)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   private boolean isNormalExitDominated(
       BasicBlock normalExit,
       IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
index ae91ac8..3703148 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
@@ -4,11 +4,13 @@
 
 package com.android.tools.r8.ir.optimize.library;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.LibraryMethod;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.library.sideeffects.JavaLangObjectsSideEffectCollection;
 import com.android.tools.r8.utils.BiPredicateUtils;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -25,23 +27,31 @@
 
   private final Set<DexMethod> nonFinalMethodsWithoutSideEffects;
 
-  public LibraryMethodSideEffectModelCollection(DexItemFactory dexItemFactory) {
-    finalMethodsWithoutSideEffects = buildFinalMethodsWithoutSideEffects(dexItemFactory);
+  public LibraryMethodSideEffectModelCollection(AppView<?> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    finalMethodsWithoutSideEffects = buildFinalMethodsWithoutSideEffects(appView, dexItemFactory);
     unconditionalFinalMethodsWithoutSideEffects =
         buildUnconditionalFinalMethodsWithoutSideEffects(dexItemFactory);
     nonFinalMethodsWithoutSideEffects = buildNonFinalMethodsWithoutSideEffects(dexItemFactory);
   }
 
   private static Map<DexMethod, BiPredicate<DexMethod, List<Value>>>
-      buildFinalMethodsWithoutSideEffects(DexItemFactory dexItemFactory) {
+      buildFinalMethodsWithoutSideEffects(AppView<?> appView, DexItemFactory dexItemFactory) {
     ImmutableMap.Builder<DexMethod, BiPredicate<DexMethod, List<Value>>> builder =
         ImmutableMap.<DexMethod, BiPredicate<DexMethod, List<Value>>>builder()
             .put(
+                dexItemFactory.objectsMethods.toStringWithObject,
+                (method, arguments) ->
+                    !JavaLangObjectsSideEffectCollection.toStringMayHaveSideEffects(
+                        appView, arguments))
+            .put(
                 dexItemFactory.stringMembers.constructor,
                 (method, arguments) -> arguments.get(1).isNeverNull())
             .put(
                 dexItemFactory.stringMembers.valueOf,
-                (method, arguments) -> arguments.get(0).isNeverNull());
+                (method, arguments) ->
+                    !JavaLangObjectsSideEffectCollection.toStringMayHaveSideEffects(
+                        appView, arguments));
     putAll(
         builder,
         dexItemFactory.stringBufferMethods.constructorMethods,
@@ -71,8 +81,11 @@
         .add(dexItemFactory.shortMembers.toString)
         .add(dexItemFactory.stringBufferMethods.toString)
         .add(dexItemFactory.stringBuilderMethods.toString)
+        .add(dexItemFactory.stringMembers.length)
         .add(dexItemFactory.stringMembers.hashCode)
+        .add(dexItemFactory.stringMembers.isEmpty)
         .add(dexItemFactory.stringMembers.toString)
+        .add(dexItemFactory.stringMembers.trim)
         .addAll(dexItemFactory.classMethods.getNames)
         .addAll(dexItemFactory.boxedValueOfMethods())
         .build();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index ecbb3e4..1141303 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -45,6 +45,7 @@
   void run(Set<DexEncodedField> finalLibraryFields) {
     modelInstanceInitializers();
     modelStaticFinalLibraryFields(finalLibraryFields);
+    modelLibraryMethodsNonNullParamOrThrow();
     modelLibraryMethodsReturningNonNull();
     modelLibraryMethodsReturningReceiver();
     modelLibraryMethodsWithoutSideEffects();
@@ -87,6 +88,30 @@
     }
   }
 
+  private void modelLibraryMethodsNonNullParamOrThrow() {
+    dexItemFactory.libraryMethodsNonNullParamOrThrow.forEach(
+        (method, nonNullParamOrThrow) -> {
+          DexEncodedMethod definition = lookupMethod(method);
+          if (definition != null) {
+            assert nonNullParamOrThrow.length > 0;
+            int size = nonNullParamOrThrow[nonNullParamOrThrow.length - 1] + 1;
+            BitSet bitSet = new BitSet(size);
+            for (int argumentIndex : nonNullParamOrThrow) {
+              assert argumentIndex < size;
+              bitSet.set(argumentIndex);
+            }
+            feedback.setNonNullParamOrThrow(definition, bitSet);
+
+            // Also set non-null-param-on-normal-exits info.
+            if (definition.getOptimizationInfo().hasNonNullParamOnNormalExits()) {
+              definition.getOptimizationInfo().getNonNullParamOnNormalExits().or(bitSet);
+            } else {
+              feedback.setNonNullParamOnNormalExits(definition, (BitSet) bitSet.clone());
+            }
+          }
+        });
+  }
+
   private void modelLibraryMethodsReturningNonNull() {
     for (DexMethod method : dexItemFactory.libraryMethodsReturningNonNull) {
       DexEncodedMethod definition = lookupMethod(method);
@@ -131,14 +156,6 @@
       DexEncodedMethod definition = lookupMethod(requireNonNullMethod);
       if (definition != null) {
         feedback.methodReturnsArgument(definition, 0);
-
-        BitSet nonNullParamOrThrow = new BitSet();
-        nonNullParamOrThrow.set(0);
-        feedback.setNonNullParamOrThrow(definition, nonNullParamOrThrow);
-
-        BitSet nonNullParamOnNormalExits = new BitSet();
-        nonNullParamOnNormalExits.set(0);
-        feedback.setNonNullParamOnNormalExits(definition, nonNullParamOnNormalExits);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
index a303082..831236b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
@@ -5,19 +5,15 @@
 package com.android.tools.r8.ir.optimize.library;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexItemFactory.ObjectsMethods;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Set;
 
 public class ObjectsMethodOptimizer extends StatelessLibraryMethodModelCollection {
@@ -90,44 +86,6 @@
         invoke.outValue().replaceUsers(object);
       }
       instructionIterator.removeOrReplaceByDebugLocalRead();
-      return;
-    }
-
-    // Remove Objects.toString() if it is unused and does not have side effects.
-    if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) {
-      // Calling toString() on an array does not call toString() on the array elements.
-      if (type.isArrayType()) {
-        instructionIterator.removeOrReplaceByDebugLocalRead();
-        return;
-      }
-
-      assert type.isClassType();
-
-      // Check if this is a library class with a toString() method that does not have side effects.
-      DexType classType = type.asClassType().getClassType();
-      DexMethod toStringMethodReference =
-          dexItemFactory.objectMembers.toString.withHolder(classType, dexItemFactory);
-      if (appView
-          .getLibraryMethodSideEffectModelCollection()
-          .isSideEffectFreeFinalMethod(toStringMethodReference, invoke.arguments())) {
-        instructionIterator.removeOrReplaceByDebugLocalRead();
-        return;
-      }
-
-      // Check if this is a program class with a toString() method that does not have side effects.
-      AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
-      if (appInfo != null) {
-        DexClass clazz = appInfo.definitionFor(classType, code.context());
-        if (clazz != null && clazz.isEffectivelyFinal(appView)) {
-          SingleResolutionResult resolutionResult =
-              appInfo.resolveMethodOn(clazz, toStringMethodReference).asSingleResolution();
-          if (resolutionResult != null
-              && !resolutionResult.getResolvedMethod().getOptimizationInfo().mayHaveSideEffects()) {
-            instructionIterator.removeOrReplaceByDebugLocalRead();
-            return;
-          }
-        }
-      }
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index a8fabe3..8c62340 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -19,7 +20,6 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.ValueUtils;
 import java.util.Set;
 
 public class StringMethodOptimizer extends StatelessLibraryMethodModelCollection {
@@ -48,7 +48,7 @@
     if (singleTargetReference == dexItemFactory.stringMembers.equals) {
       optimizeEquals(code, instructionIterator, invoke.asInvokeVirtual());
     } else if (singleTargetReference == dexItemFactory.stringMembers.valueOf) {
-      optimizeValueOf(instructionIterator, invoke.asInvokeStatic());
+      optimizeValueOf(code, instructionIterator, invoke.asInvokeStatic(), affectedValues);
     }
   }
 
@@ -65,12 +65,30 @@
     }
   }
 
-  private void optimizeValueOf(InstructionListIterator instructionIterator, InvokeStatic invoke) {
-    // Optimize String.valueOf(stringBuilder) if unused.
-    if (ValueUtils.isNonNullStringBuilder(invoke.getFirstArgument(), dexItemFactory)) {
-      if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) {
-        instructionIterator.removeOrReplaceByDebugLocalRead();
+  private void optimizeValueOf(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeStatic invoke,
+      Set<Value> affectedValues) {
+    Value object = invoke.getFirstArgument();
+    TypeElement type = object.getType();
+
+    // Optimize String.valueOf(null) into "null".
+    if (type.isDefinitelyNull()) {
+      instructionIterator.replaceCurrentInstructionWithConstString(appView, code, "null");
+      if (invoke.hasOutValue()) {
+        affectedValues.addAll(invoke.outValue().affectedValues());
       }
+      return;
+    }
+
+    // Optimize String.valueOf(nonNullString) into nonNullString.
+    if (type.isDefinitelyNotNull() && type.isStringType(dexItemFactory)) {
+      if (invoke.hasOutValue()) {
+        affectedValues.addAll(invoke.outValue().affectedValues());
+        invoke.outValue().replaceUsers(object);
+      }
+      instructionIterator.removeOrReplaceByDebugLocalRead();
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
new file mode 100644
index 0000000..5f74465
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.library.sideeffects;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.List;
+
+public class JavaLangObjectsSideEffectCollection {
+
+  public static boolean toStringMayHaveSideEffects(AppView<?> appView, List<Value> arguments) {
+    // Calling toString() on an array does not call toString() on the array elements.
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    TypeElement type = arguments.get(0).getType();
+    if (type.isArrayType() || type.isNullType()) {
+      return false;
+    }
+
+    assert type.isClassType();
+
+    // Check if this is a library class with a toString() method that does not have side effects.
+    DexType classType = type.asClassType().getClassType();
+    DexMethod toStringMethodReference =
+        dexItemFactory.objectMembers.toString.withHolder(classType, dexItemFactory);
+    if (appView
+        .getLibraryMethodSideEffectModelCollection()
+        .isSideEffectFreeFinalMethod(toStringMethodReference, arguments)) {
+      return false;
+    }
+
+    if (appView.appInfo().hasLiveness()) {
+      AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
+
+      // Check if there is an -assumenosideeffects rule for the toString() method.
+      if (appInfo.isAssumeNoSideEffectsMethod(toStringMethodReference)) {
+        return false;
+      }
+
+      // Check if this is a program class with a toString() method that does not have side effects.
+      DexClass clazz = appInfo.definitionFor(classType);
+      if (clazz != null && clazz.isEffectivelyFinal(appView)) {
+        SingleResolutionResult resolutionResult =
+            appInfo.resolveMethodOn(clazz, toStringMethodReference).asSingleResolution();
+        if (resolutionResult != null
+            && !resolutionResult.getResolvedMethod().getOptimizationInfo().mayHaveSideEffects()) {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index af2dee2..3c6c003 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -299,10 +299,6 @@
 
   // Find Class#get*Name() with a constant-class and replace it with a const-string if possible.
   public void rewriteClassGetName(AppView<?> appView, IRCode code) {
-    // Conflict with {@link CodeRewriter#collectClassInitializerDefaults}.
-    if (code.method().isClassInitializer()) {
-      return;
-    }
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     InstructionListIterator it = code.instructionListIterator();
     while (it.hasNext()) {
@@ -363,11 +359,16 @@
       // while its name is used to compute hash code, which won't be optimized, it's better not to
       // compute the name.
       if (!appView.options().testing.forceNameReflectionOptimization) {
-        EscapeAnalysis escapeAnalysis =
-            new EscapeAnalysis(appView, StringOptimizerEscapeAnalysisConfiguration.getInstance());
-        if (mayBeRenamed || escapeAnalysis.isEscaping(code, out)) {
+        if (mayBeRenamed) {
           continue;
         }
+        if (invokedMethod != factory.classMethods.getSimpleName) {
+          EscapeAnalysis escapeAnalysis =
+              new EscapeAnalysis(appView, StringOptimizerEscapeAnalysisConfiguration.getInstance());
+          if (escapeAnalysis.isEscaping(code, out)) {
+            continue;
+          }
+        }
       }
 
       String descriptor = baseType.toDescriptorString();
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index f7ac800..bc50542 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -109,7 +109,6 @@
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -1724,8 +1723,6 @@
             || !options.testing.checkForNotExpandingMainDexTracingResult
             || previousMainDexTracingResult.isRoot(clazz)
             || clazz.toSourceString().contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)
-            // TODO(b/177847090): Consider not outlining anything in main dex.
-            || clazz.toSourceString().contains(OutlineOptions.CLASS_NAME)
         : "Class " + clazz.toSourceString() + " was not a main dex root in the first round";
 
     // Mark types in inner-class attributes referenced.
@@ -3042,6 +3039,10 @@
       ImmutableSet<ProguardKeepRuleBase> keepAllSet =
           ImmutableSet.of(appView.options().getProguardConfiguration().getKeepAllRule());
       for (DexProgramClass clazz : appView.appInfo().classes()) {
+        if (appView.getSyntheticItems().isNonLegacySynthetic(clazz)) {
+          // Don't treat compiler synthesized classes as kept roots.
+          continue;
+        }
         enqueueRootClass(clazz, keepAllSet);
         clazz.forEachProgramMethod(method -> enqueueRootMethod(method, keepAllSet));
         clazz.forEachProgramField(field -> enqueueRootField(field, keepAllSet));
@@ -4050,7 +4051,7 @@
   }
 
   private void markParameterAndReturnTypesAsLive(ProgramMethod method) {
-    for (DexType parameterType : method.getDefinition().parameters().values) {
+    for (DexType parameterType : method.getDefinition().getParameters()) {
       markTypeAsLive(parameterType, method);
     }
     markTypeAsLive(method.getDefinition().returnType(), method);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index a802761..bf9106a 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -134,6 +134,7 @@
       assert !syntheticMethodsMap.containsKey(from);
       syntheticMethodsMap.put(from, to);
       methodMap.put(from, to);
+      typeMap.put(from.getHolderType(), to.getHolderType());
     }
 
     void move(DexType from, DexType to) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 657db43..d526570 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -201,6 +201,10 @@
     return pending.legacyClasses.containsKey(type);
   }
 
+  public boolean isNonLegacySynthetic(DexProgramClass clazz) {
+    return isCommittedSynthetic(clazz.type) || isPendingSynthetic(clazz.type);
+  }
+
   public boolean isSyntheticClass(DexType type) {
     return isCommittedSynthetic(type)
         || isPendingSynthetic(type)
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index b33e260..fc5b278 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -32,7 +32,8 @@
     THROW_ICCE("ThrowICCE", true),
     THROW_NSME("ThrowNSME", true),
     TWR_CLOSE_RESOURCE("TwrCloseResource", true),
-    SERVICE_LOADER("ServiceLoad", true);
+    SERVICE_LOADER("ServiceLoad", true),
+    OUTLINE("Outline", true);
 
     public final String descriptor;
     public final boolean isSingleSyntheticMethod;
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
index 895f31e..286c83d 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -467,7 +467,7 @@
       if (superTarget != null) {
         addMethod(superTarget.getReference());
       }
-      for (DexType type : method.getDefinition().parameters().values) {
+      for (DexType type : method.getDefinition().getParameters()) {
         registerTypeReference(type);
       }
       for (DexAnnotation annotation : method.getDefinition().annotations().annotations) {
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 a8bec4a..a9f55ee 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1067,10 +1067,6 @@
   }
 
   public static class OutlineOptions {
-
-    public static final String CLASS_NAME = "com.android.tools.r8.GeneratedOutlineSupport";
-    public static final String METHOD_PREFIX = "outline";
-
     public boolean enabled = true;
     public int minSize = 3;
     public int maxSize = 99;
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index bbd5836..028ed02 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -38,6 +38,11 @@
   }
 
   @Override
+  public boolean isR8TestRunResult() {
+    return true;
+  }
+
+  @Override
   protected R8TestRunResult self() {
     return this;
   }
diff --git a/src/test/java/com/android/tools/r8/SingleTestRunResult.java b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
index 875e50d..d532547 100644
--- a/src/test/java/com/android/tools/r8/SingleTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
@@ -34,6 +34,10 @@
     this.result = result;
   }
 
+  public boolean isR8TestRunResult() {
+    return false;
+  }
+
   public AndroidApp app() {
     return app;
   }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
index b21cbd0..89395ef 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
@@ -97,6 +97,13 @@
     return testForR8(Backend.CF)
         .addProgramClasses(ToStringLib.class, ToStringLib.LibEnum.class)
         .addKeepRules(enumKeepRules.getKeepRules())
+        // TODO(b/160535629): Work-around on some optimizations relying on $VALUES name.
+        .addKeepRules(
+            "-keep enum "
+                + ToStringLib.LibEnum.class.getName()
+                + " { static "
+                + ToStringLib.LibEnum.class.getName()
+                + "[] $VALUES; }")
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .addKeepMethodRules(
             Reference.methodFromMethod(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/NoOutliningForClassFileBuildsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/NoOutliningForClassFileBuildsTest.java
index 4af2593..2a837bc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/NoOutliningForClassFileBuildsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/NoOutliningForClassFileBuildsTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -72,8 +72,9 @@
     assertThat(classSubject.uniqueMethodWithName("foo"), isPresent());
     assertThat(classSubject.uniqueMethodWithName("bar"), isPresent());
     boolean hasOutlineClass =
-        inspector.allClasses().stream()
-            .anyMatch(c -> c.getFinalName().equals(OutlineOptions.CLASS_NAME));
+        inspector
+            .clazz(SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0))
+            .isPresent();
     assertEquals(forceOutline || parameters.isDexRuntime(), hasOutlineClass);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java
index 59602da..6b7bc4a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -93,9 +93,10 @@
   }
 
   private void validateOutlining(CodeInspector inspector, Class<?> main) {
-    ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME);
-    assertThat(outlineClass, isPresent());
-    MethodSubject outlineMethod = outlineClass.uniqueMethodWithName("outline0");
+    ClassSubject outlineClass =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(main, 0));
+    MethodSubject outlineMethod =
+        outlineClass.uniqueMethodWithName(SyntheticItemsTestUtils.syntheticMethodName());
     assertThat(outlineMethod, isPresent());
 
     ClassSubject argClass = inspector.clazz(TestArg.class);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithClassArrayTypeArguments.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithClassArrayTypeArguments.java
index 099afa8..440b036 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithClassArrayTypeArguments.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithClassArrayTypeArguments.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -37,11 +37,12 @@
   }
 
   private void validateOutlining(CodeInspector inspector) {
-    ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME);
+    ClassSubject outlineClass =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0));
     MethodSubject outline0Method =
         outlineClass.method(
             "void",
-            "outline0",
+            SyntheticItemsTestUtils.syntheticMethodName(),
             ImmutableList.of(
                 TestClass.class.getTypeName() + "[]", TestClass.class.getTypeName() + "[]"));
     assertThat(outline0Method, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithInterfaceArrayTypeArguments.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithInterfaceArrayTypeArguments.java
index fb4417f9..062516c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithInterfaceArrayTypeArguments.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithInterfaceArrayTypeArguments.java
@@ -11,8 +11,8 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -44,12 +44,15 @@
   private void validateOutlining(CodeInspector inspector) {
     // No outlining when arrays of interfaces are involved, see b/132420510 - unless the testing
     // option is set.
-    ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME);
+    ClassSubject outlineClass =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0));
     if (allowOutlinerInterfaceArrayArguments && parameters.isCfRuntime()) {
       assertThat(outlineClass, isPresent());
       MethodSubject outline0Method =
           outlineClass.method(
-              "void", "outline0", ImmutableList.of("java.lang.Object[]", "java.lang.Object[]"));
+              "void",
+              SyntheticItemsTestUtils.syntheticMethodName(),
+              ImmutableList.of("java.lang.Object[]", "java.lang.Object[]"));
       assertThat(outline0Method, isPresent());
       ClassSubject classSubject = inspector.clazz(TestClass.class);
       assertThat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithPrimitiveArrayTypeArguments.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithPrimitiveArrayTypeArguments.java
index 7a69926..36d9ad5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithPrimitiveArrayTypeArguments.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithPrimitiveArrayTypeArguments.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -37,9 +37,13 @@
   }
 
   private void validateOutlining(CodeInspector inspector) {
-    ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME);
+    ClassSubject outlineClass =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0));
     MethodSubject outline0Method =
-        outlineClass.method("void", "outline0", ImmutableList.of("int[]", "int[]"));
+        outlineClass.method(
+            "void",
+            SyntheticItemsTestUtils.syntheticMethodName(),
+            ImmutableList.of("int[]", "int[]"));
     assertThat(outline0Method, isPresent());
     ClassSubject classSubject = inspector.clazz(TestClass.class);
     assertThat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
index 9fcc6b7..b4dd6b7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -98,11 +99,14 @@
         .inspector();
 
     for (FoundClassSubject clazz : inspector.allClasses()) {
-      clazz.forAllMethods(method -> {
-        if (method.hasCode() && !method.getFinalName().startsWith("outline")) {
-          verifyAbsenceOfStringBuilderAppend(method.streamInstructions());
-        }
-      });
+      if (!SyntheticItemsTestUtils.isExternalOutlineClass(clazz.getFinalReference())) {
+        clazz.forAllMethods(
+            method -> {
+              if (method.hasCode()) {
+                verifyAbsenceOfStringBuilderAppend(method.streamInstructions());
+              }
+            });
+      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java
index 66c42e8..031fd9d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ir.optimize.outliner.b133215941.B133215941.TestClass.ClassWithStaticMethod;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -41,11 +41,12 @@
   }
 
   private void validateOutlining(CodeInspector inspector) {
-    ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME);
+    ClassSubject outlineClass =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0));
     MethodSubject outline0Method =
         outlineClass.method(
             "void",
-            "outline0",
+            SyntheticItemsTestUtils.syntheticMethodName(),
             ImmutableList.of(TestClass.class.getTypeName(), TestClass.class.getTypeName()));
     assertThat(outline0Method, isPresent());
     ClassSubject classSubject = inspector.clazz(TestClass.class);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
index 438aa81..dd6dace 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.outliner.b149971007;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertFalse;
@@ -15,7 +16,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.dexsplitter.SplitterTestBase;
 import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
@@ -24,6 +25,8 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -64,10 +67,17 @@
   }
 
   private void checkOutlineFromFeature(CodeInspector inspector) {
-    ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
-    assertThat(clazz, isPresent());
-    assertEquals(2, clazz.allMethods().size());
-    assertTrue(clazz.allMethods().stream().anyMatch(this::referenceFeatureClass));
+    // There are two expected outlines, each is currently in its own class.
+    List<FoundMethodSubject> allMethods = new ArrayList<>();
+    for (int i = 0; i < 2; i++) {
+      ClassSubject clazz =
+          inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, i));
+      assertThat(clazz, isPresent());
+      assertEquals(1, clazz.allMethods().size());
+      allMethods.addAll(clazz.allMethods());
+    }
+    // One of the methods is StringBuilder the other references the feature.
+    assertTrue(allMethods.stream().anyMatch(this::referenceFeatureClass));
   }
 
   @Test
@@ -85,24 +95,44 @@
     // Check that parts of method1, ..., method4 in FeatureClass was outlined.
     ClassSubject featureClass = compileResult.inspector().clazz(FeatureClass.class);
     assertThat(featureClass, isPresent());
-    String outlineClassName =
+
+    // Find the final names of the two outline classes.
+    String outlineClassName0 =
         ClassNameMapper.mapperFromString(compileResult.getProguardMap())
             .getObfuscatedToOriginalMapping()
             .inverse
-            .get(OutlineOptions.CLASS_NAME);
-    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method1"), outlineClassName));
-    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method2"), outlineClassName));
-    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method3"), outlineClassName));
-    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method4"), outlineClassName));
+            .get(
+                SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, 0).getTypeName());
+    String outlineClassName1 =
+        ClassNameMapper.mapperFromString(compileResult.getProguardMap())
+            .getObfuscatedToOriginalMapping()
+            .inverse
+            .get(
+                SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, 1).getTypeName());
+
+    // Verify they are called from the feature methods.
+    // Note: should the choice of synthetic grouping change these expectations will too.
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method1"), outlineClassName0));
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method2"), outlineClassName0));
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method3"), outlineClassName1));
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method4"), outlineClassName1));
 
     compileResult.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput("123456");
   }
 
   private void checkNoOutlineFromFeature(CodeInspector inspector) {
-    ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
-    assertThat(clazz, isPresent());
-    assertEquals(1, clazz.allMethods().size());
-    assertTrue(clazz.allMethods().stream().noneMatch(this::referenceFeatureClass));
+    // The features do not give rise to an outline.
+    ClassSubject featureOutlineClass =
+        inspector.clazz(
+            SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, 0).getTypeName());
+    assertThat(featureOutlineClass, isAbsent());
+    // The main TestClass entry does.
+    ClassSubject mainOutlineClazz =
+        inspector.clazz(
+            SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0).getTypeName());
+    assertThat(mainOutlineClazz, isPresent());
+    assertEquals(1, mainOutlineClazz.allMethods().size());
+    assertTrue(mainOutlineClazz.allMethods().stream().noneMatch(this::referenceFeatureClass));
   }
 
   @Test
@@ -124,12 +154,16 @@
     // Check that parts of method1, ..., method4 in FeatureClass was not outlined.
     CodeInspector featureInspector = new CodeInspector(featureCode);
     ClassSubject featureClass = featureInspector.clazz(FeatureClass.class);
+
+    // Note, this code does not really check a valid property now as the name of the outline is not
+    // known.
     assertThat(featureClass, isPresent());
     String outlineClassName =
         ClassNameMapper.mapperFromString(compileResult.getProguardMap())
             .getObfuscatedToOriginalMapping()
             .inverse
-            .get(OutlineOptions.CLASS_NAME);
+            .get(SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0).getTypeName());
+
     assertFalse(invokesOutline(featureClass.uniqueMethodWithName("method1"), outlineClassName));
     assertFalse(invokesOutline(featureClass.uniqueMethodWithName("method2"), outlineClassName));
     assertFalse(invokesOutline(featureClass.uniqueMethodWithName("method3"), outlineClassName));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
index eab6811..39c95f1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -37,11 +37,12 @@
   }
 
   private void validateOutlining(CodeInspector inspector) {
-    ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME);
+    ClassSubject outlineClass =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0));
     MethodSubject outline0Method =
         outlineClass.method(
             "void",
-            "outline0",
+            SyntheticItemsTestUtils.syntheticMethodName(),
             ImmutableList.of(
                 StringBuilder.class.getTypeName(),
                 String.class.getTypeName(),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java
index db3ef77..4b81714 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -38,10 +38,13 @@
   }
 
   private void validateOutlining(CodeInspector inspector, Class<?> testClass, String argumentType) {
-    ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME);
+    ClassSubject outlineClass =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(testClass, 0));
     MethodSubject outline0Method =
         outlineClass.method(
-            "java.lang.String", "outline0", ImmutableList.of(argumentType, argumentType));
+            "java.lang.String",
+            SyntheticItemsTestUtils.syntheticMethodName(),
+            ImmutableList.of(argumentType, argumentType));
     assertThat(outline0Method, isPresent());
     ClassSubject classSubject = inspector.clazz(testClass);
     assertThat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
index 5852266..b96ee7f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize.string;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -22,7 +23,6 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.Streams;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -93,17 +93,17 @@
   }
 
   private long countStringLength(MethodSubject method) {
-    return Streams.stream(method.iterateInstructions(instructionSubject -> {
-      if (instructionSubject.isInvoke()) {
-        return isStringLength(instructionSubject.getMethod());
-      }
-      return false;
-    })).count();
+    return method
+        .streamInstructions()
+        .filter(
+            instructionSubject ->
+                instructionSubject.isInvoke() && isStringLength(instructionSubject.getMethod()))
+        .count();
   }
 
   private long countNonZeroConstNumber(MethodSubject method) {
-    return Streams.stream(method.iterateInstructions(InstructionSubject::isConstNumber)).count()
-        - Streams.stream(method.iterateInstructions(instr -> instr.isConstNumber(0))).count();
+    return method.streamInstructions().filter(InstructionSubject::isConstNumber).count()
+        - method.streamInstructions().filter(instr -> instr.isConstNumber(0)).count();
   }
 
   private void test(
@@ -117,9 +117,13 @@
     ClassSubject mainClass = codeInspector.clazz(MAIN);
 
     MethodSubject clinit = mainClass.clinit();
-    assertThat(clinit, isPresent());
-    assertEquals(expectedStringLengthCountInClinit, countStringLength(clinit));
-    assertEquals(expectedConstNumberCountInClinit, countNonZeroConstNumber(clinit));
+    if (result.isR8TestRunResult()) {
+      assertThat(clinit, isAbsent());
+    } else {
+      assertThat(clinit, isPresent());
+      assertEquals(expectedStringLengthCountInClinit, countStringLength(clinit));
+      assertEquals(expectedConstNumberCountInClinit, countNonZeroConstNumber(clinit));
+    }
 
     MethodSubject m = mainClass.uniqueMethodWithName("instanceMethod");
     assertThat(m, isPresent());
@@ -154,7 +158,7 @@
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     // TODO(b/125303292): NAME_LENGTH is still not computed at compile time.
-    test(result, 1, 0, 0, 1);
+    test(result, 0, 0, 0, 1);
   }
 
   @Test
@@ -171,7 +175,6 @@
             .assertSuccessWithOutput(JAVA_OUTPUT);
     // No canonicalization in CF.
     int expectedConstNumber = parameters.isCfRuntime() ? 2 : 1;
-    // TODO(b/125303292): NAME_LENGTH is still not computed at compile time.
-    test(result, 1, 0, 0, expectedConstNumber);
+    test(result, 0, 0, 0, expectedConstNumber);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionMappingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionMappingTest.java
index 30f5c93..d8a7d6b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionMappingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionMappingTest.java
@@ -78,8 +78,8 @@
       if (method.getFinalName().equals("main")) {
         continue;
       }
-      foundZeroArgumentMethod |= method.getMethod().parameters().size() == 0;
-      foundOneArgumentMethod |= method.getMethod().parameters().size() == 1;
+      foundZeroArgumentMethod |= method.getMethod().getParameters().size() == 0;
+      foundOneArgumentMethod |= method.getMethod().getParameters().size() == 1;
     }
     assertTrue(foundZeroArgumentMethod);
     assertTrue(foundOneArgumentMethod);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
index 94392e6..8f88d08 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
@@ -80,7 +80,6 @@
             .enableInliningAnnotations()
             .enableNoHorizontalClassMergingAnnotations()
             .enableNoStaticClassMergingAnnotations()
-            .noMinification()
             .setMinApi(parameters.getApiLevel())
             .compile();
 
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java
new file mode 100644
index 0000000..867b1ec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java
@@ -0,0 +1,155 @@
+// Copyright (c) 2021, 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.maindexlist;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.BeforeClass;
+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 MainDexListFromGenerateMainHorizontalMergingTest extends TestBase {
+
+  private static List<ClassReference> mainDexList;
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
+  }
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    mainDexList =
+        testForMainDexListGenerator(getStaticTemp())
+            .addInnerClasses(MainDexListFromGenerateMainHorizontalMergingTest.class)
+            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+            .addMainDexRules(
+                "-keep class " + Main.class.getTypeName() + " { public static void foo(); }")
+            .run()
+            .getMainDexList();
+  }
+
+  public MainDexListFromGenerateMainHorizontalMergingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    assertEquals(3, mainDexList.size());
+    Set<String> mainDexReferences =
+        mainDexList.stream().map(TypeReference::getTypeName).collect(Collectors.toSet());
+    assertTrue(mainDexReferences.contains(A.class.getTypeName()));
+    assertTrue(mainDexReferences.contains(B.class.getTypeName()));
+    assertTrue(mainDexReferences.contains(Main.class.getTypeName()));
+
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .addInliningAnnotations()
+            .addKeepClassAndMembersRules(Main.class, Outside.class)
+            .addMainDexListClassReferences(mainDexList)
+            .collectMainDexClasses()
+            .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
+            .setMinApi(parameters.getApiLevel())
+            .addHorizontallyMergedClassesInspector(
+                horizontallyMergedClassesInspector -> {
+                  horizontallyMergedClassesInspector.assertMergedInto(B.class, A.class);
+                })
+            .compile();
+
+    CodeInspector inspector = compileResult.inspector();
+    ClassSubject mainClassSubject = inspector.clazz(Main.class);
+    assertThat(mainClassSubject, isPresent());
+
+    MethodSubject fooMethodSubject = mainClassSubject.uniqueMethodWithName("foo");
+    assertThat(fooMethodSubject, isPresent());
+
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+
+    // TODO(b/178460068): Should be present, but was merged with A.
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isAbsent());
+
+    MethodSubject fooASubject = aClassSubject.uniqueMethodWithName("foo");
+    assertThat(fooASubject, isPresent());
+
+    assertThat(fooMethodSubject, invokesMethod(fooASubject));
+
+    compileResult.inspectMainDexClasses(
+        mainDexClasses -> {
+          assertEquals(
+              ImmutableSet.of(mainClassSubject.getFinalName(), aClassSubject.getFinalName()),
+              mainDexClasses);
+        });
+
+    compileResult
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputThatMatches(containsString(Outside.class.getTypeName()));
+  }
+
+  static class Main {
+
+    // public static B b;
+
+    public static void main(String[] args) {
+      B.print();
+    }
+
+    public static void foo() {
+      A.foo();
+    }
+  }
+
+  public static class Outside {}
+
+  public static class A {
+
+    @NeverInline
+    public static void foo() {
+      System.out.println("A::foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+
+    @NeverInline
+    public static void print() {
+      System.out.println(Outside.class);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java
new file mode 100644
index 0000000..e3d49f5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java
@@ -0,0 +1,159 @@
+// Copyright (c) 2021, 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.maindexlist;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.BeforeClass;
+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 MainDexListFromGenerateMainVerticalMergingTest extends TestBase {
+
+  private static List<ClassReference> mainDexList;
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
+  }
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    mainDexList =
+        testForMainDexListGenerator(getStaticTemp())
+            .addInnerClasses(MainDexListFromGenerateMainVerticalMergingTest.class)
+            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+            .addMainDexRules(
+                "-keep class " + Main.class.getTypeName() + " { public static void foo(); }")
+            .run()
+            .getMainDexList();
+  }
+
+  public MainDexListFromGenerateMainVerticalMergingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    assertEquals(3, mainDexList.size());
+    Set<String> mainDexReferences =
+        mainDexList.stream().map(TypeReference::getTypeName).collect(Collectors.toSet());
+    assertTrue(mainDexReferences.contains(A.class.getTypeName()));
+    assertTrue(mainDexReferences.contains(B.class.getTypeName()));
+    assertTrue(mainDexReferences.contains(Main.class.getTypeName()));
+
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .addInliningAnnotations()
+            .addKeepClassAndMembersRules(Main.class, Outside.class)
+            .addMainDexListClassReferences(mainDexList)
+            .collectMainDexClasses()
+            .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+
+    CodeInspector inspector = compileResult.inspector();
+    ClassSubject mainClassSubject = inspector.clazz(Main.class);
+    assertThat(mainClassSubject, isPresent());
+
+    MethodSubject fooMethodSubject = mainClassSubject.uniqueMethodWithName("foo");
+    assertThat(fooMethodSubject, isPresent());
+
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    // TODO(b/178460068): Should be present, but was merged with B.
+    assertThat(aClassSubject, isAbsent());
+
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isPresent());
+
+    FieldSubject outsideFieldSubject = bClassSubject.uniqueFieldWithName("outsideField");
+    assertThat(outsideFieldSubject, isPresent());
+
+    MethodSubject fooBMethodSubject = bClassSubject.uniqueMethodWithName("foo");
+    assertThat(fooBMethodSubject, isPresent());
+
+    assertThat(fooMethodSubject, invokesMethod(fooBMethodSubject));
+
+    // TODO(b/178460068): B should not be in main dex.
+    compileResult.inspectMainDexClasses(
+        mainDexClasses -> {
+          assertEquals(
+              ImmutableSet.of(mainClassSubject.getFinalName(), bClassSubject.getFinalName()),
+              mainDexClasses);
+        });
+
+    compileResult.run(parameters.getRuntime(), Main.class).assertSuccessWithOutputLines("B::print");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new B().print();
+    }
+
+    public static void foo() {
+      A.foo();
+    }
+  }
+
+  public static class Outside {}
+
+  public static class A {
+
+    @NeverInline
+    public static void foo() {
+      System.out.println("A::foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class B extends A {
+
+    public static Outside outsideField;
+
+    {
+      outsideField = System.currentTimeMillis() > 0 ? null : new Outside();
+    }
+
+    @NeverInline
+    public void print() {
+      if (outsideField == null) {
+        System.out.println("B::print");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 04ac085..efe3826 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -30,14 +30,18 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -50,6 +54,10 @@
 
 public class OutlineTest extends SmaliTestBase {
 
+  private static ClassReference OUTLINE_CLASS =
+      SyntheticItemsTestUtils.syntheticOutlineClass(
+          Reference.classFromTypeName(DEFAULT_CLASS_NAME), 0);
+
   private static final String stringBuilderAppendSignature =
       "Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;";
   private static final String stringBuilderAppendDoubleSignature =
@@ -85,12 +93,13 @@
   }
 
   private String firstOutlineMethodName() {
-    return OutlineOptions.CLASS_NAME + '.' + OutlineOptions.METHOD_PREFIX + "0";
+    return OUTLINE_CLASS.getTypeName() + '.' + SyntheticItemsTestUtils.syntheticMethodName();
   }
 
-  private boolean isOutlineMethodName(String qualifiedName) {
-    String qualifiedPrefix = OutlineOptions.CLASS_NAME + '.' + OutlineOptions.METHOD_PREFIX;
-    return qualifiedName.indexOf(qualifiedPrefix) == 0;
+  private static boolean isOutlineMethodName(DexMethod method) {
+    return SyntheticItemsTestUtils.isExternalOutlineClass(
+            Reference.classFromDescriptor(method.holder.toDescriptorString()))
+        && method.name.toString().equals(SyntheticItemsTestUtils.syntheticMethodName());
   }
 
   @Test
@@ -142,7 +151,6 @@
 
       AndroidApp originalApplication = buildApplication(builder);
       AndroidApp processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -151,7 +159,7 @@
       assertTrue(code.instructions[0] instanceof ConstString);
       assertTrue(code.instructions[1] instanceof InvokeStatic);
       InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-      assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+      assertTrue(isOutlineMethodName(invoke.getMethod()));
 
       // Run code and check result.
       String result = runArt(processedApplication);
@@ -211,7 +219,6 @@
 
       AndroidApp originalApplication = buildApplication(builder);
       AndroidApp processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -225,7 +232,7 @@
       }
       assertTrue(code.instructions[firstOutlineInvoke] instanceof InvokeStatic);
       InvokeStatic invoke = (InvokeStatic) code.instructions[firstOutlineInvoke];
-      assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+      assertTrue(isOutlineMethodName(invoke.getMethod()));
 
       // Run code and check result.
       String result = runArt(processedApplication);
@@ -273,7 +280,6 @@
     Consumer<InternalOptions> options = configureOutlineOptions(outline -> outline.threshold = 1);
     AndroidApp originalApplication = buildApplication(builder);
     AndroidApp processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -282,7 +288,7 @@
     assertTrue(code.instructions[0] instanceof ConstString);
     assertTrue(code.instructions[1] instanceof InvokeStatic);
     InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod()));
 
     // Run code and check result.
     String result = runArt(processedApplication);
@@ -336,7 +342,6 @@
     Consumer<InternalOptions> options = configureOutlineOptions(outline -> outline.threshold = 1);
     AndroidApp originalApplication = buildApplication(builder);
     AndroidApp processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -346,7 +351,7 @@
     assertTrue(code.instructions[1] instanceof ConstString);
     assertTrue(code.instructions[2] instanceof InvokeStatic);
     InvokeStatic invoke = (InvokeStatic) code.instructions[2];
-    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod()));
 
     // Run code and check result.
     String result = runArt(processedApplication);
@@ -402,7 +407,6 @@
 
       AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
       AndroidApp processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -412,13 +416,13 @@
       if (i < 3) {
         assertTrue(code.instructions[1] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       } else {
         assertTrue(code.instructions[1] instanceof InvokeVirtual);
         assertTrue(code.instructions[2] instanceof InvokeVirtual);
         assertTrue(code.instructions[3] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[3];
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       }
 
       // Run code and check result.
@@ -480,7 +484,6 @@
 
       AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
       AndroidApp processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -490,13 +493,13 @@
       if (i < 3) {
         assertTrue(code.instructions[1] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       } else {
         assertTrue(code.instructions[1] instanceof InvokeVirtual);
         assertTrue(code.instructions[2] instanceof InvokeVirtual);
         assertTrue(code.instructions[3] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[3];
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       }
 
       // Run code and check result.
@@ -553,7 +556,6 @@
 
       AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
       AndroidApp processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed main method for inspection.
       DexEncodedMethod mainMethod = getMethod(processedApplication, mainSignature);
@@ -569,14 +571,14 @@
       }
       if (i == 2) {
         InvokeStatic invoke = (InvokeStatic) mainCode.instructions[4];
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       } else if (i == 3) {
         InvokeStatic invoke = (InvokeStatic) mainCode.instructions[1];
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       } else {
         assert i == 4 || i == 5;
         InvokeStatic invoke = (InvokeStatic) mainCode.instructions[2];
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       }
 
       // Run code and check result.
@@ -653,19 +655,19 @@
 
     AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
     AndroidApp processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, getNumberOfProgramClasses(processedApplication));
+    assertEquals(3, getNumberOfProgramClasses(processedApplication));
 
     DexCode code1 = getMethod(processedApplication, signature1).getCode().asDexCode();
     assertEquals(4, code1.instructions.length);
     assertTrue(code1.instructions[1] instanceof InvokeStatic);
     InvokeStatic invoke1 = (InvokeStatic) code1.instructions[1];
-    assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke1.getMethod()));
 
     DexCode code2 = getMethod(processedApplication, signature2).getCode().asDexCode();
     assertEquals(5, code2.instructions.length);
     assertTrue(code2.instructions[2] instanceof InvokeStatic);
     InvokeStatic invoke2 = (InvokeStatic) code2.instructions[2];
-    assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke1.getMethod()));
 
     // Run code and check result.
     String result = runArt(processedApplication);
@@ -721,7 +723,6 @@
 
       AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
       AndroidApp processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       DexCode code = getMethod(processedApplication, signature).getCode().asDexCode();
       int outlineInstructionIndex;
@@ -739,10 +740,10 @@
       Instruction instruction = code.instructions[outlineInstructionIndex];
       if (instruction instanceof InvokeStatic) {
         InvokeStatic invoke = (InvokeStatic) instruction;
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       } else {
         InvokeStaticRange invoke = (InvokeStaticRange) instruction;
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       }
 
       // Run code and check result.
@@ -791,7 +792,7 @@
     InvokeStatic invoke;
     assertTrue(code.instructions[0] instanceof InvokeStatic);
     invoke = (InvokeStatic) code.instructions[0];
-    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod()));
 
     // Run code and check result.
     String result = runArt(processedApplication);
@@ -859,16 +860,15 @@
 
     AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
     AndroidApp processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, getNumberOfProgramClasses(processedApplication));
+    assertEquals(4, getNumberOfProgramClasses(processedApplication));
 
     // Check that three outlining methods was created.
     CodeInspector inspector = new CodeInspector(processedApplication);
-    ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
-    assertTrue(clazz.isPresent());
-    assertEquals(3, clazz.getDexProgramClass().getMethodCollection().numberOfDirectMethods());
+    List<DexEncodedMethod> outlineMethods = getOutlineMethods(inspector);
+    assertEquals(3, outlineMethods.size());
     // Collect the return types of the outlines for the body of method1 and method2.
     List<DexType> r = new ArrayList<>();
-    for (DexEncodedMethod directMethod : clazz.getDexProgramClass().directMethods()) {
+    for (DexEncodedMethod directMethod : outlineMethods) {
       if (directMethod.getCode().asDexCode().instructions[0] instanceof InvokeVirtual) {
         r.add(directMethod.method.proto.returnType);
       }
@@ -885,6 +885,16 @@
     assertEquals("TestTestTestTestTest", result);
   }
 
+  private static List<DexEncodedMethod> getOutlineMethods(CodeInspector inspector) {
+    List<DexEncodedMethod> outlineMethods = new ArrayList<>();
+    for (FoundClassSubject clazz : inspector.allClasses()) {
+      if (SyntheticItemsTestUtils.isExternalOutlineClass(clazz.getFinalReference())) {
+        clazz.forAllMethods(m -> outlineMethods.add(m.getMethod()));
+      }
+    }
+    return outlineMethods;
+  }
+
   @Test
   public void outlineMultipleTimes() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
@@ -931,15 +941,18 @@
 
     AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
     AndroidApp processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, getNumberOfProgramClasses(processedApplication));
+    assertEquals(3, getNumberOfProgramClasses(processedApplication));
 
     final int count = 10;
-    // Process the application several times. Each time will outline the previous outline.
-    for (int i = 0; i < count; i++) {
+    // Process the application several times. Each time will outline the previous outlines.
+    for (int i = 1; i < count; i++) {
       // Build a new application with the Outliner class.
       originalApplication = processedApplication;
-      processedApplication = processApplication(originalApplication, options);
-      assertEquals(i + 3, getNumberOfProgramClasses(processedApplication));
+      processedApplication =
+          processApplication(
+              originalApplication,
+              options.andThen(o -> o.testing.allowConflictingSyntheticTypes = true));
+      assertEquals((i + 1) * 3, getNumberOfProgramClasses(processedApplication));
     }
 
     // Process the application several times. No more outlining as threshold has been raised.
@@ -954,7 +967,7 @@
       // Build a new application with the Outliner class.
       originalApplication = processedApplication;
       processedApplication = processApplication(originalApplication, options);
-      assertEquals(count - 1 + 3, getNumberOfProgramClasses(processedApplication));
+      assertEquals(count * 3, getNumberOfProgramClasses(processedApplication));
     }
 
     // Run the application with several levels of outlining.
@@ -1195,7 +1208,6 @@
 
     AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
     AndroidApp processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method1 = getMethod(processedApplication, signature1);
@@ -1205,7 +1217,7 @@
     assertTrue(code1.instructions[1] instanceof MoveResult);
     assertTrue(code1.instructions[2] instanceof Return);
     InvokeStatic invoke1 = (InvokeStatic) code1.instructions[0];
-    assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke1.getMethod()));
 
     DexEncodedMethod method2 = getMethod(processedApplication, signature2);
     DexCode code2 = method2.getCode().asDexCode();
@@ -1287,7 +1299,7 @@
     assertTrue(code.instructions[5] instanceof Const4);
     assertTrue(code.instructions[6] instanceof Return);
     InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod()));
 
     // Run code and check result.
     String result = runArt(processedApplication);
@@ -1614,15 +1626,13 @@
 
     AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
     AndroidApp processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Verify the code.
     runDex2Oat(processedApplication);
   }
 
   private static boolean isOutlineInvoke(Instruction instruction) {
-    return instruction instanceof InvokeStatic
-        && instruction.getMethod().holder.toSourceString().equals(OutlineOptions.CLASS_NAME);
+    return instruction instanceof InvokeStatic && isOutlineMethodName(instruction.getMethod());
   }
 
   private void assertHasOutlineInvoke(DexEncodedMethod method) {
@@ -1699,10 +1709,10 @@
     assertHasOutlineInvoke(getMethod(processedApplication, signature2));
     assertThat(
         new CodeInspector(processedApplication)
-            .clazz(OutlineOptions.CLASS_NAME)
+            .clazz(OUTLINE_CLASS)
             .method(
                 "boolean",
-                "outline0",
+                SyntheticItemsTestUtils.syntheticMethodName(),
                 ImmutableList.of("java.io.PrintStream", "java.util.ArrayList")),
         isPresent());
 
@@ -1774,8 +1784,11 @@
     assertHasOutlineInvoke(getMethod(processedApplication, signature2));
     assertThat(
         new CodeInspector(processedApplication)
-            .clazz(OutlineOptions.CLASS_NAME)
-            .method("boolean", "outline0", ImmutableList.of("java.util.List")),
+            .clazz(OUTLINE_CLASS)
+            .method(
+                "boolean",
+                SyntheticItemsTestUtils.syntheticMethodName(),
+                ImmutableList.of("java.util.List")),
         isPresent());
 
     // Run code and check result.
@@ -1848,8 +1861,11 @@
     assertHasOutlineInvoke(getMethod(processedApplication, signature2));
     assertThat(
         new CodeInspector(processedApplication)
-            .clazz(OutlineOptions.CLASS_NAME)
-            .method("boolean", "outline0", ImmutableList.of("java.util.ArrayList")),
+            .clazz(OUTLINE_CLASS)
+            .method(
+                "boolean",
+                SyntheticItemsTestUtils.syntheticMethodName(),
+                ImmutableList.of("java.util.ArrayList")),
         isPresent());
 
     // Run code and check result.
@@ -1922,8 +1938,11 @@
     assertHasOutlineInvoke(getMethod(processedApplication, signature2));
     assertThat(
         new CodeInspector(processedApplication)
-            .clazz(OutlineOptions.CLASS_NAME)
-            .method("boolean", "outline0", ImmutableList.of("java.util.ArrayList")),
+            .clazz(OUTLINE_CLASS)
+            .method(
+                "boolean",
+                SyntheticItemsTestUtils.syntheticMethodName(),
+                ImmutableList.of("java.util.ArrayList")),
         isPresent());
 
     // Run code and check result.
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index 438d3f9..3f9c6af 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -16,6 +16,10 @@
 
 public class SyntheticItemsTestUtils {
 
+  public static String syntheticMethodName() {
+    return SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX;
+  }
+
   public static ClassReference syntheticCompanionClass(Class<?> clazz) {
     return Reference.classFromDescriptor(
         InterfaceMethodRewriter.getCompanionClassDescriptor(
@@ -23,8 +27,11 @@
   }
 
   private static ClassReference syntheticClass(Class<?> clazz, SyntheticKind kind, int id) {
-    return SyntheticNaming.makeSyntheticReferenceForTest(
-        Reference.classFromClass(clazz), kind, "" + id);
+    return syntheticClass(Reference.classFromClass(clazz), kind, id);
+  }
+
+  private static ClassReference syntheticClass(ClassReference clazz, SyntheticKind kind, int id) {
+    return SyntheticNaming.makeSyntheticReferenceForTest(clazz, kind, "" + id);
   }
 
   public static MethodReference syntheticBackportMethod(Class<?> clazz, int id, Method method) {
@@ -33,10 +40,18 @@
     MethodReference originalMethod = Reference.methodFromMethod(method);
     return Reference.methodFromDescriptor(
         syntheticHolder.getDescriptor(),
-        SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX,
+        syntheticMethodName(),
         originalMethod.getMethodDescriptor());
   }
 
+  public static ClassReference syntheticOutlineClass(Class<?> clazz, int id) {
+    return syntheticClass(clazz, SyntheticKind.OUTLINE, id);
+  }
+
+  public static ClassReference syntheticOutlineClass(ClassReference clazz, int id) {
+    return syntheticClass(clazz, SyntheticKind.OUTLINE, id);
+  }
+
   public static ClassReference syntheticLambdaClass(Class<?> clazz, int id) {
     return syntheticClass(clazz, SyntheticNaming.SyntheticKind.LAMBDA, id);
   }
@@ -67,6 +82,10 @@
     return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.TWR_CLOSE_RESOURCE);
   }
 
+  public static boolean isExternalOutlineClass(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.OUTLINE);
+  }
+
   public static Matcher<String> containsInternalSyntheticReference() {
     return containsString(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
   }
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 179a50f..783b30d 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -9,7 +9,7 @@
 import re
 import subprocess
 import sys
-import urllib
+import urllib.request
 import xml.etree.ElementTree
 import zipfile
 
@@ -70,7 +70,7 @@
 
         # Verify that the merge point from master is not empty.
         merge_diff_output = subprocess.check_output([
-          'git', 'diff', 'HEAD..%s' % commithash])
+          'git', 'diff', 'HEAD..%s' % commithash]).decode('utf-8')
         other_diff = version_change_diff(
             merge_diff_output, old_version, "master")
         if not other_diff:
@@ -94,14 +94,14 @@
           'git', 'commit', '-a', '-m', 'Version %s' % version])
 
         version_diff_output = subprocess.check_output([
-          'git', 'diff', '%s..HEAD' % commithash])
+          'git', 'diff', '%s..HEAD' % commithash]).decode('utf-8')
 
         validate_version_change_diff(version_diff_output, "master", version)
 
         # Double check that we want to push the release.
         if not args.dry_run:
-          input = raw_input('Publish dev release version %s [y/N]:' % version)
-          if input != 'y':
+          answer = input('Publish dev release version %s [y/N]:' % version)
+          if answer != 'y':
             print('Aborting dev release for %s' % version)
             sys.exit(1)
 
@@ -144,10 +144,10 @@
     print(version_diff_output)
     print("=" * 80)
     accept_string = 'THE DIFF IS OK!'
-    input = raw_input(
+    answer = input(
       "Accept the additonal diff as part of the release? "
       "Type '%s' to accept: " % accept_string)
-    if input != accept_string:
+    if answer != accept_string:
       print("You did not type '%s'" % accept_string)
       print('Aborting dev release for %s' % version)
       sys.exit(1)
@@ -233,9 +233,9 @@
         release_id, "com.android.tools:r8:%s" % args.version, "r8lib.jar")
 
     print
-    input = raw_input("Continue with publishing [y/N]:")
+    answer = input("Continue with publishing [y/N]:")
 
-    if input != 'y':
+    if answer != 'y':
       print('Aborting release to Google maven')
       sys.exit(1)
 
@@ -305,7 +305,7 @@
 def g4_change(version):
   return subprocess.check_output(
       'g4 change --desc "Update R8 to version %s\n"' % (version),
-      shell=True)
+      shell=True).decode('utf-8')
 
 def get_cl_id(c4_change_output):
   startIndex = c4_change_output.find('Change ') + len('Change ')
@@ -323,7 +323,7 @@
 
 
 def download_file(version, file, dst):
-  urllib.urlretrieve(
+  urllib.request.urlretrieve(
       ('https://storage.googleapis.com/r8-releases/raw/%s/%s' % (version, file)),
       dst)
 
@@ -332,13 +332,13 @@
     print('Unexpected gfile prefix for %s' % gfile)
     sys.exit(1)
 
-  urllib.urlretrieve(
+  urllib.request.urlretrieve(
       'https://storage.googleapis.com/%s' % gfile[len('/bigstore/'):],
       dst)
 
 def blaze_run(target):
   return subprocess.check_output(
-      'blaze run %s' % target, shell=True, stderr=subprocess.STDOUT)
+      'blaze run %s' % target, shell=True, stderr=subprocess.STDOUT).decode('utf-8')
 
 
 def prepare_google3(args):
@@ -353,7 +353,7 @@
       return 'DryRun: omitting g3 release for %s' % options.version
 
     google3_base = subprocess.check_output(
-        ['p4', 'g4d', '-f', args.p4_client]).rstrip()
+        ['p4', 'g4d', '-f', args.p4_client]).decode('utf-8').rstrip()
     third_party_r8 = os.path.join(google3_base, 'third_party', 'java', 'r8')
     today = datetime.date.today()
     with utils.ChangedWorkingDirectory(third_party_r8):
@@ -504,9 +504,9 @@
             library_jar)
 
         print("")
-        input = raw_input("Continue with publishing [y/N]:")
+        answer = input("Continue with publishing [y/N]:")
 
-        if input != 'y':
+        if answer != 'y':
           print('Aborting release to Google maven')
           sys.exit(1)
 
@@ -542,11 +542,11 @@
 
 def check_no_google3_client(args, client_name):
   if not args.use_existing_work_branch:
-    clients = subprocess.check_output('g4 myclients', shell=True)
+    clients = subprocess.check_output('g4 myclients', shell=True).decode('utf-8')
     if ':%s:' % client_name in clients:
-      print("Remove the existing '%s' client before continuing " +
+      print(("Remove the existing '%s' client before continuing " +
              "(force delete: 'g4 citc -d -f %s'), " +
-             "or use option --use-existing-work-branch.") % (client_name, client_name)
+             "or use option --use-existing-work-branch.") % (client_name, client_name))
       sys.exit(1)
 
 
@@ -701,8 +701,8 @@
 
         # Double check that we want to create a new release branch.
         if not options.dry_run:
-          input = raw_input('Create new branch for %s [y/N]:' % branch_version)
-          if input != 'y':
+          answer = input('Create new branch for %s [y/N]:' % branch_version)
+          if answer != 'y':
             print('Aborting new branch for %s' % branch_version)
             sys.exit(1)