Merge "Add Proguard testing rules for controlling inlining"
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/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 7d17f11..001e5b6 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -230,7 +230,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..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/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/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 ccc1ccf..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.
@@ -72,6 +75,7 @@
     ALWAYS_INLINE,
     CONFLICT,
     ILLEGAL_ACCESS,
+    NATIVE_METHOD,
     NO_SIDE_EFFECTS,
     PINNED_SOURCE,
     RESOLUTION_FOR_FIELDS_MAY_CHANGE,
@@ -99,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;
@@ -139,40 +146,61 @@
   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, 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
@@ -194,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);
@@ -241,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)
@@ -248,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
@@ -272,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);
@@ -280,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;
   }
 
@@ -335,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() {
@@ -394,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);
       }
     }
 
@@ -414,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) {
@@ -596,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
@@ -1143,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);
@@ -1167,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()) {
@@ -1178,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;
@@ -1192,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 -> {
@@ -1216,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;
@@ -1226,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) {
@@ -1246,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;
@@ -1262,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/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/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 516e39d..b3e0ad1 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(...);
 }
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/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/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/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index 59accb3..e0d73e8 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;
@@ -343,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.
@@ -391,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(
@@ -411,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 =
@@ -420,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
@@ -496,6 +515,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/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/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..f0c74d9 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 0 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()
@@ -285,9 +295,12 @@
   print('Generated apks available at: %s' % ' '.join(apks))
   if options.install:
     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=$?