Merge "Add more split benchmarking"
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/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/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/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 640172f..d64eaf0 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;
@@ -169,6 +172,21 @@
             DescriptorUtils.getDescriptorFromClassBinaryName(tryCatchBlockNode.type))));
   }
 
+  public Constraint computeInliningConstraint(
+      AppInfoWithLiveness appInfo, GraphLense graphLense, DexType invocationContext) {
+    InliningConstraintVisitor visitor =
+        new InliningConstraintVisitor(application, appInfo, graphLense, method, invocationContext);
+    AbstractInsnNode insn = node.instructions.getFirst();
+    while (insn != null) {
+      insn.accept(visitor);
+      if (visitor.isFinished()) {
+        break;
+      }
+      insn = insn.getNext();
+    }
+    return visitor.getConstraint();
+  }
+
   @Override
   public String toString() {
     triggerDelayedParsingIfNeccessary();
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..1ad50bc 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;
@@ -113,7 +114,7 @@
 
   @Override
   public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return inliningConstraintForSinlgeTargetInvoke(info, invocationContext);
+    return new InliningConstraints(info).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..0fb447b 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;
@@ -95,7 +96,7 @@
 
   @Override
   public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return inliningConstraintForVirtualInvoke(info, invocationContext);
+    return new InliningConstraints(info).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..56b659a 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,9 +5,7 @@
 
 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;
@@ -85,85 +83,11 @@
     return lookupSingleTarget(appInfo, appInfo.dexItemFactory.objectType);
   }
 
+  // TODO(christofferqa): Pass an instance of InliningConstraints instead of [info] when
+  // InliningConstraints is complete.
   @Override
-  public abstract Constraint inliningConstraint(AppInfoWithLiveness info,
-      DexType invocationContext);
-
-  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 Constraint inliningConstraint(
+      AppInfoWithLiveness info, DexType invocationContext);
 
   public abstract InlineAction computeInlining(InliningOracle decider, DexType invocationContext);
 
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..91f657d 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;
@@ -126,7 +127,8 @@
 
   @Override
   public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return Constraint.NEVER;
+    return new InliningConstraints(info)
+        .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..b985916 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;
@@ -103,7 +104,7 @@
 
   @Override
   public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return inliningConstraintForSinlgeTargetInvoke(info, invocationContext);
+    return new InliningConstraints(info).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..64f0d7b 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;
@@ -112,7 +113,6 @@
 
   @Override
   public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    // The semantics of invoke super depend on the context.
-    return Constraint.SAMECLASS;
+    return new InliningConstraints(info).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..85c80ed 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;
@@ -95,7 +96,7 @@
 
   @Override
   public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
