Merge "Warn users of unidentified reflection while minification is allowed."
diff --git a/build.gradle b/build.gradle
index abefa15..b733b88 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,24 +7,6 @@
 import tasks.GetJarsFromConfiguration
 import utils.Utils
 
-apply plugin: 'java'
-apply plugin: 'idea'
-apply plugin: 'com.google.protobuf'
-apply plugin: 'com.cookpad.android.licensetools'
-apply plugin: 'net.ltgt.errorprone-base'
-apply plugin: "net.ltgt.apt"
-
-// Ensure importing into IntelliJ IDEA use the same output directories as Gradle. In tests we
-// use the output path for tests (ultimately through ToolHelper.getClassPathForTests()) and
-// therefore these paths need to be the same. See https://youtrack.jetbrains.com/issue/IDEA-175172
-// for context.
-idea {
-    module {
-        outputDir file('build/classes/main')
-        testOutputDir file('build/classes/test')
-    }
-}
-
 ext {
     androidSupportVersion = '25.4.0'
     apacheCommonsVersion = '1.12'
@@ -68,11 +50,6 @@
 
 apply from: 'copyAdditionalJctfCommonFiles.gradle'
 
-
-if (project.hasProperty('with_code_coverage')) {
-    apply plugin: 'jacoco'
-}
-
 repositories {
     maven { url 'https://maven.google.com' }
     maven { url 'https://kotlin.bintray.com/kotlinx' }
@@ -93,9 +70,39 @@
         classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.3'
         classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
         classpath "net.ltgt.gradle:gradle-apt-plugin:0.12"
+        classpath "com.gradle:build-scan-plugin:1.14"
     }
 }
 
