Merge "Transfer publicized bit to bridge in class merger"
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 9fcca89..584b06d 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -68,6 +68,7 @@
     Path proguardCompatibilityRulesOutput = null;
 
     private boolean allowPartiallyImplementedProguardOptions = false;
+    private boolean allowTestProguardOptions = false;
 
     private StringConsumer mainDexListConsumer = null;
 
@@ -290,7 +291,8 @@
       }
 
       ProguardConfigurationParser parser = new ProguardConfigurationParser(
-          factory, reporter, !allowPartiallyImplementedProguardOptions);
+          factory, reporter,
+          !allowPartiallyImplementedProguardOptions, allowTestProguardOptions);
       if (!proguardConfigs.isEmpty()) {
         parser.parse(proguardConfigs);
       }
@@ -393,6 +395,11 @@
     void allowPartiallyImplementedProguardOptions() {
       allowPartiallyImplementedProguardOptions = true;
     }
+
+    // Internal for-testing method to allow proguard options only available for testing.
+    void allowTestProguardOptions() {
+      allowTestProguardOptions = true;
+    }
   }
 
   // Wrapper class to ensure that R8 does not allow DEX as program inputs.
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
index 4530040..6948a08 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class AlwaysMaterializingUser extends Instruction {
@@ -58,8 +58,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forAlwaysMaterializingUser();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index f724a52..cff655a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 
 /**
@@ -76,8 +76,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forArgument();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index e755759..01101f3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -22,8 +22,8 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Arrays;
 import java.util.function.Function;
 
@@ -133,8 +133,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forArrayGet();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index 9a170a6..cc358f0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -14,8 +14,8 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.function.Function;
 
 public class ArrayLength extends Instruction {
@@ -90,8 +90,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forArrayLength();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index a721955..023afce 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -18,8 +18,8 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Arrays;
 
@@ -154,8 +154,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forArrayPut();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Binop.java b/src/main/java/com/android/tools/r8/ir/code/Binop.java
index 9e6738a..e5190bb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Binop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Binop.java
@@ -13,8 +13,8 @@
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.function.Function;
 
 public abstract class Binop extends Instruction {
@@ -124,8 +124,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forBinop();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 51e1891..dba575f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 
 public class CheckCast extends Instruction {
@@ -113,8 +113,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.classIsVisible(invocationContext, type, info);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forCheckCast(type, invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 35549fb..3041aaf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.function.Function;
 
@@ -99,8 +99,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.classIsVisible(invocationContext, clazz, info);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forConstClass(clazz, invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java b/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java
index cb9ef18..f6e212d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 
 public abstract class ConstInstruction extends Instruction {
 
@@ -29,7 +29,8 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forConstInstruction();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
index 9e1c21d..637faf7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class DebugLocalRead extends Instruction {
@@ -60,8 +60,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forDebugLocalRead();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index edf68f3..53791e5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -100,8 +100,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forDebugLocalsChange();
   }
 
   public boolean apply(Int2ReferenceMap<DebugLocalInfo> locals) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index f778037..aed2128 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class DebugPosition extends Instruction {
@@ -57,8 +57,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forDebugPosition();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 997a8c9..d88ca97 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -3,13 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.List;
 
 public abstract class FieldInstruction extends Instruction {
@@ -50,27 +44,4 @@
   public FieldInstruction asFieldInstruction() {
     return this;
   }
-
-  /**
-   * Returns the target of this field instruction, if such target is known, or null.
-   * <p>
-   * A result of null indicates that the field is either undefined or not of the right kind.
-   */
-  abstract DexEncodedField lookupTarget(DexType type, AppInfo appInfo);
-
-  @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    // Resolve the field if possible and decide whether the instruction can inlined.
-    DexType fieldHolder = field.getHolder();
-    DexEncodedField target = lookupTarget(fieldHolder, info);
-    DexClass fieldClass = info.definitionFor(fieldHolder);
-    if ((target != null) && (fieldClass != null)) {
-      Constraint fieldConstraint = Constraint
-          .deriveConstraint(invocationContext, fieldHolder, target.accessFlags, info);
-      Constraint classConstraint = Constraint
-          .deriveConstraint(invocationContext, fieldHolder, fieldClass.accessFlags, info);
-      return Constraint.min(fieldConstraint, classConstraint);
-    }
-    return Constraint.NEVER;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 08f3b8f..1083669 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -17,12 +17,13 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 import org.objectweb.asm.Opcodes;
 
@@ -113,8 +114,9 @@
   }
 
   @Override
