Merge commit '7fd2616561134814c9baee894370ff7f82f99520' into dev-release
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 74da57f..17b7485 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -144,7 +144,9 @@
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               code,
-              50);
+              false,
+              50,
+              false);
       if (method.isStatic() || method.isDirectMethod()) {
         directMethods.add(throwingMethod);
       } else {
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index b98c4cb..4574386 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -311,7 +311,7 @@
 
   private DexType computeVerificationType(Value value) {
     return value.isPhi()
-        ? value.asPhi().computeVerificationType(this)
+        ? value.asPhi().computeVerificationType(appView, this)
         : value.definition.computeVerificationType(appView, this);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 02af4ee..79d6d83 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -64,6 +64,12 @@
 
 public class CfCode extends Code implements Comparable<CfCode> {
 
+  public enum StackMapStatus {
+    NOT_VERIFIED,
+    INVALID_OR_NOT_PRESENT,
+    VALID
+  }
+
   public static class LocalVariableInfo {
 
     private final int index;
@@ -129,6 +135,7 @@
   private List<CfInstruction> instructions;
   private final List<CfTryCatch> tryCatchRanges;
   private final List<LocalVariableInfo> localVariables;
+  private StackMapStatus stackMapStatus = StackMapStatus.NOT_VERIFIED;
 
   public CfCode(
       DexType originalHolder,
@@ -157,6 +164,11 @@
     return maxLocals;
   }
 
+  public StackMapStatus getStackMapStatus() {
+    assert stackMapStatus != StackMapStatus.NOT_VERIFIED;
+    return stackMapStatus;
+  }
+
   public void setMaxLocals(int newMaxLocals) {
     maxLocals = newMaxLocals;
   }
@@ -371,7 +383,7 @@
 
   @Override
   public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
-    // TODO(b/164396438): Assert that we can validate frames.
+    verifyFramesOrRemove(method.getDefinition(), appView, origin, true);
     return internalBuildPossiblyWithLocals(method, method, appView, null, null, origin, null);
   }
 
@@ -386,11 +398,22 @@
       MethodProcessor methodProcessor) {
     assert valueNumberGenerator != null;
     assert callerPosition != null;
-    // TODO(b/164396438): Assert that we can validate frames.
+    verifyFramesOrRemove(
+        method.getDefinition(), appView, origin, methodProcessor.shouldApplyCodeRewritings(method));
     return internalBuildPossiblyWithLocals(
         context, method, appView, valueNumberGenerator, callerPosition, origin, methodProcessor);
   }
 
+  private void verifyFramesOrRemove(
+      DexEncodedMethod method,
+      AppView<?> appView,
+      Origin origin,
+      boolean shouldApplyCodeRewritings) {
+    if (!verifyFrames(method, appView, origin, shouldApplyCodeRewritings)) {
+      instructions.removeIf(CfInstruction::isFrame);
+    }
+  }
+
   // First build entry. Will either strip locals or build with locals.
   private IRCode internalBuildPossiblyWithLocals(
       ProgramMethod context,
@@ -657,9 +680,11 @@
       DexEncodedMethod method, AppView<?> appView, Origin origin, boolean applyProtoTypeChanges) {
     if (!appView.options().testing.readInputStackMaps
         || appView.options().testing.disableStackMapVerification) {
+      stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT;
       return true;
     }
     if (method.hasClassFileVersion() && method.getClassFileVersion() <= V1_6) {
+      stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT;
       return true;
     }
     if (!method.isInstanceInitializer()
@@ -668,6 +693,7 @@
             .getOriginalMethodSignature(method.method)
             .isInstanceInitializer(appView.dexItemFactory())) {
       // We cannot verify instance initializers if they are moved.
+      stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT;
       return true;
     }
     // Build a map from labels to frames.
@@ -767,6 +793,7 @@
             appView);
       }
     }
+    stackMapStatus = StackMapStatus.VALID;
     return true;
   }
 
@@ -775,6 +802,7 @@
     // started enforcing stack maps from 51 in JVM 8. As a consequence, we have different android
     // libraries that has V1_7 code but has no stack maps. To not fail on compilations we only
     // report a warning.
+    stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT;
     appView.options().reporter.warning(diagnostics);
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCodeDiagnostics.java b/src/main/java/com/android/tools/r8/graph/CfCodeDiagnostics.java
index 764a812..8a664ec 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCodeDiagnostics.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCodeDiagnostics.java
@@ -30,7 +30,7 @@
   private final MethodPosition methodPosition;
   private final String diagnosticMessage;
 