+apply plugin: "com.gradle.build-scan"
+
+buildScan {
+    licenseAgreementUrl = 'https://gradle.com/terms-of-service'
+    licenseAgree = 'yes'
+}
+
+apply plugin: 'java'
+apply plugin: 'idea'
+apply plugin: 'com.google.protobuf'
+apply plugin: 'com.cookpad.android.licensetools'
+apply plugin: 'net.ltgt.errorprone-base'
+apply plugin: "net.ltgt.apt"
+
+// Ensure importing into IntelliJ IDEA use the same output directories as Gradle. In tests we
+// use the output path for tests (ultimately through ToolHelper.getClassPathForTests()) and
+// therefore these paths need to be the same. See https://youtrack.jetbrains.com/issue/IDEA-175172
+// for context.
+idea {
+    module {
+        outputDir file('build/classes/main')
+        testOutputDir file('build/classes/test')
+    }
+}
+
+if (project.hasProperty('with_code_coverage')) {
+    apply plugin: 'jacoco'
+}
+
 // Custom source set for example tests and generated tests.
 sourceSets {
     test {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3d2cc25..805bdf7 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
@@ -249,12 +250,13 @@
       DexApplication application =
           new ApplicationReader(inputApp, options, timing).read(executorService).toDirect();
 
-      AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
+      AppView<AppInfoWithSubtyping> appView =
+          new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense());
       RootSet rootSet;
       String proguardSeedsData = null;
       timing.begin("Strip unused code");
       try {
-        Set<DexType> missingClasses = appInfo.getMissingClasses();
+        Set<DexType> missingClasses = appView.getAppInfo().getMissingClasses();
         missingClasses = filterMissingClasses(
             missingClasses, options.proguardConfiguration.getDontWarnPatterns());
         if (!missingClasses.isEmpty()) {
@@ -271,34 +273,50 @@
 
         // Compute kotlin info before setting the roots and before
         // kotlin metadata annotation is removed.
-        computeKotlinInfoForProgramClasses(application, appInfo);
+        computeKotlinInfoForProgramClasses(application, appView.getAppInfo());
 
         final ProguardConfiguration.Builder compatibility =
             ProguardConfiguration.builder(application.dexItemFactory, options.reporter);
 
-        rootSet = new RootSetBuilder(
-                    appInfo, application, options.proguardConfiguration.getRules(), options)
+        rootSet =
+            new RootSetBuilder(
+                    appView.getAppInfo(),
+                    application,
+                    options.proguardConfiguration.getRules(),
+                    options)
                 .run(executorService);
         ProtoLiteExtension protoLiteExtension =
-            options.forceProguardCompatibility ? null : new ProtoLiteExtension(appInfo);
-        Enqueuer enqueuer = new Enqueuer(appInfo, options, options.forceProguardCompatibility,
-            compatibility, protoLiteExtension);
-        appInfo = enqueuer.traceApplication(rootSet, executorService, timing);
+            options.forceProguardCompatibility
+                ? null
+                : new ProtoLiteExtension(appView.getAppInfo());
+        Enqueuer enqueuer =
+            new Enqueuer(
+                appView.getAppInfo(),
+                options,
+                options.forceProguardCompatibility,
+                compatibility,
+                protoLiteExtension);
+        appView.setAppInfo(enqueuer.traceApplication(rootSet, executorService, timing));
         if (options.proguardConfiguration.isPrintSeeds()) {
           ByteArrayOutputStream bytes = new ByteArrayOutputStream();
           PrintStream out = new PrintStream(bytes);
-          RootSetBuilder.writeSeeds(appInfo.withLiveness(), out, type -> true);
+          RootSetBuilder.writeSeeds(appView.getAppInfo().withLiveness(), out, type -> true);
           out.flush();
           proguardSeedsData = bytes.toString();
         }
         if (options.enableTreeShaking) {
-          TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+          TreePruner pruner =
+              new TreePruner(application, appView.getAppInfo().withLiveness(), options);
           application = pruner.run();
           // Recompute the subtyping information.
-          appInfo = appInfo.withLiveness().prunedCopyFrom(application, pruner.getRemovedClasses());
-          new AbstractMethodRemover(appInfo).run();
+          appView.setAppInfo(
+              appView
+                  .getAppInfo()
+                  .withLiveness()
+                  .prunedCopyFrom(application, pruner.getRemovedClasses()));
+          new AbstractMethodRemover(appView.getAppInfo()).run();
         }
-        new AnnotationRemover(appInfo.withLiveness(), compatibility, options).run();
+        new AnnotationRemover(appView.getAppInfo().withLiveness(), compatibility, options).run();
 
         // TODO(69445518): This is still work in progress, and this file writing is currently used
         // for testing.
@@ -320,44 +338,52 @@
         timing.end();
       }
 
-      GraphLense graphLense = GraphLense.getIdentityLense();
-
       if (options.proguardConfiguration.isAccessModificationAllowed()) {
-        graphLense = ClassAndMemberPublicizer.run(
-            executorService, timing, application, appInfo, rootSet, graphLense);
+        appView.setGraphLense(
+            ClassAndMemberPublicizer.run(executorService, timing, application, appView, rootSet));
         // We can now remove visibility bridges. Note that we do not need to update the
         // invoke-targets here, as the existing invokes will simply dispatch to the now
         // visible super-method. MemberRebinding, if run, will then dispatch it correctly.
-        application = new VisibilityBridgeRemover(appInfo, application).run();
+        application = new VisibilityBridgeRemover(appView.getAppInfo(), application).run();
       }
 
-      if (appInfo.hasLiveness()) {
+      if (appView.getAppInfo().hasLiveness()) {
+        AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+
         if (options.proguardConfiguration.hasApplyMappingFile()) {
           SeedMapper seedMapper =
               SeedMapper.seedMapperFromFile(options.proguardConfiguration.getApplyMappingFile());
           timing.begin("apply-mapping");
-          graphLense =
-              new ProguardMapApplier(appInfo.withLiveness(), graphLense, seedMapper).run(timing);
-          application = application.asDirect().rewrittenWithLense(graphLense);
-          appInfo = appInfo.withLiveness().rewrittenWithLense(application.asDirect(), graphLense);
+          appView.setGraphLense(
+              new ProguardMapApplier(appView.withLiveness(), seedMapper).run(timing));
+          application = application.asDirect().rewrittenWithLense(appView.getGraphLense());
+          appView.setAppInfo(
+              appView
+                  .getAppInfo()
+                  .withLiveness()
+                  .rewrittenWithLense(application.asDirect(), appView.getGraphLense()));
           timing.end();
         }
-        graphLense = new MemberRebindingAnalysis(appInfo.withLiveness(), graphLense).run();
+        appView.setGraphLense(new MemberRebindingAnalysis(appViewWithLiveness).run());
         // Class merging requires inlining.
         if (options.enableClassMerging && options.enableInlining) {
           timing.begin("ClassMerger");
           VerticalClassMerger classMerger =
-              new VerticalClassMerger(application, appInfo.withLiveness(), graphLense, timing);
-          graphLense = classMerger.run();
+              new VerticalClassMerger(application, appViewWithLiveness, timing);
+          appView.setGraphLense(classMerger.run());
           timing.end();
-          application = application.asDirect().rewrittenWithLense(graphLense);
-          appInfo = appInfo.withLiveness()
-              .prunedCopyFrom(application, classMerger.getRemovedClasses())
-              .rewrittenWithLense(application.asDirect(), graphLense);
+          application = application.asDirect().rewrittenWithLense(appView.getGraphLense());
+          appViewWithLiveness.setAppInfo(
+              appViewWithLiveness
+                  .getAppInfo()
+                  .prunedCopyFrom(application, classMerger.getRemovedClasses())
+                  .rewrittenWithLense(application.asDirect(), appView.getGraphLense()));
         }
         // Collect switch maps and ordinals maps.
-        appInfo = new SwitchMapCollector(appInfo.withLiveness(), options).run();
-        appInfo = new EnumOrdinalMapCollector(appInfo.withLiveness(), options).run();
+        appViewWithLiveness.setAppInfo(
+            new SwitchMapCollector(appViewWithLiveness.getAppInfo(), options).run());
+        appViewWithLiveness.setAppInfo(
+            new EnumOrdinalMapCollector(appViewWithLiveness.getAppInfo(), options).run());
 
         // TODO(b/79143143): re-enable once fixed.
         // graphLense = new BridgeMethodAnalysis(graphLense, appInfo.withLiveness()).run();
@@ -366,7 +392,9 @@
       timing.begin("Create IR");
       CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
       try {
-        IRConverter converter = new IRConverter(appInfo, options, timing, printer, graphLense);
+        IRConverter converter =
+            new IRConverter(
+                appView.getAppInfo(), options, timing, printer, appView.getGraphLense());
         application = converter.optimize(application, executorService);
       } finally {
         timing.end();
@@ -386,15 +414,15 @@
 
       // Overwrite SourceFile if specified. This step should be done after IR conversion.
       timing.begin("Rename SourceFile");
-      new SourceFileRewriter(appInfo, options).run();
+      new SourceFileRewriter(appView.getAppInfo(), options).run();
       timing.end();
 
       if (!options.mainDexKeepRules.isEmpty()) {
-        appInfo = new AppInfoWithSubtyping(application);
-        Enqueuer enqueuer = new Enqueuer(appInfo, options, true);
+        appView.setAppInfo(new AppInfoWithSubtyping(application));
+        Enqueuer enqueuer = new Enqueuer(appView.getAppInfo(), options, true);
         // Lets find classes which may have code executed before secondary dex files installation.
         RootSet mainDexRootSet =
-            new RootSetBuilder(appInfo, application, options.mainDexKeepRules, options)
+            new RootSetBuilder(appView.getAppInfo(), application, options.mainDexKeepRules, options)
                 .run(executorService);
         AppInfoWithLiveness mainDexAppInfo =
             enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
@@ -409,18 +437,24 @@
             .build();
       }
 
-      appInfo = new AppInfoWithSubtyping(application);
+      appView.setAppInfo(new AppInfoWithSubtyping(application));
 
       if (options.enableTreeShaking || options.enableMinification) {
         timing.begin("Post optimization code stripping");
         try {
-          Enqueuer enqueuer = new Enqueuer(appInfo, options, options.forceProguardCompatibility);
-          appInfo = enqueuer.traceApplication(rootSet, executorService, timing);
+          Enqueuer enqueuer =
+              new Enqueuer(appView.getAppInfo(), options, options.forceProguardCompatibility);
+          appView.setAppInfo(enqueuer.traceApplication(rootSet, executorService, timing));
+
+          AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
           if (options.enableTreeShaking) {
-            TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+            TreePruner pruner =
+                new TreePruner(application, appViewWithLiveness.getAppInfo(), options);
             application = pruner.run();
-            appInfo = appInfo.withLiveness()
-                .prunedCopyFrom(application, pruner.getRemovedClasses());
+            appViewWithLiveness.setAppInfo(
+                appViewWithLiveness
+                    .getAppInfo()
+                    .prunedCopyFrom(application, pruner.getRemovedClasses()));
             // Print reasons on the application after pruning, so that we reflect the actual result.
             ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(rootSet.reasonAsked);
             reasonPrinter.run(application);
@@ -440,7 +474,7 @@
       // will happen. Just avoid the overhead.
       NamingLens namingLens =
           options.enableMinification
-              ? new Minifier(appInfo.withLiveness(), rootSet, options).run(timing)
+              ? new Minifier(appView.getAppInfo().withLiveness(), rootSet, options).run(timing)
               : NamingLens.getIdentityLens();
       timing.end();
 
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/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 6549848..63c1173 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.3.4-dev";
+  public static final String LABEL = "1.3.5-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
new file mode 100644
index 0000000..64a4ce4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -0,0 +1,83 @@
+// 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.graph;
+
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+
+public class AppView<T extends AppInfo> {
+
+  private T appInfo;
+  private final DexItemFactory dexItemFactory;
+  private GraphLense graphLense;
+
+  public AppView(T appInfo, GraphLense graphLense) {
+    this.appInfo = appInfo;
+    this.dexItemFactory = appInfo != null ? appInfo.dexItemFactory : null;
+    this.graphLense = graphLense;
+  }
+
+  public T getAppInfo() {
+    return appInfo;
+  }
+
+  public void setAppInfo(T appInfo) {
+    this.appInfo = appInfo;
+  }
+
+  public DexItemFactory getDexItemFactory() {
+    return dexItemFactory;
+  }
+
+  public GraphLense getGraphLense() {
+    return graphLense;
+  }
+
+  public void setGraphLense(GraphLense graphLense) {
+    this.graphLense = graphLense;
+  }
+
+  public AppView<AppInfoWithLiveness> withLiveness() {
+    return new AppViewWithLiveness();
+  }
+
+  private class AppViewWithLiveness extends AppView<AppInfoWithLiveness> {
+
+    private AppViewWithLiveness() {
+      super(null, null);
+    }
+
+    @Override
+    public AppInfoWithLiveness getAppInfo() {
+      return AppView.this.getAppInfo().withLiveness();
+    }
+
+    @Override
+    public void setAppInfo(AppInfoWithLiveness appInfoWithLiveness) {
+      @SuppressWarnings("unchecked")
+      T appInfo = (T) appInfoWithLiveness;
+      AppView.this.setAppInfo(appInfo);
+    }
+
+    @Override
+    public DexItemFactory getDexItemFactory() {
+      return AppView.this.dexItemFactory;
+    }
+
+    @Override
+    public GraphLense getGraphLense() {
+      return AppView.this.getGraphLense();
+    }
+
+    @Override
+    public void setGraphLense(GraphLense graphLense) {
+      AppView.this.setGraphLense(graphLense);
+    }
+
+    @Override
+    public AppView<AppInfoWithLiveness> withLiveness() {
+      return this;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 69126be..3586d6d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -758,6 +758,10 @@
       publicized = true;
     }
 
+    private void unsetPublicized() {
+      publicized = false;
+    }
+
     private void markUseIdentifierNameString() {
       useIdentifierNameString = true;
     }
@@ -835,6 +839,10 @@
     ensureMutableOI().markPublicized();
   }
 
+  synchronized public void unsetPublicized() {
+    ensureMutableOI().unsetPublicized();
+  }
+
   synchronized public void markUseIdentifierNameString() {
     ensureMutableOI().markUseIdentifierNameString();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index feef8f7..91b1dc7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -729,8 +729,10 @@
   }
 
   public DexCallSite createCallSite(
-      DexString methodName, DexProto methodProto,
-      DexMethodHandle bootstrapMethod, List<DexValue> bootstrapArgs) {
+      DexString methodName,
+      DexProto methodProto,
+      DexMethodHandle bootstrapMethod,
+      List<DexValue> bootstrapArgs) {
     // Call sites are never equal and therefore we do not canonicalize.
     assert !sorted;
     DexCallSite callSite = new DexCallSite(methodName, methodProto, bootstrapMethod, bootstrapArgs);
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java
index 17f6efa..5c05605 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProto.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -8,6 +8,8 @@
 
 public class DexProto extends IndexedDexItem implements PresortedComparable<DexProto> {
 
+  public static final DexProto SENTINEL = new DexProto(null, null, null);
+
   public final DexString shorty;
   public final DexType returnType;
   public final DexTypeList parameters;
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 7d17f11..8de4a6e 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -110,6 +110,16 @@
 
   public abstract DexField lookupField(DexField field);
 
+  // The method lookupMethod() maps a pair INVOKE=(method signature, invoke type) to a new pair
+  // INVOKE'=(method signature', invoke type'). This mapping can be context sensitive, meaning that
+  // the result INVOKE' depends on where the invocation INVOKE is in the program. This is, for
+  // example, used by the vertical class merger to translate invoke-super instructions that hit
+  // a method in the direct super class to invoke-direct instructions after class merging.
+  //
+  // This method can be used to determine if a graph lense is context sensitive. If a graph lense
+  // is context insensitive, it is safe to invoke lookupMethod() without a context (or to pass null
+  // as context). Trying to invoke a context sensitive graph lense without a context will lead to
+  // an assertion error.
   public abstract boolean isContextFreeForMethods();
 
   public boolean isContextFreeForMethod(DexMethod method) {
@@ -230,7 +240,7 @@
       // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
       // that only subclasses which are known to need it actually do it?
       return new GraphLenseLookupResult(
-          newMethod, mapInvocationType(newMethod, method, context, type));
+          newMethod, mapInvocationType(newMethod, method, context, previous.getType()));
     }
 
     /**
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 640172f..2c37c6a 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -10,9 +10,12 @@
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.JarSourceCode;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.jar.InliningConstraintVisitor;
 import com.android.tools.r8.jar.JarRegisterEffectsVisitor;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import java.io.PrintWriter;
@@ -27,6 +30,7 @@
 import org.objectweb.asm.tree.LabelNode;
 import org.objectweb.asm.tree.LineNumberNode;
 import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TryCatchBlockNode;
 import org.objectweb.asm.util.Textifier;
 import org.objectweb.asm.util.TraceMethodVisitor;
 
@@ -169,6 +173,31 @@
             DescriptorUtils.getDescriptorFromClassBinaryName(tryCatchBlockNode.type))));
   }
 
+  public Constraint computeInliningConstraint(
+      DexEncodedMethod encodedMethod,
+      AppInfoWithLiveness appInfo,
+      GraphLense graphLense,
+      DexType invocationContext) {
+    InliningConstraintVisitor visitor =
+        new InliningConstraintVisitor(
+            application, appInfo, graphLense, encodedMethod, invocationContext);
+    AbstractInsnNode insn = node.instructions.getFirst();
+    while (insn != null) {
+      insn.accept(visitor);
+      if (visitor.isFinished()) {
+        return visitor.getConstraint();
+      }
+      insn = insn.getNext();
+    }
+    for (TryCatchBlockNode block : node.tryCatchBlocks) {
+      visitor.accept(block);
+      if (visitor.isFinished()) {
+        return visitor.getConstraint();
+      }
+    }
+    return visitor.getConstraint();
+  }
+
   @Override
   public String toString() {
     triggerDelayedParsingIfNeccessary();
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/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 819c6f2..5bed061 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -659,6 +659,7 @@
     for (Instruction instruction : getInstructions()) {
       if (instruction.outValue != null) {
         instruction.outValue.clearUsers();
+        instruction.setOutValue(null);
       }
       for (Value value : instruction.inValues) {
         value.removeUser(instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index 58b61af..1adcd11 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -390,7 +390,7 @@
     ImmutableList<BasicBlock> normalExits = inlinee.computeNormalExitBlocks();
     if (normalExits.isEmpty()) {
       assert inlineeCanThrow;
-      // TODO(sgjesse): Remove this restriction.
+      // TODO(sgjesse): Remove this restriction (see b/64432527).
       assert !invokeBlock.hasCatchHandlers();
       blocksToRemove.addAll(
           invokePredecessor.unlink(invokeBlock, new DominatorTree(code)));
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 b343de7..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
@@ -13,6 +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.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Collection;
 import java.util.Collections;
@@ -112,8 +113,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return inliningConstraintForSinlgeTargetInvoke(info, 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 ff8f0d0..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
@@ -13,6 +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.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Collection;
 import java.util.List;
@@ -94,8 +95,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return inliningConstraintForVirtualInvoke(info, 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 5fe8972..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
@@ -5,15 +5,12 @@
 
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
-import com.android.tools.r8.graph.AppInfo.ResolutionResult;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 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;
@@ -85,86 +82,6 @@
     return lookupSingleTarget(appInfo, appInfo.dexItemFactory.objectType);
   }
 
-  @Override
-  public abstract Constraint inliningConstraint(AppInfoWithLiveness info,
-      DexType invocationContext);
-
-  protected Constraint inliningConstraintForSinlgeTargetInvoke(AppInfoWithLiveness info,
-      DexType invocationContext) {
-    if (method.holder.isArrayType()) {
-      return Constraint.ALWAYS;
-    }
-    DexEncodedMethod target = lookupSingleTarget(info, invocationContext);
-    if (target != null) {
-      DexType methodHolder = target.method.holder;
-      DexClass methodClass = info.definitionFor(methodHolder);
-      if ((methodClass != null)) {
-        Constraint methodConstraint = Constraint
-            .deriveConstraint(invocationContext, methodHolder, target.accessFlags, info);
-        // We also have to take the constraint of the enclosing class into account.
-        Constraint classConstraint = Constraint
-            .deriveConstraint(invocationContext, methodHolder, methodClass.accessFlags, info);
-        return Constraint.min(methodConstraint, classConstraint);
-      }
-    }
-    return Constraint.NEVER;
-  }
-
-  protected Constraint inliningConstraintForVirtualInvoke(AppInfoWithSubtyping info,
-      DexType invocationContext) {
-    if (method.holder.isArrayType()) {
-      return Constraint.ALWAYS;
-    }
-    Collection<DexEncodedMethod> targets = lookupTargets(info, invocationContext);
-    if (targets == null) {
-      return Constraint.NEVER;
-    }
-
-    Constraint result = Constraint.ALWAYS;
-
-    // Perform resolution and derive inlining constraints based on the accessibility of the
-    // resolution result.
-    ResolutionResult resolutionResult = info.resolveMethod(method.holder, method);
-    DexEncodedMethod resolutionTarget = resolutionResult.asResultOfResolve();
-    if (resolutionTarget == null) {
-      // This will fail at runtime.
-      return Constraint.NEVER;
-    }
-    DexType methodHolder = resolutionTarget.method.holder;
-    DexClass methodClass = info.definitionFor(methodHolder);
-    assert methodClass != null;
-    Constraint methodConstraint = Constraint
-        .deriveConstraint(invocationContext, methodHolder, resolutionTarget.accessFlags, info);
-    result = Constraint.min(result, methodConstraint);
-    // We also have to take the constraint of the enclosing class of the resolution result
-    // into account. We do not allow inlining this method if it is calling something that
-    // is inaccessible. Inlining in that case could move the code to another package making a
-    // call succeed that should not succeed. Conversely, if the resolution result is accessible,
-    // we have to make sure that inlining cannot make it inaccessible.
-    Constraint classConstraint = Constraint
-        .deriveConstraint(invocationContext, methodHolder, methodClass.accessFlags, info);
-    result = Constraint.min(result, classConstraint);
-    if (result == Constraint.NEVER) {
-      return result;
-    }
-
-    // For each of the actual potential targets, derive constraints based on the accessibility
-    // of the method itself.
-    for (DexEncodedMethod target : targets) {
-      methodHolder = target.method.holder;
-      methodClass = info.definitionFor(methodHolder);
-      assert methodClass != null;
-      methodConstraint = Constraint
-          .deriveConstraint(invocationContext, methodHolder, target.accessFlags, info);
-      result = Constraint.min(result, methodConstraint);
-      if (result == Constraint.NEVER) {
-        return result;
-      }
-    }
-
-    return result;
-  }
-
   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 81d4cdc..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
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 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.InliningConstraints;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Collection;
@@ -125,8 +126,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.NEVER;
+  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 94df8d3..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
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 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.InliningConstraints;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Collection;
@@ -102,8 +103,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return inliningConstraintForSinlgeTargetInvoke(info, 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 e5bd604..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
@@ -14,6 +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.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Collection;
 import java.util.Collections;
@@ -111,8 +112,8 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    // The semantics of invoke super depend on the context.
-    return Constraint.SAMECLASS;
+  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 d104270..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
@@ -13,6 +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.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Collection;
 import java.util.List;
@@ -94,8 +95,9 @@
   }
 
   @Override
-  public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return inliningConstraintForVirtualInvoke(info, 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/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 53bbb7e..23d9a73 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -959,7 +959,7 @@
   private Invoke.Type invokeType(MethodInsnNode method) {
     switch (method.getOpcode()) {
       case Opcodes.INVOKEVIRTUAL:
-        if (isCallToPolymorphicSignatureMethod(method)) {
+        if (isCallToPolymorphicSignatureMethod(method.owner, method.name)) {
           return Invoke.Type.POLYMORPHIC;
         }
         return Invoke.Type.VIRTUAL;
@@ -2954,17 +2954,17 @@
     return writer.toString();
   }
 
-  private boolean isCallToPolymorphicSignatureMethod(MethodInsnNode method) {
-    if (method.owner.equals("java/lang/invoke/MethodHandle")) {
-      switch (method.name) {
+  public static boolean isCallToPolymorphicSignatureMethod(String owner, String name) {
+    if (owner.equals("java/lang/invoke/MethodHandle")) {
+      switch (name) {
         case "invoke":
         case "invokeExact":
           return true;
         default :
           return false;
       }
-    } else if (method.owner.equals("java/lang/invoke/VarHandle")) {
-      switch (method.name) {
+    } else if (owner.equals("java/lang/invoke/VarHandle")) {
+      switch (name) {
         case "compareAndExchange":
         case "compareAndExchangeAcquire":
         case "compareAndExchangeRelease":
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index b7653fa..94bf56c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -98,9 +98,10 @@
         if (target == null) {
           target = appInfo.lookupDirectTarget(method);
         }
-        assert target == null ||
-            (implHandle.type.isInvokeInstance() && isInstanceMethod(target)) ||
-            (implHandle.type.isInvokeDirect() && isPrivateInstanceMethod(target));
+        assert target == null
+            || (implHandle.type.isInvokeInstance() && isInstanceMethod(target))
+            || (implHandle.type.isInvokeDirect() && isPrivateInstanceMethod(target))
+            || (implHandle.type.isInvokeDirect() && isPublicizedInstanceMethod(target));
         return target;
       }
 
@@ -129,12 +130,17 @@
 
   private boolean isInstanceMethod(DexEncodedMethod encodedMethod) {
     assert encodedMethod != null;
-    return !encodedMethod.accessFlags.isConstructor() && !encodedMethod.accessFlags.isStatic();
+    return !encodedMethod.accessFlags.isConstructor() && !encodedMethod.isStaticMethod();
   }
 
   private boolean isPrivateInstanceMethod(DexEncodedMethod encodedMethod) {
     assert encodedMethod != null;
-    return encodedMethod.accessFlags.isPrivate() && isInstanceMethod(encodedMethod);
+    return encodedMethod.isPrivateMethod() && isInstanceMethod(encodedMethod);
+  }
+
+  private boolean isPublicizedInstanceMethod(DexEncodedMethod encodedMethod) {
+    assert encodedMethod != null;
+    return encodedMethod.isPublicized() && isInstanceMethod(encodedMethod);
   }
 
   final MethodAccessFlags getAccessibility() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index ee64165..121f47d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -3033,10 +3033,8 @@
             if (!value.isPhi()
                 && value.definition.isNumberConversion()
                 && value.definition.asNumberConversion().to == NumericType.DOUBLE) {
-              Value newValue = code.createValue(
-                  instruction.outValue().outType(), instruction.getLocalInfo());
               InvokeStatic invokeIsNaN =
-                  new InvokeStatic(javaLangDoubleisNaN.get(), newValue, ImmutableList.of(value));
+                  new InvokeStatic(javaLangDoubleisNaN.get(), null, ImmutableList.of(value));
               invokeIsNaN.setPosition(instruction.getPosition());
 
               // Insert the invoke before the current instruction.
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
new file mode 100644
index 0000000..073d6ae
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -0,0 +1,334 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.errors.Unreachable;
+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;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import java.util.Collection;
+
+// Computes the inlining constraint for a given instruction.
+public class InliningConstraints {
+
+  private AppInfoWithLiveness appInfo;
+
+  // Currently used only by the vertical class merger (in all other cases this is the identity).
+  //
+  // When merging a type A into its subtype B we need to inline A.<init>() into B.<init>().
+  // Therefore, we need to be sure that A.<init>() can in fact be inlined into B.<init>() *before*
+  // we merge the two classes. However, at this point, we may reject the method A.<init>() from
+  // being inlined into B.<init>() only because it is not declared in the same class as B (which
+  // it would be after merging A and B).
+  //
+  // To circumvent this problem, the vertical class merger creates a graph lense that maps the
+  // type A to B, to create a temporary view of what the world would look like after class merging.
+  private GraphLense graphLense;
+
+  public InliningConstraints(AppInfoWithLiveness appInfo) {
+    this(appInfo, GraphLense.getIdentityLense());
+  }
+
+  public InliningConstraints(AppInfoWithLiveness appInfo, GraphLense graphLense) {
+    assert graphLense.isContextFreeForMethods();
+    this.appInfo = appInfo;
+    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) {
+    DexField lookup = graphLense.lookupField(field);
+    return forFieldInstruction(
+        lookup, appInfo.lookupInstanceTarget(lookup.clazz, lookup), invocationContext);
+  }
+
+  public Constraint forInstanceOf(DexType type, DexType invocationContext) {
+    return Constraint.classIsVisible(invocationContext, type, appInfo);
+  }
+
+  public Constraint forInstancePut(DexField field, DexType invocationContext) {
+    DexField lookup = graphLense.lookupField(field);
+    return forFieldInstruction(
+        lookup, appInfo.lookupInstanceTarget(lookup.clazz, lookup), invocationContext);
+  }
+
+  public Constraint forInvoke(DexMethod method, Type type, DexType invocationContext) {
+    switch (type) {
+      case DIRECT:
+        return forInvokeDirect(method, invocationContext);
+      case INTERFACE:
+        return forInvokeInterface(method, invocationContext);
+      case STATIC:
+        return forInvokeStatic(method, invocationContext);
+      case SUPER:
+        return forInvokeSuper(method, invocationContext);
+      case VIRTUAL:
+        return forInvokeVirtual(method, invocationContext);
+      case CUSTOM:
+        return forInvokeCustom();
+      case POLYMORPHIC:
+        return forInvokePolymorphic(method, invocationContext);
+      default:
+        throw new Unreachable("Unexpected type: " + type);
+    }
+  }
+
+  public Constraint forInvokeCustom() {
+    return Constraint.NEVER;
+  }
+
+  public Constraint forInvokeDirect(DexMethod method, DexType invocationContext) {
+    DexMethod lookup = graphLense.lookupMethod(method);
+    return forSingleTargetInvoke(lookup, appInfo.lookupDirectTarget(lookup), invocationContext);
+  }
+
+  public Constraint forInvokeInterface(DexMethod method, DexType invocationContext) {
+    DexMethod lookup = graphLense.lookupMethod(method);
+    return forVirtualInvoke(lookup, appInfo.lookupInterfaceTargets(lookup), 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;
+  }
+
+  public Constraint forInvokeStatic(DexMethod method, DexType invocationContext) {
+    DexMethod lookup = graphLense.lookupMethod(method);
+    return forSingleTargetInvoke(lookup, appInfo.lookupStaticTarget(lookup), invocationContext);
+  }
+
+  public Constraint forInvokeSuper(DexMethod method, DexType invocationContext) {
+    // The semantics of invoke super depend on the context.
+    return Constraint.SAMECLASS;
+  }
+
+  public Constraint forInvokeVirtual(DexMethod method, DexType invocationContext) {
+    DexMethod lookup = graphLense.lookupMethod(method);
+    return forVirtualInvoke(lookup, appInfo.lookupVirtualTargets(lookup), 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) {
+    DexField lookup = graphLense.lookupField(field);
+    return forFieldInstruction(
+        lookup, appInfo.lookupStaticTarget(lookup.clazz, lookup), invocationContext);
+  }
+
+  public Constraint forStaticPut(DexField field, DexType invocationContext) {
+    DexField lookup = graphLense.lookupField(field);
+    return forFieldInstruction(
+        lookup, appInfo.lookupStaticTarget(lookup.clazz, lookup), 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()) {
+      return Constraint.ALWAYS;
+    }
+    if (target != null) {
+      DexType methodHolder = graphLense.lookupType(target.method.holder);
+      DexClass methodClass = appInfo.definitionFor(methodHolder);
+      if (methodClass != null) {
+        Constraint methodConstraint =
+            Constraint.deriveConstraint(
+                invocationContext, methodHolder, target.accessFlags, appInfo);
+        // We also have to take the constraint of the enclosing class into account.
+        Constraint classConstraint =
+            Constraint.deriveConstraint(
+                invocationContext, methodHolder, methodClass.accessFlags, appInfo);
+        return Constraint.min(methodConstraint, classConstraint);
+      }
+    }
+    return Constraint.NEVER;
+  }
+
+  private Constraint forVirtualInvoke(
+      DexMethod method, Collection<DexEncodedMethod> targets, DexType invocationContext) {
+    if (method.holder.isArrayType()) {
+      return Constraint.ALWAYS;
+    }
+    if (targets == null) {
+      return Constraint.NEVER;
+    }
+
+    // Perform resolution and derive inlining constraints based on the accessibility of the
+    // resolution result.
+    ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+    DexEncodedMethod resolutionTarget = resolutionResult.asResultOfResolve();
+    if (resolutionTarget == null) {
+      // This will fail at runtime.
+      return Constraint.NEVER;
+    }
+
+    DexType methodHolder = graphLense.lookupType(resolutionTarget.method.holder);
+    DexClass methodClass = appInfo.definitionFor(methodHolder);
+    assert methodClass != null;
+    Constraint methodConstraint =
+        Constraint.deriveConstraint(
+            invocationContext, methodHolder, resolutionTarget.accessFlags, appInfo);
+    // We also have to take the constraint of the enclosing class of the resolution result
+    // into account. We do not allow inlining this method if it is calling something that
+    // is inaccessible. Inlining in that case could move the code to another package making a
+    // call succeed that should not succeed. Conversely, if the resolution result is accessible,
+    // we have to make sure that inlining cannot make it inaccessible.
+    Constraint classConstraint =
+        Constraint.deriveConstraint(
+            invocationContext, methodHolder, methodClass.accessFlags, appInfo);
+    Constraint result = Constraint.min(methodConstraint, classConstraint);
+    if (result == Constraint.NEVER) {
+      return result;
+    }
+
+    // For each of the actual potential targets, derive constraints based on the accessibility
+    // of the method itself.
+    for (DexEncodedMethod target : targets) {
+      methodHolder = graphLense.lookupType(target.method.holder);
+      assert appInfo.definitionFor(methodHolder) != null;
+      methodConstraint =
+          Constraint.deriveConstraint(invocationContext, methodHolder, target.accessFlags, appInfo);
+      result = Constraint.min(result, methodConstraint);
+      if (result == Constraint.NEVER) {
+        return result;
+      }
+    }
+
+    return result;
+  }
+}
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/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 8556905..b3bde90 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -444,6 +444,12 @@
       return null;
     }
 
+    // Don't inline code w/o normal returns into block with catch handlers (b/64432527).
+    if (initInvoke.getBlock().hasCatchHandlers() &&
+        definition.getOptimizationInfo().neverReturnsNormally()) {
+      return null;
+    }
+
     // If the superclass of the initializer is NOT java.lang.Object, the super class
     // initializer being called must be classified as TrivialInstanceInitializer.
     //
@@ -476,16 +482,16 @@
     if (invoke.inValues().lastIndexOf(eligibleInstance) > 0) {
       return null; // Instance passed as an argument.
     }
-    return isEligibleMethodCall(invoke.getInvokedMethod(),
+    return isEligibleMethodCall(!invoke.getBlock().hasCatchHandlers(), invoke.getInvokedMethod(),
         eligibility -> !eligibility.returnsReceiver ||
             invoke.outValue() == null || invoke.outValue().numberOfAllUsers() == 0);
   }
 
   private InliningInfo isEligibleIndirectMethodCall(DexMethod callee) {
-    return isEligibleMethodCall(callee, eligibility -> !eligibility.returnsReceiver);
+    return isEligibleMethodCall(false, callee, eligibility -> !eligibility.returnsReceiver);
   }
 
-  private InliningInfo isEligibleMethodCall(
+  private InliningInfo isEligibleMethodCall(boolean allowMethodsWithoutNormalReturns,
       DexMethod callee, Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
 
     DexEncodedMethod singleTarget = findSingleTarget(callee);
@@ -496,8 +502,9 @@
       return null; // Don't inline itself.
     }
 
-    ClassInlinerEligibility eligibility =
-        singleTarget.getOptimizationInfo().getClassInlinerEligibility();
+    OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
+
+    ClassInlinerEligibility eligibility = optimizationInfo.getClassInlinerEligibility();
     if (eligibility == null) {
       return null;
     }
@@ -508,6 +515,11 @@
       return null;
     }
 
+    // Don't inline code w/o normal returns into block with catch handlers (b/64432527).
+    if (!allowMethodsWithoutNormalReturns && optimizationInfo.neverReturnsNormally()) {
+      return null;
+    }
+
     if (!singleTarget.isInliningCandidate(method, Reason.SIMPLE, appInfo)) {
       // We won't be able to inline it here.
 
@@ -570,6 +582,11 @@
 
     OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
 
+    // Don't inline code w/o normal returns into block with catch handlers (b/64432527).
+    if (invokeMethod.getBlock().hasCatchHandlers() && optimizationInfo.neverReturnsNormally()) {
+      return false;
+    }
+
     // Go through all arguments, see if all usages of eligibleInstance are good.
     for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
       Value argument = arguments.get(argIndex);
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index cebcc8c..08d0cae 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -354,7 +354,8 @@
         // avoid a bug where the index variable could end up being uninitialized.
         if (code.options.canHaveBoundsCheckEliminationBug()
             && move.from.getValue().isConstNumber()
-            && move.type == MoveType.SINGLE) {
+            && move.type == MoveType.SINGLE
+            && allocator.unadjustedRealRegisterFromAllocated(move.to.getRegister()) < 256) {
           scheduler.addMove(
               new RegisterMove(move.to.getRegister(), move.type, move.from.getValue().definition));
         } else {
diff --git a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
new file mode 100644
index 0000000..459d7de
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
@@ -0,0 +1,235 @@
+// 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.jar;
+
+import static org.objectweb.asm.Opcodes.ASM6;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexClass;
+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;
+import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.conversion.JarSourceCode;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.TryCatchBlockNode;
+
+// This visitor can be used to determine if a piece of jar code has any instructions that the
+// inliner would not be willing to inline. This can be used to determine if a method can be force
+// inlined although its IR is still not available.
+//
+// Note that this class has only been implemented for the hooks in InliningConstraints that may
+// return a non-ALWAYS inlining constraint (e.g., InliningConstraints.forReturn is not called).
+public class InliningConstraintVisitor extends MethodVisitor {
+
+  private final JarApplicationReader application;
+  private final AppInfoWithLiveness appInfo;
+  private final GraphLense graphLense;
+  private final InliningConstraints inliningConstraints;
+  private final DexEncodedMethod method;
+  private final DexType invocationContext;
+
+  private Constraint constraint;
+
+  public InliningConstraintVisitor(
+      JarApplicationReader application,
+      AppInfoWithLiveness appInfo,
+      GraphLense graphLense,
+      DexEncodedMethod method,
+      DexType invocationContext) {
+    super(ASM6);
+    assert graphLense.isContextFreeForMethods();
+    this.application = application;
+    this.appInfo = appInfo;
+    this.graphLense = graphLense;
+    this.inliningConstraints = new InliningConstraints(appInfo, graphLense);
+    this.method = method;
+    this.invocationContext = invocationContext;
+
+    // Model a synchronized method as having a monitor instruction.
+    this.constraint =
+        method.accessFlags.isSynchronized() ? inliningConstraints.forMonitor() : Constraint.ALWAYS;
+  }
+
+  public Constraint getConstraint() {
+    return constraint;
+  }
+
+  private void updateConstraint(Constraint other) {
+    constraint = Constraint.min(constraint, other);
+  }
+
+  // Used to signal that the result is ready, such that we do not need to visit all instructions of
+  // the method, if we can see early on that it cannot be inlined anyway.
+  public boolean isFinished() {
+    return constraint == Constraint.NEVER;
+  }
+
+  public void accept(TryCatchBlockNode tryCatchBlock) {
+    // Model a try-catch as a move-exception instruction.
+    updateConstraint(inliningConstraints.forMoveException());
+  }
+
+  @Override
+  public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+    DexField field = application.getField(owner, name, desc);
+    switch (opcode) {
+      case Opcodes.GETFIELD:
+        updateConstraint(inliningConstraints.forInstanceGet(field, invocationContext));
+        break;
+
+      case Opcodes.PUTFIELD:
+        updateConstraint(inliningConstraints.forInstancePut(field, invocationContext));
+        break;
+
+      case Opcodes.GETSTATIC:
+        updateConstraint(inliningConstraints.forStaticGet(field, invocationContext));
+        break;
+
+      case Opcodes.PUTSTATIC:
+        updateConstraint(inliningConstraints.forStaticPut(field, invocationContext));
+        break;
+
+      default:
+        throw new Unreachable("Unexpected opcode " + opcode);
+    }
+  }
+
+  @Override
+  public void visitLdcInsn(Object cst) {
+    if (cst instanceof Type && ((Type) cst).getSort() != Type.METHOD) {
+      DexType type = application.getType((Type) cst);
+      updateConstraint(inliningConstraints.forConstClass(type, invocationContext));
+    } else {
+      updateConstraint(inliningConstraints.forConstInstruction());
+    }
+  }
+
+  @Override
+  public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+    DexType ownerType = application.getTypeFromName(owner);
+    DexMethod target = application.getMethod(ownerType, name, desc);
+
+    // Find the DEX invocation type.
+    Invoke.Type type;
+    switch (opcode) {
+      case Opcodes.INVOKEDYNAMIC:
+        type =
+            JarSourceCode.isCallToPolymorphicSignatureMethod(owner, name)
+                ? Invoke.Type.POLYMORPHIC
+                : Invoke.Type.CUSTOM;
+        assert noNeedToUseGraphLense(target, type);
+        break;
+
+      case Opcodes.INVOKEINTERFACE:
+        // Could have changed to an invoke-virtual instruction due to vertical class merging
+        // (if an interface is merged into a class).
+        type = graphLense.lookupMethod(target, null, Invoke.Type.INTERFACE).getType();
+        assert type == Invoke.Type.INTERFACE || type == Invoke.Type.VIRTUAL;
+        break;
+
+      case Opcodes.INVOKESPECIAL:
+        if (name.equals(Constants.INSTANCE_INITIALIZER_NAME)) {
+          type = Invoke.Type.DIRECT;
+          assert noNeedToUseGraphLense(target, type);
+        } else if (ownerType == invocationContext) {
+          // The method could have been publicized.
+          type = graphLense.lookupMethod(target, null, Invoke.Type.DIRECT).getType();
+          assert type == Invoke.Type.DIRECT || type == Invoke.Type.VIRTUAL;
+        } else {
+          // This is a super call. Note that the vertical class merger translates some invoke-super
+          // instructions to invoke-direct. However, when that happens, the invoke instruction and
+          // the target method end up being in the same class, and therefore, we will allow inlining
+          // it. The result of using type=SUPER below will be the same, since it leads to the
+          // inlining constraint SAMECLASS.
+          // TODO(christofferqa): Consider using graphLense.lookupMethod (to do this, we need the
+          // context for the graph lense, though).
+          type = Invoke.Type.SUPER;
+          assert noNeedToUseGraphLense(target, type);
+        }
+        break;
+
+      case Opcodes.INVOKESTATIC:
+        type = Invoke.Type.STATIC;
+        assert noNeedToUseGraphLense(target, type);
+        break;
+
+      case Opcodes.INVOKEVIRTUAL:
+        type = Invoke.Type.VIRTUAL;
+        // Instructions that target a private method in the same class translates to invoke-direct.
+        if (target.holder == method.method.holder) {
+          DexClass clazz = appInfo.definitionFor(target.holder);
+          if (clazz != null && clazz.lookupDirectMethod(target) != null) {
+            type = Invoke.Type.DIRECT;
+          }
+        }
+        assert noNeedToUseGraphLense(target, type);
+        break;
+
+      default:
+        throw new Unreachable("Unexpected opcode " + opcode);
+    }
+
+    updateConstraint(inliningConstraints.forInvoke(target, type, invocationContext));
+  }
+
+  private boolean noNeedToUseGraphLense(DexMethod method, Invoke.Type type) {
+    assert graphLense.lookupMethod(method, null, type).getType() == type;
+    return true;
+  }
+
+  @Override
+  public void visitInsn(int opcode) {
+    switch (opcode) {
+      case Opcodes.MONITORENTER:
+      case Opcodes.MONITOREXIT:
+        updateConstraint(inliningConstraints.forMonitor());
+        break;
+
+      default:
+        // All instructions here lead to the inlining constraint ALWAYS.
+    }
+  }
+
+  @Override
+  public void visitMultiANewArrayInsn(String desc, int dims) {
+    DexType type = application.getTypeFromDescriptor(desc);
+    updateConstraint(inliningConstraints.forInvokeMultiNewArray(type, invocationContext));
+  }
+
+  @Override
+  public void visitTypeInsn(int opcode, String typeName) {
+    DexType type = application.getTypeFromName(typeName);
+    switch (opcode) {
+      case Opcodes.ANEWARRAY:
+        updateConstraint(inliningConstraints.forNewArrayEmpty(type, invocationContext));
+        break;
+
+      case Opcodes.CHECKCAST:
+        updateConstraint(inliningConstraints.forCheckCast(type, invocationContext));
+        break;
+
+      case Opcodes.INSTANCEOF:
+        updateConstraint(inliningConstraints.forInstanceOf(type, invocationContext));
+        break;
+
+      case Opcodes.NEW:
+        updateConstraint(inliningConstraints.forNewInstance(type, invocationContext));
+        break;
+
+      default:
+        throw new Unreachable("Unexpected opcode " + opcode);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index f5d48fd..f114a7e 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
@@ -33,13 +34,10 @@
   private final GraphLense previousLense;
   private final SeedMapper seedMapper;
 
-  public ProguardMapApplier(
-      AppInfoWithLiveness appInfo,
-      GraphLense previousLense,
-      SeedMapper seedMapper) {
-    assert previousLense.isContextFreeForMethods();
-    this.appInfo = appInfo;
-    this.previousLense = previousLense;
+  public ProguardMapApplier(AppView<AppInfoWithLiveness> appView, SeedMapper seedMapper) {
+    assert appView.getGraphLense().isContextFreeForMethods();
+    this.appInfo = appView.getAppInfo();
+    this.previousLense = appView.getGraphLense();
     this.seedMapper = seedMapper;
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index eaeb1ee..73ebf73 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize;
 
-import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -18,54 +18,47 @@
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 
 public final class ClassAndMemberPublicizer {
   private final DexApplication application;
-  private final AppInfo appInfo;
+  private final AppView appView;
   private final RootSet rootSet;
-  private final GraphLense previuosLense;
   private final PublicizedLenseBuilder lenseBuilder;
 
   private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
-  // TODO(b/72109068): finer-grained naming spaces, e.g., per-tree.
-  private final Set<Wrapper<DexMethod>> methodPool = Sets.newConcurrentHashSet();
+  private final Map<DexClass, MethodPool> methodPools = new ConcurrentHashMap<>();
 
-  private ClassAndMemberPublicizer(
-      DexApplication application,
-      AppInfo appInfo,
-      RootSet rootSet,
-      GraphLense previousLense) {
+  private ClassAndMemberPublicizer(DexApplication application, AppView appView, RootSet rootSet) {
     this.application = application;
-    this.appInfo = appInfo;
+    this.appView = appView;
     this.rootSet = rootSet;
-    this.previuosLense = previousLense;
     lenseBuilder = PublicizerLense.createBuilder();
   }
 
   /**
-   * Marks all package private and protected methods and fields as public.
-   * Makes all private static methods public.
-   * Makes private instance methods public final instance methods, if possible.
-   * <p>
-   * This will destructively update the DexApplication passed in as argument.
+   * Marks all package private and protected methods and fields as public. Makes all private static
+   * methods public. Makes private instance methods public final instance methods, if possible.
+   *
+   * <p>This will destructively update the DexApplication passed in as argument.
    */
   public static GraphLense run(
       ExecutorService executorService,
       Timing timing,
       DexApplication application,
-      AppInfo appInfo,
-      RootSet rootSet,
-      GraphLense previousLense) throws ExecutionException {
-    return new ClassAndMemberPublicizer(application, appInfo, rootSet, previousLense)
-        .run(executorService, timing);
+      AppView appView,
+      RootSet rootSet)
+      throws ExecutionException {
+    return new ClassAndMemberPublicizer(application, appView, rootSet).run(executorService, timing);
   }
 
   private GraphLense run(ExecutorService executorService, Timing timing)
@@ -74,9 +67,8 @@
     timing.begin("Phase 1: collectMethods");
     try {
       List<Future<?>> futures = new ArrayList<>();
-      // TODO(b/72109068): finer-grained naming spaces will need a different class visiting.
       application.classes().forEach(clazz ->
-          futures.add(executorService.submit(collectMethodPerClass(clazz))));
+          futures.add(executorService.submit(computeMethodPoolPerClass(clazz))));
       ThreadUtils.awaitFutures(futures);
     } finally {
       timing.end();
@@ -84,21 +76,39 @@
 
     // Phase 2: Visit classes and promote class/member to public if possible.
     timing.begin("Phase 2: promoteToPublic");
-    DexType.forAllInterfaces(appInfo.dexItemFactory, this::publicizeType);
-    publicizeType(appInfo.dexItemFactory.objectType);
+    DexType.forAllInterfaces(appView.getDexItemFactory(), this::publicizeType);
+    publicizeType(appView.getDexItemFactory().objectType);
     timing.end();
 
-    return lenseBuilder.build(appInfo, previuosLense);
+    return lenseBuilder.build(appView);
   }
 
-  private Runnable collectMethodPerClass(DexClass clazz) {
+  private Runnable computeMethodPoolPerClass(DexClass clazz) {
     return () -> {
+      MethodPool methodPool = methodPools.computeIfAbsent(clazz, k -> new MethodPool());
       clazz.forEachMethod(encodedMethod -> {
         // We will add private instance methods when we promote them.
         if (!encodedMethod.isPrivateMethod() || encodedMethod.isStaticMethod()) {
-          methodPool.add(equivalence.wrap(encodedMethod.method));
+          methodPool.seen(equivalence.wrap(encodedMethod.method));
         }
       });
+      if (clazz.superType != null) {
+        DexClass superClazz = application.definitionFor(clazz.superType);
+        if (superClazz != null) {
+          MethodPool superPool = methodPools.computeIfAbsent(superClazz, k -> new MethodPool());
+          superPool.linkSubtype(methodPool);
+          methodPool.linkSupertype(superPool);
+        }
+      }
+      if (clazz.isInterface()) {
+        clazz.type.forAllImplementsSubtypes(implementer -> {
+          DexClass subClazz = application.definitionFor(implementer);
+          if (subClazz != null) {
+            MethodPool childPool = methodPools.computeIfAbsent(subClazz, k -> new MethodPool());
+            childPool.linkInterface(methodPool);
+          }
+        });
+      }
     };
   }
 
@@ -118,7 +128,6 @@
       }
     }
 
-    // TODO(b/72109068): Can process sub types in parallel.
     type.forAllExtendsSubtypes(this::publicizeType);
   }
 
@@ -128,7 +137,7 @@
       return false;
     }
 
-    if (appInfo.dexItemFactory.isClassConstructor(encodedMethod.method)) {
+    if (appView.getDexItemFactory().isClassConstructor(encodedMethod.method)) {
       return false;
     }
 
@@ -139,7 +148,7 @@
     }
     assert accessFlags.isPrivate();
 
-    if (appInfo.dexItemFactory.isConstructor(encodedMethod.method)) {
+    if (appView.getDexItemFactory().isConstructor(encodedMethod.method)) {
       // TODO(b/72211928)
       return false;
     }
@@ -152,21 +161,22 @@
 
       // We can't publicize private instance methods in interfaces or methods that are copied from
       // interfaces to lambda-desugared classes because this will be added as a new default method.
-      // TODO(b/72109068): It might be possible to transform it into static methods, though.
+      // TODO(b/111118390): It might be possible to transform it into static methods, though.
       if (holder.isInterface() || accessFlags.isSynthetic()) {
         return false;
       }
 
+      MethodPool methodPool = methodPools.get(holder);
       Wrapper<DexMethod> key = equivalence.wrap(encodedMethod.method);
-      if (methodPool.contains(key)) {
+      if (methodPool.hasSeen(key)) {
         // We can't do anything further because even renaming is not allowed due to the keep rule.
         if (rootSet.noObfuscation.contains(encodedMethod)) {
           return false;
         }
-        // TODO(b/72109068): Renaming will enable more private instance methods to be publicized.
+        // TODO(b/111118390): Renaming will enable more private instance methods to be publicized.
         return false;
       }
-      methodPool.add(key);
+      methodPool.seen(key);
       lenseBuilder.add(encodedMethod.method);
       accessFlags.unsetPrivate();
       accessFlags.setFinal();
@@ -186,4 +196,51 @@
     accessFlags.setPublic();
     return false;
   }
+
+  // Per-class collection of method signatures, which will be used to determine if a certain method
+  // can be publicized or not.
+  static class MethodPool {
+    private MethodPool superType;
+    private final Set<MethodPool> interfaces = new HashSet<>();
+    private final Set<MethodPool> subTypes = new HashSet<>();
+    private final Set<Wrapper<DexMethod>> methodPool = new HashSet<>();
+
+    MethodPool() {
+    }
+
+    synchronized void linkSupertype(MethodPool superType) {
+      assert this.superType == null;
+      this.superType = superType;
+    }
+
+    synchronized void linkSubtype(MethodPool subType) {
+      boolean added = subTypes.add(subType);
+      assert added;
+    }
+
+    synchronized void linkInterface(MethodPool itf) {
+      boolean added = interfaces.add(itf);
+      assert added;
+    }
+
+    synchronized void seen(Wrapper<DexMethod> method) {
+      boolean added = methodPool.add(method);
+      assert added;
+    }
+
+    boolean hasSeen(Wrapper<DexMethod> method) {
+      return hasSeenUpwardRecursive(method) || hasSeenDownwardRecursive(method);
+    }
+
+    private boolean hasSeenUpwardRecursive(Wrapper<DexMethod> method) {
+      return methodPool.contains(method)
+          || (superType != null && superType.hasSeenUpwardRecursive(method))
+          || interfaces.stream().anyMatch(itf -> itf.hasSeenUpwardRecursive(method));
+    }
+
+    private boolean hasSeenDownwardRecursive(Wrapper<DexMethod> method) {
+      return methodPool.contains(method)
+          || subTypes.stream().anyMatch(subType -> subType.hasSeenDownwardRecursive(method));
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 9efd5c8..fe098cc 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -28,10 +29,10 @@
   private final GraphLense lense;
   private final MemberRebindingLense.Builder builder;
 
-  public MemberRebindingAnalysis(AppInfoWithLiveness appInfo, GraphLense lense) {
-    assert lense.isContextFreeForMethods();
-    this.appInfo = appInfo;
-    this.lense = lense;
+  public MemberRebindingAnalysis(AppView<AppInfoWithLiveness> appView) {
+    assert appView.getGraphLense().isContextFreeForMethods();
+    this.appInfo = appView.getAppInfo();
+    this.lense = appView.getGraphLense();
     this.builder = MemberRebindingLense.builder(appInfo);
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
index 835135e..7a9c08f 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize;
 
-import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -15,16 +15,19 @@
 import java.util.Set;
 
 final class PublicizerLense extends NestedGraphLense {
-  private final AppInfo appInfo;
+  private final AppView appView;
   private final Set<DexMethod> publicizedMethods;
 
-  PublicizerLense(
-      AppInfo appInfo, GraphLense previousLense, Set<DexMethod> publicizedMethods) {
+  PublicizerLense(AppView appView, Set<DexMethod> publicizedMethods) {
     // This lense does not map any DexItem's at all.
     // It will just tweak invoke type for publicized methods from invoke-direct to invoke-virtual.
-    super(ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of(),
-        previousLense, appInfo.dexItemFactory);
-    this.appInfo = appInfo;
+    super(
+        ImmutableMap.of(),
+        ImmutableMap.of(),
+        ImmutableMap.of(),
+        appView.getGraphLense(),
+        appView.getAppInfo().dexItemFactory);
+    this.appView = appView;
     this.publicizedMethods = publicizedMethods;
   }
 
@@ -35,18 +38,24 @@
     method = previous.getMethod();
     type = previous.getType();
     if (type == Type.DIRECT && publicizedMethods.contains(method)) {
-      DexClass holderClass = appInfo.definitionFor(method.holder);
-      if (holderClass != null) {
-        DexEncodedMethod actualEncodedTarget = holderClass.lookupVirtualMethod(method);
-        if (actualEncodedTarget != null
-            && actualEncodedTarget.isPublicized()) {
-          return new GraphLenseLookupResult(method, Type.VIRTUAL);
-        }
-      }
+      assert publicizedMethodIsPresentOnHolder(method, context);
+      return new GraphLenseLookupResult(method, Type.VIRTUAL);
     }
     return super.lookupMethod(method, context, type);
   }
 
+  private boolean publicizedMethodIsPresentOnHolder(DexMethod method, DexEncodedMethod context) {
+    GraphLenseLookupResult lookup =
+        appView.getGraphLense().lookupMethod(method, context, Type.VIRTUAL);
+    DexMethod signatureInCurrentWorld = lookup.getMethod();
+    DexClass clazz = appView.getAppInfo().definitionFor(signatureInCurrentWorld.holder);
+    assert clazz != null;
+    DexEncodedMethod actualEncodedTarget = clazz.lookupVirtualMethod(signatureInCurrentWorld);
+    assert actualEncodedTarget != null;
+    assert actualEncodedTarget.isPublicized();
+    return true;
+  }
+
   static PublicizedLenseBuilder createBuilder() {
     return new PublicizedLenseBuilder();
   }
@@ -57,8 +66,8 @@
     private PublicizedLenseBuilder() {
     }
 
-    public GraphLense build(AppInfo appInfo, GraphLense previousLense) {
-      return new PublicizerLense(appInfo, previousLense, methodSetBuilder.build());
+    public GraphLense build(AppView appView) {
+      return new PublicizerLense(appView, methodSetBuilder.build());
     }
 
     public void add(DexMethod publicizedMethod) {
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 69e0221..061b9bf 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1551,10 +1551,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;
@@ -1609,6 +1617,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;
@@ -1647,6 +1657,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;
@@ -1690,6 +1702,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.
@@ -1731,6 +1745,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 c4276dc..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();
     }
@@ -873,6 +894,7 @@
         ruleBuilder.setName(IdentifierPatternWithWildcards.withoutWildcards("<init>"));
         ruleBuilder.setArguments(parseArgumentList());
       } else {
+        TextPosition firstStart = getPosition();
         IdentifierPatternWithWildcards first =
             acceptIdentifierWithBackreference(IdentifierType.ANY);
         if (first != null) {
@@ -885,6 +907,7 @@
               ruleBuilder.setName(first);
               ruleBuilder.setArguments(parseArgumentList());
             } else {
+              TextPosition secondStart = getPosition();
               IdentifierPatternWithWildcards second =
                   acceptIdentifierWithBackreference(IdentifierType.ANY);
               if (second != null) {
@@ -897,6 +920,12 @@
                           ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory));
                   ruleBuilder.setArguments(parseArgumentList());
                 } else {
+                  if (first.hasUnusualCharacters()) {
+                    warnUnusualCharacters("type", first.pattern, "field", firstStart);
+                  }
+                  if (second.hasUnusualCharacters()) {
+                    warnUnusualCharacters("field name", second.pattern, "field", secondStart);
+                  }
                   ruleBuilder.setRuleType(ProguardMemberType.FIELD);
                   ruleBuilder.setName(second);
                   ruleBuilder
@@ -1490,6 +1519,16 @@
           "Option -" + optionName + " overrides -" + victim, origin, getPosition(start)));
     }
 
+    private void warnUnusualCharacters(
+        String kind, String pattern, String ruleType, TextPosition start) {
+      reporter.warning(new StringDiagnostic(
+          "The " + kind + " \"" + pattern + "\" is used in a " + ruleType + " rule. The "
+              + "characters in this " + kind + " are legal for the JVM, "
+              + "but unlikely to originate from a source language. "
+              + "Maybe this is not the rule you are looking for.",
+          origin, getPosition(start)));
+    }
+
     private void failPartiallyImplementedOption(String optionName, TextPosition start) {
       throw reporter.fatalError(new StringDiagnostic(
           "Option " + optionName + " currently not supported", origin, getPosition(start)));
@@ -1528,5 +1567,25 @@
     boolean isMatchAllNames() {
       return pattern.equals("*");
     }
+
+    boolean hasUnusualCharacters() {
+      if (pattern.contains("<") || pattern.contains(">")) {
+        int angleStartCount = 0;
+        int angleEndCount = 0;
+        for (int i = 0; i < pattern.length(); i++) {
+          char c = pattern.charAt(i);
+          if (c == '<') {
+            angleStartCount++;
+          }
+          if (c == '>') {
+            angleEndCount++;
+          }
+        }
+        // Check that start/end angles are matched, and *only* used for well-formed wildcard
+        // backreferences (e.g. '<1>', but not '<<1>>', '<<*>>' or '>1<').
+        return !(angleStartCount == angleEndCount && angleStartCount == wildcards.size());
+      }
+      return false;
+    }
   }
 }
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/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 6f935ee..0840d91 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo.ResolutionResult;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
@@ -21,12 +22,14 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.Builder;
+import com.android.tools.r8.graph.JarCode;
 import com.android.tools.r8.graph.KeyedDexItem;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.logging.Log;
@@ -37,9 +40,9 @@
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterators;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayDeque;
@@ -55,9 +58,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.BiFunction;
 import java.util.function.Predicate;
-import java.util.stream.Collectors;
 
 /**
  * Merges Supertypes with a single implementation into their single subtype.
@@ -74,6 +75,7 @@
     ALWAYS_INLINE,
     CONFLICT,
     ILLEGAL_ACCESS,
+    NATIVE_METHOD,
     NO_SIDE_EFFECTS,
     PINNED_SOURCE,
     RESOLUTION_FOR_FIELDS_MAY_CHANGE,
@@ -101,6 +103,9 @@
         case ILLEGAL_ACCESS:
           message = "it could lead to illegal accesses";
           break;
+        case NATIVE_METHOD:
+          message = "it has a native method";
+          break;
         case NO_SIDE_EFFECTS:
           message = "it is mentioned in appInfo.noSideEffects";
           break;
@@ -141,40 +146,61 @@
   private final Timing timing;
   private Collection<DexMethod> invokes;
 
+  // Set of merge candidates. Note that this must have a deterministic iteration order.
+  private final Set<DexProgramClass> mergeCandidates = new LinkedHashSet<>();
+
   // Map from source class to target class.
   private final Map<DexType, DexType> mergedClasses = new HashMap<>();
 
   // Map from target class to the super classes that have been merged into the target class.
   private final Map<DexType, Set<DexType>> mergedClassesInverse = new HashMap<>();
 
+  // Set of types that must not be merged into their subtype.
+  private final Set<DexType> pinnedTypes = new HashSet<>();
+
   // The resulting graph lense that should be used after class merging.
   private final VerticalClassMergerGraphLense.Builder renamedMembersLense;
 
   public VerticalClassMerger(
-      DexApplication application,
-      AppInfoWithLiveness appInfo,
-      GraphLense graphLense,
-      Timing timing) {
+      DexApplication application, AppView<AppInfoWithLiveness> appView, Timing timing) {
     this.application = application;
-    this.appInfo = appInfo;
-    this.graphLense = graphLense;
+    this.appInfo = appView.getAppInfo();
+    this.graphLense = appView.getGraphLense();
     this.renamedMembersLense = VerticalClassMergerGraphLense.builder(appInfo);
     this.timing = timing;
+
+    Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder();
+    initializePinnedTypes(classes); // Must be initialized prior to mergeCandidates.
+    initializeMergeCandidates(classes);
+  }
+
+  private void initializeMergeCandidates(Iterable<DexProgramClass> classes) {
+    for (DexProgramClass clazz : classes) {
+      if (isMergeCandidate(clazz, pinnedTypes) && isStillMergeCandidate(clazz)) {
+        mergeCandidates.add(clazz);
+      }
+    }
   }
 
   // Returns a set of types that must not be merged into other types.
-  private Set<DexType> getPinnedTypes(Iterable<DexProgramClass> classes) {
-    Set<DexType> pinnedTypes = new HashSet<>();
-
+  private void initializePinnedTypes(Iterable<DexProgramClass> classes) {
     // For all pinned fields, also pin the type of the field (because changing the type of the field
     // implicitly changes the signature of the field). Similarly, for all pinned methods, also pin
     // the return type and the parameter types of the method.
-    extractPinnedItems(appInfo.pinnedItems, pinnedTypes, AbortReason.PINNED_SOURCE);
+    extractPinnedItems(appInfo.pinnedItems, AbortReason.PINNED_SOURCE);
 
     // TODO(christofferqa): Remove the invariant that the graph lense should not modify any
     // methods from the sets alwaysInline and noSideEffects (see use of assertNotModified).
-    extractPinnedItems(appInfo.alwaysInline, pinnedTypes, AbortReason.ALWAYS_INLINE);
-    extractPinnedItems(appInfo.noSideEffects.keySet(), pinnedTypes, AbortReason.NO_SIDE_EFFECTS);
+    extractPinnedItems(appInfo.alwaysInline, AbortReason.ALWAYS_INLINE);
+    extractPinnedItems(appInfo.noSideEffects.keySet(), AbortReason.NO_SIDE_EFFECTS);
+
+    for (DexProgramClass clazz : classes) {
+      for (DexEncodedMethod method : clazz.methods()) {
+        if (method.accessFlags.isNative()) {
+          markTypeAsPinned(clazz.type, AbortReason.NATIVE_METHOD);
+        }
+      }
+    }
 
     // Avoid merging two types if this could remove a NoSuchMethodError, as illustrated by the
     // following example. (Alternatively, it would be possible to merge A and B and rewrite the
@@ -196,41 +222,41 @@
         pinnedTypes.add(signature.holder);
       }
     }
-    return pinnedTypes;
   }
 
-  private void extractPinnedItems(
-      Iterable<DexItem> items, Set<DexType> pinnedTypes, AbortReason reason) {
+  private void extractPinnedItems(Iterable<DexItem> items, AbortReason reason) {
     for (DexItem item : items) {
       if (item instanceof DexType || item instanceof DexClass) {
         DexType type = item instanceof DexType ? (DexType) item : ((DexClass) item).type;
-        // We check for the case where the type is pinned according to appInfo.isPinned, so only
-        // add it here if it is not the case.
-        if (!appInfo.isPinned(type)) {
-          markTypeAsPinned(type, pinnedTypes, reason);
-        }
+        markTypeAsPinned(type, reason);
       } else if (item instanceof DexField || item instanceof DexEncodedField) {
         // Pin the holder and the type of the field.
         DexField field =
             item instanceof DexField ? (DexField) item : ((DexEncodedField) item).field;
-        markTypeAsPinned(field.clazz, pinnedTypes, reason);
-        markTypeAsPinned(field.type, pinnedTypes, reason);
+        markTypeAsPinned(field.clazz, reason);
+        markTypeAsPinned(field.type, reason);
       } else if (item instanceof DexMethod || item instanceof DexEncodedMethod) {
         // Pin the holder, the return type and the parameter types of the method. If we were to
         // merge any of these types into their sub classes, then we would implicitly change the
         // signature of this method.
         DexMethod method =
             item instanceof DexMethod ? (DexMethod) item : ((DexEncodedMethod) item).method;
-        markTypeAsPinned(method.holder, pinnedTypes, reason);
-        markTypeAsPinned(method.proto.returnType, pinnedTypes, reason);
+        markTypeAsPinned(method.holder, reason);
+        markTypeAsPinned(method.proto.returnType, reason);
         for (DexType parameterType : method.proto.parameters.values) {
-          markTypeAsPinned(parameterType, pinnedTypes, reason);
+          markTypeAsPinned(parameterType, reason);
         }
       }
     }
   }
 
-  private void markTypeAsPinned(DexType type, Set<DexType> pinnedTypes, AbortReason reason) {
+  private void markTypeAsPinned(DexType type, AbortReason reason) {
+    if (appInfo.isPinned(type)) {
+      // We check for the case where the type is pinned according to appInfo.isPinned,
+      // so we only need to add it here if it is not the case.
+      return;
+    }
+
     DexClass clazz = appInfo.definitionFor(type);
     if (clazz != null && clazz.isProgramClass()) {
       boolean changed = pinnedTypes.add(type);
@@ -243,6 +269,8 @@
     }
   }
 
+  // Returns true if [clazz] is a merge candidate. Note that the result of the checks in this
+  // method do not change in response to any class merges.
   private boolean isMergeCandidate(DexProgramClass clazz, Set<DexType> pinnedTypes) {
     if (appInfo.instantiatedTypes.contains(clazz.type)
         || appInfo.instantiatedLambdas.contains(clazz.type)
@@ -250,14 +278,8 @@
         || pinnedTypes.contains(clazz.type)) {
       return false;
     }
-    if (mergedClassesInverse.containsKey(clazz.type)) {
-      // Do not allow merging the resulting class into its subclass.
-      // TODO(christofferqa): Get rid of this limitation.
-      if (Log.ENABLED) {
-        AbortReason.ALREADY_MERGED.printLogMessageForClass(clazz);
-      }
-      return false;
-    }
+    // Note that the property "singleSubtype == null" cannot change during merging, since we visit
+    // classes in a top-down order.
     DexType singleSubtype = clazz.type.getSingleSubtype();
     if (singleSubtype == null) {
       // TODO(christofferqa): Even if [clazz] has multiple subtypes, we could still merge it into
@@ -274,7 +296,7 @@
       if (appInfo.isPinned(method.method)) {
         return false;
       }
-      if (method.isInstanceInitializer() && disallowInlining(method)) {
+      if (method.isInstanceInitializer() && disallowInlining(method, singleSubtype)) {
         // Cannot guarantee that markForceInline() will work.
         if (Log.ENABLED) {
           AbortReason.UNSAFE_INLINING.printLogMessageForClass(clazz);
@@ -282,13 +304,64 @@
         return false;
       }
     }
-    DexClass targetClass = appInfo.definitionFor(singleSubtype);
+    if (clazz.getEnclosingMethod() != null || !clazz.getInnerClasses().isEmpty()) {
+      // TODO(herhut): Consider supporting merging of enclosing-method and inner-class attributes.
+      if (Log.ENABLED) {
+        AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(clazz);
+      }
+      return false;
+    }
+    return true;
+  }
+
+  // Returns true if [clazz] is a merge candidate. Note that the result of the checks in this
+  // method may change in response to class merges. Therefore, this method should always be called
+  // before merging [clazz] into its subtype.
+  private boolean isStillMergeCandidate(DexProgramClass clazz) {
+    assert isMergeCandidate(clazz, pinnedTypes);
+    if (mergedClassesInverse.containsKey(clazz.type)) {
+      // Do not allow merging the resulting class into its subclass.
+      // TODO(christofferqa): Get rid of this limitation.
+      if (Log.ENABLED) {
+        AbortReason.ALREADY_MERGED.printLogMessageForClass(clazz);
+      }
+      return false;
+    }
+    DexClass targetClass = appInfo.definitionFor(clazz.type.getSingleSubtype());
+    if (clazz.hasClassInitializer() && targetClass.hasClassInitializer()) {
+      // TODO(herhut): Handle class initializers.
+      if (Log.ENABLED) {
+        AbortReason.STATIC_INITIALIZERS.printLogMessageForClass(clazz);
+      }
+      return false;
+    }
+    if (targetClass.getEnclosingMethod() != null || !targetClass.getInnerClasses().isEmpty()) {
+      // TODO(herhut): Consider supporting merging of enclosing-method and inner-class attributes.
+      if (Log.ENABLED) {
+        AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(clazz);
+      }
+      return false;
+    }
     if (mergeMayLeadToIllegalAccesses(clazz, targetClass)) {
       if (Log.ENABLED) {
         AbortReason.ILLEGAL_ACCESS.printLogMessageForClass(clazz);
       }
       return false;
     }
+    if (methodResolutionMayChange(clazz, targetClass)) {
+      if (Log.ENABLED) {
+        AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE.printLogMessageForClass(clazz);
+      }
+      return false;
+    }
+    // Field resolution first considers the direct interfaces of [targetClass] before it proceeds
+    // to the super class.
+    if (fieldResolutionMayChange(clazz, targetClass)) {
+      if (Log.ENABLED) {
+        AbortReason.RESOLUTION_FOR_FIELDS_MAY_CHANGE.printLogMessageForClass(clazz);
+      }
+      return false;
+    }
     return true;
   }
 
@@ -337,49 +410,101 @@
     return false;
   }
 
-  private void addProgramMethods(Set<Wrapper<DexMethod>> set, DexMethod method,
-      Equivalence<DexMethod> equivalence) {
-    DexClass definition = appInfo.definitionFor(method.holder);
-    if (definition != null && definition.isProgramClass()) {
-      set.add(equivalence.wrap(method));
-    }
-  }
-
   private Collection<DexMethod> getInvokes() {
     if (invokes == null) {
-      // Collect all reachable methods that are not within a library class. Those defined on
-      // library classes are known not to have program classes in their signature.
-      // Also filter methods that only use types from library classes in their signatures. We
-      // know that those won't conflict.
-      Set<Wrapper<DexMethod>> filteredInvokes = new HashSet<>();
-      Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
-      appInfo.targetedMethods.forEach(m -> addProgramMethods(filteredInvokes, m, equivalence));
-      invokes = filteredInvokes.stream().map(Wrapper::get).filter(this::removeNonProgram)
-          .collect(Collectors.toList());
+      invokes = new OverloadedMethodSignaturesRetriever().get();
     }
     return invokes;
   }
 
-  private boolean isProgramClass(DexType type) {
-    if (type.isArrayType()) {
-      type = type.toBaseType(appInfo.dexItemFactory);
-    }
-    if (type.isClassType()) {
-      DexClass clazz = appInfo.definitionFor(type);
-      if (clazz != null && clazz.isProgramClass()) {
-        return true;
-      }
-    }
-    return false;
-  }
+  // Collects all potentially overloaded method signatures that reference at least one type that
+  // may be the source or target of a merge operation.
+  private class OverloadedMethodSignaturesRetriever {
+    private final Reference2BooleanOpenHashMap<DexProto> cache =
+        new Reference2BooleanOpenHashMap<>();
+    private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
+    private final Set<DexType> mergeeCandidates = new HashSet<>();
 
-  private boolean removeNonProgram(DexMethod dexMethod) {
-    for (DexType type : dexMethod.proto.parameters.values) {
-      if (isProgramClass(type)) {
-        return true;
+    public OverloadedMethodSignaturesRetriever() {
+      for (DexProgramClass mergeCandidate : mergeCandidates) {
+        mergeeCandidates.add(mergeCandidate.type.getSingleSubtype());
       }
     }
-    return isProgramClass(dexMethod.proto.returnType);
+
+    public Collection<DexMethod> get() {
+      Map<DexString, DexProto> overloadingInfo = new HashMap<>();
+
+      // Find all signatures that may reference a type that could be the source or target of a
+      // merge operation.
+      Set<Wrapper<DexMethod>> filteredSignatures = new HashSet<>();
+      for (DexMethod signature : appInfo.targetedMethods) {
+        DexClass definition = appInfo.definitionFor(signature.holder);
+        if (definition != null
+            && definition.isProgramClass()
+            && protoMayReferenceMergedSourceOrTarget(signature.proto)) {
+          filteredSignatures.add(equivalence.wrap(signature));
+
+          // Record that we have seen a method named [signature.name] with the proto
+          // [signature.proto]. If at some point, we find a method with the same name, but a
+          // different proto, it could be the case that a method with the given name is overloaded.
+          DexProto existing =
+              overloadingInfo.computeIfAbsent(signature.name, key -> signature.proto);
+          if (existing != DexProto.SENTINEL && !existing.equals(signature.proto)) {
+            // Mark that this signature is overloaded by mapping it to SENTINEL.
+            overloadingInfo.put(signature.name, DexProto.SENTINEL);
+          }
+        }
+      }
+
+      List<DexMethod> result = new ArrayList<>();
+      for (Wrapper<DexMethod> wrappedSignature : filteredSignatures) {
+        DexMethod signature = wrappedSignature.get();
+
+        // Ignore those method names that are definitely not overloaded since they cannot lead to
+        // any collisions.
+        if (overloadingInfo.get(signature.name) == DexProto.SENTINEL) {
+          result.add(signature);
+        }
+      }
+      return result;
+    }
+
+    private boolean protoMayReferenceMergedSourceOrTarget(DexProto proto) {
+      boolean result;
+      if (cache.containsKey(proto)) {
+        result = cache.getBoolean(proto);
+      } else {
+        result = false;
+        if (typeMayReferenceMergedSourceOrTarget(proto.returnType)) {
+          result = true;
+        } else {
+          for (DexType type : proto.parameters.values) {
+            if (typeMayReferenceMergedSourceOrTarget(type)) {
+              result = true;
+              break;
+            }
+          }
+        }
+        cache.put(proto, result);
+      }
+      return result;
+    }
+
+    private boolean typeMayReferenceMergedSourceOrTarget(DexType type) {
+      if (type.isArrayType()) {
+        type = type.toBaseType(appInfo.dexItemFactory);
+      }
+      if (type.isClassType()) {
+        if (mergeeCandidates.contains(type)) {
+          return true;
+        }
+        DexClass clazz = appInfo.definitionFor(type);
+        if (clazz != null && clazz.isProgramClass()) {
+          return mergeCandidates.contains(clazz.asProgramClass());
+        }
+      }
+      return false;
+    }
   }
 
   public GraphLense run() {
@@ -396,19 +521,21 @@
     return result;
   }
 
-  private void addAncestorsToWorklist(
+  private void addCandidateAncestorsToWorklist(
       DexProgramClass clazz, Deque<DexProgramClass> worklist, Set<DexProgramClass> seenBefore) {
     if (seenBefore.contains(clazz)) {
       return;
     }
 
-    worklist.addFirst(clazz);
+    if (mergeCandidates.contains(clazz)) {
+      worklist.addFirst(clazz);
+    }
 
     // Add super classes to worklist.
     if (clazz.superType != null) {
       DexClass definition = appInfo.definitionFor(clazz.superType);
       if (definition != null && definition.isProgramClass()) {
-        addAncestorsToWorklist(definition.asProgramClass(), worklist, seenBefore);
+        addCandidateAncestorsToWorklist(definition.asProgramClass(), worklist, seenBefore);
       }
     }
 
@@ -416,71 +543,60 @@
     for (DexType interfaceType : clazz.interfaces.values) {
       DexClass definition = appInfo.definitionFor(interfaceType);
       if (definition != null && definition.isProgramClass()) {
-        addAncestorsToWorklist(definition.asProgramClass(), worklist, seenBefore);
+        addCandidateAncestorsToWorklist(definition.asProgramClass(), worklist, seenBefore);
       }
     }
   }
 
   private GraphLense mergeClasses(GraphLense graphLense) {
-    Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder();
     Deque<DexProgramClass> worklist = new ArrayDeque<>();
     Set<DexProgramClass> seenBefore = new HashSet<>();
 
     int numberOfMerges = 0;
 
-    // Types that are pinned (in addition to those where appInfo.isPinned returns true).
-    Set<DexType> pinnedTypes = getPinnedTypes(classes);
-
-    Iterator<DexProgramClass> classIterator = classes.iterator();
+    Iterator<DexProgramClass> candidatesIterator = mergeCandidates.iterator();
 
     // Visit the program classes in a top-down order according to the class hierarchy.
-    while (classIterator.hasNext() || !worklist.isEmpty()) {
+    while (candidatesIterator.hasNext() || !worklist.isEmpty()) {
       if (worklist.isEmpty()) {
         // Add the ancestors of this class (including the class itself) to the worklist in such a
         // way that all super types of the class come before the class itself.
-        addAncestorsToWorklist(classIterator.next(), worklist, seenBefore);
+        addCandidateAncestorsToWorklist(candidatesIterator.next(), worklist, seenBefore);
         if (worklist.isEmpty()) {
           continue;
         }
       }
 
       DexProgramClass clazz = worklist.removeFirst();
-      if (!seenBefore.add(clazz) || !isMergeCandidate(clazz, pinnedTypes)) {
+      assert isMergeCandidate(clazz, pinnedTypes);
+      if (!seenBefore.add(clazz)) {
         continue;
       }
 
-      DexClass targetClass = appInfo.definitionFor(clazz.type.getSingleSubtype());
+      DexProgramClass targetClass =
+          appInfo.definitionFor(clazz.type.getSingleSubtype()).asProgramClass();
       assert !mergedClasses.containsKey(targetClass.type);
-      if (clazz.hasClassInitializer() && targetClass.hasClassInitializer()) {
-        // TODO(herhut): Handle class initializers.
-        if (Log.ENABLED) {
-          AbortReason.STATIC_INITIALIZERS.printLogMessageForClass(clazz);
+
+      boolean clazzOrTargetClassHasBeenMerged =
+          mergedClassesInverse.containsKey(clazz.type)
+              || mergedClassesInverse.containsKey(targetClass.type);
+      if (clazzOrTargetClassHasBeenMerged) {
+        if (!isStillMergeCandidate(clazz)) {
+          continue;
         }
-        continue;
+      } else {
+        assert isStillMergeCandidate(clazz);
       }
-      if (methodResolutionMayChange(clazz, targetClass)) {
-        if (Log.ENABLED) {
-          AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE.printLogMessageForClass(clazz);
-        }
-        continue;
-      }
-      // Field resolution first considers the direct interfaces of [targetClass] before it proceeds
-      // to the super class.
-      if (fieldResolutionMayChange(clazz, targetClass)) {
-        if (Log.ENABLED) {
-          AbortReason.RESOLUTION_FOR_FIELDS_MAY_CHANGE.printLogMessageForClass(clazz);
-        }
-        continue;
-      }
+
       // Guard against the case where we have two methods that may get the same signature
       // if we replace types. This is rare, so we approximate and err on the safe side here.
-      if (new CollisionDetector(clazz.type, targetClass.type, getInvokes(), mergedClasses)
-          .mayCollide()) {
+      if (new CollisionDetector(clazz.type, targetClass.type).mayCollide()) {
         if (Log.ENABLED) {
           AbortReason.CONFLICT.printLogMessageForClass(clazz);
         }
         continue;
       }
+
       ClassMerger merger = new ClassMerger(clazz, targetClass);
       boolean merged = merger.merge();
       if (merged) {
@@ -598,14 +714,6 @@
     }
 
     public boolean merge() {
-      if (source.getEnclosingMethod() != null || !source.getInnerClasses().isEmpty()
-          || target.getEnclosingMethod() != null || !target.getInnerClasses().isEmpty()) {
-        // TODO(herhut): Consider supporting merging of inner-class attributes.
-        if (Log.ENABLED) {
-          AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(source);
-        }
-        return false;
-      }
       // Merge the class [clazz] into [targetClass] by adding all methods to
       // targetClass that are not currently contained.
       // Step 1: Merge methods
@@ -697,34 +805,46 @@
         deferredRenamings.map(virtualMethod.method, shadowedBy.method);
       }
 
-      Collection<DexEncodedMethod> mergedDirectMethods =
-          mergeItems(
-              directMethods.values().iterator(),
-              target.directMethods(),
-              MethodSignatureEquivalence.get(),
-              this::resolveMethodConflict);
-      Collection<DexEncodedMethod> mergedVirtualMethods =
-          mergeItems(
-              virtualMethods.values().iterator(),
-              target.virtualMethods(),
-              MethodSignatureEquivalence.get(),
-              this::resolveMethodConflict);
       if (abortMerge) {
         return false;
       }
+
+      DexEncodedMethod[] mergedDirectMethods =
+          mergeMethods(directMethods.values(), target.directMethods());
+      DexEncodedMethod[] mergedVirtualMethods =
+          mergeMethods(virtualMethods.values(), target.virtualMethods());
+
       // Step 2: Merge fields
-      Collection<DexEncodedField> mergedStaticFields =
-          mergeItems(
-              Iterators.forArray(source.staticFields()),
-              target.staticFields(),
-              FieldSignatureEquivalence.get(),
-              this::resolveFieldConflict);
-      Collection<DexEncodedField> mergedInstanceFields =
-          mergeItems(
-              Iterators.forArray(source.instanceFields()),
+      Set<DexString> existingFieldNames = new HashSet<>();
+      for (DexEncodedField field : target.fields()) {
+        existingFieldNames.add(field.field.name);
+      }
+
+      // In principle, we could allow multiple fields with the same name, and then only rename the
+      // field in the end when we are done merging all the classes, if it it turns out that the two
+      // fields ended up having the same type. This would not be too expensive, since we visit the
+      // entire program using VerticalClassMerger.TreeFixer anyway.
+      //
+      // For now, we conservatively report that a signature is already taken if there is a field
+      // with the same name. If minification is used with -overloadaggressively, this is solved
+      // later anyway.
+      Predicate<DexField> availableFieldSignatures =
+          field -> !existingFieldNames.contains(field.name);
+
+      DexEncodedField[] mergedInstanceFields =
+          mergeFields(
+              source.instanceFields(),
               target.instanceFields(),
-              FieldSignatureEquivalence.get(),
-              this::resolveFieldConflict);
+              availableFieldSignatures,
+              existingFieldNames);
+
+      DexEncodedField[] mergedStaticFields =
+          mergeFields(
+              source.staticFields(),
+              target.staticFields(),
+              availableFieldSignatures,
+              existingFieldNames);
+
       // Step 3: Merge interfaces
       Set<DexType> interfaces = mergeArrays(target.interfaces.values, source.interfaces.values);
       // Now destructively update the class.
@@ -739,14 +859,10 @@
           ? DexTypeList.empty()
           : new DexTypeList(interfaces.toArray(new DexType[interfaces.size()]));
       // Step 2: replace fields and methods.
-      target.setDirectMethods(mergedDirectMethods
-          .toArray(new DexEncodedMethod[mergedDirectMethods.size()]));
-      target.setVirtualMethods(mergedVirtualMethods
-          .toArray(new DexEncodedMethod[mergedVirtualMethods.size()]));
-      target.setStaticFields(mergedStaticFields
-          .toArray(new DexEncodedField[mergedStaticFields.size()]));
-      target.setInstanceFields(mergedInstanceFields
-          .toArray(new DexEncodedField[mergedInstanceFields.size()]));
+      target.setDirectMethods(mergedDirectMethods);
+      target.setVirtualMethods(mergedVirtualMethods);
+      target.setInstanceFields(mergedInstanceFields);
+      target.setStaticFields(mergedStaticFields);
       // Step 3: Unlink old class to ease tree shaking.
       source.superType = application.dexItemFactory.objectType;
       source.setDirectMethods(null);
@@ -834,15 +950,15 @@
     }
 
     private DexEncodedMethod buildBridgeMethod(
-        DexEncodedMethod signature, DexMethod invocationTarget) {
+        DexEncodedMethod method, DexMethod invocationTarget) {
       DexType holder = target.type;
       DexProto proto = invocationTarget.proto;
-      DexString name = signature.method.name;
-      MethodAccessFlags accessFlags = signature.accessFlags.copy();
+      DexString name = method.method.name;
+      MethodAccessFlags accessFlags = method.accessFlags.copy();
       accessFlags.setBridge();
       accessFlags.setSynthetic();
       accessFlags.unsetAbstract();
-      return new DexEncodedMethod(
+      DexEncodedMethod bridge = new DexEncodedMethod(
           application.dexItemFactory.createMethod(holder, proto, name),
           accessFlags,
           DexAnnotationSet.empty(),
@@ -850,7 +966,14 @@
           new SynthesizedCode(
               new ForwardMethodSourceCode(holder, proto, holder, invocationTarget, Type.DIRECT),
               registry -> registry.registerInvokeDirect(invocationTarget)),
-          signature.hasClassFileVersion() ? signature.getClassFileVersion() : -1);
+          method.hasClassFileVersion() ? method.getClassFileVersion() : -1);
+      if (method.getOptimizationInfo().isPublicized()) {
+        // The bridge is now the public method serving the role of the original method, and should
+        // reflect that this method was publicized.
+        bridge.markPublicized();
+        method.unsetPublicized();
+      }
+      return bridge;
     }
 
     // Returns the method that shadows the given method, or null if method is not shadowed.
@@ -889,41 +1012,38 @@
       return merged;
     }
 
-    private <T extends PresortedComparable<T>, S extends KeyedDexItem<T>> Collection<S> mergeItems(
-        Iterator<S> fromItems,
-        S[] toItems,
-        Equivalence<T> equivalence,
-        BiFunction<S, Predicate<T>, S> onConflict) {
-      Map<Wrapper<T>, S> map = new HashMap<>();
-      // First add everything from the target class. These items are not preprocessed.
-      for (S item : toItems) {
-        map.put(equivalence.wrap(item.getKey()), item);
+    private DexEncodedField[] mergeFields(
+        DexEncodedField[] sourceFields,
+        DexEncodedField[] targetFields,
+        Predicate<DexField> availableFieldSignatures,
+        Set<DexString> existingFieldNames) {
+      DexEncodedField[] result = new DexEncodedField[sourceFields.length + targetFields.length];
+      // Add fields from source
+      int i = 0;
+      for (DexEncodedField field : sourceFields) {
+        DexEncodedField resultingField = renameFieldIfNeeded(field, availableFieldSignatures);
+        existingFieldNames.add(resultingField.field.name);
+        deferredRenamings.map(field.field, resultingField.field);
+        result[i] = resultingField;
+        i++;
       }
-      // Now add the new items, resolving shadowing.
-      addNonShadowed(fromItems, map, equivalence, onConflict);
-      return map.values();
+      // Add fields from target.
+      System.arraycopy(targetFields, 0, result, i, targetFields.length);
+      return result;
     }
 
-    private <T extends PresortedComparable<T>, S extends KeyedDexItem<T>> void addNonShadowed(
-        Iterator<S> items,
-        Map<Wrapper<T>, S> map,
-        Equivalence<T> equivalence,
-        BiFunction<S, Predicate<T>, S> onConflict) {
-      Predicate<T> availableSignatures = key -> !map.containsKey(equivalence.wrap(key));
-      while (items.hasNext()) {
-        S item = items.next();
-        assert item != null;
-        Wrapper<T> wrapped = equivalence.wrap(item.getKey());
-        if (map.containsKey(wrapped)) {
-          S resolved = onConflict.apply(item, availableSignatures);
-          assert availableSignatures.test(resolved.getKey());
-          wrapped = equivalence.wrap(resolved.getKey());
-          map.put(wrapped, resolved);
-          assert !availableSignatures.test(resolved.getKey());
-        } else {
-          map.put(wrapped, item);
-        }
+    private DexEncodedMethod[] mergeMethods(
+        Collection<DexEncodedMethod> sourceMethods, DexEncodedMethod[] targetMethods) {
+      DexEncodedMethod[] result = new DexEncodedMethod[sourceMethods.size() + targetMethods.length];
+      // Add methods from source.
+      int i = 0;
+      for (DexEncodedMethod method : sourceMethods) {
+        result[i] = method;
+        i++;
       }
+      // Add methods from target.
+      System.arraycopy(targetMethods, 0, result, i, targetMethods.length);
+      return result;
     }
 
     // Note that names returned by this function are not necessarily unique. Clients should
@@ -936,13 +1056,6 @@
       return application.dexItemFactory.createString(freshName);
     }
 
-    private DexEncodedMethod resolveMethodConflict(
-        DexEncodedMethod method, Predicate<DexMethod> availableMethodSignatures) {
-      assert false;
-      abortMerge = true;
-      return method;
-    }
-
     private DexEncodedMethod renameConstructor(
         DexEncodedMethod method, Predicate<DexMethod> availableMethodSignatures) {
       assert method.isInstanceInitializer();
@@ -1008,23 +1121,24 @@
       return method.toTypeSubstitutedMethod(newSignature);
     }
 
-    private DexEncodedField resolveFieldConflict(
+    private DexEncodedField renameFieldIfNeeded(
         DexEncodedField field, Predicate<DexField> availableFieldSignatures) {
       DexString oldName = field.field.name;
       DexType oldHolder = field.field.clazz;
 
-      DexField newSignature;
-      int count = 1;
-      do {
-        DexString newName = getFreshName(oldName.toSourceString(), count, oldHolder);
-        newSignature =
-            application.dexItemFactory.createField(target.type, field.field.type, newName);
-        count++;
-      } while (!availableFieldSignatures.test(newSignature));
+      DexField newSignature =
+          application.dexItemFactory.createField(target.type, field.field.type, oldName);
+      if (!availableFieldSignatures.test(newSignature)) {
+        int count = 1;
+        do {
+          DexString newName = getFreshName(oldName.toSourceString(), count, oldHolder);
+          newSignature =
+              application.dexItemFactory.createField(target.type, field.field.type, newName);
+          count++;
+        } while (!availableFieldSignatures.test(newSignature));
+      }
 
-      DexEncodedField result = field.toTypeSubstitutedField(newSignature);
-      deferredRenamings.map(field.field, result.field);
-      return result;
+      return field.toTypeSubstitutedField(newSignature);
     }
   }
 
@@ -1156,22 +1270,18 @@
 
   private class CollisionDetector {
 
-    private static final int NOT_FOUND = 1 << (Integer.SIZE - 1);
+    private static final int NOT_FOUND = Integer.MIN_VALUE;
 
     // TODO(herhut): Maybe cache seenPositions for target classes.
     private final Map<DexString, Int2IntMap> seenPositions = new IdentityHashMap<>();
     private final Reference2IntMap<DexProto> targetProtoCache;
     private final Reference2IntMap<DexProto> sourceProtoCache;
     private final DexType source, target;
-    private final Collection<DexMethod> invokes;
-    private final Map<DexType, DexType> substituions;
+    private final Collection<DexMethod> invokes = getInvokes();
 
-    private CollisionDetector(DexType source, DexType target, Collection<DexMethod> invokes,
-        Map<DexType, DexType> substitutions) {
+    private CollisionDetector(DexType source, DexType target) {
       this.source = source;
       this.target = target;
-      this.invokes = invokes;
-      this.substituions = substitutions;
       this.targetProtoCache = new Reference2IntOpenHashMap<>(invokes.size() / 2);
       this.targetProtoCache.defaultReturnValue(NOT_FOUND);
       this.sourceProtoCache = new Reference2IntOpenHashMap<>(invokes.size() / 2);
@@ -1180,7 +1290,7 @@
 
     boolean mayCollide() {
       timing.begin("collision detection");
-      fillSeenPositions(invokes);
+      fillSeenPositions();
       boolean result = false;
       // If the type is not used in methods at all, there cannot be any conflict.
       if (!seenPositions.isEmpty()) {
@@ -1191,8 +1301,7 @@
             int previous = positionsMap.get(arity);
             if (previous != NOT_FOUND) {
               assert previous != 0;
-              int positions =
-                  computePositionsFor(method.proto, source, sourceProtoCache, substituions);
+              int positions = computePositionsFor(method.proto, source, sourceProtoCache);
               if ((positions & previous) != 0) {
                 result = true;
                 break;
@@ -1205,11 +1314,11 @@
       return result;
     }
 
-    private void fillSeenPositions(Collection<DexMethod> invokes) {
+    private void fillSeenPositions() {
       for (DexMethod method : invokes) {
         DexType[] parameters = method.proto.parameters.values;
         int arity = parameters.length;
-        int positions = computePositionsFor(method.proto, target, targetProtoCache, substituions);
+        int positions = computePositionsFor(method.proto, target, targetProtoCache);
         if (positions != 0) {
           Int2IntMap positionsMap =
               seenPositions.computeIfAbsent(method.name, k -> {
@@ -1229,8 +1338,10 @@
 
     }
 
-    private int computePositionsFor(DexProto proto, DexType type,
-        Reference2IntMap<DexProto> cache, Map<DexType, DexType> substitutions) {
+    // Given a method signature and a type, this method computes a bit vector that denotes the
+    // positions at which the given type is used in the method signature.
+    private int computePositionsFor(
+        DexProto proto, DexType type, Reference2IntMap<DexProto> cache) {
       int result = cache.getInt(proto);
       if (result != NOT_FOUND) {
         return result;
@@ -1239,13 +1350,8 @@
       int bitsUsed = 0;
       int accumulator = 0;
       for (DexType aType : proto.parameters.values) {
-        if (substitutions != null) {
-          // Substitute the type with the already merged class to estimate what it will
-          // look like.
-          while (substitutions.containsKey(aType)) {
-            aType = substitutions.get(aType);
-          }
-        }
+        // Substitute the type with the already merged class to estimate what it will look like.
+        aType = mergedClasses.getOrDefault(aType, aType);
         accumulator <<= 1;
         bitsUsed++;
         if (aType == type) {
@@ -1259,12 +1365,7 @@
         }
       }
       // We also take the return type into account for potential conflicts.
-      DexType returnType = proto.returnType;
-      if (substitutions != null) {
-        while (substitutions.containsKey(returnType)) {
-          returnType = substitutions.get(returnType);
-        }
-      }
+      DexType returnType = mergedClasses.getOrDefault(proto.returnType, proto.returnType);
       accumulator <<= 1;
       if (returnType == type) {
         accumulator |= 1;
@@ -1275,84 +1376,73 @@
     }
   }
 
-  private static boolean disallowInlining(DexEncodedMethod method) {
+  private boolean disallowInlining(DexEncodedMethod method, DexType invocationContext) {
     // TODO(christofferqa): Determine the situations where markForceInline() may fail, and ensure
     // that we always return true here in these cases.
-    MethodInlineDecision registry = new MethodInlineDecision();
-    method.getCode().registerCodeReferences(registry);
-    return registry.isInliningDisallowed();
+    if (method.getCode().isJarCode()) {
+      JarCode jarCode = method.getCode().asJarCode();
+      Constraint constraint =
+          jarCode.computeInliningConstraint(
+              method,
+              appInfo,
+              new SingleTypeMapperGraphLense(method.method.holder, invocationContext),
+              invocationContext);
+      return constraint == Constraint.NEVER;
+    }
+    // TODO(christofferqa): For non-jar code we currently cannot guarantee that markForceInline()
+    // will succeed.
+    return true;
   }
 
-  private static class MethodInlineDecision extends UseRegistry {
-    private boolean disallowInlining = false;
+  private class SingleTypeMapperGraphLense extends GraphLense {
 
-    public boolean isInliningDisallowed() {
-      return disallowInlining;
+    private final DexType source;
+    private final DexType target;
+
+    public SingleTypeMapperGraphLense(DexType source, DexType target) {
+      this.source = source;
+      this.target = target;
     }
 
-    private boolean allowInlining() {
+    @Override
+    public DexType lookupType(DexType type) {
+      return type == source ? target : mergedClasses.getOrDefault(type, type);
+    }
+
+    @Override
+    public GraphLenseLookupResult lookupMethod(
+        DexMethod method, DexEncodedMethod context, Type type) {
+      // First look up the method using the existing graph lense (for example, the type will have
+      // changed if the method was publicized by ClassAndMemberPublicizer).
+      GraphLenseLookupResult lookup = graphLense.lookupMethod(method, context, type);
+      DexMethod previousMethod = lookup.getMethod();
+      Type previousType = lookup.getType();
+      // Then check if there is a renaming due to the vertical class merger.
+      DexMethod newMethod = renamedMembersLense.methodMap.get(previousMethod);
+      if (newMethod != null) {
+        if (previousType == Type.INTERFACE) {
+          // If an interface has been merged into a class, invoke-interface needs to be translated
+          // to invoke-virtual.
+          DexClass clazz = appInfo.definitionFor(newMethod.holder);
+          if (clazz != null && !clazz.accessFlags.isInterface()) {
+            assert appInfo.definitionFor(method.holder).accessFlags.isInterface();
+            return new GraphLenseLookupResult(newMethod, Type.VIRTUAL);
+          }
+        }
+        return new GraphLenseLookupResult(newMethod, previousType);
+      }
+      return new GraphLenseLookupResult(previousMethod, previousType);
+    }
+
+    @Override
+    public DexField lookupField(DexField field) {
+      return renamedMembersLense.fieldMap.getOrDefault(field, field);
+    }
+
+    @Override
+    public boolean isContextFreeForMethods() {
       return true;
     }
-
-    private boolean disallowInlining() {
-      disallowInlining = true;
-      return true;
-    }
-
-    @Override
-    public boolean registerInvokeInterface(DexMethod method) {
-      return disallowInlining();
-    }
-
-    @Override
-    public boolean registerInvokeVirtual(DexMethod method) {
-      return disallowInlining();
-    }
-
-    @Override
-    public boolean registerInvokeDirect(DexMethod method) {
-      return allowInlining();
-    }
-
-    @Override
-    public boolean registerInvokeStatic(DexMethod method) {
-      return allowInlining();
-    }
-
-    @Override
-    public boolean registerInvokeSuper(DexMethod method) {
-      return allowInlining();
-    }
-
-    @Override
-    public boolean registerInstanceFieldWrite(DexField field) {
-      return allowInlining();
-    }
-
-    @Override
-    public boolean registerInstanceFieldRead(DexField field) {
-      return allowInlining();
-    }
-
-    @Override
-    public boolean registerNewInstance(DexType type) {
-      return allowInlining();
-    }
-
-    @Override
-    public boolean registerStaticFieldRead(DexField field) {
-      return allowInlining();
-    }
-
-    @Override
-    public boolean registerStaticFieldWrite(DexField field) {
-      return allowInlining();
-    }
-
-    @Override
-    public boolean registerTypeReference(DexType type) {
-      return allowInlining();
-    }
   }
 
   // Searches for a reference to a non-public class, field or method declared in the same package
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 4f614b4..fa10215 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -136,9 +136,8 @@
   public static class Builder {
     private final AppInfo appInfo;
 
-    private final ImmutableMap.Builder<DexField, DexField> fieldMapBuilder = ImmutableMap.builder();
-    private final ImmutableMap.Builder<DexMethod, DexMethod> methodMapBuilder =
-        ImmutableMap.builder();
+    protected final Map<DexField, DexField> fieldMap = new HashMap<>();
+    protected final Map<DexMethod, DexMethod> methodMap = new HashMap<>();
     private final ImmutableSet.Builder<DexMethod> mergedMethodsBuilder = ImmutableSet.builder();
     private final Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps =
         new HashMap<>();
@@ -151,8 +150,6 @@
         GraphLense previousLense,
         Map<DexType, DexType> mergedClasses,
         DexItemFactory dexItemFactory) {
-      Map<DexField, DexField> fieldMap = fieldMapBuilder.build();
-      Map<DexMethod, DexMethod> methodMap = methodMapBuilder.build();
       if (fieldMap.isEmpty()
           && methodMap.isEmpty()
           && contextualVirtualToDirectMethodMaps.isEmpty()) {
@@ -205,11 +202,11 @@
     }
 
     public void map(DexField from, DexField to) {
-      fieldMapBuilder.put(from, to);
+      fieldMap.put(from, to);
     }
 
     public void map(DexMethod from, DexMethod to) {
-      methodMapBuilder.put(from, to);
+      methodMap.put(from, to);
     }
 
     public void mapVirtualMethodToDirectInType(DexMethod from, DexMethod to, DexType type) {
@@ -219,8 +216,8 @@
     }
 
     public void merge(VerticalClassMergerGraphLense.Builder builder) {
-      fieldMapBuilder.putAll(builder.fieldMapBuilder.build());
-      methodMapBuilder.putAll(builder.methodMapBuilder.build());
+      fieldMap.putAll(builder.fieldMap);
+      methodMap.putAll(builder.methodMap);
       mergedMethodsBuilder.addAll(builder.mergedMethodsBuilder.build());
       for (DexType context : builder.contextualVirtualToDirectMethodMaps.keySet()) {
         Map<DexMethod, DexMethod> current = contextualVirtualToDirectMethodMaps.get(context);
diff --git a/src/test/examples/classmerging/ClassWithNativeMethodTest.java b/src/test/examples/classmerging/ClassWithNativeMethodTest.java
new file mode 100644
index 0000000..b28433d
--- /dev/null
+++ b/src/test/examples/classmerging/ClassWithNativeMethodTest.java
@@ -0,0 +1,22 @@
+// 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 classmerging;
+
+public class ClassWithNativeMethodTest {
+
+  public static void main(String[] args) {
+    B obj = new B();
+
+    // Make sure that A.method is not removed by tree shaking.
+    if (args.length == 42) {
+      obj.method();
+    }
+  }
+
+  public static class A {
+    public native void method();
+  }
+
+  public static class B extends A {}
+}
diff --git a/src/test/examples/classmerging/FieldCollisionTest.java b/src/test/examples/classmerging/FieldCollisionTest.java
new file mode 100644
index 0000000..5bb2260
--- /dev/null
+++ b/src/test/examples/classmerging/FieldCollisionTest.java
@@ -0,0 +1,43 @@
+// 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 classmerging;
+
+public class FieldCollisionTest {
+
+  private static final B SENTINEL_A = new B("A");
+  private static final B SENTINEL_B = new B("B");
+
+  public static void main(String[] args) {
+    B obj = new B();
+    System.out.println(obj.toString());
+  }
+
+  // Will be merged into B.
+  public static class A {
+
+    // After class merging, this field will have the same name and type as the field B.obj,
+    // unless we handle the collision.
+    protected final A obj = SENTINEL_A;
+  }
+
+  public static class B extends A {
+
+    protected final String message;
+    protected final B obj = SENTINEL_B;
+
+    public B() {
+      this(null);
+    }
+
+    public B(String message) {
+      this.message = message;
+    }
+
+    @Override
+    public String toString() {
+      return obj.message + System.lineSeparator() + ((B) super.obj).message;
+    }
+  }
+}
diff --git a/src/test/examples/classmerging/MethodCollisionTest.java b/src/test/examples/classmerging/MethodCollisionTest.java
new file mode 100644
index 0000000..a9010a4
--- /dev/null
+++ b/src/test/examples/classmerging/MethodCollisionTest.java
@@ -0,0 +1,57 @@
+// 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 classmerging;
+
+public class MethodCollisionTest {
+
+  public static void main(String[] args) {
+    new B().m();
+    new D().m();
+  }
+
+  public static class A {
+
+    // After class merging, this method will have the same signature as the method B.m,
+    // unless we handle the collision.
+    private A m() {
+      System.out.println("A.m");
+      return null;
+    }
+
+    public void invokeM() {
+      m();
+    }
+  }
+
+  public static class B extends A {
+
+    private B m() {
+      System.out.println("B.m");
+      invokeM();
+      return null;
+    }
+  }
+
+  public static class C {
+
+    // After class merging, this method will have the same signature as the method D.m,
+    // unless we handle the collision.
+    public C m() {
+      System.out.println("C.m");
+      return null;
+    }
+  }
+
+  public static class D extends C {
+
+    public D m() {
+      System.out.println("D.m");
+      super.m();
+      return null;
+    }
+  }
+
+
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 516e39d..4ba014e 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -7,6 +7,9 @@
 -keep public class classmerging.Test {
   public static void main(...);
 }
+-keep public class classmerging.ClassWithNativeMethodTest {
+  public static void main(...);
+}
 -keep public class classmerging.ConflictInGeneratedNameTest {
   public static void main(...);
 }
@@ -16,6 +19,12 @@
 -keep public class classmerging.ExceptionTest {
   public static void main(...);
 }
+-keep public class classmerging.FieldCollisionTest {
+  public static void main(...);
+}
+-keep public class classmerging.MethodCollisionTest {
+  public static void main(...);
+}
 -keep public class classmerging.RewritePinnedMethodTest {
   public static void main(...);
 }
diff --git a/src/test/examples/regress_110373181/Regress.java b/src/test/examples/regress_110373181/Regress.java
new file mode 100644
index 0000000..ae0d5bc
--- /dev/null
+++ b/src/test/examples/regress_110373181/Regress.java
@@ -0,0 +1,1234 @@
+// 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 regress_110373181;
+
+public class Regress {
+  public static class Inner {
+    private int i0 = 0;
+    private int i1 = 1;
+    private int i2 = 2;
+    private int i3 = 3;
+    private int i4 = 4;
+    private int i5 = 5;
+    private int i6 = 6;
+    private int i7 = 7;
+    private int i8 = 8;
+    private int i9 = 9;
+    private int i10 = 10;
+    private int i11 = 11;
+    private int i12 = 12;
+    private int i13 = 13;
+    private int i14 = 14;
+    private int i15 = 15;
+    private int i16 = 16;
+    private int i17 = 17;
+    private int i18 = 18;
+    private int i19 = 19;
+    private int i20 = 20;
+    private int i21 = 21;
+    private int i22 = 22;
+    private int i23 = 23;
+    private int i24 = 24;
+    private int i25 = 25;
+    private int i26 = 26;
+    private int i27 = 27;
+    private int i28 = 28;
+    private int i29 = 29;
+    private int i30 = 30;
+    private int i31 = 31;
+    private int i32 = 32;
+    private int i33 = 33;
+    private int i34 = 34;
+    private int i35 = 35;
+    private int i36 = 36;
+    private int i37 = 37;
+    private int i38 = 38;
+    private int i39 = 39;
+    private int i40 = 40;
+    private int i41 = 41;
+    private int i42 = 42;
+    private int i43 = 43;
+    private int i44 = 44;
+    private int i45 = 45;
+    private int i46 = 46;
+    private int i47 = 47;
+    private int i48 = 48;
+    private int i49 = 49;
+    private int i50 = 50;
+    private int i51 = 51;
+    private int i52 = 52;
+    private int i53 = 53;
+    private int i54 = 54;
+    private int i55 = 55;
+    private int i56 = 56;
+    private int i57 = 57;
+    private int i58 = 58;
+    private int i59 = 59;
+    private int i60 = 60;
+    private int i61 = 61;
+    private int i62 = 62;
+    private int i63 = 63;
+    private int i64 = 64;
+    private int i65 = 65;
+    private int i66 = 66;
+    private int i67 = 67;
+    private int i68 = 68;
+    private int i69 = 69;
+    private int i70 = 70;
+    private int i71 = 71;
+    private int i72 = 72;
+    private int i73 = 73;
+    private int i74 = 74;
+    private int i75 = 75;
+    private int i76 = 76;
+    private int i77 = 77;
+    private int i78 = 78;
+    private int i79 = 79;
+    private int i80 = 80;
+    private int i81 = 81;
+    private int i82 = 82;
+    private int i83 = 83;
+    private int i84 = 84;
+    private int i85 = 85;
+    private int i86 = 86;
+    private int i87 = 87;
+    private int i88 = 88;
+    private int i89 = 89;
+    private int i90 = 90;
+    private int i91 = 91;
+    private int i92 = 92;
+    private int i93 = 93;
+    private int i94 = 94;
+    private int i95 = 95;
+    private int i96 = 96;
+    private int i97 = 97;
+    private int i98 = 98;
+    private int i99 = 99;
+    private int i100 = 100;
+    private int i101 = 101;
+    private int i102 = 102;
+    private int i103 = 103;
+    private int i104 = 104;
+    private int i105 = 105;
+    private int i106 = 106;
+    private int i107 = 107;
+    private int i108 = 108;
+    private int i109 = 109;
+    private int i110 = 110;
+    private int i111 = 111;
+    private int i112 = 112;
+    private int i113 = 113;
+    private int i114 = 114;
+    private int i115 = 115;
+    private int i116 = 116;
+    private int i117 = 117;
+    private int i118 = 118;
+    private int i119 = 119;
+    private int i120 = 120;
+    private int i121 = 121;
+    private int i122 = 122;
+    private int i123 = 123;
+    private int i124 = 124;
+    private int i125 = 125;
+    private int i126 = 126;
+    private int i127 = 127;
+    private int i128 = 128;
+    private int i129 = 129;
+    private int i130 = 130;
+    private int i131 = 131;
+    private int i132 = 132;
+    private int i133 = 133;
+    private int i134 = 134;
+    private int i135 = 135;
+    private int i136 = 136;
+    private int i137 = 137;
+    private int i138 = 138;
+    private int i139 = 139;
+    private int i140 = 140;
+    private int i141 = 141;
+    private int i142 = 142;
+    private int i143 = 143;
+    private int i144 = 144;
+    private int i145 = 145;
+    private int i146 = 146;
+    private int i147 = 147;
+    private int i148 = 148;
+    private int i149 = 149;
+    private int i150 = 150;
+    private int i151 = 151;
+    private int i152 = 152;
+    private int i153 = 153;
+    private int i154 = 154;
+    private int i155 = 155;
+    private int i156 = 156;
+    private int i157 = 157;
+    private int i158 = 158;
+    private int i159 = 159;
+    private int i160 = 160;
+    private int i161 = 161;
+    private int i162 = 162;
+    private int i163 = 163;
+    private int i164 = 164;
+    private int i165 = 165;
+    private int i166 = 166;
+    private int i167 = 167;
+    private int i168 = 168;
+    private int i169 = 169;
+    private int i170 = 170;
+    private int i171 = 171;
+    private int i172 = 172;
+    private int i173 = 173;
+    private int i174 = 174;
+    private int i175 = 175;
+    private int i176 = 176;
+    private int i177 = 177;
+    private int i178 = 178;
+    private int i179 = 179;
+    private int i180 = 180;
+    private int i181 = 181;
+    private int i182 = 182;
+    private int i183 = 183;
+    private int i184 = 184;
+    private int i185 = 185;
+    private int i186 = 186;
+    private int i187 = 187;
+    private int i188 = 188;
+    private int i189 = 189;
+    private int i190 = 190;
+    private int i191 = 191;
+    private int i192 = 192;
+    private int i193 = 193;
+    private int i194 = 194;
+    private int i195 = 195;
+    private int i196 = 196;
+    private int i197 = 197;
+    private int i198 = 198;
+    private int i199 = 199;
+
+    private String s0 = "s0";
+    private String s1 = "s1";
+    private String s2 = "s2";
+    private String s3 = "s3";
+    private String s4 = "s4";
+    private String s5 = "s5";
+    private String s6 = "s6";
+    private String s7 = "s7";
+    private String s8 = "s8";
+    private String s9 = "s9";
+    private String s10 = "s10";
+    private String s11 = "s11";
+    private String s12 = "s12";
+    private String s13 = "s13";
+    private String s14 = "s14";
+    private String s15 = "s15";
+    private String s16 = "s16";
+    private String s17 = "s17";
+    private String s18 = "s18";
+    private String s19 = "s19";
+    private String s20 = "s20";
+    private String s21 = "s21";
+    private String s22 = "s22";
+    private String s23 = "s23";
+    private String s24 = "s24";
+    private String s25 = "s25";
+    private String s26 = "s26";
+    private String s27 = "s27";
+    private String s28 = "s28";
+    private String s29 = "s29";
+    private String s30 = "s30";
+    private String s31 = "s31";
+    private String s32 = "s32";
+    private String s33 = "s33";
+    private String s34 = "s34";
+    private String s35 = "s35";
+    private String s36 = "s36";
+    private String s37 = "s37";
+    private String s38 = "s38";
+    private String s39 = "s39";
+    private String s40 = "s40";
+    private String s41 = "s41";
+    private String s42 = "s42";
+    private String s43 = "s43";
+    private String s44 = "s44";
+    private String s45 = "s45";
+    private String s46 = "s46";
+    private String s47 = "s47";
+    private String s48 = "s48";
+    private String s49 = "s49";
+    private String s50 = "s50";
+    private String s51 = "s51";
+    private String s52 = "s52";
+    private String s53 = "s53";
+    private String s54 = "s54";
+    private String s55 = "s55";
+    private String s56 = "s56";
+    private String s57 = "s57";
+    private String s58 = "s58";
+    private String s59 = "s59";
+    private String s60 = "s60";
+    private String s61 = "s61";
+    private String s62 = "s62";
+    private String s63 = "s63";
+    private String s64 = "s64";
+    private String s65 = "s65";
+    private String s66 = "s66";
+    private String s67 = "s67";
+    private String s68 = "s68";
+    private String s69 = "s69";
+    private String s70 = "s70";
+    private String s71 = "s71";
+    private String s72 = "s72";
+    private String s73 = "s73";
+    private String s74 = "s74";
+    private String s75 = "s75";
+    private String s76 = "s76";
+    private String s77 = "s77";
+    private String s78 = "s78";
+    private String s79 = "s79";
+    private String s80 = "s80";
+    private String s81 = "s81";
+    private String s82 = "s82";
+    private String s83 = "s83";
+    private String s84 = "s84";
+    private String s85 = "s85";
+    private String s86 = "s86";
+    private String s87 = "s87";
+    private String s88 = "s88";
+    private String s89 = "s89";
+    private String s90 = "s90";
+    private String s91 = "s91";
+    private String s92 = "s92";
+    private String s93 = "s93";
+    private String s94 = "s94";
+    private String s95 = "s95";
+    private String s96 = "s96";
+    private String s97 = "s97";
+    private String s98 = "s98";
+    private String s99 = "s99";
+    private String s100 = "s100";
+    private String s101 = "s101";
+    private String s102 = "s102";
+    private String s103 = "s103";
+    private String s104 = "s104";
+    private String s105 = "s105";
+    private String s106 = "s106";
+    private String s107 = "s107";
+    private String s108 = "s108";
+    private String s109 = "s109";
+    private String s110 = "s110";
+    private String s111 = "s111";
+    private String s112 = "s112";
+    private String s113 = "s113";
+    private String s114 = "s114";
+    private String s115 = "s115";
+    private String s116 = "s116";
+    private String s117 = "s117";
+    private String s118 = "s118";
+    private String s119 = "s119";
+    private String s120 = "s120";
+    private String s121 = "s121";
+    private String s122 = "s122";
+    private String s123 = "s123";
+    private String s124 = "s124";
+    private String s125 = "s125";
+    private String s126 = "s126";
+    private String s127 = "s127";
+    private String s128 = "s128";
+    private String s129 = "s129";
+    private String s130 = "s130";
+    private String s131 = "s131";
+    private String s132 = "s132";
+    private String s133 = "s133";
+    private String s134 = "s134";
+    private String s135 = "s135";
+    private String s136 = "s136";
+    private String s137 = "s137";
+    private String s138 = "s138";
+    private String s139 = "s139";
+    private String s140 = "s140";
+    private String s141 = "s141";
+    private String s142 = "s142";
+    private String s143 = "s143";
+    private String s144 = "s144";
+    private String s145 = "s145";
+    private String s146 = "s146";
+    private String s147 = "s147";
+    private String s148 = "s148";
+    private String s149 = "s149";
+    private String s150 = "s150";
+    private String s151 = "s151";
+    private String s152 = "s152";
+    private String s153 = "s153";
+    private String s154 = "s154";
+    private String s155 = "s155";
+    private String s156 = "s156";
+    private String s157 = "s157";
+    private String s158 = "s158";
+    private String s159 = "s159";
+    private String s160 = "s160";
+    private String s161 = "s161";
+    private String s162 = "s162";
+    private String s163 = "s163";
+    private String s164 = "s164";
+    private String s165 = "s165";
+    private String s166 = "s166";
+    private String s167 = "s167";
+    private String s168 = "s168";
+    private String s169 = "s169";
+    private String s170 = "s170";
+    private String s171 = "s171";
+    private String s172 = "s172";
+    private String s173 = "s173";
+    private String s174 = "s174";
+    private String s175 = "s175";
+    private String s176 = "s176";
+    private String s177 = "s177";
+    private String s178 = "s178";
+    private String s179 = "s179";
+    private String s180 = "s180";
+    private String s181 = "s181";
+    private String s182 = "s182";
+    private String s183 = "s183";
+    private String s184 = "s184";
+    private String s185 = "s185";
+    private String s186 = "s186";
+    private String s187 = "s187";
+    private String s188 = "s188";
+    private String s189 = "s189";
+    private String s190 = "s190";
+    private String s191 = "s191";
+    private String s192 = "s192";
+    private String s193 = "s193";
+    private String s194 = "s194";
+    private String s195 = "s195";
+    private String s196 = "s196";
+    private String s197 = "s197";
+    private String s198 = "s198";
+    private String s199 = "s199";
+  }
+
+  private static void use(int i) {
+    if (i == 0) {
+      System.out.println("foo");
+    }
+  }
+
+  private static void useString(String s) {
+    if (s.equals("ab")) {
+      System.out.println("bar");
+    }
+  }
+
+  public static int test() {
+    Inner inner = new Inner();
+    String s0 = inner.s0;
+    int i0 = s0 != null ? inner.i0 : 0;
+    String s1 = inner.s1;
+    int i1 = s1 != null ? inner.i1 : 0;
+    String s2 = inner.s2;
+    int i2 = s2 != null ? inner.i2 : 0;
+    String s3 = inner.s3;
+    int i3 = s3 != null ? inner.i3 : 0;
+    String s4 = inner.s4;
+    int i4 = s4 != null ? inner.i4 : 0;
+    String s5 = inner.s5;
+    int i5 = s5 != null ? inner.i5 : 0;
+    String s6 = inner.s6;
+    int i6 = s6 != null ? inner.i6 : 0;
+    String s7 = inner.s7;
+    int i7 = s7 != null ? inner.i7 : 0;
+    String s8 = inner.s8;
+    int i8 = s8 != null ? inner.i8 : 0;
+    String s9 = inner.s9;
+    int i9 = s9 != null ? inner.i9 : 0;
+    String s10 = inner.s10;
+    int i10 = s10 != null ? inner.i10 : 0;
+    String s11 = inner.s11;
+    int i11 = s11 != null ? inner.i11 : 0;
+    String s12 = inner.s12;
+    int i12 = s12 != null ? inner.i12 : 0;
+    String s13 = inner.s13;
+    int i13 = s13 != null ? inner.i13 : 0;
+    String s14 = inner.s14;
+    int i14 = s14 != null ? inner.i14 : 0;
+    String s15 = inner.s15;
+    int i15 = s15 != null ? inner.i15 : 0;
+    String s16 = inner.s16;
+    int i16 = s16 != null ? inner.i16 : 0;
+    String s17 = inner.s17;
+    int i17 = s17 != null ? inner.i17 : 0;
+    String s18 = inner.s18;
+    int i18 = s18 != null ? inner.i18 : 0;
+    String s19 = inner.s19;
+    int i19 = s19 != null ? inner.i19 : 0;
+    String s20 = inner.s20;
+    int i20 = s20 != null ? inner.i20 : 0;
+    String s21 = inner.s21;
+    int i21 = s21 != null ? inner.i21 : 0;
+    String s22 = inner.s22;
+    int i22 = s22 != null ? inner.i22 : 0;
+    String s23 = inner.s23;
+    int i23 = s23 != null ? inner.i23 : 0;
+    String s24 = inner.s24;
+    int i24 = s24 != null ? inner.i24 : 0;
+    String s25 = inner.s25;
+    int i25 = s25 != null ? inner.i25 : 0;
+    String s26 = inner.s26;
+    int i26 = s26 != null ? inner.i26 : 0;
+    String s27 = inner.s27;
+    int i27 = s27 != null ? inner.i27 : 0;
+    String s28 = inner.s28;
+    int i28 = s28 != null ? inner.i28 : 0;
+    String s29 = inner.s29;
+    int i29 = s29 != null ? inner.i29 : 0;
+    String s30 = inner.s30;
+    int i30 = s30 != null ? inner.i30 : 0;
+    String s31 = inner.s31;
+    int i31 = s31 != null ? inner.i31 : 0;
+    String s32 = inner.s32;
+    int i32 = s32 != null ? inner.i32 : 0;
+    String s33 = inner.s33;
+    int i33 = s33 != null ? inner.i33 : 0;
+    String s34 = inner.s34;
+    int i34 = s34 != null ? inner.i34 : 0;
+    String s35 = inner.s35;
+    int i35 = s35 != null ? inner.i35 : 0;
+    String s36 = inner.s36;
+    int i36 = s36 != null ? inner.i36 : 0;
+    String s37 = inner.s37;
+    int i37 = s37 != null ? inner.i37 : 0;
+    String s38 = inner.s38;
+    int i38 = s38 != null ? inner.i38 : 0;
+    String s39 = inner.s39;
+    int i39 = s39 != null ? inner.i39 : 0;
+    String s40 = inner.s40;
+    int i40 = s40 != null ? inner.i40 : 0;
+    String s41 = inner.s41;
+    int i41 = s41 != null ? inner.i41 : 0;
+    String s42 = inner.s42;
+    int i42 = s42 != null ? inner.i42 : 0;
+    String s43 = inner.s43;
+    int i43 = s43 != null ? inner.i43 : 0;
+    String s44 = inner.s44;
+    int i44 = s44 != null ? inner.i44 : 0;
+    String s45 = inner.s45;
+    int i45 = s45 != null ? inner.i45 : 0;
+    String s46 = inner.s46;
+    int i46 = s46 != null ? inner.i46 : 0;
+    String s47 = inner.s47;
+    int i47 = s47 != null ? inner.i47 : 0;
+    String s48 = inner.s48;
+    int i48 = s48 != null ? inner.i48 : 0;
+    String s49 = inner.s49;
+    int i49 = s49 != null ? inner.i49 : 0;
+    String s50 = inner.s50;
+    int i50 = s50 != null ? inner.i50 : 0;
+    String s51 = inner.s51;
+    int i51 = s51 != null ? inner.i51 : 0;
+    String s52 = inner.s52;
+    int i52 = s52 != null ? inner.i52 : 0;
+    String s53 = inner.s53;
+    int i53 = s53 != null ? inner.i53 : 0;
+    String s54 = inner.s54;
+    int i54 = s54 != null ? inner.i54 : 0;
+    String s55 = inner.s55;
+    int i55 = s55 != null ? inner.i55 : 0;
+    String s56 = inner.s56;
+    int i56 = s56 != null ? inner.i56 : 0;
+    String s57 = inner.s57;
+    int i57 = s57 != null ? inner.i57 : 0;
+    String s58 = inner.s58;
+    int i58 = s58 != null ? inner.i58 : 0;
+    String s59 = inner.s59;
+    int i59 = s59 != null ? inner.i59 : 0;
+    String s60 = inner.s60;
+    int i60 = s60 != null ? inner.i60 : 0;
+    String s61 = inner.s61;
+    int i61 = s61 != null ? inner.i61 : 0;
+    String s62 = inner.s62;
+    int i62 = s62 != null ? inner.i62 : 0;
+    String s63 = inner.s63;
+    int i63 = s63 != null ? inner.i63 : 0;
+    String s64 = inner.s64;
+    int i64 = s64 != null ? inner.i64 : 0;
+    String s65 = inner.s65;
+    int i65 = s65 != null ? inner.i65 : 0;
+    String s66 = inner.s66;
+    int i66 = s66 != null ? inner.i66 : 0;
+    String s67 = inner.s67;
+    int i67 = s67 != null ? inner.i67 : 0;
+    String s68 = inner.s68;
+    int i68 = s68 != null ? inner.i68 : 0;
+    String s69 = inner.s69;
+    int i69 = s69 != null ? inner.i69 : 0;
+    String s70 = inner.s70;
+    int i70 = s70 != null ? inner.i70 : 0;
+    String s71 = inner.s71;
+    int i71 = s71 != null ? inner.i71 : 0;
+    String s72 = inner.s72;
+    int i72 = s72 != null ? inner.i72 : 0;
+    String s73 = inner.s73;
+    int i73 = s73 != null ? inner.i73 : 0;
+    String s74 = inner.s74;
+    int i74 = s74 != null ? inner.i74 : 0;
+    String s75 = inner.s75;
+    int i75 = s75 != null ? inner.i75 : 0;
+    String s76 = inner.s76;
+    int i76 = s76 != null ? inner.i76 : 0;
+    String s77 = inner.s77;
+    int i77 = s77 != null ? inner.i77 : 0;
+    String s78 = inner.s78;
+    int i78 = s78 != null ? inner.i78 : 0;
+    String s79 = inner.s79;
+    int i79 = s79 != null ? inner.i79 : 0;
+    String s80 = inner.s80;
+    int i80 = s80 != null ? inner.i80 : 0;
+    String s81 = inner.s81;
+    int i81 = s81 != null ? inner.i81 : 0;
+    String s82 = inner.s82;
+    int i82 = s82 != null ? inner.i82 : 0;
+    String s83 = inner.s83;
+    int i83 = s83 != null ? inner.i83 : 0;
+    String s84 = inner.s84;
+    int i84 = s84 != null ? inner.i84 : 0;
+    String s85 = inner.s85;
+    int i85 = s85 != null ? inner.i85 : 0;
+    String s86 = inner.s86;
+    int i86 = s86 != null ? inner.i86 : 0;
+    String s87 = inner.s87;
+    int i87 = s87 != null ? inner.i87 : 0;
+    String s88 = inner.s88;
+    int i88 = s88 != null ? inner.i88 : 0;
+    String s89 = inner.s89;
+    int i89 = s89 != null ? inner.i89 : 0;
+    String s90 = inner.s90;
+    int i90 = s90 != null ? inner.i90 : 0;
+    String s91 = inner.s91;
+    int i91 = s91 != null ? inner.i91 : 0;
+    String s92 = inner.s92;
+    int i92 = s92 != null ? inner.i92 : 0;
+    String s93 = inner.s93;
+    int i93 = s93 != null ? inner.i93 : 0;
+    String s94 = inner.s94;
+    int i94 = s94 != null ? inner.i94 : 0;
+    String s95 = inner.s95;
+    int i95 = s95 != null ? inner.i95 : 0;
+    String s96 = inner.s96;
+    int i96 = s96 != null ? inner.i96 : 0;
+    String s97 = inner.s97;
+    int i97 = s97 != null ? inner.i97 : 0;
+    String s98 = inner.s98;
+    int i98 = s98 != null ? inner.i98 : 0;
+    String s99 = inner.s99;
+    int i99 = s99 != null ? inner.i99 : 0;
+    String s100 = inner.s100;
+    int i100 = s100 != null ? inner.i100 : 0;
+    String s101 = inner.s101;
+    int i101 = s101 != null ? inner.i101 : 0;
+    String s102 = inner.s102;
+    int i102 = s102 != null ? inner.i102 : 0;
+    String s103 = inner.s103;
+    int i103 = s103 != null ? inner.i103 : 0;
+    String s104 = inner.s104;
+    int i104 = s104 != null ? inner.i104 : 0;
+    String s105 = inner.s105;
+    int i105 = s105 != null ? inner.i105 : 0;
+    String s106 = inner.s106;
+    int i106 = s106 != null ? inner.i106 : 0;
+    String s107 = inner.s107;
+    int i107 = s107 != null ? inner.i107 : 0;
+    String s108 = inner.s108;
+    int i108 = s108 != null ? inner.i108 : 0;
+    String s109 = inner.s109;
+    int i109 = s109 != null ? inner.i109 : 0;
+    String s110 = inner.s110;
+    int i110 = s110 != null ? inner.i110 : 0;
+    String s111 = inner.s111;
+    int i111 = s111 != null ? inner.i111 : 0;
+    String s112 = inner.s112;
+    int i112 = s112 != null ? inner.i112 : 0;
+    String s113 = inner.s113;
+    int i113 = s113 != null ? inner.i113 : 0;
+    String s114 = inner.s114;
+    int i114 = s114 != null ? inner.i114 : 0;
+    String s115 = inner.s115;
+    int i115 = s115 != null ? inner.i115 : 0;
+    String s116 = inner.s116;
+    int i116 = s116 != null ? inner.i116 : 0;
+    String s117 = inner.s117;
+    int i117 = s117 != null ? inner.i117 : 0;
+    String s118 = inner.s118;
+    int i118 = s118 != null ? inner.i118 : 0;
+    String s119 = inner.s119;
+    int i119 = s119 != null ? inner.i119 : 0;
+    String s120 = inner.s120;
+    int i120 = s120 != null ? inner.i120 : 0;
+    String s121 = inner.s121;
+    int i121 = s121 != null ? inner.i121 : 0;
+    String s122 = inner.s122;
+    int i122 = s122 != null ? inner.i122 : 0;
+    String s123 = inner.s123;
+    int i123 = s123 != null ? inner.i123 : 0;
+    String s124 = inner.s124;
+    int i124 = s124 != null ? inner.i124 : 0;
+    String s125 = inner.s125;
+    int i125 = s125 != null ? inner.i125 : 0;
+    String s126 = inner.s126;
+    int i126 = s126 != null ? inner.i126 : 0;
+    String s127 = inner.s127;
+    int i127 = s127 != null ? inner.i127 : 0;
+    String s128 = inner.s128;
+    int i128 = s128 != null ? inner.i128 : 0;
+    String s129 = inner.s129;
+    int i129 = s129 != null ? inner.i129 : 0;
+    String s130 = inner.s130;
+    int i130 = s130 != null ? inner.i130 : 0;
+    String s131 = inner.s131;
+    int i131 = s131 != null ? inner.i131 : 0;
+    String s132 = inner.s132;
+    int i132 = s132 != null ? inner.i132 : 0;
+    String s133 = inner.s133;
+    int i133 = s133 != null ? inner.i133 : 0;
+    String s134 = inner.s134;
+    int i134 = s134 != null ? inner.i134 : 0;
+    String s135 = inner.s135;
+    int i135 = s135 != null ? inner.i135 : 0;
+    String s136 = inner.s136;
+    int i136 = s136 != null ? inner.i136 : 0;
+    String s137 = inner.s137;
+    int i137 = s137 != null ? inner.i137 : 0;
+    String s138 = inner.s138;
+    int i138 = s138 != null ? inner.i138 : 0;
+    String s139 = inner.s139;
+    int i139 = s139 != null ? inner.i139 : 0;
+    String s140 = inner.s140;
+    int i140 = s140 != null ? inner.i140 : 0;
+    String s141 = inner.s141;
+    int i141 = s141 != null ? inner.i141 : 0;
+    String s142 = inner.s142;
+    int i142 = s142 != null ? inner.i142 : 0;
+    String s143 = inner.s143;
+    int i143 = s143 != null ? inner.i143 : 0;
+    String s144 = inner.s144;
+    int i144 = s144 != null ? inner.i144 : 0;
+    String s145 = inner.s145;
+    int i145 = s145 != null ? inner.i145 : 0;
+    String s146 = inner.s146;
+    int i146 = s146 != null ? inner.i146 : 0;
+    String s147 = inner.s147;
+    int i147 = s147 != null ? inner.i147 : 0;
+    String s148 = inner.s148;
+    int i148 = s148 != null ? inner.i148 : 0;
+    String s149 = inner.s149;
+    int i149 = s149 != null ? inner.i149 : 0;
+    String s150 = inner.s150;
+    int i150 = s150 != null ? inner.i150 : 0;
+    String s151 = inner.s151;
+    int i151 = s151 != null ? inner.i151 : 0;
+    String s152 = inner.s152;
+    int i152 = s152 != null ? inner.i152 : 0;
+    String s153 = inner.s153;
+    int i153 = s153 != null ? inner.i153 : 0;
+    String s154 = inner.s154;
+    int i154 = s154 != null ? inner.i154 : 0;
+    String s155 = inner.s155;
+    int i155 = s155 != null ? inner.i155 : 0;
+    String s156 = inner.s156;
+    int i156 = s156 != null ? inner.i156 : 0;
+    String s157 = inner.s157;
+    int i157 = s157 != null ? inner.i157 : 0;
+    String s158 = inner.s158;
+    int i158 = s158 != null ? inner.i158 : 0;
+    String s159 = inner.s159;
+    int i159 = s159 != null ? inner.i159 : 0;
+    String s160 = inner.s160;
+    int i160 = s160 != null ? inner.i160 : 0;
+    String s161 = inner.s161;
+    int i161 = s161 != null ? inner.i161 : 0;
+    String s162 = inner.s162;
+    int i162 = s162 != null ? inner.i162 : 0;
+    String s163 = inner.s163;
+    int i163 = s163 != null ? inner.i163 : 0;
+    String s164 = inner.s164;
+    int i164 = s164 != null ? inner.i164 : 0;
+    String s165 = inner.s165;
+    int i165 = s165 != null ? inner.i165 : 0;
+    String s166 = inner.s166;
+    int i166 = s166 != null ? inner.i166 : 0;
+    String s167 = inner.s167;
+    int i167 = s167 != null ? inner.i167 : 0;
+    String s168 = inner.s168;
+    int i168 = s168 != null ? inner.i168 : 0;
+    String s169 = inner.s169;
+    int i169 = s169 != null ? inner.i169 : 0;
+    String s170 = inner.s170;
+    int i170 = s170 != null ? inner.i170 : 0;
+    String s171 = inner.s171;
+    int i171 = s171 != null ? inner.i171 : 0;
+    String s172 = inner.s172;
+    int i172 = s172 != null ? inner.i172 : 0;
+    String s173 = inner.s173;
+    int i173 = s173 != null ? inner.i173 : 0;
+    String s174 = inner.s174;
+    int i174 = s174 != null ? inner.i174 : 0;
+    String s175 = inner.s175;
+    int i175 = s175 != null ? inner.i175 : 0;
+    String s176 = inner.s176;
+    int i176 = s176 != null ? inner.i176 : 0;
+    String s177 = inner.s177;
+    int i177 = s177 != null ? inner.i177 : 0;
+    String s178 = inner.s178;
+    int i178 = s178 != null ? inner.i178 : 0;
+    String s179 = inner.s179;
+    int i179 = s179 != null ? inner.i179 : 0;
+    String s180 = inner.s180;
+    int i180 = s180 != null ? inner.i180 : 0;
+    String s181 = inner.s181;
+    int i181 = s181 != null ? inner.i181 : 0;
+    String s182 = inner.s182;
+    int i182 = s182 != null ? inner.i182 : 0;
+    String s183 = inner.s183;
+    int i183 = s183 != null ? inner.i183 : 0;
+    String s184 = inner.s184;
+    int i184 = s184 != null ? inner.i184 : 0;
+    String s185 = inner.s185;
+    int i185 = s185 != null ? inner.i185 : 0;
+    String s186 = inner.s186;
+    int i186 = s186 != null ? inner.i186 : 0;
+    String s187 = inner.s187;
+    int i187 = s187 != null ? inner.i187 : 0;
+    String s188 = inner.s188;
+    int i188 = s188 != null ? inner.i188 : 0;
+    String s189 = inner.s189;
+    int i189 = s189 != null ? inner.i189 : 0;
+    String s190 = inner.s190;
+    int i190 = s190 != null ? inner.i190 : 0;
+    String s191 = inner.s191;
+    int i191 = s191 != null ? inner.i191 : 0;
+    String s192 = inner.s192;
+    int i192 = s192 != null ? inner.i192 : 0;
+    String s193 = inner.s193;
+    int i193 = s193 != null ? inner.i193 : 0;
+    String s194 = inner.s194;
+    int i194 = s194 != null ? inner.i194 : 0;
+    String s195 = inner.s195;
+    int i195 = s195 != null ? inner.i195 : 0;
+    String s196 = inner.s196;
+    int i196 = s196 != null ? inner.i196 : 0;
+    String s197 = inner.s197;
+    int i197 = s197 != null ? inner.i197 : 0;
+    String s198 = inner.s198;
+    int i198 = s198 != null ? inner.i198 : 0;
+    String s199 = inner.s199;
+    int i199 = s199 != null ? inner.i199 : 0;
+
+    useString(s0);
+    use(i0);
+    useString(s1);
+    use(i1);
+    useString(s2);
+    use(i2);
+    useString(s3);
+    use(i3);
+    useString(s4);
+    use(i4);
+    useString(s5);
+    use(i5);
+    useString(s6);
+    use(i6);
+    useString(s7);
+    use(i7);
+    useString(s8);
+    use(i8);
+    useString(s9);
+    use(i9);
+    useString(s10);
+    use(i10);
+    useString(s11);
+    use(i11);
+    useString(s12);
+    use(i12);
+    useString(s13);
+    use(i13);
+    useString(s14);
+    use(i14);
+    useString(s15);
+    use(i15);
+    useString(s16);
+    use(i16);
+    useString(s17);
+    use(i17);
+    useString(s18);
+    use(i18);
+    useString(s19);
+    use(i19);
+    useString(s20);
+    use(i20);
+    useString(s21);
+    use(i21);
+    useString(s22);
+    use(i22);
+    useString(s23);
+    use(i23);
+    useString(s24);
+    use(i24);
+    useString(s25);
+    use(i25);
+    useString(s26);
+    use(i26);
+    useString(s27);
+    use(i27);
+    useString(s28);
+    use(i28);
+    useString(s29);
+    use(i29);
+    useString(s30);
+    use(i30);
+    useString(s31);
+    use(i31);
+    useString(s32);
+    use(i32);
+    useString(s33);
+    use(i33);
+    useString(s34);
+    use(i34);
+    useString(s35);
+    use(i35);
+    useString(s36);
+    use(i36);
+    useString(s37);
+    use(i37);
+    useString(s38);
+    use(i38);
+    useString(s39);
+    use(i39);
+    useString(s40);
+    use(i40);
+    useString(s41);
+    use(i41);
+    useString(s42);
+    use(i42);
+    useString(s43);
+    use(i43);
+    useString(s44);
+    use(i44);
+    useString(s45);
+    use(i45);
+    useString(s46);
+    use(i46);
+    useString(s47);
+    use(i47);
+    useString(s48);
+    use(i48);
+    useString(s49);
+    use(i49);
+    useString(s50);
+    use(i50);
+    useString(s51);
+    use(i51);
+    useString(s52);
+    use(i52);
+    useString(s53);
+    use(i53);
+    useString(s54);
+    use(i54);
+    useString(s55);
+    use(i55);
+    useString(s56);
+    use(i56);
+    useString(s57);
+    use(i57);
+    useString(s58);
+    use(i58);
+    useString(s59);
+    use(i59);
+    useString(s60);
+    use(i60);
+    useString(s61);
+    use(i61);
+    useString(s62);
+    use(i62);
+    useString(s63);
+    use(i63);
+    useString(s64);
+    use(i64);
+    useString(s65);
+    use(i65);
+    useString(s66);
+    use(i66);
+    useString(s67);
+    use(i67);
+    useString(s68);
+    use(i68);
+    useString(s69);
+    use(i69);
+    useString(s70);
+    use(i70);
+    useString(s71);
+    use(i71);
+    useString(s72);
+    use(i72);
+    useString(s73);
+    use(i73);
+    useString(s74);
+    use(i74);
+    useString(s75);
+    use(i75);
+    useString(s76);
+    use(i76);
+    useString(s77);
+    use(i77);
+    useString(s78);
+    use(i78);
+    useString(s79);
+    use(i79);
+    useString(s80);
+    use(i80);
+    useString(s81);
+    use(i81);
+    useString(s82);
+    use(i82);
+    useString(s83);
+    use(i83);
+    useString(s84);
+    use(i84);
+    useString(s85);
+    use(i85);
+    useString(s86);
+    use(i86);
+    useString(s87);
+    use(i87);
+    useString(s88);
+    use(i88);
+    useString(s89);
+    use(i89);
+    useString(s90);
+    use(i90);
+    useString(s91);
+    use(i91);
+    useString(s92);
+    use(i92);
+    useString(s93);
+    use(i93);
+    useString(s94);
+    use(i94);
+    useString(s95);
+    use(i95);
+    useString(s96);
+    use(i96);
+    useString(s97);
+    use(i97);
+    useString(s98);
+    use(i98);
+    useString(s99);
+    use(i99);
+    useString(s100);
+    use(i100);
+    useString(s101);
+    use(i101);
+    useString(s102);
+    use(i102);
+    useString(s103);
+    use(i103);
+    useString(s104);
+    use(i104);
+    useString(s105);
+    use(i105);
+    useString(s106);
+    use(i106);
+    useString(s107);
+    use(i107);
+    useString(s108);
+    use(i108);
+    useString(s109);
+    use(i109);
+    useString(s110);
+    use(i110);
+    useString(s111);
+    use(i111);
+    useString(s112);
+    use(i112);
+    useString(s113);
+    use(i113);
+    useString(s114);
+    use(i114);
+    useString(s115);
+    use(i115);
+    useString(s116);
+    use(i116);
+    useString(s117);
+    use(i117);
+    useString(s118);
+    use(i118);
+    useString(s119);
+    use(i119);
+    useString(s120);
+    use(i120);
+    useString(s121);
+    use(i121);
+    useString(s122);
+    use(i122);
+    useString(s123);
+    use(i123);
+    useString(s124);
+    use(i124);
+    useString(s125);
+    use(i125);
+    useString(s126);
+    use(i126);
+    useString(s127);
+    use(i127);
+    useString(s128);
+    use(i128);
+    useString(s129);
+    use(i129);
+    useString(s130);
+    use(i130);
+    useString(s131);
+    use(i131);
+    useString(s132);
+    use(i132);
+    useString(s133);
+    use(i133);
+    useString(s134);
+    use(i134);
+    useString(s135);
+    use(i135);
+    useString(s136);
+    use(i136);
+    useString(s137);
+    use(i137);
+    useString(s138);
+    use(i138);
+    useString(s139);
+    use(i139);
+    useString(s140);
+    use(i140);
+    useString(s141);
+    use(i141);
+    useString(s142);
+    use(i142);
+    useString(s143);
+    use(i143);
+    useString(s144);
+    use(i144);
+    useString(s145);
+    use(i145);
+    useString(s146);
+    use(i146);
+    useString(s147);
+    use(i147);
+    useString(s148);
+    use(i148);
+    useString(s149);
+    use(i149);
+    useString(s150);
+    use(i150);
+    useString(s151);
+    use(i151);
+    useString(s152);
+    use(i152);
+    useString(s153);
+    use(i153);
+    useString(s154);
+    use(i154);
+    useString(s155);
+    use(i155);
+    useString(s156);
+    use(i156);
+    useString(s157);
+    use(i157);
+    useString(s158);
+    use(i158);
+    useString(s159);
+    use(i159);
+    useString(s160);
+    use(i160);
+    useString(s161);
+    use(i161);
+    useString(s162);
+    use(i162);
+    useString(s163);
+    use(i163);
+    useString(s164);
+    use(i164);
+    useString(s165);
+    use(i165);
+    useString(s166);
+    use(i166);
+    useString(s167);
+    use(i167);
+    useString(s168);
+    use(i168);
+    useString(s169);
+    use(i169);
+    useString(s170);
+    use(i170);
+    useString(s171);
+    use(i171);
+    useString(s172);
+    use(i172);
+    useString(s173);
+    use(i173);
+    useString(s174);
+    use(i174);
+    useString(s175);
+    use(i175);
+    useString(s176);
+    use(i176);
+    useString(s177);
+    use(i177);
+    useString(s178);
+    use(i178);
+    useString(s179);
+    use(i179);
+    useString(s180);
+    use(i180);
+    useString(s181);
+    use(i181);
+    useString(s182);
+    use(i182);
+    useString(s183);
+    use(i183);
+    useString(s184);
+    use(i184);
+    useString(s185);
+    use(i185);
+    useString(s186);
+    use(i186);
+    useString(s187);
+    use(i187);
+    useString(s188);
+    use(i188);
+    useString(s189);
+    use(i189);
+    useString(s190);
+    use(i190);
+    useString(s191);
+    use(i191);
+    useString(s192);
+    use(i192);
+    useString(s193);
+    use(i193);
+    useString(s194);
+    use(i194);
+    useString(s195);
+    use(i195);
+    useString(s196);
+    use(i196);
+    useString(s197);
+    use(i197);
+    useString(s198);
+    use(i198);
+    useString(s199);
+    use(i199);
+    return i199;
+  }
+
+
+  public static void main(String[] args) {
+    Regress instance = new Regress();
+    instance.test();
+  }
+}
diff --git a/src/test/examplesAndroidN/dexsplitsample/Class3.java b/src/test/examplesAndroidN/dexsplitsample/Class3.java
index 98469a5..7dba306 100644
--- a/src/test/examplesAndroidN/dexsplitsample/Class3.java
+++ b/src/test/examplesAndroidN/dexsplitsample/Class3.java
@@ -5,6 +5,9 @@
 package dexsplitsample;
 
 public class Class3 extends Class1 {
+  // Instantiate Class1 to prevent it from being merged into Class3.
+  private static final Class1 obj = new Class1();
+
   public static void main(String[] args) {
     Class3 clazz = new Class3();
     if (clazz.getClass1String() != "Class1String") {
diff --git a/src/test/examplesAndroidO/classmerging/MergeDefaultMethodIntoClassTest.java b/src/test/examplesAndroidO/classmerging/MergeDefaultMethodIntoClassTest.java
new file mode 100644
index 0000000..10f995d
--- /dev/null
+++ b/src/test/examplesAndroidO/classmerging/MergeDefaultMethodIntoClassTest.java
@@ -0,0 +1,23 @@
+// 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 classmerging;
+
+public class MergeDefaultMethodIntoClassTest {
+
+  public static void main(String[] args) {
+    // Note: Important that the static type of [obj] is A, such that the call to f becomes an
+    // invoke-interface instruction and not invoke-virtual instruction.
+    A obj = new B();
+    obj.f();
+  }
+
+  public interface A {
+    default void f() {
+      System.out.println("In A.f");
+    }
+  }
+
+  public static class B implements A {}
+}
diff --git a/src/test/examplesAndroidO/classmerging/keep-rules.txt b/src/test/examplesAndroidO/classmerging/keep-rules.txt
index 9868dcf..fc91808 100644
--- a/src/test/examplesAndroidO/classmerging/keep-rules.txt
+++ b/src/test/examplesAndroidO/classmerging/keep-rules.txt
@@ -7,6 +7,9 @@
 -keep public class classmerging.LambdaRewritingTest {
   public static void main(...);
 }
+-keep public class classmerging.MergeDefaultMethodIntoClassTest {
+  public static void main(...);
+}
 
 # TODO(herhut): Consider supporting merging of inner-class attributes.
 # -keepattributes *
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
index 2f45ae2..d79aaf0 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -116,9 +116,14 @@
         messageParts);
   }
 
+  public static Diagnostic checkDiagnostics(List<Diagnostic> diagnostics, int index, Path path,
+      int lineStart, int columnStart, String... messageParts) {
+    return checkDiagnostic(diagnostics.get(index), path, lineStart, columnStart, messageParts);
+  }
+
   public static Diagnostic checkDiagnostics(List<Diagnostic> diagnostics, Path path,
       int lineStart, int columnStart, String... messageParts) {
     assertEquals(1, diagnostics.size());
-    return checkDiagnostic(diagnostics.get(0), path, lineStart, columnStart, messageParts);
+    return checkDiagnostics(diagnostics, 0, path, lineStart, columnStart, messageParts);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 36d924f..221d427 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -74,6 +74,7 @@
         "regress_70736958.Test",
         "regress_70737019.Test",
         "regress_72361252.Test",
+        "regress_110373181.Regress",
         "memberrebinding2.Memberrebinding",
         "memberrebinding3.Memberrebinding",
         "minification.Minification",
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/accessrelaxation/AccessRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
index 027b7e8..53a8c31 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
@@ -171,10 +171,13 @@
     assertNotPublic(dexInspector, Base.class,
         new MethodSignature("foo2", STRING, ImmutableList.of()));
 
-    // Sub1#bar1(int) can't be publicized due to Base#bar1(int).
-    assertNotPublic(dexInspector, Sub1.class,
+    // Sub?#bar1(int) can be publicized as they don't bother each other.
+    assertPublic(dexInspector, Sub1.class,
+        new MethodSignature("bar1", STRING, ImmutableList.of("int")));
+    assertPublic(dexInspector, Sub2.class,
         new MethodSignature("bar1", STRING, ImmutableList.of("int")));
 
+    // Sub2#bar2(int) is unique throughout the hierarchy, hence publicized.
     assertPublic(dexInspector, Sub2.class,
         new MethodSignature("bar2", STRING, ImmutableList.of("int")));
   }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
index fd1fd1c..6325c78 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
@@ -30,19 +30,10 @@
     return foo2();
   }
 
-  private synchronized String bar1(int i) {
-    throw new AssertionError("Sub1#bar1(int) will not use this signature.");
-  }
-
   public void dump() {
     System.out.println(foo());
     System.out.println(foo1());
     System.out.println(foo2());
-    try {
-      bar1(0);
-    } catch (AssertionError e) {
-      // expected
-    }
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
index 4cbbf11..317aaff 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
@@ -22,11 +22,7 @@
   public void dump() {
     System.out.println(foo1());
     System.out.println(foo1(0));
-    try {
-      System.out.println(bar1(0));
-    } catch (Throwable e) {
-      System.out.println(e.getClass().getName());
-    }
+    System.out.println(bar1(0));
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
index 8c4e01e..039e3ac 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
@@ -10,6 +10,14 @@
     return "Sub2::foo2()";
   }
 
+  private synchronized String bar1(int i) {
+    return "Sub2::bar1(" + i + ")";
+  }
+
+  public String pBar1() {
+    return bar1(1);
+  }
+
   private synchronized String bar2(int i) {
     return "Sub2::bar2(" + i + ")";
   }
@@ -23,6 +31,11 @@
     System.out.println(foo2());
     System.out.println(foo2(0));
     System.out.println(bar2(0));
+    try {
+      bar1(0);
+    } catch (AssertionError e) {
+      // expected
+    }
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index 59accb3..f28c86f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.smali.SmaliBuilder.buildCode;
 import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
@@ -22,8 +23,11 @@
 import com.android.tools.r8.code.MoveException;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
@@ -183,6 +187,22 @@
   }
 
   @Test
+  public void testFieldCollision() throws Exception {
+    String main = "classmerging.FieldCollisionTest";
+    Path[] programFiles =
+        new Path[] {
+          CF_DIR.resolve("FieldCollisionTest.class"),
+          CF_DIR.resolve("FieldCollisionTest$A.class"),
+          CF_DIR.resolve("FieldCollisionTest$B.class")
+        };
+    Set<String> preservedClassNames =
+        ImmutableSet.of(
+            "classmerging.FieldCollisionTest",
+            "classmerging.FieldCollisionTest$B");
+    runTest(main, programFiles, preservedClassNames::contains);
+  }
+
+  @Test
   public void testLambdaRewriting() throws Exception {
     String main = "classmerging.LambdaRewritingTest";
     Path[] programFiles =
@@ -206,6 +226,31 @@
   }
 
   @Test
+  public void testMethodCollision() throws Exception {
+    String main = "classmerging.MethodCollisionTest";
+    Path[] programFiles =
+        new Path[] {
+            CF_DIR.resolve("MethodCollisionTest.class"),
+            CF_DIR.resolve("MethodCollisionTest$A.class"),
+            CF_DIR.resolve("MethodCollisionTest$B.class"),
+            CF_DIR.resolve("MethodCollisionTest$C.class"),
+            CF_DIR.resolve("MethodCollisionTest$D.class")
+        };
+    // TODO(christofferqa): Currently we do not allow merging A into B because we find a collision.
+    // However, we are free to change the names of private methods, so we should handle them similar
+    // to fields (i.e., we should allow merging A into B). This would also improve the performance
+    // of the collision detector, because it would only have to consider non-private methods.
+    Set<String> preservedClassNames =
+        ImmutableSet.of(
+            "classmerging.MethodCollisionTest",
+            "classmerging.MethodCollisionTest$A",
+            "classmerging.MethodCollisionTest$B",
+            "classmerging.MethodCollisionTest$C",
+            "classmerging.MethodCollisionTest$D");
+    runTest(main, programFiles, preservedClassNames::contains);
+  }
+
+  @Test
   public void testPinnedParameterTypes() throws Exception {
     String main = "classmerging.PinnedParameterTypesTest";
     Path[] programFiles =
@@ -343,47 +388,57 @@
             "classmerging.D",
             "classmerging.F");
 
-    SmaliBuilder smaliBuilder = new SmaliBuilder();
+    JasminBuilder jasminBuilder = new JasminBuilder();
 
-    smaliBuilder.addClass(main);
-    smaliBuilder.addMainMethod(
-        2,
+    ClassBuilder classBuilder = jasminBuilder.addClass(main);
+    classBuilder.addMainMethod(
+        ".limit locals 1",
+        ".limit stack 2",
         // Instantiate B so that it is not merged into C.
-        "new-instance v1, Lclassmerging/B;",
-        "invoke-direct {v1}, Lclassmerging/B;-><init>()V",
-        "invoke-virtual {v1}, Lclassmerging/B;->m()V",
+        "new classmerging/B",
+        "dup",
+        "invokespecial classmerging/B/<init>()V",
+        "invokevirtual classmerging/B/m()V",
         // Instantiate D so that it is not merged into E.
-        "new-instance v1, Lclassmerging/D;",
-        "invoke-direct {v1}, Lclassmerging/D;-><init>()V",
-        "invoke-virtual {v1}, Lclassmerging/D;->m()V",
+        "new classmerging/D",
+        "dup",
+        "invokespecial classmerging/D/<init>()V",
+        "invokevirtual classmerging/D/m()V",
         // Start the actual testing.
-        "new-instance v1, Lclassmerging/F;",
-        "invoke-direct {v1}, Lclassmerging/F;-><init>()V",
-        "invoke-virtual {v1}, Lclassmerging/F;->invokeMethodOnB()V",
-        "invoke-virtual {v1}, Lclassmerging/F;->invokeMethodOnC()V",
-        "invoke-virtual {v1}, Lclassmerging/F;->invokeMethodOnD()V",
-        "invoke-virtual {v1}, Lclassmerging/F;->invokeMethodOnE()V",
-        "invoke-virtual {v1}, Lclassmerging/F;->invokeMethodOnF()V",
+        "new classmerging/F",
+        "dup",
+        "invokespecial classmerging/F/<init>()V",
+        "dup",
+        "invokevirtual classmerging/F/invokeMethodOnB()V",
+        "dup",
+        "invokevirtual classmerging/F/invokeMethodOnC()V",
+        "dup",
+        "invokevirtual classmerging/F/invokeMethodOnD()V",
+        "dup",
+        "invokevirtual classmerging/F/invokeMethodOnE()V",
+        "dup",
+        "invokevirtual classmerging/F/invokeMethodOnF()V",
+        "dup",
         // The method invokeMethodOnA() should yield a NoSuchMethodError.
-        ":try_start",
-        "invoke-virtual {v1}, Lclassmerging/F;->invokeMethodOnA()V",
-        ":try_end",
-        "return-void",
-        ".catch Ljava/lang/NoSuchMethodError; {:try_start .. :try_end} :catch",
-        ":catch",
-        smaliCodeForPrinting("NoSuchMethodError", 0, 1),
-        "return-void");
+        "try_start:",
+        "invokevirtual classmerging/F/invokeMethodOnA()V",
+        "try_end:",
+        "return",
+        "catch:",
+        jasminCodeForPrinting("NoSuchMethodError"),
+        "return",
+        ".catch java/lang/NoSuchMethodError from try_start to try_end using catch");
 
     // Class A deliberately has no method m. We need to make sure that the "invoke-super A.m()"
     // instruction in class F is not rewritten into something that does not throw.
-    smaliBuilder.addClass("classmerging.A");
-    smaliBuilder.addDefaultConstructor();
+    classBuilder = jasminBuilder.addClass("classmerging.A");
+    classBuilder.addDefaultConstructor();
 
     // Class B declares a virtual method m() that prints "In B.m()".
-    smaliBuilder.addClass("classmerging.B", "classmerging.A");
-    smaliBuilder.addDefaultConstructor();
-    smaliBuilder.addInstanceMethod(
-        "void", "m", 2, smaliCodeForPrinting("In B.m()", 0, 1), "return-void");
+    classBuilder = jasminBuilder.addClass("classmerging.B", "classmerging.A");
+    classBuilder.addDefaultConstructor();
+    classBuilder.addVirtualMethod(
+        "m", "V", ".limit locals 1", ".limit stack 2", jasminCodeForPrinting("In B.m()"), "return");
 
     // Class C, D, and E declare a virtual method m() that prints "In C.m()", "In D.m()", and
     // "In E.m()", respectively.
@@ -391,17 +446,24 @@
         new String[][] {new String[] {"C", "B"}, new String[] {"D", "C"}, new String[] {"E", "D"}};
     for (String[] pair : pairs) {
       String name = pair[0], superName = pair[1];
-      smaliBuilder.addClass("classmerging." + name, "classmerging." + superName);
-      smaliBuilder.addDefaultConstructor();
-      smaliBuilder.addInstanceMethod(
-          "void",
+      classBuilder = jasminBuilder.addClass("classmerging." + name, "classmerging." + superName);
+      classBuilder.addDefaultConstructor();
+      classBuilder.addVirtualMethod(
           "m",
-          2,
-          smaliCodeForPrinting("In " + name + ".m()", 0, 1),
-          buildCode("invoke-super {p0}, Lclassmerging/" + superName + ";->m()V", "return-void"));
+          "V",
+          ".limit locals 1",
+          ".limit stack 2",
+          jasminCodeForPrinting("In " + name + ".m()"),
+          "aload_0",
+          "invokespecial classmerging/" + superName + "/m()V",
+          "return");
     }
 
     // Class F declares a virtual method m that throws an exception (it is expected to be dead).
+    //
+    // Note that F is generated from smali since it is the only way to generate an instruction on
+    // the form "invoke-super F.m()".
+    SmaliBuilder smaliBuilder = new SmaliBuilder();
     smaliBuilder.addClass("classmerging.F", "classmerging.E");
     smaliBuilder.addDefaultConstructor();
     smaliBuilder.addInstanceMethod(
@@ -411,7 +473,6 @@
         "new-instance v1, Ljava/lang/Exception;",
         "invoke-direct {v1}, Ljava/lang/Exception;-><init>()V",
         "throw v1");
-
     // Add methods to F with an "invoke-super X.m()" instruction for X in {A, B, C, D, E, F}.
     for (String type : ImmutableList.of("A", "B", "C", "D", "E", "F")) {
       String code =
@@ -420,24 +481,23 @@
     }
 
     // Build app.
-    AndroidApp.Builder builder = AndroidApp.builder();
-    builder.addDexProgramData(smaliBuilder.compile(), Origin.unknown());
+    AndroidApp.Builder appBuilder = AndroidApp.builder();
+    appBuilder.addClassProgramData(jasminBuilder.buildClasses());
+    appBuilder.addDexProgramData(smaliBuilder.compile(), Origin.unknown());
 
     // Run test.
     runTestOnInput(
         main,
-        builder.build(),
+        appBuilder.build(),
         preservedClassNames::contains,
         String.format("-keep class %s { public static void main(...); }", main));
   }
 
-  private static String smaliCodeForPrinting(String message, int reg0, int reg1) {
+  private static String jasminCodeForPrinting(String message) {
     return buildCode(
-        String.format("sget-object v%d, Ljava/lang/System;->out:Ljava/io/PrintStream;", reg0),
-        String.format("const-string v1, \"%s\"", message),
-        String.format(
-            "invoke-virtual {v%d, v%d}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
-            reg0, reg1));
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        String.format("ldc \"%s\"", message),
+        "invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V");
   }
 
   @Test
@@ -496,6 +556,58 @@
   }
 
   @Test
+  public void testMergeDefaultMethodIntoClass() throws Exception {
+    String main = "classmerging.MergeDefaultMethodIntoClassTest";
+    Path[] programFiles =
+        new Path[] {
+          JAVA8_CF_DIR.resolve("MergeDefaultMethodIntoClassTest.class"),
+          JAVA8_CF_DIR.resolve("MergeDefaultMethodIntoClassTest$A.class"),
+          JAVA8_CF_DIR.resolve("MergeDefaultMethodIntoClassTest$B.class")
+        };
+    ImmutableSet<String> preservedClassNames =
+        ImmutableSet.of(
+            "classmerging.MergeDefaultMethodIntoClassTest",
+            "classmerging.MergeDefaultMethodIntoClassTest$B");
+    AndroidApp app =
+        AndroidApp.builder()
+            .addProgramFiles(programFiles)
+            .addLibraryFile(ToolHelper.getAndroidJar(AndroidApiLevel.O))
+            .build();
+
+    // Sanity check that there is actually an invoke-interface instruction in the input. We need
+    // to make sure that this invoke-interface instruction is translated to invoke-virtual after
+    // the classes A and B are merged.
+    DexInspector inputInspector = new DexInspector(app);
+    ClassSubject clazz = inputInspector.clazz("classmerging.MergeDefaultMethodIntoClassTest");
+    assertThat(clazz, isPresent());
+    MethodSubject method = clazz.method("void", "main", ImmutableList.of("java.lang.String[]"));
+    assertThat(method, isPresent());
+    assertThat(
+        method.getMethod().getCode().asJarCode().toString(),
+        containsString("INVOKEINTERFACE classmerging/MergeDefaultMethodIntoClassTest$A.f"));
+
+    runTestOnInput(main, app, preservedClassNames::contains, getProguardConfig(JAVA8_EXAMPLE_KEEP));
+  }
+
+  @Test
+  public void testNativeMethod() throws Exception {
+    String main = "classmerging.ClassWithNativeMethodTest";
+    Path[] programFiles =
+        new Path[] {
+          CF_DIR.resolve("ClassWithNativeMethodTest.class"),
+          CF_DIR.resolve("ClassWithNativeMethodTest$A.class"),
+          CF_DIR.resolve("ClassWithNativeMethodTest$B.class")
+        };
+    // Ensures that the class A with a native method has not been merged into its subclass B.
+    ImmutableSet<String> preservedClassNames =
+        ImmutableSet.of(
+            "classmerging.ClassWithNativeMethodTest",
+            "classmerging.ClassWithNativeMethodTest$A",
+            "classmerging.ClassWithNativeMethodTest$B");
+    runTest(main, programFiles, preservedClassNames::contains);
+  }
+
+  @Test
   public void testNoIllegalClassAccess() throws Exception {
     String main = "classmerging.SimpleInterfaceAccessTest";
     Path[] programFiles =
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 51a5262..3239191 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.OutputMode;
@@ -27,6 +28,7 @@
 import com.android.tools.r8.ir.optimize.classinliner.builders.Tuple;
 import com.android.tools.r8.ir.optimize.classinliner.code.C;
 import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
+import com.android.tools.r8.ir.optimize.classinliner.invalidroot.InvalidRootsTestClass;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
@@ -254,6 +256,51 @@
     assertFalse(inspector.clazz(C.F.class).isPresent());
   }
 
+  @Test
+  public void testInvalidatedRoot() throws Exception {
+    String prefix = "com.android.tools.r8.ir.optimize.classinliner.invalidroot.";
+
+    byte[][] classes = {
+        ToolHelper.getClassAsBytes(InvalidRootsTestClass.class),
+        ToolHelper.getClassAsBytes(InvalidRootsTestClass.A.class),
+        ToolHelper.getClassAsBytes(InvalidRootsTestClass.B.class),
+        ToolHelper.getClassAsBytes(InvalidRootsTestClass.NeverReturnsNormally.class),
+        ToolHelper.getClassAsBytes(InvalidRootsTestClass.InitNeverReturnsNormally.class)
+    };
+    AndroidApp app = runR8(buildAndroidApp(classes), InvalidRootsTestClass.class);
+
+    String javaOutput = runOnJava(InvalidRootsTestClass.class);
+    String artOutput = runOnArt(app, InvalidRootsTestClass.class);
+    assertEquals(javaOutput, artOutput);
+
+    DexInspector inspector = new DexInspector(app);
+    ClassSubject clazz = inspector.clazz(InvalidRootsTestClass.class);
+
+    assertEquals(
+        Sets.newHashSet(prefix + "InvalidRootsTestClass$NeverReturnsNormally"),
+        collectTypes(clazz, "testExtraNeverReturnsNormally", "void"));
+
+    assertEquals(
+        Sets.newHashSet(prefix + "InvalidRootsTestClass$NeverReturnsNormally"),
+        collectTypes(clazz, "testDirectNeverReturnsNormally", "void"));
+
+    assertEquals(
+        Sets.newHashSet(prefix + "InvalidRootsTestClass$InitNeverReturnsNormally"),
+        collectTypes(clazz, "testInitNeverReturnsNormally", "void"));
+
+    assertTrue(inspector.clazz(InvalidRootsTestClass.NeverReturnsNormally.class).isPresent());
+    assertTrue(inspector.clazz(InvalidRootsTestClass.InitNeverReturnsNormally.class).isPresent());
+
+    assertEquals(
+        Sets.newHashSet(
+            "java.lang.StringBuilder",
+            "java.lang.RuntimeException"),
+        collectTypes(clazz, "testRootInvalidatesAfterInlining", "void"));
+
+    assertFalse(inspector.clazz(InvalidRootsTestClass.A.class).isPresent());
+    assertFalse(inspector.clazz(InvalidRootsTestClass.B.class).isPresent());
+  }
+
   private Set<String> collectTypes(
       ClassSubject clazz, String methodName, String retValue, String... params) {
     return Stream.concat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java
new file mode 100644
index 0000000..26dc8cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.invalidroot;
+
+public class InvalidRootsTestClass {
+  private static int ID = 0;
+
+  private static String next() {
+    return Integer.toString(ID++);
+  }
+
+  public static void main(String[] args) {
+    InvalidRootsTestClass test = new InvalidRootsTestClass();
+    test.testExtraNeverReturnsNormally();
+    test.testDirectNeverReturnsNormally();
+    test.testInitNeverReturnsNormally();
+    test.testRootInvalidatesAfterInlining();
+  }
+
+  private synchronized void testExtraNeverReturnsNormally() {
+    testExtraNeverReturnsNormallyA();
+    testExtraNeverReturnsNormallyB();
+
+    try {
+      NeverReturnsNormally a = new NeverReturnsNormally();
+      neverReturnsNormallyExtra(next(), a);
+    } catch (RuntimeException re) {
+      System.out.println(re.toString());
+    }
+  }
+
+  private synchronized void testExtraNeverReturnsNormallyA() {
+    try {
+      neverReturnsNormallyExtra(next(), null);
+    } catch (RuntimeException re) {
+      System.out.println(re.toString());
+    }
+  }
+
+  private synchronized void testExtraNeverReturnsNormallyB() {
+    try {
+      neverReturnsNormallyExtra(next(), null);
+    } catch (RuntimeException re) {
+      System.out.println(re.toString());
+    }
+  }
+
+  private synchronized void testDirectNeverReturnsNormally() {
+    try {
+      NeverReturnsNormally a = new NeverReturnsNormally();
+      System.out.println(a.foo());
+    } catch (RuntimeException re) {
+      System.out.println(re.toString());
+    }
+  }
+
+  private synchronized void testInitNeverReturnsNormally() {
+    try {
+      new InitNeverReturnsNormally();
+    } catch (RuntimeException re) {
+      System.out.println(re.toString());
+    }
+  }
+
+  private void neverReturnsNormallyExtra(String prefix, NeverReturnsNormally a) {
+    throw new RuntimeException("neverReturnsNormallyExtra(" +
+        prefix + ", " + (a == null ? "null" : a.foo()) + "): " + next());
+  }
+
+  public static class NeverReturnsNormally {
+    public String foo() {
+      throw new RuntimeException("NeverReturnsNormally::foo(): " + next());
+    }
+  }
+
+  public static class InitNeverReturnsNormally {
+    public InitNeverReturnsNormally() {
+      throw new RuntimeException("InitNeverReturnsNormally::init(): " + next());
+    }
+
+    public String foo() {
+      return "InitNeverReturnsNormally::foo(): " + next();
+    }
+  }
+
+  private synchronized void testRootInvalidatesAfterInlining() {
+    A a = new A();
+    try {
+      notInlinedExtraMethod(next(), a);
+      System.out.println(new B().foo() + " " + next());
+      testRootInvalidatesAfterInliningA(a);
+      testRootInvalidatesAfterInliningB(a);
+    } catch (RuntimeException re) {
+      System.out.println(re.toString());
+    }
+  }
+
+  private void notInlinedExtraMethod(String prefix, A a) {
+    System.out.println("notInlinedExtraMethod(" +
+        prefix + ", " + (a == null ? "null" : a.foo()) + "): " + next());
+    if (a != null) {
+      throw new RuntimeException(
+          "notInlinedExtraMethod(" + prefix + ", " + a.foo() + "): " + next());
+    }
+    System.out.println("notInlinedExtraMethod(" + prefix + ", null): " + next());
+  }
+
+  private void testRootInvalidatesAfterInliningA(A a) {
+    notInlinedExtraMethod(next(), a);
+  }
+
+  private void testRootInvalidatesAfterInliningB(A a) {
+    notInlinedExtraMethod(next(), a);
+  }
+
+  public static class A {
+    public String foo() {
+      return "B::foo(" + next() + ")";
+    }
+  }
+
+  public static class B {
+    public String foo() {
+      return "B::foo(" + next() + ")";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index ba04454..82381dd 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -131,6 +131,10 @@
       return addMethod("public final", name, argumentTypes, returnType, lines);
     }
 
+    public MethodSignature addVirtualMethod(String name, String returnType, String... lines) {
+      return addVirtualMethod(name, ImmutableList.of(), returnType, lines);
+    }
+
     public MethodSignature addVirtualMethod(
         String name,
         List<String> argumentTypes,
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index 6d16184..5cc07a4 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.GraphLense;
@@ -44,7 +45,7 @@
 
   private DexApplication program;
   DexItemFactory dexItemFactory;
-  private AppInfoWithSubtyping appInfo;
+  private AppView<AppInfoWithSubtyping> appView;
 
   NamingTestBase(
       String test,
@@ -61,7 +62,7 @@
   public void readApp() throws IOException, ExecutionException {
     program = ToolHelper.buildApplication(ImmutableList.of(appFileName));
     dexItemFactory = program.dexItemFactory;
-    appInfo = new AppInfoWithSubtyping(program);
+    appView = new AppView<>(new AppInfoWithSubtyping(program), GraphLense.getIdentityLense());
   }
 
   NamingLens runMinifier(List<Path> configPaths)
@@ -73,12 +74,12 @@
 
     ExecutorService executor = ThreadUtils.getExecutorService(1);
 
+    AppInfoWithSubtyping appInfo = appView.getAppInfo();
     RootSet rootSet = new RootSetBuilder(appInfo, program, configuration.getRules(), options)
         .run(executor);
 
     if (options.proguardConfiguration.isAccessModificationAllowed()) {
-      ClassAndMemberPublicizer.run(
-          executor, timing, program, appInfo, rootSet, GraphLense.getIdentityLense());
+      ClassAndMemberPublicizer.run(executor, timing, program, appView, rootSet);
       rootSet =
           new RootSetBuilder(appInfo, program, configuration.getRules(), options).run(executor);
     }
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index 3e46098..c929eaf 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -22,12 +22,12 @@
 import com.google.common.collect.ImmutableList;
 import java.util.Iterator;
 import java.util.function.BiConsumer;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class NeverReturnsNormallyTest extends TestBase {
   private void runTest(
-      BiConsumer<DexInspector, CompilationMode> inspection, CompilationMode mode) throws Exception {
+      BiConsumer<DexInspector, CompilationMode> inspection,
+      boolean enableClassInliner, CompilationMode mode) throws Exception {
     R8Command.Builder builder = R8Command.builder();
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
@@ -49,7 +49,8 @@
             "-allowaccessmodification"
         ),
         Origin.unknown());
-    AndroidApp app = ToolHelper.runR8(builder.build());
+    AndroidApp app = ToolHelper.runR8(builder.build(),
+        opts -> opts.enableClassInlining = enableClassInliner);
     inspection.accept(new DexInspector(app), mode);
 
     // Run on Art to check generated code against verifier.
@@ -128,10 +129,11 @@
     return instructions.next();
   }
 
-  @Ignore("b/110736241")
   @Test
   public void test() throws Exception {
-    runTest(this::validate, CompilationMode.DEBUG);
-    runTest(this::validate, CompilationMode.RELEASE);
+    runTest(this::validate, true, CompilationMode.DEBUG);
+    runTest(this::validate, true, CompilationMode.RELEASE);
+    runTest(this::validate, false, CompilationMode.DEBUG);
+    runTest(this::validate, false, CompilationMode.RELEASE);
   }
 }
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 c176ff3..1379b95 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.DiagnosticsChecker.checkDiagnostics;
 import static com.android.tools.r8.shaking.ProguardConfigurationSourceStrings.createConfigurationForTesting;
+import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -14,9 +16,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import static org.hamcrest.core.StringContains.containsString;
-
-import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -25,10 +24,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.position.TextPosition;
-import com.android.tools.r8.position.TextRange;
 import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
 import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
@@ -153,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
@@ -694,7 +696,7 @@
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     Path path = Paths.get(PACKAGE_OBFUSCATION_5);
     parser.parse(path);
-    checkDiagnostic(handler.warnings, path, 6, 1,
+    checkDiagnostics(handler.warnings, path, 6, 1,
         "repackageclasses", "overrides", "flattenpackagehierarchy");
     ProguardConfiguration config = parser.getConfig();
     assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
@@ -708,7 +710,7 @@
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     Path path = Paths.get(PACKAGE_OBFUSCATION_6);
     parser.parse(path);
-    checkDiagnostic(handler.warnings, path, 6, 1,
+    checkDiagnostics(handler.warnings, path, 6, 1,
         "repackageclasses", "overrides", "flattenpackagehierarchy");
     ProguardConfiguration config = parser.getConfig();
     assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
@@ -735,7 +737,7 @@
       parser.parse(path);
       fail("Expect to fail due to the lack of file name.");
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, path, 6, 14, "File name expected");
+      checkDiagnostics(handler.errors, path, 6, 14, "File name expected");
     }
   }
 
@@ -755,7 +757,7 @@
           .parse(path);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, path, 6, 10,"does-not-exist.flags");
+      checkDiagnostics(handler.errors, path, 6, 10,"does-not-exist.flags");
     }
   }
 
@@ -767,7 +769,7 @@
           .parse(path);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, path, 6,2, "does-not-exist.flags");
+      checkDiagnostics(handler.errors, path, 6,2, "does-not-exist.flags");
     }
   }
 
@@ -866,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();
   }
@@ -930,7 +932,7 @@
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     Path path = Paths.get(DONT_OPTIMIZE_OVERRIDES_PASSES);
     parser.parse(path);
-    checkDiagnostic(handler.warnings, path, 7, 1,
+    checkDiagnostics(handler.warnings, path, 7, 1,
         "Ignoring", "-optimizationpasses");
     ProguardConfiguration config = parser.getConfig();
     assertFalse(config.isOptimizing());
@@ -942,7 +944,7 @@
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     Path path = Paths.get(OPTIMIZATION_PASSES);
     parser.parse(path);
-    checkDiagnostic(handler.warnings, path, 5, 1,
+    checkDiagnostics(handler.warnings, path, 5, 1,
         "Ignoring", "-optimizationpasses");
     ProguardConfiguration config = parser.getConfig();
     assertTrue(config.isOptimizing());
@@ -957,7 +959,7 @@
       parser.parse(path);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, path, 6, 1, "Missing n");
+      checkDiagnostics(handler.errors, path, 6, 1, "Missing n");
     }
   }
 
@@ -970,8 +972,8 @@
       parser.parse(path);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, path, 5, 1, "Unsupported option",
-          "-skipnonpubliclibraryclasses");
+      checkDiagnostics(handler.errors, path, 5, 1,
+          "Unsupported option", "-skipnonpubliclibraryclasses");
     }
   }
 
@@ -1052,7 +1054,8 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 1, 1, "Unknown option", "-keepclassx");
+      checkDiagnostics(handler.errors, proguardConfig, 1, 1,
+          "Unknown option", "-keepclassx");
     }
   }
 
@@ -1325,7 +1328,7 @@
         parser.parse(createConfigurationForTesting(ImmutableList.of(option + " ,")));
         fail("Expect to fail due to the lack of path filter.");
       } catch (AbortException e) {
-        checkDiagnostic(handler.errors, null, 1, option.length() + 2, "Path filter expected");
+        checkDiagnostics(handler.errors, null, 1, option.length() + 2, "Path filter expected");
       }
     }
   }
@@ -1340,7 +1343,7 @@
         parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,,yyy")));
         fail("Expect to fail due to the lack of path filter.");
       } catch (AbortException e) {
-        checkDiagnostic(handler.errors, null, 1, option.length() + 6, "Path filter expected");
+        checkDiagnostics(handler.errors, null, 1, option.length() + 6, "Path filter expected");
       }
     }
   }
@@ -1355,7 +1358,23 @@
         parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,")));
         fail("Expect to fail due to the lack of path filter.");
       } catch (AbortException e) {
-        checkDiagnostic(handler.errors, null, 1, option.length() + 6, "Path filter expected");
+        checkDiagnostics(handler.errors, null, 1, option.length() + 6, "Path filter expected");
+      }
+    }
+  }
+
+  @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 + "\"");
       }
     }
   }
@@ -1438,7 +1457,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 2, 13,
+      checkDiagnostics(handler.errors, proguardConfig, 2, 13,
           "Use of generics not allowed for java type");
     }
     verifyFailWithProguard6(proguardConfig, "Use of generics not allowed for java type");
@@ -1456,7 +1475,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 2, 13,
+      checkDiagnostics(handler.errors, proguardConfig, 2, 13,
           "Use of generics not allowed for java type");
     }
     verifyFailWithProguard6(proguardConfig, "Use of generics not allowed for java type");
@@ -1489,7 +1508,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(proguardConfig);
-    verifyParserEndsCleanly();
+    checkDiagnostics(handler.warnings, proguardConfig, 3, 7, "The field name \"id<<*>>\" is");
 
     verifyWithProguard6(proguardConfig);
   }
@@ -1508,7 +1527,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 4, 2,
+      checkDiagnostics(handler.errors, proguardConfig, 4, 2,
           "Wildcard", "<4>", "invalid");
     }
     verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (4,");
@@ -1526,7 +1545,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 2, 13,
+      checkDiagnostics(handler.errors, proguardConfig, 2, 13,
           "Wildcard", "<0>", "invalid");
     }
     verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (0,");
@@ -1544,7 +1563,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 3, 1,
+      checkDiagnostics(handler.errors, proguardConfig, 3, 1,
           "Wildcard", "<4>", "invalid");
     }
     verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (4,");
@@ -1562,7 +1581,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 3, 1,
+      checkDiagnostics(handler.errors, proguardConfig, 3, 1,
           "Wildcard", "<2>", "invalid");
     }
     verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (2,");
@@ -1584,7 +1603,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 6, 2,
+      checkDiagnostics(handler.errors, proguardConfig, 6, 2,
           "Wildcard", "<3>", "invalid");
     }
     verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (3,");
@@ -1602,7 +1621,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+      checkDiagnostics(handler.errors, proguardConfig, 1, 1,
           "Expecting", "'-keep'", "after", "'-if'");
     }
     verifyFailWithProguard6(proguardConfig, "Expecting '-keep' option after '-if' option");
@@ -1619,7 +1638,7 @@
       parser.parse(proguardConfig);
       fail();
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+      checkDiagnostics(handler.errors, proguardConfig, 1, 1,
           "Expecting", "'-keep'", "after", "'-if'");
     }
     verifyFailWithProguard6(proguardConfig, "Expecting '-keep' option after '-if' option");
@@ -1649,7 +1668,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(proguardConfig);
-    checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+    checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
         "Ignoring", "-assumenoexternalsideeffects");
   }
 
@@ -1663,7 +1682,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(proguardConfig);
-    checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+    checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
         "Ignoring", "-assumenoescapingparameters");
   }
 
@@ -1687,7 +1706,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(proguardConfig);
-    checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+    checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
         "Ignoring", "-assumenoexternalreturnvalues");
   }
 
@@ -1721,7 +1740,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(proguardConfig);
-    checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+    checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
         "Ignoring", "-addconfigurationdebugging");
   }
 
@@ -1735,7 +1754,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(proguardConfig);
-    verifyParserEndsCleanly();
+    checkDiagnostics(handler.warnings, proguardConfig, 2, 5, "The field name \"<fields>\" is");
     verifyWithProguard(proguardConfig);
   }
 
@@ -1757,13 +1776,33 @@
     verifyWithProguard(proguardConfig);
   }
 
+  @Test
+  public void parse_regress110021323() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+      "-keepclassmembernames class A {",
+      "  <public methods>;",
+      "  <public fields>;",
+      "}"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    assertEquals(4, handler.warnings.size());
+    checkDiagnostics(handler.warnings, 0, proguardConfig, 2, 3, "The type \"<public\" is");
+    checkDiagnostics(handler.warnings, 1, proguardConfig, 2, 11, "The field name \"methods>\" is");
+    checkDiagnostics(handler.warnings, 2, proguardConfig, 3, 3, "The type \"<public\" is");
+    checkDiagnostics(handler.warnings, 3, proguardConfig, 3, 11, "The field name \"fields>\" is");
+
+    verifyWithProguard(proguardConfig);
+  }
+
   public void testNotSupported(String option) {
     try {
       reset();
       parser.parse(createConfigurationForTesting(ImmutableList.of(option)));
       fail("Expect to fail due to unsupported option.");
     } catch (AbortException e) {
-      checkDiagnostic(handler.errors, null, 1, 1, "Option " + option + " currently not supported");
+      checkDiagnostics(handler.errors, null, 1, 1, "Option " + option + " currently not supported");
     }
   }
 
@@ -1787,31 +1826,6 @@
     assertEquals(0, handler.errors.size());
   }
 
-  // TODO(sgjesse): Change to use DiagnosticsChecker.
-  private Diagnostic checkDiagnostic(List<Diagnostic> diagnostics, Path path, int lineStart,
-      int columnStart, String... messageParts) {
-    assertEquals(1, diagnostics.size());
-    Diagnostic diagnostic = diagnostics.get(0);
-    if (path != null) {
-      assertEquals(path, ((PathOrigin) diagnostic.getOrigin()).getPath());
-    } else {
-      assertSame(Origin.unknown(), diagnostic.getOrigin());
-    }
-    TextPosition position;
-    if (diagnostic.getPosition() instanceof TextRange) {
-      position = ((TextRange) diagnostic.getPosition()).getStart();
-    } else {
-      position = ((TextPosition) diagnostic.getPosition());
-    }
-    assertEquals(lineStart, position.getLine());
-    assertEquals(columnStart, position.getColumn());
-    for (String part : messageParts) {
-      assertTrue(diagnostic.getDiagnosticMessage() + " doesn't contain \"" + part + "\"",
-          diagnostic.getDiagnosticMessage().contains(part));
-    }
-    return diagnostic;
-  }
-
   private void verifyWithProguard(Path proguardConfig) throws Exception {
     if (isRunProguard()) {
       // Add a keep rule for the test class as Proguard will fail if the resulting output jar is
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
index 5845dd3..e543d46 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
@@ -86,18 +86,24 @@
         "  public int method();",
         "}"
     ), this::defaultMethodKept);
-    runTest(ImmutableList.of(
-        "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
-        "  <methods>;",
-        "}"
-    ), this::defaultMethodNotKept);
-    runTest(ImmutableList.of(
-        "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
-        "  <methods>;",
-        "}",
-        "-keep class " + TestClass.class.getCanonicalName() + "{",
-        "  public void useInterfaceMethod();",
-        "}"
-    ), this::defaultMethodAbstract);
+    runTest(
+        ImmutableList.of(
+            "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
+            "  <methods>;",
+            "}",
+            // Prevent InterfaceWithDefaultMethods from being merged into ClassImplementingInterface
+            "-keep class " + InterfaceWithDefaultMethods.class.getCanonicalName()),
+        this::defaultMethodNotKept);
+    runTest(
+        ImmutableList.of(
+            "-keep class " + ClassImplementingInterface.class.getCanonicalName() + "{",
+            "  <methods>;",
+            "}",
+            "-keep class " + TestClass.class.getCanonicalName() + "{",
+            "  public void useInterfaceMethod();",
+            "}",
+            // Prevent InterfaceWithDefaultMethods from being merged into ClassImplementingInterface
+            "-keep class " + InterfaceWithDefaultMethods.class.getCanonicalName()),
+        this::defaultMethodAbstract);
   }
 }
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=$?