-    return inliningConstraintForVirtualInvoke(info, invocationContext);
+    return new InliningConstraints(info).forInvokeVirtual(getInvokedMethod(), invocationContext);
   }
 
   @Override
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..d4a06cd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -0,0 +1,148 @@
+// 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.graph.AppInfo.ResolutionResult;
+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.graph.GraphLense;
+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.
+//
+// TODO(christofferqa): This class is incomplete.
+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) {
+    this.appInfo = appInfo;
+    this.graphLense = graphLense;
+  }
+
+  public Constraint forCheckCast(DexType type, DexType invocationContext) {
+    return Constraint.classIsVisible(invocationContext, type, appInfo);
+  }
+
+  public Constraint forInvokeDirect(DexMethod method, DexType invocationContext) {
+    return forSingleTargetInvoke(method, appInfo.lookupDirectTarget(method), invocationContext);
+  }
+
+  public Constraint forInvokeInterface(DexMethod method, DexType invocationContext) {
+    return forVirtualInvoke(method, appInfo.lookupInterfaceTargets(method), invocationContext);
+  }
+
+  public Constraint forInvokePolymorphic(DexMethod method, DexType invocationContext) {
+    return Constraint.NEVER;
+  }
+
+  public Constraint forInvokeStatic(DexMethod method, DexType invocationContext) {
+    return forSingleTargetInvoke(method, appInfo.lookupStaticTarget(method), 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) {
+    return forVirtualInvoke(method, appInfo.lookupVirtualTargets(method), invocationContext);
+  }
+
+  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/jar/InliningConstraintVisitor.java b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
new file mode 100644
index 0000000..5652be8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
@@ -0,0 +1,128 @@
+// 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.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.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;
+
+// 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.
+//
+// TODO(christofferqa): This class is incomplete. Still need to add support for ConstClass,
+// InstanceGet, InstanceOf, InstancePut, Monitor, MoveException, NewArrayEmpty, NewInstance,
+// StaticGet, StaticPut, etc.
+public class InliningConstraintVisitor extends MethodVisitor {
+
+  private final JarApplicationReader application;
+  private final AppInfoWithLiveness appInfo;
+  private final InliningConstraints inliningConstraints;
+  private final DexMethod method;
+  private final DexType invocationContext;
+
+  private Constraint constraint = Constraint.ALWAYS;
+
+  public InliningConstraintVisitor(
+      JarApplicationReader application,
+      AppInfoWithLiveness appInfo,
+      GraphLense graphLense,
+      DexMethod method,
+      DexType invocationContext) {
+    super(ASM6);
+    this.application = application;
+    this.appInfo = appInfo;
+    this.inliningConstraints = new InliningConstraints(appInfo, graphLense);
+    this.method = method;
+    this.invocationContext = invocationContext;
+  }
+
+  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;
+  }
+
+  @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);
+    switch (opcode) {
+      case Opcodes.INVOKEINTERFACE:
+        updateConstraint(inliningConstraints.forInvokeInterface(target, invocationContext));
+        break;
+
+      case Opcodes.INVOKESPECIAL:
+        if (name.equals(Constants.INSTANCE_INITIALIZER_NAME) || ownerType == invocationContext) {
+          updateConstraint(inliningConstraints.forInvokeDirect(target, invocationContext));
+        } else {
+          updateConstraint(inliningConstraints.forInvokeSuper(target, invocationContext));
+        }
+        break;
+
+      case Opcodes.INVOKESTATIC:
+        updateConstraint(inliningConstraints.forInvokeStatic(target, invocationContext));
+        break;
+
+      case Opcodes.INVOKEVIRTUAL:
+        // Instructions that target a private method in the same class are translated to
+        // invoke-direct.
+        if (target.holder == method.holder) {
+          DexClass clazz = appInfo.definitionFor(target.holder);
+          if (clazz != null && clazz.lookupDirectMethod(target) != null) {
+            updateConstraint(inliningConstraints.forInvokeDirect(target, invocationContext));
+            break;
+          }
+        }
+
+        updateConstraint(inliningConstraints.forInvokeVirtual(target, invocationContext));
+        break;
+
+      default:
+        throw new Unreachable("Unexpected opcode " + opcode);
+    }
+  }
+
+  @Override
+  public void visitTypeInsn(int opcode, String typeName) {
+    DexType type = application.getTypeFromName(typeName);
+    switch (opcode) {
+      case Opcodes.ANEWARRAY:
+        break;
+
+      case Opcodes.CHECKCAST:
+        updateConstraint(inliningConstraints.forCheckCast(type, invocationContext));
+        break;
+
+      case Opcodes.INSTANCEOF:
+        break;
+
+      case Opcodes.NEW:
+        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 04f8a3b..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;
@@ -31,42 +31,34 @@
 
 public final class ClassAndMemberPublicizer {
   private final DexApplication application;
-  private final AppInfo appInfo;
+  private final AppView appView;
   private final RootSet rootSet;
-  private final GraphLense previousLense;
   private final PublicizedLenseBuilder lenseBuilder;
 
   private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
   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.previousLense = 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)
@@ -84,11 +76,11 @@
 
     // 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, previousLense);
+    return lenseBuilder.build(appView);
   }
 
   private Runnable computeMethodPoolPerClass(DexClass clazz) {
@@ -145,7 +137,7 @@
       return false;
     }
 
-    if (appInfo.dexItemFactory.isClassConstructor(encodedMethod.method)) {
+    if (appView.getDexItemFactory().isClassConstructor(encodedMethod.method)) {
       return false;
     }
 
@@ -156,7 +148,7 @@
     }
     assert accessFlags.isPrivate();
 
-    if (appInfo.dexItemFactory.isConstructor(encodedMethod.method)) {
+    if (appView.getDexItemFactory().isConstructor(encodedMethod.method)) {
       // TODO(b/72211928)
       return false;
     }
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/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 3dc03f8..0ed3c11 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;
@@ -39,6 +42,7 @@
 import com.google.common.collect.ImmutableSet;
 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,7 +59,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Predicate;
-import java.util.stream.Collectors;
 
 /**
  * Merges Supertypes with a single implementation into their single subtype.
@@ -143,45 +146,58 @@
   private final Timing timing;
   private Collection<DexMethod> invokes;
 
+  // Set of merge candidates.
+  private final Set<DexProgramClass> mergeCandidates = new HashSet<>();
+
   // 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, pinnedTypes, AbortReason.NATIVE_METHOD);
+          markTypeAsPinned(clazz.type, AbortReason.NATIVE_METHOD);
         }
       }
     }
@@ -206,37 +222,35 @@
         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;
-        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.
@@ -255,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)
@@ -262,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
@@ -286,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);
@@ -294,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;
   }
 
@@ -349,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.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() {
@@ -408,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);
       }
     }
 
@@ -428,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) {
@@ -610,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
@@ -1157,22 +1253,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);
@@ -1181,7 +1273,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()) {
@@ -1192,8 +1284,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;
@@ -1206,11 +1297,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 -> {
@@ -1230,8 +1321,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;
@@ -1240,13 +1333,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) {
@@ -1260,12 +1348,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;
@@ -1276,83 +1359,52 @@
     }
   }
 
-  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(
+              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 static class SingleTypeMapperGraphLense extends GraphLense {
 
-    public boolean isInliningDisallowed() {
-      return disallowInlining;
-    }
+    private final DexType source;
+    private final DexType target;
 
-    private boolean allowInlining() {
-      return true;
-    }
-
-    private boolean disallowInlining() {
-      disallowInlining = true;
-      return true;
+    public SingleTypeMapperGraphLense(DexType source, DexType target) {
+      this.source = source;
+      this.target = target;
     }
 
     @Override
-    public boolean registerInvokeInterface(DexMethod method) {
-      return disallowInlining();
+    public DexType lookupType(DexType type) {
+      return type == source ? target : type;
     }
 
     @Override
-    public boolean registerInvokeVirtual(DexMethod method) {
-      return disallowInlining();
+    public GraphLenseLookupResult lookupMethod(
+        DexMethod method, DexEncodedMethod context, Type type) {
+      throw new Unreachable();
     }
 
     @Override
-    public boolean registerInvokeDirect(DexMethod method) {
-      return allowInlining();
+    public DexField lookupField(DexField field) {
+      throw new Unreachable();
     }
 
     @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();
+    public boolean isContextFreeForMethods() {
+      throw new Unreachable();
     }
   }
 
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 c2df29a..e0d73e8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -23,6 +23,8 @@
 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;
@@ -345,47 +347,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.
@@ -393,17 +405,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(
@@ -413,7 +432,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 =
@@ -422,24 +440,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
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);
     }