-  CfCodeDiagnostics(Origin origin, DexMethod method, String diagnosticMessage) {
+  public CfCodeDiagnostics(Origin origin, DexMethod method, String diagnosticMessage) {
     this.origin = origin;
     this.methodPosition = new MethodPosition(method.asMethodReference());
     this.diagnosticMessage = diagnosticMessage;
diff --git a/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java b/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
index 6c54a1a..b6d1b1e 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
@@ -13,8 +13,8 @@
     super(message);
   }
 
-  public static CfCodeStackMapValidatingException error(String messsage) {
-    return new CfCodeStackMapValidatingException(messsage);
+  public static CfCodeStackMapValidatingException error(String message) {
+    return new CfCodeStackMapValidatingException(message);
   }
 
   public static CfCodeDiagnostics unexpectedStackMapFrame(
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 7683f12..8068417 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -27,6 +27,7 @@
   public final DexField field;
   public final FieldAccessFlags accessFlags;
   private DexValue staticValue;
+  private final boolean deprecated;
 
   private FieldOptimizationInfo optimizationInfo = DefaultFieldOptimizationInfo.getInstance();
   private KotlinFieldLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
@@ -35,17 +36,31 @@
       DexField field,
       FieldAccessFlags accessFlags,
       DexAnnotationSet annotations,
-      DexValue staticValue) {
+      DexValue staticValue,
+      boolean deprecated) {
     super(annotations);
     this.field = field;
     this.accessFlags = accessFlags;
     this.staticValue = staticValue;
+    this.deprecated = deprecated;
+  }
+
+  public DexEncodedField(
+      DexField field,
+      FieldAccessFlags accessFlags,
+      DexAnnotationSet annotations,
+      DexValue staticValue) {
+    this(field, accessFlags, annotations, staticValue, false);
   }
 
   public DexType type() {
     return field.type;
   }
 
+  public boolean isDeprecated() {
+    return deprecated;
+  }
+
   public boolean isProgramField(DexDefinitionSupplier definitions) {
     if (field.holder.isClassType()) {
       DexClass clazz = definitions.definitionFor(field.holder);
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 f049ec5..24776d0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -139,6 +139,7 @@
 
   public final DexMethod method;
   public final MethodAccessFlags accessFlags;
+  public final boolean deprecated;
   public ParameterAnnotationsList parameterAnnotationsList;
   private Code code;
   // TODO(b/128967328): towards finer-grained inlining constraints,
@@ -227,7 +228,7 @@
       DexAnnotationSet annotations,
       ParameterAnnotationsList parameterAnnotationsList,
       Code code) {
-    this(method, accessFlags, annotations, parameterAnnotationsList, code, -1);
+    this(method, accessFlags, annotations, parameterAnnotationsList, code, false, -1);
   }
 
   public DexEncodedMethod(
@@ -236,8 +237,27 @@
       DexAnnotationSet annotations,
       ParameterAnnotationsList parameterAnnotationsList,
       Code code,
+      boolean d8R8Synthesized) {
+    this(method, accessFlags, annotations, parameterAnnotationsList, code, d8R8Synthesized, -1);
+  }
+
+  public DexEncodedMethod(
+      DexMethod method,
+      MethodAccessFlags accessFlags,
+      DexAnnotationSet annotations,
+      ParameterAnnotationsList parameterAnnotationsList,
+      Code code,
+      boolean d8R8Synthesized,
       int classFileVersion) {
-    this(method, accessFlags, annotations, parameterAnnotationsList, code, classFileVersion, false);
+    this(
+        method,
+        accessFlags,
+        annotations,
+        parameterAnnotationsList,
+        code,
+        d8R8Synthesized,
+        classFileVersion,
+        false);
   }
 
   public DexEncodedMethod(
@@ -246,21 +266,13 @@
       DexAnnotationSet annotations,
       ParameterAnnotationsList parameterAnnotationsList,
       Code code,
-      boolean d8R8Synthesized) {
-    this(method, accessFlags, annotations, parameterAnnotationsList, code, -1, d8R8Synthesized);
-  }
-
-  public DexEncodedMethod(
-      DexMethod method,
-      MethodAccessFlags accessFlags,
-      DexAnnotationSet annotations,
-      ParameterAnnotationsList parameterAnnotationsList,
-      Code code,
+      boolean d8R8Synthesized,
       int classFileVersion,
-      boolean d8R8Synthesized) {
+      boolean deprecated) {
     super(annotations);
     this.method = method;
     this.accessFlags = accessFlags;
+    this.deprecated = deprecated;
     this.parameterAnnotationsList = parameterAnnotationsList;
     this.code = code;
     this.classFileVersion = classFileVersion;
@@ -270,6 +282,10 @@
     assert parameterAnnotationsList != null;
   }
 
+  public boolean isDeprecated() {
+    return deprecated;
+  }
+
   public void hashSyntheticContent(Hasher hasher) {
     // Method holder does not contribute to the synthetic hash (it is freely chosen).
     // Method name does not contribute to the synthetic hash (it is freely chosen).
@@ -1511,8 +1527,8 @@
               annotations,
               parameterAnnotations,
               code,
-              classFileVersion,
-              d8R8Synthesized);
+              d8R8Synthesized,
+              classFileVersion);
       result.setKotlinMemberInfo(kotlinMemberInfo);
       result.compilationState = compilationState;
       result.optimizationInfo = optimizationInfo;
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index ca7a264..68da447 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -45,6 +45,7 @@
   private final ProgramResource.Kind originKind;
   private final Collection<DexProgramClass> synthesizedFrom;
   private int initialClassFileVersion = -1;
+  private boolean deprecated = false;
   private KotlinClassLevelInfo kotlinInfo = NO_KOTLIN_INFO;
 
   private final ChecksumSupplier checksumSupplier;
@@ -516,6 +517,14 @@
     return initialClassFileVersion;
   }
 
+  public void setDeprecated() {
+    deprecated = true;
+  }
+
+  public boolean isDeprecated() {
+    return deprecated;
+  }
+
   /**
    * Is the class reachability sensitive.
    *
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 2b38f4f..ea58f8d 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.AsmUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FieldSignatureEquivalence;
@@ -195,6 +196,7 @@
 
     // DexClass data.
     private int version;
+    private boolean deprecated;
     private DexType type;
     private ClassAccessFlags accessFlags;
     private DexType superType;
@@ -301,6 +303,7 @@
       if (InternalOptions.SUPPORTED_CF_MAJOR_VERSION < getMajorVersion()) {
         throw new CompilationError("Unsupported class file version: " + getMajorVersion(), origin);
       }
+      this.deprecated = AsmUtils.isDeprecated(access);
       accessFlags = ClassAccessFlags.fromCfAccessFlags(cleanAccessFlags(access));
       type = application.getTypeFromName(name);
       // Check if constraints from
@@ -449,6 +452,9 @@
       if (clazz.isProgramClass()) {
         DexProgramClass programClass = clazz.asProgramClass();
         programClass.setInitialClassFileVersion(version);
+        if (deprecated) {
+          programClass.setDeprecated();
+        }
       }
       classConsumer.accept(clazz);
     }
@@ -587,7 +593,9 @@
         DexAnnotationSet annotationSet =
             createAnnotationSet(annotations, parent.application.options);
         DexValue staticValue = flags.isStatic() ? getStaticValue(value, dexField.type) : null;
-        DexEncodedField field = new DexEncodedField(dexField, flags, annotationSet, staticValue);
+        DexEncodedField field =
+            new DexEncodedField(
+                dexField, flags, annotationSet, staticValue, AsmUtils.isDeprecated(access));
         if (flags.isStatic()) {
           parent.staticFields.add(field);
         } else {
@@ -662,6 +670,7 @@
     private List<DexValue> parameterFlags = null;
     final DexMethod method;
     final MethodAccessFlags flags;
+    final boolean deprecated;
     Code code = null;
 
     public CreateMethodVisitor(int access, String name, String desc, String signature,
@@ -671,6 +680,7 @@
       this.parent = parent;
       this.method = parent.application.getMethod(parent.type, name, desc);
       this.flags = createMethodAccessFlags(name, access);
+      this.deprecated = AsmUtils.isDeprecated(access);
       parameterCount = DescriptorUtils.getArgumentCount(desc);
       if (exceptions != null && exceptions.length > 0) {
         DexValue[] values = new DexValue[exceptions.length];
@@ -813,7 +823,9 @@
               createAnnotationSet(annotations, options),
               parameterAnnotationsList,
               code,
-              parent.version);
+              false,
+              parent.version,
+              deprecated);
       Wrapper<DexMethod> signature = MethodSignatureEquivalence.get().wrap(method);
       if (parent.methodSignatures.add(signature)) {
         parent.hasReachabilitySensitiveMethod |= isReachabilitySensitive();
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index f783b63..e0aace9 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -212,8 +212,7 @@
 
   @Override
   public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
-    return verifyFrames(asCfCode(), method.getDefinition(), appView, origin, true)
-        .buildIR(method, appView, origin);
+    return asCfCode().buildIR(method, appView, origin);
   }
 
   @Override
@@ -225,12 +224,7 @@
       Position callerPosition,
       Origin origin,
       MethodProcessor methodProcessor) {
-    return verifyFrames(
-            asCfCode(),
-            method.getDefinition(),
-            appView,
-            origin,
-            methodProcessor.shouldApplyCodeRewritings(method))
+    return asCfCode()
         .buildInliningIR(
             context,
             method,
@@ -241,20 +235,6 @@
             methodProcessor);
   }
 
-  private CfCode verifyFrames(
-      CfCode cfCode,
-      DexEncodedMethod method,
-      AppView<?> appView,
-      Origin origin,
-      boolean shouldApplyCodeRewritings) {
-    if (!cfCode.verifyFrames(method, appView, origin, shouldApplyCodeRewritings)) {
-      ArrayList<CfInstruction> newInstructions = new ArrayList<>(cfCode.getInstructions());
-      newInstructions.removeIf(CfInstruction::isFrame);
-      cfCode.setInstructions(newInstructions);
-    }
-    return cfCode;
-  }
-
   @Override
   public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
     asCfCode().registerCodeReferences(method, registry);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
index 2f3c068..37e1690 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -165,8 +165,8 @@
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
             synthesizedCode,
-            classFileVersion,
-            true);
+            true,
+            classFileVersion);
 
     if (isTrivialMerge()) {
       // The constructor does not require the additional argument, just map it like a regular
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index fd998a5..c8f2f6d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -170,8 +170,8 @@
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
             synthesizedCode,
-            classFileVersion,
-            true);
+            true,
+            classFileVersion);
 
     // Map each old method to the newly synthesized method in the graph lens.
     for (ProgramMethod oldMethod : methods) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index cd5cb77..c1de847 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -123,7 +123,7 @@
       operands.add(operand);
     }
 
-    if (readType != RegisterReadType.NORMAL) {
+    if (readType == RegisterReadType.DEBUG) {
       for (Value operand : operands) {
         TypeElement type = operand.getType();
         ValueTypeConstraint constraint = TypeConstraintResolver.constraintForType(type);
@@ -397,7 +397,7 @@
     return operands.indexOf(usedValue) == operands.lastIndexOf(usedValue);
   }
 
-  public DexType computeVerificationType(TypeVerificationHelper helper) {
+  public DexType computeVerificationType(AppView<?> appView, TypeVerificationHelper helper) {
     assert outType().isObject();
     Set<DexType> operandTypes = new HashSet<>(operands.size());
     for (Value operand : operands) {
@@ -447,4 +447,38 @@
     }
     return result;
   }
+
+  // TODO(b/169120386) This class is added to ensure we do not narrow or widen phi's in D8 when
+  //  having stack map information. It should be removed when we are certain to never widen or
+  //  narrowing phi's in D8.
+  public static class StackMapPhi extends Phi {
+
+    public StackMapPhi(
+        int number,
+        BasicBlock block,
+        TypeElement type,
+        DebugLocalInfo local,
+        RegisterReadType readType) {
+      super(number, block, type, local, readType);
+    }
+
+    @Override
+    public DexType computeVerificationType(AppView<?> appView, TypeVerificationHelper helper) {
+      assert !appView.enableWholeProgramOptimizations();
+      if (type.isPrimitiveType()) {
+        return type.asPrimitiveType().toDexType(appView.dexItemFactory());
+      } else if (type.isArrayType()) {
+        return type.asArrayType().toDexType(appView.dexItemFactory());
+      } else {
+        assert type.isClassType();
+        return type.asClassType().getClassType();
+      }
+    }
+
+    @Override
+    public TypeElement computePhiType(AppView<?> appView) {
+      assert !appView.enableWholeProgramOptimizations();
+      return type;
+    }
+  }
 }
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 c2a63d4..b842c63 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
@@ -1027,7 +1027,7 @@
   public void widening(AppView<?> appView, TypeElement newType) {
     // During WIDENING (due to fix-point iteration), type update is monotonically upwards,
     //   i.e., towards something wider.
-    assert this.type.lessThanOrEqual(newType, appView)
+    assert skipWideningOrNarrowingCheck(appView) || this.type.lessThanOrEqual(newType, appView)
         : "During WIDENING, "
             + newType
             + " < "
@@ -1040,9 +1040,7 @@
   public void narrowing(AppView<?> appView, TypeElement newType) {
     // During NARROWING (e.g., after inlining), type update is monotonically downwards,
     //   i.e., towards something narrower, with more specific type info.
-    assert (!appView.options().testing.enableNarrowingChecksInD8
-                && !appView.enableWholeProgramOptimizations())
-            || !this.type.strictlyLessThan(newType, appView)
+    assert skipWideningOrNarrowingCheck(appView) || !this.type.strictlyLessThan(newType, appView)
         : "During NARROWING, "
             + type
             + " < "
@@ -1052,6 +1050,12 @@
     setType(newType);
   }
 
+  private boolean skipWideningOrNarrowingCheck(AppView<?> appView) {
+    // TODO(b/169120386): We should not check widening or narrowing when in D8 with valid type-info.
+    return !appView.options().testing.enableNarrowAndWideningingChecksInD8
+        && !appView.enableWholeProgramOptimizations();
+  }
+
   public BasicBlock getBlock() {
     return definition.getBlock();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 36c4111..6deea80 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -20,6 +20,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
+import com.android.tools.r8.graph.CfCode.StackMapStatus;
+import com.android.tools.r8.graph.CfCodeDiagnostics;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DebugLocalInfo.PrintLevel;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -30,8 +32,10 @@
 import com.android.tools.r8.ir.code.CanonicalPositions;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Monitor;
+import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.code.ValueTypeConstraint;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.CfState.Snapshot;
 import com.android.tools.r8.ir.conversion.IRBuilder.BlockInfo;
@@ -210,6 +214,7 @@
   private TryHandlerList cachedTryHandlerList;
   private LocalVariableList cachedLocalVariableList;
   private int currentInstructionIndex;
+  private int currentBlockIndex;
   private boolean inPrelude;
   private Int2ReferenceMap<DebugLocalInfo> incomingLocals;
   private Int2ReferenceMap<DebugLocalInfo> outgoingLocals;
@@ -526,6 +531,7 @@
           incomingState.get(instructionIndex),
           instructionIndex == 0,
           getCanonicalDebugPositionAtOffset(instructionIndex));
+      currentBlockIndex = currentInstructionIndex;
     }
 
     assert currentBlockInfo != null;
@@ -613,8 +619,13 @@
     for (FrameType frameType : frame.getStack()) {
       stack[index++] = convertUninitialized(frameType);
     }
-    state.setStateFromFrame(
-        locals, stack, getCanonicalDebugPositionAtOffset(currentInstructionIndex));
+    // TODO(b/169135126) Assert that all values are precise.
+    Snapshot snapshot =
+        state.setStateFromFrame(
+            locals, stack, getCanonicalDebugPositionAtOffset(currentInstructionIndex));
+    // Update the incoming state as well with precise information.
+    assert incomingState.get(currentBlockIndex) != null;
+    incomingState.put(currentBlockIndex, snapshot);
   }
 
   private DexType convertUninitialized(FrameType type) {
@@ -659,6 +670,74 @@
   }
 
   @Override
+  public DexType getPhiTypeForBlock(
+      int register, int blockOffset, ValueTypeConstraint constraint, RegisterReadType readType) {
+    assert code.getStackMapStatus() != StackMapStatus.NOT_VERIFIED;
+    if (code.getStackMapStatus() == StackMapStatus.INVALID_OR_NOT_PRESENT) {
+      return null;
+    }
+    // We should be able to find the a snapshot at the block-offset:
+    Snapshot snapshot = incomingState.get(blockOffset);
+    if (snapshot == null) {
+      appView
+          .options()
+          .reporter
+          .warning(
+              new CfCodeDiagnostics(
+                  origin,
+                  method.getReference(),
+                  "Could not find stack map for block at offset "
+                      + blockOffset
+                      + ". This is most likely due to invalid"
+                      + " stack maps in input."));
+      return null;
+    }
+    // TODO(b/169135126) Assert that all values are precise.
+    Slot slot =
+        Slot.isStackSlot(register)
+            ? snapshot.getStack(Slot.stackPosition(register))
+            : snapshot.getLocal(register);
+    if (slot == null) {
+      if (readType == RegisterReadType.DEBUG) {
+        DebugLocalInfo incomingLocalAtBlock = getIncomingLocalAtBlock(register, blockOffset);
+        if (incomingLocalAtBlock != null) {
+          return incomingLocalAtBlock.type;
+        }
+        // TODO(b/b/169137397): The local ranges are not defined on the block. We should investigate
+        //   the impact of this when debugging. For now, make a final attempt at finding a local
+        //   variable with specified register.
+        List<LocalVariableInfo> localVariablesWithRegister = new ArrayList<>();
+        for (LocalVariableInfo variable : localVariables) {
+          if (variable.getIndex() == register) {
+            localVariablesWithRegister.add(variable);
+          }
+        }
+        if (localVariablesWithRegister.size() == 1) {
+          return localVariablesWithRegister.get(0).getLocal().type;
+        }
+      }
+      appView
+          .options()
+          .reporter
+          .warning(
+              new CfCodeDiagnostics(
+                  origin,
+                  method.getReference(),
+                  "Could not find phi type for register "
+                      + register
+                      + ". This is most likely due to invalid stack maps in input."));
+      return null;
+    }
+    if (slot.isPrecise()) {
+      return slot.preciseType;
+    }
+    // TODO(b/169135126): We should be able to remove this when having valid stack maps.
+    return slot.type.isObject()
+        ? appView.dexItemFactory().objectType
+        : slot.type.toPrimitiveType().toDexType(appView.dexItemFactory());
+  }
+
+  @Override
   public DebugLocalInfo getIncomingLocal(int register) {
     return isCurrentlyGeneratingMethodSynchronization() ? null : incomingLocals.get(register);
   }
@@ -774,6 +853,11 @@
   }
 
   @Override
+  public boolean hasValidTypesFromStackMap() {
+    return code.getStackMapStatus() == StackMapStatus.VALID;
+  }
+
+  @Override
   public Position getCanonicalDebugPositionAtOffset(int offset) {
     if (offset == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
       return canonicalPositions.getExceptionalExitPosition(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfState.java b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
index 0f111de..353faac 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
@@ -17,6 +17,10 @@
 
     public abstract ValueType getImprecise();
 
+    public boolean isPrecise() {
+      return false;
+    }
+
     private static class Precise extends SlotType {
       private final DexType type;
 
@@ -38,6 +42,11 @@
       public String toString() {
         return "Precise(" + type + ")";
       }
+
+      @Override
+      public boolean isPrecise() {
+        return true;
+      }
     }
 
     private static class Imprecise extends SlotType {
@@ -90,9 +99,11 @@
     this.position = position;
   }
 
-  public void setStateFromFrame(DexType[] locals, DexType[] stack, Position position) {
+  public BaseSnapshot setStateFromFrame(DexType[] locals, DexType[] stack, Position position) {
     assert current == null || stackHeight() == stack.length;
-    current = new BaseSnapshot(locals, stack, position);
+    BaseSnapshot newSnapShot = new BaseSnapshot(locals, stack, position);
+    this.current = newSnapShot;
+    return newSnapShot;
   }
 
   public void merge(Snapshot snapshot) {
@@ -233,10 +244,6 @@
     public final DexType preciseType;
     private final SlotType slotType;
 
-    private Slot(int register, DexType preciseType) {
-      this(register, new SlotType.Precise(preciseType));
-    }
-
     private Slot(int register, SlotType type) {
       this.register = register;
       this.slotType = type;
@@ -249,6 +256,11 @@
     }
 
     private int stackPosition() {
+      return stackPosition(register);
+    }
+
+    public static int stackPosition(int register) {
+      assert isStackSlot(register);
       assert register >= STACK_OFFSET;
       return register - STACK_OFFSET;
     }
@@ -259,6 +271,18 @@
           ? register + "=" + slotType
           : "s" + (register - STACK_OFFSET) + "=" + slotType;
     }
+
+    public boolean isStackSlot() {
+      return isStackSlot(register);
+    }
+
+    public static boolean isStackSlot(int register) {
+      return register >= STACK_OFFSET;
+    }
+
+    public boolean isPrecise() {
+      return slotType.isPrecise();
+    }
   }
 
   public abstract static class Snapshot {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index f2da1ac..6382b86 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -14,6 +14,7 @@
 import static com.android.tools.r8.ir.analysis.type.TypeElement.getNull;
 import static com.android.tools.r8.ir.analysis.type.TypeElement.getSingle;
 import static com.android.tools.r8.ir.analysis.type.TypeElement.getWide;
+import static org.objectweb.asm.Opcodes.V1_8;
 
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.CompilationError;
@@ -97,6 +98,7 @@
 import com.android.tools.r8.ir.code.Or;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
+import com.android.tools.r8.ir.code.Phi.StackMapPhi;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Rem;
 import com.android.tools.r8.ir.code.Return;
@@ -176,10 +178,6 @@
     }
   }
 
-  public TypeElement getTypeLattice(DexType type, Nullability nullability) {
-    return TypeElement.fromDexType(type, nullability, appView);
-  }
-
   // SSA construction uses a worklist of basic blocks reachable from the entry and their
   // instruction offsets.
   private static class WorklistItem {
@@ -415,14 +413,15 @@
   // Lazily populated list of local values that are referenced without being actually defined.
   private Int2ReferenceMap<List<Value>> uninitializedDebugLocalValues = null;
 
-  private int nextBlockNumber = 0;
-
   // Flag indicating if any instructions have imprecise internal types (eg, int|float member types)
   private List<ImpreciseMemberTypeInstruction> impreciseInstructions = null;
 
   // Flag indicating if any values have imprecise types.
   private boolean hasImpreciseValues = false;
 
+  // Flag indicating incorrect reading of stack map phi types.
+  private boolean hasIncorrectStackMapTypes = false;
+
   // Information about which kinds of instructions that may be present in the IR. This information
   // is sound (i.e., if the IR has a const-string instruction then metadata.mayHaveConstString()
   // returns true) but not necessarily complete (i.e., if metadata.mayHaveConstString() returns true
@@ -720,9 +719,13 @@
       // In DEX we may need to constrain all values and instructions to precise types.
       assert source instanceof DexSourceCode;
       new TypeConstraintResolver(appView, this).resolve(impreciseInstructions, ir);
-    } else {
+    } else if (!canUseStackMapTypes() || hasIncorrectStackMapTypes) {
+      // TODO(b/169137397): We may have ended up generating StackMapPhi's before concluding
+      //  having incorrect stack map types. Figure out a way to clean that up.
       new TypeAnalysis(appView).widening(ir);
     }
+    // TODO(b/169137397): If we have canUseStackMapTypes() && !hasIncorrectStackMapTypes we should
+    //  have that all phi's are stack-map phis.
 
     // Update the IR code if collected call site optimization info has something useful.
     // While aggregation of parameter information at call sites would be more precise than static
@@ -747,6 +750,11 @@
     return ir;
   }
 
+  public boolean canUseStackMapTypes() {
+    // TODO(b/168592290): See if we can get using stack map types to work with R8.
+    return !appView.enableWholeProgramOptimizations() && source.hasValidTypesFromStackMap();
+  }
+
   public void constrainType(Value value, ValueTypeConstraint constraint) {
     value.constrainType(constraint, method.getReference(), origin, appView.options().reporter);
   }
@@ -1123,7 +1131,19 @@
   public void addArrayGet(MemberType type, int dest, int array, int index) {
     Value in1 = readRegister(array, ValueTypeConstraint.OBJECT);
     Value in2 = readRegister(index, ValueTypeConstraint.INT);
-    TypeElement typeLattice = fromMemberType(type);
+    TypeElement typeLattice;
+    if (type == MemberType.OBJECT && canUseStackMapTypes()) {
+      if (in1.getType().isNullType()) {
+        typeLattice = TypeElement.getNull();
+      } else if (in1.getType().isArrayType()) {
+        typeLattice = in1.getType().asArrayType().getMemberType();
+      } else {
+        assert in1.getType().isBottom() && hasIncorrectStackMapTypes;
+        typeLattice = fromMemberType(type);
+      }
+    } else {
+      typeLattice = fromMemberType(type);
+    }
     Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
     ArrayGet instruction = new ArrayGet(type, out, in1, in2);
     assert instruction.instructionTypeCanThrow();
@@ -2164,9 +2184,28 @@
         value = getUninitializedDebugLocalValue(register, constraint);
       } else {
         DebugLocalInfo local = getIncomingLocalAtBlock(register, block);
-        TypeElement phiType = TypeConstraintResolver.typeForConstraint(constraint);
-        hasImpreciseValues |= !phiType.isPreciseType();
-        Phi phi = new Phi(valueNumberGenerator.next(), block, phiType, local, readType);
+        TypeElement constrainedType = TypeConstraintResolver.typeForConstraint(constraint);
+        hasImpreciseValues |= !constrainedType.isPreciseType();
+        Phi phi = null;
+        if (canUseStackMapTypes() && !hasIncorrectStackMapTypes) {
+          DexType phiTypeForBlock =
+              source.getPhiTypeForBlock(register, offsets.getInt(block), constraint, readType);
+          if (phiTypeForBlock != null) {
+            phi =
+                new StackMapPhi(
+                    valueNumberGenerator.next(),
+                    block,
+                    TypeElement.fromDexType(phiTypeForBlock, Nullability.maybeNull(), appView),
+                    local,
+                    readType);
+          } else {
+            assert method.getDefinition().getClassFileVersion() < V1_8;
+            hasIncorrectStackMapTypes = true;
+          }
+        }
+        if (phi == null) {
+          phi = new Phi(valueNumberGenerator.next(), block, constrainedType, local, readType);
+        }
         if (!block.isSealed()) {
           block.addIncompletePhi(register, phi, readingEdge);
           value = phi;
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 100131c..8db8c01 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
@@ -1132,9 +1132,13 @@
       return false;
     }
     boolean didDesugar = false;
+    Supplier<AppInfoWithClassHierarchy> lazyAppInfo =
+        Suppliers.memoize(() -> appView.appInfoForDesugaring());
     if (lambdaRewriter != null) {
-      AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
-      didDesugar |= lambdaRewriter.desugarLambdas(method, appInfo) > 0;
+      didDesugar |= lambdaRewriter.desugarLambdas(method, lazyAppInfo.get()) > 0;
+    }
+    if (backportedMethodRewriter != null) {
+      didDesugar |= backportedMethodRewriter.desugar(method, lazyAppInfo.get());
     }
     return didDesugar;
   }
@@ -1420,12 +1424,6 @@
       timing.end();
     }
 
-    if (backportedMethodRewriter != null) {
-      timing.begin("Rewrite backport methods");
-      backportedMethodRewriter.desugar(code);
-      timing.end();
-    }
-
     timing.begin("Desugar string concat");
     stringConcatRewriter.desugarStringConcats(method.method, code);
     timing.end();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
index df2b663..e3b69b7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
@@ -5,8 +5,11 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueTypeConstraint;
 
 /**
  * Abstraction of the input/source code for the IRBuilder.
@@ -23,6 +26,11 @@
 
   DebugLocalInfo getIncomingLocalAtBlock(int register, int blockOffset);
 
+  default DexType getPhiTypeForBlock(
+      int register, int blockOffset, ValueTypeConstraint constraint, RegisterReadType readType) {
+    return null;
+  }
+
   DebugLocalInfo getIncomingLocal(int register);
 
   DebugLocalInfo getOutgoingLocal(int register);
@@ -71,4 +79,8 @@
   boolean verifyRegister(int register);
   boolean verifyCurrentInstructionCanThrow();
   boolean verifyLocalInScope(DebugLocalInfo local);
+
+  default boolean hasValidTypesFromStackMap() {
+    return false;
+  }
 }
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 da1a799..1f41aa4 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
@@ -4,27 +4,25 @@
 
 package com.android.tools.r8.ir.desugar;
 
-
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexApplication;
 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.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
 import com.android.tools.r8.ir.desugar.backports.BooleanMethodRewrites;
@@ -40,19 +38,19 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Queue;
-import java.util.Set;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
+import org.objectweb.asm.Opcodes;
 
 public final class BackportedMethodRewriter {
 
@@ -111,29 +109,34 @@
     BackportedMethods.registerSynthesizedCodeReferences(options.itemFactory);
   }
 
-  public void desugar(IRCode code) {
+  public boolean desugar(ProgramMethod method, AppInfoWithClassHierarchy appInfo) {
     if (!enabled) {
-      return; // Nothing to do!
+      return false;
     }
-    Set<Value> affectedValues = Sets.newIdentityHashSet();
-    InstructionListIterator iterator = code.instructionListIterator();
+    CfCode code = method.getDefinition().getCode().asCfCode();
+    ListIterator<CfInstruction> iterator = code.getInstructions().listIterator();
+    boolean replaced = false;
     while (iterator.hasNext()) {
-      Instruction instruction = iterator.next();
-      if (!instruction.isInvokeMethod()) {
+      CfInvoke invoke = iterator.next().asInvoke();
+      if (invoke == null) {
         continue;
       }
-
-      InvokeMethod invoke = instruction.asInvokeMethod();
-      DexMethod invokedMethod = invoke.getInvokedMethod();
+      DexMethod invokedMethod = invoke.getMethod();
       MethodProvider provider = getMethodProviderOrNull(invokedMethod);
       if (provider != null) {
+        if (!replaced) {
+          // Create mutable instructions on first write.
+          ArrayList<CfInstruction> mutableInstructions = new ArrayList<>(code.getInstructions());
+          code.setInstructions(mutableInstructions);
+          iterator = mutableInstructions.listIterator(iterator.previousIndex());
+          iterator.next();
+        }
         provider.rewriteInvoke(
-            invoke, iterator, code, appView, affectedValues, synthesizedMethods::add);
+            invoke, iterator, method.getHolder(), appInfo, synthesizedMethods::add);
+        replaced = true;
       }
     }
-    if (!affectedValues.isEmpty()) {
-      new TypeAnalysis(appView).narrowing(affectedValues);
-    }
+    return replaced;
   }
 
   public void processSynthesizedClasses(IRConverter converter, ExecutorService executor)
@@ -248,7 +251,7 @@
       name = factory.createString("compare");
       proto = factory.createProto(factory.intType, factory.longType, factory.longType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, LongMethodRewrites::rewriteCompare));
+      addProvider(new InvokeRewriter(method, LongMethodRewrites.rewriteCompare()));
 
       // Boolean
       type = factory.boxedBooleanType;
@@ -293,7 +296,7 @@
       name = factory.createString("hash");
       proto = factory.createProto(factory.intType, factory.objectArrayType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, ObjectsMethodRewrites::rewriteToArraysHashCode));
+      addProvider(new InvokeRewriter(method, ObjectsMethodRewrites.rewriteToArraysHashCode()));
 
       // int Objects.hashCode(Object o)
       name = factory.createString("hashCode");
@@ -303,7 +306,7 @@
 
       // T Objects.requireNonNull(T obj)
       method = factory.objectsMethods.requireNonNull;
-      addProvider(new InvokeRewriter(method, ObjectsMethodRewrites::rewriteRequireNonNull));
+      addProvider(new InvokeRewriter(method, ObjectsMethodRewrites.rewriteRequireNonNull()));
 
       // T Objects.requireNonNull(T obj, String message)
       name = factory.createString("requireNonNull");
@@ -360,7 +363,7 @@
       DexString name = factory.createString("hashCode");
       DexProto proto = factory.createProto(factory.intType, factory.byteType);
       DexMethod method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteAsIdentity));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteAsIdentity()));
 
       // Short
       type = factory.boxedShortType;
@@ -368,7 +371,7 @@
       name = factory.createString("hashCode");
       proto = factory.createProto(factory.intType, factory.shortType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteAsIdentity));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteAsIdentity()));
 
       // Integer
       type = factory.boxedIntType;
@@ -377,25 +380,25 @@
       name = factory.createString("hashCode");
       proto = factory.createProto(factory.intType, factory.intType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteAsIdentity));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteAsIdentity()));
 
       // int Integer.max(int a, int b)
       name = factory.createString("max");
       proto = factory.createProto(factory.intType, factory.intType, factory.intType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // int Integer.min(int a, int b)
       name = factory.createString("min");
       proto = factory.createProto(factory.intType, factory.intType, factory.intType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // int Integer.sum(int a, int b)
       name = factory.createString("sum");
       proto = factory.createProto(factory.intType, factory.intType, factory.intType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToAddInstruction));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToAddInstruction()));
 
       // Double
       type = factory.boxedDoubleType;
@@ -410,19 +413,19 @@
       name = factory.createString("max");
       proto = factory.createProto(factory.doubleType, factory.doubleType, factory.doubleType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // double Double.min(double a, double b)
       name = factory.createString("min");
       proto = factory.createProto(factory.doubleType, factory.doubleType, factory.doubleType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // double Double.sum(double a, double b)
       name = factory.createString("sum");
       proto = factory.createProto(factory.doubleType, factory.doubleType, factory.doubleType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToAddInstruction));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToAddInstruction()));
 
       // boolean Double.isFinite(double a)
       name = factory.createString("isFinite");
@@ -437,25 +440,25 @@
       name = factory.createString("hashCode");
       proto = factory.createProto(factory.intType, factory.floatType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, FloatMethodRewrites::rewriteHashCode));
+      addProvider(new InvokeRewriter(method, FloatMethodRewrites.rewriteHashCode()));
 
       // float Float.max(float a, float b)
       name = factory.createString("max");
       proto = factory.createProto(factory.floatType, factory.floatType, factory.floatType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // float Float.min(float a, float b)
       name = factory.createString("min");
       proto = factory.createProto(factory.floatType, factory.floatType, factory.floatType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // float Float.sum(float a, float b)
       name = factory.createString("sum");
       proto = factory.createProto(factory.floatType, factory.floatType, factory.floatType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToAddInstruction));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToAddInstruction()));
 
       // boolean Float.isFinite(float a)
       name = factory.createString("isFinite");
@@ -476,19 +479,19 @@
       name = factory.createString("logicalAnd");
       proto = factory.createProto(factory.booleanType, factory.booleanType, factory.booleanType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, BooleanMethodRewrites::rewriteLogicalAnd));
+      addProvider(new InvokeRewriter(method, BooleanMethodRewrites.rewriteLogicalAnd()));
 
       // boolean Boolean.logicalOr(boolean a, boolean b)
       name = factory.createString("logicalOr");
       proto = factory.createProto(factory.booleanType, factory.booleanType, factory.booleanType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, BooleanMethodRewrites::rewriteLogicalOr));
+      addProvider(new InvokeRewriter(method, BooleanMethodRewrites.rewriteLogicalOr()));
 
       // boolean Boolean.logicalXor(boolean a, boolean b)
       name = factory.createString("logicalXor");
       proto = factory.createProto(factory.booleanType, factory.booleanType, factory.booleanType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, BooleanMethodRewrites::rewriteLogicalXor));
+      addProvider(new InvokeRewriter(method, BooleanMethodRewrites.rewriteLogicalXor()));
 
       // Long
       type = factory.boxedLongType;
@@ -503,19 +506,19 @@
       name = factory.createString("max");
       proto = factory.createProto(factory.longType, factory.longType, factory.longType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // long Long.min(long a, long b)
       name = factory.createString("min");
       proto = factory.createProto(factory.longType, factory.longType, factory.longType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // long Long.sum(long a, long b)
       name = factory.createString("sum");
       proto = factory.createProto(factory.longType, factory.longType, factory.longType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToAddInstruction));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToAddInstruction()));
 
       // Character
       type = factory.boxedCharType;
@@ -524,7 +527,7 @@
       name = factory.createString("hashCode");
       proto = factory.createProto(factory.intType, factory.charType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteAsIdentity));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteAsIdentity()));
 
       // Objects
       type = factory.objectsType;
@@ -916,7 +919,7 @@
         method = factory.createMethod(type, proto, name);
         addProvider(
             i == 0
-                ? new InvokeRewriter(method, CollectionMethodRewrites::rewriteListOfEmpty)
+                ? new InvokeRewriter(method, CollectionMethodRewrites.rewriteListOfEmpty())
                 : new MethodGenerator(
                     method,
                     (options, methodArg) ->
@@ -937,7 +940,7 @@
         method = factory.createMethod(type, proto, name);
         addProvider(
             i == 0
-                ? new InvokeRewriter(method, CollectionMethodRewrites::rewriteSetOfEmpty)
+                ? new InvokeRewriter(method, CollectionMethodRewrites.rewriteSetOfEmpty())
                 : new MethodGenerator(
                     method,
                     (options, methodArg) ->
@@ -957,7 +960,7 @@
         method = factory.createMethod(type, proto, name);
         addProvider(
             i == 0
-                ? new InvokeRewriter(method, CollectionMethodRewrites::rewriteMapOfEmpty)
+                ? new InvokeRewriter(method, CollectionMethodRewrites.rewriteMapOfEmpty())
                 : new MethodGenerator(
                     method,
                     (options, methodArg) ->
@@ -1224,10 +1227,10 @@
           };
       MethodInvokeRewriter[] rewriters =
           new MethodInvokeRewriter[] {
-            OptionalMethodRewrites::rewriteOrElseGet,
-            OptionalMethodRewrites::rewriteDoubleOrElseGet,
-            OptionalMethodRewrites::rewriteLongOrElseGet,
-            OptionalMethodRewrites::rewriteIntOrElseGet,
+            OptionalMethodRewrites.rewriteOrElseGet(),
+            OptionalMethodRewrites.rewriteDoubleOrElseGet(),
+            OptionalMethodRewrites.rewriteLongOrElseGet(),
+            OptionalMethodRewrites.rewriteIntOrElseGet(),
           };
       DexString name = factory.createString("orElseThrow");
       for (int i = 0; i < optionalTypes.length; i++) {
@@ -1294,11 +1297,10 @@
     }
 
     public abstract void rewriteInvoke(
-        InvokeMethod invoke,
-        InstructionListIterator iterator,
-        IRCode code,
-        AppView<?> appView,
-        Set<Value> affectedValues,
+        CfInvoke invoke,
+        ListIterator<CfInstruction> iterator,
+        DexProgramClass context,
+        AppInfoWithClassHierarchy appInfo,
         Consumer<ProgramMethod> registerSynthesizedMethod);
   }
 
@@ -1313,14 +1315,12 @@
 
     @Override
     public void rewriteInvoke(
-        InvokeMethod invoke,
-        InstructionListIterator iterator,
-        IRCode code,
-        AppView<?> appView,
-        Set<Value> affectedValues,
+        CfInvoke invoke,
+        ListIterator<CfInstruction> iterator,
+        DexProgramClass context,
+        AppInfoWithClassHierarchy appInfo,
         Consumer<ProgramMethod> registerSynthesizedMethod) {
-      rewriter.rewrite(invoke, iterator, appView.dexItemFactory(), affectedValues);
-      assert code.isConsistentSSA();
+      rewriter.rewrite(invoke, iterator, appInfo.dexItemFactory());
     }
   }
 
@@ -1341,34 +1341,33 @@
 
     @Override
     public void rewriteInvoke(
-        InvokeMethod invoke,
-        InstructionListIterator iterator,
-        IRCode code,
-        AppView<?> appView,
-        Set<Value> affectedValues,
+        CfInvoke invoke,
+        ListIterator<CfInstruction> iterator,
+        DexProgramClass context,
+        AppInfoWithClassHierarchy appInfo,
         Consumer<ProgramMethod> registerSynthesizedMethod) {
-      ProgramMethod method =
-          appView
-              .getSyntheticItems()
-              .createMethod(
-                  code.context().getHolder(),
-                  appView.dexItemFactory(),
-                  builder ->
-                      builder
-                          .setProto(getProto(appView.dexItemFactory()))
-                          .setAccessFlags(
-                              MethodAccessFlags.fromSharedAccessFlags(
-                                  Constants.ACC_PUBLIC
-                                      | Constants.ACC_STATIC
-                                      | Constants.ACC_SYNTHETIC,
-                                  false))
-                          .setCode(
-                              methodSig -> generateTemplateMethod(appView.options(), methodSig)));
-
-      iterator.replaceCurrentInstruction(
-          new InvokeStatic(method.getReference(), invoke.outValue(), invoke.inValues()));
-
+      ProgramMethod method = getSyntheticMethod(context, appInfo);
       registerSynthesizedMethod.accept(method);
+      iterator.remove();
+      iterator.add(new CfInvoke(Opcodes.INVOKESTATIC, method.getReference(), false));
+    }
+
+    private ProgramMethod getSyntheticMethod(
+        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+      return appInfo
+          .getSyntheticItems()
+          .createMethod(
+              context,
+              appInfo.dexItemFactory(),
+              builder ->
+                  builder
+                      .setProto(getProto(appInfo.dexItemFactory()))
+                      .setAccessFlags(
+                          MethodAccessFlags.fromSharedAccessFlags(
+                              Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC,
+                              false))
+                      .setCode(
+                          methodSig -> generateTemplateMethod(appInfo.app().options, methodSig)));
     }
 
     public DexProto getProto(DexItemFactory itemFactory) {
@@ -1400,16 +1399,30 @@
   }
 
   private interface TemplateMethodFactory {
-
     Code create(InternalOptions options, DexMethod method);
   }
 
-  private interface MethodInvokeRewriter {
+  public interface MethodInvokeRewriter {
 
-    void rewrite(
-        InvokeMethod invoke,
-        InstructionListIterator iterator,
-        DexItemFactory factory,
-        Set<Value> affectedValues);
+    CfInstruction rewriteSingle(CfInvoke invoke, DexItemFactory factory);
+
+    // Convenience wrapper since most rewrites are to a single instruction.
+    default void rewrite(
+        CfInvoke invoke, ListIterator<CfInstruction> iterator, DexItemFactory factory) {
+      iterator.remove();
+      iterator.add(rewriteSingle(invoke, factory));
+    }
+  }
+
+  public abstract static class FullMethodInvokeRewriter implements MethodInvokeRewriter {
+
+    @Override
+    public final CfInstruction rewriteSingle(CfInvoke invoke, DexItemFactory factory) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public abstract void rewrite(
+        CfInvoke invoke, ListIterator<CfInstruction> iterator, DexItemFactory factory);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java
index 90654b2..ec0a554 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java
@@ -4,52 +4,27 @@
 
 package com.android.tools.r8.ir.desugar.backports;
 
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.ir.code.And;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.cf.code.CfLogicalBinop;
+import com.android.tools.r8.cf.code.CfLogicalBinop.Opcode;
 import com.android.tools.r8.ir.code.NumericType;
-import com.android.tools.r8.ir.code.Or;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.Xor;
-import java.util.List;
-import java.util.Set;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
 
 public final class BooleanMethodRewrites {
-  public static void rewriteLogicalAnd(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    List<Value> inValues = invoke.inValues();
-    assert inValues.size() == 2;
 
-    iterator.replaceCurrentInstruction(
-        new And(NumericType.INT, invoke.outValue(), inValues.get(0), inValues.get(1)));
+  private static MethodInvokeRewriter createRewriter(CfLogicalBinop.Opcode op) {
+    return (invoke, factory) -> new CfLogicalBinop(op, NumericType.INT);
   }
 
-  public static void rewriteLogicalOr(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    List<Value> inValues = invoke.inValues();
-    assert inValues.size() == 2;
-
-    iterator.replaceCurrentInstruction(
-        new Or(NumericType.INT, invoke.outValue(), inValues.get(0), inValues.get(1)));
+  public static MethodInvokeRewriter rewriteLogicalAnd() {
+    return createRewriter(Opcode.And);
   }
 
-  public static void rewriteLogicalXor(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    List<Value> inValues = invoke.inValues();
-    assert inValues.size() == 2;
+  public static MethodInvokeRewriter rewriteLogicalOr() {
+    return createRewriter(Opcode.Or);
+  }
 
-    iterator.replaceCurrentInstruction(
-        new Xor(NumericType.INT, invoke.outValue(), inValues.get(0), inValues.get(1)));
+  public static MethodInvokeRewriter rewriteLogicalXor() {
+    return createRewriter(Opcode.Xor);
   }
 
   private BooleanMethodRewrites() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java
index 695f51f..64c94c5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java
@@ -4,51 +4,31 @@
 
 package com.android.tools.r8.ir.desugar.backports;
 
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.Value;
-import java.util.Collections;
-import java.util.Set;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import org.objectweb.asm.Opcodes;
 
 public final class CollectionMethodRewrites {
 
   private CollectionMethodRewrites() {}
 
-  public static void rewriteListOfEmpty(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    rewriteToCollectionMethod(invoke, iterator, factory, "emptyList");
+  public static MethodInvokeRewriter rewriteListOfEmpty() {
+    return rewriteToCollectionMethod("emptyList");
   }
 
-  public static void rewriteSetOfEmpty(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    rewriteToCollectionMethod(invoke, iterator, factory, "emptySet");
+  public static MethodInvokeRewriter rewriteSetOfEmpty() {
+    return rewriteToCollectionMethod("emptySet");
   }
 
-  public static void rewriteMapOfEmpty(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    rewriteToCollectionMethod(invoke, iterator, factory, "emptyMap");
+  public static MethodInvokeRewriter rewriteMapOfEmpty() {
+    return rewriteToCollectionMethod("emptyMap");
   }
 
-  private static void rewriteToCollectionMethod(InvokeMethod invoke,
-      InstructionListIterator iterator, DexItemFactory factory, String methodName) {
-    assert invoke.inValues().isEmpty();
-
-    DexMethod collectionsEmptyList =
-        factory.createMethod(factory.collectionsType, invoke.getInvokedMethod().proto, methodName);
-    InvokeStatic newInvoke =
-        new InvokeStatic(collectionsEmptyList, invoke.outValue(), Collections.emptyList());
-    iterator.replaceCurrentInstruction(newInvoke);
+  private static MethodInvokeRewriter rewriteToCollectionMethod(String methodName) {
+    return (invoke, factory) ->
+        new CfInvoke(
+            Opcodes.INVOKESTATIC,
+            factory.createMethod(factory.collectionsType, invoke.getMethod().proto, methodName),
+            false);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java
index a5d557d..8670d3b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java
@@ -4,25 +4,20 @@
 
 package com.android.tools.r8.ir.desugar.backports;
 
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.Value;
-import java.util.Set;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import org.objectweb.asm.Opcodes;
 
 public final class FloatMethodRewrites {
 
   private FloatMethodRewrites() {}
 
-  public static void rewriteHashCode(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    InvokeStatic mathInvoke = new InvokeStatic(
-        factory.createMethod(factory.boxedFloatType, invoke.getInvokedMethod().proto,
-            "floatToIntBits"), invoke.outValue(), invoke.inValues(), false);
-    iterator.replaceCurrentInstruction(mathInvoke);
+  public static MethodInvokeRewriter rewriteHashCode() {
+    return (invoke, factory) ->
+        new CfInvoke(
+            Opcodes.INVOKESTATIC,
+            factory.createMethod(
+                factory.boxedFloatType, invoke.getMethod().proto, "floatToIntBits"),
+            false);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java
index 348194b..5c29f81 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java
@@ -4,28 +4,16 @@
 
 package com.android.tools.r8.ir.desugar.backports;
 
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.ir.code.Cmp;
+import com.android.tools.r8.cf.code.CfCmp;
 import com.android.tools.r8.ir.code.Cmp.Bias;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.NumericType;
-import com.android.tools.r8.ir.code.Value;
-import java.util.List;
-import java.util.Set;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
 
 public final class LongMethodRewrites {
 
   private LongMethodRewrites() {}
 
-  public static void rewriteCompare(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    List<Value> inValues = invoke.inValues();
-    assert inValues.size() == 2;
-    iterator.replaceCurrentInstruction(
-        new Cmp(NumericType.LONG, Bias.NONE, invoke.outValue(), inValues.get(0), inValues.get(1)));
+  public static MethodInvokeRewriter rewriteCompare() {
+    return (invoke, factory) -> new CfCmp(Bias.NONE, NumericType.LONG);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
index 342c767..050aefa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
@@ -1,52 +1,44 @@
 package com.android.tools.r8.ir.desugar.backports;
 
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.ir.code.Add;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.NumericType;
-import com.android.tools.r8.ir.code.Value;
-import java.util.List;
-import java.util.Set;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.FullMethodInvokeRewriter;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import java.util.ListIterator;
+import org.objectweb.asm.Opcodes;
 
 public final class NumericMethodRewrites {
-  public static void rewriteToInvokeMath(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    InvokeStatic mathInvoke = new InvokeStatic(
-        factory.createMethod(factory.mathType, invoke.getInvokedMethod().proto,
-            invoke.getInvokedMethod().name), invoke.outValue(), invoke.inValues(), false);
-    iterator.replaceCurrentInstruction(mathInvoke);
+
+  public static MethodInvokeRewriter rewriteToInvokeMath() {
+    return (invoke, factory) -> {
+      DexMethod method = invoke.getMethod();
+      return new CfInvoke(
+          Opcodes.INVOKESTATIC,
+          factory.createMethod(factory.mathType, method.proto, method.name),
+          false);
+    };
   }
 
-  public static void rewriteToAddInstruction(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    List<Value> values = invoke.inValues();
-    assert values.size() == 2;
-
-    NumericType numericType = NumericType.fromDexType(invoke.getReturnType());
-    Add add = new Add(numericType, invoke.outValue(), values.get(0), values.get(1));
-    iterator.replaceCurrentInstruction(add);
+  public static MethodInvokeRewriter rewriteToAddInstruction() {
+    return (invoke, factory) -> {
+      NumericType numericType = NumericType.fromDexType(invoke.getMethod().getReturnType());
+      return new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, numericType);
+    };
   }
 
-  public static void rewriteAsIdentity(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    List<Value> values = invoke.inValues();
-    assert values.size() == 1;
-    if (invoke.hasOutValue()) {
-      invoke.outValue().replaceUsers(values.get(0));
-    }
-    // TODO(b/152853271): Debugging information is lost here (DebugLocalWrite may be required).
-    iterator.removeOrReplaceByDebugLocalRead();
+  public static MethodInvokeRewriter rewriteAsIdentity() {
+    return new FullMethodInvokeRewriter() {
+      @Override
+      public void rewrite(
+          CfInvoke invoke, ListIterator<CfInstruction> iterator, DexItemFactory factory) {
+        // The invoke consumes the stack value and pushes another assumed to be the same.
+        iterator.remove();
+      }
+    };
   }
 
   private NumericMethodRewrites() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
index de16e98..69253be 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
@@ -4,43 +4,41 @@
 
 package com.android.tools.r8.ir.desugar.backports;
 
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
 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.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-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 java.util.Set;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.FullMethodInvokeRewriter;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import java.util.ListIterator;
+import org.objectweb.asm.Opcodes;
 
 public final class ObjectsMethodRewrites {
 
-  public static void rewriteToArraysHashCode(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    DexType arraysType = factory.createType(factory.arraysDescriptor);
-    DexMethod hashCodeMethod =
-        factory.createMethod(arraysType, invoke.getInvokedMethod().proto, "hashCode");
-    InvokeStatic arraysHashCode =
-        new InvokeStatic(hashCodeMethod, invoke.outValue(), invoke.inValues(), false);
-    iterator.replaceCurrentInstruction(arraysHashCode);
+  public static MethodInvokeRewriter rewriteToArraysHashCode() {
+    return (invoke, factory) -> {
+      DexType arraysType = factory.createType(factory.arraysDescriptor);
+      return new CfInvoke(
+          Opcodes.INVOKESTATIC,
+          factory.createMethod(arraysType, invoke.getMethod().proto, "hashCode"),
+          false);
+    };
   }
 
-  public static void rewriteRequireNonNull(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    InvokeVirtual getClass =
-        new InvokeVirtual(factory.objectMembers.getClass, null, invoke.inValues());
-    if (invoke.hasOutValue()) {
-      affectedValues.addAll(invoke.outValue().affectedValues());
-      invoke.outValue().replaceUsers(invoke.inValues().get(0));
-      invoke.setOutValue(null);
-    }
-    iterator.replaceCurrentInstruction(getClass);
+  public static MethodInvokeRewriter rewriteRequireNonNull() {
+    return new FullMethodInvokeRewriter() {
+
+      @Override
+      public void rewrite(
+          CfInvoke invoke, ListIterator<CfInstruction> iterator, DexItemFactory factory) {
+        iterator.remove();
+        // requireNonNull returns the operand, so dup top-of-stack, do getClass and pop the class.
+        iterator.add(new CfStackInstruction(Opcode.Dup));
+        iterator.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.objectMembers.getClass, false));
+        iterator.add(new CfStackInstruction(Opcode.Pop));
+      }
+    };
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java
index c10a292..d66d7f5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java
@@ -4,58 +4,40 @@
 
 package com.android.tools.r8.ir.desugar.backports;
 
+import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.Value;
-import java.util.Set;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import java.util.function.Function;
+import org.objectweb.asm.Opcodes;
 
 public final class OptionalMethodRewrites {
 
   private OptionalMethodRewrites() {}
 
-  public static void rewriteOrElseGet(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    InvokeVirtual getInvoke = new InvokeVirtual(
-        factory.createMethod(factory.optionalType, invoke.getInvokedMethod().proto,
-            "get"), invoke.outValue(), invoke.inValues());
-    iterator.replaceCurrentInstruction(getInvoke);
+  private static MethodInvokeRewriter createRewriter(
+      Function<DexItemFactory, DexType> holderTypeSupplier, String methodName) {
+    return (invoke, factory) ->
+        new CfInvoke(
+            Opcodes.INVOKEVIRTUAL,
+            factory.createMethod(
+                holderTypeSupplier.apply(factory), invoke.getMethod().proto, methodName),
+            false);
   }
 
-  public static void rewriteDoubleOrElseGet(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    InvokeVirtual getInvoke = new InvokeVirtual(
-        factory.createMethod(factory.optionalDoubleType, invoke.getInvokedMethod().proto,
-            "getAsDouble"), invoke.outValue(), invoke.inValues());
-    iterator.replaceCurrentInstruction(getInvoke);
+  public static MethodInvokeRewriter rewriteOrElseGet() {
+    return createRewriter(factory -> factory.optionalType, "get");
   }
 
-  public static void rewriteIntOrElseGet(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    InvokeVirtual getInvoke = new InvokeVirtual(
-        factory.createMethod(factory.optionalIntType, invoke.getInvokedMethod().proto,
-            "getAsInt"), invoke.outValue(), invoke.inValues());
-    iterator.replaceCurrentInstruction(getInvoke);
+  public static MethodInvokeRewriter rewriteDoubleOrElseGet() {
+    return createRewriter(factory -> factory.optionalDoubleType, "getAsDouble");
   }
 
-  public static void rewriteLongOrElseGet(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    InvokeVirtual getInvoke = new InvokeVirtual(
-        factory.createMethod(factory.optionalLongType, invoke.getInvokedMethod().proto,
-            "getAsLong"), invoke.outValue(), invoke.inValues());
-    iterator.replaceCurrentInstruction(getInvoke);
+  public static MethodInvokeRewriter rewriteIntOrElseGet() {
+    return createRewriter(factory -> factory.optionalIntType, "getAsInt");
+  }
+
+  public static MethodInvokeRewriter rewriteLongOrElseGet() {
+    return createRewriter(factory -> factory.optionalLongType, "getAsLong");
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 7a5dce9..0fc9b80 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -264,8 +264,7 @@
       return null;
     }
 
-    if (inliner.isBlacklisted(
-        invoke, resolutionResult, singleTarget, whyAreYouNotInliningReporter)) {
+    if (inliner.neverInline(invoke, resolutionResult, singleTarget, whyAreYouNotInliningReporter)) {
       return null;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index a36f313..ca93336 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -85,7 +85,7 @@
 public class Inliner implements PostOptimization {
 
   protected final AppView<AppInfoWithLiveness> appView;
-  private final Set<DexMethod> blacklist;
+  private final Set<DexMethod> extraNeverInlineMethods;
   private final LambdaMerger lambdaMerger;
   private final LensCodeRewriter lensCodeRewriter;
   final MainDexTracingResult mainDexClasses;
@@ -106,7 +106,7 @@
       LensCodeRewriter lensCodeRewriter) {
     Kotlin.Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
     this.appView = appView;
-    this.blacklist =
+    this.extraNeverInlineMethods =
         appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations
             ? ImmutableSet.of()
             : ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
@@ -119,7 +119,7 @@
             : null;
   }
 
-  boolean isBlacklisted(
+  boolean neverInline(
       InvokeMethod invoke,
       SingleResolutionResult resolutionResult,
       ProgramMethod singleTarget,
@@ -136,10 +136,11 @@
       return true;
     }
 
-    if (blacklist.contains(appView.graphLens().getOriginalMethodSignature(singleTargetReference))
+    if (extraNeverInlineMethods.contains(
+            appView.graphLens().getOriginalMethodSignature(singleTargetReference))
         || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(
             singleTargetReference, appView)) {
-      whyAreYouNotInliningReporter.reportBlacklisted();
+      whyAreYouNotInliningReporter.reportExtraNeverInline();
       return true;
     }
 
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 e09dab4..7d40dba 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
@@ -635,8 +635,8 @@
         DexAnnotationSet.empty(),
         ParameterAnnotationsList.empty(),
         cfCode,
-        REQUIRED_CLASS_FILE_VERSION,
-        true);
+        true,
+        REQUIRED_CLASS_FILE_VERSION);
   }
 
   private MethodAccessFlags synthesizedMethodAccessFlags(boolean sync) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
index 8cbb1f4..e398dce 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -22,7 +22,7 @@
   }
 
   @Override
-  public void reportBlacklisted() {}
+  public void reportExtraNeverInline() {}
 
   @Override
   public void reportCallerNotSameClass() {}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index 05d9b49..b3e72bf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -45,7 +45,7 @@
     }
   }
 
-  public abstract void reportBlacklisted();
+  public abstract void reportExtraNeverInline();
 
   public abstract void reportCallerNotSameClass();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index 934651c..ee2d4b5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -48,8 +48,8 @@
   }
 
   @Override
-  public void reportBlacklisted() {
-    print("method is blacklisted from inlining.");
+  public void reportExtraNeverInline() {
+    print("method is marked as an additional never inline method.");
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index 3f6569e..ae06b60 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -174,7 +174,7 @@
   @Override
   public void buildBlockTransfer(
       IRBuilder builder, int predecessorOffset, int successorOffset, boolean isExceptional) {
-    // Intensionally empty as synthetic code does not contain locals information.
+    // Intentionally empty as synthetic code does not contain locals information.
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 9ccc19c..e28734b 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -43,6 +43,7 @@
 import com.android.tools.r8.naming.ProguardMapSupplier;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.AsmUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableMap;
@@ -173,6 +174,9 @@
       }
     }
     int access = clazz.accessFlags.getAsCfAccessFlags();
+    if (clazz.isDeprecated()) {
+      access = AsmUtils.withDeprecated(access);
+    }
     String desc = namingLens.lookupDescriptor(clazz.type).toString();
     String name = namingLens.lookupInternalName(clazz.type);
     String signature = getSignature(clazz.annotations());
@@ -326,6 +330,9 @@
 
   private void writeField(DexEncodedField field, ClassWriter writer) {
     int access = field.accessFlags.getAsCfAccessFlags();
+    if (field.isDeprecated()) {
+      access = AsmUtils.withDeprecated(access);
+    }
     String name = namingLens.lookupName(field.field).toString();
     String desc = namingLens.lookupDescriptor(field.field.type).toString();
     String signature = getSignature(field.annotations());
@@ -343,6 +350,9 @@
       ImmutableMap<DexString, DexValue> defaults) {
     DexEncodedMethod definition = method.getDefinition();
     int access = definition.getAccessFlags().getAsCfAccessFlags();
+    if (definition.isDeprecated()) {
+      access = AsmUtils.withDeprecated(access);
+    }
     String name = namingLens.lookupName(method.getReference()).toString();
     String desc = definition.descriptor(namingLens);
     String signature = getSignature(definition.annotations());
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
index 038d078..4dc644c 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -247,6 +247,9 @@
         appView.dexItemFactory().createMethod(clazz.type, method.proto, method.name);
     DexEncodedMethod newMethod =
         representative.getDefinition().toTypeSubstitutedMethod(newMethodReference);
+    if (newMethod.getAccessFlags().isFinal()) {
+      newMethod.getAccessFlags().demoteFromFinal();
+    }
     clazz.addVirtualMethod(newMethod);
     lensBuilder.move(representative.getReference(), newMethodReference);
 
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
index 77aff7d..fe0bac2 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
@@ -19,14 +19,14 @@
 
   public static String methodDescriptionFromMethodReference(
       MethodReference methodReference, boolean appendHolder, boolean verbose) {
-    if (!verbose || methodReference.isUnknown()) {
-      return methodReference.getHolderClass().getTypeName() + "." + methodReference.getMethodName();
-    }
     StringBuilder sb = new StringBuilder();
     if (appendHolder) {
       sb.append(methodReference.getHolderClass().getTypeName());
       sb.append(".");
     }
+    if (!verbose || methodReference.isUnknown()) {
+      return sb.append(methodReference.getMethodName()).toString();
+    }
     sb.append(
         methodReference.getReturnType() == null
             ? "void"
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 953779d..0815577 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1251,8 +1251,8 @@
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               code,
-              method.hasClassFileVersion() ? method.getClassFileVersion() : -1,
-              true);
+              true,
+              method.hasClassFileVersion() ? method.getClassFileVersion() : -1);
       bridge.setLibraryMethodOverride(method.isLibraryMethodOverride());
       if (method.accessFlags.isPromotedToPublic()) {
         // The bridge is now the public method serving the role of the original method, and should
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 31a4afd..9dd8bee 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -96,8 +97,11 @@
     List<SyntheticMethodDefinition> methodDefinitions =
         lookupSyntheticMethodDefinitions(application);
 
-    Map<HashCode, List<SyntheticMethodDefinition>> potentialEquivalences =
-        computePotentialEquivalences(methodDefinitions);
+    Collection<List<SyntheticMethodDefinition>> potentialEquivalences =
+        // Don't share synthetics in intermediate mode builds.
+        options.intermediate
+            ? ListUtils.map(methodDefinitions, Collections::singletonList)
+            : computePotentialEquivalences(methodDefinitions);
 
     Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> equivalences =
         computeActualEquivalences(potentialEquivalences, options.itemFactory);
@@ -328,10 +332,10 @@
 
   private static <T extends SyntheticDefinition & Comparable<T>>
       Map<DexType, EquivalenceGroup<T>> computeActualEquivalences(
-          Map<HashCode, List<T>> potentialEquivalences, DexItemFactory factory) {
+          Collection<List<T>> potentialEquivalences, DexItemFactory factory) {
     Map<DexType, List<EquivalenceGroup<T>>> groupsPerContext = new IdentityHashMap<>();
     potentialEquivalences.forEach(
-        (hash, members) -> {
+        members -> {
           // Get a representative member and add to its group.
           T representative = findDeterministicRepresentative(members);
           List<T> group = new ArrayList<>(members.size());
@@ -391,14 +395,14 @@
                 + nextContextId));
   }
 
-  private static <T extends SyntheticDefinition>
-      Map<HashCode, List<T>> computePotentialEquivalences(List<T> definitions) {
+  private static <T extends SyntheticDefinition> Collection<List<T>> computePotentialEquivalences(
+      List<T> definitions) {
     Map<HashCode, List<T>> equivalences = new HashMap<>(definitions.size());
     for (T definition : definitions) {
       HashCode hash = definition.computeHash();
       equivalences.computeIfAbsent(hash, k -> new ArrayList<>()).add(definition);
     }
-    return equivalences;
+    return equivalences.values();
   }
 
   private List<SyntheticMethodDefinition> lookupSyntheticMethodDefinitions(
diff --git a/src/main/java/com/android/tools/r8/utils/AsmUtils.java b/src/main/java/com/android/tools/r8/utils/AsmUtils.java
new file mode 100644
index 0000000..44fcc6f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/AsmUtils.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import static org.objectweb.asm.Opcodes.ACC_DEPRECATED;
+
+public class AsmUtils {
+  public static boolean isDeprecated(int access) {
+    // ASM stores the Deprecated attribute
+    // (https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.15) in the
+    // access flags.
+    return (access & ACC_DEPRECATED) == ACC_DEPRECATED;
+  }
+
+  public static int withDeprecated(int access) {
+    // ASM stores the Deprecated attribute
+    // (https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.15) in the
+    // access flags.
+    return access | ACC_DEPRECATED;
+  }
+}
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 df274fe..cdd3ca3 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1241,7 +1241,7 @@
     public Set<Inliner.Reason> validInliningReasons = null;
     public boolean noLocalsTableOnInput = false;
     public boolean forceNameReflectionOptimization = false;
-    public boolean enableNarrowingChecksInD8 = false;
+    public boolean enableNarrowAndWideningingChecksInD8 = false;
     public Consumer<IRCode> irModifier = null;
     public int basicBlockMuncherIterationLimit = NO_LIMIT;
     public boolean dontReportFailingCheckDiscarded = false;
diff --git a/src/test/java/com/android/tools/r8/R8FullTestBuilder.java b/src/test/java/com/android/tools/r8/R8FullTestBuilder.java
index 7e41239..3343329 100644
--- a/src/test/java/com/android/tools/r8/R8FullTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8FullTestBuilder.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.utils.AndroidApp;
 
 public class R8FullTestBuilder extends R8TestBuilder<R8FullTestBuilder> {
 
@@ -17,6 +18,11 @@
     return new R8FullTestBuilder(state, builder, backend);
   }
 
+  public static R8FullTestBuilder create(
+      TestState state, AndroidApp.Builder appBuilder, Backend backend) {
+    return new R8FullTestBuilder(state, R8Command.builder(appBuilder.build()), backend);
+  }
+
   @Override
   R8FullTestBuilder self() {
     return this;
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index d9ed43f..9fe1539 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -620,76 +620,87 @@
               "461-get-reference-vreg",
               TestCondition.match(
                   TestCondition.D8_COMPILER,
-                  TestCondition
-                      .runtimes(DexVm.Version.V7_0_0, DexVm.Version.V6_0_1, DexVm.Version.V5_1_1)))
+                  TestCondition.runtimes(
+                      DexVm.Version.V7_0_0, DexVm.Version.V6_0_1, DexVm.Version.V5_1_1)))
           // Dalvik fails on reading an uninitialized local.
           .put(
               "471-uninitialized-locals",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Out of memory.
-          .put("152-dead-large-object",
+          .put(
+              "152-dead-large-object",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Cannot resolve exception handler. Interestingly, D8 generates different code in
           // release mode (which is also the code generated by R8) which passes.
-          .put("111-unresolvable-exception",
+          .put(
+              "111-unresolvable-exception",
               TestCondition.match(
-                  TestCondition.D8_COMPILER,
-                  TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
+                  TestCondition.D8_COMPILER, TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Fails because the code has to be desugared to run on art <= 6.0.1
           // When running from dx code we don't desugar.
-          .put("530-checker-lse2",
+          .put(
+              "530-checker-lse2",
               TestCondition.match(
                   TestCondition.tools(DexTool.DX),
-                  TestCondition.D8_COMPILER,
                   TestCondition.runtimesUpTo(DexVm.Version.V6_0_1)))
-          .put("534-checker-bce-deoptimization",
-              TestCondition
-                  .match(TestCondition.D8_COMPILER, TestCondition.runtimes(DexVm.Version.V6_0_1)))
+          .put(
+              "534-checker-bce-deoptimization",
+              TestCondition.match(
+                  TestCondition.D8_COMPILER, TestCondition.runtimes(DexVm.Version.V6_0_1)))
           // Type not present.
-          .put("124-missing-classes",
+          .put(
+              "124-missing-classes",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Failed creating vtable.
-          .put("587-inline-class-error",
+          .put(
+              "587-inline-class-error",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Failed creating vtable.
-          .put("595-error-class",
+          .put(
+              "595-error-class",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // NoSuchFieldException: systemThreadGroup on Art 4.4.4.
-          .put("129-ThreadGetId",
+          .put(
+              "129-ThreadGetId",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Verifier says: can't modify final field LMain;.staticFinalField.
-          .put("600-verifier-fails",
+          .put(
+              "600-verifier-fails",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // VFY: tried to get class from non-ref register.
-          .put("506-verify-aput",
+          .put(
+              "506-verify-aput",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // NoSuchMethod: startMethodTracing.
-          .put("545-tracing-and-jit",
+          .put(
+              "545-tracing-and-jit",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // filled-new-array arg 0(1) not valid.
-          .put("412-new-array",
+          .put(
+              "412-new-array",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // TODO(ager): unclear what is failing here.
-          .put("098-ddmc",
-              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
+          .put("098-ddmc", TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Unsatisfiable link error:
           // libarttest.so: undefined symbol: _ZN3art6Thread18RunEmptyCheckpointEv
-          .put("543-env-long-ref",
+          .put(
+              "543-env-long-ref",
               TestCondition.match(
                   TestCondition.D8_COMPILER,
-                  TestCondition
-                      .runtimes(
-                          DexVm.Version.V8_1_0,
-                          DexVm.Version.V7_0_0,
-                          DexVm.Version.V6_0_1,
-                          DexVm.Version.V5_1_1)))
+                  TestCondition.runtimes(
+                      DexVm.Version.V8_1_0,
+                      DexVm.Version.V7_0_0,
+                      DexVm.Version.V6_0_1,
+                      DexVm.Version.V5_1_1)))
           // lib64 libarttest.so: wrong ELF class ELFCLASS64.
-          .put("543-env-long-ref",
+          .put(
+              "543-env-long-ref",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Leaving two static-get triggers LSE bug on 6.0.1 (b/25735083).
           // R8, with subtyping, knows the first sget is dead, and removing it avoids the bug.
           // Due to the lack of subtype hierarchy, D8 can't guarantee <clinit> side effects.
-          .put("550-new-instance-clinit",
+          .put(
+              "550-new-instance-clinit",
               TestCondition.match(
                   TestCondition.D8_COMPILER, TestCondition.runtimes(DexVm.Version.V6_0_1)))
           // Regression test for an issue that is not fixed on version 5.1.1. Throws an Exception
@@ -697,40 +708,37 @@
           // running the R8 generated code when starting from jar or from dex code generated with
           // dx. However, the code that R8 generates is valid and there is nothing we can do for
           // this one.
-          .put("551-implicit-null-checks",
+          .put(
+              "551-implicit-null-checks",
               TestCondition.match(
                   TestCondition.tools(DexTool.NONE, DexTool.DX),
                   TestCondition.R8DEX_COMPILER,
                   TestCondition.runtimes(DexVm.Version.V5_1_1)))
           // Contains a method (B.<init>) which pass too few arguments to invoke. Also, contains an
           // iput on a static field.
-          .put("600-verifier-fails",
+          .put(
+              "600-verifier-fails",
               TestCondition.match(
                   TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.Version.V7_0_0, DexVm.Version.V6_0_1,
-                      DexVm.Version.V5_1_1)))
+                  TestCondition.runtimes(
+                      DexVm.Version.V7_0_0, DexVm.Version.V6_0_1, DexVm.Version.V5_1_1)))
           // Dalvik 4.0.4 is missing ReflectiveOperationException class.
-          .put("140-field-packing",
-              TestCondition.match(
-                  TestCondition.runtimes(DexVm.Version.V4_0_4)))
+          .put(
+              "140-field-packing",
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4)))
           // Dalvik 4.0.4 is missing theUnsafe field.
-          .put("528-long-hint",
-              TestCondition.match(
-                  TestCondition.runtimes(DexVm.Version.V4_0_4)))
+          .put("528-long-hint", TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4)))
           // Cannot catch exception in Dalvik 4.0.4.
-          .put("084-class-init",
-              TestCondition.match(
-                  TestCondition.runtimes(DexVm.Version.V4_0_4)))
+          .put("084-class-init", TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4)))
           // Tested regression still exists in Dalvik 4.0.4.
-          .put("301-abstract-protected",
-              TestCondition.match(
-                  TestCondition.runtimes(DexVm.Version.V4_0_4)))
+          .put(
+              "301-abstract-protected",
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4)))
           // Illegal class flags in Dalvik 4.0.4.
-          .put("121-modifiers",
-              TestCondition.match(
-                  TestCondition.runtimes(DexVm.Version.V4_0_4)))
+          .put("121-modifiers", TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4)))
           // Switch regression still present in Dalvik 4.0.4.
-          .put("095-switch-MAX_INT",
+          .put(
+              "095-switch-MAX_INT",
               TestCondition.match(
                   TestCondition.tools(DexTool.DX),
                   TestCondition.D8_COMPILER,
diff --git a/src/test/java/com/android/tools/r8/SwitchDebugLocalsConflictTest.java b/src/test/java/com/android/tools/r8/SwitchDebugLocalsConflictTest.java
index bec20c9..7a679e5 100644
--- a/src/test/java/com/android/tools/r8/SwitchDebugLocalsConflictTest.java
+++ b/src/test/java/com/android/tools/r8/SwitchDebugLocalsConflictTest.java
@@ -3,7 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import static org.junit.Assert.assertTrue;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -35,11 +36,11 @@
         .noDesugaring()
         .compileWithExpectedDiagnostics(
             diagnotics -> {
-              diagnotics.assertOnlyInfos();
-              assertTrue(
-                  diagnotics.getInfos().stream()
-                      .anyMatch(
-                          d -> d.getDiagnosticMessage().contains("invalid locals information")));
+              diagnotics.assertNoErrors();
+              diagnotics.assertInfoThatMatches(
+                  diagnosticMessage(containsString("invalid locals information")));
+              diagnotics.assertAllWarningsMatch(
+                  diagnosticMessage(containsString("Could not find phi type for register 14")));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index d065497..bb8cb6e 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1482,6 +1482,10 @@
     public boolean isAggressive() {
       return this == AGGRESSIVE;
     }
+
+    public static MinifyMode[] withoutNone() {
+      return new MinifyMode[] {JAVA, AGGRESSIVE};
+    }
   }
 
   public static ProgramConsumer emptyConsumer(Backend backend) {
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/FinalBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/FinalBridgeHoistingTest.java
new file mode 100644
index 0000000..de2e041
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/FinalBridgeHoistingTest.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.bridgeremoval.hoisting;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FinalBridgeHoistingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public FinalBridgeHoistingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, A.class, B1.class)
+        .addProgramClassFileData(
+            transformer(B2.class)
+                .setBridge(B2.class.getDeclaredMethod("virtualBridge", Object.class))
+                .transform())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(B1.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.uniqueMethodWithName("m"), isPresent());
+    assertThat(aClassSubject.uniqueMethodWithName("virtualBridge"), isPresent());
+
+    ClassSubject b1ClassSubject = inspector.clazz(B1.class);
+    assertThat(b1ClassSubject, isPresent());
+    assertThat(b1ClassSubject.uniqueMethodWithName("virtualBridge"), isPresent());
+
+    ClassSubject b2ClassSubject = inspector.clazz(B2.class);
+    assertThat(b2ClassSubject, isPresent());
+    assertThat(b2ClassSubject.uniqueMethodWithName("virtualBridge"), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.print(new B1().virtualBridge("Hello"));
+      System.out.println(new B2().virtualBridge(" world!"));
+    }
+  }
+
+  static class A {
+
+    @NeverInline
+    public Object m(String arg) {
+      return System.currentTimeMillis() >= 0 ? arg : null;
+    }
+  }
+
+  @NeverClassInline
+  static class B1 extends A {
+
+    public String virtualBridge(Object o) {
+      return (String) m((String) o);
+    }
+  }
+
+  @NeverClassInline
+  static class B2 extends A {
+
+    @NeverInline
+    public final /*bridge*/ String virtualBridge(Object o) {
+      return (String) m((String) o);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileDeprecatedAttribute.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileDeprecatedAttribute.java
new file mode 100644
index 0000000..0eac295
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileDeprecatedAttribute.java
@@ -0,0 +1,128 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+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.utils.InternalOptions;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class DesugarToClassFileDeprecatedAttribute extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private final TestParameters parameters;
+
+  public DesugarToClassFileDeprecatedAttribute(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private boolean isDeprecated(int access) {
+    return (access & Opcodes.ACC_DEPRECATED) == Opcodes.ACC_DEPRECATED;
+  }
+
+  private void checkDeprecatedAttributes(byte[] classBytes) {
+    ClassReader cr = new ClassReader(classBytes);
+    cr.accept(
+        new ClassVisitor(InternalOptions.ASM_VERSION) {
+          @Override
+          public void visit(
+              int version,
+              int access,
+              String name,
+              String signature,
+              String superName,
+              String[] interfaces) {
+            assertTrue(isDeprecated(access));
+          }
+
+          @Override
+          public MethodVisitor visitMethod(
+              int access, String name, String desc, String signature, String[] exceptions) {
+            if (!name.equals("<init>")) {
+              assertTrue(isDeprecated(access));
+            }
+            return super.visitMethod(access, name, desc, signature, exceptions);
+          }
+
+          @Override
+          public FieldVisitor visitField(
+              int access, String name, String descriptor, String signature, Object value) {
+            assertTrue(isDeprecated(access));
+            return super.visitField(access, name, descriptor, signature, value);
+          }
+        },
+        0);
+  }
+
+  @Test
+  public void test() throws Exception {
+    checkDeprecatedAttributes(
+        Files.readAllBytes(ToolHelper.getClassFileForTestClass(TestClass.class)));
+
+    // Use D8 to desugar with Java classfile output.
+    Path jar =
+        testForD8(Backend.CF)
+            .addProgramClasses(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .setProgramConsumer(
+                new ClassFileConsumer.ForwardingConsumer(null) {
+                  @Override
+                  public void accept(
+                      ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+                    checkDeprecatedAttributes(data.getBuffer());
+                  }
+                })
+            .compile()
+            .writeToZip();
+
+    if (parameters.getRuntime().isCf()) {
+      // Run on the JVM.
+      testForJvm()
+          .addProgramFiles(jar)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello, world!");
+    } else {
+      assert parameters.getRuntime().isDex();
+      // Convert to DEX without desugaring.
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello, world!");
+    }
+  }
+
+  @Deprecated
+  public static class TestClass {
+    @Deprecated public Object object = new Object();
+
+    @Deprecated
+    public static void main(String[] args) {
+      System.out.println("Hello, world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
index 31575df..d4346cc 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -155,6 +156,33 @@
             });
   }
 
+  @Test
+  public void testD8FilePerClassFile() throws Exception {
+    runD8FilePerMode(OutputMode.DexFilePerClassFile);
+  }
+
+  @Test
+  public void testD8FilePerClass() throws Exception {
+    runD8FilePerMode(OutputMode.DexFilePerClass);
+  }
+
+  public void runD8FilePerMode(OutputMode outputMode) throws Exception {
+    Path perClassOutput =
+        testForD8(parameters.getBackend())
+            .setOutputMode(outputMode)
+            .addProgramClasses(CLASSES)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .writeToZip();
+    testForD8()
+        .addProgramFiles(perClassOutput)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkNoInternalSyntheticNames)
+        .inspect(this::checkExpectedSynthetics);
+  }
+
   private void checkNoInternalSyntheticNames(CodeInspector inspector) {
     inspector.forAllClasses(
         clazz -> {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
index 424a48f..4a2e645 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.GenerateMainDexListRunResult;
+import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -119,9 +120,6 @@
     Path out =
         testForD8()
             .addProgramClasses(CLASSES)
-            // Setting intermediate will annotate synthetics, which should not cause types in those
-            // to become main-dex included.
-            .setIntermediate(true)
             .setMinApi(parameters.getApiLevel())
             .compile()
             .writeToZip();
@@ -136,6 +134,24 @@
   }
 
   @Test
+  public void testMainDexTracingDexIntermediates() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    Path out =
+        testForD8()
+            .addProgramClasses(CLASSES)
+            // Setting intermediate will annotate synthetics, which should not cause types in those
+            // to become main-dex included.
+            .setIntermediate(true)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .writeToZip();
+    GenerateMainDexListRunResult mainDexListFromDex =
+        traceMainDex(Collections.emptyList(), Collections.singleton(out));
+    // Compiling in intermediate will not share the synthetics so there is one per call site.
+    assertEquals(MAIN_DEX_LIST_CLASSES.size() + 6, mainDexListFromDex.getMainDexList().size());
+  }
+
+  @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
     MainDexConsumer mainDexConsumer = new MainDexConsumer();
@@ -151,6 +167,38 @@
     checkMainDex(mainDexConsumer);
   }
 
+  @Test
+  public void testD8FilePerClassFile() throws Exception {
+    runD8FilePerMode(OutputMode.DexFilePerClassFile);
+  }
+
+  @Test
+  public void testD8FilePerClass() throws Exception {
+    runD8FilePerMode(OutputMode.DexFilePerClass);
+  }
+
+  private void runD8FilePerMode(OutputMode outputMode) throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    Path perClassOutput =
+        testForD8(parameters.getBackend())
+            .setOutputMode(outputMode)
+            .addProgramClasses(CLASSES)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .writeToZip();
+    MainDexConsumer mainDexConsumer = new MainDexConsumer();
+    testForD8()
+        .addProgramFiles(perClassOutput)
+        .setMinApi(parameters.getApiLevel())
+        .addMainDexListClasses(MiniAssert.class, TestClass.class, User2.class)
+        .setProgramConsumer(mainDexConsumer)
+        .compile()
+        .inspect(this::checkExpectedSynthetics)
+        .run(parameters.getRuntime(), TestClass.class, getRunArgs())
+        .assertSuccessWithOutput(EXPECTED);
+    checkMainDex(mainDexConsumer);
+  }
+
   // TODO(b/168584485): This test should be removed once support is dropped.
   @Test
   public void testD8MergingWithTraceCf() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
index 6db1591..558b5f1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 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.fail;
@@ -18,7 +17,6 @@
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -230,19 +228,11 @@
           .assertSuccessWithOutput(expectedOutput);
     } else {
       // Run on the JVM with desugared library on classpath.
-      TestRunResult<?> result =
-          testForJvm()
-              .addProgramFiles(jar)
-              .addRunClasspathFiles(desugaredLibraryClassFile.get())
-              .run(parameters.getRuntime(), TestClass.class);
-      if (parameters.getApiLevel().isGreaterThan(AndroidApiLevel.N_MR1)) {
-        // java.time is present from O, so the desugared library classes are not loaded.
-        result.assertSuccessWithOutput(expectedOutput);
-      } else {
-        // TODO(b/164396438): Produce correct stack map.
-        result.assertFailureWithErrorThatMatches(
-            containsString("java.lang.VerifyError: Bad type on operand stack"));
-      }
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(desugaredLibraryClassFile.get())
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(expectedOutput);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/DebugLocalSynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/ir/DebugLocalSynchronizedBlockTest.java
new file mode 100644
index 0000000..f6be937
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/DebugLocalSynchronizedBlockTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DebugLocalSynchronizedBlockTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public DebugLocalSynchronizedBlockTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  public static class Main {
+
+    private int state = 0;
+    private int[] values = {42};
+
+    public static void main(String[] args) {
+      Main main = new Main();
+      if (args.length == 0) {
+        main.state = 1;
+      } else {
+        main.state = 0;
+      }
+      main.thread1();
+      System.out.println("Hello World!");
+    }
+
+    void thread1() {
+      int s;
+      do {
+        synchronized (Main.class) {
+          s = state;
+        }
+      } while (s != 1); // Busy loop.
+      synchronized (Main.class) {
+        values = null;
+        state = 2;
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/DebugLocalWithoutStackMapTypeTest.java b/src/test/java/com/android/tools/r8/ir/DebugLocalWithoutStackMapTypeTest.java
new file mode 100644
index 0000000..0326e06
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/DebugLocalWithoutStackMapTypeTest.java
@@ -0,0 +1,200 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+@RunWith(Parameterized.class)
+public class DebugLocalWithoutStackMapTypeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public DebugLocalWithoutStackMapTypeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(MainDump.dump())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("addSuppressed");
+  }
+
+  public static class Main {
+
+    private static final Method addSuppressedExceptionMethod;
+
+    static {
+      Method m;
+      try {
+        m = Throwable.class.getDeclaredMethod("addSuppressed", Throwable.class);
+      } catch (Exception e) {
+        m = null;
+      }
+      addSuppressedExceptionMethod = m;
+    }
+
+    public static void main(String[] args) {
+      System.out.println(addSuppressedExceptionMethod.getName());
+    }
+  }
+
+  // When compiling with JDK 7 the local variable for m is not split in Main::<clinit>. See
+  // comment below where the local variable ranges for clinit is added.
+  public static class MainDump implements Opcodes {
+
+    public static byte[] dump() {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      FieldVisitor fieldVisitor;
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8,
+          ACC_PUBLIC | ACC_SUPER,
+          "com/android/tools/r8/ir/DebugLocalWithoutStackMapTypeTest$Main",
+          null,
+          "java/lang/Object",
+          null);
+
+      classWriter.visitInnerClass(
+          "com/android/tools/r8/ir/DebugLocalWithoutStackMapTypeTest$Main",
+          "com/android/tools/r8/ir/DebugLocalWithoutStackMapTypeTest",
+          "Main",
+          ACC_PUBLIC | ACC_STATIC);
+
+      {
+        fieldVisitor =
+            classWriter.visitField(
+                ACC_PRIVATE | ACC_FINAL | ACC_STATIC,
+                "addSuppressedExceptionMethod",
+                "Ljava/lang/reflect/Method;",
+                null,
+                null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+        methodVisitor.visitCode();
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(1, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(63, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitFieldInsn(
+            GETSTATIC,
+            "com/android/tools/r8/ir/DebugLocalWithoutStackMapTypeTest$Main",
+            "addSuppressedExceptionMethod",
+            "Ljava/lang/reflect/Method;");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/lang/reflect/Method", "getName", "()Ljava/lang/String;", false);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(64, label1);
+        methodVisitor.visitInsn(RETURN);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLocalVariable("args", "[Ljava/lang/String;", null, label0, label2, 0);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        Label label1 = new Label();
+        Label label2 = new Label();
+        methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/Exception");
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(55, label0);
+        methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Throwable;"));
+        methodVisitor.visitLdcInsn("addSuppressed");
+        methodVisitor.visitInsn(ICONST_1);
+        methodVisitor.visitTypeInsn(ANEWARRAY, "java/lang/Class");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Throwable;"));
+        methodVisitor.visitInsn(AASTORE);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "java/lang/Class",
+            "getDeclaredMethod",
+            "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;",
+            false);
+        methodVisitor.visitVarInsn(ASTORE, 0);
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(58, label1);
+        Label label3 = new Label();
+        methodVisitor.visitJumpInsn(GOTO, label3);
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLineNumber(56, label2);
+        methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Exception"});
+        methodVisitor.visitVarInsn(ASTORE, 1);
+        Label label4 = new Label();
+        methodVisitor.visitLabel(label4);
+        methodVisitor.visitLineNumber(57, label4);
+        methodVisitor.visitInsn(ACONST_NULL);
+        methodVisitor.visitVarInsn(ASTORE, 0);
+        methodVisitor.visitLabel(label3);
+        methodVisitor.visitLineNumber(59, label3);
+        methodVisitor.visitFrame(
+            Opcodes.F_APPEND, 1, new Object[] {"java/lang/reflect/Method"}, 0, null);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            PUTSTATIC,
+            "com/android/tools/r8/ir/DebugLocalWithoutStackMapTypeTest$Main",
+            "addSuppressedExceptionMethod",
+            "Ljava/lang/reflect/Method;");
+        Label label5 = new Label();
+        methodVisitor.visitLabel(label5);
+        methodVisitor.visitLineNumber(60, label5);
+        methodVisitor.visitInsn(RETURN);
+        // When compiling with JDK v7 the local variable for m is not split, and m therefore ranges
+        // from label1 to label5. From JDK v8 and upward, the range for m is split in label1-label2
+        // and label4-label5.
+        methodVisitor.visitLocalVariable(
+            "m", "Ljava/lang/reflect/Method;", null, label1, label5, 0);
+        methodVisitor.visitLocalVariable("e", "Ljava/lang/Exception;", null, label4, label3, 1);
+        methodVisitor.visitMaxs(6, 2);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NarrowingWithoutSubtypingTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NarrowingWithoutSubtypingTest.java
index 6644af2..403372c 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NarrowingWithoutSubtypingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NarrowingWithoutSubtypingTest.java
@@ -4,10 +4,16 @@
 
 package com.android.tools.r8.ir.analysis.type;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -17,29 +23,47 @@
 public class NarrowingWithoutSubtypingTest extends TestBase {
 
   private final TestParameters parameters;
+  private final boolean readStackMap;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  @Parameters(name = "{0}, read stack map: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
   }
 
-  public NarrowingWithoutSubtypingTest(TestParameters parameters) {
+  public NarrowingWithoutSubtypingTest(TestParameters parameters, boolean readStackMap) {
     this.parameters = parameters;
+    this.readStackMap = readStackMap;
   }
 
-  @Test(expected = CompilationFailedException.class)
+  @Test
   public void test() throws Exception {
-    testForD8()
-        .addInnerClasses(NarrowingWithoutSubtypingTest.class)
-        .addOptionsModification(
-            options -> {
-              options.testing.enableNarrowingChecksInD8 = true;
-              options.testing.noLocalsTableOnInput = true;
-            })
-        .setMinApi(parameters.getApiLevel())
-        .compile()
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("Hello world!");
+    D8TestBuilder d8TestBuilder =
+        testForD8()
+            .addInnerClasses(NarrowingWithoutSubtypingTest.class)
+            .addOptionsModification(
+                options -> {
+                  options.testing.readInputStackMaps = readStackMap;
+                  options.testing.enableNarrowAndWideningingChecksInD8 = true;
+                  options.testing.noLocalsTableOnInput = true;
+                })
+            .setMinApi(parameters.getApiLevel());
+    if (readStackMap) {
+      d8TestBuilder
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello world!");
+    } else {
+      // TODO(b/169120386): We should not be narrowing in D8.
+      assertThrows(
+          CompilationFailedException.class,
+          () -> {
+            d8TestBuilder.compileWithExpectedDiagnostics(
+                diagnostics ->
+                    diagnostics.assertAllErrorsMatch(
+                        diagnosticMessage(
+                            containsString("java.lang.AssertionError: During NARROWING"))));
+          });
+    }
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index b7a60bc..ab2d643 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -253,6 +253,7 @@
     return ToolHelper.runArtRaw(ImmutableList.of(dex.toString(), libraryDex.toString()), main, null);
   }
 
+  @Override
   protected ProcessResult runOnArtRaw(AndroidApp app, String main) throws IOException {
     Path out = temp.getRoot().toPath().resolve("out.zip");
     app.writeToZip(out, OutputMode.DexIndexed);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 37d2899..e03e056 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -53,7 +53,9 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueTypeConstraint;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -915,6 +917,12 @@
     }
 
     @Override
+    public DexType getPhiTypeForBlock(
+        int register, int blockOffset, ValueTypeConstraint constraint, RegisterReadType readType) {
+      throw new Unreachable("Should never generate a phi");
+    }
+
+    @Override
     public DebugLocalInfo getIncomingLocal(int register) {
       return null;
     }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index 8a0f5e5..15ed0d9 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -7,7 +7,6 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -175,14 +174,6 @@
     assertEquals(expected, result.stdout);
   }
 
-  private void runTestNotEquals(
-      String mapping, String stackTrace, boolean stacktraceStdIn, String expected, String... args)
-      throws IOException {
-    ProcessResult result = runRetrace(mapping, stackTrace, stacktraceStdIn, args);
-    assertEquals(0, result.exitCode);
-    assertNotEquals(expected, result.stdout);
-  }
-
   private void runAbortTest(Matcher<String> errorMatch, String... args) throws IOException {
     ProcessResult result = runRetraceCommandLine(null, Arrays.asList(args));
     assertEquals(1, result.exitCode);
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 8bf3b29..d7a5246 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -20,6 +20,8 @@
 import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTrace;
 import com.android.tools.r8.retrace.stacktraces.AmbiguousMissingLineStackTrace;
 import com.android.tools.r8.retrace.stacktraces.AmbiguousStackTrace;
+import com.android.tools.r8.retrace.stacktraces.AmbiguousWithMultipleLineMappingsStackTrace;
+import com.android.tools.r8.retrace.stacktraces.AmbiguousWithSignatureNonVerboseStackTrace;
 import com.android.tools.r8.retrace.stacktraces.CircularReferenceStackTrace;
 import com.android.tools.r8.retrace.stacktraces.FileNameExtensionStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
@@ -141,6 +143,16 @@
   }
 
   @Test
+  public void testAmbiguousMissingLineNotVerbose() {
+    runRetraceTest(new AmbiguousWithSignatureNonVerboseStackTrace());
+  }
+
+  @Test
+  public void testAmbiguousMultipleMappingsTest() {
+    runRetraceTest(new AmbiguousWithMultipleLineMappingsStackTrace());
+  }
+
+  @Test
   public void testInliningWithLineNumbers() {
     runRetraceTest(new InlineWithLineNumbersStackTrace());
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
index 277d190..fb65c1d 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
@@ -4,15 +4,19 @@
 
 package com.android.tools.r8.retrace;
 
+import static com.android.tools.r8.retrace.Retrace.DEFAULT_REGULAR_EXPRESSION;
 import static junit.framework.TestCase.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.retrace.stacktraces.AmbiguousMethodVerboseStackTrace;
+import com.android.tools.r8.retrace.stacktraces.AmbiguousWithSignatureVerboseStackTrace;
 import com.android.tools.r8.retrace.stacktraces.FoundMethodVerboseStackTrace;
 import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
-import com.android.tools.r8.retrace.stacktraces.UnknownMethodVerboseStackTrace;
+import com.android.tools.r8.retrace.stacktraces.VerboseUnknownStackTrace;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.Collection;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -21,12 +25,16 @@
 @RunWith(Parameterized.class)
 public class RetraceVerboseTests extends TestBase {
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
+  @Parameters(name = "{0}, use regular expression: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), BooleanUtils.values());
   }
 
-  public RetraceVerboseTests(TestParameters parameters) {}
+  private final boolean useRegExpParsing;
+
+  public RetraceVerboseTests(TestParameters parameters, boolean useRegExpParsing) {
+    this.useRegExpParsing = useRegExpParsing;
+  }
 
   @Test
   public void testFoundMethod() {
@@ -35,7 +43,17 @@
 
   @Test
   public void testUnknownMethod() {
-    runRetraceTest(new UnknownMethodVerboseStackTrace());
+    runRetraceTest(new AmbiguousMethodVerboseStackTrace());
+  }
+
+  @Test
+  public void testVerboseUnknownMethod() {
+    runRetraceTest(new VerboseUnknownStackTrace());
+  }
+
+  @Test
+  public void testAmbiguousMissingLineVerbose() {
+    runRetraceTest(new AmbiguousWithSignatureVerboseStackTrace());
   }
 
   private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
@@ -44,6 +62,7 @@
         RetraceCommand.builder(diagnosticsHandler)
             .setProguardMapProducer(stackTraceForTest::mapping)
             .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
+            .setRegularExpression(useRegExpParsing ? DEFAULT_REGULAR_EXPRESSION : null)
             .setVerbose(true)
             .setRetracedStackTraceConsumer(
                 retraced -> assertEquals(stackTraceForTest.retracedStackTrace(), retraced))
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownMethodVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMethodVerboseStackTrace.java
similarity index 95%
rename from src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownMethodVerboseStackTrace.java
rename to src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMethodVerboseStackTrace.java
index 5a47079..dcaaa58 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownMethodVerboseStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMethodVerboseStackTrace.java
@@ -8,7 +8,7 @@
 import java.util.Arrays;
 import java.util.List;
 
-public class UnknownMethodVerboseStackTrace implements StackTraceForTest {
+public class AmbiguousMethodVerboseStackTrace implements StackTraceForTest {
 
   @Override
   public List<String> obfuscatedStackTrace() {
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java
new file mode 100644
index 0000000..ddd4b92
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class AmbiguousWithMultipleLineMappingsStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "java.lang.IndexOutOfBoundsException",
+        "\tat java.util.ArrayList.get(ArrayList.java:411)",
+        "\tat com.android.tools.r8.Internal.zza(Unknown)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.Internal -> com.android.tools.r8.Internal:",
+        "  10:10:void foo(int):10:10 -> zza",
+        "  11:11:void foo(int):11:11 -> zza",
+        "  12:12:void foo(int):12:12 -> zza");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "java.lang.IndexOutOfBoundsException",
+        "\tat java.util.ArrayList.get(ArrayList.java:411)",
+        "\tat com.android.tools.r8.Internal.foo(Internal.java)",
+        "\t<OR> at com.android.tools.r8.Internal.foo(Internal.java)",
+        "\t<OR> at com.android.tools.r8.Internal.foo(Internal.java)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java
new file mode 100644
index 0000000..aa5946c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class AmbiguousWithSignatureNonVerboseStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "java.lang.IndexOutOfBoundsException",
+        "\tat java.util.ArrayList.get(ArrayList.java:411)",
+        "\tat com.android.tools.r8.Internal.zza(Unknown)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.Internal -> com.android.tools.r8.Internal:",
+        "  10:10:void foo(int):10:10 -> zza",
+        "  11:11:void foo(int, int):11:11 -> zza",
+        "  12:12:void foo(int, boolean):12:12 -> zza",
+        "  13:13:boolean foo(int, int):13:13 -> zza");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "java.lang.IndexOutOfBoundsException",
+        "\tat java.util.ArrayList.get(ArrayList.java:411)",
+        "\tat com.android.tools.r8.Internal.foo(Internal.java)",
+        "\t<OR> at com.android.tools.r8.Internal.foo(Internal.java)",
+        "\t<OR> at com.android.tools.r8.Internal.foo(Internal.java)",
+        "\t<OR> at com.android.tools.r8.Internal.foo(Internal.java)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureVerboseStackTrace.java
new file mode 100644
index 0000000..1dfb3c9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureVerboseStackTrace.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class AmbiguousWithSignatureVerboseStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "java.lang.IndexOutOfBoundsException",
+        "\tat java.util.ArrayList.get(ArrayList.java:411)",
+        "\tat com.android.tools.r8.Internal.zza(Unknown)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.Internal -> com.android.tools.r8.Internal:",
+        "  10:10:void foo(int):10:10 -> zza",
+        "  11:11:void foo(int,int):11:11 -> zza",
+        "  12:12:void foo(int,boolean):12:12 -> zza",
+        "  13:13:boolean foo(int,int):13:13 -> zza");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "java.lang.IndexOutOfBoundsException",
+        "\tat java.util.ArrayList.get(ArrayList.java:411)",
+        "\tat com.android.tools.r8.Internal.void foo(int)(Internal.java)",
+        "\t<OR> at com.android.tools.r8.Internal.void foo(int,int)(Internal.java)",
+        "\t<OR> at com.android.tools.r8.Internal.void foo(int,boolean)(Internal.java)",
+        "\t<OR> at com.android.tools.r8.Internal.boolean foo(int,int)(Internal.java)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/VerboseUnknownStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/VerboseUnknownStackTrace.java
new file mode 100644
index 0000000..3cb6808
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/VerboseUnknownStackTrace.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class VerboseUnknownStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "java.lang.IndexOutOfBoundsException", "\tat java.util.ArrayList.get(ArrayList.java:411)");
+  }
+
+  @Override
+  public String mapping() {
+    return "";
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "java.lang.IndexOutOfBoundsException", "\tat java.util.ArrayList.get(ArrayList.java:411)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 6f7c644..96c6fb3 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -3,25 +3,28 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import static com.android.tools.r8.ToolHelper.DEFAULT_PROGUARD_MAP_FILE;
+import static org.junit.Assume.assumeFalse;
 
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder.DiagnosticsConsumer;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
+import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundFieldSubject;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
-import com.google.common.collect.ImmutableList;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -31,8 +34,7 @@
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.rules.TemporaryFolder;
+import org.junit.runners.Parameterized.Parameters;
 
 /**
  * Base class of individual tree shaking tests in com.android.tools.r8.shaking.examples.
@@ -46,94 +48,50 @@
  */
 public abstract class TreeShakingTest extends TestBase {
 
-  private Path proguardMap;
-  private Path out;
+  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  public static List<Object[]> defaultTreeShakingParameters() {
+    return data(Frontend.values(), MinifyMode.values());
+  }
+
+  public static List<Object[]> data(MinifyMode[] minifyModes) {
+    return data(Frontend.values(), minifyModes);
+  }
+
+  public static List<Object[]> data(Frontend[] frontends, MinifyMode[] minifyModes) {
+    return buildParameters(
+        frontends, getTestParameters().withAllRuntimesAndApiLevels().build(), minifyModes);
+  }
+
+  protected abstract String getName();
+
+  protected abstract String getMainClass();
 
   protected enum Frontend {
     DEX, JAR
   }
 
-  private final String name;
-  private final String mainClass;
   private final Frontend frontend;
-  private final Backend backend;
+  private final TestParameters parameters;
   private final MinifyMode minify;
 
   public Frontend getFrontend() {
     return frontend;
   }
 
-  public Backend getBackend() {
-    return backend;
+  public TestParameters getParameters() {
+    return parameters;
   }
 
   public MinifyMode getMinify() {
     return minify;
   }
 
-  @Rule
-  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
-  @Rule
-  public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
-
-  protected TreeShakingTest(
-      String name, String mainClass, Frontend frontend, Backend backend, MinifyMode minify) {
-    this.name = name;
-    this.mainClass = mainClass;
+  public TreeShakingTest(Frontend frontend, TestParameters parameters, MinifyMode minify) {
     this.frontend = frontend;
-    this.backend = backend;
+    this.parameters = parameters;
     this.minify = minify;
   }
 
-  private void generateTreeShakedVersion(
-      Backend backend,
-      String programFile,
-      List<Path> jarLibraries,
-      MinifyMode minify,
-      List<String> keepRulesFiles,
-      Consumer<InternalOptions> optionsConsumer)
-      throws Exception {
-    out = temp.getRoot().toPath().resolve("out.zip");
-    proguardMap = temp.getRoot().toPath().resolve(DEFAULT_PROGUARD_MAP_FILE);
-    // Generate R8 processed version without library option.
-    boolean inline = programFile.contains("inlining");
-
-    R8Command.Builder builder =
-        ToolHelper.addProguardConfigurationConsumer(
-                R8Command.builder(),
-                pgConfig -> {
-                  pgConfig.setPrintMapping(true);
-                  pgConfig.setPrintMappingFile(proguardMap);
-                  pgConfig.setOverloadAggressively(minify == MinifyMode.AGGRESSIVE);
-                  if (!minify.isMinify()) {
-                    pgConfig.disableObfuscation();
-                  }
-                })
-            .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
-            .addLibraryFiles(jarLibraries);
-    switch (backend) {
-      case CF:
-        builder.setOutput(out, OutputMode.ClassFile);
-        break;
-      case DEX:
-        builder.setOutput(out, OutputMode.DexIndexed);
-        break;
-      default:
-        throw new Unreachable();
-    }
-    ToolHelper.getAppBuilder(builder).addProgramFiles(Paths.get(programFile));
-    ToolHelper.allowTestProguardOptions(builder);
-    ToolHelper.runR8(
-        builder.build(),
-        options -> {
-          options.enableInlining = inline;
-          if (optionsConsumer != null) {
-            optionsConsumer.accept(options);
-          }
-        });
-  }
-
   protected static void checkSameStructure(CodeInspector ref, CodeInspector inspector) {
     ref.forAllClasses(
         refClazz ->
@@ -169,59 +127,82 @@
   }
 
   protected void runTest(
-      Consumer<CodeInspector> inspection,
+      ThrowingConsumer<CodeInspector, Exception> inspection,
       BiConsumer<String, String> outputComparator,
       BiConsumer<CodeInspector, CodeInspector> dexComparator,
       List<String> keepRulesFiles)
       throws Exception {
-    runTest(inspection, outputComparator, dexComparator, keepRulesFiles, null);
+    runTest(inspection, outputComparator, dexComparator, keepRulesFiles, null, null, null);
   }
 
   protected void runTest(
-      Consumer<CodeInspector> inspection,
+      ThrowingConsumer<CodeInspector, Exception> inspection,
       BiConsumer<String, String> outputComparator,
       BiConsumer<CodeInspector, CodeInspector> dexComparator,
       List<String> keepRulesFiles,
       Consumer<InternalOptions> optionsConsumer)
       throws Exception {
-    String originalDex = ToolHelper.TESTS_BUILD_DIR + name + "/classes.dex";
-    String programFile;
-    if (frontend == Frontend.DEX) {
-      programFile = originalDex;
-    } else {
-      programFile = ToolHelper.TESTS_BUILD_DIR + name + ".jar";
-    }
-    List<Path> jarLibraries;
-    if (backend == Backend.CF) {
-      jarLibraries =
-          ImmutableList.of(
-              ToolHelper.getJava8RuntimeJar(),
-              Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"));
-    } else {
-      jarLibraries =
-          ImmutableList.of(
-              ToolHelper.getDefaultAndroidJar(),
-              Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"));
-    }
+    runTest(
+        inspection, outputComparator, dexComparator, keepRulesFiles, optionsConsumer, null, null);
+  }
 
-    generateTreeShakedVersion(
-        backend, programFile, jarLibraries, minify, keepRulesFiles, optionsConsumer);
-
-    if (backend == Backend.CF) {
+  protected void runTest(
+      ThrowingConsumer<CodeInspector, Exception> inspection,
+      BiConsumer<String, String> outputComparator,
+      BiConsumer<CodeInspector, CodeInspector> dexComparator,
+      List<String> keepRulesFiles,
+      Consumer<InternalOptions> optionsConsumer,
+      ThrowableConsumer<R8FullTestBuilder> testBuilderConsumer,
+      DiagnosticsConsumer diagnosticsConsumer)
+      throws Exception {
+    assumeFalse(frontend == Frontend.DEX && parameters.isCfRuntime());
+    String originalDex = ToolHelper.TESTS_BUILD_DIR + getName() + "/classes.dex";
+    String programFile =
+        frontend == Frontend.DEX ? originalDex : ToolHelper.TESTS_BUILD_DIR + getName() + ".jar";
+    R8FullTestBuilder testBuilder =
+        testForR8(parameters.getBackend())
+            // Go through app builder to add dex files.
+            .apply(
+                b ->
+                    ToolHelper.getAppBuilder(b.getBuilder())
+                        .addProgramFiles(Paths.get(programFile)))
+            .enableProguardTestOptions()
+            .applyIf(minify.isAggressive(), b -> b.addKeepRules("-overloadaggressively"))
+            .minification(minify.isMinify())
+            .setMinApi(parameters.getApiLevel())
+            .addKeepRuleFiles(ListUtils.map(keepRulesFiles, Paths::get))
+            .addLibraryFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"))
+            .addDefaultRuntimeLibrary(parameters)
+            .addOptionsModification(
+                options -> {
+                  options.enableInlining = programFile.contains("inlining");
+                  if (optionsConsumer != null) {
+                    optionsConsumer.accept(options);
+                  }
+                })
+            .allowStdoutMessages()
+            .applyIf(testBuilderConsumer != null, testBuilderConsumer);
+    R8TestCompileResult compileResult =
+        diagnosticsConsumer == null
+            ? testBuilder.compile()
+            : testBuilder.compileWithExpectedDiagnostics(diagnosticsConsumer);
+    Path outJar = compileResult.writeToZip();
+    if (parameters.isCfRuntime()) {
       Path shakinglib = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "shakinglib.jar");
+      CfRuntime cfRuntime = parameters.getRuntime().asCf();
       ProcessResult resultInput =
-          ToolHelper.runJava(Arrays.asList(Paths.get(programFile), shakinglib), mainClass);
+          ToolHelper.runJava(
+              cfRuntime, Arrays.asList(Paths.get(programFile), shakinglib), getMainClass());
       Assert.assertEquals(0, resultInput.exitCode);
       ProcessResult resultOutput =
-          ToolHelper.runJava(Arrays.asList(out, shakinglib), mainClass);
+          ToolHelper.runJava(cfRuntime, Arrays.asList(outJar, shakinglib), getMainClass());
       if (outputComparator != null) {
         outputComparator.accept(resultInput.stdout, resultOutput.stdout);
       } else {
         Assert.assertEquals(resultInput.toString(), resultOutput.toString());
       }
       if (inspection != null) {
-        CodeInspector inspector = new CodeInspector(out, minify.isMinify() ? proguardMap : null);
-        inspection.accept(inspector);
+        compileResult.inspect(inspection);
       }
       return;
     }
@@ -231,35 +212,36 @@
     Consumer<ArtCommandBuilder> extraArtArgs = builder -> {
       builder.appendClasspath(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib/classes.dex");
     };
-
+    DexVm dexVm = parameters.getRuntime().asDex().getVm();
     if (Files.exists(Paths.get(originalDex))) {
       if (outputComparator != null) {
-        String output1 = ToolHelper.runArtNoVerificationErrors(
-            Collections.singletonList(originalDex), mainClass, extraArtArgs, null);
-        String output2 = ToolHelper.runArtNoVerificationErrors(
-            Collections.singletonList(out.toString()), mainClass, extraArtArgs, null);
+        String output1 =
+            ToolHelper.runArtNoVerificationErrors(
+                Collections.singletonList(originalDex), getMainClass(), extraArtArgs, dexVm);
+        String output2 =
+            ToolHelper.runArtNoVerificationErrors(
+                Collections.singletonList(outJar.toString()), getMainClass(), extraArtArgs, dexVm);
         outputComparator.accept(output1, output2);
       } else {
-        ToolHelper.checkArtOutputIdentical(Collections.singletonList(originalDex),
-            Collections.singletonList(out.toString()), mainClass,
-            extraArtArgs, null);
+        ToolHelper.checkArtOutputIdentical(
+            Collections.singletonList(originalDex),
+            Collections.singletonList(outJar.toString()),
+            getMainClass(),
+            extraArtArgs,
+            null);
       }
-
       if (dexComparator != null) {
         CodeInspector ref = new CodeInspector(Paths.get(originalDex));
-        CodeInspector inspector = new CodeInspector(out, minify.isMinify() ? proguardMap : null);
-        dexComparator.accept(ref, inspector);
+        dexComparator.accept(ref, compileResult.inspector());
       }
     } else {
       Assert.assertNull(outputComparator);
       Assert.assertNull(dexComparator);
       ToolHelper.runArtNoVerificationErrors(
-          Collections.singletonList(out.toString()), mainClass, extraArtArgs, null);
+          Collections.singletonList(outJar.toString()), getMainClass(), extraArtArgs, dexVm);
     }
-
     if (inspection != null) {
-      CodeInspector inspector = new CodeInspector(out, minify.isMinify() ? proguardMap : null);
-      inspection.accept(inspector);
+      compileResult.inspect(inspection);
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java
index fea123d..c20ebb6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java
@@ -3,10 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -17,18 +16,22 @@
 public class TreeShaking10Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking10Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking10", "shaking10.Shaking", frontend, backend, minify);
+  public TreeShaking10Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking10";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking10.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
index 3833645..47b696d 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import org.junit.Assert;
@@ -20,18 +19,22 @@
 public class TreeShaking11Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking11Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking11", "shaking11.Shaking", frontend, backend, minify);
+  public TreeShaking11Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking11";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking11.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
index 82ab49b..b30a14d 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
@@ -7,12 +7,11 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import org.junit.Assert;
@@ -25,18 +24,22 @@
 public class TreeShaking12Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking12Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking12", "shaking12.Shaking", frontend, backend, minify);
+  public TreeShaking12Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking12";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking12.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
index 5cdc80e..eab4bff 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -10,8 +11,6 @@
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import org.junit.Assert;
@@ -24,18 +23,22 @@
 public class TreeShaking13Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking13Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking13", "shaking13.Shaking", frontend, backend, minify);
+  public TreeShaking13Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking13";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking13.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
index 74a4efe..36b3b37 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
@@ -3,12 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -20,18 +19,22 @@
 public class TreeShaking14Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking14Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking14", "shaking14.Shaking", frontend, backend, minify);
+  public TreeShaking14Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking14";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking14.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
index 2b5cd16..7111f51 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
@@ -3,14 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 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.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -21,21 +24,22 @@
 public class TreeShaking15Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      if (minify == MinifyMode.NONE) {
-        continue;
-      }
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return data(MinifyMode.withoutNone());
   }
 
-  public TreeShaking15Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking15", "shaking15.Shaking", frontend, backend, minify);
+  public TreeShaking15Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking15";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking15.Shaking";
   }
 
   @Test
@@ -44,11 +48,16 @@
         TreeShaking15Test::shaking15testDictionary,
         null,
         null,
-        ImmutableList.of("src/test/examples/shaking15/keep-rules.txt"));
+        ImmutableList.of("src/test/examples/shaking15/keep-rules.txt"),
+        null,
+        R8TestBuilder::allowDiagnosticInfoMessages,
+        diagnostics ->
+            diagnostics.assertAllInfosMatch(
+                diagnosticMessage(containsString("Invalid character in dictionary"))));
   }
 
   private static void shaking15testDictionary(CodeInspector inspector) {
-    inspector.forAllClasses((clazz) -> checkClassAndMemberInDictionary(clazz));
+    inspector.forAllClasses(TreeShaking15Test::checkClassAndMemberInDictionary);
   }
 
   private static List<String> names =
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java
index dbae1da..785e505 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java
@@ -3,10 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -17,21 +16,22 @@
 public class TreeShaking16Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      if (minify == MinifyMode.NONE) {
-        continue;
-      }
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return data(MinifyMode.withoutNone());
   }
 
-  public TreeShaking16Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking16", "shaking16.Shaking", frontend, backend, minify);
+  public TreeShaking16Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking16";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking16.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
index 88d3b65..e2b7095 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
@@ -3,12 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import org.junit.Assert;
@@ -21,18 +20,22 @@
 public class TreeShaking17Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking17Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking17", "shaking17.Shaking", frontend, backend, minify);
+  public TreeShaking17Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking17";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking17.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
index f28f242..d91a2fd 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
@@ -5,11 +5,10 @@
 
 import static org.junit.Assert.assertFalse;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -20,18 +19,22 @@
 public class TreeShaking18Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking18Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking18", "shaking18.Shaking", frontend, backend, minify);
+  public TreeShaking18Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking18";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking18.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
index dcc71b0..e5f45bb 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
@@ -7,13 +7,12 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 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.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -24,18 +23,22 @@
 public class TreeShaking19Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking19Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking19", "shaking19.Shaking", frontend, backend, minify);
+  public TreeShaking19Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking19";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking19.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
index 6189f73..ad658b9 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
@@ -3,13 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import org.junit.Assert;
@@ -23,18 +22,22 @@
 public class TreeShaking1Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking1Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking1", "shaking1.Shaking", frontend, backend, minify);
+  public TreeShaking1Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking1";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking1.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
index 49a5910..d951404 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
@@ -3,12 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import org.junit.Assert;
@@ -21,18 +20,22 @@
 public class TreeShaking2Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking2Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking2", "shaking2.Shaking", frontend, backend, minify);
+  public TreeShaking2Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking2";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking2.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
index 49a1e14..38cee4e 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
@@ -3,12 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -20,18 +19,22 @@
 public class TreeShaking3Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking3Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking3", "shaking3.Shaking", frontend, backend, minify);
+  public TreeShaking3Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking3";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking3.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java
index d2e1cd0..abf9be6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java
@@ -3,10 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -17,18 +16,22 @@
 public class TreeShaking4Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking4Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking4", "shaking4.Shaking", frontend, backend, minify);
+  public TreeShaking4Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking4";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking4.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
index 212e883..cca8f8b 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import org.junit.Assert;
@@ -20,18 +19,22 @@
 public class TreeShaking5Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking5Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking5", "shaking5.Shaking", frontend, backend, minify);
+  public TreeShaking5Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking5";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking5.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
index 01c0de9..7a91541 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
@@ -3,12 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import org.junit.Assert;
@@ -21,18 +20,22 @@
 public class TreeShaking6Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking6Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking6", "shaking6.Shaking", frontend, backend, minify);
+  public TreeShaking6Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking6";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking6.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
index 4cbd1c0..8a6cf36 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
@@ -3,12 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -20,18 +19,22 @@
 public class TreeShaking7Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking7Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking7", "shaking7.Shaking", frontend, backend, minify);
+  public TreeShaking7Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking7";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking7.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
index 9b8d209..9da3d01 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
@@ -7,12 +7,11 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -24,18 +23,22 @@
 public class TreeShaking8Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking8Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking8", "shaking8.Shaking", frontend, backend, minify);
+  public TreeShaking8Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking8";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking8.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
index 8974d6b..ec8c6ad 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
@@ -3,12 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -20,18 +19,22 @@
 public class TreeShaking9Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShaking9Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/shaking9", "shaking9.Shaking", frontend, backend, minify);
+  public TreeShaking9Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/shaking9";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking9.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
index c204e06..b008254 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
@@ -3,10 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -17,24 +16,23 @@
 public class TreeShakingAbstractMethodRemovalTest extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
   public TreeShakingAbstractMethodRemovalTest(
-      Frontend frontend, Backend backend, MinifyMode minify) {
-    super(
-        "examples/abstractmethodremoval",
-        "abstractmethodremoval.AbstractMethodRemoval",
-        frontend,
-        backend,
-        minify);
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/abstractmethodremoval";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "abstractmethodremoval.AbstractMethodRemoval";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java
index 4d64e0e..0c65f06 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java
@@ -3,10 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -17,17 +16,22 @@
 public class TreeShakingAndroidNTest extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return data(new Frontend[] {Frontend.JAR}, MinifyMode.values());
   }
 
-  public TreeShakingAndroidNTest(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examplesAndroidN/shaking", "shaking.Shaking", frontend, backend, minify);
+  public TreeShakingAndroidNTest(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examplesAndroidN/shaking";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "shaking.Shaking";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
index 1c9a3d0..728aa09 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
@@ -3,12 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -20,23 +19,23 @@
 public class TreeShakingAnnotationremovalTest extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAnnotationremovalTest(Frontend frontend, Backend backend, MinifyMode minify) {
-    super(
-        "examples/annotationremoval",
-        "annotationremoval.Annotationremoval",
-        frontend,
-        backend,
-        minify);
+  public TreeShakingAnnotationremovalTest(
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/annotationremoval";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "annotationremoval.Annotationremoval";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java
index 9c2c387..1aa50e1 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -19,24 +18,23 @@
 public class TreeShakingAssumenosideeffects1Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
   public TreeShakingAssumenosideeffects1Test(
-      Frontend frontend, Backend backend, MinifyMode minify) {
-    super(
-        "examples/assumenosideeffects1",
-        "assumenosideeffects1.Assumenosideeffects",
-        frontend,
-        backend,
-        minify);
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/assumenosideeffects1";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "assumenosideeffects1.Assumenosideeffects";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java
index e4b9ed6..a57e98f 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Ignore;
@@ -21,24 +20,23 @@
 public class TreeShakingAssumenosideeffects2Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
   public TreeShakingAssumenosideeffects2Test(
-      Frontend frontend, Backend backend, MinifyMode minify) {
-    super(
-        "examples/assumenosideeffects2",
-        "assumenosideeffects2.Assumenosideeffects",
-        frontend,
-        backend,
-        minify);
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/assumenosideeffects2";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "assumenosideeffects2.Assumenosideeffects";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java
index 72b6d17..b83e0b9 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -19,24 +18,23 @@
 public class TreeShakingAssumenosideeffects3Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
   public TreeShakingAssumenosideeffects3Test(
-      Frontend frontend, Backend backend, MinifyMode minify) {
-    super(
-        "examples/assumenosideeffects3",
-        "assumenosideeffects3.Assumenosideeffects",
-        frontend,
-        backend,
-        minify);
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/assumenosideeffects3";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "assumenosideeffects3.Assumenosideeffects";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java
index c36ddd7..56083fc 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -19,24 +18,23 @@
 public class TreeShakingAssumenosideeffects4Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
   public TreeShakingAssumenosideeffects4Test(
-      Frontend frontend, Backend backend, MinifyMode minify) {
-    super(
-        "examples/assumenosideeffects4",
-        "assumenosideeffects4.Assumenosideeffects",
-        frontend,
-        backend,
-        minify);
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/assumenosideeffects4";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "assumenosideeffects4.Assumenosideeffects";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java
index 60cbd65..fa138dd 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -19,24 +18,23 @@
 public class TreeShakingAssumenosideeffects5Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
   public TreeShakingAssumenosideeffects5Test(
-      Frontend frontend, Backend backend, MinifyMode minify) {
-    super(
-        "examples/assumenosideeffects5",
-        "assumenosideeffects5.Assumenosideeffects",
-        frontend,
-        backend,
-        minify);
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/assumenosideeffects5";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "assumenosideeffects5.Assumenosideeffects";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java
index 59d8dd2..172c64d 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -19,24 +18,23 @@
 public class TreeShakingAssumenosideeffects6Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
   public TreeShakingAssumenosideeffects6Test(
-      Frontend frontend, Backend backend, MinifyMode minify) {
-    super(
-        "examples/assumenosideeffects6",
-        "assumenosideeffects6.Assumenosideeffects",
-        frontend,
-        backend,
-        minify);
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/assumenosideeffects6";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "assumenosideeffects6.Assumenosideeffects";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java
index 15e8195..45511b7 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -19,18 +18,23 @@
 public class TreeShakingAssumevalues1Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumevalues1Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/assumevalues1", "assumevalues1.Assumevalues", frontend, backend, minify);
+  public TreeShakingAssumevalues1Test(
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/assumevalues1";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "assumevalues1.Assumevalues";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java
index 6f2d721..88ca339 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -19,18 +18,23 @@
 public class TreeShakingAssumevalues2Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumevalues2Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/assumevalues2", "assumevalues2.Assumevalues", frontend, backend, minify);
+  public TreeShakingAssumevalues2Test(
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/assumevalues2";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "assumevalues2.Assumevalues";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java
index a765f30..5237778 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -19,18 +18,23 @@
 public class TreeShakingAssumevalues3Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumevalues3Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/assumevalues3", "assumevalues3.Assumevalues", frontend, backend, minify);
+  public TreeShakingAssumevalues3Test(
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/assumevalues3";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "assumevalues3.Assumevalues";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java
index 61b4154..4790850 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -19,18 +18,23 @@
 public class TreeShakingAssumevalues4Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumevalues4Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/assumevalues4", "assumevalues4.Assumevalues", frontend, backend, minify);
+  public TreeShakingAssumevalues4Test(
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/assumevalues4";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "assumevalues4.Assumevalues";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java
index 48ef889..9304ed8 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -19,18 +18,23 @@
 public class TreeShakingAssumevalues5Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumevalues5Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/assumevalues5", "assumevalues5.Assumevalues", frontend, backend, minify);
+  public TreeShakingAssumevalues5Test(
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/assumevalues5";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "assumevalues5.Assumevalues";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
index de515d0..59c535d 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
@@ -3,14 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.ConstStringInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -22,24 +21,31 @@
 public class TreeShakingAssumevalues6Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumevalues6Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/assumevalues6", "assumevalues6.Assumevalues", frontend, backend, minify);
+  public TreeShakingAssumevalues6Test(
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/assumevalues6";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "assumevalues6.Assumevalues";
   }
 
   @Test
   public void test() throws Exception {
     runTest(
-        getBackend() == Backend.DEX ? TreeShakingAssumevalues6Test::assumevalues6CheckCode : null,
+        getParameters().isDexRuntime()
+            ? TreeShakingAssumevalues6Test::assumevalues6CheckCode
+            : null,
         TreeShakingAssumevalues6Test::assumevalues6CheckOutput,
         null,
         ImmutableList.of("src/test/examples/assumevalues6/keep-rules.txt"));
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java
index e02b4cd..c6ca232 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java
@@ -3,14 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.ConstStringInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
@@ -22,24 +21,31 @@
 public class TreeShakingAssumevalues7Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumevalues7Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/assumevalues7", "assumevalues7.Assumevalues", frontend, backend, minify);
+  public TreeShakingAssumevalues7Test(
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/assumevalues7";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "assumevalues7.Assumevalues";
   }
 
   @Test
   public void test() throws Exception {
     runTest(
-        getBackend() == Backend.DEX ? TreeShakingAssumevalues7Test::assumevalues7CheckCode : null,
+        getParameters().isDexRuntime()
+            ? TreeShakingAssumevalues7Test::assumevalues7CheckCode
+            : null,
         TreeShakingAssumevalues7Test::assumevalues7CheckOutput,
         null,
         ImmutableList.of("src/test/examples/assumevalues7/keep-rules.txt"));
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
index a6fb153..08dfc6a 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -17,18 +17,22 @@
 public class TreeShakingInliningTest extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShakingInliningTest(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/inlining", "inlining.Inlining", frontend, backend, minify);
+  public TreeShakingInliningTest(Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/inlining";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "inlining.Inlining";
   }
 
   @Test
@@ -39,11 +43,12 @@
   @Test
   public void testKeeprulesdiscard() throws Exception {
     // On the cf backend, we don't inline into constructors, see: b/136250031
-    List<String> keepRules = getBackend() == Backend.CF
-        ? ImmutableList.of("src/test/examples/inlining/keep-rules-discard.txt")
-        : ImmutableList.of("src/test/examples/inlining/keep-rules-discard.txt",
-            "src/test/examples/inlining/keep-rules-discard-constructor.txt");
-    runTest(
-        null, null, null, keepRules);
+    List<String> keepRules =
+        getParameters().isCfRuntime()
+            ? ImmutableList.of("src/test/examples/inlining/keep-rules-discard.txt")
+            : ImmutableList.of(
+                "src/test/examples/inlining/keep-rules-discard.txt",
+                "src/test/examples/inlining/keep-rules-discard-constructor.txt");
+    runTest(null, null, null, keepRules, null, TestCompilerBuilder::allowStderrMessages, null);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java
index 779c140..95a0876 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java
@@ -3,10 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -17,19 +16,23 @@
 public class TreeShakingMemberrebinding2Test extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShakingMemberrebinding2Test(Frontend frontend, Backend backend, MinifyMode minify) {
-    super(
-        "examples/memberrebinding2", "memberrebinding2.Memberrebinding", frontend, backend, minify);
+  public TreeShakingMemberrebinding2Test(
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/memberrebinding2";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "memberrebinding2.Memberrebinding";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
index 7ca8334..b8151d6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
@@ -3,10 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -18,18 +17,23 @@
 public class TreeShakingMinificationTest extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return defaultTreeShakingParameters();
   }
 
-  public TreeShakingMinificationTest(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/minification", "minification.Minification", frontend, backend, minify);
+  public TreeShakingMinificationTest(
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/minification";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "minification.Minification";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java
index 0a48ba2..7223763 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java
@@ -3,10 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -17,21 +16,23 @@
 public class TreeShakingMinifygenericTest extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      if (minify == MinifyMode.NONE) {
-        continue;
-      }
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return data(MinifyMode.withoutNone());
   }
 
-  public TreeShakingMinifygenericTest(Frontend frontend, Backend backend, MinifyMode minify) {
-    super("examples/minifygeneric", "minifygeneric.Minifygeneric", frontend, backend, minify);
+  public TreeShakingMinifygenericTest(
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/minifygeneric";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "minifygeneric.Minifygeneric";
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java
index 977de65..968ef92 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java
@@ -3,10 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -17,27 +16,23 @@
 public class TreeShakingMinifygenericwithinnerTest extends TreeShakingTest {
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (MinifyMode minify : MinifyMode.values()) {
-      if (minify == MinifyMode.NONE) {
-        continue;
-      }
-      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
-      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
-      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
-    }
-    return parameters;
+  public static List<Object[]> data() {
+    return data(MinifyMode.withoutNone());
   }
 
   public TreeShakingMinifygenericwithinnerTest(
-      Frontend frontend, Backend backend, MinifyMode minify) {
-    super(
-        "examples/minifygenericwithinner",
-        "minifygenericwithinner.Minifygenericwithinner",
-        frontend,
-        backend,
-        minify);
+      Frontend frontend, TestParameters parameters, MinifyMode minify) {
+    super(frontend, parameters, minify);
+  }
+
+  @Override
+  protected String getName() {
+    return "examples/minifygenericwithinner";
+  }
+
+  @Override
+  protected String getMainClass() {
+    return "minifygenericwithinner.Minifygenericwithinner";
   }
 
   @Test