-  DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
-    return appInfo.lookupInstanceTarget(type, field);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forInstanceGet(field, invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index 6542e99..53fd31c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 
 public class InstanceOf extends Instruction {
@@ -81,8 +81,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.classIsVisible(invocationContext, type, info);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forInstanceOf(type, invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index ededf98..3585348 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -15,12 +15,12 @@
 import com.android.tools.r8.code.IputWide;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.Arrays;
 import org.objectweb.asm.Opcodes;
 
@@ -113,8 +113,9 @@
   }
 
   @Override
-  DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
-    return appInfo.lookupInstanceTarget(type, field);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forInstancePut(field, invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 62e518d..eb85222 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -17,8 +17,8 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
@@ -1056,11 +1056,11 @@
 
   /**
    * Returns the inlining constraint for this method when used in the context of the given type.
-   * <p>
-   * The type is used to judge visibility constraints and also for dispatch decisions.
+   *
+   * <p>The type is used to judge visibility constraints and also for dispatch decisions.
    */
-  public abstract Constraint inliningConstraint(AppInfoWithLiveness info,
-      DexType invocationContext);
+  public abstract Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext);
 
   public abstract void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper);
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 0579362..a6e6fd1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.List;
 
 public final class InvokeCustom extends Invoke {
@@ -101,8 +101,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.NEVER;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forInvokeCustom();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index 1ad50bc..88716dc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -113,8 +113,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return new InliningConstraints(info).forInvokeDirect(getInvokedMethod(), invocationContext);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forInvokeDirect(getInvokedMethod(), invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index 0fb447b..06d368d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -95,8 +95,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return new InliningConstraints(info).forInvokeInterface(getInvokedMethod(), invocationContext);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forInvokeInterface(getInvokedMethod(), invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 56b659a..a8ec608 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -83,12 +82,6 @@
     return lookupSingleTarget(appInfo, appInfo.dexItemFactory.objectType);
   }
 
-  // TODO(christofferqa): Pass an instance of InliningConstraints instead of [info] when
-  // InliningConstraints is complete.
-  @Override
-  public abstract Constraint inliningConstraint(
-      AppInfoWithLiveness info, DexType invocationContext);
-
   public abstract InlineAction computeInlining(InliningOracle decider, DexType invocationContext);
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index d4f6bd2..e34d25f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.List;
 import java.util.function.Function;
 
@@ -69,8 +69,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.classIsVisible(invocationContext, type, info);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forInvokeMultiNewArray(type, invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 96680b3..a2a9c36 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.List;
 import java.util.function.Function;
 
@@ -99,8 +99,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.classIsVisible(invocationContext, type, info);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forInvokeNewArray(type, invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 91f657d..8f90630 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -126,9 +126,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return new InliningConstraints(info)
-        .forInvokePolymorphic(getInvokedMethod(), invocationContext);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forInvokePolymorphic(getInvokedMethod(), invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index b985916..d2183c0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -103,8 +103,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return new InliningConstraints(info).forInvokeStatic(getInvokedMethod(), invocationContext);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forInvokeStatic(getInvokedMethod(), invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index 64f0d7b..920d94b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -112,7 +112,8 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return new InliningConstraints(info).forInvokeSuper(getInvokedMethod(), invocationContext);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forInvokeSuper(getInvokedMethod(), invocationContext);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 85c80ed..f55a407 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -95,8 +95,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return new InliningConstraints(info).forInvokeVirtual(getInvokedMethod(), invocationContext);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forInvokeVirtual(getInvokedMethod(), invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
index db151c6..6096292 100644
--- a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.List;
 
@@ -47,8 +47,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forJumpInstruction();
   }
 
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Load.java b/src/main/java/com/android/tools/r8/ir/code/Load.java
index 10f4dad..32654b8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Load.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 
 public class Load extends Instruction {
 
@@ -51,8 +51,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forLoad();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index 97d6e01..6b3c89c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 
 public class Monitor extends Instruction {
 
@@ -89,9 +89,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    // Conservative choice.
-    return Constraint.NEVER;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forMonitor();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index b095bb4..e474bb8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 
 public class Move extends Instruction {
@@ -100,8 +100,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forMove();
   }
 
   @Override
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 b7f0138..eabf7b2 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
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.HashSet;
 import java.util.List;
@@ -75,9 +75,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    // TODO(64432527): Revisit this constraint.
-    return Constraint.NEVER;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forMoveException();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index 3a69ac5..edca979 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 
 public class NewArrayEmpty extends Instruction {
@@ -84,8 +84,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.classIsVisible(invocationContext, type, info);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forNewArrayEmpty(type, invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index f203597..ac46cc7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Arrays;
 
@@ -112,8 +112,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forNewArrayFilledData();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index a5599fa..75931ad 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 
 public class NewInstance extends Instruction {
@@ -81,8 +81,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.classIsVisible(invocationContext, clazz, info);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forNewInstance(clazz, invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NonNull.java b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
index 0cf0616..462af06 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NonNull.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 
 public class NonNull extends Instruction {
@@ -85,8 +85,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forNonNull();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 70d6af4..0fb1949 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class Pop extends Instruction {
@@ -50,8 +50,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forPop();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 6d3a85b..37cfaf0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 
 public class Return extends JumpInstruction {
 
@@ -116,8 +116,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forReturn();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 1e4d125..486f9e2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -16,12 +16,13 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 import org.objectweb.asm.Opcodes;
 
@@ -108,8 +109,9 @@
   }
 
   @Override
-  DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
-    return appInfo.lookupStaticTarget(type, field);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forStaticGet(field, invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index df42f42..f910ff6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -14,12 +14,12 @@
 import com.android.tools.r8.code.SputWide;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import org.objectweb.asm.Opcodes;
 
 public class StaticPut extends FieldInstruction {
@@ -107,8 +107,9 @@
   }
 
   @Override
-  DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
-    return appInfo.lookupStaticTarget(type, field);
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forStaticPut(field, invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index ba5a502..b81af69 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class Store extends Instruction {
@@ -53,8 +53,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forStore();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Throw.java b/src/main/java/com/android/tools/r8/ir/code/Throw.java
index 5d8a4e7..4295dbe 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Throw.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 
 public class Throw extends JumpInstruction {
 
@@ -65,8 +65,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forThrow();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Unop.java b/src/main/java/com/android/tools/r8/ir/code/Unop.java
index 6db3ad5..2f6ed7f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Unop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Unop.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 
 abstract public class Unop extends Instruction {
@@ -48,8 +48,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.ALWAYS;
+  public Constraint inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forUnop();
   }
 
   @Override
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 cc7f571..631f234 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
@@ -91,7 +91,9 @@
   }
 
   private Reason computeInliningReason(DexEncodedMethod target) {
-    if (target.getOptimizationInfo().forceInline()) {
+    if (target.getOptimizationInfo().forceInline()
+        || (inliner.appInfo.hasLiveness()
+            && inliner.appInfo.withLiveness().forceInline.contains(target))) {
       return Reason.FORCE;
     }
     if (inliner.appInfo.hasLiveness()
@@ -252,7 +254,7 @@
   public InlineAction computeForInvokeWithReceiver(
       InvokeMethodWithReceiver invoke, DexType invocationContext) {
     DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
-    if (candidate == null || inliner.isBlackListed(candidate.method)) {
+    if (candidate == null || inliner.isBlackListed(candidate)) {
       return null;
     }
 
@@ -268,6 +270,7 @@
       if (info != null) {
         info.exclude(invoke, "receiver for candidate can be null");
       }
+      assert !inliner.appInfo.forceInline.contains(candidate.method);
       return null;
     }
 
@@ -293,7 +296,7 @@
   @Override
   public InlineAction computeForInvokeStatic(InvokeStatic invoke, DexType invocationContext) {
     DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
-    if (candidate == null || inliner.isBlackListed(candidate.method)) {
+    if (candidate == null || inliner.isBlackListed(candidate)) {
       return null;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 44ebdc7..db674bd 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
@@ -70,14 +70,14 @@
     blackList.add(appInfo.dexItemFactory.kotlin.intrinsics.throwNpe);
   }
 
-  public boolean isBlackListed(DexMethod method) {
-    return blackList.contains(method);
+  public boolean isBlackListed(DexEncodedMethod method) {
+    return blackList.contains(method.method) || appInfo.neverInline.contains(method);
   }
 
   private Constraint instructionAllowedForInlining(
-      DexEncodedMethod method, Instruction instruction) {
-    Constraint result = instruction.inliningConstraint(appInfo, method.method.holder);
-    if ((result == Constraint.NEVER) && instruction.isDebugInstruction()) {
+      Instruction instruction, InliningConstraints inliningConstraints, DexType invocationContext) {
+    Constraint result = instruction.inliningConstraint(inliningConstraints, invocationContext);
+    if (result == Constraint.NEVER && instruction.isDebugInstruction()) {
       return Constraint.ALWAYS;
     }
     return result;
@@ -85,10 +85,12 @@
 
   public Constraint computeInliningConstraint(IRCode code, DexEncodedMethod method) {
     Constraint result = Constraint.ALWAYS;
+    InliningConstraints inliningConstraints = new InliningConstraints(appInfo);
     InstructionIterator it = code.instructionIterator();
     while (it.hasNext()) {
       Instruction instruction = it.next();
-      Constraint state = instructionAllowedForInlining(method, instruction);
+      Constraint state =
+          instructionAllowedForInlining(instruction, inliningConstraints, method.method.holder);
       result = Constraint.min(result, state);
       if (result == Constraint.NEVER) {
         break;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index d4a06cd..78d70a0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -6,7 +6,9 @@
 
 import com.android.tools.r8.graph.AppInfo.ResolutionResult;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
@@ -42,10 +44,72 @@
     this.graphLense = graphLense;
   }
 
+  public Constraint forAlwaysMaterializingUser() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forArgument() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forArrayGet() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forArrayLength() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forArrayPut() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forBinop() {
+    return Constraint.ALWAYS;
+  }
+
   public Constraint forCheckCast(DexType type, DexType invocationContext) {
     return Constraint.classIsVisible(invocationContext, type, appInfo);
   }
 
+  public Constraint forConstClass(DexType type, DexType invocationContext) {
+    return Constraint.classIsVisible(invocationContext, type, appInfo);
+  }
+
+  public Constraint forConstInstruction() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forDebugLocalRead() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forDebugLocalsChange() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forDebugPosition() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forInstanceGet(DexField field, DexType invocationContext) {
+    return forFieldInstruction(
+        field, appInfo.lookupInstanceTarget(field.clazz, field), invocationContext);
+  }
+
+  public Constraint forInstanceOf(DexType type, DexType invocationContext) {
+    return Constraint.classIsVisible(invocationContext, type, appInfo);
+  }
+
+  public Constraint forInvokeCustom() {
+    return Constraint.NEVER;
+  }
+
+  public Constraint forInstancePut(DexField field, DexType invocationContext) {
+    return forFieldInstruction(
+        field, appInfo.lookupInstanceTarget(field.clazz, field), invocationContext);
+  }
+
   public Constraint forInvokeDirect(DexMethod method, DexType invocationContext) {
     return forSingleTargetInvoke(method, appInfo.lookupDirectTarget(method), invocationContext);
   }
@@ -54,6 +118,14 @@
     return forVirtualInvoke(method, appInfo.lookupInterfaceTargets(method), invocationContext);
   }
 
+  public Constraint forInvokeMultiNewArray(DexType type, DexType invocationContext) {
+    return Constraint.classIsVisible(invocationContext, type, appInfo);
+  }
+
+  public Constraint forInvokeNewArray(DexType type, DexType invocationContext) {
+    return Constraint.classIsVisible(invocationContext, type, appInfo);
+  }
+
   public Constraint forInvokePolymorphic(DexMethod method, DexType invocationContext) {
     return Constraint.NEVER;
   }
@@ -71,6 +143,90 @@
     return forVirtualInvoke(method, appInfo.lookupVirtualTargets(method), invocationContext);
   }
 
+  public Constraint forJumpInstruction() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forLoad() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forMonitor() {
+    // Conservative choice.
+    return Constraint.NEVER;
+  }
+
+  public Constraint forMove() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forMoveException() {
+    // TODO(64432527): Revisit this constraint.
+    return Constraint.NEVER;
+  }
+
+  public Constraint forNewArrayEmpty(DexType type, DexType invocationContext) {
+    return Constraint.classIsVisible(invocationContext, type, appInfo);
+  }
+
+  public Constraint forNewArrayFilledData() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forNewInstance(DexType type, DexType invocationContext) {
+    return Constraint.classIsVisible(invocationContext, type, appInfo);
+  }
+
+  public Constraint forNonNull() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forPop() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forReturn() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forStaticGet(DexField field, DexType invocationContext) {
+    return forFieldInstruction(
+        field, appInfo.lookupStaticTarget(field.clazz, field), invocationContext);
+  }
+
+  public Constraint forStaticPut(DexField field, DexType invocationContext) {
+    return forFieldInstruction(
+        field, appInfo.lookupStaticTarget(field.clazz, field), invocationContext);
+  }
+
+  public Constraint forStore() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forThrow() {
+    return Constraint.ALWAYS;
+  }
+
+  public Constraint forUnop() {
+    return Constraint.ALWAYS;
+  }
+
+  private Constraint forFieldInstruction(
+      DexField field, DexEncodedField target, DexType invocationContext) {
+    // Resolve the field if possible and decide whether the instruction can inlined.
+    DexType fieldHolder = graphLense.lookupType(field.clazz);
+    DexClass fieldClass = appInfo.definitionFor(fieldHolder);
+    if (target != null && fieldClass != null) {
+      Constraint fieldConstraint =
+          Constraint.deriveConstraint(invocationContext, fieldHolder, target.accessFlags, appInfo);
+      Constraint classConstraint =
+          Constraint.deriveConstraint(
+              invocationContext, fieldHolder, fieldClass.accessFlags, appInfo);
+      return Constraint.min(fieldConstraint, classConstraint);
+    }
+    return Constraint.NEVER;
+  }
+
   private Constraint forSingleTargetInvoke(
       DexMethod method, DexEncodedMethod target, DexType invocationContext) {
     if (method.holder.isArrayType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index e749479..c3a3781 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -107,6 +107,7 @@
 
   final private AppInfoWithLiveness appInfo;
   final private DexItemFactory dexItemFactory;
+  private final InliningConstraints inliningConstraints;
 
   // Representation of an outline.
   // This includes the instructions in the outline, and a map from the arguments of this outline
@@ -475,7 +476,7 @@
 
       // See whether we could move this invoke somewhere else. We reuse the logic from inlining
       // here, as the constraints are the same.
-      Constraint constraint = invoke.inliningConstraint(appInfo, method.method.holder);
+      Constraint constraint = invoke.inliningConstraint(inliningConstraints, method.method.holder);
       if (constraint != Constraint.ALWAYS) {
         return false;
       }
@@ -818,6 +819,7 @@
   public Outliner(AppInfoWithLiveness appInfo, InternalOptions options) {
     this.appInfo = appInfo;
     this.dexItemFactory = appInfo.dexItemFactory;
+    this.inliningConstraints = new InliningConstraints(appInfo);
     this.options = options;
   }
 
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 baacb1e..a1cc440 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1544,10 +1544,18 @@
      */
     public final Map<DexItem, ProguardMemberRule> assumedValues;
     /**
-     * All methods that have to be inlined due to a configuration directive.
+     * All methods that should be inlined if possible due to a configuration directive.
      */
     public final Set<DexItem> alwaysInline;
     /**
+     * All methods that *must* be inlined due to a configuration directive (testing only).
+     */
+    public final Set<DexItem> forceInline;
+    /**
+     * All methods that *must* never be inlined due to a configuration directive (testing only).
+     */
+    public final Set<DexItem> neverInline;
+    /**
      * All items with -identifiernamestring rule.
      */
     public final Set<DexItem> identifierNameStrings;
@@ -1602,6 +1610,8 @@
       this.noSideEffects = enqueuer.rootSet.noSideEffects;
       this.assumedValues = enqueuer.rootSet.assumedValues;
       this.alwaysInline = enqueuer.rootSet.alwaysInline;
+      this.forceInline = enqueuer.rootSet.forceInline;
+      this.neverInline = enqueuer.rootSet.neverInline;
       this.identifierNameStrings =
           Sets.union(enqueuer.rootSet.identifierNameStrings, enqueuer.identifierNameStrings);
       this.protoLiteFields = enqueuer.protoLiteFields;
@@ -1640,6 +1650,8 @@
       this.brokenSuperInvokes = previous.brokenSuperInvokes;
       this.protoLiteFields = previous.protoLiteFields;
       this.alwaysInline = previous.alwaysInline;
+      this.forceInline = previous.forceInline;
+      this.neverInline = previous.neverInline;
       this.identifierNameStrings = previous.identifierNameStrings;
       this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses);
       this.switchMaps = previous.switchMaps;
@@ -1683,6 +1695,8 @@
       this.assumedValues = previous.assumedValues;
       assert lense.assertNotModified(previous.alwaysInline);
       this.alwaysInline = previous.alwaysInline;
+      this.forceInline = previous.forceInline;
+      this.neverInline = previous.neverInline;
       this.identifierNameStrings =
           rewriteMixedItemsConservatively(previous.identifierNameStrings, lense);
       // Switchmap classes should never be affected by renaming.
@@ -1724,6 +1738,8 @@
       this.brokenSuperInvokes = previous.brokenSuperInvokes;
       this.protoLiteFields = previous.protoLiteFields;
       this.alwaysInline = previous.alwaysInline;
+      this.forceInline = previous.forceInline;
+      this.neverInline = previous.neverInline;
       this.identifierNameStrings = previous.identifierNameStrings;
       this.prunedTypes = previous.prunedTypes;
       this.switchMaps = switchMaps;
diff --git a/src/main/java/com/android/tools/r8/shaking/InlineRule.java b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
new file mode 100644
index 0000000..565856c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2017, 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 java.util.List;
+
+public class InlineRule extends ProguardConfigurationRule {
+
+  public enum Type {
+    ALWAYS, FORCE, NEVER
+  }
+
+  public static class Builder extends ProguardConfigurationRule.Builder {
+
+    private Builder() {
+    }
+
+    Type type;
+
+    public Builder setType(Type type) {
+      this.type = type;
+      return this;
+    }
+
+    public InlineRule build() {
+      return new InlineRule(classAnnotation, classAccessFlags,
+          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+          inheritanceClassName, inheritanceIsExtends, memberRules, type);
+    }
+  }
+
+  private final Type type;
+
+  private InlineRule(
+      ProguardTypeMatcher classAnnotation,
+      ProguardAccessFlags classAccessFlags,
+      ProguardAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      ProguardClassNameList classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      List<ProguardMemberRule> memberRules,
+      Type type) {
+    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
+        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    this.type = type;
+  }
+
+  public static InlineRule.Builder builder() {
+    return new InlineRule.Builder();
+  }
+
+  public Type getType() {
+    return type;
+  }
+
+  public ProguardCheckDiscardRule asProguardCheckDiscardRule() {
+    assert type == Type.FORCE;
+    ProguardCheckDiscardRule.Builder builder = ProguardCheckDiscardRule.builder();
+    builder.setClassAnnotation(getClassAnnotation());
+    builder.setClassAccessFlags(getClassAccessFlags());
+    builder.setNegatedClassAccessFlags(getNegatedClassAccessFlags());
+    builder.setClassTypeNegated(getClassTypeNegated());
+    builder.setClassType(getClassType());
+    builder.setClassNames(getClassNames());
+    builder.setInheritanceAnnotation(getInheritanceAnnotation());
+    builder.setInheritanceIsExtends(getInheritanceIsExtends());
+    builder.setMemberRules(getMemberRules());
+    return builder.build();
+  }
+
+  @Override
+  String typeString() {
+    switch (type) {
+      case ALWAYS:
+        return "alwaysinline";
+      case FORCE:
+        return "forceinline";
+      case NEVER:
+        return "neverinline";
+    }
+    throw new Unreachable("Unknown inline type " + type);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAlwaysInlineRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAlwaysInlineRule.java
deleted file mode 100644
index 8d4a14b..0000000
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAlwaysInlineRule.java
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (c) 2017, 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 java.util.List;
-
-public class ProguardAlwaysInlineRule extends ProguardConfigurationRule {
-
-  public static class Builder extends ProguardConfigurationRule.Builder {
-
-    private Builder() {
-    }
-
-    public ProguardAlwaysInlineRule build() {
-      return new ProguardAlwaysInlineRule(classAnnotation, classAccessFlags,
-          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
-          inheritanceClassName, inheritanceIsExtends, memberRules);
-    }
-  }
-
-  private ProguardAlwaysInlineRule(
-      ProguardTypeMatcher classAnnotation,
-      ProguardAccessFlags classAccessFlags,
-      ProguardAccessFlags negatedClassAccessFlags,
-      boolean classTypeNegated,
-      ProguardClassType classType,
-      ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
-      ProguardTypeMatcher inheritanceClassName,
-      boolean inheritanceIsExtends,
-      List<ProguardMemberRule> memberRules) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
-  }
-
-  public static ProguardAlwaysInlineRule.Builder builder() {
-    return new ProguardAlwaysInlineRule.Builder();
-  }
-
-  @Override
-  String typeString() {
-    return "alwaysinline";
-  }
-}
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 1de3f17..5b22456 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
 
+import com.android.tools.r8.Version;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -15,6 +16,7 @@
 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;
@@ -48,6 +50,7 @@
 
   private final Reporter reporter;
   private final boolean failOnPartiallyImplementedOptions;
+  private final boolean allowTestOptions;
 
   private static final List<String> IGNORED_SINGLE_ARG_OPTIONS = ImmutableList.of(
       "protomapping",
@@ -97,16 +100,18 @@
 
   public ProguardConfigurationParser(
       DexItemFactory dexItemFactory, Reporter reporter) {
-    this(dexItemFactory, reporter, true);
+    this(dexItemFactory, reporter, true, false);
   }
 
   public ProguardConfigurationParser(
-      DexItemFactory dexItemFactory, Reporter reporter, boolean failOnPartiallyImplementedOptions) {
+      DexItemFactory dexItemFactory, Reporter reporter, boolean failOnPartiallyImplementedOptions,
+      boolean allowTestOptions) {
     this.dexItemFactory = dexItemFactory;
     configurationBuilder = ProguardConfiguration.builder(dexItemFactory, reporter);
 
     this.reporter = reporter;
     this.failOnPartiallyImplementedOptions = failOnPartiallyImplementedOptions;
+    this.allowTestOptions = allowTestOptions;
   }
 
   public ProguardConfiguration.Builder getConfigurationBuilder() {
@@ -343,7 +348,16 @@
       } else if (acceptString("packageobfuscationdictionary")) {
         configurationBuilder.setPackageObfuscationDictionary(parseFileName());
       } else if (acceptString("alwaysinline")) {
-        ProguardAlwaysInlineRule rule = parseAlwaysInlineRule();
+        InlineRule rule = parseInlineRule(Type.ALWAYS);
+        configurationBuilder.addRule(rule);
+      } else if (allowTestOptions && acceptString("forceinline")) {
+        InlineRule rule = parseInlineRule(Type.FORCE);
+        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);
         configurationBuilder.addRule(rule);
       } else if (acceptString("useuniqueclassmembernames")) {
         configurationBuilder.setUseUniqueClassMemberNames(true);
@@ -367,8 +381,15 @@
         configurationBuilder.addRule(parseIfRule(optionStart));
       } else {
         String unknownOption = acceptString();
+        String devMessage = "";
+        if (Version.isDev()
+            && unknownOption != null
+            && (unknownOption.equals("forceinline") || unknownOption.equals("neverinline"))) {
+          devMessage = ", this option needs to be turned on explicitly if used for tests.";
+        }
         reporter.error(new StringDiagnostic(
-            "Unknown option \"-" + unknownOption + "\"", origin, getPosition(optionStart)));
+            "Unknown option \"-" + unknownOption + "\"" + devMessage,
+            origin, getPosition(optionStart)));
       }
       return true;
     }
@@ -563,9 +584,9 @@
       return keepRuleBuilder.build();
     }
 
-    private ProguardAlwaysInlineRule parseAlwaysInlineRule()
+    private InlineRule parseInlineRule(InlineRule.Type type)
         throws ProguardRuleParserException {
-      ProguardAlwaysInlineRule.Builder keepRuleBuilder = ProguardAlwaysInlineRule.builder();
+      InlineRule.Builder keepRuleBuilder = InlineRule.builder().setType(type);
       parseClassSpec(keepRuleBuilder, false);
       return keepRuleBuilder.build();
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index ce1840f..e4c5691 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -56,7 +56,7 @@
     if (!(o instanceof ProguardConfigurationRule)) {
       return false;
     }
-    ProguardKeepRule that = (ProguardKeepRule) o;
+    ProguardConfigurationRule that = (ProguardConfigurationRule) o;
     return super.equals(that);
   }
 
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 c860d83..c95cfe9 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -60,6 +60,8 @@
       Sets.newIdentityHashSet();
   private final Set<DexItem> checkDiscarded = Sets.newIdentityHashSet();
   private final Set<DexItem> alwaysInline = Sets.newIdentityHashSet();
+  private final Set<DexItem> forceInline = Sets.newIdentityHashSet();
+  private final Set<DexItem> neverInline = Sets.newIdentityHashSet();
   private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking =
       new IdentityHashMap<>();
   private final Map<DexItem, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
@@ -240,7 +242,7 @@
       } else if (rule instanceof ProguardAssumeNoSideEffectRule) {
         markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
         markMatchingFields(clazz, memberKeepRules, rule, null);
-      } else if (rule instanceof ProguardAlwaysInlineRule) {
+      } else if (rule instanceof InlineRule) {
         markMatchingMethods(clazz, memberKeepRules, rule, null);
       } else if (rule instanceof ProguardAssumeValuesRule) {
         markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
@@ -310,6 +312,8 @@
         keepPackageName,
         checkDiscarded,
         alwaysInline,
+        forceInline,
+        neverInline,
         noSideEffects,
         assumedValues,
         dependentNoShrinking,
@@ -720,8 +724,20 @@
       assumedValues.put(item, rule);
     } else if (context instanceof ProguardCheckDiscardRule) {
       checkDiscarded.add(item);
-    } else if (context instanceof ProguardAlwaysInlineRule) {
-      alwaysInline.add(item);
+    } else if (context instanceof InlineRule) {
+      switch (((InlineRule) context).getType()) {
+        case ALWAYS:
+          alwaysInline.add(item);
+          break;
+        case FORCE:
+          forceInline.add(item);
+          break;
+        case NEVER:
+          neverInline.add(item);
+          break;
+        default:
+          throw new Unreachable();
+      }
     } else if (context instanceof ProguardIdentifierNameStringRule) {
       if (item instanceof DexEncodedField) {
         identifierNameStrings.add(((DexEncodedField) item).field);
@@ -740,6 +756,8 @@
     public final Set<DexItem> keepPackageName;
     public final Set<DexItem> checkDiscarded;
     public final Set<DexItem> alwaysInline;
+    public final Set<DexItem> forceInline;
+    public final Set<DexItem> neverInline;
     public final Map<DexItem, ProguardMemberRule> noSideEffects;
     public final Map<DexItem, ProguardMemberRule> assumedValues;
     private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
@@ -775,6 +793,8 @@
         Set<DexItem> keepPackageName,
         Set<DexItem> checkDiscarded,
         Set<DexItem> alwaysInline,
+        Set<DexItem> forceInline,
+        Set<DexItem> neverInline,
         Map<DexItem, ProguardMemberRule> noSideEffects,
         Map<DexItem, ProguardMemberRule> assumedValues,
         Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking,
@@ -787,6 +807,8 @@
       this.keepPackageName = Collections.unmodifiableSet(keepPackageName);
       this.checkDiscarded = Collections.unmodifiableSet(checkDiscarded);
       this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
+      this.forceInline = Collections.unmodifiableSet(forceInline);
+      this.neverInline = Collections.unmodifiableSet(neverInline);
       this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
       this.assumedValues = Collections.unmodifiableMap(assumedValues);
       this.dependentNoShrinking = dependentNoShrinking;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index d5d0558..10f62a1 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1546,6 +1546,12 @@
     return builder;
   }
 
+  public static R8Command.Builder allowTestProguardOptions(
+      R8Command.Builder builder) {
+    builder.allowTestProguardOptions();
+    return builder;
+  }
+
   public static AndroidApp getApp(BaseCommand command) {
     return command.getInputApp();
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index f484bc8..1379b95 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -148,7 +148,14 @@
   public void resetAllowPartiallyImplementedOptions() {
     handler = new KeepingDiagnosticHandler();
     reporter = new Reporter(handler);
-    parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, false);
+    parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, false, false);
+  }
+
+  @Before
+  public void resetAllowTestOptions() {
+    handler = new KeepingDiagnosticHandler();
+    reporter = new Reporter(handler);
+    parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, true, true);
   }
 
   @Test
@@ -861,7 +868,7 @@
   @Test
   public void parseKeepdirectories() throws Exception {
     ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(), reporter, false);
+        new ProguardConfigurationParser(new DexItemFactory(), reporter, false, false);
     parser.parse(Paths.get(KEEPDIRECTORIES));
     verifyParserEndsCleanly();
   }
@@ -1357,6 +1364,22 @@
   }
 
   @Test
+  public void parse_testInlineOptions() {
+    List<String> options = ImmutableList.of(
+        "-neverinline", "-forceinline");
+    for (String option : options) {
+      try {
+        reset();
+        parser.parse(createConfigurationForTesting(ImmutableList.of(option + " class A { *; }")));
+        fail("Expect to fail due to testing option being turned off.");
+      } catch (AbortException e) {
+        assertEquals(2, handler.errors.size());
+        checkDiagnostics(handler.errors, 0, null, 1, 1, "Unknown option \"" + option + "\"");
+      }
+    }
+  }
+
+  @Test
   public void parse_if() throws Exception {
     Path proguardConfig = writeTextToTempFile(
         "-if   class **$$ModuleAdapter",
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
index 10c64e3..693f02d 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
@@ -99,6 +99,7 @@
       throws Exception {
     AndroidApp app = readClassesAndAndriodJar(programClasses);
     R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app);
+    ToolHelper.allowTestProguardOptions(builder);
     builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
     return ToolHelper.runR8(builder.build(), configure);
   }
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 14391ee..22f9ef6 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
@@ -33,13 +33,6 @@
   }
 }
 
-class B {
-  // Depending on inlining option, this method is kept to make inlining of A.a() infeasible.
-  void x() {
-    System.out.print("" + A.a() + A.a() + A.a() + A.a() + A.a() + A.a() + A.a() + A.a());
-  }
-}
-
 class D {
 }
 
@@ -52,7 +45,7 @@
 @RunWith(Parameterized.class)
 public class IfRuleWithInlining extends ProguardCompatabilityTestBase {
   private final static List<Class> CLASSES = ImmutableList.of(
-      A.class, B.class, D.class, Main.class);
+      A.class, D.class, Main.class);
 
   private final Shrinker shrinker;
   private final boolean inlineMethod;
@@ -90,11 +83,8 @@
     List<String> config = ImmutableList.of(
         "-keep class **.Main { public static void main(java.lang.String[]); }",
         inlineMethod
-            ? "-alwaysinline class **.A { int a(); }"
-            : "-keep class **.B { *; }",
-        inlineMethod
-            ? "-checkdiscard class **.A { int a(); }"
-            : "",
+            ? "-forceinline class **.A { int a(); }"
+            : "-neverinline class **.A { int a(); }",
         "-if class **.A { static int a(); }",
         "-keep class **.D",
         "-dontobfuscate"
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/A.java b/src/test/java/com/android/tools/r8/shaking/testrules/A.java
new file mode 100644
index 0000000..af05562
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/A.java
@@ -0,0 +1,18 @@
+// 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.testrules;
+
+public class A {
+
+  public static int m(int a, int b) {
+    int r = a + b;
+    System.out.println(a + " + " + b + " = " + r);
+    return r;
+  }
+
+  public static int method() {
+    return m(m(m(1, 2), m(3, 4)), m(m(5, 6), m(7, 8)));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/B.java b/src/test/java/com/android/tools/r8/shaking/testrules/B.java
new file mode 100644
index 0000000..4d1f085
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/B.java
@@ -0,0 +1,17 @@
+// 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.testrules;
+
+public class B {
+
+  public int m(int a, int b) {
+    int r = a + b;
+    System.out.println(a + " + " + b + " = " + r);
+    return r;
+  }
+  public int method() {
+    return m(m(m(1, 2), m(3, 4)), m(m(5, 6), m(7, 8)));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/C.java b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
new file mode 100644
index 0000000..5ee4d53
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
@@ -0,0 +1,14 @@
+// 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.testrules;
+
+public class C {
+
+  private static int i;
+
+  public static int x() {
+    return i;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
new file mode 100644
index 0000000..e4be101
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -0,0 +1,120 @@
+// 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.
+
+// 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.testrules;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+
+public class ForceInlineTest extends TestBase {
+
+  private DexInspector runTest(List<String> proguardConfiguration) throws Exception {
+    R8Command.Builder builder =
+        ToolHelper.prepareR8CommandBuilder(readClasses(Main.class, A.class, B.class, C.class));
+    ToolHelper.allowTestProguardOptions(builder);
+    builder.addProguardConfiguration(proguardConfiguration, Origin.unknown());
+    return new DexInspector(ToolHelper.runR8(builder.build()));
+  }
+
+  @Test
+  public void testDefaultInlining() throws Exception {
+    DexInspector inspector = runTest(ImmutableList.of(
+        "-keep class **.Main { *; }",
+        "-dontobfuscate"
+    ));
+
+    ClassSubject classA = inspector.clazz(A.class);
+    ClassSubject classB = inspector.clazz(B.class);
+    ClassSubject classC = inspector.clazz(C.class);
+    ClassSubject classMain = inspector.clazz(Main.class);
+    assertThat(classA, isPresent());
+    assertThat(classB, isPresent());
+    assertThat(classC, isPresent());
+    assertThat(classMain, isPresent());
+
+    // By default A.m *will not* be inlined (called several times and not small).
+    assertThat(classA.method("int", "m", ImmutableList.of("int", "int")), isPresent());
+    // By default A.method *will* be inlined (called only once).
+    assertThat(classA.method("int", "method", ImmutableList.of()), not(isPresent()));
+    // By default B.m *will not* be inlined (called several times and not small).
+    assertThat(classB.method("int", "m", ImmutableList.of("int", "int")), isPresent());
+    // By default B.method *will* be inlined (called only once).
+    assertThat(classB.method("int", "method", ImmutableList.of()), not(isPresent()));
+  }
+
+  @Test
+  public void testNeverInline() throws Exception {
+    DexInspector inspector = runTest(ImmutableList.of(
+        "-neverinline class **.A { method(); }",
+        "-neverinline class **.B { method(); }",
+        "-keep class **.Main { *; }",
+        "-dontobfuscate"
+    ));
+
+    ClassSubject classA = inspector.clazz(A.class);
+    ClassSubject classB = inspector.clazz(B.class);
+    ClassSubject classC = inspector.clazz(C.class);
+    ClassSubject classMain = inspector.clazz(Main.class);
+    assertThat(classA, isPresent());
+    assertThat(classB, isPresent());
+    assertThat(classC, isPresent());
+    assertThat(classMain, isPresent());
+
+    // Compared to the default method is no longer inlined.
+    assertThat(classA.method("int", "m", ImmutableList.of("int", "int")), isPresent());
+    assertThat(classA.method("int", "method", ImmutableList.of()), isPresent());
+    assertThat(classB.method("int", "m", ImmutableList.of("int", "int")), isPresent());
+    assertThat(classB.method("int", "method", ImmutableList.of()), isPresent());
+  }
+
+  @Test
+  public void testForceInline() throws Exception {
+    DexInspector inspector = runTest(ImmutableList.of(
+        "-forceinline class **.A { int m(int, int); }",
+        "-forceinline class **.B { int m(int, int); }",
+        "-keep class **.Main { *; }",
+        "-dontobfuscate"
+    ));
+
+    ClassSubject classA = inspector.clazz(A.class);
+    ClassSubject classB = inspector.clazz(B.class);
+    ClassSubject classC = inspector.clazz(C.class);
+    ClassSubject classMain = inspector.clazz(Main.class);
+
+    // Compared to the default m is now inlined and method still is, so classes A and B are gone.
+    assertThat(classA, not(isPresent()));
+    assertThat(classB, not(isPresent()));
+    assertThat(classC, isPresent());
+    assertThat(classMain, isPresent());
+  }
+
+  @Test
+  public void testForceInlineFails() throws Exception {
+    try {
+      DexInspector inspector = runTest(ImmutableList.of(
+          "-forceinline class **.A { int x(); }",
+          "-keep class **.Main { *; }",
+          "-dontobfuscate"
+      ));
+      fail("Force inline of non-inlinable method succeeded");
+    } catch (Throwable t) {
+      // Ignore assertion error.
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/Main.java b/src/test/java/com/android/tools/r8/shaking/testrules/Main.java
new file mode 100644
index 0000000..d326216
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/Main.java
@@ -0,0 +1,14 @@
+// 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.testrules;
+
+public class Main {
+
+  public static void main(String[] args) {
+    System.out.println(A.method());
+    System.out.println(new B().method());
+    System.out.println(C.x());
+  }
+}
diff --git a/src/test/sampleApks/split/split.spec b/src/test/sampleApks/split/split.spec
index a06e259..ed40b98 100644
--- a/src/test/sampleApks/split/split.spec
+++ b/src/test/sampleApks/split/split.spec
@@ -1 +1,2 @@
 com.android.tools.r8.sample.split.SplitClass:split
+com.android.tools.r8.sample.split.SplitInheritBase:split
diff --git a/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/BaseClass.java b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/BaseClass.java
new file mode 100644
index 0000000..eef119c
--- /dev/null
+++ b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/BaseClass.java
@@ -0,0 +1,104 @@
+// 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.sample.split;
+
+
+public class BaseClass {
+
+  int initialValue;
+
+  public BaseClass(int initialValue) {
+    this.initialValue = initialValue;
+  }
+
+  public int calculate(int x) {
+    int result = 2;
+    for (int i = 0; i < 42; i++) {
+      result += initialValue + x;
+    }
+    return result;
+  }
+
+   public int largeMethod(int x, int y) {
+    int a = x + y;
+    int b;
+    int c;
+    double d;
+    String s;
+    if (a < 22) {
+      b = a * 42 / y;
+      c = b - 80;
+      d = a + b + c * y - x * a;
+      s = "foobar";
+    } else if (a < 42) {
+      b = x * 42 / y;
+      c = b - 850;
+      d = a + b + c * x - x * a;
+      s = "foo";
+    } else {
+      b = x * 42 / y;
+      c = b - 850;
+      d = a + b + c * x - x * a;
+      s = "bar";
+    }
+    if (s.equals("bar")) {
+      d = 49;
+      s += b;
+    } else {
+      b = 63;
+      s = "barbar" + b;
+    }
+
+    if (a < 22) {
+      b = a * 42 / y;
+      c = b - 80;
+      d = a + b + c * y - x * a;
+      s = "foobar";
+    } else if (a < 42) {
+      b = x * 42 / y;
+      c = b - 850;
+      d = a + b + c * x - x * a;
+      s = "foo";
+    } else {
+      b = x * 42 / y;
+      c = b - 850;
+      d = a + b + c * x - x * a;
+      s = "bar";
+    }
+    if (s.equals("bar")) {
+      d = 49;
+      s += b;
+    } else {
+      b = 63;
+      s = "barbar" + b;
+    }
+
+    if (a < 22) {
+      b = a * 42 / y;
+      c = b - 80;
+      d = a + b + c * y - x * a;
+      s = "foobar";
+    } else if (a < 42) {
+      b = x * 42 / y;
+      c = b - 850;
+      d = a + b + c * x - x * a;
+      s = "foo";
+    } else {
+      b = x * 42 / y;
+      c = b - 850;
+      d = a + b + c * x - x * a;
+      s = "bar";
+    }
+    if (s.equals("bar")) {
+      d = 49;
+      s += b;
+    } else {
+      b = 63;
+      s = "barbar" + b;
+    }
+
+    return a + b - c * x;
+  }
+}
diff --git a/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/R8Activity.java b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/R8Activity.java
index a80cf55..307cac9 100644
--- a/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/R8Activity.java
+++ b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/R8Activity.java
@@ -8,33 +8,201 @@
 import android.os.Bundle;
 import com.android.tools.r8.sample.split.R;
 import com.android.tools.r8.sample.split.SplitClass;
+import java.util.ArrayList;
+import java.util.List;
+
 
 public class R8Activity extends Activity {
+  // Enables easy splitting of iterations to better see effect of jit in later versions of art
+  public static final int ITERATIONS = 100000;
+  public static final int SPLITS = 1;
+
   private int res = 0;
 
   public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setTheme(android.R.style.Theme_Light);
     setContentView(R.layout.main);
-    // Currently this is split up into 100 iterations to be able to better see
-    // the impact of the jit on later versions of art.
     long total = 0;
-    for (int i = 0; i < 100; i++) {
+    for (int i = 0; i < SPLITS; i++) {
+      total += benchmarkCallBaseline();
+    }
+    System.out.println("CallBaseline Total: " + total);
+
+    total = 0;
+    for (int i = 0; i < SPLITS; i++) {
       total += benchmarkCall();
     }
-    System.out.println("Total: " + total);
+    System.out.println("Call Total: " + total);
+
+    total = 0;
+    for (int i = 0; i < SPLITS; i++) {
+      total += benchmarkCallLocal();
+    }
+    System.out.println("CallLocal Total: " + total);
+
+    total = 0;
+    for (int i = 0; i < SPLITS; i++) {
+      total += benchmarkSplitCallback();
+    }
+    System.out.println("Callback Total: " + total);
+
+    total = 0;
+    for (int i = 0; i < SPLITS; i++) {
+      total += benchmarkConstructor();
+    }
+    System.out.println("Constructor Total: " + total);
+
+    total = 0;
+    for (int i = 0; i < SPLITS; i++) {
+      total += benchmarkConstructorLocal();
+    }
+    System.out.println("ConstructorLocal Total: " + total);
+
+    total = 0;
+    for (int i = 0; i < SPLITS; i++) {
+      total += benchmarkInheritanceConstructor();
+    }
+    System.out.println("InheritanceConstructor Total: " + total);
+
+    total = 0;
+    for (int i = 0; i < SPLITS; i++) {
+      total += benchmarkLargeMethodCall();
+    }
+    System.out.println("LargeMethodCall Total: " + total);
+
+    total = 0;
+    for (int i = 0; i < SPLITS; i++) {
+      total += benchmarkSplitCallbackLong();
+    }
+    System.out.println("CallbackLarge Total: " + total);
+
+    total = 0;
+    for (int i = 0; i < SPLITS; i++) {
+      total += benchmarkLargeMethodCallLocally();
+    }
+    System.out.println("LargeMethodCallLocal Total: " + total);
+
   }
 
-  public long benchmarkCall() {
+  private long benchmarkCall() {
     SplitClass split = new SplitClass(3);
     long start = System.nanoTime();
-    for (int i = 0; i < 1000; i++) {
+    for (int i = 0; i < ITERATIONS / SPLITS; i++) {
       // Ensure no dead code elimination.
       res = split.calculate(i);
     }
     long finish = System.nanoTime();
-    long timeElapsed = finish - start;
-    System.out.println("Took: " + timeElapsed);
+    long timeElapsed = (finish - start) / 1000;
     return timeElapsed;
   }
+
+  private long benchmarkCallLocal() {
+    long start = System.nanoTime();
+    for (int i = 0; i < ITERATIONS / SPLITS; i++) {
+      // Ensure no dead code elimination.
+      res = calculate(i);
+    }
+    long finish = System.nanoTime();
+    long timeElapsed = (finish - start) / 1000;
+    return timeElapsed;
+  }
+
+  private long benchmarkCallBaseline() {
+    SplitClass split = new SplitClass(3);
+    long start = System.nanoTime();
+    for (int i = 0; i < ITERATIONS / SPLITS; i++) {
+      int result = 2;
+      for (int j = 0; j < 42; j++) {
+        result += result + i;
+      }
+      res = result;
+    }
+    long finish = System.nanoTime();
+    long timeElapsed = (finish - start) / 1000;
+    return timeElapsed;
+  }
+
+  private long benchmarkInheritanceConstructor() {
+    List<SplitInheritBase> instances = new ArrayList<SplitInheritBase>();
+    long start = System.nanoTime();
+    for (int i = 0; i < ITERATIONS / SPLITS; i++) {
+      instances.add(new SplitInheritBase(i));
+    }
+    long finish = System.nanoTime();
+    long timeElapsed = (finish - start) / 1000;
+    return timeElapsed;
+  }
+
+  private long benchmarkConstructor() {
+    List<SplitClass> instances = new ArrayList<SplitClass>();
+    long start = System.nanoTime();
+    for (int i = 0; i < ITERATIONS / SPLITS; i++) {
+      instances.add(new SplitClass(i));
+    }
+    long finish = System.nanoTime();
+    long timeElapsed = (finish - start) / 1000;
+    return timeElapsed;
+  }
+
+  private long benchmarkConstructorLocal() {
+    List<BaseClass> instances = new ArrayList<BaseClass>();
+    long start = System.nanoTime();
+    for (int i = 0; i < ITERATIONS / SPLITS; i++) {
+      instances.add(new BaseClass(i));
+    }
+    long finish = System.nanoTime();
+    long timeElapsed = (finish - start) / 1000;
+    return timeElapsed;
+  }
+
+  private long benchmarkLargeMethodCall() {
+    SplitClass split = new SplitClass(3);
+    long start = System.nanoTime();
+    for (int i = 0; i < ITERATIONS / SPLITS; i++) {
+      // Ensure no dead code elimination.
+      res = split.largeMethod(i, i + 1);
+    }
+    long finish = System.nanoTime();
+    long timeElapsed = (finish - start) / 1000;
+    return timeElapsed;
+  }
+
+  private long benchmarkSplitCallback() {
+    SplitClass split = new SplitClass(3);
+    long start = System.nanoTime();
+    res = split.callBase();
+    long finish = System.nanoTime();
+    long timeElapsed = (finish - start) / 1000;
+    return timeElapsed;
+  }
+
+  private long benchmarkSplitCallbackLong() {
+    SplitClass split = new SplitClass(3);
+    long start = System.nanoTime();
+    res = split.callBaseLarge();
+    long finish = System.nanoTime();
+    long timeElapsed = (finish - start) / 1000;
+    return timeElapsed;
+  }
+
+  private long benchmarkLargeMethodCallLocally() {
+    BaseClass base = new BaseClass(3);
+    long start = System.nanoTime();
+    for (int i = 0; i < ITERATIONS / SPLITS; i++) {
+      // Ensure no dead code elimination.
+      res = base.largeMethod(i, i + 1);
+    }
+    long finish = System.nanoTime();
+    long timeElapsed = (finish - start) / 1000;
+    return timeElapsed;
+  }
+
+  public int calculate(int x) {
+    int result = 2;
+    for (int i = 0; i < 42; i++) {
+      result += res + x;
+    }
+    return result;
+  }
 }
diff --git a/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/SplitClass.java b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/SplitClass.java
index 9b6990d..b493326 100644
--- a/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/SplitClass.java
+++ b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/SplitClass.java
@@ -4,13 +4,17 @@
 
 package com.android.tools.r8.sample.split;
 
+import com.android.tools.r8.sample.split.R8Activity;
+
 public class SplitClass {
-  final int initialValue;
+  int initialValue;
 
   public SplitClass(int initialValue) {
     this.initialValue = initialValue;
   }
 
+
+
   public int calculate(int x) {
     int result = 2;
     for (int i = 0; i < 42; i++) {
@@ -18,4 +22,104 @@
     }
     return result;
   }
+
+  public int largeMethod(int x, int y) {
+    int a = x + y;
+    int b;
+    int c;
+    double d;
+    String s;
+    if (a < 22) {
+      b = a * 42 / y;
+      c = b - 80;
+      d = a + b + c * y - x * a;
+      s = "foobar";
+    } else if (a < 42) {
+      b = x * 42 / y;
+      c = b - 850;
+      d = a + b + c * x - x * a;
+      s = "foo";
+    } else {
+      b = x * 42 / y;
+      c = b - 850;
+      d = a + b + c * x - x * a;
+      s = "bar";
+    }
+    if (s.equals("bar")) {
+      d = 49;
+      s += b;
+    } else {
+      b = 63;
+      s = "barbar" + b;
+    }
+
+    if (a < 22) {
+      b = a * 42 / y;
+      c = b - 80;
+      d = a + b + c * y - x * a;
+      s = "foobar";
+    } else if (a < 42) {
+      b = x * 42 / y;
+      c = b - 850;
+      d = a + b + c * x - x * a;
+      s = "foo";
+    } else {
+      b = x * 42 / y;
+      c = b - 850;
+      d = a + b + c * x - x * a;
+      s = "bar";
+    }
+    if (s.equals("bar")) {
+      d = 49;
+      s += b;
+    } else {
+      b = 63;
+      s = "barbar" + b;
+    }
+
+    if (a < 22) {
+      b = a * 42 / y;
+      c = b - 80;
+      d = a + b + c * y - x * a;
+      s = "foobar";
+    } else if (a < 42) {
+      b = x * 42 / y;
+      c = b - 850;
+      d = a + b + c * x - x * a;
+      s = "foo";
+    } else {
+      b = x * 42 / y;
+      c = b - 850;
+      d = a + b + c * x - x * a;
+      s = "bar";
+    }
+    if (s.equals("bar")) {
+      d = 49;
+      s += b;
+    } else {
+      b = 63;
+      s = "barbar" + b;
+    }
+
+    return a + b - c * x;
+  }
+
+  public int callBase() {
+    BaseClass base = new BaseClass(initialValue);
+    for (int i = 0; i < R8Activity.ITERATIONS / R8Activity.SPLITS; i++) {
+      // Ensure no dead code elimination.
+      initialValue = base.calculate(i);
+    }
+    return initialValue;
+  }
+
+  public int callBaseLarge() {
+    BaseClass base = new BaseClass(initialValue);
+    for (int i = 0; i < R8Activity.ITERATIONS / R8Activity.SPLITS; i++) {
+      // Ensure no dead code elimination.
+      initialValue = base.largeMethod(i, i + 1);
+    }
+    return initialValue;
+  }
+
 }
diff --git a/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/SplitInheritBase.java b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/SplitInheritBase.java
new file mode 100644
index 0000000..6c5ed1e
--- /dev/null
+++ b/src/test/sampleApks/split/src/com/android/tools/r8/sample/split/SplitInheritBase.java
@@ -0,0 +1,17 @@
+// 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.sample.split;
+
+public class SplitInheritBase extends BaseClass {
+
+  public SplitInheritBase(int initialValue) {
+    super(initialValue);
+    initialValue = calculate(initialValue);
+  }
+
+  public int calculate(int x) {
+    return super.calculate(x) * 2;
+  }
+}
diff --git a/tools/build_sample_apk.py b/tools/build_sample_apk.py
index 90e34ed..b74356b 100755
--- a/tools/build_sample_apk.py
+++ b/tools/build_sample_apk.py
@@ -26,7 +26,7 @@
 DEFAULT_KEYSTORE = os.path.join(os.getenv('HOME'), '.android', 'debug.keystore')
 PACKAGE_PREFIX = 'com.android.tools.r8.sample'
 STANDARD_ACTIVITY = "R8Activity"
-BENCHMARK_ITERATIONS = 100
+BENCHMARK_ITERATIONS = 30
 
 SAMPLE_APKS = [
     'simple',
@@ -161,11 +161,15 @@
   utils.PrintCmd(command)
   subprocess.check_call(command)
 
-def run_adb(args):
+def run_adb(args, ignore_exit=False):
   command = ['adb']
   command.extend(args)
   utils.PrintCmd(command)
-  subprocess.check_call(command)
+  # On M adb install-multiple exits 1 but succeed in installing.
+  if ignore_exit:
+    subprocess.call(command)
+  else:
+    subprocess.check_call(command)
 
 def adb_install(apks):
   args = [
@@ -173,7 +177,7 @@
       '-r',
       '-d']
   args.extend(apks)
-  run_adb(args)
+  run_adb(args, ignore_exit=True)
 
 def create_temp_apk(app, prefix):
   temp_apk_path = os.path.join(get_bin_path(app), '%s.ap_' % app)
@@ -192,7 +196,7 @@
   run_adb(args)
 
 def start_logcat():
-  return subprocess.Popen(['adb', 'logcat'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  return subprocess.Popen(['adb', 'logcat'], bufsize=1024*1024, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
 def start(app):
   args = ['shell', 'am', 'start', '-n', get_qualified_activity(app)]
@@ -211,34 +215,35 @@
   return lines
 
 def store_or_print_benchmarks(lines, output):
-  single_runs = []
-  total_time = None
-  # We assume that the individual runs are prefixed with 'Took: ' and the total time is
-  # prefixed with 'Total: '. The logcat lines looks like:
-  # 06-28 12:22:00.991 13698 13698 I System.out: Took: 61614
+  results = {}
+  overall_total = 0
+  # We assume that the total times are
+  # prefixed with 'NAME Total: '. The logcat lines looks like:
+  # 06-28 12:22:00.991 13698 13698 I System.out: Call Total: 61614
   for l in lines:
-    if 'Took: ' in l:
-      timing = l.split('Took: ')[1]
-      single_runs.append(timing)
     if 'Total: ' in l:
-      timing = l.split('Total: ')[1]
-      total_time = timing
-  assert len(single_runs) > 0
-  assert total_time
-  if not output:
-    print 'Individual timings: \n%s' % ''.join(single_runs)
-    print 'Total time: \n%s' % total_time
-    return
+      split = l.split('Total: ')
+      time = split[1]
+      name = split[0].split()[-1]
+      overall_total += int(time)
+      print '%s: %s' % (name, time)
+      results[name] = time
 
+  print 'Total: %s' % overall_total
+  if not output:
+    return overall_total
+  results['total'] = str(overall_total)
   output_dir = os.path.join(output, str(uuid.uuid4()))
   os.makedirs(output_dir)
-  single_run_file = os.path.join(output_dir, 'single_runs')
-  with open(single_run_file, 'w') as f:
-    f.writelines(single_runs)
-  total_file = os.path.join(output_dir, 'total')
-  with open(total_file, 'w') as f:
-    f.write(total_time)
-  print 'Result stored in %s and %s' % (single_run_file, total_file)
+  written_files = []
+  for name, time in results.iteritems():
+    total_file = os.path.join(output_dir, name)
+    written_files.append(total_file)
+    with open(total_file, 'w') as f:
+      f.write(time)
+
+  print 'Result stored in: \n%s' % ('\n'.join(written_files))
+  return overall_total
 
 def benchmark(app, output_dir):
   # Ensure app is not running
@@ -248,9 +253,14 @@
   start(app)
   # We could do better here by continiously parsing the logcat for a marker, but
   # this works nicely with the current setup.
-  time.sleep(3)
+  time.sleep(8)
   kill(app)
-  store_or_print_benchmarks(stop_logcat(logcat), output_dir)
+  return float(store_or_print_benchmarks(stop_logcat(logcat), output_dir))
+
+def ensure_no_logcat():
+  output = subprocess.check_output(['ps', 'aux'])
+  if 'adb logcat' in output:
+    raise Exception('You have adb logcat running, please close it and rerun')
 
 def Main():
   (options, args) = parse_options()
@@ -283,11 +293,14 @@
     apks.append(split_apk_path)
 
   print('Generated apks available at: %s' % ' '.join(apks))
-  if options.install:
+  if options.install or options.benchmark:
     adb_install(apks)
+  grand_total = 0
   if options.benchmark:
+    ensure_no_logcat()
     for _ in range(BENCHMARK_ITERATIONS):
-      benchmark(options.app, options.benchmark_output_dir)
+      grand_total += benchmark(options.app, options.benchmark_output_dir)
+  print 'Combined average: %s' % (grand_total/BENCHMARK_ITERATIONS)
 
 if __name__ == '__main__':
   sys.exit(Main())
diff --git a/tools/track_memory.sh b/tools/track_memory.sh
index c0b4716..4629d4b 100755
--- a/tools/track_memory.sh
+++ b/tools/track_memory.sh
@@ -16,7 +16,7 @@
 
 function Exit {
   kill $lid
-  exit 0
+  exit $code
 }
 
 function Kill {
@@ -41,3 +41,4 @@
 trap "Exit" EXIT
 trap "Kill" SIGINT
 wait $pid
+code=$?