Merge "Allow for emitting tableswitches"
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index d5221b2..6914bc1 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -145,15 +145,14 @@
   }
 
   @Override
-  public void allocateRegisters(boolean debug) {
-    assert options.debug == debug;
-    allocateRegisters();
-  }
-
   public void allocateRegisters() {
     computeNeedsRegister();
     ImmutableList<BasicBlock> blocks = computeLivenessInformation();
     performLinearScan();
+    // Even if the method is reachability sensitive, we do not compute debug information after
+    // register allocation. We just treat the method as being in debug mode in order to keep
+    // locals alive for their entire live range. In release mode the liveness is all that matters
+    // and we do not actually want locals information in the output.
     if (options.debug) {
       LinearScanRegisterAllocator.computeDebugInfo(blocks, liveIntervals, this, liveAtEntrySets);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 150e516..fd04436 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -3,16 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
 import java.util.Collections;
+import java.util.Deque;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -243,32 +242,19 @@
    * <p>The returned set of methods all have {@code callSite.methodName} as the method name.
    *
    * @param callSite Call site to resolve.
-   * @param reporter Reporter used when an unknown metafactory is encountered.
    * @return Methods implemented by the lambda expression that created the {@code callSite}.
    */
-  public Set<DexEncodedMethod> lookupLambdaImplementedMethods(
-      DexCallSite callSite, Reporter reporter) {
+  public Set<DexEncodedMethod> lookupLambdaImplementedMethods(DexCallSite callSite) {
     List<DexType> callSiteInterfaces = LambdaDescriptor.getInterfaces(callSite, this);
-    if (callSiteInterfaces == null) {
-      if (!isStringConcat(callSite.bootstrapMethod)) {
-        if (reporter != null) {
-          Diagnostic message =
-              new StringDiagnostic("Unknown bootstrap method " + callSite.bootstrapMethod);
-          reporter.warning(message);
-        }
-      }
+    if (callSiteInterfaces == null || callSiteInterfaces.isEmpty()) {
       return Collections.emptySet();
     }
     Set<DexEncodedMethod> result = new HashSet<>();
-    for (DexType iface : callSiteInterfaces) {
+    Deque<DexType> worklist = new ArrayDeque<>(callSiteInterfaces);
+    Set<DexType> visited = Sets.newIdentityHashSet();
+    while (!worklist.isEmpty()) {
+      DexType iface = worklist.removeFirst();
       if (iface.isUnknown()) {
-        if (reporter != null) {
-          StringDiagnostic message =
-              new StringDiagnostic(
-                  "Lambda expression implements missing library interface "
-                      + iface.toSourceString());
-          reporter.warning(message);
-        }
         // Skip this interface. If the lambda only implements missing library interfaces and not any
         // program interfaces, then minification and tree shaking are not interested in this
         // DexCallSite anyway, so skipping this interface is harmless. On the other hand, if
@@ -279,21 +265,26 @@
         // anyway.
         continue;
       }
+      if (!visited.add(iface)) {
+        // Already visited previously. May happen due to "diamond shapes" in the interface
+        // hierarchy.
+        continue;
+      }
       assert iface.isInterface();
       DexClass clazz = definitionFor(iface);
       if (clazz != null) {
-        clazz.forEachMethod(
-            method -> {
-              if (method.method.name == callSite.methodName && method.accessFlags.isAbstract()) {
-                result.add(method);
-              }
-            });
+        for (DexEncodedMethod method : clazz.virtualMethods()) {
+          if (method.method.name == callSite.methodName && method.accessFlags.isAbstract()) {
+            result.add(method);
+          }
+        }
+        Collections.addAll(worklist, clazz.interfaces.values);
       }
     }
     return result;
   }
 
-  private boolean isStringConcat(DexMethodHandle bootstrapMethod) {
+  public boolean isStringConcat(DexMethodHandle bootstrapMethod) {
     return bootstrapMethod.type.isInvokeStatic()
         && (bootstrapMethod.asMethod() == dexItemFactory.stringConcatWithConstantsMethod
             || bootstrapMethod.asMethod() == dexItemFactory.stringConcatMethod);
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 4d7a9e1..0474696 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -34,7 +34,6 @@
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -669,6 +668,7 @@
                   new ForwardMethodSourceCode(
                       accessFlags.isStatic() ? null : holder.type,
                       newMethod,
+                      newMethod,
                       accessFlags.isStatic() ? null : method.holder,
                       method,
                       type,
@@ -847,6 +847,11 @@
     }
 
     @Override
+    public boolean isReachabilitySensitive() {
+      return false;
+    }
+
+    @Override
     public boolean returnsArgument() {
       return false;
     }
@@ -956,6 +961,7 @@
     // TODO(b/71500340): We call this *hint* because it does not 100% guarantee that a parameter is
     // not null when the method returns normally. Maybe nonNullParamOnNormalExit in the future.
     private BitSet nonNullParamHints = null;
+    private boolean reachabilitySensitive = false;
 
     private OptimizationInfoImpl() {
       // Intentionally left empty, just use the default values.
@@ -977,6 +983,7 @@
       initializerEnablingJavaAssertions = template.initializerEnablingJavaAssertions;
       parametersUsages = template.parametersUsages;
       nonNullParamHints = template.nonNullParamHints;
+      reachabilitySensitive = template.reachabilitySensitive;
     }
 
     @Override
@@ -995,6 +1002,11 @@
     }
 
     @Override
+    public boolean isReachabilitySensitive() {
+      return reachabilitySensitive;
+    }
+
+    @Override
     public boolean returnsArgument() {
       return returnedArgument != -1;
     }
@@ -1077,6 +1089,11 @@
     }
 
     @Override
+    public void setReachabilitySensitive(boolean reachabilitySensitive) {
+      this.reachabilitySensitive = reachabilitySensitive;
+    }
+
+    @Override
     public void setClassInlinerEligibility(ClassInlinerEligibility eligibility) {
       this.classInlinerEligibility = eligibility;
     }
@@ -1186,11 +1203,11 @@
     optimizationInfo = info;
   }
 
-  public void copyMetadataFromInlinee(DexEncodedMethod inlinee, OptimizationFeedback feedback) {
+  public void copyMetadataFromInlinee(DexEncodedMethod inlinee) {
     checkIfObsolete();
     // Record that the current method uses identifier name string if the inlinee did so.
     if (inlinee.getOptimizationInfo().useIdentifierNameString()) {
-      feedback.markUseIdentifierNameString(this);
+      getMutableOptimizationInfo().markUseIdentifierNameString();
     }
     if (inlinee.classFileVersion > classFileVersion) {
       upgradeClassFileVersion(inlinee.getClassFileVersion());
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 6503f11..0450937 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -272,6 +272,8 @@
       createType("Ldalvik/annotation/codegen/CovariantReturnType;");
   public final DexType annotationCovariantReturnTypes =
       createType("Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;");
+  public final DexType annotationReachabilitySensitive =
+      createType("Ldalvik/annotation/optimization/ReachabilitySensitive;");
 
   private static final String METAFACTORY_METHOD_NAME = "metafactory";
   private static final String METAFACTORY_ALT_METHOD_NAME = "altMetafactory";
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 1516398..a895aa7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -382,4 +382,44 @@
     assert classFileVersion != -1;
     return classFileVersion;
   }
+
+  /**
+   * Is the class reachability sensitive.
+   *
+   * <p>A class is reachability sensitive if the
+   * dalvik.annotation.optimization.ReachabilitySensitive annotation is on any field or method. When
+   * that is the case, dead reference elimination is disabled and locals are kept alive for their
+   * entire scope.
+   */
+  public boolean hasReachabilitySensitiveAnnotation(DexItemFactory factory) {
+    for (DexEncodedMethod directMethod : directMethods) {
+      for (DexAnnotation annotation : directMethod.annotations.annotations) {
+        if (annotation.annotation.type == factory.annotationReachabilitySensitive) {
+          return true;
+        }
+      }
+    }
+    for (DexEncodedMethod virtualMethod : virtualMethods) {
+      for (DexAnnotation annotation : virtualMethod.annotations.annotations) {
+        if (annotation.annotation.type == factory.annotationReachabilitySensitive) {
+          return true;
+        }
+      }
+    }
+    for (DexEncodedField staticField : staticFields) {
+      for (DexAnnotation annotation : staticField.annotations.annotations) {
+        if (annotation.annotation.type == factory.annotationReachabilitySensitive) {
+          return true;
+        }
+      }
+    }
+    for (DexEncodedField instanceField : instanceFields) {
+      for (DexAnnotation annotation : instanceField.annotations.annotations) {
+        if (annotation.annotation.type == factory.annotationReachabilitySensitive) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index d68cb7d..27e91ef 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -84,13 +85,15 @@
 
   public static class Builder {
 
-    protected Builder() {
-    }
+    protected Builder() {}
 
     protected final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
     protected final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
     protected final Map<DexField, DexField> fieldMap = new IdentityHashMap<>();
 
+    private final BiMap<DexField, DexField> originalFieldSignatures = HashBiMap.create();
+    private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+
     public void map(DexType from, DexType to) {
       typeMap.put(from, to);
     }
@@ -103,6 +106,16 @@
       fieldMap.put(from, to);
     }
 
+    public void move(DexMethod from, DexMethod to) {
+      map(from, to);
+      originalMethodSignatures.put(to, from);
+    }
+
+    public void move(DexField from, DexField to) {
+      fieldMap.put(from, to);
+      originalFieldSignatures.put(to, from);
+    }
+
     public GraphLense build(DexItemFactory dexItemFactory) {
       return build(dexItemFactory, new IdentityGraphLense());
     }
@@ -112,7 +125,13 @@
         return previousLense;
       }
       return new NestedGraphLense(
-          typeMap, methodMap, fieldMap, null, null, previousLense, dexItemFactory);
+          typeMap,
+          methodMap,
+          fieldMap,
+          originalFieldSignatures,
+          originalMethodSignatures,
+          previousLense,
+          dexItemFactory);
     }
 
   }
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 99e544a..c2f4943 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -122,11 +122,10 @@
       Origin origin) {
     assert getOwner() == encodedMethod;
     triggerDelayedParsingIfNeccessary();
-    return options.debug
+    return options.debug || encodedMethod.getOptimizationInfo().isReachabilitySensitive()
         ? internalBuildWithLocals(
             encodedMethod, encodedMethod, appInfo, graphLense, options, null, null)
-        : internalBuild(
-            encodedMethod, encodedMethod, appInfo, graphLense, options, null, null);
+        : internalBuild(encodedMethod, encodedMethod, appInfo, graphLense, options, null, null);
   }
 
   @Override
@@ -142,7 +141,7 @@
     assert getOwner() == encodedMethod;
     assert generator != null;
     triggerDelayedParsingIfNeccessary();
-    return options.debug
+    return options.debug || encodedMethod.getOptimizationInfo().isReachabilitySensitive()
         ? internalBuildWithLocals(
             context, encodedMethod, appInfo, graphLense, options, generator, callerPosition)
         : internalBuild(
@@ -176,7 +175,9 @@
       InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition) {
-    if (!options.debug || !options.proguardConfiguration.getKeepAttributes().localVariableTable) {
+    if (!(encodedMethod.getOptimizationInfo().isReachabilitySensitive()
+        || (options.debug
+            && options.proguardConfiguration.getKeepAttributes().localVariableTable))) {
       node.localVariables.clear();
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java
index c693ea4..4fc81d0 100644
--- a/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java
@@ -21,6 +21,8 @@
 
   BitSet getNonNullParamHints();
 
+  boolean isReachabilitySensitive();
+
   boolean returnsArgument();
 
   int getReturnedArgument();
diff --git a/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
index d7ae7ae..cb93ee0 100644
--- a/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
@@ -31,6 +31,8 @@
 
   void setNonNullParamHints(BitSet hints);
 
+  void setReachabilitySensitive(boolean reachabilitySensitive);
+
   void markUseIdentifierNameString();
 
   void markForceInline();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index e348d10..e11f1bd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -635,7 +635,9 @@
   private boolean consistentBlockInstructions() {
     boolean argumentsAllowed = true;
     for (BasicBlock block : blocks) {
-      block.consistentBlockInstructions(argumentsAllowed, options.debug);
+      block.consistentBlockInstructions(
+          argumentsAllowed,
+          options.debug || method.getOptimizationInfo().isReachabilitySensitive());
       argumentsAllowed = false;
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 190ab3a..7cdcd76 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -63,7 +63,8 @@
 
   @Override
   public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
-    return !code.options.debug && code.options.isGeneratingDex();
+    return !(code.options.debug || code.method.getOptimizationInfo().isReachabilitySensitive())
+        && code.options.isGeneratingDex();
   }
 
   @Override
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 eda63f0..953a7f2 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
@@ -458,7 +458,7 @@
   }
 
   public boolean isDebugMode() {
-    return options.debug;
+    return options.debug || method.getOptimizationInfo().isReachabilitySensitive();
   }
 
   public Int2ReferenceSortedMap<BlockInfo> getCFG() {
@@ -630,7 +630,7 @@
 
   private boolean insertDebugPositions() {
     boolean hasDebugPositions = false;
-    if (!options.debug) {
+    if (!isDebugMode()) {
       return hasDebugPositions;
     }
     for (BasicBlock block : blocks) {
@@ -847,7 +847,7 @@
 
   public void addDebugLocalStart(int register, DebugLocalInfo local) {
     assert local != null;
-    if (!options.debug) {
+    if (!isDebugMode()) {
       return;
     }
     // If the local was not introduced by the previous instruction, start it here.
@@ -867,7 +867,7 @@
 
   public void addDebugLocalEnd(int register, DebugLocalInfo local) {
     assert local != null;
-    if (!options.debug) {
+    if (!isDebugMode()) {
       return;
     }
     Value value = readRegisterForDebugLocal(register, local);
@@ -877,7 +877,7 @@
   }
 
   public void addDebugPosition(Position position) {
-    if (options.debug) {
+    if (isDebugMode()) {
       assert previousLocalValue == null;
       assert source.getCurrentPosition().equals(position);
       if (!debugLocalEnds.isEmpty()) {
@@ -1096,7 +1096,7 @@
 
   public void addMove(ValueTypeConstraint constraint, int dest, int src) {
     Value in = readRegister(src, constraint);
-    if (options.debug) {
+    if (isDebugMode()) {
       // If the move is writing to a different local we must construct a new value.
       DebugLocalInfo destLocal = getOutgoingLocal(dest);
       if (destLocal != null && destLocal != in.getLocalInfo()) {
@@ -1876,7 +1876,7 @@
   }
 
   private Value readRegisterForDebugLocal(int register, DebugLocalInfo local) {
-    assert options.debug;
+    assert isDebugMode();
     ValueTypeConstraint type = ValueTypeConstraint.fromDexType(local.type);
     return readRegister(register, type, currentBlock, EdgeType.NON_EDGE, RegisterReadType.DEBUG);
   }
@@ -1959,7 +1959,7 @@
   }
 
   private DebugLocalInfo getIncomingLocalAtBlock(int register, BasicBlock block) {
-    if (options.debug) {
+    if (isDebugMode()) {
       int blockOffset = offsets.getInt(block);
       return source.getIncomingLocalAtBlock(register, blockOffset);
     }
@@ -2055,11 +2055,11 @@
   }
 
   private DebugLocalInfo getIncomingLocal(int register) {
-    return options.debug ? source.getIncomingLocal(register) : null;
+    return isDebugMode() ? source.getIncomingLocal(register) : null;
   }
 
   private DebugLocalInfo getOutgoingLocal(int register) {
-    return options.debug ? source.getOutgoingLocal(register) : null;
+    return isDebugMode() ? source.getOutgoingLocal(register) : null;
   }
 
   private void checkRegister(int register) {
@@ -2161,7 +2161,7 @@
   }
 
   private void attachLocalValues(Instruction ir) {
-    if (!options.debug) {
+    if (!isDebugMode()) {
       assert previousLocalValue == null;
       assert debugLocalEnds.isEmpty();
       return;
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 fdc47c0..05fd603 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
@@ -438,18 +438,22 @@
   }
 
   private void convertMethodsToDex(DexProgramClass clazz) {
+    boolean isReachabilitySensitive = clazz.hasReachabilitySensitiveAnnotation(options.itemFactory);
     // When converting all methods on a class always convert <clinit> first.
     for (DexEncodedMethod method : clazz.directMethods()) {
       if (method.isClassInitializer()) {
+        method.getMutableOptimizationInfo().setReachabilitySensitive(isReachabilitySensitive);
         convertMethodToDex(method);
         break;
       }
     }
-    clazz.forEachMethod(method -> {
-      if (!method.isClassInitializer()) {
-        convertMethodToDex(method);
-      }
-    });
+    clazz.forEachMethod(
+        method -> {
+          if (!method.isClassInitializer()) {
+            method.getMutableOptimizationInfo().setReachabilitySensitive(isReachabilitySensitive);
+            convertMethodToDex(method);
+          }
+        });
   }
 
   private void convertMethodToDex(DexEncodedMethod method) {
@@ -478,6 +482,7 @@
 
   public DexApplication optimize(DexApplication application, ExecutorService executorService)
       throws ExecutionException {
+    computeReachabilitySensitivity(application);
     removeLambdaDeserializationMethods();
     collectLambdaMergingCandidates(application);
     collectStaticizerCandidates(application);
@@ -566,6 +571,14 @@
     return builder.build();
   }
 
+  private void computeReachabilitySensitivity(DexApplication application) {
+    application.classes().forEach(c -> {
+      if (c.hasReachabilitySensitiveAnnotation(options.itemFactory)) {
+        c.methods().forEach(m -> m.getMutableOptimizationInfo().setReachabilitySensitive(true));
+      }
+    });
+  }
+
   private void forEachSelectedOutliningMethod(
       ExecutorService executorService, BiConsumer<IRCode, DexEncodedMethod> consumer)
       throws ExecutionException {
@@ -812,7 +825,9 @@
       CodeRewriter.ensureDirectStringNewToInit(code);
     }
 
-    if (options.debug) {
+    boolean isDebugMode = options.debug || method.getOptimizationInfo().isReachabilitySensitive();
+
+    if (isDebugMode) {
       codeRewriter.simplifyDebugLocals(code);
     }
 
@@ -849,7 +864,7 @@
     }
 
     if (identifierNameStringMarker != null) {
-      identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(method, code, feedback);
+      identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(method, code);
       assert code.isConsistentSSA();
     }
 
@@ -867,10 +882,15 @@
       nonNullTracker.addNonNull(code);
       assert code.isConsistentSSA();
     }
-    if (options.enableInlining && inliner != null) {
+    if (!isDebugMode && options.enableInlining && inliner != null) {
       // TODO(zerny): Should we support inlining in debug mode? b/62937285
-      assert !options.debug;
-      inliner.performInlining(method, code, isProcessedConcurrently, callSiteInformation, feedback);
+      inliner.performInlining(method, code, isProcessedConcurrently, callSiteInformation);
+    }
+
+    // Either marked by IdentifierNameStringMarker or propagated from inlinee.
+    // Then, make it visible to IdentifierMinifier.
+    if (method.getOptimizationInfo().useIdentifierNameString()) {
+      feedback.markUseIdentifierNameString(method);
     }
 
     if (appInfo.hasLiveness()) {
@@ -878,7 +898,7 @@
       codeRewriter.rewriteGetClass(code);
     }
 
-    if (!options.debug) {
+    if (!isDebugMode) {
       // TODO(jsjeon): Consider merging these into one single optimize().
       stringOptimizer.computeTrivialOperationsOnConstString(code, appInfo.dexItemFactory);
       // Reflection optimization 2. get*Name() with const-class -> const-string
@@ -920,7 +940,7 @@
       assert code.isConsistentSSA();
     }
 
-    if (!options.debug) {
+    if (!isDebugMode) {
       codeRewriter.collectClassInitializerDefaults(method, code);
     }
     if (Log.ENABLED) {
@@ -951,12 +971,8 @@
       // lambda, it does not get collected by merger.
       assert options.enableInlining && inliner != null;
       classInliner.processMethodCode(
-          appInfo.withLiveness(),
-          codeRewriter,
-          method,
-          code,
-          isProcessedConcurrently,
-          methodsToInline -> inliner.performForcedInlining(method, code, methodsToInline, feedback),
+          appInfo.withLiveness(), codeRewriter, method, code, isProcessedConcurrently,
+          methodsToInline -> inliner.performForcedInlining(method, code, methodsToInline),
           Suppliers.memoize(() -> inliner.createDefaultOracle(
               method, code,
               isProcessedConcurrently, callSiteInformation,
@@ -1173,7 +1189,7 @@
     workaroundForwardingInitializerBug(code);
     LinearScanRegisterAllocator registerAllocator =
         new LinearScanRegisterAllocator(appInfo, code, options);
-    registerAllocator.allocateRegisters(options.debug);
+    registerAllocator.allocateRegisters();
     if (options.canHaveExceptionTargetingLoopHeaderBug()) {
       codeRewriter.workaroundExceptionTargetingLoopHeaderBug(code);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 0ca4f16..d2a233b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -120,6 +120,7 @@
                 new ForwardMethodSourceCode(
                     clazz.type,
                     newMethod,
+                    newMethod,
                     null /* static method */,
                     rewriter.defaultAsMethodOfCompanionClass(method),
                     Invoke.Type.STATIC,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index f963581..955abf0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -170,6 +170,7 @@
                     new ForwardMethodSourceCode(
                         clazz.type,
                         newMethod,
+                        newMethod,
                         method.method.holder,
                         method.method,
                         Invoke.Type.VIRTUAL,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index f989cc9..6ba9b6a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -247,7 +247,13 @@
               new SynthesizedCode(
                   callerPosition ->
                       new ForwardMethodSourceCode(
-                          null, newMethod, null, origMethod, Type.STATIC, callerPosition)));
+                          null,
+                          newMethod,
+                          newMethod,
+                          null,
+                          origMethod,
+                          Type.STATIC,
+                          callerPosition)));
       newEncodedMethod.getMutableOptimizationInfo().markNeverInline();
       dispatchMethods.add(newEncodedMethod);
     }
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 f9e43d7..bad5456 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
@@ -16,7 +16,6 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CallSiteInformation;
-import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -98,6 +97,7 @@
     if (target.getOptimizationInfo().forceInline()
         || (inliner.appView.appInfo().hasLiveness()
             && inliner.appView.withLiveness().appInfo().forceInline.contains(target.method))) {
+      assert !appView.appInfo().neverInline.contains(target.method);
       return Reason.FORCE;
     }
     if (inliner.appView.appInfo().hasLiveness()
@@ -343,14 +343,13 @@
   }
 
   @Override
-  public void ensureMethodProcessed(
-      DexEncodedMethod target, IRCode inlinee, OptimizationFeedback feedback) {
+  public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
     if (!target.isProcessed()) {
       if (Log.ENABLED) {
         Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
       }
       inliner.performInlining(
-          target, inlinee, isProcessedConcurrently, callSiteInformation, feedback);
+          target, inlinee, isProcessedConcurrently, callSiteInformation);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index 0aaf27f..6890dd8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokePolymorphic;
 import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -65,8 +64,7 @@
   }
 
   @Override
-  public void ensureMethodProcessed(
-      DexEncodedMethod target, IRCode inlinee, OptimizationFeedback feedback) {
+  public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
     // Do nothing. If the method is not yet processed, we still should
     // be able to build IR for inlining, though.
   }
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 1368ea3..63d0242 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
@@ -543,19 +543,17 @@
   public void performForcedInlining(
       DexEncodedMethod method,
       IRCode code,
-      Map<InvokeMethod, InliningInfo> invokesToInline,
-      OptimizationFeedback feedback) {
+      Map<InvokeMethod, InliningInfo> invokesToInline) {
 
     ForcedInliningOracle oracle = new ForcedInliningOracle(method, invokesToInline);
-    performInliningImpl(oracle, oracle, method, code, feedback);
+    performInliningImpl(oracle, oracle, method, code);
   }
 
   public void performInlining(
       DexEncodedMethod method,
       IRCode code,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
-      CallSiteInformation callSiteInformation,
-      OptimizationFeedback feedback) {
+      CallSiteInformation callSiteInformation) {
 
     DefaultInliningOracle oracle =
         createDefaultOracle(
@@ -566,7 +564,7 @@
             options.inliningInstructionLimit,
             options.inliningInstructionAllowance - numberOfInstructions(code));
 
-    performInliningImpl(oracle, oracle, method, code, feedback);
+    performInliningImpl(oracle, oracle, method, code);
   }
 
   public DefaultInliningOracle createDefaultOracle(
@@ -590,11 +588,7 @@
   }
 
   private void performInliningImpl(
-      InliningStrategy strategy,
-      InliningOracle oracle,
-      DexEncodedMethod context,
-      IRCode code,
-      OptimizationFeedback feedback) {
+      InliningStrategy strategy, InliningOracle oracle, DexEncodedMethod context, IRCode code) {
     List<BasicBlock> blocksToRemove = new ArrayList<>();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
     while (blockIterator.hasNext()) {
@@ -631,7 +625,7 @@
 
               // If this code did not go through the full pipeline, apply inlining to make sure
               // that force inline targets get processed.
-              strategy.ensureMethodProcessed(target, inlinee.code, feedback);
+              strategy.ensureMethodProcessed(target, inlinee.code);
 
               // Make sure constructor inlining is legal.
               assert !target.isClassInitializer();
@@ -653,7 +647,7 @@
                 context.accessFlags.unsetBridge();
               }
 
-              context.copyMetadataFromInlinee(target, feedback);
+              context.copyMetadataFromInlinee(target);
               code.copyMetadataFromInlinee(inlinee.code);
             }
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
index e0ff04b..7dc2b62 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import java.util.ListIterator;
 
@@ -28,8 +27,7 @@
   /** Inform the strategy that the inlinee has been inlined. */
   void markInlined(InlineeWithReason inlinee);
 
-  void ensureMethodProcessed(
-      DexEncodedMethod target, IRCode inlinee, OptimizationFeedback feedback);
+  void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee);
 
   boolean isValidTarget(InvokeMethod invoke, DexEncodedMethod target, IRCode inlinee);
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index d83c550..eb42723 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.classinliner;
 
-import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -190,7 +189,7 @@
     }
   }
 
-  private boolean isClassEligible(AppInfo appInfo, DexClass clazz) {
+  private boolean isClassEligible(AppInfoWithLiveness appInfo, DexClass clazz) {
     Boolean eligible = knownClasses.get(clazz);
     if (eligible == null) {
       Boolean computed = computeClassEligible(appInfo, clazz);
@@ -205,9 +204,12 @@
   //   - is not an abstract class or interface
   //   - does not declare finalizer
   //   - does not trigger any static initializers except for its own
-  private boolean computeClassEligible(AppInfo appInfo, DexClass clazz) {
-    if (clazz == null || clazz.isLibraryClass() ||
-        clazz.accessFlags.isAbstract() || clazz.accessFlags.isInterface()) {
+  private boolean computeClassEligible(AppInfoWithLiveness appInfo, DexClass clazz) {
+    if (clazz == null
+        || clazz.isLibraryClass()
+        || clazz.accessFlags.isAbstract()
+        || clazz.accessFlags.isInterface()
+        || appInfo.neverClassInline.contains(clazz.type)) {
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 880a674..c971a14 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -199,7 +199,7 @@
    * Perform register allocation for the IRCode.
    */
   @Override
-  public void allocateRegisters(boolean debug) {
+  public void allocateRegisters() {
     // There are no linked values prior to register allocation.
     assert noLinkedValues();
     assert code.isConsistentSSA();
@@ -216,7 +216,11 @@
       Log.debug(this.getClass(), toString());
     }
     assert registersUsed() == 0 || unusedRegisters != null;
-    if (debug) {
+    // Even if the method is reachability sensitive, we do not compute debug information after
+    // register allocation. We just treat the method as being in debug mode in order to keep
+    // locals alive for their entire live range. In release mode the liveness is all that matters
+    // and we do not actually want locals information in the output.
+    if (options.debug) {
       computeDebugInfo(blocks);
     }
     clearUserInfo();
@@ -1612,9 +1616,11 @@
     // Set all free positions for possible registers to max integer.
     RegisterPositions freePositions = new RegisterPositions(registerConstraint + 1);
 
-    if (options.debug && !code.method.accessFlags.isStatic()) {
-      // If we are generating debug information, we pin the this value register since the
-      // debugger expects to always be able to find it in the input register.
+    if ((options.debug || code.method.getOptimizationInfo().isReachabilitySensitive())
+        && !code.method.accessFlags.isStatic()) {
+      // If we are generating debug information or if the method is reachability sensitive,
+      // we pin the this value register. The debugger expects to always be able to find it in
+      // the input register.
       assert numberOfArgumentRegisters > 0;
       assert firstArgumentValue != null && firstArgumentValue.requiredRegisters() == 1;
       freePositions.set(0, 0);
@@ -2620,7 +2626,9 @@
             }
           }
         }
-        if (options.debug) {
+        if (options.debug || code.method.getOptimizationInfo().isReachabilitySensitive()) {
+          // In debug mode, or if the method is reachability sensitive, extend the live range
+          // to cover the full scope of a local variable (encoded as debug values).
           int number = instruction.getNumber();
           for (Value use : instruction.getDebugValues()) {
             assert use.needsRegister();
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
index ba83787..1314b3f 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
@@ -9,7 +9,7 @@
 import java.util.List;
 
 public interface RegisterAllocator {
-  void allocateRegisters(boolean debug);
+  void allocateRegisters();
   int registersUsed();
   int getRegisterForValue(Value value, int instructionNumber);
   int getArgumentOrAllocateRegisterForValue(Value value, int instructionNumber);
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
index b8e79cd..37341fb 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
@@ -27,22 +27,32 @@
   public ForwardMethodSourceCode(
       DexType receiver,
       DexMethod method,
+      DexMethod originalMethod,
       DexType targetReceiver,
       DexMethod target,
       Type invokeType,
       Position callerPosition) {
-    this(receiver, method, targetReceiver, target, invokeType, callerPosition, false);
+    this(
+        receiver,
+        method,
+        originalMethod,
+        targetReceiver,
+        target,
+        invokeType,
+        callerPosition,
+        false);
   }
 
   public ForwardMethodSourceCode(
       DexType receiver,
       DexMethod method,
+      DexMethod originalMethod,
       DexType targetReceiver,
       DexMethod target,
       Type invokeType,
       Position callerPosition,
       boolean castResult) {
-    super(receiver, method, callerPosition);
+    super(receiver, method, callerPosition, originalMethod);
     assert (targetReceiver == null) == (invokeType == Invoke.Type.STATIC);
 
     this.target = target;
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 fd04882..88bbc6c 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
@@ -51,6 +51,11 @@
   private final Position position;
 
   protected SyntheticSourceCode(DexType receiver, DexMethod method, Position callerPosition) {
+    this(receiver, method, callerPosition, method);
+  }
+
+  protected SyntheticSourceCode(
+      DexType receiver, DexMethod method, Position callerPosition, DexMethod originalMethod) {
     assert method != null;
     this.receiver = receiver;
     this.method = method;
@@ -67,7 +72,7 @@
       this.paramRegisters[i] = nextRegister(ValueType.fromDexType(params[i]));
     }
 
-    position = Position.synthetic(0, method, callerPosition);
+    position = Position.synthetic(0, originalMethod, callerPosition);
   }
 
   protected final void add(Consumer<IRBuilder> constructor) {
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 0aff19d..481f6c4 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -32,7 +32,6 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -84,8 +83,7 @@
     }
   }
 
-  public void decoupleIdentifierNameStringsInMethod(
-      DexEncodedMethod encodedMethod, IRCode code, OptimizationFeedback feedback) {
+  public void decoupleIdentifierNameStringsInMethod(DexEncodedMethod encodedMethod, IRCode code) {
     if (!code.hasConstString) {
       return;
     }
@@ -165,7 +163,7 @@
             InstancePut instancePut = instruction.asInstancePut();
             iterator.replaceCurrentInstruction(new InstancePut(field, instancePut.object(), newIn));
           }
-          feedback.markUseIdentifierNameString(encodedMethod);
+          encodedMethod.getMutableOptimizationInfo().markUseIdentifierNameString();
         } else if (instruction.isInvokeMethod()) {
           InvokeMethod invoke = instruction.asInvokeMethod();
           DexMethod invokedMethod = invoke.getInvokedMethod();
@@ -280,7 +278,7 @@
                     invokedMethod.proto,
                     invoke.outValue(),
                     newIns));
-            feedback.markUseIdentifierNameString(encodedMethod);
+            encodedMethod.getMutableOptimizationInfo().markUseIdentifierNameString();
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 6b109c5..df11ba9 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -264,7 +264,7 @@
           // Don't report errors, as the set of call sites is a conservative estimate, and can
           // refer to interfaces which has been removed.
           Set<DexEncodedMethod> implementedMethods =
-              appInfo.lookupLambdaImplementedMethods(callSite, null);
+              appInfo.lookupLambdaImplementedMethods(callSite);
           if (implementedMethods.isEmpty()) {
             return;
           }
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java b/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java
new file mode 100644
index 0000000..e7dc76c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class ClassInlineRule extends ProguardConfigurationRule {
+
+  public enum Type {
+    NEVER
+  }
+
+  public static class Builder extends ProguardConfigurationRule.Builder<ClassInlineRule, Builder> {
+
+    private Builder() {
+      super();
+    }
+
+    Type type;
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    public Builder setType(Type type) {
+      this.type = type;
+      return this;
+    }
+
+    @Override
+    public ClassInlineRule build() {
+      return new ClassInlineRule(
+          origin,
+          getPosition(),
+          source,
+          classAnnotation,
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          inheritanceAnnotation,
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules,
+          type);
+    }
+  }
+
+  private final Type type;
+
+  protected ClassInlineRule(
+      Origin origin,
+      Position position,
+      String source,
+      ProguardTypeMatcher classAnnotation,
+      ProguardAccessFlags classAccessFlags,
+      ProguardAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      ProguardClassNameList classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      List<ProguardMemberRule> memberRules,
+      Type type) {
+    super(
+        origin,
+        position,
+        source,
+        classAnnotation,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotation,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
+    this.type = type;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public Type getType() {
+    return type;
+  }
+
+  @Override
+  String typeString() {
+    switch (type) {
+      case NEVER:
+        return "neverclassinline";
+    }
+    throw new Unreachable("Unknown class inline type " + type);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 06df4a9..fc10fe9 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -44,6 +44,7 @@
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
+import com.android.tools.r8.shaking.RootSetBuilder.IfRuleEvaluator;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -553,9 +554,22 @@
     public void registerCallSite(DexCallSite callSite) {
       callSites.add(callSite);
       super.registerCallSite(callSite);
-      for (DexEncodedMethod method :
-          appInfo.lookupLambdaImplementedMethods(callSite, options.reporter)) {
-        markLambdaInstantiated(method.method.holder, currentMethod);
+
+      List<DexType> directInterfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
+      if (directInterfaces != null) {
+        for (DexType lambdaInstantiatedInterface : directInterfaces) {
+          markLambdaInstantiated(lambdaInstantiatedInterface, currentMethod);
+        }
+      } else {
+        if (!appInfo.isStringConcat(callSite.bootstrapMethod)) {
+          if (options.reporter != null) {
+            Diagnostic message =
+                new StringDiagnostic(
+                    "Unknown bootstrap method " + callSite.bootstrapMethod,
+                    appInfo.originFor(currentMethod.method.holder));
+            options.reporter.warning(message);
+          }
+        }
       }
 
       LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo);
@@ -599,7 +613,6 @@
       // and implement all lambda interfaces.
 
       ScopedDexMethodSet seen = new ScopedDexMethodSet();
-      List<DexType> directInterfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
       if (directInterfaces == null) {
         return;
       }
@@ -1058,7 +1071,31 @@
   }
 
   private void markLambdaInstantiated(DexType itf, DexEncodedMethod method) {
-    instantiatedLambdas.add(itf, KeepReason.instantiatedIn(method));
+    DexClass clazz = appInfo.definitionFor(itf);
+    if (clazz == null) {
+      if (options.reporter != null) {
+        StringDiagnostic message =
+            new StringDiagnostic(
+                "Lambda expression implements missing interface `" + itf.toSourceString() + "`",
+                appInfo.originFor(method.method.holder));
+        options.reporter.warning(message);
+      }
+      return;
+    }
+    if (!clazz.isInterface()) {
+      if (options.reporter != null) {
+        StringDiagnostic message =
+            new StringDiagnostic(
+                "Lambda expression expected to implement an interface, but found "
+                    + "`" + itf.toSourceString() + "`",
+                appInfo.originFor(method.method.holder));
+        options.reporter.warning(message);
+      }
+      return;
+    }
+    if (clazz.isProgramClass()) {
+      instantiatedLambdas.add(itf, KeepReason.instantiatedIn(method));
+    }
   }
 
   private void markDirectStaticOrConstructorMethodAsLive(
@@ -1349,9 +1386,12 @@
         if (numOfLiveItemsAfterProcessing > numOfLiveItems) {
           RootSetBuilder consequentSetBuilder =
               new RootSetBuilder(appView, rootSet.ifRules, options);
-          ConsequentRootSet consequentRootSet = consequentSetBuilder.runForIfRules(
-              executorService, liveTypes, liveMethods.getItems(), liveFields.getItems());
+          IfRuleEvaluator ifRuleEvaluator =
+              consequentSetBuilder.getIfRuleEvaluator(
+                  liveMethods.getItems(), liveFields.getItems(), executorService);
+          ConsequentRootSet consequentRootSet = ifRuleEvaluator.run(liveTypes);
           enqueueRootItems(consequentRootSet.noShrinking);
+          rootSet.neverInline.addAll(consequentRootSet.neverInline);
           rootSet.noOptimization.addAll(consequentRootSet.noOptimization);
           rootSet.noObfuscation.addAll(consequentRootSet.noObfuscation);
           rootSet.addDependentItems(consequentRootSet.dependentNoShrinking);
@@ -1839,6 +1879,10 @@
      */
     public final Set<DexMethod> neverInline;
     /**
+     * All types that *must* never be inlined due to a configuration directive (testing only).
+     */
+    public final Set<DexType> neverClassInline;
+    /**
      * All types that *must* never be merged due to a configuration directive (testing only).
      */
     public final Set<DexType> neverMerge;
@@ -1903,6 +1947,7 @@
       this.alwaysInline = enqueuer.rootSet.alwaysInline;
       this.forceInline = enqueuer.rootSet.forceInline;
       this.neverInline = enqueuer.rootSet.neverInline;
+      this.neverClassInline = enqueuer.rootSet.neverClassInline;
       this.neverMerge = enqueuer.rootSet.neverMerge;
       this.identifierNameStrings = joinIdentifierNameStrings(
           enqueuer.rootSet.identifierNameStrings, enqueuer.identifierNameStrings);
@@ -1947,6 +1992,7 @@
       this.alwaysInline = previous.alwaysInline;
       this.forceInline = previous.forceInline;
       this.neverInline = previous.neverInline;
+      this.neverClassInline = previous.neverClassInline;
       this.neverMerge = previous.neverMerge;
       this.identifierNameStrings = previous.identifierNameStrings;
       this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses);
@@ -2005,6 +2051,7 @@
       assert lense.assertDefinitionNotModified(
           previous.neverMerge.stream().map(this::definitionFor).filter(Objects::nonNull)
               .collect(Collectors.toList()));
+      this.neverClassInline = rewriteItems(previous.neverClassInline, lense::lookupType);
       this.neverMerge = previous.neverMerge;
       this.identifierNameStrings =
           lense.rewriteReferencesConservatively(previous.identifierNameStrings);
@@ -2050,6 +2097,7 @@
       this.alwaysInline = previous.alwaysInline;
       this.forceInline = previous.forceInline;
       this.neverInline = previous.neverInline;
+      this.neverClassInline = previous.neverClassInline;
       this.neverMerge = previous.neverMerge;
       this.identifierNameStrings = previous.identifierNameStrings;
       this.prunedTypes = previous.prunedTypes;
@@ -2419,6 +2467,9 @@
               : Iterables.concat(
                   ImmutableList.of(refinedReceiverType), subtypes(refinedReceiverType));
       for (DexType type : subTypesToExplore) {
+        if (instantiatedLambdas.contains(type)) {
+          return null;
+        }
         if (pinnedItems.contains(type)) {
           // For kept classes we cannot ensure a single target.
           return null;
diff --git a/src/main/java/com/android/tools/r8/shaking/InlineRule.java b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
index f263bd6..3f255f2 100644
--- a/src/main/java/com/android/tools/r8/shaking/InlineRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
@@ -42,7 +42,7 @@
 
   private final Type type;
 
-  private InlineRule(
+  protected InlineRule(
       Origin origin,
       Position position,
       String source,
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index ea20704..2ab9308 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.position.TextRange;
-import com.android.tools.r8.shaking.InlineRule.Type;
 import com.android.tools.r8.shaking.ProguardConfiguration.Builder;
 import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType;
 import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
@@ -353,16 +352,19 @@
       } else if (acceptString("packageobfuscationdictionary")) {
         configurationBuilder.setPackageObfuscationDictionary(parseFileName(false));
       } else if (acceptString("alwaysinline")) {
-        InlineRule rule = parseInlineRule(Type.ALWAYS, optionStart);
+        InlineRule rule = parseInlineRule(InlineRule.Type.ALWAYS, optionStart);
         configurationBuilder.addRule(rule);
       } else if (allowTestOptions && acceptString("forceinline")) {
-        InlineRule rule = parseInlineRule(Type.FORCE, optionStart);
+        InlineRule rule = parseInlineRule(InlineRule.Type.FORCE, optionStart);
         configurationBuilder.addRule(rule);
         // Insert a matching -checkdiscard rule to ensure force inlining happens.
         ProguardCheckDiscardRule ruled = rule.asProguardCheckDiscardRule();
         configurationBuilder.addRule(ruled);
       } else if (allowTestOptions && acceptString("neverinline")) {
-        InlineRule rule = parseInlineRule(Type.NEVER, optionStart);
+        InlineRule rule = parseInlineRule(InlineRule.Type.NEVER, optionStart);
+        configurationBuilder.addRule(rule);
+      } else if (allowTestOptions && acceptString("neverclassinline")) {
+        ClassInlineRule rule = parseClassInlineRule(ClassInlineRule.Type.NEVER, optionStart);
         configurationBuilder.addRule(rule);
       } else if (allowTestOptions && acceptString("nevermerge")) {
         ClassMergingRule rule = parseClassMergingRule(ClassMergingRule.Type.NEVER, optionStart);
@@ -597,6 +599,17 @@
       return keepRuleBuilder.build();
     }
 
+    private ClassInlineRule parseClassInlineRule(ClassInlineRule.Type type, Position start)
+        throws ProguardRuleParserException {
+      ClassInlineRule.Builder keepRuleBuilder =
+          ClassInlineRule.builder().setOrigin(origin).setStart(start).setType(type);
+      parseClassSpec(keepRuleBuilder, false);
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
+      return keepRuleBuilder.build();
+    }
+
     private ClassMergingRule parseClassMergingRule(ClassMergingRule.Type type, Position start)
         throws ProguardRuleParserException {
       ClassMergingRule.Builder keepRuleBuilder =
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index 6d6d943..3011951 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -87,6 +87,52 @@
         subsequentRule.materialize());
   }
 
+  /**
+   * Consider the following rule, which requests that class Y should be kept if the method X.m() is
+   * in the final output.
+   *
+   * <pre>
+   * -if class X {
+   *   public void m();
+   * }
+   * -keep class Y
+   * </pre>
+   *
+   * When the {@link Enqueuer} finds that the method X.m() is reachable, it applies the subsequent
+   * keep rule of the -if rule. Thus, Y will be marked as pinned, which guarantees, for example,
+   * that it will not be merged into another class by the vertical class merger.
+   *
+   * <p>However, when the {@link Enqueuer} runs for the second time, it is important that X.m() has
+   * not been inlined into another method Z.z(), because that would mean that Z.z() now relies on
+   * the presence of Y, meanwhile Y will not be kept because X.m() is no longer present.
+   *
+   * <p>Therefore, each time the subsequent rule of an -if rule is applied, we also apply a
+   * -neverinline rule for the condition of the -if rule.
+   */
+  protected InlineRule neverInlineRuleForCondition() {
+    if (getMemberRules() == null || getMemberRules().isEmpty()) {
+      return null;
+    }
+    return new InlineRule(
+        Origin.unknown(),
+        Position.UNKNOWN,
+        null,
+        getClassAnnotation() == null ? null : getClassAnnotation().materialize(),
+        getClassAccessFlags(),
+        getNegatedClassAccessFlags(),
+        getClassTypeNegated(),
+        getClassType(),
+        getClassNames().materialize(),
+        getInheritanceAnnotation() == null ? null : getInheritanceAnnotation().materialize(),
+        getInheritanceClassName() == null ? null : getInheritanceClassName().materialize(),
+        getInheritanceIsExtends(),
+        getMemberRules().stream()
+            .filter(rule -> rule.getRuleType().includesMethods())
+            .map(ProguardMemberRule::materialize)
+            .collect(Collectors.toList()),
+        InlineRule.Type.NEVER);
+  }
+
   @Override
   public boolean equals(Object o) {
     if (!(o instanceof ProguardIfRule)) {
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index df72e96..c59081e 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -66,6 +66,7 @@
   private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
   private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
   private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
+  private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
   private final Set<DexType> neverMerge = Sets.newIdentityHashSet();
   private final Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking =
       new IdentityHashMap<>();
@@ -180,6 +181,10 @@
         }
       } else if (rule instanceof InlineRule) {
         markMatchingMethods(clazz, memberKeepRules, rule, null);
+      } else if (rule instanceof ClassInlineRule) {
+        if (allRulesSatisfied(memberKeepRules, clazz)) {
+          markClass(clazz, rule);
+        }
       } else if (rule instanceof ProguardAssumeValuesRule) {
         markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
         markMatchingVisibleFields(clazz, memberKeepRules, rule, null);
@@ -250,6 +255,7 @@
         alwaysInline,
         forceInline,
         neverInline,
+        neverClassInline,
         neverMerge,
         noSideEffects,
         assumedValues,
@@ -258,55 +264,15 @@
         ifRules);
   }
 
-  ConsequentRootSet runForIfRules(
-      ExecutorService executorService,
-      Set<DexType> liveTypes,
+  IfRuleEvaluator getIfRuleEvaluator(
       Set<DexEncodedMethod> liveMethods,
-      Set<DexEncodedField> liveFields) throws ExecutionException {
-    application.timing.begin("Find consequent items for -if rules...");
-    try {
-      if (rules != null) {
-        IfRuleEvaluator evaluator =
-            new IfRuleEvaluator(liveTypes, liveMethods, liveFields, executorService);
-        for (ProguardConfigurationRule rule : rules) {
-          assert rule instanceof ProguardIfRule;
-          ProguardIfRule ifRule = (ProguardIfRule) rule;
-          // Depending on which types that trigger the -if rule, the application of the subsequent
-          // -keep rule may vary (due to back references). So, we need to try all pairs of -if rule
-          // and live types.
-          for (DexType type : liveTypes) {
-            DexClass clazz = appView.appInfo().definitionFor(type);
-            if (clazz == null) {
-              continue;
-            }
-
-            // Check if the class matches the if-rule.
-            evaluator.evaluateIfRule(ifRule, clazz, clazz);
-
-            // Check if one of the types that have been merged into `clazz` satisfies the if-rule.
-            if (options.enableVerticalClassMerging && appView.verticallyMergedClasses() != null) {
-              for (DexType sourceType : appView.verticallyMergedClasses().getSourcesFor(type)) {
-                // Note that, although `sourceType` has been merged into `type`, the dex class for
-                // `sourceType` is still available until the second round of tree shaking. This way
-                // we can still retrieve the access flags of `sourceType`.
-                DexClass sourceClass = appView.appInfo().definitionFor(sourceType);
-                assert sourceClass != null;
-                evaluator.evaluateIfRule(ifRule, sourceClass, clazz);
-              }
-            }
-          }
-        }
-        ThreadUtils.awaitFutures(evaluator.futures);
-      }
-    } finally {
-      application.timing.end();
-    }
-    return new ConsequentRootSet(noShrinking, noOptimization, noObfuscation, dependentNoShrinking);
+      Set<DexEncodedField> liveFields,
+      ExecutorService executorService) {
+    return new IfRuleEvaluator(liveMethods, liveFields, executorService);
   }
 
-  private class IfRuleEvaluator {
+  class IfRuleEvaluator {
 
-    private final Set<DexType> liveTypes;
     private final Set<DexEncodedMethod> liveMethods;
     private final Set<DexEncodedField> liveFields;
     private final ExecutorService executorService;
@@ -314,16 +280,56 @@
     private final List<Future<?>> futures = new ArrayList<>();
 
     public IfRuleEvaluator(
-        Set<DexType> liveTypes,
         Set<DexEncodedMethod> liveMethods,
         Set<DexEncodedField> liveFields,
         ExecutorService executorService) {
-      this.liveTypes = liveTypes;
       this.liveMethods = liveMethods;
       this.liveFields = liveFields;
       this.executorService = executorService;
     }
 
+    public ConsequentRootSet run(Set<DexType> liveTypes) throws ExecutionException {
+      application.timing.begin("Find consequent items for -if rules...");
+      try {
+        if (rules != null) {
+          for (ProguardConfigurationRule rule : rules) {
+            assert rule instanceof ProguardIfRule;
+            ProguardIfRule ifRule = (ProguardIfRule) rule;
+            // Depending on which types that trigger the -if rule, the application of the subsequent
+            // -keep rule may vary (due to back references). So, we need to try all pairs of -if
+            // rule and live types.
+            for (DexType type : liveTypes) {
+              DexClass clazz = appView.appInfo().definitionFor(type);
+              if (clazz == null) {
+                continue;
+              }
+
+              // Check if the class matches the if-rule.
+              evaluateIfRule(ifRule, clazz, clazz);
+
+              // Check if one of the types that have been merged into `clazz` satisfies the if-rule.
+              if (options.enableVerticalClassMerging && appView.verticallyMergedClasses() != null) {
+                for (DexType sourceType : appView.verticallyMergedClasses().getSourcesFor(type)) {
+                  // Note that, although `sourceType` has been merged into `type`, the dex class for
+                  // `sourceType` is still available until the second round of tree shaking. This
+                  // way
+                  // we can still retrieve the access flags of `sourceType`.
+                  DexClass sourceClass = appView.appInfo().definitionFor(sourceType);
+                  assert sourceClass != null;
+                  evaluateIfRule(ifRule, sourceClass, clazz);
+                }
+              }
+            }
+          }
+          ThreadUtils.awaitFutures(futures);
+        }
+      } finally {
+        application.timing.end();
+      }
+      return new ConsequentRootSet(
+          neverInline, noShrinking, noOptimization, noObfuscation, dependentNoShrinking);
+    }
+
     /**
      * Determines if `sourceClass` satisfies the given if-rule. If `sourceClass` has not been merged
      * into another class, then `targetClass` is the same as `sourceClass`. Otherwise, `targetClass`
@@ -406,6 +412,16 @@
 
     private void materializeIfRule(ProguardIfRule rule) {
       ProguardIfRule materializedRule = rule.materialize();
+
+      // If the condition of the -if rule has any members, then we need to keep these members to
+      // ensure that the subsequent rule will be applied again in the second round of tree
+      // shaking.
+      InlineRule neverInlineRuleForCondition = materializedRule.neverInlineRuleForCondition();
+      if (neverInlineRuleForCondition != null) {
+        runPerRule(executorService, futures, neverInlineRuleForCondition, materializedRule);
+      }
+
+      // Keep whatever is required by the -if rule.
       runPerRule(executorService, futures, materializedRule.subsequentRule, materializedRule);
     }
   }
@@ -852,20 +868,26 @@
     } else if (context instanceof ProguardCheckDiscardRule) {
       checkDiscarded.add(item);
     } else if (context instanceof InlineRule) {
-      switch (((InlineRule) context).getType()) {
-        case ALWAYS:
-          if (item.isDexEncodedMethod()) {
+      if (item.isDexEncodedMethod()) {
+        switch (((InlineRule) context).getType()) {
+          case ALWAYS:
             alwaysInline.add(item.asDexEncodedMethod().method);
-          }
-          break;
-        case FORCE:
-          if (item.isDexEncodedMethod()) {
+            break;
+          case FORCE:
             forceInline.add(item.asDexEncodedMethod().method);
-          }
-          break;
-        case NEVER:
-          if (item.isDexEncodedMethod()) {
+            break;
+          case NEVER:
             neverInline.add(item.asDexEncodedMethod().method);
+            break;
+          default:
+            throw new Unreachable();
+        }
+      }
+    } else if (context instanceof ClassInlineRule) {
+      switch (((ClassInlineRule) context).getType()) {
+        case NEVER:
+          if (item.isDexClass()) {
+            neverClassInline.add(item.asDexClass().type);
           }
           break;
         default:
@@ -901,6 +923,7 @@
     public final Set<DexMethod> alwaysInline;
     public final Set<DexMethod> forceInline;
     public final Set<DexMethod> neverInline;
+    public final Set<DexType> neverClassInline;
     public final Set<DexType> neverMerge;
     public final Map<DexDefinition, ProguardMemberRule> noSideEffects;
     public final Map<DexDefinition, ProguardMemberRule> assumedValues;
@@ -918,6 +941,7 @@
         Set<DexMethod> alwaysInline,
         Set<DexMethod> forceInline,
         Set<DexMethod> neverInline,
+        Set<DexType> neverClassInline,
         Set<DexType> neverMerge,
         Map<DexDefinition, ProguardMemberRule> noSideEffects,
         Map<DexDefinition, ProguardMemberRule> assumedValues,
@@ -932,7 +956,8 @@
       this.checkDiscarded = Collections.unmodifiableSet(checkDiscarded);
       this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
       this.forceInline = Collections.unmodifiableSet(forceInline);
-      this.neverInline = Collections.unmodifiableSet(neverInline);
+      this.neverInline = neverInline;
+      this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
       this.neverMerge = Collections.unmodifiableSet(neverMerge);
       this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
       this.assumedValues = Collections.unmodifiableMap(assumedValues);
@@ -984,16 +1009,19 @@
 
   // A partial RootSet that becomes live due to the enabled -if rule.
   static class ConsequentRootSet {
+    final Set<DexMethod> neverInline;
     final Map<DexDefinition, ProguardKeepRule> noShrinking;
     final Set<DexDefinition> noOptimization;
     final Set<DexDefinition> noObfuscation;
     final Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking;
 
     private ConsequentRootSet(
+        Set<DexMethod> neverInline,
         Map<DexDefinition, ProguardKeepRule> noShrinking,
         Set<DexDefinition> noOptimization,
         Set<DexDefinition> noObfuscation,
         Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking) {
+      this.neverInline = Collections.unmodifiableSet(neverInline);
       this.noShrinking = Collections.unmodifiableMap(noShrinking);
       this.noOptimization = Collections.unmodifiableSet(noOptimization);
       this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
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 b9b9a99..4dfee8e 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -60,6 +60,7 @@
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -613,9 +614,28 @@
     assert result.assertDefinitionNotModified(appInfo.noSideEffects.keySet());
     // TODO(christofferqa): Enable this assert.
     // assert result.assertNotModified(appInfo.pinnedItems);
+    assert verifyGraphLense(graphLense);
     return result;
   }
 
+  private boolean verifyGraphLense(GraphLense graphLense) {
+    for (DexProgramClass clazz : appInfo.classes()) {
+      for (DexEncodedMethod encodedMethod : clazz.methods()) {
+        DexMethod method = encodedMethod.method;
+        DexMethod originalMethod = graphLense.getOriginalMethodSignature(method);
+
+        // Must be able to map back.
+        assert method == graphLense.getRenamedMethodSignature(originalMethod);
+
+        // Verify that all types are up-to-date. After vertical class merging, there should be no
+        // more references to types that have been merged into another type.
+        assert !mergedClasses.containsKey(method.proto.returnType);
+        assert Arrays.stream(method.proto.parameters.values).noneMatch(mergedClasses::containsKey);
+      }
+    }
+    return true;
+  }
+
   private GraphLense mergeClasses(GraphLense graphLense) {
     // Visit the program classes in a top-down order according to the class hierarchy.
     TopDownClassHierarchyTraversal.visit(appView, mergeCandidates, this::mergeClassIfPossible);
@@ -900,6 +920,7 @@
           // it turns out that the method is never used, it will be removed by the final round
           // of tree shaking.
           shadowedBy = buildBridgeMethod(virtualMethod, resultingDirectMethod);
+          deferredRenamings.recordCreationOfBridgeMethod(virtualMethod.method, shadowedBy.method);
           add(virtualMethods, shadowedBy, MethodSignatureEquivalence.get());
         }
 
@@ -1109,6 +1130,7 @@
       SynthesizedBridgeCode code =
           new SynthesizedBridgeCode(
               newMethod,
+              graphLense.getOriginalMethodSignature(method.method),
               invocationTarget.method,
               invocationTarget.isPrivateMethod() ? DIRECT : STATIC);
 
@@ -1393,7 +1415,7 @@
         DexMethod newMethod = application.dexItemFactory.createMethod(newHolder, newProto,
             method.name);
         if (newMethod != encodedMethod.method) {
-          lense.map(encodedMethod.method, newMethod);
+          lense.move(encodedMethod.method, newMethod);
           methods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
         }
       }
@@ -1411,7 +1433,7 @@
         DexType newHolder = fixupType(field.clazz);
         DexField newField = application.dexItemFactory.createField(newHolder, newType, field.name);
         if (newField != encodedField.field) {
-          lense.map(encodedField.field, newField);
+          lense.move(encodedField.field, newField);
           fields[i] = encodedField.toTypeSubstitutedField(newField);
         }
       }
@@ -1801,11 +1823,14 @@
   protected static class SynthesizedBridgeCode extends AbstractSynthesizedCode {
 
     private DexMethod method;
+    private DexMethod originalMethod;
     private DexMethod invocationTarget;
     private Type type;
 
-    public SynthesizedBridgeCode(DexMethod method, DexMethod invocationTarget, Type type) {
+    public SynthesizedBridgeCode(
+        DexMethod method, DexMethod originalMethod, DexMethod invocationTarget, Type type) {
       this.method = method;
+      this.originalMethod = originalMethod;
       this.invocationTarget = invocationTarget;
       this.type = type;
     }
@@ -1832,6 +1857,7 @@
           new ForwardMethodSourceCode(
               method.holder,
               method,
+              originalMethod,
               type == DIRECT ? method.holder : null,
               invocationTarget,
               type,
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 43c5e92..6b66465 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -17,10 +17,10 @@
 import com.android.tools.r8.shaking.VerticalClassMerger.SynthesizedBridgeCode;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
-import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -53,9 +53,10 @@
 public class VerticalClassMergerGraphLense extends NestedGraphLense {
   private final AppInfo appInfo;
 
-  private final Set<DexMethod> mergedMethods;
   private final Map<DexType, Map<DexMethod, GraphLenseLookupResult>>
       contextualVirtualToDirectMethodMaps;
+  private final Set<DexMethod> mergedMethods;
+  private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges;
 
   public VerticalClassMergerGraphLense(
       AppInfo appInfo,
@@ -65,6 +66,7 @@
       Map<DexType, Map<DexMethod, GraphLenseLookupResult>> contextualVirtualToDirectMethodMaps,
       BiMap<DexField, DexField> originalFieldSignatures,
       BiMap<DexMethod, DexMethod> originalMethodSignatures,
+      Map<DexMethod, DexMethod> originalMethodSignaturesForBridges,
       GraphLense previousLense) {
     super(
         ImmutableMap.of(),
@@ -75,8 +77,15 @@
         previousLense,
         appInfo.dexItemFactory);
     this.appInfo = appInfo;
-    this.mergedMethods = mergedMethods;
     this.contextualVirtualToDirectMethodMaps = contextualVirtualToDirectMethodMaps;
+    this.mergedMethods = mergedMethods;
+    this.originalMethodSignaturesForBridges = originalMethodSignaturesForBridges;
+  }
+
+  @Override
+  public DexMethod getOriginalMethodSignature(DexMethod method) {
+    return super.getOriginalMethodSignature(
+        originalMethodSignaturesForBridges.getOrDefault(method, method));
   }
 
   @Override
@@ -154,7 +163,9 @@
     private final Map<DexType, Map<DexMethod, GraphLenseLookupResult>>
         contextualVirtualToDirectMethodMaps = new HashMap<>();
 
-    private final Map<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+    private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+    private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges =
+        new IdentityHashMap<>();
 
     public GraphLense build(
         GraphLense previousLense,
@@ -184,10 +195,9 @@
           getMergedMethodSignaturesAfterClassMerging(
               mergedMethodsBuilder.build(), mergedClasses, appInfo.dexItemFactory, cache),
           contextualVirtualToDirectMethodMaps,
-          getOriginalFieldSignaturesAfterClassMerging(
-              originalFieldSignatures, mergedClasses, appInfo.dexItemFactory),
-          getOriginalMethodSignaturesAfterClassMerging(
-              originalMethodSignatures, mergedClasses, appInfo.dexItemFactory, cache),
+          originalFieldSignatures,
+          originalMethodSignatures,
+          originalMethodSignaturesForBridges,
           previousLense);
     }
 
@@ -207,44 +217,6 @@
       return result.build();
     }
 
-    private static BiMap<DexField, DexField> getOriginalFieldSignaturesAfterClassMerging(
-        Map<DexField, DexField> originalFieldSignatures,
-        Map<DexType, DexType> mergedClasses,
-        DexItemFactory dexItemFactory) {
-      ImmutableBiMap.Builder<DexField, DexField> result = ImmutableBiMap.builder();
-      for (Map.Entry<DexField, DexField> entry : originalFieldSignatures.entrySet()) {
-        result.put(
-            getFieldSignatureAfterClassMerging(entry.getKey(), mergedClasses, dexItemFactory),
-            entry.getValue());
-      }
-      return result.build();
-    }
-
-    private static BiMap<DexMethod, DexMethod> getOriginalMethodSignaturesAfterClassMerging(
-        Map<DexMethod, DexMethod> originalMethodSignatures,
-        Map<DexType, DexType> mergedClasses,
-        DexItemFactory dexItemFactory,
-        Map<DexProto, DexProto> cache) {
-      ImmutableBiMap.Builder<DexMethod, DexMethod> result = ImmutableBiMap.builder();
-      for (Map.Entry<DexMethod, DexMethod> entry : originalMethodSignatures.entrySet()) {
-        result.put(
-            getMethodSignatureAfterClassMerging(
-                entry.getKey(), mergedClasses, dexItemFactory, cache),
-            entry.getValue());
-      }
-      return result.build();
-    }
-
-    private static DexField getFieldSignatureAfterClassMerging(
-        DexField signature, Map<DexType, DexType> mergedClasses, DexItemFactory dexItemFactory) {
-      DexType newClass = mergedClasses.getOrDefault(signature.clazz, signature.clazz);
-      DexType newType = mergedClasses.getOrDefault(signature.type, signature.type);
-      if (signature.clazz.equals(newClass) && signature.type.equals(newType)) {
-        return signature;
-      }
-      return dexItemFactory.createField(newClass, newType, signature.name);
-    }
-
     private static DexMethod getMethodSignatureAfterClassMerging(
         DexMethod signature,
         Map<DexType, DexType> mergedClasses,
@@ -285,6 +257,10 @@
       originalMethodSignatures.put(to, from);
     }
 
+    public void recordCreationOfBridgeMethod(DexMethod from, DexMethod to) {
+      originalMethodSignaturesForBridges.put(to, from);
+    }
+
     public void mapVirtualMethodToDirectInType(
         DexMethod from, GraphLenseLookupResult to, DexType type) {
       Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
@@ -297,6 +273,7 @@
       methodMap.putAll(builder.methodMap);
       mergedMethodsBuilder.addAll(builder.mergedMethodsBuilder.build());
       originalMethodSignatures.putAll(builder.originalMethodSignatures);
+      originalMethodSignaturesForBridges.putAll(builder.originalMethodSignaturesForBridges);
       for (DexType context : builder.contextualVirtualToDirectMethodMaps.keySet()) {
         Map<DexMethod, GraphLenseLookupResult> current =
             contextualVirtualToDirectMethodMaps.get(context);
diff --git a/src/main/java/com/android/tools/r8/utils/BooleanUtils.java b/src/main/java/com/android/tools/r8/utils/BooleanUtils.java
new file mode 100644
index 0000000..bacea6f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/BooleanUtils.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+public class BooleanUtils {
+
+  private static final Boolean[] VALUES = new Boolean[] { Boolean.TRUE, Boolean.FALSE };
+
+  public static Boolean[] values() {
+    return VALUES;
+  }
+}
diff --git a/src/test/examples/inlining/AlwaysInline.java b/src/test/examples/inlining/AlwaysInline.java
index 815d50a..22cb31e 100644
--- a/src/test/examples/inlining/AlwaysInline.java
+++ b/src/test/examples/inlining/AlwaysInline.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package inlining;
 
-public @interface AlwaysInline {
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
 
-}
+@Target({ElementType.METHOD})
+public @interface AlwaysInline {}
diff --git a/src/test/examples/inlining/NeverInline.java b/src/test/examples/inlining/NeverInline.java
index 0db5239..0c5b581 100644
--- a/src/test/examples/inlining/NeverInline.java
+++ b/src/test/examples/inlining/NeverInline.java
@@ -3,4 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package inlining;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
 public @interface NeverInline {}
diff --git a/src/test/java/com/android/tools/r8/NeverClassInline.java b/src/test/java/com/android/tools/r8/NeverClassInline.java
new file mode 100644
index 0000000..aa7f9b5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/NeverClassInline.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+public @interface NeverClassInline {}
diff --git a/src/test/java/com/android/tools/r8/NeverMerge.java b/src/test/java/com/android/tools/r8/NeverMerge.java
index 7fc97b9..7c6922a 100644
--- a/src/test/java/com/android/tools/r8/NeverMerge.java
+++ b/src/test/java/com/android/tools/r8/NeverMerge.java
@@ -3,4 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
 public @interface NeverMerge {}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 3a249e6..5550415 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -30,6 +30,7 @@
   }
 
   private boolean enableInliningAnnotations = false;
+  private boolean enableClassInliningAnnotations = false;
   private boolean enableMergeAnnotations = false;
 
   @Override
@@ -41,7 +42,7 @@
   R8TestCompileResult internalCompile(
       Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
       throws CompilationFailedException {
-    if (enableInliningAnnotations || enableMergeAnnotations) {
+    if (enableInliningAnnotations || enableClassInliningAnnotations || enableMergeAnnotations) {
       ToolHelper.allowTestProguardOptions(builder);
     }
     StringBuilder proguardMapBuilder = new StringBuilder();
@@ -71,6 +72,14 @@
     return self();
   }
 
+  public R8TestBuilder enableClassInliningAnnotations() {
+    if (!enableClassInliningAnnotations) {
+      enableClassInliningAnnotations = true;
+      addKeepRules("-neverclassinline @com.android.tools.r8.NeverClassInline class *");
+    }
+    return self();
+  }
+
   public R8TestBuilder enableMergeAnnotations() {
     if (!enableMergeAnnotations) {
       enableMergeAnnotations = true;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index d6e7c0d..f968b8c 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -143,6 +143,10 @@
         this.shortName = shortName;
       }
 
+      public boolean isLatest() {
+        return this == DEFAULT;
+      }
+
       public boolean isNewerThan(Version other) {
         return compareTo(other) > 0;
       }
diff --git a/src/test/java/com/android/tools/r8/d8/IncompatiblePrimitiveTypesTest.java b/src/test/java/com/android/tools/r8/d8/IncompatiblePrimitiveTypesTest.java
new file mode 100644
index 0000000..010f903
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/d8/IncompatiblePrimitiveTypesTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.d8;
+
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import org.hamcrest.Matcher;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class IncompatiblePrimitiveTypesTest extends TestBase {
+
+  @ClassRule
+  public static TemporaryFolder tempFolder = ToolHelper.getTemporaryFolderForTest();
+
+  private static Path inputJar;
+  private static String expectedOutput = "true";
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    JasminBuilder jasminBuilder = new JasminBuilder();
+    ClassBuilder classBuilder = jasminBuilder.addClass("TestClass");
+    classBuilder
+        .staticMethodBuilder("main", ImmutableList.of("[Ljava/lang/String;"), "V")
+        .setCode(
+            "invokestatic TestClass/getByte()B", "invokestatic TestClass/takeBoolean(Z)V", "return")
+        .build();
+    classBuilder
+        .staticMethodBuilder("getByte", ImmutableList.of(), "B")
+        .setCode("iconst_1", "ireturn")
+        .build();
+    classBuilder
+        .staticMethodBuilder("takeBoolean", ImmutableList.of("Z"), "V")
+        .setCode(
+            "getstatic java/lang/System/out Ljava/io/PrintStream;",
+            "iload_0",
+            "invokevirtual java/io/PrintStream/print(Z)V",
+            "return")
+        .build();
+    inputJar = tempFolder.getRoot().toPath().resolve("input.jar");
+    jasminBuilder.writeJar(inputJar);
+  }
+
+  @Test
+  public void jvmTest() throws Exception {
+    assumeTrue(
+        "JVM test independent of Art version - only run when testing on latest",
+        ToolHelper.getDexVm().getVersion().isLatest());
+    testForJvm().addClasspath(inputJar).run("TestClass").assertSuccessWithOutput(expectedOutput);
+  }
+
+  @Test
+  public void dexTest() throws Exception {
+    TestRunResult d8Result = testForD8().addProgramFiles(inputJar).run("TestClass");
+    TestRunResult dxResult = testForDX().addProgramFiles(inputJar).run("TestClass");
+    if (ToolHelper.getDexVm().getVersion().isNewerThan(Version.V4_4_4)) {
+      d8Result.assertSuccessWithOutput(expectedOutput);
+      dxResult.assertSuccessWithOutput(expectedOutput);
+    } else {
+      // TODO(b/119812046): On Art 4.0.4 and 4.4.4 it is a verification error to use one short type
+      // as another short type.
+      Matcher<String> expectedError = containsString("java.lang.VerifyError");
+      d8Result.assertFailureWithErrorThatMatches(expectedError);
+      dxResult.assertFailureWithErrorThatMatches(expectedError);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java b/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java
index 6fe1f16..6008aab 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.jasmin.JasminTestBase;
 import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -44,11 +45,7 @@
 
   @Parameters(name = "Backend: {0}, vertical class merging: {1}")
   public static Collection<Object[]> data() {
-    return ImmutableList.of(
-        new Object[] {Backend.CF, false},
-        new Object[] {Backend.CF, true},
-        new Object[] {Backend.DEX, false},
-        new Object[] {Backend.DEX, true});
+    return buildParameters(Backend.values(), BooleanUtils.values());
   }
 
   public B116282409(Backend backend, boolean enableVerticalClassMerging) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 0572c36..55c64ee 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -128,6 +128,7 @@
     blocks.add(block);
 
     InternalOptions options = new InternalOptions();
+    options.debug = true;
     AppInfo appInfo = new AppInfo(DexApplication.builder(options.itemFactory, null).build());
     IRCode code =
         new IRCode(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index e1167b9..c9e5f1e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -27,7 +28,6 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
@@ -49,15 +49,11 @@
 
   @Parameters(name = "{0}, backend={1}, minification={2}, allowaccessmodification={3}")
   public static Collection<Object[]> data() {
-    List<Object[]> configs = new ArrayList<>();
-    for (Backend backend : Backend.values()) {
-      for (boolean minification : new Boolean[] {false, true}) {
-        for (boolean allowaccessmodification : new Boolean[] {false, true}) {
-          configs.add(new Object[] {"Inlining", backend, minification, allowaccessmodification});
-        }
-      }
-    }
-    return configs;
+    return buildParameters(
+        ImmutableList.of("Inlining"),
+        Backend.values(),
+        BooleanUtils.values(),
+        BooleanUtils.values());
   }
 
   private final String name;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 49a9457..eedfc2f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -69,9 +69,11 @@
     // Check that the goto in block0 remains. There was a bug in the trivial goto elimination
     // that ended up removing that goto changing the code to start with the unreachable
     // throw.
+    InternalOptions options = new InternalOptions();
+    options.debug = true;
     IRCode code =
         new IRCode(
-            new InternalOptions(),
+            options,
             null,
             blocks,
             new ValueNumberGenerator(),
@@ -150,9 +152,11 @@
     // Check that the goto in block0 remains. There was a bug in the trivial goto elimination
     // that ended up removing that goto changing the code to start with the unreachable
     // throw.
+    InternalOptions options = new InternalOptions();
+    options.debug = true;
     IRCode code =
         new IRCode(
-            new InternalOptions(),
+            options,
             null,
             blocks,
             new ValueNumberGenerator(),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java
index e8ed51f..ef7a07b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Streams;
 import java.util.Collection;
 import org.junit.runner.RunWith;
@@ -22,12 +22,7 @@
 
   @Parameterized.Parameters(name = "Backend: {0} minification: {1}")
   public static Collection<Object[]> data() {
-    ImmutableList.Builder<Object[]> builder = new ImmutableList.Builder<>();
-    for (Backend backend : Backend.values()) {
-      builder.add(new Object[]{backend, Boolean.TRUE});
-      builder.add(new Object[]{backend, Boolean.FALSE});
-    }
-    return builder.build();
+    return buildParameters(Backend.values(), BooleanUtils.values());
   }
 
   GetNameTestBase(Backend backend, boolean enableMinification) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index c184e6e..0794d92 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -9,9 +9,14 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.ForceInline;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.VmTestRunner;
@@ -36,13 +41,16 @@
 import com.android.tools.r8.ir.optimize.staticizer.trivial.Simple;
 import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithGetter;
 import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithParams;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithPhi;
 import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithSideEffects;
 import com.android.tools.r8.ir.optimize.staticizer.trivial.TrivialTestClass;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Streams;
 import java.nio.file.Path;
@@ -50,26 +58,49 @@
 import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
-@RunWith(VmTestRunner.class)
+@RunWith(Parameterized.class)
 public class ClassStaticizerTest extends TestBase {
+  private Backend backend;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public ClassStaticizerTest(Backend backend) {
+    this.backend = backend;
+  }
+
   @Test
   public void testTrivial() throws Exception {
-    byte[][] classes = {
-        ToolHelper.getClassAsBytes(TrivialTestClass.class),
-        ToolHelper.getClassAsBytes(Simple.class),
-        ToolHelper.getClassAsBytes(SimpleWithSideEffects.class),
-        ToolHelper.getClassAsBytes(SimpleWithParams.class),
-        ToolHelper.getClassAsBytes(SimpleWithGetter.class),
+    assumeTrue("b/112831361", backend == Backend.DEX);
+    Class<?> main = TrivialTestClass.class;
+    Class<?>[] classes = {
+        NeverInline.class,
+        TrivialTestClass.class,
+        Simple.class,
+        SimpleWithSideEffects.class,
+        SimpleWithParams.class,
+        SimpleWithGetter.class,
+        SimpleWithPhi.class,
+        SimpleWithPhi.Companion.class
     };
-    AndroidApp app = runR8(buildAndroidApp(classes), TrivialTestClass.class);
+    String javaOutput = runOnJava(main);
+    TestRunResult result = testForR8(backend)
+        .addProgramClasses(classes)
+        .enableProguardTestOptions()
+        .enableInliningAnnotations()
+        .addKeepMainRule(main)
+        .addKeepRules("-dontobfuscate", "-allowaccessmodification")
+        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+        .addOptionsModification(this::configure)
+        .run(main)
+        .assertSuccessWithOutput(javaOutput);
 
-    String javaOutput = runOnJava(TrivialTestClass.class);
-    String artOutput = runOnArt(app, TrivialTestClass.class);
-    assertEquals(javaOutput, artOutput);
-
-    CodeInspector inspector = new CodeInspector(app);
-    ClassSubject clazz = inspector.clazz(TrivialTestClass.class);
+    CodeInspector inspector = result.inspector();
+    ClassSubject clazz = inspector.clazz(main);
 
     assertEquals(
         Lists.newArrayList(
@@ -82,6 +113,16 @@
 
     assertEquals(
         Lists.newArrayList(
+            "STATIC: String trivial.SimpleWithPhi.bar(String)",
+            "STATIC: String trivial.SimpleWithPhi.foo()",
+            "STATIC: String trivial.SimpleWithPhi.foo()",
+            "STATIC: String trivial.TrivialTestClass.next()"),
+        references(clazz, "testSimpleWithPhi", "void", "int"));
+
+    assertTrue(instanceMethods(inspector.clazz(SimpleWithPhi.class)).isEmpty());
+
+    assertEquals(
+        Lists.newArrayList(
             "STATIC: String trivial.SimpleWithParams.bar(String)",
             "STATIC: String trivial.SimpleWithParams.foo()",
             "STATIC: String trivial.TrivialTestClass.next()"),
@@ -115,25 +156,33 @@
 
   @Test
   public void testMoveToHost() throws Exception {
-    byte[][] classes = {
-        ToolHelper.getClassAsBytes(MoveToHostTestClass.class),
-        ToolHelper.getClassAsBytes(HostOk.class),
-        ToolHelper.getClassAsBytes(CandidateOk.class),
-        ToolHelper.getClassAsBytes(HostOkSideEffects.class),
-        ToolHelper.getClassAsBytes(CandidateOkSideEffects.class),
-        ToolHelper.getClassAsBytes(HostConflictMethod.class),
-        ToolHelper.getClassAsBytes(CandidateConflictMethod.class),
-        ToolHelper.getClassAsBytes(HostConflictField.class),
-        ToolHelper.getClassAsBytes(CandidateConflictField.class),
+    assumeTrue("b/112831361", backend == Backend.DEX);
+    Class<?> main = MoveToHostTestClass.class;
+    Class<?>[] classes = {
+        NeverInline.class,
+        MoveToHostTestClass.class,
+        HostOk.class,
+        CandidateOk.class,
+        HostOkSideEffects.class,
+        CandidateOkSideEffects.class,
+        HostConflictMethod.class,
+        CandidateConflictMethod.class,
+        HostConflictField.class,
+        CandidateConflictField.class
     };
-    AndroidApp app = runR8(buildAndroidApp(classes), MoveToHostTestClass.class);
+    String javaOutput = runOnJava(main);
+    TestRunResult result = testForR8(backend)
+        .addProgramClasses(classes)
+        .enableProguardTestOptions()
+        .enableInliningAnnotations()
+        .addKeepMainRule(main)
+        .addKeepRules("-dontobfuscate", "-allowaccessmodification")
+        .addOptionsModification(this::configure)
+        .run(main)
+        .assertSuccessWithOutput(javaOutput);
 
-    String javaOutput = runOnJava(MoveToHostTestClass.class);
-    String artOutput = runOnArt(app, MoveToHostTestClass.class);
-    assertEquals(javaOutput, artOutput);
-
-    CodeInspector inspector = new CodeInspector(app);
-    ClassSubject clazz = inspector.clazz(MoveToHostTestClass.class);
+    CodeInspector inspector = result.inspector();
+    ClassSubject clazz = inspector.clazz(main);
 
     assertEquals(
         Lists.newArrayList(
@@ -232,40 +281,6 @@
     return type.toSourceString().startsWith("com.android.tools.r8.ir.optimize.staticizer");
   }
 
-  private AndroidApp runR8(AndroidApp app, Class mainClass) throws Exception {
-    AndroidApp compiled =
-        compileWithR8(app, getProguardConfig(mainClass.getCanonicalName()), this::configure);
-
-    // Materialize file for execution.
-    Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
-    compiled.writeToZip(generatedDexFile, OutputMode.DexIndexed);
-
-    // Run with ART.
-    String artOutput = ToolHelper.runArtNoVerificationErrors(
-        generatedDexFile.toString(), mainClass.getCanonicalName());
-
-    // Compare with Java.
-    ProcessResult javaResult = ToolHelper.runJava(
-        ToolHelper.getClassPathForTests(), mainClass.getCanonicalName());
-
-    if (javaResult.exitCode != 0) {
-      System.out.println(javaResult.stdout);
-      System.err.println(javaResult.stderr);
-      fail("JVM failed for: " + mainClass);
-    }
-    assertEquals("JVM and ART output differ", javaResult.stdout, artOutput);
-
-    return compiled;
-  }
-
-  private String getProguardConfig(String main) {
-    return keepMainProguardConfiguration(main)
-        + System.lineSeparator()
-        + "-dontobfuscate"
-        + System.lineSeparator()
-        + "-allowaccessmodification";
-  }
-
   private void configure(InternalOptions options) {
     options.enableClassInlining = false;
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java
index f2108b0..82eef8e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java
@@ -4,18 +4,18 @@
 
 package com.android.tools.r8.ir.optimize.staticizer.movetohost;
 
+import com.android.tools.r8.NeverInline;
+
 public class CandidateConflictField {
   public static String field;
 
+  @NeverInline
   public String foo() {
-    synchronized ("") {
-      return bar("CandidateConflictMethod::foo()");
-    }
+    return bar("CandidateConflictMethod::foo()");
   }
 
+  @NeverInline
   public String bar(String other) {
-    synchronized ("") {
-      return "CandidateConflictMethod::bar(" + other + ")";
-    }
+    return "CandidateConflictMethod::bar(" + other + ")";
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java
index 40d0576..4e5ce3a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java
@@ -4,16 +4,16 @@
 
 package com.android.tools.r8.ir.optimize.staticizer.movetohost;
 
+import com.android.tools.r8.NeverInline;
+
 public class CandidateConflictMethod {
+  @NeverInline
   public String foo() {
-    synchronized ("") {
-      return bar("CandidateConflictMethod::foo()");
-    }
+    return bar("CandidateConflictMethod::foo()");
   }
 
+  @NeverInline
   public String bar(String other) {
-    synchronized ("") {
-      return "CandidateConflictMethod::bar(" + other + ")";
-    }
+    return "CandidateConflictMethod::bar(" + other + ")";
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java
index daf5647..0834c000 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java
@@ -4,22 +4,21 @@
 
 package com.android.tools.r8.ir.optimize.staticizer.movetohost;
 
+import com.android.tools.r8.NeverInline;
+
 public class CandidateOk {
+  @NeverInline
   public String foo() {
-    synchronized ("") {
-      return bar("CandidateOk::foo()");
-    }
+    return bar("CandidateOk::foo()");
   }
 
+  @NeverInline
   public String bar(String other) {
-    synchronized ("") {
-      return "CandidateOk::bar(" + other + ")";
-    }
+    return "CandidateOk::bar(" + other + ")";
   }
 
+  @NeverInline
   public void blah(String other) {
-    synchronized ("") {
-      System.out.println("CandidateOk::blah(" + other + ")");
-    }
+    System.out.println("CandidateOk::blah(" + other + ")");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkSideEffects.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkSideEffects.java
index 2077056..a02bed3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkSideEffects.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkSideEffects.java
@@ -4,16 +4,16 @@
 
 package com.android.tools.r8.ir.optimize.staticizer.movetohost;
 
+import com.android.tools.r8.NeverInline;
+
 public class CandidateOkSideEffects {
+  @NeverInline
   public String foo() {
-    synchronized ("") {
-      return bar("CandidateOkSideEffects::foo()");
-    }
+    return bar("CandidateOkSideEffects::foo()");
   }
 
+  @NeverInline
   public String bar(String other) {
-    synchronized ("") {
-      return "CandidateOkSideEffects::bar(" + other + ")";
-    }
+    return "CandidateOkSideEffects::bar(" + other + ")";
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictMethod.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictMethod.java
index ad86b35..8804e14 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictMethod.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictMethod.java
@@ -4,12 +4,13 @@
 
 package com.android.tools.r8.ir.optimize.staticizer.movetohost;
 
+import com.android.tools.r8.NeverInline;
+
 public class HostConflictMethod {
   static CandidateConflictMethod INSTANCE = new CandidateConflictMethod();
 
+  @NeverInline
   public String bar(String other) {
-    synchronized ("") {
-      return "HostConflictMethod::bar(" + other + ")";
-    }
+    return "HostConflictMethod::bar(" + other + ")";
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostTestClass.java
index 3f31839..3be6e5f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostTestClass.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.staticizer.movetohost;
 
+import com.android.tools.r8.NeverInline;
+
 public class MoveToHostTestClass {
   private static int ID = 0;
 
@@ -19,24 +21,28 @@
     test.testConflictField();
   }
 
-  private synchronized void testOk() {
+  @NeverInline
+  private void testOk() {
     System.out.println(HostOk.INSTANCE.foo());
     System.out.println(HostOk.INSTANCE.bar(next()));
     HostOk.INSTANCE.blah(next());
   }
 
-  private synchronized void testOkSideEffects() {
+  @NeverInline
+  private void testOkSideEffects() {
     System.out.println(HostOkSideEffects.INSTANCE.foo());
     System.out.println(HostOkSideEffects.INSTANCE.bar(next()));
   }
 
-  private synchronized void testConflictMethod() {
+  @NeverInline
+  private void testConflictMethod() {
     System.out.println(new HostConflictMethod().bar(next()));
     System.out.println(HostConflictMethod.INSTANCE.foo());
     System.out.println(HostConflictMethod.INSTANCE.bar(next()));
   }
 
-  private synchronized void testConflictField() {
+  @NeverInline
+  private void testConflictField() {
     System.out.println(new HostConflictField().field);
     System.out.println(CandidateConflictField.field);
     System.out.println(HostConflictField.INSTANCE.foo());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/Simple.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/Simple.java
index 8df079c..3cb1c49 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/Simple.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/Simple.java
@@ -4,18 +4,18 @@
 
 package com.android.tools.r8.ir.optimize.staticizer.trivial;
 
+import com.android.tools.r8.NeverInline;
+
 public class Simple {
   static Simple INSTANCE = new Simple();
 
+  @NeverInline
   String foo() {
-    synchronized ("") {
-      return bar("Simple::foo()");
-    }
+    return bar("Simple::foo()");
   }
 
+  @NeverInline
   String bar(String other) {
-    synchronized ("") {
-      return "Simple::bar(" + other + ")";
-    }
+    return "Simple::bar(" + other + ")";
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
index 9ff20c3..55487ac 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.staticizer.trivial;
 
+import com.android.tools.r8.NeverInline;
+
 public class SimpleWithGetter {
   private static SimpleWithGetter INSTANCE = new SimpleWithGetter();
 
@@ -11,15 +13,13 @@
     return INSTANCE;
   }
 
+  @NeverInline
   String foo() {
-    synchronized ("") {
-      return bar("Simple::foo()");
-    }
+    return bar("Simple::foo()");
   }
 
+  @NeverInline
   String bar(String other) {
-    synchronized ("") {
-      return "Simple::bar(" + other + ")";
-    }
+    return "Simple::bar(" + other + ")";
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java
index a71f1f5..24edea4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java
@@ -4,21 +4,21 @@
 
 package com.android.tools.r8.ir.optimize.staticizer.trivial;
 
+import com.android.tools.r8.NeverInline;
+
 public class SimpleWithParams {
   static SimpleWithParams INSTANCE = new SimpleWithParams(123);
 
   SimpleWithParams(int i) {
   }
 
+  @NeverInline
   String foo() {
-    synchronized ("") {
-      return bar("SimpleWithParams::foo()");
-    }
+    return bar("SimpleWithParams::foo()");
   }
 
+  @NeverInline
   String bar(String other) {
-    synchronized ("") {
-      return "SimpleWithParams::bar(" + other + ")";
-    }
+    return "SimpleWithParams::bar(" + other + ")";
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithPhi.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithPhi.java
new file mode 100644
index 0000000..f3c76cb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithPhi.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.staticizer.trivial;
+
+import com.android.tools.r8.NeverInline;
+
+public class SimpleWithPhi {
+  public static class Companion {
+    @NeverInline
+    String foo() {
+      return bar("SimpleWithPhi$Companion::foo()");
+    }
+
+    @NeverInline
+    String bar(String other) {
+      return "SimpleWithPhi$Companion::bar(" + other + ")";
+    }
+  }
+
+  static Companion INSTANCE = new Companion();
+
+  @NeverInline
+  static String foo() {
+    return INSTANCE.foo();
+  }
+
+  @NeverInline
+  static String bar(String other) {
+    if (other.length() > 2) {
+      return INSTANCE.foo();
+    } else {
+      return INSTANCE.bar(other);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithSideEffects.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithSideEffects.java
index 5ba97dc..2ee78f5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithSideEffects.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithSideEffects.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.staticizer.trivial;
 
+import com.android.tools.r8.NeverInline;
+
 public class SimpleWithSideEffects {
   static SimpleWithSideEffects INSTANCE = new SimpleWithSideEffects();
 
@@ -11,15 +13,13 @@
     System.out.println("SimpleWithSideEffects::<clinit>()");
   }
 
+  @NeverInline
   String foo() {
-    synchronized ("") {
-      return bar("SimpleWithSideEffects::foo()");
-    }
+    return bar("SimpleWithSideEffects::foo()");
   }
 
+  @NeverInline
   String bar(String other) {
-    synchronized ("") {
-      return "SimpleWithSideEffects::bar(" + other + ")";
-    }
+    return "SimpleWithSideEffects::bar(" + other + ")";
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java
index 2dad273..964567c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.staticizer.trivial;
 
+import com.android.tools.r8.NeverInline;
+
 public class TrivialTestClass {
   private static int ID = 0;
 
@@ -14,27 +16,47 @@
   public static void main(String[] args) {
     TrivialTestClass test = new TrivialTestClass();
     test.testSimple();
+    test.testSimpleWithPhi(args.length);
     test.testSimpleWithSideEffects();
     test.testSimpleWithParams();
     test.testSimpleWithGetter();
   }
 
-  private synchronized void testSimple() {
+  @NeverInline
+  private void testSimple() {
     System.out.println(Simple.INSTANCE.foo());
     System.out.println(Simple.INSTANCE.bar(next()));
   }
 
-  private synchronized void testSimpleWithSideEffects() {
+  @NeverInline
+  private void testSimpleWithPhi(int arg) {
+    switch (arg) {
+      case 0:
+        System.out.println(SimpleWithPhi.foo() + " " + true);
+        break;
+      case 2:
+        System.out.println(SimpleWithPhi.foo() + " " + false);
+        break;
+      default:
+        System.out.println(SimpleWithPhi.bar(next()));
+        break;
+    }
+  }
+
+  @NeverInline
+  private void testSimpleWithSideEffects() {
     System.out.println(SimpleWithSideEffects.INSTANCE.foo());
     System.out.println(SimpleWithSideEffects.INSTANCE.bar(next()));
   }
 
-  private synchronized void testSimpleWithParams() {
+  @NeverInline
+  private void testSimpleWithParams() {
     System.out.println(SimpleWithParams.INSTANCE.foo());
     System.out.println(SimpleWithParams.INSTANCE.bar(next()));
   }
 
-  private synchronized void testSimpleWithGetter() {
+  @NeverInline
+  private void testSimpleWithGetter() {
     System.out.println(SimpleWithGetter.getInstance().foo());
     System.out.println(SimpleWithGetter.getInstance().bar(next()));
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
new file mode 100644
index 0000000..d9dfcd8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.uninstantiatedtypes;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Ignore;
+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 ParameterRewritingTest extends TestBase {
+
+  private final Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public ParameterRewritingTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Ignore("b/110806787")
+  @Test
+  public void test() throws Exception {
+    String expected =
+        StringUtils.lines(
+            "Factory.createStatic() -> null",
+            "Factory.createStaticWithUnused1() -> null",
+            "Factory.createStaticWithUnused2() -> null",
+            "Factory.createStaticWithUnused3() -> null");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
+
+    CodeInspector inspector =
+        testForR8(backend)
+            .addInnerClasses(ParameterRewritingTest.class)
+            .addKeepMainRule(TestClass.class)
+            .enableInliningAnnotations()
+            .enableMergeAnnotations()
+            .addKeepRules("-dontobfuscate")
+            .addOptionsModification(options -> options.enableClassInlining = false)
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expected)
+            .inspector();
+
+    ClassSubject factoryClassSubject = inspector.clazz(Factory.class);
+    MethodSubject createStaticMethodSubject =
+        factoryClassSubject.uniqueMethodWithName("createStatic");
+    assertThat(createStaticMethodSubject, isPresent());
+    assertEquals(1, createStaticMethodSubject.getMethod().method.proto.parameters.size());
+
+    for (int i = 1; i <= 3; ++i) {
+      String createStaticWithUnusedMethodName = "createStaticWithUnused" + i;
+      MethodSubject createStaticWithUnusedMethodSubject =
+          factoryClassSubject.uniqueMethodWithName(createStaticWithUnusedMethodName);
+      assertThat(createStaticWithUnusedMethodSubject, isPresent());
+
+      DexMethod method = createStaticWithUnusedMethodSubject.getMethod().method;
+      assertEquals(1, method.proto.parameters.size());
+      assertEquals("java.lang.String", method.proto.parameters.toString());
+    }
+
+    assertThat(inspector.clazz(Uninstantiated.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Object obj1 = Factory.createStatic(null, "Factory.createStatic()");
+      System.out.println(" -> " + obj1);
+
+      Object obj2 =
+          Factory.createStaticWithUnused1(new Object(), null, "Factory.createStaticWithUnused1()");
+      System.out.println(" -> " + obj2);
+
+      Object obj3 =
+          Factory.createStaticWithUnused2(null, new Object(), "Factory.createStaticWithUnused2()");
+      System.out.println(" -> " + obj3);
+
+      Object obj4 =
+          Factory.createStaticWithUnused3(null, "Factory.createStaticWithUnused3()", new Object());
+      System.out.println(" -> " + obj4);
+    }
+  }
+
+  @NeverMerge
+  static class Uninstantiated {}
+
+  @NeverMerge
+  static class Factory {
+
+    @NeverInline
+    public static Object createStatic(Uninstantiated uninstantiated, String msg) {
+      System.out.print(msg);
+      return uninstantiated;
+    }
+
+    @NeverInline
+    public static Object createStaticWithUnused1(
+        Object unused, Uninstantiated uninstantiated, String msg) {
+      System.out.print(msg);
+      return uninstantiated;
+    }
+
+    @NeverInline
+    public static Object createStaticWithUnused2(
+        Uninstantiated uninstantiated, Object unused, String msg) {
+      System.out.print(msg);
+      return uninstantiated;
+    }
+
+    @NeverInline
+    public static Object createStaticWithUnused3(
+        Uninstantiated uninstantiated, String msg, Object unused) {
+      System.out.print(msg);
+      return uninstantiated;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
new file mode 100644
index 0000000..0fb68e6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
@@ -0,0 +1,149 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.uninstantiatedtypes;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Ignore;
+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 VoidReturnTypeRewritingTest extends TestBase {
+
+  private final Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public VoidReturnTypeRewritingTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Ignore("b/110806787")
+  @Test
+  public void test() throws Exception {
+    String expected =
+        StringUtils.lines(
+            "Factory.createStatic() -> null",
+            "Factory.createVirtual() -> null",
+            "SubFactory.createVirtual() -> null",
+            "SubSubFactory.createVirtual() -> null");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
+
+    CodeInspector inspector =
+        testForR8(backend)
+            .addInnerClasses(VoidReturnTypeRewritingTest.class)
+            .addKeepMainRule(TestClass.class)
+            .enableInliningAnnotations()
+            .enableMergeAnnotations()
+            .addKeepRules("-dontobfuscate")
+            .addOptionsModification(options -> options.enableClassInlining = false)
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expected)
+            .inspector();
+
+    ClassSubject factoryClassSubject = inspector.clazz(Factory.class);
+    MethodSubject createStaticMethodSubject =
+        factoryClassSubject.uniqueMethodWithName("createStatic");
+    assertThat(createStaticMethodSubject, isPresent());
+    assertTrue(createStaticMethodSubject.getMethod().method.proto.returnType.isVoidType());
+    MethodSubject createVirtualMethodSubject =
+        factoryClassSubject.uniqueMethodWithName("createVirtual");
+    assertThat(createVirtualMethodSubject, isPresent());
+    assertTrue(createVirtualMethodSubject.getMethod().method.proto.returnType.isVoidType());
+
+    createVirtualMethodSubject =
+        inspector.clazz(SubFactory.class).uniqueMethodWithName("createVirtual");
+    assertThat(createVirtualMethodSubject, isPresent());
+    assertTrue(createVirtualMethodSubject.getMethod().method.proto.returnType.isVoidType());
+
+    ClassSubject subSubFactoryClassSubject = inspector.clazz(SubSubFactory.class);
+    assertThat(subSubFactoryClassSubject.method("void", "createVirtual"), isPresent());
+    assertThat(
+        subSubFactoryClassSubject.method(SubUninstantiated.class.getTypeName(), "createVirtual"),
+        isPresent());
+
+    // TODO(b/110806787): Uninstantiated is kept because SubUninstantiated inherits from it.
+    // We should consider rewriting SubUninstantiated such that it no longer inherits from
+    // Uninstantiated.
+    assertThat(inspector.clazz(Uninstantiated.class), isPresent());
+    assertThat(inspector.clazz(SubUninstantiated.class), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Uninstantiated obj1 = Factory.createStatic();
+      System.out.println(" -> " + obj1);
+
+      Uninstantiated obj2 = new Factory().createVirtual();
+      System.out.println(" -> " + obj2);
+
+      Uninstantiated obj3 = new SubFactory().createVirtual();
+      System.out.println(" -> " + obj3);
+
+      Uninstantiated obj4 = new SubSubFactory().createVirtual();
+      System.out.println(" -> " + obj4);
+    }
+  }
+
+  @NeverMerge
+  static class Uninstantiated {}
+
+  @NeverMerge
+  static class SubUninstantiated extends Uninstantiated {}
+
+  @NeverMerge
+  static class Factory {
+
+    @NeverInline
+    public static Uninstantiated createStatic() {
+      System.out.print("Factory.createStatic()");
+      return null;
+    }
+
+    @NeverInline
+    public Uninstantiated createVirtual() {
+      System.out.print("Factory.createVirtual()");
+      return null;
+    }
+  }
+
+  @NeverMerge
+  static class SubFactory extends Factory {
+
+    @Override
+    @NeverInline
+    public Uninstantiated createVirtual() {
+      System.out.print("SubFactory.createVirtual()");
+      return null;
+    }
+  }
+
+  @NeverMerge
+  static class SubSubFactory extends SubFactory {
+
+    @Override
+    @NeverInline
+    public SubUninstantiated createVirtual() {
+      System.out.print("SubSubFactory.createVirtual()");
+      return null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 7e4b76a..5ed3cc5 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -21,7 +21,7 @@
 
   private static class MockRegisterAllocator implements RegisterAllocator {
     @Override
-    public void allocateRegisters(boolean debug) {
+    public void allocateRegisters() {
     }
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index b76e947..9380115 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -59,12 +60,7 @@
 
   @Parameters(name = "allowAccessModification: {0} target: {1}")
   public static Collection<Object[]> data() {
-    ImmutableList.Builder<Object[]> builder = new ImmutableList.Builder<>();
-    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
-      builder.add(new Object[]{Boolean.TRUE, targetVersion});
-      builder.add(new Object[]{Boolean.FALSE, targetVersion});
-    }
-    return builder.build();
+    return buildParameters(BooleanUtils.values(), KotlinTargetVersion.values());
   }
 
   protected void addExtraClasspath(Path path) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 7d73d8f..1d3f25e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -8,8 +8,8 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.SgetObject;
 import com.android.tools.r8.graph.DexClass;
@@ -20,28 +20,17 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
-import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.junit.Test;
-import org.junit.runners.Parameterized.Parameters;
 
 public class KotlinClassInlinerTest extends AbstractR8KotlinTestBase {
-  @Parameters(name = "allowAccessModification: {0} target: {1}")
-  public static Collection<Object[]> data() {
-    ImmutableList.Builder<Object[]> builder = new ImmutableList.Builder<>();
-    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
-      builder.add(new Object[]{Boolean.TRUE, targetVersion});
-    }
-    return builder.build();
-  }
 
   private static boolean isLambda(DexClass clazz) {
     return !clazz.type.getPackageDescriptor().startsWith("kotlin") &&
@@ -67,6 +56,7 @@
 
   @Test
   public void testJStyleLambdas() throws Exception {
+    assumeTrue("Only work with -allowaccessmodification", allowAccessModification);
     final String mainClassName = "class_inliner_lambda_j_style.MainKt";
     runTest("class_inliner_lambda_j_style", mainClassName, false, (app) -> {
       CodeInspector inspector = new CodeInspector(app);
@@ -112,6 +102,7 @@
 
   @Test
   public void testKStyleLambdas() throws Exception {
+    assumeTrue("Only work with -allowaccessmodification", allowAccessModification);
     final String mainClassName = "class_inliner_lambda_k_style.MainKt";
     runTest("class_inliner_lambda_k_style", mainClassName, false, (app) -> {
       CodeInspector inspector = new CodeInspector(app);
@@ -186,6 +177,7 @@
 
   @Test
   public void testDataClass() throws Exception {
+    assumeTrue("Only work with -allowaccessmodification", allowAccessModification);
     final String mainClassName = "class_inliner_data_class.MainKt";
     runTest("class_inliner_data_class", mainClassName, true, (app) -> {
       CodeInspector inspector = new CodeInspector(app);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
index c19b80e..ced08f5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -7,28 +7,18 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 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.Collection;
 import java.util.concurrent.atomic.AtomicInteger;
 import org.junit.Test;
-import org.junit.runners.Parameterized.Parameters;
 
 public class KotlinClassStaticizerTest extends AbstractR8KotlinTestBase {
-  @Parameters(name = "allowAccessModification: {0} target: {1}")
-  public static Collection<Object[]> data() {
-    ImmutableList.Builder<Object[]> builder = new ImmutableList.Builder<>();
-    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
-      builder.add(new Object[]{Boolean.TRUE, targetVersion});
-    }
-    return builder.build();
-  }
 
   @Test
   public void testCompanionAndRegularObjects() throws Exception {
+    assumeTrue("Only work with -allowaccessmodification", allowAccessModification);
     final String mainClassName = "class_staticizer.MainKt";
 
     // Without class staticizer.
diff --git a/src/test/java/com/android/tools/r8/kotlin/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/MetadataStripTest.java
index 2abc1ed..1ab3147 100644
--- a/src/test/java/com/android/tools/r8/kotlin/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/MetadataStripTest.java
@@ -47,13 +47,7 @@
 
   @Parameterized.Parameters(name = "Backend: {0} target: {1}")
   public static Collection<Object[]> data() {
-    ImmutableList.Builder<Object[]> builder = new ImmutableList.Builder<>();
-    for (Backend backend : Backend.values()) {
-      for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
-        builder.add(new Object[]{backend, targetVersion});
-      }
-    }
-    return builder.build();
+    return buildParameters(Backend.values(), KotlinTargetVersion.values());
   }
 
   public MetadataStripTest(Backend backend, KotlinTargetVersion targetVersion) {
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
index 4a7db37..0f3accb 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -168,7 +168,8 @@
         getProguardConfig(enableAdaptResourceFileContents, adaptResourceFileContentsPathFilter),
         "-neverinline class com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B {",
         "  public void method();",
-        "}");
+        "}",
+        "-neverclassinline class com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B");
   }
 
   @Test
@@ -324,13 +325,7 @@
         .addDataResources(getDataResources())
         .enableProguardTestOptions()
         .addKeepRules(proguardConfig)
-        .addOptionsModification(
-            o -> {
-              // TODO(christofferqa): Class inliner should respect -neverinline.
-              o.enableClassInlining = false;
-              o.enableVerticalClassMerging = true;
-              o.dataResourceConsumer = dataResourceConsumer;
-            })
+        .addOptionsModification(o -> o.dataResourceConsumer = dataResourceConsumer)
         .compile();
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
index ecfddf4..095d7e9 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.KeepingDiagnosticHandler;
+import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.io.File;
@@ -93,15 +94,15 @@
 
   private static String getProguardConfigWithNeverInline(
       boolean enableAdaptResourceFileNames, String adaptResourceFileNamesPathFilter) {
-    return String.join(
-        System.lineSeparator(),
+    return StringUtils.lines(
         getProguardConfig(enableAdaptResourceFileNames, adaptResourceFileNamesPathFilter),
         "-neverinline class " + adaptresourcefilenames.A.class.getName() + " {",
         "  public void method();",
         "}",
         "-neverinline class " + adaptresourcefilenames.B.Inner.class.getName() + " {",
         "  public void method();",
-        "}");
+        "}",
+        "-neverclassinline class *");
   }
 
   @Test
@@ -262,9 +263,6 @@
     return ToolHelper.runR8(
         command,
         options -> {
-          // TODO(christofferqa): Class inliner should respect -neverinline.
-          options.enableClassInlining = false;
-          options.enableVerticalClassMerging = true;
           options.dataResourceConsumer = dataResourceConsumer;
           options.proguardMapConsumer = proguardMapConsumer;
           options.testing.suppressExperimentalCfBackendWarning = true;
diff --git a/src/test/java/com/android/tools/r8/naming/b114554345/B114554345.java b/src/test/java/com/android/tools/r8/naming/b114554345/B114554345.java
index 5e9b9c8..0d2ab3a 100644
--- a/src/test/java/com/android/tools/r8/naming/b114554345/B114554345.java
+++ b/src/test/java/com/android/tools/r8/naming/b114554345/B114554345.java
@@ -9,9 +9,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApp;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -22,13 +19,9 @@
 
   private final Backend backend;
 
-  @Parameters(name = "mode:{0}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (Backend backend : Backend.values()) {
-      parameters.add(new Object[] {backend});
-    }
-    return parameters;
+  @Parameters(name = "backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
   }
 
   public B114554345(Backend backend) {
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTest.java
index e1c61c1..b110ead 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTest.java
@@ -22,7 +22,6 @@
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
@@ -39,13 +38,7 @@
 
   @Parameters(name = "Backend: {0}, mode: {1}")
   public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (Backend backend : Backend.values()) {
-      for (CompilationMode mode : CompilationMode.values()) {
-        parameters.add(new Object[] {backend, mode});
-      }
-    }
-    return parameters;
+    return buildParameters(Backend.values(), CompilationMode.values());
   }
 
   public RetraceTest(Backend backend, CompilationMode mode) {
diff --git a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
new file mode 100644
index 0000000..edd6fd6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
@@ -0,0 +1,181 @@
+// Copyright (c) 2018 the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.reachabilitysensitive;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.AddIntLit8;
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import dalvik.annotation.optimization.ReachabilitySensitive;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+class TestClass {
+  public void method() {
+    int i = 2;
+    int j = i + 1;
+    int k = j + 2;
+    System.out.println(k);
+  }
+}
+
+class TestClassWithAnnotatedField {
+  @ReachabilitySensitive private final long field = 0;
+
+  public void method() {
+    int i = 2;
+    int j = i + 1;
+    int k = j + 2;
+    System.out.println(k);
+  }
+}
+
+class TestClassWithAnnotatedMethod {
+
+  @ReachabilitySensitive
+  public void unrelatedAnnotatedMethod() {}
+
+  public void method() {
+    int i = 2;
+    int j = i + 1;
+    int k = j + 2;
+    System.out.println(k);
+  }
+}
+
+@RunWith(Parameterized.class)
+public class ReachabilitySensitiveTest extends TestBase {
+
+  private final Tool tool;
+
+  @Parameters(name = "{0}")
+  public static List<Object> data() {
+    return ImmutableList.of(Tool.D8, Tool. R8);
+  }
+
+  public ReachabilitySensitiveTest(Tool tool) {
+    this.tool = tool;
+  }
+
+  @Test
+  public void testNoAnnotation()
+      throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
+    CodeInspector inspector = tool == Tool.R8
+        ? compileR8(TestClass.class)
+        : compile(TestClass.class);
+    DexCode code =
+        inspector.method(TestClass.class.getMethod("method")).getMethod().getCode().asDexCode();
+    // Computation of k is constant folded and the value takes up one register. System.out takes
+    // up another register and the receiver is the last.
+    assertEquals(3, code.registerSize);
+    checkNoLocals(code);
+  }
+
+  @Test
+  public void testFieldAnnotation()
+      throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
+    CodeInspector inspector = tool == Tool.R8
+        ? compileR8(TestClassWithAnnotatedField.class)
+        : compile(TestClassWithAnnotatedField.class);
+    checkAnnotatedCode(
+        inspector
+            .method(TestClassWithAnnotatedField.class.getMethod("method"))
+            .getMethod()
+            .getCode()
+            .asDexCode());
+  }
+
+  @Test
+  public void testMethodAnnotation()
+      throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
+    CodeInspector inspector = tool == Tool.R8
+        ? compileR8(TestClassWithAnnotatedMethod.class)
+        : compile(TestClassWithAnnotatedMethod.class);
+    checkAnnotatedCode(
+        inspector
+            .method(TestClassWithAnnotatedMethod.class.getMethod("method"))
+            .getMethod()
+            .getCode()
+            .asDexCode());
+  }
+
+  private void checkNoLocals(DexCode code) {
+    // Even if we preserve live range of locals, we do not output locals information
+    // as this is a release build.
+    assertTrue(Arrays.stream(code.getDebugInfo().events)
+            .allMatch(event -> !(event instanceof StartLocal)));
+  }
+
+  private void checkAnnotatedCode(DexCode code) {
+    // All live at the same time: receiver, i, j, k, System.out.
+    assertEquals(5, code.registerSize);
+    Instruction first = code.instructions[0];
+    Instruction second = code.instructions[1];
+    Instruction third = code.instructions[2];
+    // None of the local declarations overwrite other locals.
+    assertTrue(first instanceof Const4);
+    assertTrue(second instanceof AddIntLit8);
+    assertTrue(third instanceof AddIntLit8);
+    int firstRegister = ((Const4) first).A;
+    int secondRegister = ((AddIntLit8) second).AA;
+    int thirdRegister = ((AddIntLit8) third).AA;
+    assertFalse(firstRegister == secondRegister);
+    assertFalse(firstRegister == thirdRegister);
+    assertFalse(secondRegister == thirdRegister);
+    checkNoLocals(code);
+  }
+
+  private CodeInspector compile(Class... classes)
+      throws CompilationFailedException, IOException, ExecutionException {
+    return testForD8()
+        .addProgramClasses(classes)
+        .setMode(CompilationMode.RELEASE)
+        .compile()
+        .inspector();
+  }
+
+  private CodeInspector compileR8(Class... classes)
+      throws CompilationFailedException, IOException, ExecutionException {
+    List<String> keepRules =
+        Arrays.stream(classes)
+            .map(c -> "-keep class " + c.getCanonicalName() + " { <methods>; }")
+            .collect(Collectors.toList());
+    return testForR8(Backend.DEX)
+        .addProgramClasses(classes)
+        // TODO(ager): This will be in android.jar over time. For now, make it part of the app.
+        .addProgramClasses(ReachabilitySensitive.class)
+        .setMode(CompilationMode.RELEASE)
+        // Keep the input class and its methods.
+        .addKeepRules(keepRules)
+        // Keep the annotation class.
+        .addKeepRules("-keep class dalvik.annotation.optimization.ReachabilitySensitive")
+        // Keep the annotation and debug information which is needed for debug mode to actually
+        // keep things alive.
+        .addKeepRules("-keepattributes *Annotations*,LineNumberTable," +
+            "LocalVariableTable,LocalVariableTypeTable")
+        .compile()
+        .inspector();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java b/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
index daf0bc2..ba79ec5 100644
--- a/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
+++ b/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
@@ -21,9 +21,9 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Set;
@@ -50,13 +50,7 @@
 
   @Parameterized.Parameters(name = "Backend: {0}, Minify: {1}")
   public static Collection<Object[]> data() {
-    List<Object[]> result = new ArrayList<>();
-    for (Backend backend : Backend.values()) {
-      for (boolean minify : new boolean[] {false, true}) {
-        result.add(new Object[] {backend, minify});
-      }
-    }
-    return result;
+    return buildParameters(Backend.values(), BooleanUtils.values());
   }
 
   public KeepDirectoriesTest(Backend backend, boolean minify) {
diff --git a/src/test/java/com/android/tools/r8/shaking/AtomicFieldUpdaterTest.java b/src/test/java/com/android/tools/r8/shaking/AtomicFieldUpdaterTest.java
index 7e262ee..8dcdd38 100644
--- a/src/test/java/com/android/tools/r8/shaking/AtomicFieldUpdaterTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/AtomicFieldUpdaterTest.java
@@ -15,9 +15,6 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.File;
 import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,13 +26,9 @@
 
   private final Backend backend;
 
-  @Parameters(name = "mode:{0}")
-  public static Collection<Object[]> data() {
-    List<Object[]> parameters = new ArrayList<>();
-    for (Backend backend : Backend.values()) {
-      parameters.add(new Object[] {backend});
-    }
-    return parameters;
+  @Parameters(name = "backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
   }
 
   public AtomicFieldUpdaterTest(Backend backend) {
diff --git a/src/test/java/com/android/tools/r8/shaking/ClassKindTest.java b/src/test/java/com/android/tools/r8/shaking/ClassKindTest.java
index 89cbbbe..5eba7b5 100644
--- a/src/test/java/com/android/tools/r8/shaking/ClassKindTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ClassKindTest.java
@@ -33,7 +33,7 @@
   }
 
   @Parameters(name = "{0}")
-  public static Collection<Object[]> paramters() {
+  public static Collection<Object[]> parameters() {
     return ImmutableList.copyOf(new Object[][]{
         {"-keep interface *", ImmutableList.of(Interface.class)},
         {"-keep class *", CLASSES_TO_INCLUDE},
diff --git a/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java b/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java
index bebe66f..afc319a 100644
--- a/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -48,12 +49,7 @@
 
   @Parameterized.Parameters(name = "Backend: {0} minification: {1}")
   public static Collection<Object[]> data() {
-    ImmutableList.Builder<Object[]> builder = new ImmutableList.Builder<>();
-    for (Backend backend : Backend.values()) {
-      builder.add(new Object[]{backend, Boolean.TRUE});
-      builder.add(new Object[]{backend, Boolean.FALSE});
-    }
-    return builder.build();
+    return buildParameters(Backend.values(), BooleanUtils.values());
   }
 
   public EnclosingMethodTest(Backend backend, boolean enableMinification) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java
new file mode 100644
index 0000000..225ddd6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+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 InstantiatedLambdaReceiverTest extends TestBase {
+
+  private Backend backend;
+
+  private static final String expectedOutput = "In C.m()";
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public InstantiatedLambdaReceiverTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void jvmTest() throws Exception {
+    assumeTrue(
+        "JVM test independent of Art version - only run when testing on latest",
+        ToolHelper.getDexVm().getVersion().isLatest());
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+  }
+
+  @Test
+  public void dexTest() throws Exception {
+    testForR8(backend)
+        .addInnerClasses(InstantiatedLambdaReceiverTest.class)
+        .addKeepMainRule(TestClass.class)
+        .run(TestClass.class)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  interface I {
+    void m();
+  }
+
+  interface II extends I {}
+
+  static class C implements I {
+
+    @Override
+    public void m() {
+      System.out.print("In C.m()");
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      I i = new C();
+      II x = i::m; // This should mark II as being instantiated!
+      x.m();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
index 2490d41..4be2aaf 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -45,15 +46,7 @@
 
   @Parameterized.Parameters(name = "Backend: {0}, class inlining: {1}, vertical class merging: {2}")
   public static Collection<Object[]> data() {
-    ImmutableList.Builder<Object[]> builder = ImmutableList.builder();
-    for (Backend backend : Backend.values()) {
-      for (boolean enableClassInlining : ImmutableList.of(true, false)) {
-        for (boolean enableVerticalClassMerging : ImmutableList.of(true, false)) {
-          builder.add(new Object[] {backend, enableClassInlining, enableVerticalClassMerging});
-        }
-      }
-    }
-    return builder.build();
+    return buildParameters(Backend.values(), BooleanUtils.values(), BooleanUtils.values());
   }
 
   public NonVirtualOverrideTest(
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index 3b5a830..e5f3517 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.shaking.PrintUsageTest.PrintUsageInspector.ClassSubject;
 import com.android.tools.r8.utils.ListUtils;
@@ -39,12 +40,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class PrintUsageTest {
-
-  private enum Backend {
-    CF,
-    DEX
-  }
+public class PrintUsageTest extends TestBase {
 
   private static final String PRINT_USAGE_FILE_SUFFIX = "-print-usage.txt";
 
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 c268023..b302615 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -7,7 +7,7 @@
 
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
-import com.android.tools.r8.TestBase.MinifyMode;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -44,7 +44,7 @@
  * that calls {@link TreeShakingTest::runTest}, passing in the path to your keep rule file and
  * lambdas to determine if the right bits of the application are kept or discarded.
  */
-public abstract class TreeShakingTest {
+public abstract class TreeShakingTest extends TestBase {
 
   private Path proguardMap;
   private Path out;
@@ -53,10 +53,6 @@
     DEX, JAR
   }
 
-  protected enum Backend {
-    DEX, CF
-  }
-
   private final String name;
   private final String mainClass;
   private final Frontend frontend;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index e420e1e..a603f92 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -11,13 +11,11 @@
 
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 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.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
index d02fa5b..6aa68de 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
@@ -48,35 +49,30 @@
 
 @RunWith(Parameterized.class)
 public class IfRuleWithInlining extends ProguardCompatibilityTestBase {
-  private final static List<Class> CLASSES = ImmutableList.of(
-      A.class, D.class, Main.class);
+  private static final List<Class> CLASSES = ImmutableList.of(A.class, D.class, Main.class);
 
   private final Shrinker shrinker;
-  private final boolean inlineMethod;
+  private final boolean neverInlineMethod;
 
-  public IfRuleWithInlining(Shrinker shrinker, boolean inlineMethod) {
+  public IfRuleWithInlining(Shrinker shrinker, boolean neverInlineMethod) {
     this.shrinker = shrinker;
-    this.inlineMethod = inlineMethod;
+    this.neverInlineMethod = neverInlineMethod;
   }
 
-  @Parameters(name = "shrinker: {0} inlineMethod: {1}")
+  @Parameters(name = "shrinker: {0} neverInlineMethod: {1}")
   public static Collection<Object[]> data() {
     // We don't run this on Proguard, as triggering inlining in Proguard is out of our control.
-    return ImmutableList.of(
-        new Object[] {Shrinker.R8, true},
-        new Object[] {Shrinker.R8, false},
-        new Object[] {Shrinker.R8_CF, true},
-        new Object[] {Shrinker.R8_CF, false});
+    return buildParameters(
+        ImmutableList.of(Shrinker.R8, Shrinker.R8_CF), BooleanUtils.values());
   }
 
   private void check(AndroidApp app) throws Exception {
     CodeInspector inspector = new CodeInspector(app);
     ClassSubject clazzA = inspector.clazz(A.class);
     assertThat(clazzA, isPresent());
-    // A.a might be inlined.
-    assertEquals(!inlineMethod, clazzA.method("int", "a", ImmutableList.of()).isPresent());
-    // TODO(110148109): class D should be present - inlining or not.
-    assertEquals(!inlineMethod, inspector.clazz(D.class).isPresent());
+    // A.a should not be inlined.
+    assertThat(clazzA.method("int", "a", ImmutableList.of()), isPresent());
+    assertThat(inspector.clazz(D.class), isPresent());
     ProcessResult result;
     if (shrinker == Shrinker.R8) {
       result = runOnArtRaw(app, Main.class.getName());
@@ -87,22 +83,18 @@
       result = ToolHelper.runJava(file, Main.class.getName());
     }
     assertEquals(0, result.exitCode);
-    // TODO(110148109): Output should be the same - inlining or not.
-    assertEquals(!inlineMethod ? "1" : "2", result.stdout);
+    assertEquals("1", result.stdout);
   }
 
   @Test
   public void testMergedClassMethodInIfRule() throws Exception {
-    List<String> config = ImmutableList.of(
-        "-keep class **.Main { public static void main(java.lang.String[]); }",
-        inlineMethod
-            ? "-forceinline class **.A { int a(); }"
-            : "-neverinline class **.A { int a(); }",
-        "-if class **.A { static int a(); }",
-        "-keep class **.D",
-        "-dontobfuscate"
-    );
-
+    List<String> config =
+        ImmutableList.of(
+            "-keep class **.Main { public static void main(java.lang.String[]); }",
+            neverInlineMethod ? "-neverinline class **.A { int a(); }" : "",
+            "-if class **.A { static int a(); }",
+            "-keep class **.D",
+            "-dontobfuscate");
     check(runShrinker(shrinker, CLASSES, config));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
index 57ad556..2d9027d 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
@@ -8,14 +8,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
-import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
@@ -23,6 +23,7 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
+@NeverClassInline
 class A {
   int x = 1;
   int a() throws ClassNotFoundException {
@@ -34,6 +35,7 @@
   }
 }
 
+@NeverClassInline
 class B extends A {
   int y = 2;
   int b() {
@@ -41,6 +43,7 @@
   }
 }
 
+@NeverClassInline
 class C extends B {
   int z = 3;
   int c() {
@@ -48,8 +51,8 @@
   }
 }
 
-class D {
-}
+@NeverClassInline
+class D {}
 
 class Main {
   public static void main(String[] args) throws ClassNotFoundException {
@@ -61,7 +64,7 @@
 @RunWith(Parameterized.class)
 public class IfRuleWithVerticalClassMerging extends TestBase {
 
-  private static final List<Class> CLASSES =
+  private static final List<Class<?>> CLASSES =
       ImmutableList.of(A.class, B.class, C.class, D.class, Main.class);
 
   private final Backend backend;
@@ -75,44 +78,20 @@
   @Parameters(name = "Backend: {0}, vertical class merging: {1}")
   public static Collection<Object[]> data() {
     // We don't run this on Proguard, as Proguard does not merge A into B.
-    return ImmutableList.of(
-        new Object[] {Backend.DEX, true},
-        new Object[] {Backend.DEX, false},
-        new Object[] {Backend.CF, true},
-        new Object[] {Backend.CF, false});
+    return buildParameters(Backend.values(), BooleanUtils.values());
   }
 
   private void configure(InternalOptions options) {
     options.enableVerticalClassMerging = enableVerticalClassMerging;
-
-    // TODO(b/110148109): Allow ordinary method inlining when -if rules work with inlining.
-    options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
-  }
-
-  private void check(AndroidApp app) throws Exception {
-    CodeInspector inspector = new CodeInspector(app);
-    ClassSubject clazzA = inspector.clazz(A.class);
-    assertEquals(!enableVerticalClassMerging, clazzA.isPresent());
-    ClassSubject clazzB = inspector.clazz(B.class);
-    assertThat(clazzB, isPresent());
-    ClassSubject clazzD = inspector.clazz(D.class);
-    assertThat(clazzD, isPresent());
-    assertEquals("123456", runOnVM(app, Main.class, backend));
   }
 
   @Test
   public void testMergedClassInIfRule() throws Exception {
     // Class C is kept, meaning that it will not be touched.
     // Class A will be merged into class B.
-    String config =
-        String.join(
-            System.lineSeparator(),
-            "-keep class **.Main { public static void main(java.lang.String[]); }",
-            "-keep class **.C",
-            "-if class **.A",
-            "-keep class **.D",
-            "-dontobfuscate");
-    check(compileWithR8(readClasses(CLASSES), config, this::configure, backend));
+    runTestWithProguardConfig(
+        StringUtils.lines(
+            "-keep class **.C", "-if class **.A", "-keep class **.D", "-dontobfuscate"));
   }
 
   @Test
@@ -120,15 +99,9 @@
     // Class C is kept, meaning that it will not be touched.
     // Class A will be merged into class B.
     // Main.main access A.x, so that field exists satisfying the if rule.
-    String config =
-        String.join(
-            System.lineSeparator(),
-            "-keep class **.Main { public static void main(java.lang.String[]); }",
-            "-keep class **.C",
-            "-if class **.A { int x; }",
-            "-keep class **.D",
-            "-dontobfuscate");
-    check(compileWithR8(readClasses(CLASSES), config, this::configure, backend));
+    runTestWithProguardConfig(
+        StringUtils.lines(
+            "-keep class **.C", "-if class **.A { int x; }", "-keep class **.D", "-dontobfuscate"));
   }
 
   @Test
@@ -136,14 +109,31 @@
     // Class C is kept, meaning that it will not be touched.
     // Class A will be merged into class B.
     // Main.main access A.a(), that method exists satisfying the if rule.
-    String config =
-        String.join(
-            System.lineSeparator(),
-            "-keep class **.Main { public static void main(java.lang.String[]); }",
+    runTestWithProguardConfig(
+        StringUtils.lines(
             "-keep class **.C",
             "-if class **.A { int a(); }",
             "-keep class **.D",
-            "-dontobfuscate");
-    check(compileWithR8(readClasses(CLASSES), config, this::configure, backend));
+            "-dontobfuscate"));
+  }
+
+  private void runTestWithProguardConfig(String config) throws Exception {
+    CodeInspector inspector =
+        testForR8(backend)
+            .addProgramClasses(CLASSES)
+            .addKeepMainRule(Main.class)
+            .addKeepRules(config)
+            .enableClassInliningAnnotations()
+            .addOptionsModification(this::configure)
+            .run(Main.class)
+            .assertSuccessWithOutput("123456")
+            .inspector();
+
+    ClassSubject clazzA = inspector.clazz(A.class);
+    assertEquals(!enableVerticalClassMerging, clazzA.isPresent());
+    ClassSubject clazzB = inspector.clazz(B.class);
+    assertThat(clazzB, isPresent());
+    ClassSubject clazzD = inspector.clazz(D.class);
+    assertThat(clazzD, isPresent());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
index 7c85a7b..879efe0 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
@@ -55,6 +56,7 @@
       }
     }
 
+    @NeverClassInline
     static class TestClass extends SuperTestClass {
 
       private A field = null;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
index cda8096..4fa2440 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
@@ -5,18 +5,15 @@
 package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
-import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
@@ -42,7 +39,7 @@
   static class Unused {}
 
   final Backend backend;
-  final List<Class> classes;
+  final List<Class<?>> classes;
   final boolean enableVerticalClassMerging;
 
   public MergedTypeBaseTest(Backend backend, boolean enableVerticalClassMerging) {
@@ -54,7 +51,7 @@
     this.backend = backend;
     this.enableVerticalClassMerging = enableVerticalClassMerging;
     this.classes =
-        ImmutableList.<Class>builder()
+        ImmutableList.<Class<?>>builder()
             .add(A.class, B.class, C.class, I.class, J.class, K.class, Unused.class, getTestClass())
             .addAll(additionalClasses)
             .build();
@@ -63,11 +60,7 @@
   @Parameters(name = "Backend: {0}, vertical class merging: {1}")
   public static Collection<Object[]> data() {
     // We don't run this on Proguard, as Proguard does not merge A into B.
-    return ImmutableList.of(
-        new Object[] {Backend.DEX, true},
-        new Object[] {Backend.DEX, false},
-        new Object[] {Backend.CF, true},
-        new Object[] {Backend.CF, false});
+    return buildParameters(Backend.values(), BooleanUtils.values());
   }
 
   public abstract Class<?> getTestClass();
@@ -95,17 +88,18 @@
     String expected = getExpectedStdout();
     assertEquals(expected, runOnJava(getTestClass()));
 
-    String config =
-        StringUtils.joinLines(
-            "-keep class " + getTestClass().getTypeName() + " {",
-            "  public static void main(java.lang.String[]);",
-            "}",
+    testForR8(backend)
+        .addProgramClasses(classes)
+        .addKeepMainRule(getTestClass())
+        .addKeepRules(
             getConditionForProguardIfRule(),
             "-keep class " + Unused.class.getTypeName(),
-            getAdditionalKeepRules());
-    AndroidApp output = compileWithR8(readClasses(classes), config, this::configure, backend);
-    assertEquals(expected, runOnVM(output, getTestClass(), backend));
-    inspect(new CodeInspector(output));
+            getAdditionalKeepRules())
+        .addOptionsModification(this::configure)
+        .enableClassInliningAnnotations()
+        .run(getTestClass())
+        .assertSuccessWithOutput(expected)
+        .inspect(this::inspect);
   }
 
   private void configure(InternalOptions options) {
@@ -115,8 +109,5 @@
     // To ensure that the handling of extends and implements rules work as intended,
     // and that we don't end up keeping `Unused` only because one of the two implementations work.
     options.testing.allowProguardRulesThatUseExtendsOrImplementsWrong = false;
-
-    // TODO(b/110148109): Allow ordinary method inlining when -if rules work with inlining.
-    options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
   }
 }
diff --git a/src/test/java/dalvik/annotation/optimization/ReachabilitySensitive.java b/src/test/java/dalvik/annotation/optimization/ReachabilitySensitive.java
new file mode 100644
index 0000000..04fc705
--- /dev/null
+++ b/src/test/java/dalvik/annotation/optimization/ReachabilitySensitive.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2018 the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package dalvik.annotation.optimization;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+// This is a copy of libcore/dalvik/annotation/optimiztion/ReachabilitySensitive.java.
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface ReachabilitySensitive {}