Merge "Reproduce b/80083341."
diff --git a/LIBRARY-LICENSE b/LIBRARY-LICENSE
index c19c9c3..12674e3 100644
--- a/LIBRARY-LICENSE
+++ b/LIBRARY-LICENSE
@@ -3,11 +3,11 @@
   copyrightHolder: The Guava Authors
   license: The Apache Software License, Version 2.0
   licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
-- artifact: com.googlecode.json-simple:json-simple:+
-  name: JSON.Simple
+- artifact: com.google.code.gson:gson:+
+  name: Gson
   license: The Apache Software License, Version 2.0
   licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
-  url: http://code.google.com/p/json-simple/
+  url: https://github.com/google/gson
 - artifact: it.unimi.dsi:fastutil:+
   name: fastutil
   license: Apache License, Version 2.0
diff --git a/build.gradle b/build.gradle
index 0f782b2..8ee6b41 100644
--- a/build.gradle
+++ b/build.gradle
@@ -34,7 +34,7 @@
     fastutilVersion = '7.2.0'
     guavaVersion = '23.0'
     joptSimpleVersion = '4.6'
-    jsonSimpleVersion = '1.1'
+    gsonVersion = '2.7'
     junitVersion = '4.12'
     kotlinVersion = '1.2.30'
     protobufVersion = '3.0.0'
@@ -205,7 +205,7 @@
 
 dependencies {
     compile "net.sf.jopt-simple:jopt-simple:$joptSimpleVersion"
-    compile "com.googlecode.json-simple:json-simple:$jsonSimpleVersion"
+    compile "com.google.code.gson:gson:$gsonVersion"
     // Include all of guava when compiling the code, but exclude annotations that we don't
     // need from the packaging.
     compileOnly("com.google.guava:guava:$guavaVersion")
@@ -485,11 +485,11 @@
 
 static configureRelocations(ShadowJar task) {
     task.relocate('com.google.common', 'com.android.tools.r8.com.google.common')
+    task.relocate('com.google.gson', 'com.android.tools.r8.com.google.gson')
     task.relocate('com.google.thirdparty', 'com.android.tools.r8.com.google.thirdparty')
     task.relocate('joptsimple', 'com.android.tools.r8.joptsimple')
     task.relocate('org.apache.commons', 'com.android.tools.r8.org.apache.commons')
     task.relocate('org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm')
-    task.relocate('org.json.simple', 'com.android.tools.r8.org.json.simple')
     task.relocate('it.unimi.dsi.fastutil', 'com.android.tools.r8.it.unimi.dsi.fastutil')
 }
 
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index aa99407..2d52c64 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -155,6 +155,7 @@
       // Disable global optimizations.
       options.enableMinification = false;
       options.enableInlining = false;
+      options.enableClassInlining = false;
       options.outline.enabled = false;
 
       DexApplication app = new ApplicationReader(inputApp, options, timing).read(executor);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index bb7c37f..4c65134 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -333,6 +333,8 @@
     // Disable some of R8 optimizations.
     assert internal.enableInlining;
     internal.enableInlining = false;
+    assert internal.enableClassInlining;
+    internal.enableClassInlining = false;
     assert internal.enableSwitchMapRemoval;
     internal.enableSwitchMapRemoval = false;
     assert internal.outline.enabled;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 9d6e40d..dafa35a 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -701,6 +701,7 @@
     if (internal.debug) {
       // TODO(zerny): Should we support inlining in debug mode? b/62937285
       internal.enableInlining = false;
+      internal.enableClassInlining = false;
       // TODO(zerny): Should we support outlining in debug mode? b/62937285
       internal.outline.enabled = false;
     }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index 733f2d9..a30c6e6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -216,9 +216,7 @@
           // Current stack: ..., value1, value2, value1copy
           state.pop();
           // Output stack: ..., value1, value2
-          throw new Unimplemented(
-              "Building IR for CfStackInstruction " + opcode + " not supported");
-          // break;
+          break;
         }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index 31628ad..b36f30c 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -4,11 +4,12 @@
 package com.android.tools.r8.dex;
 
 import com.android.tools.r8.graph.DexString;
-import java.util.Map;
-import java.util.TreeMap;
-import org.json.simple.JSONObject;
-import org.json.simple.parser.JSONParser;
-import org.json.simple.parser.ParseException;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import java.util.Comparator;
+import java.util.Map.Entry;
 
 /**
  * Abstraction for hidden dex marker intended for the main dex file.
@@ -26,22 +27,17 @@
   private static final String D8_PREFIX = PREFIX + Tool.D8 + "{";
   private static final String R8_PREFIX = PREFIX + Tool.R8 + "{";
 
-  private final TreeMap<String, Object> content;
+  private final JsonObject jsonObject;
   private final Tool tool;
 
   public Marker(Tool tool) {
     this.tool = tool;
-    this.content = new TreeMap<>();
+    jsonObject = new JsonObject();
   }
 
-  private Marker(Tool tool, JSONObject object) {
+  private Marker(Tool tool, JsonObject jsonObject) {
     this.tool = tool;
-    content = new TreeMap<>();
-    // This loop is necessary to make the type checker to shut up.
-    for (Object e : object.entrySet()) {
-      Map.Entry<?,?> entry = (Map.Entry<?,?>) e;
-      content.put(String.valueOf(entry.getKey()), entry.getValue());
-    }
+    this.jsonObject = jsonObject;
   }
 
   public Tool getTool() {
@@ -57,70 +53,58 @@
   }
 
   public String getVersion() {
-    return (String) content.get(VERSION);
+    return jsonObject.get(VERSION).getAsString();
   }
 
   public Marker setVersion(String version) {
-    internalPut(VERSION, version);
+    assert !jsonObject.has(VERSION);
+    jsonObject.addProperty(VERSION, version);
     return this;
   }
 
   public Long getMinApi() {
-    return (Long) content.get(MIN_API);
+    return jsonObject.get(MIN_API).getAsLong();
   }
 
   public Marker setMinApi(long minApi) {
-    internalPut(MIN_API, minApi);
+    assert !jsonObject.has(MIN_API);
+    jsonObject.addProperty(MIN_API, minApi);
     return this;
   }
 
   public String getSha1() {
-    return (String) content.get(SHA1);
+    return jsonObject.get(SHA1).getAsString();
   }
 
   public Marker setSha1(String sha1) {
-    internalPut(SHA1, sha1);
-    return this;
-  }
-
-  private Marker internalPut(String key, Object value) {
-    assert (key != null) && (value != null);
-    assert !content.containsKey(key);
-    content.put(key, value);
+    assert !jsonObject.has(SHA1);
+    jsonObject.addProperty(SHA1, sha1);
     return this;
   }
 
   @Override
   public String toString() {
-    // The JSONObject does not support a predictable sorted serialization of the object.
-    // Therefore, a TreeMap is used and iteration is over the keySet.
-    StringBuffer sb = new StringBuffer(PREFIX + tool);
-    boolean first = true;
-    sb.append('{');
-    for (String key : content.keySet()) {
-      if (first) {
-        first = false;
-      } else {
-        sb.append(',');
-      }
-      sb.append(JSONObject.toString(key, content.get(key)));
-    }
-    sb.append('}');
-    return sb.toString();
+    // In order to make printing of markers deterministic we sort the entries by key.
+    final JsonObject sortedJson = new JsonObject();
+    jsonObject.entrySet()
+        .stream()
+        .sorted(Comparator.comparing(Entry::getKey))
+        .forEach(entry -> sortedJson.add(entry.getKey(), entry.getValue()));
+    return PREFIX + tool + sortedJson;
   }
 
   @Override
   public boolean equals(Object obj) {
     if (obj instanceof Marker) {
       Marker other = (Marker) obj;
-      return (tool == other.tool) && content.equals(other.content);
+      return (tool == other.tool) && jsonObject.equals(other.jsonObject);
     }
     return false;
   }
 
   @Override
   public int hashCode() {
-    return tool.hashCode() + 3 * content.hashCode();
+    return tool.hashCode() + 3 * jsonObject.hashCode();
   }
 
   // Try to parse str as a marker.
@@ -142,11 +126,11 @@
 
   private static Marker internalParse(Tool tool, String str) {
     try {
-      Object result =  new JSONParser().parse(str);
-      if (result instanceof JSONObject) {
-        return new Marker(tool, (JSONObject) result);
+      JsonElement result =  new JsonParser().parse(str);
+      if (result.isJsonObject()) {
+        return new Marker(tool, result.getAsJsonObject());
       }
-    } catch (ParseException e) {
+    } catch (JsonSyntaxException e) {
       // Fall through.
     }
     return null;
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 55bc2db..7daef42 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -3,15 +3,21 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
@@ -475,6 +481,40 @@
     return result;
   }
 
+  public boolean canTriggerStaticInitializer(DexType clazz) {
+    Set<DexType> knownInterfaces = Sets.newIdentityHashSet();
+
+    // Process superclass chain.
+    while (clazz != null && clazz != dexItemFactory.objectType) {
+      DexClass definition = definitionFor(clazz);
+      if (definition == null || definition.hasClassInitializer()) {
+        return true; // Assume it *may* trigger if we didn't find the definition.
+      }
+      knownInterfaces.addAll(Arrays.asList(definition.interfaces.values));
+      clazz = definition.superType;
+    }
+
+    // Process interfaces.
+    Queue<DexType> queue = new ArrayDeque<>(knownInterfaces);
+    while (!queue.isEmpty()) {
+      DexType iface = queue.remove();
+      DexClass definition = definitionFor(iface);
+      if (definition == null || definition.hasClassInitializer()) {
+        return true; // Assume it *may* trigger if we didn't find the definition.
+      }
+      if (!definition.accessFlags.isInterface()) {
+        throw new Unreachable(iface.toSourceString() + " is expected to be an interface");
+      }
+
+      for (DexType superIface : definition.interfaces.values) {
+        if (knownInterfaces.add(superIface)) {
+          queue.add(superIface);
+        }
+      }
+    }
+    return false;
+  }
+
   public interface ResolutionResult {
 
     DexEncodedMethod asResultOfResolve();
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 61595b9..6e93a90 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -200,14 +200,19 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
+  public IRCode buildIR(
+      DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
+      InternalOptions options,
+      Origin origin)
       throws ApiLevelException {
-    return internalBuild(encodedMethod, options, null, null, origin);
+    return internalBuild(encodedMethod, appInfo, options, null, null, origin);
   }
 
   @Override
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
@@ -215,11 +220,13 @@
       throws ApiLevelException {
     assert valueNumberGenerator != null;
     assert callerPosition != null;
-    return internalBuild(encodedMethod, options, valueNumberGenerator, callerPosition, origin);
+    return internalBuild(
+        encodedMethod, appInfo, options, valueNumberGenerator, callerPosition, origin);
   }
 
   private IRCode internalBuild(
       DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
       InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition,
@@ -230,8 +237,8 @@
     CfSourceCode source = new CfSourceCode(this, encodedMethod, callerPosition, origin);
     IRBuilder builder =
         (generator == null)
-            ? new IRBuilder(encodedMethod, source, options)
-            : new IRBuilder(encodedMethod, source, options, generator);
+            ? new IRBuilder(encodedMethod, appInfo, source, options)
+            : new IRBuilder(encodedMethod, appInfo, source, options, generator);
     return builder.build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 5a4faff..6f86412 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -18,11 +18,15 @@
 public abstract class Code extends CachedHashValueDexItem {
 
   public abstract IRCode buildIR(
-      DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
+      DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
+      InternalOptions options,
+      Origin origin)
       throws ApiLevelException;
 
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index d9560c8..685f32e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -164,19 +164,20 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
+  public IRCode buildIR(DexEncodedMethod encodedMethod, AppInfo appInfo,
+      InternalOptions options, Origin origin)
       throws ApiLevelException {
     DexSourceCode source =
         new DexSourceCode(
             this, encodedMethod, null, options.lineNumberOptimization == LineNumberOptimization.ON);
-    IRBuilder builder = new IRBuilder(encodedMethod, source, options);
+    IRBuilder builder = new IRBuilder(encodedMethod, appInfo, source, options);
     return builder.build();
   }
 
   @Override
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
-      InternalOptions options,
+      AppInfo appInfo, InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin)
@@ -187,7 +188,8 @@
             encodedMethod,
             callerPosition,
             options.lineNumberOptimization == LineNumberOptimization.ON);
-    IRBuilder builder = new IRBuilder(encodedMethod, source, options, valueNumberGenerator);
+    IRBuilder builder =
+        new IRBuilder(encodedMethod, appInfo, source, options, valueNumberGenerator);
     return builder.build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 0cabaf1..ee29ff3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -44,9 +44,12 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableMap;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.function.Consumer;
 
 public class DexEncodedMethod extends KeyedDexItem<DexMethod> implements ResolutionResult {
@@ -228,23 +231,25 @@
     compilationState = CompilationState.NOT_PROCESSED;
   }
 
-  public IRCode buildIR(InternalOptions options, Origin origin) throws ApiLevelException {
-    return code == null ? null : code.buildIR(this, options, origin);
+  public IRCode buildIR(
+      AppInfo appInfo, InternalOptions options, Origin origin) throws ApiLevelException {
+    return code == null ? null : code.buildIR(this, appInfo, options, origin);
   }
 
   public IRCode buildInliningIRForTesting(
       InternalOptions options, ValueNumberGenerator valueNumberGenerator)
       throws ApiLevelException {
-    return buildInliningIR(options, valueNumberGenerator, null, Origin.unknown());
+    return buildInliningIR(null, options, valueNumberGenerator, null, Origin.unknown());
   }
 
   public IRCode buildInliningIR(
-      InternalOptions options,
+      AppInfo appInfo, InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin)
       throws ApiLevelException {
-    return code.buildInliningIR(this, options, valueNumberGenerator, callerPosition, origin);
+    return code.buildInliningIR(
+        this, appInfo, options, valueNumberGenerator, callerPosition, origin);
   }
 
   public void setCode(Code code) {
@@ -564,6 +569,8 @@
     private boolean useIdentifierNameString = false;
     private boolean checksNullReceiverBeforeAnySideEffect = false;
     private boolean triggersClassInitBeforeAnySideEffect = false;
+    private Set<DexField> receiverOnlyUsedForReadingFields = null;
+    private Map<DexField, Integer> onlyInitializesFieldsWithNoOtherSideEffects = null;
 
     private OptimizationInfo() {
       // Intentionally left empty.
@@ -600,6 +607,15 @@
       return returnsConstant;
     }
 
+    public boolean isReceiverOnlyUsedForReadingFields(Set<DexField> fields) {
+      return receiverOnlyUsedForReadingFields != null &&
+          fields.containsAll(receiverOnlyUsedForReadingFields);
+    }
+
+    public Map<DexField, Integer> onlyInitializesFieldsWithNoOtherSideEffects() {
+      return onlyInitializesFieldsWithNoOtherSideEffects;
+    }
+
     public long getReturnedConstant() {
       assert returnsConstant();
       return returnedConstant;
@@ -641,6 +657,19 @@
       returnedConstant = value;
     }
 
+    private void markReceiverOnlyUsedForReadingFields(Set<DexField> fields) {
+      receiverOnlyUsedForReadingFields = fields;
+    }
+
+    private void markOnlyInitializesFieldsWithNoOtherSideEffects(Map<DexField, Integer> mapping) {
+      if (mapping == null) {
+        onlyInitializesFieldsWithNoOtherSideEffects = null;
+      } else {
+        onlyInitializesFieldsWithNoOtherSideEffects = mapping.isEmpty()
+            ? Collections.emptyMap() : ImmutableMap.copyOf(mapping);
+      }
+    }
+
     private void markForceInline() {
       forceInline = true;
     }
@@ -698,6 +727,15 @@
     ensureMutableOI().markReturnsConstant(value);
   }
 
+  synchronized public void markReceiverOnlyUsedForReadingFields(Set<DexField> fields) {
+    ensureMutableOI().markReceiverOnlyUsedForReadingFields(fields);
+  }
+
+  synchronized public void markOnlyInitializesFieldsWithNoOtherSideEffects(
+      Map<DexField, Integer> mapping) {
+    ensureMutableOI().markOnlyInitializesFieldsWithNoOtherSideEffects(mapping);
+  }
+
   synchronized public void markForceInline() {
     ensureMutableOI().markForceInline();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index b971ab4..86ca12a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -115,6 +115,7 @@
   public final DexString valueOfMethodName = createString("valueOf");
 
   public final DexString getClassMethodName = createString("getClass");
+  public final DexString finalizeMethodName = createString("finalize");
   public final DexString ordinalMethodName = createString("ordinal");
   public final DexString desiredAssertionStatusMethodName = createString("desiredAssertionStatus");
   public final DexString forNameMethodName = createString("forName");
@@ -237,6 +238,7 @@
   public final DexType callSiteType = createType("Ljava/lang/invoke/CallSite;");
   public final DexType lookupType = createType("Ljava/lang/invoke/MethodHandles$Lookup;");
   public final DexType serializableType = createType("Ljava/io/Serializable;");
+  public final DexType comparableType = createType("Ljava/lang/Comparable;");
 
   public final DexMethod metafactoryMethod =
       createMethod(
@@ -325,12 +327,15 @@
 
     public final DexMethod getClass;
     public final DexMethod constructor;
+    public final DexMethod finalize;
 
     private ObjectMethods() {
       getClass = createMethod(objectDescriptor, getClassMethodName, classDescriptor,
           DexString.EMPTY_ARRAY);
       constructor = createMethod(objectDescriptor,
           constructorMethodName, voidType.descriptor, DexString.EMPTY_ARRAY);
+      finalize = createMethod(objectDescriptor,
+          finalizeMethodName, voidType.descriptor, DexString.EMPTY_ARRAY);
     }
   }
 
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 6c414ac..3bd4cde 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -104,18 +104,19 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
+  public IRCode buildIR(DexEncodedMethod encodedMethod, AppInfo appInfo,
+      InternalOptions options, Origin origin)
       throws ApiLevelException {
     triggerDelayedParsingIfNeccessary();
     return options.debug
-        ? internalBuildWithLocals(encodedMethod, options, null, null)
-        : internalBuild(encodedMethod, options, null, null);
+        ? internalBuildWithLocals(encodedMethod, appInfo, options, null, null)
+        : internalBuild(encodedMethod, appInfo, options, null, null);
   }
 
   @Override
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
-      InternalOptions options,
+      AppInfo appInfo, InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition,
       Origin origin)
@@ -123,28 +124,28 @@
     assert generator != null;
     triggerDelayedParsingIfNeccessary();
     return options.debug
-        ? internalBuildWithLocals(encodedMethod, options, generator, callerPosition)
-        : internalBuild(encodedMethod, options, generator, callerPosition);
+        ? internalBuildWithLocals(encodedMethod, appInfo, options, generator, callerPosition)
+        : internalBuild(encodedMethod, appInfo, options, generator, callerPosition);
   }
 
   private IRCode internalBuildWithLocals(
       DexEncodedMethod encodedMethod,
-      InternalOptions options,
+      AppInfo appInfo, InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition)
       throws ApiLevelException {
     try {
-      return internalBuild(encodedMethod, options, generator, callerPosition);
+      return internalBuild(encodedMethod, appInfo, options, generator, callerPosition);
     } catch (InvalidDebugInfoException e) {
       options.warningInvalidDebugInfo(encodedMethod, origin, e);
       node.localVariables.clear();
-      return internalBuild(encodedMethod, options, generator, callerPosition);
+      return internalBuild(encodedMethod, appInfo, options, generator, callerPosition);
     }
   }
 
   private IRCode internalBuild(
       DexEncodedMethod encodedMethod,
-      InternalOptions options,
+      AppInfo appInfo, InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition)
       throws ApiLevelException {
@@ -155,8 +156,8 @@
         method.getHolder(), node, application, encodedMethod.method, callerPosition);
     IRBuilder builder =
         (generator == null)
-            ? new IRBuilder(encodedMethod, source, options)
-            : new IRBuilder(encodedMethod, source, options, generator);
+            ? new IRBuilder(encodedMethod, appInfo, source, options)
+            : new IRBuilder(encodedMethod, appInfo, source, options, generator);
     return builder.build();
   }
 
@@ -186,15 +187,44 @@
   }
 
   private void triggerDelayedParsingIfNeccessary() {
-    if (context != null) {
-      // The SecondVistor is in charge of setting the context to null.
-      DexProgramClass owner = context.owner;
-      new ClassReader(context.classCache).accept(new SecondVisitor(context, application),
-          ClassReader.SKIP_FRAMES);
-      assert verifyNoReparseContext(owner);
+    if (this.context != null) {
+      // The SecondVisitor is in charge of setting this.context to null.
+      ReparseContext context = this.context;
+      parseCode(context, false);
+      if (hasJsr(context)) {
+        System.out.println("JarCode: JSR encountered; reparse using JSRInlinerAdapter");
+        parseCode(context, true);
+        assert !hasJsr(context);
+      }
+      assert verifyNoReparseContext(context.owner);
     }
   }
 
+  private void parseCode(ReparseContext context, boolean useJsrInliner) {
+    SecondVisitor classVisitor = new SecondVisitor(context, application, useJsrInliner);
+    new ClassReader(context.classCache).accept(classVisitor, ClassReader.SKIP_FRAMES);
+  }
+
+  private boolean hasJsr(ReparseContext context) {
+    for (Code code : context.codeList) {
+      if (hasJsr(code.asJarCode().node)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean hasJsr(MethodNode node) {
+    Iterator<AbstractInsnNode> it = node.instructions.iterator();
+    while (it.hasNext()) {
+      int opcode = it.next().getOpcode();
+      if (opcode == Opcodes.JSR || opcode == Opcodes.RET) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   /**
    * Fills the MethodNodes of all the methods in the class and removes the ReparseContext.
    */
@@ -202,18 +232,24 @@
 
     private final ReparseContext context;
     private final JarApplicationReader application;
+    private final boolean useJsrInliner;
     private int methodIndex = 0;
 
-    public SecondVisitor(ReparseContext context, JarApplicationReader application) {
+    public SecondVisitor(
+        ReparseContext context, JarApplicationReader application, boolean useJsrInliner) {
       super(Opcodes.ASM6);
       this.context = context;
       this.application = application;
+      this.useJsrInliner = useJsrInliner;
     }
 
     @Override
     public MethodVisitor visitMethod(int access, String name, String desc, String signature,
         String[] exceptions) {
-      MethodNode node = new JSRInlinerAdapter(null, access, name, desc, signature, exceptions);
+      MethodNode node =
+          useJsrInliner
+              ? new JSRInlinerAdapter(null, access, name, desc, signature, exceptions)
+              : new MethodNode(Opcodes.ASM6, access, name, desc, signature, exceptions);
       JarCode code = null;
       MethodAccessFlags flags = JarClassFileReader.createMethodAccessFlags(name, access);
       if (!flags.isAbstract() && !flags.isNative()) {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index e816bd5..bd6d3f7 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -64,6 +64,7 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -78,6 +79,12 @@
 
 public class LazyCfCode extends Code {
 
+  private static class JsrEncountered extends RuntimeException {
+    public JsrEncountered(String s) {
+      super(s);
+    }
+  }
+
   public LazyCfCode(
       DexMethod method, Origin origin, ReparseContext context, JarApplicationReader application) {
 
@@ -107,17 +114,34 @@
   @Override
   public CfCode asCfCode() {
     if (code == null) {
+      ReparseContext context = this.context;
       assert context != null;
-      // The SecondVistor is in charge of setting the context to null.
-      DexProgramClass owner = context.owner;
-      ClassReader classReader = new ClassReader(context.classCache);
-      classReader.accept(new ClassCodeVisitor(context, application), ClassReader.EXPAND_FRAMES);
-      assert verifyNoReparseContext(owner);
+      // The ClassCodeVisitor is in charge of setting this.context to null.
+      try {
+        parseCode(context, false);
+      } catch (JsrEncountered e) {
+        System.out.println("LazyCfCode: JSR encountered; reparse using JSRInlinerAdapter");
+        for (Code code : context.codeList) {
+          code.asLazyCfCode().code = null;
+          code.asLazyCfCode().context = context;
+        }
+        try {
+          parseCode(context, true);
+        } catch (JsrEncountered e1) {
+          throw new Unreachable(e1);
+        }
+      }
+      assert verifyNoReparseContext(context.owner);
     }
     assert code != null;
     return code;
   }
 
+  public void parseCode(ReparseContext context, boolean useJsrInliner) {
+    ClassCodeVisitor classVisitor = new ClassCodeVisitor(context, application, useJsrInliner);
+    new ClassReader(context.classCache).accept(classVisitor, ClassReader.EXPAND_FRAMES);
+  }
+
   private void setCode(CfCode code) {
     assert this.code == null;
     assert this.context != null;
@@ -151,21 +175,23 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
+  public IRCode buildIR(DexEncodedMethod encodedMethod, AppInfo appInfo,
+      InternalOptions options, Origin origin)
       throws ApiLevelException {
-    return asCfCode().buildIR(encodedMethod, options, origin);
+    return asCfCode().buildIR(encodedMethod, appInfo, options, origin);
   }
 
   @Override
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin)
       throws ApiLevelException {
     return asCfCode().buildInliningIR(
-        encodedMethod, options, valueNumberGenerator, callerPosition, origin);
+        encodedMethod, appInfo, options, valueNumberGenerator, callerPosition, origin);
   }
 
   @Override
@@ -188,11 +214,14 @@
     private final ReparseContext context;
     private final JarApplicationReader application;
     private int methodIndex = 0;
+    private boolean usrJsrInliner;
 
-    ClassCodeVisitor(ReparseContext context, JarApplicationReader application) {
+    ClassCodeVisitor(
+        ReparseContext context, JarApplicationReader application, boolean useJsrInliner) {
       super(Opcodes.ASM6);
       this.context = context;
       this.application = application;
+      this.usrJsrInliner = useJsrInliner;
     }
 
     @Override
@@ -204,6 +233,9 @@
         DexMethod method = application.getMethod(context.owner.type, name, desc);
         assert code.method == method;
         MethodCodeVisitor methodVisitor = new MethodCodeVisitor(application, code);
+        if (!usrJsrInliner) {
+          return methodVisitor;
+        }
         return new JSRInlinerAdapter(methodVisitor, access, name, desc, signature, exceptions);
       }
       return null;
@@ -218,6 +250,7 @@
     private List<CfInstruction> instructions;
     private List<CfTryCatch> tryCatchRanges;
     private List<LocalVariableInfo> localVariables;
+    private final Map<DebugLocalInfo, DebugLocalInfo> canonicalDebugLocalInfo = new HashMap<>();
     private Map<Label, CfLabel> labelMap;
     private final LazyCfCode code;
     private DexMethod method;
@@ -579,7 +612,7 @@
           type = ValueType.OBJECT;
           break;
         case Opcodes.RET:
-          throw new Unreachable("RET should be handled by the ASM jsr inliner");
+          throw new JsrEncountered("RET should be handled by the ASM jsr inliner");
         default:
           throw new Unreachable("Unexpected VarInsn opcode: " + opcode);
       }
@@ -668,7 +701,7 @@
             instructions.add(new CfIf(type, ValueType.OBJECT, target));
             break;
           case Opcodes.JSR:
-            throw new Unreachable("JSR should be handled by the ASM jsr inliner");
+            throw new JsrEncountered("JSR should be handled by the ASM jsr inliner");
           default:
             throw new Unreachable("Unexpected JumpInsn opcode: " + opcode);
         }
@@ -780,14 +813,19 @@
     public void visitLocalVariable(
         String name, String desc, String signature, Label start, Label end, int index) {
       DebugLocalInfo debugLocalInfo =
-          new DebugLocalInfo(
-              factory.createString(name),
-              factory.createType(desc),
-              signature == null ? null : factory.createString(signature));
+          canonicalize(
+              new DebugLocalInfo(
+                  factory.createString(name),
+                  factory.createType(desc),
+                  signature == null ? null : factory.createString(signature)));
       localVariables.add(
           new LocalVariableInfo(index, debugLocalInfo, getLabel(start), getLabel(end)));
     }
 
+    private DebugLocalInfo canonicalize(DebugLocalInfo debugLocalInfo) {
+      return canonicalDebugLocalInfo.computeIfAbsent(debugLocalInfo, o -> debugLocalInfo);
+    }
+
     @Override
     public void visitLineNumber(int line, Label start) {
       instructions.add(new CfPosition(getLabel(start), new Position(line, null, method, null)));
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index bb13495..1ec23d0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -358,6 +358,15 @@
     return true;
   }
 
+  public boolean hasCatchHandlers() {
+    for (BasicBlock block : blocks) {
+      if (block.hasCatchHandlers()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   private boolean consistentDefUseChains() {
     Set<Value> values = new HashSet<>();
 
@@ -578,16 +587,23 @@
   }
 
   public List<Value> collectArguments() {
+    return collectArguments(false);
+  }
+
+  public List<Value> collectArguments(boolean ignoreReceiver) {
     final List<Value> arguments = new ArrayList<>();
     Iterator<Instruction> iterator = blocks.get(0).iterator();
     while (iterator.hasNext()) {
       Instruction instruction = iterator.next();
       if (instruction.isArgument()) {
-        arguments.add(instruction.asArgument().outValue());
+        Value out = instruction.asArgument().outValue();
+        if (!ignoreReceiver || !out.isThis()) {
+          arguments.add(out);
+        }
       }
     }
     assert arguments.size()
-        == method.method.getArity() + (method.accessFlags.isStatic() ? 0 : 1);
+        == method.method.getArity() + ((method.accessFlags.isStatic() || ignoreReceiver) ? 0 : 1);
     return arguments;
   }
 
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 d280dfe..81d4cdc 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
@@ -131,6 +131,6 @@
 
   @Override
   public InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
-    return decider.computeForInvokePolymorpic(this, invocationContext);
+    return decider.computeForInvokePolymorphic(this, invocationContext);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 1a210b2..c697105 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -295,18 +296,15 @@
   private final LinkedList<BasicBlock> blocks = new LinkedList<>();
 
   private BasicBlock currentBlock = null;
-
   private final List<BasicBlock.Pair> needGotoToCatchBlocks = new ArrayList<>();
-
   final private ValueNumberGenerator valueNumberGenerator;
-
   private final DexEncodedMethod method;
+  private final AppInfo appInfo;
 
   // Source code to build IR from. Null if already built.
   private SourceCode source;
 
-  boolean throwingInstructionInCurrentBlock = false;
-
+  private boolean throwingInstructionInCurrentBlock = false;
   private final InternalOptions options;
 
   // Pending local reads.
@@ -315,16 +313,17 @@
 
   private int nextBlockNumber = 0;
 
-  public IRBuilder(DexEncodedMethod method, SourceCode source, InternalOptions options) {
-    this(method, source, options, new ValueNumberGenerator());
+  public IRBuilder(DexEncodedMethod method, AppInfo appInfo,
+      SourceCode source, InternalOptions options) {
+    this(method, appInfo, source, options, new ValueNumberGenerator());
   }
 
   public IRBuilder(
-      DexEncodedMethod method,
-      SourceCode source,
+      DexEncodedMethod method, AppInfo appInfo, SourceCode source,
       InternalOptions options, ValueNumberGenerator valueNumberGenerator) {
     assert source != null;
     this.method = method;
+    this.appInfo = appInfo;
     this.source = source;
     this.valueNumberGenerator = valueNumberGenerator;
     this.options = options;
@@ -1043,7 +1042,7 @@
   public void addInvoke(
       Type type, DexItem item, DexProto callSiteProto, List<Value> arguments, boolean itf)
       throws ApiLevelException {
-    if (type == Invoke.Type.POLYMORPHIC) {
+    if (type == Type.POLYMORPHIC) {
       assert item instanceof DexMethod;
       if (!options.canUseInvokePolymorphic()) {
         throw new ApiLevelException(
@@ -1058,6 +1057,19 @@
             null /* sourceString */);
       }
     }
+    if (appInfo != null && type == Type.VIRTUAL) {
+      // If an invoke-virtual targets a private method in the current class overriding will
+      // not apply (see jvm spec on method resolution 5.4.3.3 and overriding 5.4.5) and
+      // therefore we use an invoke-direct instead. We need to do this as the Android Runtime
+      // will not allow invoke-virtual of a private method.
+      DexMethod invocationMethod = (DexMethod) item;
+      if (invocationMethod.holder == method.method.holder) {
+        DexEncodedMethod directTarget = appInfo.lookupDirectTarget(invocationMethod);
+        if (directTarget != null && invocationMethod.holder == directTarget.method.holder) {
+          type = Type.DIRECT;
+        }
+      }
+    }
     add(Invoke.create(type, item, callSiteProto, null, arguments, itf));
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 20cbd10..8477d9b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -46,6 +46,7 @@
 import com.android.tools.r8.ir.optimize.NonNullTracker;
 import com.android.tools.r8.ir.optimize.Outliner;
 import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
 import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -87,6 +88,7 @@
   private final LambdaRewriter lambdaRewriter;
   private final InterfaceMethodRewriter interfaceMethodRewriter;
   private final LambdaMerger lambdaMerger;
+  private final ClassInliner classInliner;
   private final InternalOptions options;
   private final CfgPrinter printer;
   private final GraphLense graphLense;
@@ -160,6 +162,9 @@
       this.identifierNameStringMarker = null;
       this.devirtualizer = null;
     }
+    this.classInliner =
+        (options.enableClassInlining && options.enableInlining && inliner != null)
+            ? new ClassInliner(appInfo.dexItemFactory) : null;
   }
 
   /**
@@ -578,7 +583,7 @@
       feedback.markProcessed(method, Constraint.NEVER);
       return;
     }
-    IRCode code = method.buildIR(options, appInfo.originFor(method.method.holder));
+    IRCode code = method.buildIR(appInfo, options, appInfo.originFor(method.method.holder));
     if (code == null) {
       feedback.markProcessed(method, Constraint.NEVER);
       return;
@@ -686,6 +691,15 @@
       assert code.isConsistentSSA();
     }
 
+    if (classInliner != null) {
+      assert options.enableInlining && inliner != null;
+      classInliner.processMethodCode(
+          appInfo.withSubtyping(), method, code, isProcessedConcurrently,
+          methodsToInline -> inliner.performForcedInlining(method, code, methodsToInline)
+      );
+      assert code.isConsistentSSA();
+    }
+
     if (options.outline.enabled) {
       outlineHandler.accept(code, method);
       assert code.isConsistentSSA();
@@ -705,6 +719,12 @@
       assert code.isConsistentSSA();
     }
 
+    // Analysis must be done after method is rewritten by logArgumentTypes()
+    codeRewriter.identifyReceiverOnlyUsedForReadingFields(method, code, feedback);
+    if (method.isInstanceInitializer()) {
+      codeRewriter.identifyOnlyInitializesFieldsWithNoOtherSideEffects(method, code, feedback);
+    }
+
     printMethod(code, "Optimized IR (SSA)");
     finalizeIR(method, code, feedback);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
index b2c5cfd..26ca6b2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
@@ -5,7 +5,10 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import java.util.Map;
+import java.util.Set;
 
 public interface OptimizationFeedback {
   void methodReturnsArgument(DexEncodedMethod method, int argument);
@@ -15,4 +18,7 @@
   void markProcessed(DexEncodedMethod method, Constraint state);
   void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
   void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
+  void markReceiverOnlyUsedForReadingFields(DexEncodedMethod method, Set<DexField> fields);
+  void markOnlyInitializesFieldsWithNoOtherSideEffects(
+      DexEncodedMethod method, Map<DexField, Integer> mapping);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
index ac1972b..645df64 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
@@ -5,7 +5,10 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import java.util.Map;
+import java.util.Set;
 
 public class OptimizationFeedbackDirect implements OptimizationFeedback {
 
@@ -43,4 +46,15 @@
   public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
     method.markTriggerClassInitBeforeAnySideEffect(mark);
   }
+
+  @Override
+  public void markReceiverOnlyUsedForReadingFields(DexEncodedMethod method, Set<DexField> fields) {
+    method.markReceiverOnlyUsedForReadingFields(fields);
+  }
+
+  @Override
+  public void markOnlyInitializesFieldsWithNoOtherSideEffects(DexEncodedMethod method,
+      Map<DexField, Integer> mapping) {
+    method.markOnlyInitializesFieldsWithNoOtherSideEffects(mapping);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
index 80cb04f..97fe052 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
@@ -5,7 +5,10 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import java.util.Map;
+import java.util.Set;
 
 public class OptimizationFeedbackIgnore implements OptimizationFeedback {
 
@@ -29,4 +32,13 @@
 
   @Override
   public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {}
+
+  @Override
+  public void markReceiverOnlyUsedForReadingFields(DexEncodedMethod method, Set<DexField> fields) {
+  }
+
+  @Override
+  public void markOnlyInitializesFieldsWithNoOtherSideEffects(DexEncodedMethod method,
+      Map<DexField, Integer> mapping) {
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index 5ea5d0e..45c08fb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -126,7 +126,10 @@
 
       // `a` is primitive and `b` is a supertype of the boxed type `a`.
       DexType boxedPrimitiveType = getBoxedForPrimitiveType(a);
-      if (b == boxedPrimitiveType || b == factory.objectType) {
+      if (b == boxedPrimitiveType ||
+          b == factory.objectType ||
+          b == factory.serializableType ||
+          b == factory.comparableType) {
         return true;
       }
       return boxedPrimitiveType != factory.boxedCharType
@@ -337,6 +340,8 @@
       DexType boxedFromType = getBoxedForPrimitiveType(fromType);
       if (toType == boxedFromType ||
           toType == factory().objectType ||
+          toType == factory().serializableType ||
+          toType == factory().comparableType ||
           (boxedFromType != factory().booleanType &&
               boxedFromType != factory().charType &&
               toType == factory().boxedNumberType)) {
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 85a3ac6..e5c8962 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
@@ -47,10 +47,13 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeVirtual;
@@ -733,6 +736,122 @@
     }
   }
 
+  public void identifyReceiverOnlyUsedForReadingFields(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+    if (!method.isNonAbstractVirtualMethod()) {
+      return;
+    }
+
+    feedback.markReceiverOnlyUsedForReadingFields(method, null);
+
+    Value instance = code.getThis();
+    if (instance.numberOfPhiUsers() > 0) {
+      return;
+    }
+
+    Set<DexField> fields = Sets.newIdentityHashSet();
+    for (Instruction insn : instance.uniqueUsers()) {
+      if (!insn.isInstanceGet()) {
+        return;
+      }
+      InstanceGet get = insn.asInstanceGet();
+      if (get.dest() == instance || get.object() != instance) {
+        return;
+      }
+      fields.add(get.getField());
+    }
+    feedback.markReceiverOnlyUsedForReadingFields(method, fields);
+  }
+
+  public void identifyOnlyInitializesFieldsWithNoOtherSideEffects(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+    assert method.isInstanceInitializer();
+
+    feedback.markOnlyInitializesFieldsWithNoOtherSideEffects(method, null);
+
+    if (code.hasCatchHandlers()) {
+      return;
+    }
+
+    List<Value> args = code.collectArguments(true /* not interested in receiver */);
+    Map<DexField, Integer> mapping = new IdentityHashMap<>();
+    Value receiver = code.getThis();
+
+    InstructionIterator it = code.instructionIterator();
+    while (it.hasNext()) {
+      Instruction instruction = it.next();
+
+      // Mark an argument.
+      if (instruction.isArgument()) {
+        continue;
+      }
+
+      // Allow super call to java.lang.Object.<init>() on 'this'.
+      if (instruction.isInvokeDirect()) {
+        InvokeDirect invokedDirect = instruction.asInvokeDirect();
+        if (invokedDirect.getInvokedMethod() != dexItemFactory.objectMethods.constructor ||
+            invokedDirect.getReceiver() != receiver) {
+          return;
+        }
+        continue;
+      }
+
+      // Allow final return.
+      if (instruction.isReturn()) {
+        continue;
+      }
+
+      // Allow assignment to this class' fields. If the assigned value is an argument
+      // reference update the mep. Otherwise just allow assigning any value, since all
+      // invalid values should be filtered out at the definitions.
+      if (instruction.isInstancePut()) {
+        InstancePut instancePut = instruction.asInstancePut();
+        DexField field = instancePut.getField();
+        if (instancePut.object() != receiver) {
+          return;
+        }
+
+        Value value = instancePut.value();
+        if (value.isArgument() && !value.isThis()) {
+          assert (args.contains(value));
+          mapping.put(field, args.indexOf(value));
+        } else {
+          mapping.remove(field);
+        }
+        continue;
+      }
+
+      // Allow non-throwing constants.
+      if (instruction.isConstInstruction()) {
+        if (instruction.instructionTypeCanThrow()) {
+          return;
+        }
+        continue;
+      }
+
+      // Allow goto instructions jumping to the next block.
+      if (instruction.isGoto()) {
+        if (instruction.getBlock().getNumber() + 1 !=
+            instruction.asGoto().getTarget().getNumber()) {
+          return;
+        }
+        continue;
+      }
+
+      // Allow binary and unary instructions if they don't throw.
+      if (instruction.isBinop() || instruction.isUnop()) {
+        if (instruction.instructionTypeCanThrow()) {
+          return;
+        }
+        continue;
+      }
+
+      return;
+    }
+
+    feedback.markOnlyInitializesFieldsWithNoOtherSideEffects(method, mapping);
+  }
+
   /**
    * An enum used to classify instructions according to a particular effect that they produce.
    *
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
new file mode 100644
index 0000000..90081d1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -0,0 +1,351 @@
+// 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.ApiLevelException;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokePolymorphic;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.conversion.CallSiteInformation;
+import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.logging.Log;
+import java.util.ListIterator;
+import java.util.function.Predicate;
+
+final class DefaultInliningOracle implements InliningOracle, InliningStrategy {
+
+  private final Inliner inliner;
+  private final DexEncodedMethod method;
+  private final IRCode code;
+  private final TypeEnvironment typeEnvironment;
+  private final CallSiteInformation callSiteInformation;
+  private final Predicate<DexEncodedMethod> isProcessedConcurrently;
+  private final InliningInfo info;
+  private final int inliningInstructionLimit;
+  private int instructionAllowance;
+
+  DefaultInliningOracle(
+      Inliner inliner,
+      DexEncodedMethod method,
+      IRCode code,
+      TypeEnvironment typeEnvironment,
+      CallSiteInformation callSiteInformation,
+      Predicate<DexEncodedMethod> isProcessedConcurrently,
+      int inliningInstructionLimit,
+      int inliningInstructionAllowance) {
+    this.inliner = inliner;
+    this.method = method;
+    this.code = code;
+    this.typeEnvironment = typeEnvironment;
+    this.callSiteInformation = callSiteInformation;
+    this.isProcessedConcurrently = isProcessedConcurrently;
+    info = Log.ENABLED ? new InliningInfo(method) : null;
+    this.inliningInstructionLimit = inliningInstructionLimit;
+    this.instructionAllowance = inliningInstructionAllowance;
+  }
+
+  @Override
+  public void finish() {
+    if (Log.ENABLED) {
+      Log.debug(getClass(), info.toString());
+    }
+  }
+
+  private DexEncodedMethod validateCandidate(InvokeMethod invoke, DexType invocationContext) {
+    DexEncodedMethod candidate =
+        invoke.computeSingleTarget(inliner.appInfo, typeEnvironment, invocationContext);
+    if ((candidate == null)
+        || (candidate.getCode() == null)
+        || inliner.appInfo.definitionFor(candidate.method.getHolder()).isLibraryClass()) {
+      if (info != null) {
+        info.exclude(invoke, "No inlinee");
+      }
+      return null;
+    }
+    // Ignore the implicit receiver argument.
+    int numberOfArguments =
+        invoke.arguments().size() - (invoke.isInvokeMethodWithReceiver() ? 1 : 0);
+    if (numberOfArguments != candidate.method.getArity()) {
+      if (info != null) {
+        info.exclude(invoke, "Argument number mismatch");
+      }
+      return null;
+    }
+    return candidate;
+  }
+
+  private Reason computeInliningReason(DexEncodedMethod target) {
+    if (target.getOptimizationInfo().forceInline()) {
+      return Reason.FORCE;
+    }
+    if (inliner.appInfo.hasLiveness()
+        && inliner.appInfo.withLiveness().alwaysInline.contains(target)) {
+      return Reason.ALWAYS;
+    }
+    if (callSiteInformation.hasSingleCallSite(target)) {
+      return Reason.SINGLE_CALLER;
+    }
+    if (isDoubleInliningTarget(target)) {
+      return Reason.DUAL_CALLER;
+    }
+    return Reason.SIMPLE;
+  }
+
+  private boolean canInlineStaticInvoke(DexEncodedMethod method, DexEncodedMethod target) {
+    // Only proceed with inlining a static invoke if:
+    // - the holder for the target equals the holder for the method, or
+    // - the target method always triggers class initialization of its holder before any other side
+    //   effect (hence preserving class initialization semantics).
+    // - there is no non-trivial class initializer.
+    DexType targetHolder = target.method.getHolder();
+    if (method.method.getHolder() == targetHolder) {
+      return true;
+    }
+    DexClass clazz = inliner.appInfo.definitionFor(targetHolder);
+    assert clazz != null;
+    if (target.getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) {
+      return true;
+    }
+    return classInitializationHasNoSideffects(targetHolder);
+  }
+
+  /**
+   * Check for class initializer side effects when loading this class, as inlining might remove the
+   * load operation.
+   * <p>
+   * See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5.
+   * <p>
+   * For simplicity, we are conservative and consider all interfaces, not only the ones with default
+   * methods.
+   */
+  private boolean classInitializationHasNoSideffects(DexType classToCheck) {
+    DexClass clazz = inliner.appInfo.definitionFor(classToCheck);
+    if ((clazz == null)
+        || clazz.hasNonTrivialClassInitializer()
+        || clazz.defaultValuesForStaticFieldsMayTriggerAllocation()) {
+      return false;
+    }
+    for (DexType iface : clazz.interfaces.values) {
+      if (!classInitializationHasNoSideffects(iface)) {
+        return false;
+      }
+    }
+    return clazz.superType == null || classInitializationHasNoSideffects(clazz.superType);
+  }
+
+  private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
+    // 10 is found from measuring.
+    return inliner.isDoubleInliningTarget(callSiteInformation, candidate)
+        && candidate.getCode().estimatedSizeForInliningAtMost(10);
+  }
+
+  private boolean passesInliningConstraints(InvokeMethod invoke, DexEncodedMethod candidate,
+      Reason reason) {
+    if (method == candidate) {
+      // Cannot handle recursive inlining at this point.
+      // Force inlined method should never be recursive.
+      assert !candidate.getOptimizationInfo().forceInline();
+      if (info != null) {
+        info.exclude(invoke, "direct recursion");
+      }
+      return false;
+    }
+
+    if (reason != Reason.FORCE && isProcessedConcurrently.test(candidate)) {
+      if (info != null) {
+        info.exclude(invoke, "is processed in parallel");
+      }
+      return false;
+    }
+
+    // Abort inlining attempt if method -> target access is not right.
+    if (!inliner.hasInliningAccess(method, candidate)) {
+      if (info != null) {
+        info.exclude(invoke, "target does not have right access");
+      }
+      return false;
+    }
+
+    DexClass holder = inliner.appInfo.definitionFor(candidate.method.getHolder());
+    if (holder.isInterface()) {
+      // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at
+      // runtime.
+      if (info != null) {
+        info.exclude(invoke, "Do not inline target if method holder is an interface class");
+      }
+      return false;
+    }
+
+    if (holder.isLibraryClass()) {
+      // Library functions should not be inlined.
+      return false;
+    }
+
+    // Don't inline if target is synchronized.
+    if (candidate.accessFlags.isSynchronized()) {
+      if (info != null) {
+        info.exclude(invoke, "target is synchronized");
+      }
+      return false;
+    }
+
+    // Attempt to inline a candidate that is only called twice.
+    if ((reason == Reason.DUAL_CALLER) && (inliner.doubleInlining(method, candidate) == null)) {
+      if (info != null) {
+        info.exclude(invoke, "target is not ready for double inlining");
+      }
+      return false;
+    }
+
+    if (reason == Reason.SIMPLE) {
+      // If we are looking for a simple method, only inline if actually simple.
+      Code code = candidate.getCode();
+      if (!code.estimatedSizeForInliningAtMost(inliningInstructionLimit)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public InlineAction computeForInvokeWithReceiver(
+      InvokeMethodWithReceiver invoke, DexType invocationContext) {
+    DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
+    if (candidate == null || inliner.isBlackListed(candidate.method)) {
+      return null;
+    }
+
+    // We can only inline an instance method call if we preserve the null check semantic (which
+    // would throw NullPointerException if the receiver is null). Therefore we can inline only if
+    // one of the following conditions is true:
+    // * the candidate inlinee checks null receiver before any side effect
+    // * the receiver is known to be non-null
+    boolean receiverIsNeverNull =
+        !typeEnvironment.getLatticeElement(invoke.getReceiver()).isNullable();
+    if (!receiverIsNeverNull
+        && !candidate.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) {
+      if (info != null) {
+        info.exclude(invoke, "receiver for candidate can be null");
+      }
+      return null;
+    }
+
+    Reason reason = computeInliningReason(candidate);
+    if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
+      // Abort inlining attempt if the single target is not an inlining candidate.
+      if (info != null) {
+        info.exclude(invoke, "target is not identified for inlining");
+      }
+      return null;
+    }
+
+    if (!passesInliningConstraints(invoke, candidate, reason)) {
+      return null;
+    }
+
+    if (info != null) {
+      info.include(invoke.getType(), candidate);
+    }
+    return new InlineAction(candidate, invoke, reason);
+  }
+
+  @Override
+  public InlineAction computeForInvokeStatic(InvokeStatic invoke, DexType invocationContext) {
+    DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
+    if (candidate == null || inliner.isBlackListed(candidate.method)) {
+      return null;
+    }
+
+    Reason reason = computeInliningReason(candidate);
+    // Determine if this should be inlined no matter how big it is.
+    if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
+      // Abort inlining attempt if the single target is not an inlining candidate.
+      if (info != null) {
+        info.exclude(invoke, "target is not identified for inlining");
+      }
+      return null;
+    }
+
+    // Abort inlining attempt if we can not guarantee class for static target has been initialized.
+    if (!canInlineStaticInvoke(method, candidate)) {
+      if (info != null) {
+        info.exclude(invoke, "target is static but we cannot guarantee class has been initialized");
+      }
+      return null;
+    }
+
+    if (!passesInliningConstraints(invoke, candidate, reason)) {
+      return null;
+    }
+
+    if (info != null) {
+      info.include(invoke.getType(), candidate);
+    }
+    return new InlineAction(candidate, invoke, reason);
+  }
+
+  @Override
+  public InlineAction computeForInvokePolymorphic(
+      InvokePolymorphic invoke, DexType invocationContext) {
+    // TODO: No inlining of invoke polymorphic for now.
+    if (info != null) {
+      info.exclude(invoke, "inlining through invoke signature polymorpic is not supported");
+    }
+    return null;
+  }
+
+  @Override
+  public void ensureMethodProcessed(
+      DexEncodedMethod target, IRCode inlinee) throws ApiLevelException {
+    if (!target.isProcessed()) {
+      if (Log.ENABLED) {
+        Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
+      }
+      inliner.performInlining(
+          target, inlinee, typeEnvironment, isProcessedConcurrently, callSiteInformation);
+    }
+  }
+
+  @Override
+  public boolean exceededAllowance() {
+    return instructionAllowance < 0;
+  }
+
+  @Override
+  public void markInlined(IRCode inlinee) {
+    instructionAllowance -= inliner.numberOfInstructions(inlinee);
+  }
+
+  @Override
+  public ListIterator<BasicBlock> updateTypeInformationIfNeeded(IRCode inlinee,
+      ListIterator<BasicBlock> blockIterator, BasicBlock block, BasicBlock invokeSuccessor) {
+    if (inliner.options.enableNonNullTracking) {
+      // Move the cursor back to where the inlinee blocks are added.
+      blockIterator = code.blocks.listIterator(code.blocks.indexOf(block));
+      // Kick off the tracker to add non-null IRs only to the inlinee blocks.
+      new NonNullTracker()
+          .addNonNullInPart(code, blockIterator, inlinee.blocks::contains);
+      // Move the cursor forward to where the inlinee blocks end.
+      blockIterator = code.blocks.listIterator(code.blocks.indexOf(invokeSuccessor));
+    }
+    // Update type env for inlined blocks.
+    typeEnvironment.analyzeBlocks(inlinee.topologicallySortedBlocks());
+    // TODO(b/69964136): need a test where refined env in inlinee affects the caller.
+    return blockIterator;
+  }
+
+  @Override
+  public DexType getReceiverTypeIfKnown(InvokeMethod invoke) {
+    return null; // Maybe improve later.
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
index c2af6ea..59ddc36 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
@@ -56,7 +56,7 @@
       return;
     }
     DexEncodedMethod initializer = clazz.getClassInitializer();
-    IRCode code = initializer.getCode().buildIR(initializer, options, clazz.origin);
+    IRCode code = initializer.getCode().buildIR(initializer, appInfo, options, clazz.origin);
     Reference2IntMap<DexField> ordinalsMap = new Reference2IntArrayMap<>();
     ordinalsMap.defaultReturnValue(-1);
     InstructionIterator it = code.instructionIterator();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
new file mode 100644
index 0000000..86d40d9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -0,0 +1,85 @@
+// 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.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokePolymorphic;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import java.util.ListIterator;
+import java.util.Map;
+
+final class ForcedInliningOracle implements InliningOracle, InliningStrategy {
+  private final DexEncodedMethod method;
+  private final Map<InvokeMethodWithReceiver, Inliner.InliningInfo> invokesToInline;
+
+  ForcedInliningOracle(DexEncodedMethod method,
+      Map<InvokeMethodWithReceiver, Inliner.InliningInfo> invokesToInline) {
+    this.method = method;
+    this.invokesToInline = invokesToInline;
+  }
+
+  @Override
+  public void finish() {
+  }
+
+  @Override
+  public InlineAction computeForInvokeWithReceiver(
+      InvokeMethodWithReceiver invoke, DexType invocationContext) {
+    Inliner.InliningInfo info = invokesToInline.get(invoke);
+    if (info == null) {
+      return null;
+    }
+
+    assert method != info.target;
+    return new InlineAction(info.target, invoke, Reason.FORCE);
+  }
+
+  @Override
+  public InlineAction computeForInvokeStatic(
+      InvokeStatic invoke, DexType invocationContext) {
+    return null; // Not yet supported.
+  }
+
+  @Override
+  public InlineAction computeForInvokePolymorphic(
+      InvokePolymorphic invoke, DexType invocationContext) {
+    return null; // Not yet supported.
+  }
+
+  @Override
+  public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
+    assert target.isProcessed();
+  }
+
+  @Override
+  public ListIterator<BasicBlock> updateTypeInformationIfNeeded(IRCode inlinee,
+      ListIterator<BasicBlock> blockIterator, BasicBlock block, BasicBlock invokeSuccessor) {
+    return blockIterator;
+  }
+
+  @Override
+  public boolean exceededAllowance() {
+    return false; // Never exceeds allowance.
+  }
+
+  @Override
+  public void markInlined(IRCode inlinee) {
+  }
+
+  @Override
+  public DexType getReceiverTypeIfKnown(InvokeMethod invoke) {
+    assert invoke.isInvokeMethodWithReceiver();
+    Inliner.InliningInfo info = invokesToInline.get(invoke.asInvokeMethodWithReceiver());
+    assert info != null;
+    return info.receiverType;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index d6efdbb..860cc21 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
@@ -28,7 +29,6 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.LensCodeRewriter;
 import com.android.tools.r8.ir.conversion.OptimizationFeedback;
-import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -43,10 +43,11 @@
 import java.util.stream.Collectors;
 
 public class Inliner {
+  private static final int INITIAL_INLINING_INSTRUCTION_ALLOWANCE = 1500;
 
   protected final AppInfoWithLiveness appInfo;
   private final GraphLense graphLense;
-  private final InternalOptions options;
+  final InternalOptions options;
 
   // State for inlining methods which are known to be called twice.
   private boolean applyDoubleInlining = false;
@@ -270,7 +271,7 @@
         throws ApiLevelException {
       // Build the IR for a yet not processed method, and perform minimal IR processing.
       Origin origin = appInfo.originFor(target.method.holder);
-      IRCode code = target.buildInliningIR(options, generator, callerPosition, origin);
+      IRCode code = target.buildInliningIR(appInfo, options, generator, callerPosition, origin);
       if (!target.isProcessed()) {
         new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
       }
@@ -278,7 +279,7 @@
     }
   }
 
-  private int numberOfInstructions(IRCode code) {
+  final int numberOfInstructions(IRCode code) {
     int numOfInstructions = 0;
     for (BasicBlock block : code.blocks) {
       numOfInstructions += block.getInstructions().size();
@@ -364,6 +365,23 @@
     return true;
   }
 
+  public static class InliningInfo {
+    public final DexEncodedMethod target;
+    public final DexType receiverType; // null, if unknown
+
+    public InliningInfo(DexEncodedMethod target, DexType receiverType) {
+      this.target = target;
+      this.receiverType = receiverType;
+    }
+  }
+
+  public void performForcedInlining(DexEncodedMethod method, IRCode code,
+      Map<InvokeMethodWithReceiver, InliningInfo> invokesToInline) throws ApiLevelException {
+
+    ForcedInliningOracle oracle = new ForcedInliningOracle(method, invokesToInline);
+    performInliningImpl(oracle, oracle, method, code);
+  }
+
   public void performInlining(
       DexEncodedMethod method,
       IRCode code,
@@ -371,29 +389,40 @@
       Predicate<DexEncodedMethod> isProcessedConcurrently,
       CallSiteInformation callSiteInformation)
       throws ApiLevelException {
-    int instruction_allowance = 1500;
-    instruction_allowance -= numberOfInstructions(code);
-    if (instruction_allowance < 0) {
-      return;
-    }
-    InliningOracle oracle =
-        new InliningOracle(
+
+    DefaultInliningOracle oracle =
+        new DefaultInliningOracle(
             this,
             method,
+            code,
             typeEnvironment,
             callSiteInformation,
             isProcessedConcurrently,
-            options.inliningInstructionLimit);
+            options.inliningInstructionLimit,
+            INITIAL_INLINING_INSTRUCTION_ALLOWANCE - numberOfInstructions(code));
+
+    performInliningImpl(oracle, oracle, method, code);
+  }
+
+  private void performInliningImpl(
+      InliningStrategy strategy,
+      InliningOracle oracle,
+      DexEncodedMethod method,
+      IRCode code)
+      throws ApiLevelException {
+    if (strategy.exceededAllowance()) {
+      return;
+    }
 
     List<BasicBlock> blocksToRemove = new ArrayList<>();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
-    while (blockIterator.hasNext() && (instruction_allowance >= 0)) {
+    while (blockIterator.hasNext() && !strategy.exceededAllowance()) {
       BasicBlock block = blockIterator.next();
       if (blocksToRemove.contains(block)) {
         continue;
       }
       InstructionListIterator iterator = block.listIterator();
-      while (iterator.hasNext() && (instruction_allowance >= 0)) {
+      while (iterator.hasNext() && !strategy.exceededAllowance()) {
         Instruction current = iterator.next();
         if (current.isInvokeMethod()) {
           InvokeMethod invoke = current.asInvokeMethod();
@@ -416,49 +445,27 @@
               if (block.hasCatchHandlers() && inlinee.computeNormalExitBlocks().isEmpty()) {
                 continue;
               }
+
               // If this code did not go through the full pipeline, apply inlining to make sure
               // that force inline targets get processed.
-              if (!target.isProcessed()) {
-                assert result.reason == Reason.FORCE;
-                if (Log.ENABLED) {
-                  Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
-                }
-                performInlining(
-                    target, inlinee, typeEnvironment, isProcessedConcurrently, callSiteInformation);
-              }
+              strategy.ensureMethodProcessed(target, inlinee);
+
               // Make sure constructor inlining is legal.
               assert !target.isClassInitializer();
               if (target.isInstanceInitializer()
                   && !legalConstructorInline(method, invoke, inlinee)) {
                 continue;
               }
-              DexType downcast = null;
-              if (invoke.isInvokeMethodWithReceiver()) {
-                // If the invoke has a receiver but the declared method holder is different
-                // from the computed target holder, inlining requires a downcast of the receiver.
-                if (target.method.getHolder() != invoke.getInvokedMethod().getHolder()) {
-                  downcast = result.target.method.getHolder();
-                }
-              }
+              DexType downcast = createDowncastIfNeeded(strategy, invoke, target);
               // Inline the inlinee code in place of the invoke instruction
               // Back up before the invoke instruction.
               iterator.previous();
-              instruction_allowance -= numberOfInstructions(inlinee);
-              if (instruction_allowance >= 0 || result.ignoreInstructionBudget()) {
+              strategy.markInlined(inlinee);
+              if (!strategy.exceededAllowance() || result.ignoreInstructionBudget()) {
                 BasicBlock invokeSuccessor =
                     iterator.inlineInvoke(code, inlinee, blockIterator, blocksToRemove, downcast);
-                if (options.enableNonNullTracking) {
-                  // Move the cursor back to where the inlinee blocks are added.
-                  blockIterator = code.blocks.listIterator(code.blocks.indexOf(block));
-                  // Kick off the tracker to add non-null IRs only to the inlinee blocks.
-                  new NonNullTracker()
-                      .addNonNullInPart(code, blockIterator, inlinee.blocks::contains);
-                  // Move the cursor forward to where the inlinee blocks end.
-                  blockIterator = code.blocks.listIterator(code.blocks.indexOf(invokeSuccessor));
-                }
-                // Update type env for inlined blocks.
-                typeEnvironment.analyzeBlocks(inlinee.topologicallySortedBlocks());
-                // TODO(b/69964136): need a test where refined env in inlinee affects the caller.
+                blockIterator = strategy.
+                    updateTypeInformationIfNeeded(inlinee, blockIterator, block, invokeSuccessor);
 
                 // If we inlined the invoke from a bridge method, it is no longer a bridge method.
                 if (method.accessFlags.isBridge()) {
@@ -481,4 +488,22 @@
     code.removeAllTrivialPhis();
     assert code.isConsistentSSA();
   }
+
+  private DexType createDowncastIfNeeded(
+      InliningStrategy strategy, InvokeMethod invoke, DexEncodedMethod target) {
+    if (invoke.isInvokeMethodWithReceiver()) {
+      // If the invoke has a receiver but the actual type of the receiver is different
+      // from the computed target holder, inlining requires a downcast of the receiver.
+      DexType assumedReceiverType = strategy.getReceiverTypeIfKnown(invoke);
+      if (assumedReceiverType == null) {
+        // In case we don't know exact type of the receiver we use declared
+        // method holder as a fallback.
+        assumedReceiverType = invoke.getInvokedMethod().getHolder();
+      }
+      if (assumedReceiverType != target.method.getHolder()) {
+        return target.method.getHolder();
+      }
+    }
+    return null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index beb4caa..08f8045 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -1,296 +1,28 @@
 // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
+
 package com.android.tools.r8.ir.optimize;
 
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
-import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokePolymorphic;
 import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.conversion.CallSiteInformation;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
-import com.android.tools.r8.logging.Log;
-import java.util.function.Predicate;
 
 /**
- * The InliningOracle contains information needed for when inlining
- * other methods into @method.
+ * The InliningOracle contains information needed for when inlining other methods into @method.
  */
-public class InliningOracle {
+public interface InliningOracle {
 
-  private final Inliner inliner;
-  private final DexEncodedMethod method;
-  private final TypeEnvironment typeEnvironment;
-  private final CallSiteInformation callSiteInformation;
-  private final Predicate<DexEncodedMethod> isProcessedConcurrently;
-  private final InliningInfo info;
-  private final int inliningInstructionLimit;
+  void finish();
 
-  InliningOracle(
-      Inliner inliner,
-      DexEncodedMethod method,
-      TypeEnvironment typeEnvironment,
-      CallSiteInformation callSiteInformation,
-      Predicate<DexEncodedMethod> isProcessedConcurrently,
-      int inliningInstructionLimit) {
-    this.inliner = inliner;
-    this.method = method;
-    this.typeEnvironment = typeEnvironment;
-    this.callSiteInformation = callSiteInformation;
-    this.isProcessedConcurrently = isProcessedConcurrently;
-    info = Log.ENABLED ? new InliningInfo(method) : null;
-    this.inliningInstructionLimit = inliningInstructionLimit;
-  }
+  InlineAction computeForInvokeWithReceiver(
+      InvokeMethodWithReceiver invoke, DexType invocationContext);
 
-  void finish() {
-    if (Log.ENABLED) {
-      Log.debug(getClass(), info.toString());
-    }
-  }
+  InlineAction computeForInvokeStatic(
+      InvokeStatic invoke, DexType invocationContext);
 
-  private DexEncodedMethod validateCandidate(InvokeMethod invoke, DexType invocationContext) {
-    DexEncodedMethod candidate =
-        invoke.computeSingleTarget(inliner.appInfo, typeEnvironment, invocationContext);
-    if ((candidate == null)
-        || (candidate.getCode() == null)
-        || inliner.appInfo.definitionFor(candidate.method.getHolder()).isLibraryClass()) {
-      if (info != null) {
-        info.exclude(invoke, "No inlinee");
-      }
-      return null;
-    }
-    // Ignore the implicit receiver argument.
-    int numberOfArguments =
-        invoke.arguments().size() - (invoke.isInvokeMethodWithReceiver() ? 1 : 0);
-    if (numberOfArguments != candidate.method.getArity()) {
-      if (info != null) {
-        info.exclude(invoke, "Argument number mismatch");
-      }
-      return null;
-    }
-    return candidate;
-  }
-
-  private Reason computeInliningReason(DexEncodedMethod target) {
-    if (target.getOptimizationInfo().forceInline()) {
-      return Reason.FORCE;
-    }
-    if (inliner.appInfo.hasLiveness()
-        && inliner.appInfo.withLiveness().alwaysInline.contains(target)) {
-      return Reason.ALWAYS;
-    }
-    if (callSiteInformation.hasSingleCallSite(target)) {
-      return Reason.SINGLE_CALLER;
-    }
-    if (isDoubleInliningTarget(target)) {
-      return Reason.DUAL_CALLER;
-    }
-    return Reason.SIMPLE;
-  }
-
-  private boolean canInlineStaticInvoke(DexEncodedMethod method, DexEncodedMethod target) {
-    // Only proceed with inlining a static invoke if:
-    // - the holder for the target equals the holder for the method, or
-    // - the target method always triggers class initialization of its holder before any other side
-    //   effect (hence preserving class initialization semantics).
-    // - there is no non-trivial class initializer.
-    DexType targetHolder = target.method.getHolder();
-    if (method.method.getHolder() == targetHolder) {
-      return true;
-    }
-    DexClass clazz = inliner.appInfo.definitionFor(targetHolder);
-    assert clazz != null;
-    if (target.getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) {
-      return true;
-    }
-    return classInitializationHasNoSideffects(targetHolder);
-  }
-
-  /**
-   * Check for class initializer side effects when loading this class, as inlining might remove the
-   * load operation.
-   * <p>
-   * See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5.
-   * <p>
-   * For simplicity, we are conservative and consider all interfaces, not only the ones with default
-   * methods.
-   */
-  private boolean classInitializationHasNoSideffects(DexType classToCheck) {
-    DexClass clazz = inliner.appInfo.definitionFor(classToCheck);
-    if ((clazz == null)
-        || clazz.hasNonTrivialClassInitializer()
-        || clazz.defaultValuesForStaticFieldsMayTriggerAllocation()) {
-      return false;
-    }
-    for (DexType iface : clazz.interfaces.values) {
-      if (!classInitializationHasNoSideffects(iface)) {
-        return false;
-      }
-    }
-    return clazz.superType == null || classInitializationHasNoSideffects(clazz.superType);
-  }
-
-  private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
-    // 10 is found from measuring.
-    return inliner.isDoubleInliningTarget(callSiteInformation, candidate)
-        && candidate.getCode().estimatedSizeForInliningAtMost(10);
-  }
-
-  private boolean passesInliningConstraints(InvokeMethod invoke, DexEncodedMethod candidate,
-      Reason reason) {
-    if (method == candidate) {
-      // Cannot handle recursive inlining at this point.
-      // Force inlined method should never be recursive.
-      assert !candidate.getOptimizationInfo().forceInline();
-      if (info != null) {
-        info.exclude(invoke, "direct recursion");
-      }
-      return false;
-    }
-
-    if (reason != Reason.FORCE && isProcessedConcurrently.test(candidate)) {
-      if (info != null) {
-        info.exclude(invoke, "is processed in parallel");
-      }
-      return false;
-    }
-
-    // Abort inlining attempt if method -> target access is not right.
-    if (!inliner.hasInliningAccess(method, candidate)) {
-      if (info != null) {
-        info.exclude(invoke, "target does not have right access");
-      }
-      return false;
-    }
-
-    DexClass holder = inliner.appInfo.definitionFor(candidate.method.getHolder());
-    if (holder.isInterface()) {
-      // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at
-      // runtime.
-      if (info != null) {
-        info.exclude(invoke, "Do not inline target if method holder is an interface class");
-      }
-      return false;
-    }
-
-    if (holder.isLibraryClass()) {
-      // Library functions should not be inlined.
-      return false;
-    }
-
-    // Don't inline if target is synchronized.
-    if (candidate.accessFlags.isSynchronized()) {
-      if (info != null) {
-        info.exclude(invoke, "target is synchronized");
-      }
-      return false;
-    }
-
-    // Attempt to inline a candidate that is only called twice.
-    if ((reason == Reason.DUAL_CALLER) && (inliner.doubleInlining(method, candidate) == null)) {
-      if (info != null) {
-        info.exclude(invoke, "target is not ready for double inlining");
-      }
-      return false;
-    }
-
-    if (reason == Reason.SIMPLE) {
-      // If we are looking for a simple method, only inline if actually simple.
-      Code code = candidate.getCode();
-      if (!code.estimatedSizeForInliningAtMost(inliningInstructionLimit)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  public InlineAction computeForInvokeWithReceiver(
-      InvokeMethodWithReceiver invoke, DexType invocationContext) {
-    DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
-    if (candidate == null || inliner.isBlackListed(candidate.method)) {
-      return null;
-    }
-
-    // We can only inline an instance method call if we preserve the null check semantic (which
-    // would throw NullPointerException if the receiver is null). Therefore we can inline only if
-    // one of the following conditions is true:
-    // * the candidate inlinee checks null receiver before any side effect
-    // * the receiver is known to be non-null
-    boolean receiverIsNeverNull =
-        !typeEnvironment.getLatticeElement(invoke.getReceiver()).isNullable();
-    if (!receiverIsNeverNull
-        && !candidate.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) {
-      if (info != null) {
-        info.exclude(invoke, "receiver for candidate can be null");
-      }
-      return null;
-    }
-
-    Reason reason = computeInliningReason(candidate);
-    if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
-      // Abort inlining attempt if the single target is not an inlining candidate.
-      if (info != null) {
-        info.exclude(invoke, "target is not identified for inlining");
-      }
-      return null;
-    }
-
-    if (!passesInliningConstraints(invoke, candidate, reason)) {
-      return null;
-    }
-
-    if (info != null) {
-      info.include(invoke.getType(), candidate);
-    }
-    return new InlineAction(candidate, invoke, reason);
-  }
-
-  public InlineAction computeForInvokeStatic(InvokeStatic invoke, DexType invocationContext) {
-    DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
-    if (candidate == null || inliner.isBlackListed(candidate.method)) {
-      return null;
-    }
-
-    Reason reason = computeInliningReason(candidate);
-    // Determine if this should be inlined no matter how big it is.
-    if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
-      // Abort inlining attempt if the single target is not an inlining candidate.
-      if (info != null) {
-        info.exclude(invoke, "target is not identified for inlining");
-      }
-      return null;
-    }
-
-    // Abort inlining attempt if we can not guarantee class for static target has been initialized.
-    if (!canInlineStaticInvoke(method, candidate)) {
-      if (info != null) {
-        info.exclude(invoke, "target is static but we cannot guarantee class has been initialized");
-      }
-      return null;
-    }
-
-    if (!passesInliningConstraints(invoke, candidate, reason)) {
-      return null;
-    }
-
-    if (info != null) {
-      info.include(invoke.getType(), candidate);
-    }
-    return new InlineAction(candidate, invoke, reason);
-  }
-
-  public InlineAction computeForInvokePolymorpic(
-      InvokePolymorphic invoke, DexType invocationContext) {
-    // TODO: No inlining of invoke polymorphic for now.
-    if (info != null) {
-      info.exclude(invoke, "inlining through invoke signature polymorpic is not supported");
-    }
-    return null;
-  }
+  InlineAction computeForInvokePolymorphic(
+      InvokePolymorphic invoke, DexType invocationContext);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
new file mode 100644
index 0000000..2b0c7e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -0,0 +1,27 @@
+// 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.ApiLevelException;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import java.util.ListIterator;
+
+interface InliningStrategy {
+  boolean exceededAllowance();
+
+  void markInlined(IRCode inlinee);
+
+  void ensureMethodProcessed(
+      DexEncodedMethod target, IRCode inlinee) throws ApiLevelException;
+
+  ListIterator<BasicBlock> updateTypeInformationIfNeeded(IRCode inlinee,
+      ListIterator<BasicBlock> blockIterator, BasicBlock block, BasicBlock invokeSuccessor);
+
+  DexType getReceiverTypeIfKnown(InvokeMethod invoke);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 7af555c..9462354 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DebugLocalInfo;
@@ -1003,10 +1004,11 @@
     }
 
     @Override
-    public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
+    public IRCode buildIR(DexEncodedMethod encodedMethod,
+        AppInfo appInfo, InternalOptions options, Origin origin)
         throws ApiLevelException {
       OutlineSourceCode source = new OutlineSourceCode(outline);
-      IRBuilder builder = new IRBuilder(encodedMethod, source, options);
+      IRBuilder builder = new IRBuilder(encodedMethod, appInfo, source, options);
       return builder.build();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
index bd85615..1284671 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
@@ -93,7 +93,7 @@
     List<DexEncodedField> switchMapFields = Arrays.stream(clazz.staticFields())
         .filter(this::maybeIsSwitchMap).collect(Collectors.toList());
     if (!switchMapFields.isEmpty()) {
-      IRCode initializer = clazz.getClassInitializer().buildIR(options, clazz.origin);
+      IRCode initializer = clazz.getClassInitializer().buildIR(appInfo, options, clazz.origin);
       switchMapFields.forEach(field -> extractSwitchMap(field, initializer));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
new file mode 100644
index 0000000..7d13b1a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -0,0 +1,385 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+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.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.google.common.collect.Streams;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+public final class ClassInliner {
+  private final DexItemFactory factory;
+  private final ConcurrentHashMap<DexType, Boolean> knownClasses = new ConcurrentHashMap<>();
+
+  private static final Map<DexField, Integer> NO_MAPPING = new IdentityHashMap<>();
+
+  public interface InlinerAction {
+    void inline(Map<InvokeMethodWithReceiver, InliningInfo> methods) throws ApiLevelException;
+  }
+
+  public ClassInliner(DexItemFactory factory) {
+    this.factory = factory;
+  }
+
+  // Process method code and inline eligible class instantiations, in short:
+  //
+  // - collect all 'new-instance' instructions in the original code. Note that class
+  // inlining, if happens, mutates code and can add 'new-instance' instructions.
+  // Processing them as well is possible, but does not seem to have much value.
+  //
+  // - for each 'new-instance' we check if it is eligible for inlining, i.e:
+  //     -> the class of the new instance is 'eligible' (see computeClassEligible(...))
+  //     -> the instance is initialized with 'eligible' constructor (see
+  //        onlyInitializesFieldsWithNoOtherSideEffects flag in method's optimization
+  //        info); eligible constructor also defines a set of instance fields directly
+  //        initialized with parameter values, called field initialization mapping below
+  //     -> has only 'eligible' uses, i.e:
+  //          * it is a receiver of a field read if the field is present in the
+  //            field initialization mapping
+  //          * it is a receiver of virtual or interface call with single target being
+  //            a method only reading fields in the current field initialization mapping
+  //
+  // - inline eligible 'new-instance' instructions, i.e:
+  //     -> force inline methods called on the instance (which may introduce additional
+  //        instance field reads, but only for fields present in the current field
+  //        initialization mapping)
+  //     -> replace instance field reads with appropriate values passed to the constructor
+  //        according to field initialization mapping
+  //     -> remove constructor call
+  //     -> remove 'new-instance' instructions
+  //
+  // For example:
+  //
+  // Original code:
+  //   class C {
+  //     static class L {
+  //       final int x;
+  //       L(int x) {
+  //         this.x = x;
+  //       }
+  //       int getX() {
+  //         return x;
+  //       }
+  //     }
+  //     static int method1() {
+  //       return new L(1).x;
+  //     }
+  //     static int method2() {
+  //       return new L(1).getX();
+  //     }
+  //   }
+  //
+  // Code after class C is 'inlined':
+  //   class C {
+  //     static int method1() {
+  //       return 1;
+  //     }
+  //     static int method2() {
+  //       return 1;
+  //     }
+  //   }
+  //
+  public final void processMethodCode(
+      AppInfoWithSubtyping appInfo,
+      DexEncodedMethod method,
+      IRCode code,
+      Predicate<DexEncodedMethod> isProcessedConcurrently,
+      InlinerAction inliner) throws ApiLevelException {
+
+    // Collect all the new-instance instructions in the code before inlining.
+    List<NewInstance> newInstances = Streams.stream(code.instructionIterator())
+        .filter(Instruction::isNewInstance)
+        .map(Instruction::asNewInstance)
+        .collect(Collectors.toList());
+
+    nextNewInstance:
+    for (NewInstance newInstance : newInstances) {
+      Value eligibleInstance = newInstance.outValue();
+      if (eligibleInstance == null) {
+        continue;
+      }
+
+      DexType eligibleClass = newInstance.clazz;
+      if (!isClassEligible(appInfo, eligibleClass)) {
+        continue;
+      }
+
+      // No Phi users.
+      if (eligibleInstance.numberOfPhiUsers() > 0) {
+        continue;
+      }
+
+      Set<Instruction> uniqueUsers = eligibleInstance.uniqueUsers();
+
+      // Find an initializer invocation.
+      InvokeDirect eligibleInitCall = null;
+      Map<DexField, Integer> mappings = null;
+      for (Instruction user : uniqueUsers) {
+        if (!user.isInvokeDirect()) {
+          continue;
+        }
+
+        InvokeDirect candidate = user.asInvokeDirect();
+        DexMethod candidateInit = candidate.getInvokedMethod();
+        if (factory.isConstructor(candidateInit) &&
+            candidate.inValues().lastIndexOf(eligibleInstance) == 0) {
+
+          if (candidateInit.holder != eligibleClass) {
+            // Inlined constructor call? We won't get field initialization mapping in this
+            // case, but since we only support eligible classes extending java.lang.Object,
+            // it's safe to assume an empty mapping.
+            if (candidateInit.holder == factory.objectType) {
+              mappings = Collections.emptyMap();
+            }
+
+          } else {
+            // Is it a call to an *eligible* constructor?
+            mappings = getConstructorFieldMappings(appInfo, candidateInit, isProcessedConcurrently);
+          }
+
+          eligibleInitCall = candidate;
+          break;
+        }
+      }
+
+      if (mappings == null) {
+        continue;
+      }
+
+      // Check all regular users.
+      Map<InvokeMethodWithReceiver, InliningInfo> methodCalls = new IdentityHashMap<>();
+
+      for (Instruction user : uniqueUsers) {
+        if (user == eligibleInitCall) {
+          continue /* next user */;
+        }
+
+        if (user.isInstanceGet()) {
+          InstanceGet instanceGet = user.asInstanceGet();
+          if (mappings.containsKey(instanceGet.getField())) {
+            continue /* next user */;
+          }
+
+          // Not replaceable field read.
+          continue nextNewInstance;
+        }
+
+        if (user.isInvokeVirtual() || user.isInvokeInterface()) {
+          InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
+          if (invoke.inValues().lastIndexOf(eligibleInstance) > 0) {
+            continue nextNewInstance; // Instance must only be passes as a receiver.
+          }
+
+          DexEncodedMethod singleTarget =
+              findSingleTarget(appInfo, invoke, eligibleClass);
+          if (singleTarget == null) {
+            continue nextNewInstance;
+          }
+          if (isProcessedConcurrently.test(singleTarget)) {
+            continue nextNewInstance;
+          }
+          if (method == singleTarget) {
+            continue nextNewInstance; // Don't inline itself.
+          }
+
+          if (!singleTarget.getOptimizationInfo()
+              .isReceiverOnlyUsedForReadingFields(mappings.keySet())) {
+            continue nextNewInstance; // Target must be trivial.
+          }
+
+          if (!singleTarget.isInliningCandidate(method, Reason.SIMPLE, appInfo)) {
+            // We won't be able to inline it here.
+
+            // Note that there may be some false negatives here since the method may
+            // reference private fields of its class which are supposed to be replaced
+            // with arguments after inlining. We should try and improve it later.
+
+            // Using -allowaccessmodification mitigates this.
+            continue nextNewInstance;
+          }
+
+          methodCalls.put(invoke, new InliningInfo(singleTarget, eligibleClass));
+          continue /* next user */;
+        }
+
+        continue nextNewInstance; // Unsupported user.
+      }
+
+      // Force-inline of method invocation if any.
+      inlineAllCalls(inliner, methodCalls);
+      assert assertOnlyConstructorAndFieldReads(eligibleInstance, eligibleInitCall, mappings);
+
+      // Replace all field reads with arguments passed to the constructor.
+      patchFieldReads(eligibleInstance, eligibleInitCall, mappings);
+      assert assertOnlyConstructor(eligibleInstance, eligibleInitCall);
+
+      // Remove constructor call and new-instance instructions.
+      removeInstruction(eligibleInitCall);
+      removeInstruction(newInstance);
+      code.removeAllTrivialPhis();
+    }
+  }
+
+  private void inlineAllCalls(InlinerAction inliner,
+      Map<InvokeMethodWithReceiver, InliningInfo> methodCalls) throws ApiLevelException {
+    if (!methodCalls.isEmpty()) {
+      inliner.inline(methodCalls);
+    }
+  }
+
+  private void patchFieldReads(
+      Value instance, InvokeDirect invokeMethod, Map<DexField, Integer> mappings) {
+    for (Instruction user : instance.uniqueUsers()) {
+      if (!user.isInstanceGet()) {
+        continue;
+      }
+      InstanceGet fieldRead = user.asInstanceGet();
+
+      // Replace the field read with
+      assert mappings.containsKey(fieldRead.getField());
+      Value arg = invokeMethod.inValues().get(1 + mappings.get(fieldRead.getField()));
+      assert arg != null;
+      Value value = fieldRead.outValue();
+      if (value != null) {
+        value.replaceUsers(arg);
+        assert value.numberOfAllUsers() == 0;
+      }
+
+      // Remove instruction.
+      removeInstruction(fieldRead);
+    }
+  }
+
+  private void removeInstruction(Instruction instruction) {
+    instruction.inValues().forEach(v -> v.removeUser(instruction));
+    instruction.getBlock().removeInstruction(instruction);
+  }
+
+  private boolean assertOnlyConstructorAndFieldReads(
+      Value instance, InvokeDirect invokeMethod, Map<DexField, Integer> mappings) {
+    for (Instruction user : instance.uniqueUsers()) {
+      if (user != invokeMethod &&
+          !(user.isInstanceGet() && mappings.containsKey(user.asFieldInstruction().getField()))) {
+        throw new Unreachable("Not all calls are inlined!");
+      }
+    }
+    return true;
+  }
+
+  private boolean assertOnlyConstructor(Value instance, InvokeDirect invokeMethod) {
+    for (Instruction user : instance.uniqueUsers()) {
+      if (user != invokeMethod) {
+        throw new Unreachable("Not all field reads are substituted!");
+      }
+    }
+    return true;
+  }
+
+  private DexEncodedMethod findSingleTarget(
+      AppInfo appInfo, InvokeMethodWithReceiver invoke, DexType instanceType) {
+
+    // We don't use computeSingleTarget(...) on invoke since it sometimes fails to
+    // find the single target, while this code may be more successful since we exactly
+    // know what is the actual type of the receiver.
+
+    // Note that we also intentionally limit ourselves to methods directly defined in
+    // the instance's class. This may be improved later.
+
+    DexClass clazz = appInfo.definitionFor(instanceType);
+    if (clazz != null) {
+      DexMethod callee = invoke.getInvokedMethod();
+      for (DexEncodedMethod candidate : clazz.virtualMethods()) {
+        if (candidate.method.name == callee.name && candidate.method.proto == callee.proto) {
+          return candidate;
+        }
+      }
+    }
+    return null;
+  }
+
+  private Map<DexField, Integer> getConstructorFieldMappings(
+      AppInfo appInfo, DexMethod init, Predicate<DexEncodedMethod> isProcessedConcurrently) {
+    assert isClassEligible(appInfo, init.holder);
+
+    DexEncodedMethod definition = appInfo.definitionFor(init);
+    if (definition == null) {
+      return NO_MAPPING;
+    }
+
+    if (isProcessedConcurrently.test(definition)) {
+      return NO_MAPPING;
+    }
+
+    if (definition.accessFlags.isAbstract() || definition.accessFlags.isNative()) {
+      return NO_MAPPING;
+    }
+
+    return definition.getOptimizationInfo().onlyInitializesFieldsWithNoOtherSideEffects();
+  }
+
+  private boolean isClassEligible(AppInfo appInfo, DexType clazz) {
+    Boolean eligible = knownClasses.get(clazz);
+    if (eligible == null) {
+      Boolean computed = computeClassEligible(appInfo, clazz);
+      Boolean existing = knownClasses.putIfAbsent(clazz, computed);
+      assert existing == null || existing == computed;
+      eligible = existing == null ? computed : existing;
+    }
+    return eligible;
+  }
+
+  // Class is eligible for this optimization. Eligibility implementation:
+  //   - not an abstract or interface
+  //   - directly extends java.lang.Object
+  //   - does not declare finalizer
+  //   - does not trigger any static initializers
+  private boolean computeClassEligible(AppInfo appInfo, DexType clazz) {
+    DexClass definition = appInfo.definitionFor(clazz);
+    if (definition == null || definition.isLibraryClass() ||
+        definition.accessFlags.isAbstract() || definition.accessFlags.isInterface()) {
+      return false;
+    }
+
+    // Must directly extend Object.
+    if (definition.superType != factory.objectType) {
+      return false;
+    }
+
+    // Class must not define finalizer.
+    for (DexEncodedMethod method : definition.virtualMethods()) {
+      if (method.method.name == factory.finalizeMethodName &&
+          method.method.proto == factory.objectMethods.finalize.proto) {
+        return false;
+      }
+    }
+
+    // Check for static initializers in this class or any of interfaces it implements.
+    return !appInfo.canTriggerStaticInitializer(clazz);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index b2100e2..f8134f4 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.UseRegistry;
@@ -39,9 +40,10 @@
 
   @Override
   public final IRCode buildIR(
-      DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
+      DexEncodedMethod encodedMethod, AppInfo appInfo,
+      InternalOptions options, Origin origin)
       throws ApiLevelException {
-    return new IRBuilder(encodedMethod, sourceCode, options).build();
+    return new IRBuilder(encodedMethod, appInfo, sourceCode, options).build();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index c359fc7..2765a85 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -834,7 +834,7 @@
       return;
     }
     if (Log.ENABLED) {
-      Log.verbose(getClass(), "Register new instatiation of `%s`.", clazz);
+      Log.verbose(getClass(), "Register new instantiation of `%s`.", clazz);
     }
     workList.add(Action.markInstantiated(clazz, KeepReason.instantiatedIn(method)));
   }
@@ -1344,7 +1344,7 @@
 
   private void handleProguardReflectiveBehavior(DexEncodedMethod method) {
     try {
-      IRCode code = method.buildIR(options, appInfo.originFor(method.method.holder));
+      IRCode code = method.buildIR(appInfo, options, appInfo.originFor(method.method.holder));
       code.instructionIterator().forEachRemaining(this::handleProguardReflectiveBehavior);
     } catch (ApiLevelException e) {
       // Ignore this exception here. It will be hit again further in the pipeline when
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 8eb8e9c..4a25e42 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -717,6 +717,11 @@
       }
     }
 
+    private StringDiagnostic parseClassTypeUnexpected(Origin origin, TextPosition start) {
+      return new StringDiagnostic(
+          "Expected [!]interface|@interface|class|enum", origin, getPosition(start));
+    }
+
     private void parseClassType(
         ProguardClassSpecification.Builder builder) throws ProguardRuleParserException {
       skipWhitespace();
@@ -724,17 +729,21 @@
       if (acceptChar('!')) {
         builder.setClassTypeNegated(true);
       }
-      if (acceptString("interface")) {
+      if (acceptChar('@')) {
+        skipWhitespace();
+        if (acceptString("interface")) {
+          builder.setClassType(ProguardClassType.ANNOTATION_INTERFACE);
+        } else {
+          throw reporter.fatalError(parseClassTypeUnexpected(origin, start));
+        }
+      } else if (acceptString("interface")) {
         builder.setClassType(ProguardClassType.INTERFACE);
-      } else if (acceptString("@interface")) {
-        builder.setClassType(ProguardClassType.ANNOTATION_INTERFACE);
       } else if (acceptString("class")) {
         builder.setClassType(ProguardClassType.CLASS);
       } else if (acceptString("enum")) {
         builder.setClassType(ProguardClassType.ENUM);
       } else {
-        throw reporter.fatalError(new StringDiagnostic(
-            "Expected [!]interface|@interface|class|enum", origin, getPosition(start)));
+        throw reporter.fatalError(parseClassTypeUnexpected(origin, start));
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 4c40d61..92c68aa 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -81,6 +81,7 @@
       enableDevirtualization = false;
       enableNonNullTracking = false;
       enableInlining = false;
+      enableClassInlining = false;
       enableSwitchMapRemoval = false;
       outline.enabled = false;
       enableValuePropagation = false;
@@ -97,6 +98,7 @@
   public boolean enableDevirtualization = true;
   public boolean enableNonNullTracking = true;
   public boolean enableInlining = true;
+  public boolean enableClassInlining = true;
   public int inliningInstructionLimit = 5;
   public boolean enableSwitchMapRemoval = true;
   public final OutlineOptions outline = new OutlineOptions();
diff --git a/src/test/examples/multidex/main-dex-rules-whyareyoukeeping.txt b/src/test/examples/multidex/main-dex-rules-whyareyoukeeping.txt
new file mode 100644
index 0000000..4a94d41
--- /dev/null
+++ b/src/test/examples/multidex/main-dex-rules-whyareyoukeeping.txt
@@ -0,0 +1,30 @@
+# 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class * extends **.Instrumentation {
+  <init>();
+}
+-keep public class * extends **.Application {
+  <init>();
+  void attachBaseContext(*.Context);
+}
+-keep public class * extends **.Activity {
+  <init>();
+}
+-keep public class * extends **.Service {
+  <init>();
+}
+-keep public class * extends **.ContentProvider {
+ <init>();
+}
+-keep public class * extends **.BroadcastReceiver {
+ <init>();
+}
+-keep public class * extends **.BackupAgent {
+ <init>();
+}
+
+-whyareyoukeeping class **
diff --git a/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java b/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java
index f2344b5..1d6772a 100644
--- a/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java
+++ b/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package lambdadesugaring;
 
+import java.io.Serializable;
+
 public class ValueAdjustments {
   interface B2i {
     int foo(Byte i);
@@ -37,6 +39,22 @@
     Number f();
   }
 
+  interface iSerializableOut {
+    Serializable f();
+  }
+
+  interface iSerializableInt {
+    Object f(Serializable s);
+  }
+
+  interface iComparableOut<T> {
+    Comparable<T> f();
+  }
+
+  interface iComparableInt<T> {
+    Object f(Comparable<T> c);
+  }
+
   interface iByte {
     Byte f();
   }
@@ -164,6 +182,30 @@
         .append(((iNumber) ValueAdjustments::D).f()).append('\n');
   }
 
+  private static void checkSerializableOut(StringBuffer builder) {
+    builder
+        .append(((iSerializableOut) ValueAdjustments::z).f()).append(' ')
+        .append(((iSerializableOut) ValueAdjustments::c).f()).append(' ')
+        .append(((iSerializableOut) ValueAdjustments::b).f()).append(' ')
+        .append(((iSerializableOut) ValueAdjustments::s).f()).append(' ')
+        .append(((iSerializableOut) ValueAdjustments::i).f()).append(' ')
+        .append(((iSerializableOut) ValueAdjustments::j).f()).append(' ')
+        .append(((iSerializableOut) ValueAdjustments::f).f()).append(' ')
+        .append(((iSerializableOut) ValueAdjustments::d).f()).append(' ');
+  }
+
+  private static void checkComparableOut(StringBuffer builder) {
+    builder
+        .append(((iComparableOut) ValueAdjustments::z).f()).append(' ')
+        .append(((iComparableOut) ValueAdjustments::c).f()).append(' ')
+        .append(((iComparableOut) ValueAdjustments::b).f()).append(' ')
+        .append(((iComparableOut) ValueAdjustments::s).f()).append(' ')
+        .append(((iComparableOut) ValueAdjustments::i).f()).append(' ')
+        .append(((iComparableOut) ValueAdjustments::j).f()).append(' ')
+        .append(((iComparableOut) ValueAdjustments::f).f()).append(' ')
+        .append(((iComparableOut) ValueAdjustments::d).f()).append(' ');
+  }
+
   private static void checkBoxes(StringBuffer builder) {
     builder
         .append(((iBoolean) ValueAdjustments::z).f()).append(' ')
@@ -310,20 +352,44 @@
         .append(((BnUnB) ValueAdjustments::boxingAndUnboxingW).foo(true, false, (byte) 1, (byte) 2,
             (char) 33, (char) 44, (short) 5, (short) 6, 7, 8, 9, 10L, 11, 12f, 13, 14d))
         .append('\n')
+        .append(((BnUnB) ValueAdjustments::allSerializable).foo(true, false, (byte) 1, (byte) 2,
+            (char) 33, (char) 44, (short) 5, (short) 6, 7, 8, 9, 10L, 11, 12f, 13, 14d))
+        .append('\n')
+        .append(((BnUnB) ValueAdjustments::allComparable).foo(true, false, (byte) 1, (byte) 2,
+            (char) 33, (char) 44, (short) 5, (short) 6, 7, 8, 9, 10L, 11, 12f, 13, 14d))
+        .append('\n')
         .append(((B2i) (Integer::new)).foo(Byte.valueOf((byte) 44))).append('\n');
   }
 
   static String boxingAndUnboxing(Boolean Z, boolean z, Byte B, byte b, Character C, char c,
       Short S, short s, Integer I, int i, Long L, long l, Float F, float f, Double D, double d) {
-    return "" + Z + ":" + z + ":" + B + ":" + b + ":" + C + ":" + c + ":" + S + ":" + s
-        + ":" + I + ":" + i + ":" + L + ":" + l + ":" + F + ":" + f + ":" + D + ":" + d;
+    return "boxingAndUnboxing: " + Z + ":" + z + ":" + B + ":" + b + ":" + C + ":" + c + ":" + S +
+        ":" + s + ":" + I + ":" + i + ":" + L + ":" + l + ":" + F + ":" + f + ":" + D + ":" + d;
   }
 
   static String boxingAndUnboxingW(boolean Z, boolean z, double B, double b,
       double C, double c, double S, double s, double I, double i, double L, double l,
       double F, double f, double D, double d) {
-    return "" + Z + ":" + z + ":" + B + ":" + b + ":" + C + ":" + c + ":" + S + ":" + s
-        + ":" + I + ":" + i + ":" + L + ":" + l + ":" + F + ":" + f + ":" + D + ":" + d;
+    return "boxingAndUnboxingW: " + Z + ":" + z + ":" + B + ":" + b + ":" + C + ":" + c + ":" + S +
+        ":" + s + ":" + I + ":" + i + ":" + L + ":" + l + ":" + F + ":" + f + ":" + D + ":" + d;
+  }
+
+  static String allSerializable(
+      Serializable Z, Serializable z, Serializable B, Serializable b,
+      Serializable C, Serializable c, Serializable S, Serializable s,
+      Serializable I, Serializable i, Serializable L, Serializable l,
+      Serializable F, Serializable f, Serializable D, Serializable d) {
+    return "allSerializable: " + Z + ":" + z + ":" + B + ":" + b + ":" + C + ":" + c + ":" + S +
+        ":" + s + ":" + I + ":" + i + ":" + L + ":" + l + ":" + F + ":" + f + ":" + D + ":" + d;
+  }
+
+  static String allComparable(
+      Comparable Z, Comparable z, Comparable B, Comparable b,
+      Comparable C, Comparable c, Comparable S, Comparable s,
+      Comparable I, Comparable i, Comparable L, Comparable l,
+      Comparable F, Comparable f, Comparable D, Comparable d) {
+    return "allComparable: " + Z + ":" + z + ":" + B + ":" + b + ":" + C + ":" + c + ":" + S +
+        ":" + s + ":" + I + ":" + i + ":" + L + ":" + l + ":" + F + ":" + f + ":" + D + ":" + d;
   }
 
   static boolean z() {
@@ -393,7 +459,7 @@
   private static void bB70348575(StringBuffer builder) {
     B70348575_C1 c1 = new B70348575_C1();
     B70348575_A1 a = c1.getB().get();
-    builder.append(a.greet()).append('\n');;
+    builder.append(a.greet()).append('\n');
   }
 
   public static void main(String[] args) {
@@ -410,6 +476,8 @@
 
     checkBoxes(builder);
     checkNumber(builder);
+    checkSerializableOut(builder);
+    checkComparableOut(builder);
     checkObject(builder);
 
     checkMisc(builder);
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index ac0955e..b27569b 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -995,6 +995,14 @@
       "625-checker-licm-regressions"
   );
 
+  private static List<String> requireClassInliningToBeDisabled = ImmutableList.of(
+      // Test depends on exception produced for missing method or similar cases, but
+      // after class inlining removes class instantiations and references the exception
+      // is not produced.
+      "042-new-instance",
+      "075-verification-error"
+  );
+
   private static List<String> hasMissingClasses = ImmutableList.of(
       "091-override-package-private-method",
       "003-omnibus-opcodes",
@@ -1085,6 +1093,8 @@
     private final boolean outputMayDiffer;
     // Whether to disable inlining
     private final boolean disableInlining;
+    // Whether to disable class inlining
+    private final boolean disableClassInlining;
     // Has missing classes.
     private final boolean hasMissingClasses;
 
@@ -1102,6 +1112,7 @@
         boolean expectedToFailWithX8,
         boolean outputMayDiffer,
         boolean disableInlining,
+        boolean disableClassInlining,
         boolean hasMissingClasses,
         DexVm dexVm) {
       this.name = name;
@@ -1117,6 +1128,7 @@
       this.expectedToFailWithX8 = expectedToFailWithX8;
       this.outputMayDiffer = outputMayDiffer;
       this.disableInlining = disableInlining;
+      this.disableClassInlining = disableClassInlining;
       this.hasMissingClasses = hasMissingClasses;
     }
 
@@ -1142,6 +1154,7 @@
           false,
           false,
           disableInlining,
+          true, // Disable class inlining for JCTF tests.
           false,
           dexVm);
     }
@@ -1306,6 +1319,7 @@
                 expectedToFailWithCompilerSet.contains(name),
                 outputMayDiffer.contains(name),
                 requireInliningToBeDisabled.contains(name),
+                requireClassInliningToBeDisabled.contains(name),
                 hasMissingClasses.contains(name),
                 dexVm));
       }
@@ -1372,11 +1386,12 @@
       String resultPath,
       CompilationMode compilationMode,
       boolean disableInlining,
+      boolean disableClassInlining,
       boolean hasMissingClasses)
       throws IOException, ProguardRuleParserException, ExecutionException, CompilationException,
       CompilationFailedException {
     executeCompilerUnderTest(compilerUnderTest, fileNames, resultPath, compilationMode, null,
-        disableInlining, hasMissingClasses);
+        disableInlining, disableClassInlining, hasMissingClasses);
   }
 
   private void executeCompilerUnderTest(
@@ -1385,7 +1400,9 @@
       String resultPath,
       CompilationMode mode,
       String keepRulesFile,
-      boolean disableInlining, boolean hasMissingClasses)
+      boolean disableInlining,
+      boolean disableClassInlining,
+      boolean hasMissingClasses)
       throws IOException, ProguardRuleParserException, ExecutionException, CompilationException,
         CompilationFailedException {
     assert mode != null;
@@ -1523,6 +1540,9 @@
                 if (disableInlining) {
                   options.enableInlining = false;
                 }
+                if (disableClassInlining) {
+                  options.enableClassInlining = false;
+                }
                 options.lineNumberOptimization = LineNumberOptimization.OFF;
                 // Some tests actually rely on missing classes for what they test.
                 options.ignoreMissingClasses = hasMissingClasses;
@@ -1734,7 +1754,8 @@
       throws IOException, ProguardRuleParserException, ExecutionException, CompilationException,
       CompilationFailedException {
     executeCompilerUnderTest(compilerUnderTest, fileNames, resultDir.getAbsolutePath(), mode,
-        specification.disableInlining, specification.hasMissingClasses);
+        specification.disableInlining, specification.disableClassInlining,
+        specification.hasMissingClasses);
 
     if (!ToolHelper.artSupported()) {
       return;
@@ -1907,7 +1928,8 @@
       try {
         executeCompilerUnderTest(
             compilerUnderTest, fileNames, resultDir.getCanonicalPath(), compilationMode,
-            specification.disableInlining, specification.hasMissingClasses);
+            specification.disableInlining, specification.disableClassInlining,
+            specification.hasMissingClasses);
       } catch (CompilationException | CompilationFailedException e) {
         throw new CompilationError(e.getMessage(), e);
       } catch (ExecutionException e) {
@@ -1919,13 +1941,15 @@
       expectException(Throwable.class);
       executeCompilerUnderTest(
           compilerUnderTest, fileNames, resultDir.getCanonicalPath(), compilationMode,
-          specification.disableInlining, specification.hasMissingClasses);
+          specification.disableInlining, specification.disableClassInlining,
+          specification.hasMissingClasses);
       System.err.println("Should have failed R8/D8 compilation with an exception.");
       return;
     } else {
       executeCompilerUnderTest(
           compilerUnderTest, fileNames, resultDir.getCanonicalPath(), compilationMode,
-          specification.disableInlining, specification.hasMissingClasses);
+          specification.disableInlining, specification.disableClassInlining,
+          specification.hasMissingClasses);
     }
 
     if (!specification.skipArt && ToolHelper.artSupported()) {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index b973818..62dd4af 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -155,24 +155,27 @@
                 .build());
         break;
       }
-      case R8:
-        {
-          R8Command command =
-              addInputFile(R8Command.builder())
-                  .setOutput(getOutputFile(), outputMode)
-                  .setMode(mode)
-                  .build();
-          ExceptionUtils.withR8CompilationHandler(
-              command.getReporter(),
-              () ->
-                  ToolHelper.runR8(
-                      command,
-                      options -> {
-                        options.lineNumberOptimization = LineNumberOptimization.OFF;
-                        options.enableCfFrontend = frontend == Frontend.CF;
-                      }));
-          break;
-        }
+      case R8: {
+        R8Command command =
+            addInputFile(R8Command.builder())
+                .setOutput(getOutputFile(), outputMode)
+                .setMode(mode)
+                .build();
+        ExceptionUtils.withR8CompilationHandler(
+            command.getReporter(),
+            () ->
+                ToolHelper.runR8(
+                    command,
+                    options -> {
+                      options.lineNumberOptimization = LineNumberOptimization.OFF;
+                      options.enableCfFrontend = frontend == Frontend.CF;
+                      if (output == Output.CF) {
+                        // Class inliner is not supported with CF backend yet.
+                        options.enableClassInlining = false;
+                      }
+                    }));
+        break;
+      }
       default:
         throw new Unreachable();
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index e256ae4..c6a2bd3 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -39,6 +39,7 @@
 
   private void configure(InternalOptions options) {
     options.enableClassMerging = true;
+    options.enableClassInlining = false;
   }
 
   private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index 443a20e..16785f6 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -58,7 +58,7 @@
     AppInfo appInfo = new AppInfo(dexApplication);
     DexInspector dexInspector = new DexInspector(appInfo.app);
     DexEncodedMethod foo = dexInspector.clazz(mainClass.getName()).method(signature).getMethod();
-    IRCode irCode = foo.buildIR(TEST_OPTIONS, Origin.unknown());
+    IRCode irCode = foo.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
     NonNullTracker nonNullTracker = new NonNullTracker();
     nonNullTracker.addNonNull(irCode);
     TypeAnalysis analysis = new TypeAnalysis(appInfo, foo, irCode);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
index 509b922..967b33e 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
@@ -125,7 +125,7 @@
                 new MethodSignature("subtractConstants8bitRegisters", "int", ImmutableList.of()))
             .getMethod();
     try {
-      IRCode irCode = subtract.buildIR(TEST_OPTIONS, Origin.unknown());
+      IRCode irCode = subtract.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, subtract, irCode);
       analysis.forEach((v, l) -> {
         assertEither(l, PRIMITIVE, NULL, TOP);
@@ -143,7 +143,7 @@
             .method(new MethodSignature("fibonacci", "int", ImmutableList.of("int")))
             .getMethod();
     try {
-      IRCode irCode = fib.buildIR(TEST_OPTIONS, Origin.unknown());
+      IRCode irCode = fib.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, fib, irCode);
       analysis.forEach((v, l) -> {
         assertEither(l, PRIMITIVE, NULL);
@@ -161,7 +161,7 @@
             .method(new MethodSignature("test1", "int[]", ImmutableList.of()))
             .getMethod();
     try {
-      IRCode irCode = test1.buildIR(TEST_OPTIONS, Origin.unknown());
+      IRCode irCode = test1.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, test1, irCode);
       Value array = null;
       InstructionIterator iterator = irCode.instructionIterator();
@@ -196,7 +196,7 @@
             .method(new MethodSignature("test4", "int[]", ImmutableList.of()))
             .getMethod();
     try {
-      IRCode irCode = test4.buildIR(TEST_OPTIONS, Origin.unknown());
+      IRCode irCode = test4.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, test4, irCode);
       Value array = null;
       InstructionIterator iterator = irCode.instructionIterator();
@@ -231,7 +231,7 @@
             .method(new MethodSignature("loop2", "void", ImmutableList.of()))
             .getMethod();
     try {
-      IRCode irCode = loop2.buildIR(TEST_OPTIONS, Origin.unknown());
+      IRCode irCode = loop2.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, loop2, irCode);
       analysis.forEach((v, l) -> {
         if (l.isClassTypeLatticeElement()) {
@@ -254,7 +254,7 @@
             .method(new MethodSignature("test2_throw", "int", ImmutableList.of()))
             .getMethod();
     try {
-      IRCode irCode = test2.buildIR(TEST_OPTIONS, Origin.unknown());
+      IRCode irCode = test2.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, test2, irCode);
       analysis.forEach((v, l) -> {
         if (l.isClassTypeLatticeElement()) {
@@ -284,7 +284,7 @@
         CheckCast.class, new ClassTypeLatticeElement(test, true),
         NewInstance.class, new ClassTypeLatticeElement(test, false));
     try {
-      IRCode irCode = method.buildIR(TEST_OPTIONS, Origin.unknown());
+      IRCode irCode = method.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
       analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
     } catch (ApiLevelException e) {
@@ -306,7 +306,7 @@
       InstanceOf.class, PRIMITIVE,
       StaticGet.class, new ClassTypeLatticeElement(test, true));
     try {
-      IRCode irCode = method.buildIR(TEST_OPTIONS, Origin.unknown());
+      IRCode irCode = method.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
       TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
       analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
     } catch (ApiLevelException e) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
index 1f7c990..be7ee92 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
@@ -50,7 +50,7 @@
     List<String> pgConfigs = ImmutableList.of(
         "-keep class " + CLASS_NAME + " { *; }",
         "-dontshrink");
-    AndroidApp app = compileWithR8(builder, pgConfigs, null);
+    AndroidApp app = compileWithR8(builder, pgConfigs, o -> o.enableClassInlining = false);
 
     DexEncodedMethod method = getMethod(app, CLASS_NAME, main);
     assertNotNull(method);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
index e8f2a2a..47f0db3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
@@ -110,7 +110,7 @@
             .addProguardConfigurationFiles(proguardConfig)
             .setDisableMinification(true)
             .build(),
-        null);
+        o -> o.enableClassInlining = false);
     return dexOutputDir.resolve("classes.dex");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index fac2ca0..7c90976 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -48,7 +48,7 @@
     AppInfo appInfo = new AppInfo(dexApplication);
     DexInspector dexInspector = new DexInspector(appInfo.app);
     DexEncodedMethod foo = dexInspector.clazz(testClass.getName()).method(signature).getMethod();
-    IRCode irCode = foo.buildIR(TEST_OPTIONS, Origin.unknown());
+    IRCode irCode = foo.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
     checkCountOfNonNull(irCode, 0);
 
     NonNullTracker nonNullTracker = new NonNullTracker();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
new file mode 100644
index 0000000..e9319d2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -0,0 +1,161 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.code.NewInstance;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.EmptyClass;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.EmptyClassWithInitializer;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.Iface1;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.Iface1Impl;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2Impl;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.ReferencedFields;
+import com.android.tools.r8.ir.optimize.classinliner.trivial.TrivialTestClass;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.Sets;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class ClassInlinerTest extends TestBase {
+  @Test
+  public void testTrivial() throws Exception {
+    byte[][] classes = {
+        ToolHelper.getClassAsBytes(TrivialTestClass.class),
+        ToolHelper.getClassAsBytes(TrivialTestClass.Inner.class),
+        ToolHelper.getClassAsBytes(ReferencedFields.class),
+        ToolHelper.getClassAsBytes(EmptyClass.class),
+        ToolHelper.getClassAsBytes(EmptyClassWithInitializer.class),
+        ToolHelper.getClassAsBytes(Iface1.class),
+        ToolHelper.getClassAsBytes(Iface1Impl.class),
+        ToolHelper.getClassAsBytes(Iface2.class),
+        ToolHelper.getClassAsBytes(Iface2Impl.class),
+        ToolHelper.getClassAsBytes(CycleReferenceAB.class),
+        ToolHelper.getClassAsBytes(CycleReferenceBA.class),
+        ToolHelper.getClassAsBytes(ClassWithFinal.class)
+    };
+    String main = TrivialTestClass.class.getCanonicalName();
+    ProcessResult javaOutput = runOnJava(main, classes);
+    assertEquals(0, javaOutput.exitCode);
+
+    AndroidApp app = runR8(buildAndroidApp(classes), TrivialTestClass.class);
+
+    DexInspector inspector = new DexInspector(app);
+    ClassSubject clazz = inspector.clazz(TrivialTestClass.class);
+
+    assertEquals(
+        Collections.singleton("java.lang.StringBuilder"),
+        collectNewInstanceTypes(clazz, "testInner"));
+
+    assertEquals(
+        Collections.emptySet(),
+        collectNewInstanceTypes(clazz, "testConstructorMapping1"));
+
+    assertEquals(
+        Collections.singleton(
+            "com.android.tools.r8.ir.optimize.classinliner.trivial.ReferencedFields"),
+        collectNewInstanceTypes(clazz, "testConstructorMapping2"));
+
+    assertEquals(
+        Collections.singleton("java.lang.StringBuilder"),
+        collectNewInstanceTypes(clazz, "testConstructorMapping3"));
+
+    assertEquals(
+        Collections.emptySet(),
+        collectNewInstanceTypes(clazz, "testEmptyClass"));
+
+    assertEquals(
+        Collections.singleton(
+            "com.android.tools.r8.ir.optimize.classinliner.trivial.EmptyClassWithInitializer"),
+        collectNewInstanceTypes(clazz, "testEmptyClassWithInitializer"));
+
+    assertEquals(
+        Collections.singleton(
+            "com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal"),
+        collectNewInstanceTypes(clazz, "testClassWithFinalizer"));
+
+    assertEquals(
+        Collections.emptySet(),
+        collectNewInstanceTypes(clazz, "testCallOnIface1"));
+
+    assertEquals(
+        Collections.singleton(
+            "com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2Impl"),
+        collectNewInstanceTypes(clazz, "testCallOnIface2"));
+
+    assertEquals(
+        Sets.newHashSet(
+            "com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB",
+            "java.lang.StringBuilder"),
+        collectNewInstanceTypes(clazz, "testCycles"));
+
+    assertEquals(
+        Sets.newHashSet("java.lang.StringBuilder",
+            "com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB"),
+        collectNewInstanceTypes(inspector.clazz(CycleReferenceAB.class), "foo", "int"));
+
+    assertFalse(inspector.clazz(CycleReferenceBA.class).isPresent());
+  }
+
+  private Set<String> collectNewInstanceTypes(
+      ClassSubject clazz, String methodName, String... params) {
+    assertNotNull(clazz);
+    MethodSignature signature = new MethodSignature(methodName, "void", params);
+    DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
+    return filterInstructionKind(code, NewInstance.class)
+        .map(insn -> ((NewInstance) insn).getType().toSourceString())
+        .collect(Collectors.toSet());
+  }
+
+  private AndroidApp runR8(AndroidApp app, Class mainClass) throws Exception {
+    String config = keepMainProguardConfiguration(mainClass) + "\n"
+        + "-dontobfuscate\n"
+        + "-allowaccessmodification";
+
+    AndroidApp compiled = compileWithR8(app, config, o -> o.enableClassInlining = true);
+
+    // Materialize file for execution.
+    Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
+    compiled.writeToZip(generatedDexFile, OutputMode.DexIndexed);
+
+    // Run with ART.
+    String artOutput = ToolHelper.runArtNoVerificationErrors(
+        generatedDexFile.toString(), mainClass.getCanonicalName());
+
+    // Compare with Java.
+    ToolHelper.ProcessResult javaResult = ToolHelper.runJava(
+        ToolHelper.getClassPathForTests(), mainClass.getCanonicalName());
+
+    if (javaResult.exitCode != 0) {
+      System.out.println(javaResult.stdout);
+      System.err.println(javaResult.stderr);
+      fail("JVM failed for: " + mainClass);
+    }
+    assertEquals("JVM and ART output differ", javaResult.stdout, artOutput);
+
+    return compiled;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ClassWithFinal.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ClassWithFinal.java
new file mode 100644
index 0000000..7e0239c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ClassWithFinal.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.trivial;
+
+public class ClassWithFinal {
+  public String doNothing() {
+    return "nothing at all";
+  }
+
+  @Override
+  protected void finalize() throws Throwable {
+    doNothing();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java
new file mode 100644
index 0000000..d26641a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.trivial;
+
+public class CycleReferenceAB {
+  private String a;
+
+  public CycleReferenceAB(String a) {
+    this.a = a;
+  }
+
+  public void foo(int depth) {
+    CycleReferenceBA ba = new CycleReferenceBA("depth=" + depth);
+    System.out.println("CycleReferenceAB::foo(" + depth + ")");
+    if (depth > 0) {
+      ba.foo(depth - 1);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "CycleReferenceAB(" + a + ")";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java
new file mode 100644
index 0000000..1c5d147
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.trivial;
+
+public class CycleReferenceBA {
+  private String a;
+
+  public CycleReferenceBA(String a) {
+    this.a = a;
+  }
+
+  public void foo(int depth) {
+    CycleReferenceAB ab = new CycleReferenceAB("depth=" + depth);
+    System.out.println("CycleReferenceBA::foo(" + depth + ")");
+    if (depth > 0) {
+      ab.foo(depth - 1);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "CycleReferenceBA(" + a + ")";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClass.java
new file mode 100644
index 0000000..cedfd77
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClass.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.trivial;
+
+public class EmptyClass {
+  public String doNothing() {
+    return "nothing at all";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClassWithInitializer.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClassWithInitializer.java
new file mode 100644
index 0000000..9671ea5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/EmptyClassWithInitializer.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.trivial;
+
+public class EmptyClassWithInitializer {
+  public String doNothing() {
+    return "nothing at all";
+  }
+
+  static {
+    System.out.println("EmptyClassWithInitializer.<clinit>()");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1.java
new file mode 100644
index 0000000..8320ec9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.trivial;
+
+public interface Iface1 {
+  void foo();
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1Impl.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1Impl.java
new file mode 100644
index 0000000..e42a04b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface1Impl.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.trivial;
+
+public class Iface1Impl implements Iface1 {
+  final String value;
+
+  public Iface1Impl(String value) {
+    this.value = value;
+  }
+
+  @Override
+  public void foo() {
+    System.out.println(value);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2.java
new file mode 100644
index 0000000..93a3d5d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.trivial;
+
+public interface Iface2 extends Iface1 {
+  Integer CONSTANT = 123;
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2Impl.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2Impl.java
new file mode 100644
index 0000000..17dd4c1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/Iface2Impl.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.trivial;
+
+public class Iface2Impl implements Iface2 {
+  final String value;
+
+  public Iface2Impl(String value) {
+    this.value = value;
+  }
+
+  @Override
+  public void foo() {
+    System.out.println(value);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ReferencedFields.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ReferencedFields.java
new file mode 100644
index 0000000..90263d6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/ReferencedFields.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.trivial;
+
+public class ReferencedFields {
+  private String a;
+  private String b;
+
+  public ReferencedFields(String a, String b) {
+    this.a = a;
+    this.b = b;
+  }
+
+  public ReferencedFields(String a) {
+    this.a = a;
+  }
+
+  public String getConcat() {
+    return a + " " + b;
+  }
+
+  public String getA() {
+    return a;
+  }
+
+  public String getB() {
+    return b;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
new file mode 100644
index 0000000..7e3cba2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.trivial;
+
+public class TrivialTestClass {
+  private static int ID = 0;
+
+  private static String next() {
+    return Integer.toString(ID++);
+  }
+
+  public static void main(String[] args) {
+    TrivialTestClass test = new TrivialTestClass();
+    test.testInner();
+    test.testConstructorMapping1();
+    test.testConstructorMapping2();
+    test.testConstructorMapping3();
+    test.testEmptyClass();
+    test.testEmptyClassWithInitializer();
+    test.testClassWithFinalizer();
+    test.testCallOnIface1();
+    test.testCallOnIface2();
+    test.testCycles();
+  }
+
+  private synchronized void testInner() {
+    Inner inner = new Inner("inner{", 123, next() + "}");
+    System.out.println(inner.toString() + " " + inner.getPrefix() + " = " + inner.prefix);
+  }
+
+  private synchronized void testConstructorMapping1() {
+    ReferencedFields o = new ReferencedFields(next());
+    System.out.println(o.getA());
+  }
+
+  private synchronized void testConstructorMapping2() {
+    ReferencedFields o = new ReferencedFields(next());
+    System.out.println(o.getB());
+  }
+
+  private synchronized void testConstructorMapping3() {
+    ReferencedFields o = new ReferencedFields(next(), next());
+    System.out.println(o.getA() + o.getB() + o.getConcat());
+  }
+
+  private synchronized void testEmptyClass() {
+    new EmptyClass();
+  }
+
+  private synchronized void testEmptyClassWithInitializer() {
+    new EmptyClassWithInitializer();
+  }
+
+  private synchronized void testClassWithFinalizer() {
+    new ClassWithFinal();
+  }
+
+  private void callOnIface1(Iface1 iface) {
+    iface.foo();
+  }
+
+  private synchronized void testCallOnIface1() {
+    callOnIface1(new Iface1Impl(next()));
+  }
+
+  private void callOnIface2(Iface2 iface) {
+    iface.foo();
+  }
+
+  private synchronized void testCallOnIface2() {
+    callOnIface2(new Iface2Impl(next()));
+    System.out.println(Iface2Impl.CONSTANT); // Keep constant referenced
+  }
+
+  private synchronized void testCycles() {
+    new CycleReferenceAB("first").foo(3);
+    new CycleReferenceBA("second").foo(4);
+  }
+
+  public class Inner {
+    private String prefix;
+    private String suffix;
+    private double id;
+
+    public Inner(String prefix, double id, String suffix) {
+      this.prefix = prefix;
+      this.suffix = suffix;
+      this.id = id;
+    }
+
+    @Override
+    public String toString() {
+      return prefix + id + suffix;
+    }
+
+    public String getPrefix() {
+      return prefix;
+    }
+
+    public String getSuffix() {
+      return suffix;
+    }
+
+    public double getId() {
+      return id;
+    }
+
+    public void setPrefix(String prefix) {
+      this.prefix = prefix;
+    }
+
+    public void setSuffix(String suffix) {
+      this.suffix = suffix;
+    }
+
+    public void setId(double id) {
+      this.id = id;
+    }
+  }
+}
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 b50718d..eb6e943 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -28,6 +28,7 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -86,6 +87,7 @@
     private final List<String> fields = new ArrayList<>();
     private boolean makeInit = false;
     private boolean hasInit = false;
+    private final List<String> clinit = new ArrayList<>();
     private boolean isInterface = false;
     private String access = "public";
 
@@ -217,6 +219,10 @@
       return new MethodSignature(name, returnJavaType, argumentJavaTypes);
     }
 
+    public void addClassInitializer(String... lines) {
+      clinit.addAll(Arrays.asList(lines));
+    }
+
     public FieldSignature addField(String flags, String name, String type, String value) {
       fields.add(
           ".field " + flags + " " + name + " " + type + (value != null ? (" = " + value) : ""));
@@ -262,6 +268,11 @@
       for (String method : methods) {
         builder.append(method).append("\n");
       }
+      if (!clinit.isEmpty()) {
+        builder.append(".method public static <clinit>()V\n");
+        clinit.forEach(line -> builder.append(line).append('\n'));
+        builder.append(".end method\n");
+      }
       return builder.toString();
     }
 
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
index 5e41a25..ec18fac 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
@@ -8,11 +8,15 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.android.tools.r8.utils.AndroidApp;
 import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import org.junit.Test;
 
 public class JumpSubroutineTests extends JasminTestBase {
@@ -22,10 +26,28 @@
     assertEquals(expected, javaResult);
     String artResult = runOnArtD8(builder, main);
     assertEquals(expected, artResult);
+    String cfFrontendResult = runOnJavaR8CfFrontend(builder, main);
+    assertEquals(expected, cfFrontendResult);
     String dxArtResult = runOnArtDx(builder, main);
     assertEquals(expected, dxArtResult);
   }
 
+  private String runOnJavaR8CfFrontend(JasminBuilder builder, String main) throws Exception {
+    Path inputJar = temp.getRoot().toPath().resolve("input.jar");
+    Path outputJar = temp.getRoot().toPath().resolve("output.jar");
+    builder.writeJar(inputJar, null);
+    ToolHelper.runR8(
+        R8Command.builder()
+            .addProgramFiles(inputJar)
+            .setOutput(outputJar, OutputMode.ClassFile)
+            .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+            .build(),
+        options -> options.enableCfFrontend = true);
+    ProcessResult processResult = ToolHelper.runJava(outputJar, main);
+    assertEquals(0, processResult.exitCode);
+    return processResult.stdout;
+  }
+
   private void expectDxFailure(JasminBuilder builder) throws Exception {
     // This expects this dx failure:
     // Uncaught translation error: com.android.dex.util.ExceptionWithContext: returning from
@@ -387,6 +409,8 @@
     assertEquals(expected, javaResult);
     String artResult = runOnArtD8(builder, clazz.name);
     assertEquals(expected, artResult);
+    String cfFrontendResult = runOnJavaR8CfFrontend(builder, clazz.name);
+    assertEquals(expected, cfFrontendResult);
     // This fails with dx.
     expectDxFailure(builder);
   }
@@ -1344,6 +1368,8 @@
     String artResult = runOnArtD8(builder, clazz.name);
     // The ASM jsr inliner does not get the control-flow dependent ret right in his case.
     assertNotEquals(expected, artResult);
+    String cfFrontendResult = runOnJavaR8CfFrontend(builder, clazz.name);
+    assertNotEquals(expected, cfFrontendResult);
     // This fails with dx.
     expectDxFailure(builder);
   }
@@ -1392,6 +1418,8 @@
     String artResult = runOnArtD8(builder, clazz.name);
     // The ASM jsr inliner does not get the control-flow dependent ret right in his case.
     assertNotEquals(expected, artResult);
+    String cfFrontendResult = runOnJavaR8CfFrontend(builder, clazz.name);
+    assertNotEquals(expected, cfFrontendResult);
     // This fails with dx.
     expectDxFailure(builder);
   }
@@ -1441,6 +1469,8 @@
     String artResult = runOnArtD8(builder, clazz.name);
     // The ASM jsr inliner does not get the control-flow dependent ret right in his case.
     assertNotEquals(expected, artResult);
+    String cfFrontendResult = runOnJavaR8CfFrontend(builder, clazz.name);
+    assertNotEquals(expected, cfFrontendResult);
     // This fails with dx.
     expectDxFailure(builder);
   }
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress80124071.java b/src/test/java/com/android/tools/r8/jasmin/Regress80124071.java
new file mode 100644
index 0000000..0eda515
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress80124071.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.jasmin;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class Regress80124071 extends JasminTestBase {
+
+  private JasminBuilder buildClass() {
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    clazz.addPrivateVirtualMethod("privateMethod", ImmutableList.of(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "ldc \"privateMethod\"",
+        "invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
+        "return");
+
+    clazz.addDefaultConstructor();
+
+    clazz.addMainMethod(
+        ".limit stack 3",
+        ".limit locals 1",
+        "new Test",
+        "dup",
+        "invokespecial Test/<init>()V",
+        // Should have been invokespecial but JVM is OK with it so we need to transform
+        // to invoke-direct to be able to run on Art.
+        "invokevirtual Test/privateMethod()V",
+        "new TestSub",
+        "dup",
+        "dup",
+        "invokespecial TestSub/<init>()V",
+        // Should have been invokespecial but JVM is OK with it and invokes the private method
+        // on the Test class. Therefore, there is no virtual dispatch and we need to transform
+        // to invoke-direct to be able to run on Art.
+        "invokevirtual Test/privateMethod()V",
+        "invokevirtual TestSub/privateMethod()V",
+        "return");
+
+    JasminBuilder.ClassBuilder subclazz = builder.addClass("TestSub", "Test");
+
+    subclazz.addVirtualMethod("privateMethod", ImmutableList.of(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "ldc \"privateMethod2\"",
+        "invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
+        "return");
+
+    return builder;
+  }
+
+  @Test
+  public void test() throws Exception {
+    JasminBuilder builder = buildClass();
+    String jvm = runOnArtDx(builder, "Test");
+    String dx = runOnJava(builder, "Test");
+    assertEquals(jvm, dx);
+    String d8 = runOnArtD8(builder, "Test");
+    assertEquals(jvm, d8);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index 93f4249..bb83093 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.utils.DexInspector.FieldSubject;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -34,6 +35,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 import org.junit.Assume;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -183,13 +185,23 @@
         + "}";
   }
 
-  protected void runTest(String folder, String mainClass, AndroidAppInspector inspector)
-      throws Exception {
-    runTest(folder, mainClass, null, inspector);
+  protected void runTest(String folder, String mainClass,
+      AndroidAppInspector inspector) throws Exception {
+    runTest(folder, mainClass, null, null, inspector);
+  }
+
+  protected void runTest(String folder, String mainClass,
+      Consumer<InternalOptions> optionsConsumer, AndroidAppInspector inspector) throws Exception {
+    runTest(folder, mainClass, null, optionsConsumer, inspector);
+  }
+
+  protected void runTest(String folder, String mainClass,
+      String extraProguardRules, AndroidAppInspector inspector) throws Exception {
+    runTest(folder, mainClass, extraProguardRules, null, inspector);
   }
 
   protected void runTest(String folder, String mainClass, String extraProguardRules,
-      AndroidAppInspector inspector) throws Exception {
+      Consumer<InternalOptions> optionsConsumer, AndroidAppInspector inspector) throws Exception {
     Assume.assumeTrue(ToolHelper.artSupported());
 
     String proguardRules = buildProguardRules(mainClass);
@@ -206,7 +218,7 @@
     // Build with R8
     AndroidApp.Builder builder = AndroidApp.builder();
     builder.addProgramFiles(classpath);
-    AndroidApp app = compileWithR8(builder.build(), proguardRules);
+    AndroidApp app = compileWithR8(builder.build(), proguardRules, optionsConsumer);
 
     // Materialize file for execution.
     Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
index 315e980..ee9065f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -252,7 +252,7 @@
   @Test
   public void testTrivialKs() throws Exception {
     final String mainClassName = "lambdas_kstyle_trivial.MainKt";
-    runTest("lambdas_kstyle_trivial", mainClassName, null, (app) -> {
+    runTest("lambdas_kstyle_trivial", mainClassName, (app) -> {
       Verifier verifier = new Verifier(app);
       String pkg = "lambdas_kstyle_trivial";
 
@@ -293,7 +293,7 @@
   @Test
   public void testCapturesKs() throws Exception {
     final String mainClassName = "lambdas_kstyle_captures.MainKt";
-    runTest("lambdas_kstyle_captures", mainClassName, null, (app) -> {
+    runTest("lambdas_kstyle_captures", mainClassName, (app) -> {
       Verifier verifier = new Verifier(app);
       String pkg = "lambdas_kstyle_captures";
       String grpPkg = allowAccessModification ? "" : pkg;
@@ -318,7 +318,7 @@
   @Test
   public void testGenericsNoSignatureKs() throws Exception {
     final String mainClassName = "lambdas_kstyle_generics.MainKt";
-    runTest("lambdas_kstyle_generics", mainClassName, null, (app) -> {
+    runTest("lambdas_kstyle_generics", mainClassName, (app) -> {
       Verifier verifier = new Verifier(app);
       String pkg = "lambdas_kstyle_generics";
       String grpPkg = allowAccessModification ? "" : pkg;
@@ -387,7 +387,7 @@
   @Test
   public void testTrivialJs() throws Exception {
     final String mainClassName = "lambdas_jstyle_trivial.MainKt";
-    runTest("lambdas_jstyle_trivial", mainClassName, null, (app) -> {
+    runTest("lambdas_jstyle_trivial", mainClassName, (app) -> {
       Verifier verifier = new Verifier(app);
       String pkg = "lambdas_jstyle_trivial";
       String grp = allowAccessModification ? "" : pkg;
@@ -435,7 +435,7 @@
   @Test
   public void testSingleton() throws Exception {
     final String mainClassName = "lambdas_singleton.MainKt";
-    runTest("lambdas_singleton", mainClassName, null, (app) -> {
+    runTest("lambdas_singleton", mainClassName, (app) -> {
       Verifier verifier = new Verifier(app);
       String pkg = "lambdas_singleton";
       String grp = allowAccessModification ? "" : pkg;
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index 73d70e1..f884376 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -12,6 +12,8 @@
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.function.Consumer;
 import org.junit.Test;
 
 public class R8KotlinPropertiesTest extends AbstractR8KotlinTestBase {
@@ -79,11 +81,13 @@
           .addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
           .addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
 
+  private Consumer<InternalOptions> disableClassInliner = o -> o.enableClassInlining = false;
+
   @Test
   public void testMutableProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_noUseOfProperties");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -100,7 +104,7 @@
   public void testMutableProperty_privateIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_usePrivateProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -122,7 +126,7 @@
   public void testMutableProperty_protectedIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_useProtectedProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -145,7 +149,7 @@
   public void testMutableProperty_internalIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_useInternalProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -168,7 +172,7 @@
   public void testMutableProperty_publicIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_usePublicProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -191,7 +195,7 @@
   public void testMutableProperty_primitivePropertyIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_usePrimitiveProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -216,7 +220,7 @@
   public void testLateInitProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
     String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
         "lateInitProperty_noUseOfProperties");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -233,7 +237,7 @@
   public void testLateInitProperty_privateIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/LateInitPropertyKt", "lateInitProperty_usePrivateLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -256,7 +260,7 @@
   public void testLateInitProperty_protectedIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
         "lateInitProperty_useProtectedLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -277,7 +281,7 @@
   public void testLateInitProperty_internalIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/LateInitPropertyKt", "lateInitProperty_useInternalLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -296,7 +300,7 @@
   public void testLateInitProperty_publicIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/LateInitPropertyKt", "lateInitProperty_usePublicLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -315,7 +319,7 @@
   public void testUserDefinedProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/UserDefinedPropertyKt", "userDefinedProperty_noUseOfProperties");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           USER_DEFINED_PROPERTY_CLASS.getClassName());
@@ -332,7 +336,7 @@
   public void testUserDefinedProperty_publicIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/UserDefinedPropertyKt", "userDefinedProperty_useProperties");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassExists(dexInspector,
           USER_DEFINED_PROPERTY_CLASS.getClassName());
@@ -358,7 +362,7 @@
   public void testCompanionProperty_primitivePropertyCannotBeInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_usePrimitiveProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassExists(dexInspector,
           "properties.CompanionProperties");
@@ -389,7 +393,7 @@
   public void testCompanionProperty_privatePropertyIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_usePrivateProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassExists(dexInspector,
           "properties.CompanionProperties");
@@ -423,7 +427,7 @@
   public void testCompanionProperty_internalPropertyCannotBeInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_useInternalProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassExists(dexInspector,
           "properties.CompanionProperties");
@@ -454,7 +458,7 @@
   public void testCompanionProperty_publicPropertyCannotBeInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_usePublicProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassExists(dexInspector,
           "properties.CompanionProperties");
@@ -486,7 +490,7 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePrivateLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
       ClassSubject companionClass = checkClassExists(dexInspector, testedClass.getClassName());
@@ -517,7 +521,7 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_useInternalLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
       ClassSubject companionClass = checkClassExists(dexInspector, testedClass.getClassName());
@@ -541,7 +545,7 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePublicLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassExists(dexInspector, testedClass.getOuterClassName());
       ClassSubject companionClass = checkClassExists(dexInspector, testedClass.getClassName());
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 9de9f5a..590baa6 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DebugLocalInfo;
@@ -619,7 +620,7 @@
                 DexAnnotationSet.empty(),
                 ParameterAnnotationsList.empty(),
                 code);
-        IRCode ir = code.buildIR(method, options, Origin.unknown());
+        IRCode ir = code.buildIR(method, null, options, Origin.unknown());
         RegisterAllocator allocator = new LinearScanRegisterAllocator(ir, options);
         method.setCode(ir, allocator, options);
         directMethods[i] = method;
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index bb2628a..43b4c0f 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -15,8 +15,12 @@
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -40,6 +44,23 @@
   @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
   @Test
+  public void traceMainDexList001_whyareyoukeeping() throws Throwable {
+    PrintStream stdout = System.out;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    System.setOut(new PrintStream(baos));
+    doTest(
+        "traceMainDexList001_1",
+        "multidex001",
+        EXAMPLE_BUILD_DIR,
+        Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules-whyareyoukeeping.txt"),
+        Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
+        AndroidApiLevel.I);
+    String output = new String(baos.toByteArray(), Charset.defaultCharset());
+    Assert.assertTrue(output.contains("is live because referenced in keep rule:"));
+    System.setOut(stdout);
+  }
+
+  @Test
   public void traceMainDexList001_1() throws Throwable {
     doTest(
         "traceMainDexList001_1",
@@ -271,10 +292,9 @@
   }
 
   private String mainDexStringToDescriptor(String mainDexString) {
-    final String CLASS_EXTENSION = ".class";
-    Assert.assertTrue(mainDexString.endsWith(CLASS_EXTENSION));
+    Assert.assertTrue(mainDexString.endsWith(FileUtils.CLASS_EXTENSION));
     return DescriptorUtils.getDescriptorFromClassBinaryName(
-        mainDexString.substring(0, mainDexString.length() - CLASS_EXTENSION.length()));
+        mainDexString.substring(0, mainDexString.length() - FileUtils.CLASS_EXTENSION.length()));
   }
 
   private void checkSameMainDexEntry(String reference, String computed) {
diff --git a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
index 9026740..a185f4c 100644
--- a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
@@ -33,7 +33,7 @@
         "-dontobfuscate"),
         Origin.unknown());
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    AndroidApp app = ToolHelper.runR8(builder.build());
+    AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
     DexInspector inspector = new DexInspector(app);
     List<FoundClassSubject> classes = inspector.allClasses();
 
@@ -66,7 +66,7 @@
         "-dontobfuscate"),
         Origin.unknown());
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    AndroidApp app = ToolHelper.runR8(builder.build());
+    AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
     DexInspector inspector = new DexInspector(app);
     List<FoundClassSubject> classes = inspector.allClasses();
 
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
new file mode 100644
index 0000000..574ec6d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class FieldTypeTest extends TestBase {
+
+  @Ignore("b/78788577")
+  @Test
+  public void test_brokenTypeHierarchy() throws Exception {
+    JasminBuilder jasminBuilder = new JasminBuilder();
+    // interface Itf
+    ClassBuilder itf = jasminBuilder.addInterface("Itf");
+    MethodSignature foo = itf.addAbstractMethod("foo", ImmutableList.of(), "V");
+    // class Impl /* implements Itf */
+    ClassBuilder impl = jasminBuilder.addClass("Impl");
+    impl.addDefaultConstructor();
+    impl.addVirtualMethod("foo", ImmutableList.of(), "V",
+        ".limit locals 2",
+        ".limit stack 2",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "ldc \"" + "foo" + "\"",
+        "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "return");
+    impl.addVirtualMethod("toString", ImmutableList.of(), "Ljava/lang/String;",
+        ".limit locals 1",
+        ".limit stack 2",
+        "ldc \"" + impl.name + "\"",
+        "areturn");
+    ClassBuilder client = jasminBuilder.addClass("Client");
+    FieldSignature obj = client.addStaticFinalField("obj", itf.getDescriptor(), null);
+    client.addClassInitializer(
+        ".limit locals 1",
+        ".limit stack 2",
+        "new " + impl.name,
+        "dup",
+        "invokespecial " + impl.name + "/<init>()V",
+        "putstatic " + client.name + "/" + obj.name + " " + itf.getDescriptor(),
+        "return"
+    );
+
+    ClassBuilder mainClass = jasminBuilder.addClass("Main");
+    mainClass.addMainMethod(
+        ".limit locals 2",
+        ".limit stack 2",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "getstatic " + client.name + "/" + obj.name + " " + itf.getDescriptor(),
+        /*
+        "astore_0",
+        "aload_0",
+        // java.lang.IncompatibleClassChangeError:
+        //     Class Impl does not implement the requested interface Itf
+        "invokeinterface " + itf.name + "/" + foo.name + "()V 1",
+        "aload_0",
+        */
+        "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V",
+        "return"
+    );
+
+    final String mainClassName = mainClass.name;
+    String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
+
+    // Run input program on java.
+    Path outputDirectory = temp.newFolder().toPath();
+    jasminBuilder.writeClassFiles(outputDirectory);
+    ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
+    assertEquals(0, javaResult.exitCode);
+    assertThat(javaResult.stdout, containsString(impl.name));
+
+    AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
+        // Disable inlining to avoid the (short) tested method from being inlined then removed.
+        internalOptions -> internalOptions.enableInlining = false);
+
+    // Run processed (output) program on ART
+    ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
+    assertEquals(0, artResult.exitCode);
+    assertThat(artResult.stdout, containsString(impl.name));
+    assertEquals(-1, artResult.stderr.indexOf("DoFieldPut"));
+
+    DexInspector inspector = new DexInspector(processedApp);
+    ClassSubject itfSubject = inspector.clazz(itf.name);
+    assertThat(itfSubject, isPresent());
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index dd7465f..f558ee7 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -139,6 +139,8 @@
   private Reporter reporter;
   private KeepingDiagnosticHandler handler;
   private ProguardConfigurationParser parser;
+  private List<String> whiteSpace = ImmutableList.of("", " ", "   ", "\t", " \t", " \t", " \t ", " \t\t \t ");
+
 
   @Before
   public void reset() {
@@ -789,12 +791,10 @@
 
   @Test
   public void parseKeepModifiers() {
-    List<String> ws = ImmutableList.of("", " ", "   ", "\t", " \t", " \t", " \t ", " \t\t \t ");
-
-    for (String before : ws) {
-      for (String after : ws) {
+    for (String before : whiteSpace) {
+      for (String after : whiteSpace) {
         reset();
-        ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of(
+        parseAndVerifyParserEndsCleanly(ImmutableList.of(
             "-keep"
                 + before + "," + after + "includedescriptorclasses"
                 + before + "," + after + "allowshrinking"
@@ -807,6 +807,16 @@
   }
 
   @Test
+  public void parseKeepAnnotation() {
+    for (String space : whiteSpace) {
+      reset();
+      parseAndVerifyParserEndsCleanly(ImmutableList.of(
+          "-keep @" + space + "interface A"
+      ));
+    }
+  }
+
+  @Test
   public void regress78442725() {
     parseAndVerifyParserEndsCleanly(ImmutableList.of(
         "-keep, includedescriptorclasses class in.uncod.android.bypass.Document { *; }",
@@ -1717,6 +1727,24 @@
     verifyWithProguard(proguardConfig);
   }
 
+  @Test
+  public void parse_regress79925760() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-keep public @ interface test.MyAnnotation"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    verifyParserEndsCleanly();
+
+    ProguardConfiguration config = parser.getConfig();
+    assertEquals(1, config.getRules().size());
+    ProguardKeepRule rule = (ProguardKeepRule) config.getRules().get(0);
+    assertEquals(ProguardClassType.ANNOTATION_INTERFACE, rule.getClassType());
+
+    verifyWithProguard(proguardConfig);
+  }
+
   private ProguardConfiguration parseAndVerifyParserEndsCleanly(List<String> config) {
     parser.parse(createConfigurationForTesting(config));
     verifyParserEndsCleanly();
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
index 3de225d..5845dd3 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
@@ -39,7 +39,7 @@
         "-dontobfuscate"),
         Origin.unknown());
     builder.addProguardConfiguration(additionalKeepRules, Origin.unknown());
-    AndroidApp app = ToolHelper.runR8(builder.build());
+    AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
     inspection.accept(new DexInspector(app));
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 1151648..91fc9d4 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -599,7 +599,7 @@
     AndroidApp app;
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
     try {
-      app = ToolHelper.runR8(builder.build());
+      app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
     } catch (CompilationError e) {
       assertTrue(!forceProguardCompatibility && (!innerClasses || !enclosingMethod));
       return;
@@ -656,7 +656,7 @@
     builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
     Path proguardCompatibilityRules = temp.newFile().toPath();
     builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
-    AndroidApp app = ToolHelper.runR8(builder.build());
+    AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
     inspection.accept(new DexInspector(app));
     // Check the Proguard compatibility configuration generated.
     ProguardConfigurationParser parser =
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index 31abcf1..6b90d88 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -68,7 +68,7 @@
 
     DexEncodedMethod method = getMethod(originalApplication, methodSig);
     // Get the IR pre-optimization.
-    IRCode code = method.buildIR(new InternalOptions(), Origin.unknown());
+    IRCode code = method.buildIR(null, new InternalOptions(), Origin.unknown());
 
     // Find the exit block and assert that the value is a phi merging the exceptional edge
     // with the normal edge.
diff --git a/tools/run-d8-on-gmscore.py b/tools/run-d8-on-gmscore.py
index b1bdc6e..4a64693 100755
--- a/tools/run-d8-on-gmscore.py
+++ b/tools/run-d8-on-gmscore.py
@@ -76,7 +76,7 @@
         build=not options.no_build,
         debug=not options.no_debug,
         profile=options.profile,
-        track_memory_to_file=options.track_memory_to_file)
+        track_memory_file=options.track_memory_to_file)
 
 if __name__ == '__main__':
   sys.exit(main())
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 571d96d..41340c7 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -203,7 +203,7 @@
       toolhelper.run(options.compiler, args, build=not options.no_build,
                      debug=not options.no_debug,
                      profile=options.profile,
-                     track_memory_to_file=options.track_memory_to_file)
+                     track_memory_file=options.track_memory_to_file)
       if options.print_memoryuse:
         print('{}(MemoryUse): {}'
             .format(options.print_memoryuse,