Merge "Preserve input markers from dex inputs to D8."
diff --git a/build.gradle b/build.gradle
index 7761e37..abefa15 100644
--- a/build.gradle
+++ b/build.gradle
@@ -504,6 +504,7 @@
     task.relocate('kotlin', 'com.android.tools.r8.jetbrains.kotlin')
     task.relocate('kotlinx', 'com.android.tools.r8.jetbrains.kotlinx')
     task.relocate('org.jetbrains', 'com.android.tools.r8.org.jetbrains')
+    task.relocate('org.intellij', 'com.android.tools.r8.org.intellij')
 }
 
 task repackageDeps(type: ShadowJar) {
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index f896eee..00ce913 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.3.1-dev";
+  public static final String LABEL = "1.3.2-dev";
 
   private Version() {
   }
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 6cbad63..69a744f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -481,15 +481,15 @@
     return result;
   }
 
-  public boolean canTriggerStaticInitializer(DexType type) {
+  public boolean canTriggerStaticInitializer(DexType type, boolean ignoreTypeItself) {
     Set<DexType> knownInterfaces = Sets.newIdentityHashSet();
 
     // Process superclass chain.
     DexType clazz = type;
     while (clazz != null && clazz != dexItemFactory.objectType) {
       DexClass definition = definitionFor(clazz);
-      if (canTriggerStaticInitializer(definition)) {
-        return true; // Assume it *may* trigger if we didn't find the definition.
+      if (canTriggerStaticInitializer(definition) && (!ignoreTypeItself || clazz != type)) {
+        return true;
       }
       knownInterfaces.addAll(Arrays.asList(definition.interfaces.values));
       clazz = definition.superType;
@@ -501,7 +501,7 @@
       DexType iface = queue.remove();
       DexClass definition = definitionFor(iface);
       if (canTriggerStaticInitializer(definition)) {
-        return true; // Assume it *may* trigger if we didn't find the definition.
+        return true;
       }
       if (!definition.isInterface()) {
         throw new Unreachable(iface.toSourceString() + " is expected to be an interface");
@@ -517,6 +517,7 @@
   }
 
   private static boolean canTriggerStaticInitializer(DexClass clazz) {
+    // Assume it *may* trigger if we didn't find the definition.
     return clazz == null || clazz.hasClassInitializer();
   }
 
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 d234702..4281e80 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -590,6 +590,24 @@
     }
   }
 
+  public static class TrivialInitializer {
+    private TrivialInitializer() {
+    }
+
+    public static final class InstanceClassTrivialInitializer extends TrivialInitializer {
+      public static final InstanceClassTrivialInitializer INSTANCE =
+          new InstanceClassTrivialInitializer();
+    }
+
+    public static final class ClassTrivialInitializer extends TrivialInitializer {
+      public final DexField field;
+
+      public ClassTrivialInitializer(DexField field) {
+        this.field = field;
+      }
+    }
+  }
+
   public static class OptimizationInfo {
 
     private int returnedArgument = -1;
@@ -604,6 +622,7 @@
     // Stores information about instance methods and constructors for
     // class inliner, null value indicates that the method is not eligible.
     private ClassInlinerEligibility classInlinerEligibility = null;
+    private TrivialInitializer trivialInitializerInfo = null;
 
     private OptimizationInfo() {
       // Intentionally left empty.
@@ -648,6 +667,14 @@
       return this.classInlinerEligibility;
     }
 
+    private void setTrivialInitializer(TrivialInitializer info) {
+      this.trivialInitializerInfo = info;
+    }
+
+    public TrivialInitializer getTrivialInitializerInfo() {
+      return this.trivialInitializerInfo;
+    }
+
     public long getReturnedConstant() {
       assert returnsConstant();
       return returnedConstant;
@@ -750,6 +777,10 @@
     ensureMutableOI().setClassInlinerEligibility(eligibility);
   }
 
+  synchronized public void setTrivialInitializer(TrivialInitializer info) {
+    ensureMutableOI().setTrivialInitializer(info);
+  }
+
   synchronized public void markForceInline() {
     ensureMutableOI().markForceInline();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 2c12a90..7d17f11 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -20,11 +20,35 @@
  * <li>Renaming private methods/fields.</li>
  * <li>Moving methods/fields to a super/subclass.</li>
  * <li>Replacing method/field references by the same method/field on a super/subtype</li>
+ * <li>Moved methods might require changed invocation type at the call site</li>
  * </ul>
  * Note that the latter two have to take visibility into account.
  */
 public abstract class GraphLense {
 
+  /**
+   * Result of a method lookup in a GraphLense.
+   *
+   * This provide the new target and the invoke type to use.
+   */
+  public static class GraphLenseLookupResult {
+    private final DexMethod method;
+    private final Type type;
+
+    public GraphLenseLookupResult(DexMethod method, Type type) {
+      this.method = method;
+      this.type = type;
+    }
+
+    public DexMethod getMethod() {
+      return method;
+    }
+
+    public Type getType() {
+      return type;
+    }
+  }
+
   public static class Builder {
 
     protected Builder() {
@@ -68,10 +92,11 @@
   // This overload can be used when the graph lense is known to be context insensitive.
   public DexMethod lookupMethod(DexMethod method) {
     assert isContextFreeForMethod(method);
-    return lookupMethod(method, null, null);
+    return lookupMethod(method, null, null).getMethod();
   }
 
-  public abstract DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type);
+  public abstract GraphLenseLookupResult lookupMethod(
+      DexMethod method, DexEncodedMethod context, Type type);
 
   // Context sensitive graph lenses should override this method.
   public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
@@ -126,8 +151,9 @@
     }
 
     @Override
-    public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
-      return method;
+    public GraphLenseLookupResult lookupMethod(
+        DexMethod method, DexEncodedMethod context, Type type) {
+      return new GraphLenseLookupResult(method, type);
     }
 
     @Override
@@ -141,19 +167,29 @@
     }
   }
 
+  /**
+   * GraphLense implementation with a parent lense using a simple mapping for type, method and
+   * field mapping.
+   *
+   * Subclasses can override the lookup methods.
+   *
+   * For method mapping where invocation type can change just override
+   * {@link #mapInvocationType(DexMethod, DexMethod, DexEncodedMethod, Type)} if
+   * the default name mapping applies, and only invocation type might need to change.
+   */
   public static class NestedGraphLense extends GraphLense {
 
-    private final GraphLense previousLense;
+    protected final GraphLense previousLense;
     protected final DexItemFactory dexItemFactory;
 
-    private final Map<DexType, DexType> typeMap;
+    protected final Map<DexType, DexType> typeMap;
     private final Map<DexType, DexType> arrayTypeCache = new IdentityHashMap<>();
-    private final Map<DexMethod, DexMethod> methodMap;
-    private final Map<DexField, DexField> fieldMap;
+    protected final Map<DexMethod, DexMethod> methodMap;
+    protected final Map<DexField, DexField> fieldMap;
 
     public NestedGraphLense(Map<DexType, DexType> typeMap, Map<DexMethod, DexMethod> methodMap,
         Map<DexField, DexField> fieldMap, GraphLense previousLense, DexItemFactory dexItemFactory) {
-      this.typeMap = typeMap;
+      this.typeMap = typeMap.isEmpty() ? null : typeMap;
       this.methodMap = methodMap;
       this.fieldMap = fieldMap;
       this.previousLense = previousLense;
@@ -180,13 +216,58 @@
         }
       }
       DexType previous = previousLense.lookupType(type);
-      return typeMap.getOrDefault(previous, previous);
+      return typeMap != null ? typeMap.getOrDefault(previous, previous) : previous;
     }
 
     @Override
-    public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
-      DexMethod previous = previousLense.lookupMethod(method, context, type);
-      return methodMap.getOrDefault(previous, previous);
+    public GraphLenseLookupResult lookupMethod(
+        DexMethod method, DexEncodedMethod context, Type type) {
+      GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
+      DexMethod newMethod = methodMap.get(previous.getMethod());
+      if (newMethod == null) {
+        return previous;
+      }
+      // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
+      // that only subclasses which are known to need it actually do it?
+      return new GraphLenseLookupResult(
+          newMethod, mapInvocationType(newMethod, method, context, type));
+    }
+
+    /**
+     * Default invocation type mapping.
+     *
+     * This is an identity mapping. If a subclass need invocation type mapping either override
+     * this method or {@link #lookupMethod(DexMethod, DexEncodedMethod, Type)}
+     */
+    protected Type mapInvocationType(
+        DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+      return type;
+    }
+
+    /**
+     * Standard mapping between interface and virtual invoke type.
+     *
+     * Handle methods moved from interface to class or class to interface.
+     */
+    final protected Type mapVirtualInterfaceInvocationTypes(
+        AppInfo appInfo, DexMethod newMethod, DexMethod originalMethod,
+        DexEncodedMethod context, Type type) {
+      if (type == Type.VIRTUAL || type == Type.INTERFACE) {
+        // Get the invoke type of the actual definition.
+        DexClass newTargetClass = appInfo.definitionFor(newMethod.holder);
+        if (newTargetClass == null) {
+          return type;
+        }
+        DexClass originalTargetClass = appInfo.definitionFor(originalMethod.holder);
+        if (originalTargetClass != null
+            && (originalTargetClass.isInterface() ^ (type == Type.INTERFACE))) {
+          // The invoke was wrong to start with, so we keep it wrong. This is to ensure we get
+          // the IncompatibleClassChangeError the original invoke would have triggered.
+          return newTargetClass.accessFlags.isInterface() ? Type.VIRTUAL : Type.INTERFACE;
+        }
+        return newTargetClass.accessFlags.isInterface() ? Type.INTERFACE : Type.VIRTUAL;
+      }
+      return type;
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 21b8dc4..85506ad 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -363,7 +363,7 @@
 
     private void processInvoke(Type type, DexMethod method) {
       DexEncodedMethod source = caller.method;
-      method = graphLense.lookupMethod(method, source, type);
+      method = graphLense.lookupMethod(method, source, type).getMethod();
       DexEncodedMethod definition = appInfo.lookup(type, method, source.method.holder);
       if (definition != null) {
         assert !source.accessFlags.isBridge() || definition != caller.method;
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 7d7e5f3..1134686 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
@@ -141,7 +141,7 @@
       this.memberValuePropagation =
           options.enableValuePropagation ?
               new MemberValuePropagation(appInfo.withLiveness()) : null;
-      this.lensCodeRewriter = new LensCodeRewriter(graphLense, appInfo.withSubtyping(), options);
+      this.lensCodeRewriter = new LensCodeRewriter(graphLense, appInfo.withSubtyping());
       if (appInfo.hasLiveness()) {
         // When disabling the pruner here, also disable the ProtoLiteExtension in R8.java.
         this.protoLiteRewriter =
@@ -725,17 +725,20 @@
       interfaceMethodRewriter.rewriteMethodReferences(method, code);
       assert code.isConsistentSSA();
     }
-    if (lambdaMerger != null) {
-      lambdaMerger.processMethodCode(method, code);
+
+    if (classInliner != null) {
+      // Class inliner should work before lambda merger, so if it inlines the
+      // lambda, it is not get collected by merger.
+      assert options.enableInlining && inliner != null;
+      classInliner.processMethodCode(
+          appInfo.withLiveness(), method, code, isProcessedConcurrently,
+          methodsToInline -> inliner.performForcedInlining(method, code, methodsToInline)
+      );
       assert code.isConsistentSSA();
     }
 
-    if (classInliner != null) {
-      assert options.enableInlining && inliner != null;
-      classInliner.processMethodCode(
-          appInfo.withSubtyping(), method, code, isProcessedConcurrently,
-          methodsToInline -> inliner.performForcedInlining(method, code, methodsToInline)
-      );
+    if (lambdaMerger != null) {
+      lambdaMerger.processMethodCode(method, code);
       assert code.isConsistentSSA();
     }
 
@@ -760,6 +763,9 @@
 
     // Analysis must be done after method is rewritten by logArgumentTypes()
     codeRewriter.identifyClassInlinerEligibility(method, code, feedback);
+    if (method.isInstanceInitializer() || method.isClassInitializer()) {
+      codeRewriter.identifyTrivialInitializer(method, code, feedback);
+    }
 
     printMethod(code, "Optimized IR (SSA)");
     finalizeIR(method, code, feedback);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 4c0be09..e653ff0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstClass;
@@ -35,7 +36,6 @@
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.InternalOptions;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.stream.Collectors;
@@ -44,13 +44,10 @@
 
   private final GraphLense graphLense;
   private final AppInfoWithSubtyping appInfo;
-  private final InternalOptions options;
 
-  public LensCodeRewriter(
-      GraphLense graphLense, AppInfoWithSubtyping appInfo, InternalOptions options) {
+  public LensCodeRewriter(GraphLense graphLense, AppInfoWithSubtyping appInfo) {
     this.graphLense = graphLense;
     this.appInfo = appInfo;
-    this.options = options;
   }
 
   private Value makeOutValue(Instruction insn, IRCode code) {
@@ -110,8 +107,10 @@
           if (!invokedHolder.isClassType()) {
             continue;
           }
-          DexMethod actualTarget = graphLense.lookupMethod(invokedMethod, method, invoke.getType());
-          Invoke.Type invokeType = getInvokeType(invoke, actualTarget, invokedMethod, method);
+          GraphLenseLookupResult lenseLookup =
+              graphLense.lookupMethod(invokedMethod, method, invoke.getType());
+          DexMethod actualTarget = lenseLookup.getMethod();
+          Invoke.Type invokeType = lenseLookup.getType();
           if (actualTarget != invokedMethod || invoke.getType() != invokeType) {
             Invoke newInvoke = Invoke.create(invokeType, actualTarget, null,
                     invoke.outValue(), invoke.inValues());
@@ -230,16 +229,17 @@
       DexEncodedMethod method, DexMethodHandle methodHandle) {
     if (methodHandle.isMethodHandle()) {
       DexMethod invokedMethod = methodHandle.asMethod();
-      DexMethod actualTarget =
+      GraphLenseLookupResult lenseLookup =
           graphLense.lookupMethod(invokedMethod, method, methodHandle.type.toInvokeType());
+      DexMethod actualTarget = lenseLookup.getMethod();
       if (actualTarget != invokedMethod) {
-        DexClass clazz = appInfo.definitionFor(actualTarget.holder);
         MethodHandleType newType = methodHandle.type;
-        if (clazz != null
-            && (newType.isInvokeInterface() || newType.isInvokeInstance())) {
-          newType = clazz.accessFlags.isInterface()
-              ? MethodHandleType.INVOKE_INTERFACE
-              : MethodHandleType.INVOKE_INSTANCE;
+        DexClass clazz = appInfo.definitionFor(actualTarget.holder);
+        if (clazz != null && (newType.isInvokeInterface() || newType.isInvokeInstance())) {
+          newType =
+              lenseLookup.getType() == Type.INTERFACE
+                  ? MethodHandleType.INVOKE_INTERFACE
+                  : MethodHandleType.INVOKE_INSTANCE;
         }
         return new DexMethodHandle(newType, actualTarget);
       }
@@ -252,55 +252,4 @@
     }
     return methodHandle;
   }
-
-  private Type getInvokeType(
-      InvokeMethod invoke,
-      DexMethod actualTarget,
-      DexMethod originalTarget,
-      DexEncodedMethod invocationContext) {
-    // We might move methods from interfaces to classes and vice versa. So we have to support
-    // fixing the invoke kind, yet only if it was correct to start with.
-    if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
-      // Get the invoke type of the actual definition.
-      DexClass newTargetClass = appInfo.definitionFor(actualTarget.holder);
-      if (newTargetClass == null) {
-        return invoke.getType();
-      }
-      DexClass originalTargetClass = appInfo.definitionFor(originalTarget.holder);
-      if (originalTargetClass != null
-          && (originalTargetClass.isInterface() ^ (invoke.getType() == Type.INTERFACE))) {
-        // The invoke was wrong to start with, so we keep it wrong. This is to ensure we get
-        // the IncompatibleClassChangeError the original invoke would have triggered.
-        return newTargetClass.accessFlags.isInterface() ? Type.VIRTUAL : Type.INTERFACE;
-      }
-      return newTargetClass.accessFlags.isInterface() ? Type.INTERFACE : Type.VIRTUAL;
-    }
-    if (options.enableClassMerging && invoke.isInvokeSuper()) {
-      if (actualTarget.getHolder() == invocationContext.method.getHolder()) {
-        DexClass targetClass = appInfo.definitionFor(actualTarget.holder);
-        if (targetClass == null) {
-          return invoke.getType();
-        }
-
-        // If the super class A of the enclosing class B (i.e., invocationContext.method.holder)
-        // has been merged into B during vertical class merging, and this invoke-super instruction
-        // was resolving to a method in A, then the target method has been changed to a direct
-        // method and moved into B, so that we need to use an invoke-direct instruction instead of
-        // invoke-super.
-        //
-        // At this point, we have an invoke-super instruction where the static target is the
-        // enclosing class. However, such an instruction could occur even if a subclass has never
-        // been merged into the enclosing class. Therefore, to determine if vertical class merging
-        // has been applied, we look if there is a direct method with the right signature, and only
-        // return Type.DIRECT in that case.
-        DexEncodedMethod method = targetClass.lookupDirectMethod(actualTarget);
-        if (method != null) {
-          // The target method has been moved from the super class into the sub class during class
-          // merging such that we now need to use an invoke-direct instruction.
-          return Type.DIRECT;
-        }
-      }
-    }
-    return invoke.getType();
-  }
 }
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 16a77c8..2f17a4c 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
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 
 public interface OptimizationFeedback {
@@ -17,4 +18,5 @@
   void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
   void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
   void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibility eligibility);
+  void setTrivialInitializer(DexEncodedMethod method, TrivialInitializer info);
 }
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 7303b6b..1026e9b 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
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 
 public class OptimizationFeedbackDirect implements OptimizationFeedback {
@@ -50,4 +51,9 @@
       DexEncodedMethod method, ClassInlinerEligibility eligibility) {
     method.setClassInlinerEligibility(eligibility);
   }
+
+  @Override
+  public void setTrivialInitializer(DexEncodedMethod method, TrivialInitializer info) {
+    method.setTrivialInitializer(info);
+  }
 }
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 6915a2f..8fac02f 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
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 
 public class OptimizationFeedbackIgnore implements OptimizationFeedback {
@@ -35,4 +36,8 @@
   public void setClassInlinerEligibility(
       DexEncodedMethod method, ClassInlinerEligibility eligibility) {
   }
+
+  @Override
+  public void setTrivialInitializer(DexEncodedMethod method, TrivialInitializer info) {
+  }
 }
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 e610d20..c3f02da 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
@@ -13,6 +13,9 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.ClassTrivialInitializer;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.InstanceClassTrivialInitializer;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -48,6 +51,7 @@
 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.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;
@@ -812,6 +816,187 @@
         method, new ClassInlinerEligibility(receiverUsedAsReturnValue));
   }
 
+  public void identifyTrivialInitializer(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+    boolean isInstanceInitializer = method.isInstanceInitializer();
+    boolean isClassInitializer = method.isClassInitializer();
+    assert isInstanceInitializer || isClassInitializer;
+    if (method.accessFlags.isNative()) {
+      return;
+    }
+
+    DexClass clazz = appInfo.definitionFor(method.method.holder);
+    if (clazz == null) {
+      return;
+    }
+
+    feedback.setTrivialInitializer(method,
+        isInstanceInitializer
+            ? computeInstanceInitializerInfo(code, clazz, appInfo::definitionFor)
+            : computeClassInitializerInfo(code, clazz));
+  }
+
+  // This method defines trivial instance initializer as follows:
+  //
+  // ** The initializer may only call the initializer of the base class, which
+  //    itself must be trivial.
+  //
+  // ** java.lang.Object.<init>() is considered trivial.
+  //
+  // ** all arguments passed to a super-class initializer must be non-throwing
+  //    constants or arguments.
+  //
+  // ** Assigns arguments or non-throwing constants to fields of this class.
+  //
+  // (Note that this initializer does not have to have zero arguments.)
+  private TrivialInitializer computeInstanceInitializerInfo(
+      IRCode code, DexClass clazz, Function<DexType, DexClass> typeToClass) {
+    Value receiver = code.getThis();
+
+    InstructionIterator it = code.instructionIterator();
+    while (it.hasNext()) {
+      Instruction insn = it.next();
+
+      if (insn.isReturn()) {
+        continue;
+      }
+
+      if (insn.isArgument()) {
+        continue;
+      }
+
+      if (insn.isConstInstruction()) {
+        if (insn.instructionInstanceCanThrow()) {
+          return null;
+        } else {
+          continue;
+        }
+      }
+
+      if (insn.isInvokeDirect()) {
+        InvokeDirect invokedDirect = insn.asInvokeDirect();
+        DexMethod invokedMethod = invokedDirect.getInvokedMethod();
+
+        if (invokedMethod.holder != clazz.superType) {
+          return null;
+        }
+
+        // java.lang.Object.<init>() is considered trivial.
+        if (invokedMethod == dexItemFactory.objectMethods.constructor) {
+          continue;
+        }
+
+        DexClass holder = typeToClass.apply(invokedMethod.holder);
+        if (holder == null) {
+          return null;
+        }
+
+        DexEncodedMethod callTarget = holder.lookupDirectMethod(invokedMethod);
+        if (callTarget == null ||
+            !callTarget.isInstanceInitializer() ||
+            callTarget.getOptimizationInfo().getTrivialInitializerInfo() == null ||
+            invokedDirect.getReceiver() != receiver) {
+          return null;
+        }
+
+        for (Value value : invokedDirect.inValues()) {
+          if (value != receiver && !(value.isConstant() || value.isArgument())) {
+            return null;
+          }
+        }
+        continue;
+      }
+
+      if (insn.isInstancePut()) {
+        InstancePut instancePut = insn.asInstancePut();
+        DexEncodedField field = clazz.lookupInstanceField(instancePut.getField());
+        if (field == null ||
+            instancePut.object() != receiver ||
+            (instancePut.value() != receiver && !instancePut.value().isArgument())) {
+          return null;
+        }
+        continue;
+      }
+
+      // Other instructions make the instance initializer not eligible.
+      return null;
+    }
+
+    return InstanceClassTrivialInitializer.INSTANCE;
+  }
+
+  // This method defines trivial class initializer as follows:
+  //
+  // ** The initializer may only instantiate an instance of the same class,
+  //    initialize it with a call to a trivial constructor *without* arguments,
+  //    and assign this instance to a static final field of the same class.
+  //
+  private synchronized TrivialInitializer computeClassInitializerInfo(IRCode code, DexClass clazz) {
+    InstructionIterator it = code.instructionIterator();
+
+    Value createdSingletonInstance = null;
+    DexField singletonField = null;
+
+    while (it.hasNext()) {
+      Instruction insn = it.next();
+
+      if (insn.isReturn()) {
+        continue;
+      }
+
+      if (insn.isNewInstance()) {
+        NewInstance newInstance = insn.asNewInstance();
+        if (createdSingletonInstance != null ||
+            newInstance.clazz != clazz.type ||
+            insn.outValue() == null) {
+          return null;
+        }
+        createdSingletonInstance = insn.outValue();
+        continue;
+      }
+
+      if (insn.isInvokeDirect()) {
+        InvokeDirect invokedDirect = insn.asInvokeDirect();
+        if (createdSingletonInstance == null ||
+            invokedDirect.getReceiver() != createdSingletonInstance) {
+          return null;
+        }
+
+        DexEncodedMethod callTarget = clazz.lookupDirectMethod(invokedDirect.getInvokedMethod());
+        if (callTarget == null ||
+            !callTarget.isInstanceInitializer() ||
+            !callTarget.method.proto.parameters.isEmpty() ||
+            callTarget.getOptimizationInfo().getTrivialInitializerInfo() == null) {
+          return null;
+        }
+        continue;
+      }
+
+      if (insn.isStaticPut()) {
+        StaticPut staticPut = insn.asStaticPut();
+        if (singletonField != null ||
+            createdSingletonInstance == null ||
+            staticPut.inValue() != createdSingletonInstance) {
+          return null;
+        }
+
+        DexEncodedField field = clazz.lookupStaticField(staticPut.getField());
+        if (field == null ||
+            !field.accessFlags.isStatic() ||
+            !field.accessFlags.isFinal()) {
+          return null;
+        }
+        singletonField = field.field;
+        continue;
+      }
+
+      // Other instructions make the class initializer not eligible.
+      return null;
+    }
+
+    return singletonField == null ? null : new ClassTrivialInitializer(singletonField);
+  }
+
   /**
    * 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/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 029b169..25cd969 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
@@ -271,7 +271,7 @@
       Origin origin = appInfo.originFor(target.method.holder);
       IRCode code = target.buildInliningIR(appInfo, options, generator, callerPosition, origin);
       if (!target.isProcessed()) {
-        new LensCodeRewriter(graphLense, appInfo, options).rewrite(code, target);
+        new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
       }
       return code;
     }
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
index d7a57df..e6b61f8 100644
--- 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
@@ -10,6 +10,8 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.ClassTrivialInitializer;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -22,12 +24,13 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 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.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.kotlin.KotlinInfo;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.google.common.collect.Streams;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
@@ -55,11 +58,12 @@
 
   // 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 may add new 'new-instance' instructions.
-  // Processing them as well is possible, but does not seem to bring much value.
+  // - collect all 'new-instance' and 'static-get' instructions (called roots below) in
+  // the original code. Note that class inlining, if happens, mutates code and may add
+  // new root instructions. Processing them as well is possible, but does not seem to
+  // bring much value.
   //
-  // - for each 'new-instance' we check if it is eligible for inlining, i.e:
+  // - for each 'new-instance' root 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 comments in
   //        CodeRewriter::identifyClassInlinerEligibility(...))
@@ -71,14 +75,20 @@
   //            NOTE: if method receiver is used as a return value, the method call
   //            should ignore return value
   //
-  // - inline eligible 'new-instance' instructions, i.e:
+  // - for each 'static-get' root we check if it is eligible for inlining, i.e:
+  //     -> the class of the new instance is 'eligible' (see computeClassEligible(...))
+  //        *and* has a trivial class constructor (see CoreRewriter::computeClassInitializerInfo
+  //        and description in isClassAndUsageEligible(...)) initializing the field root reads
+  //     -> has only 'eligible' uses, (see above)
+  //
+  // - inline eligible root instructions, i.e:
   //     -> force inline methods called on the instance (including the initializer);
   //        (this may introduce additional instance field reads/writes on the receiver)
   //     -> replace instance field reads with appropriate values calculated based on
   //        fields writes
-  //     -> remove the call to superclass initializer
+  //     -> remove the call to superclass initializer (if root is 'new-instance')
   //     -> remove all field writes
-  //     -> remove 'new-instance' instructions
+  //     -> remove root instructions
   //
   // For example:
   //
@@ -93,12 +103,21 @@
   //         return x;
   //       }
   //     }
+  //     static class F {
+  //       final static F I = new F();
+  //       int getX() {
+  //         return 123;
+  //       }
+  //     }
   //     static int method1() {
   //       return new L(1).x;
   //     }
   //     static int method2() {
   //       return new L(1).getX();
   //     }
+  //     static int method3() {
+  //       return F.I.getX();
+  //     }
   //   }
   //
   // Code after class C is 'inlined':
@@ -109,47 +128,59 @@
   //     static int method2() {
   //       return 1;
   //     }
+  //     static int method3() {
+  //       return "F::getX";
+  //     }
   //   }
   //
   public final void processMethodCode(
-      AppInfoWithSubtyping appInfo,
+      AppInfoWithLiveness appInfo,
       DexEncodedMethod method,
       IRCode code,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
       InlinerAction inliner) {
 
-    // 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 all the new-instance and static-get instructions in the code before inlining.
+    List<Instruction> roots = Streams.stream(code.instructionIterator())
+        .filter(insn -> insn.isNewInstance() || insn.isStaticGet())
         .collect(Collectors.toList());
 
-    for (NewInstance newInstance : newInstances) {
-      Value eligibleInstance = newInstance.outValue();
+    for (Instruction root : roots) {
+      Value eligibleInstance = root.outValue();
       if (eligibleInstance == null) {
         continue;
       }
 
-      DexType eligibleClass = newInstance.clazz;
-      if (!isClassEligible(appInfo, eligibleClass)) {
+      DexType eligibleClass = root.isNewInstance()
+          ? root.asNewInstance().clazz : root.asStaticGet().getField().type;
+      DexClass eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
+      if (eligibleClassDefinition == null) {
         continue;
       }
 
-      Map<InvokeMethodWithReceiver, InliningInfo> methodCalls = checkInstanceUsersEligibility(
-          appInfo, method, isProcessedConcurrently, newInstance, eligibleInstance, eligibleClass);
+      // Check the static initializer of the type.
+      if (!isClassAndUsageEligible(root, eligibleClass,
+          eligibleClassDefinition, isProcessedConcurrently, appInfo)) {
+        continue;
+      }
+
+      Map<InvokeMethodWithReceiver, InliningInfo> methodCalls =
+          checkInstanceUsersEligibility(appInfo, method, isProcessedConcurrently,
+              root, eligibleInstance, eligibleClass, eligibleClassDefinition);
       if (methodCalls == null) {
         continue;
       }
 
-      if (getTotalEstimatedMethodSize(methodCalls) >= totalMethodInstructionLimit) {
+      if (!instructionLimitExempt(eligibleClassDefinition, appInfo) &&
+          getTotalEstimatedMethodSize(methodCalls) >= totalMethodInstructionLimit) {
         continue;
       }
 
       // Inline the class instance.
       forceInlineAllMethodInvocations(inliner, methodCalls);
-      removeSuperClassInitializerAndFieldReads(code, newInstance, eligibleInstance);
+      removeSuperClassInitializerAndFieldReads(code, root, eligibleInstance);
       removeFieldWrites(eligibleInstance, eligibleClass);
-      removeInstruction(newInstance);
+      removeInstruction(root);
 
       // Restore normality.
       code.removeAllTrivialPhis();
@@ -157,10 +188,96 @@
     }
   }
 
+  private boolean isClassAndUsageEligible(Instruction root,
+      DexType eligibleClass, DexClass eligibleClassDefinition,
+      Predicate<DexEncodedMethod> isProcessedConcurrently, AppInfoWithLiveness appInfo) {
+    if (!isClassEligible(appInfo, eligibleClass)) {
+      return false;
+    }
+
+    if (root.isNewInstance()) {
+      // There must be no static initializer on the class itself.
+      return !eligibleClassDefinition.hasClassInitializer();
+    }
+
+    assert root.isStaticGet();
+
+    // Checking if we can safely inline class implemented following singleton-like
+    // pattern, by which we assume a static final field holding on to the reference
+    // initialized in class constructor.
+    //
+    // In general we are targeting cases when the class is defined as:
+    //
+    //   class X {
+    //     static final X F;
+    //     static {
+    //       F = new X();
+    //     }
+    //   }
+    //
+    // and being used as follows:
+    //
+    //   void foo() {
+    //     f = X.F;
+    //     f.bar();
+    //   }
+    //
+    // The main difference from the similar case of class inliner with 'new-instance'
+    // instruction is that in this case the instance we inline is not just leaked, but
+    // is actually published via X.F field. There are several risks we need to address
+    // in this case:
+    //
+    //    Risk: instance stored in field X.F has changed after it was initialized in
+    //      class initializer
+    //    Solution: we assume that final field X.F is not modified outside the class
+    //      initializer. In rare cases when it is (e.g. via reflections) it should
+    //      be marked with keep rules
+    //
+    //    Risk: instance stored in field X.F is not initialized yet
+    //    Solution: not initialized instance can only be visible if X.<clinit>
+    //      triggers other class initialization which references X.F. This
+    //      situation should never happen if we:
+    //        -- don't allow any superclasses to have static initializer,
+    //        -- don't allow any subclasses,
+    //        -- guarantee the class has trivial class initializer
+    //           (see CodeRewriter::computeClassInitializerInfo), and
+    //        -- guarantee the instance is initialized with trivial instance
+    //           initializer (see CodeRewriter::computeInstanceInitializerInfo)
+    //
+    //    Risk: instance stored in field X.F was mutated
+    //    Solution: we require that class X does not have any instance fields, and
+    //      if any of its superclasses has instance fields, accessing them will make
+    //      this instance not eligible for inlining. I.e. even though the instance is
+    //      publicized and its state has been mutated, it will not effect the logic
+    //      of class inlining
+    //
+
+    if (eligibleClassDefinition.instanceFields().length > 0 ||
+        !eligibleClassDefinition.accessFlags.isFinal()) {
+      return false;
+    }
+
+    // Singleton instance must be initialized in class constructor.
+    DexEncodedMethod classInitializer = eligibleClassDefinition.getClassInitializer();
+    if (classInitializer == null || isProcessedConcurrently.test(classInitializer)) {
+      return false;
+    }
+
+    TrivialInitializer info =
+        classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
+    assert info == null || info instanceof ClassTrivialInitializer;
+    DexField instanceField = root.asStaticGet().getField();
+    // Singleton instance field must NOT be pinned.
+    return info != null &&
+        ((ClassTrivialInitializer) info).field == instanceField &&
+        !appInfo.isPinned(eligibleClassDefinition.lookupStaticField(instanceField).field);
+  }
+
   private Map<InvokeMethodWithReceiver, InliningInfo> checkInstanceUsersEligibility(
       AppInfoWithSubtyping appInfo, DexEncodedMethod method,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
-      NewInstance newInstanceInsn, Value receiver, DexType clazz) {
+      Instruction root, Value receiver,
+      DexType eligibleClass, DexClass eligibleClassDefinition) {
 
     // No Phi users.
     if (receiver.numberOfPhiUsers() > 0) {
@@ -169,14 +286,13 @@
 
     Map<InvokeMethodWithReceiver, InliningInfo> methodCalls = new IdentityHashMap<>();
 
-    DexClass definition = appInfo.definitionFor(clazz);
-
     for (Instruction user : receiver.uniqueUsers()) {
       // Field read/write.
       if (user.isInstanceGet() ||
           (user.isInstancePut() && user.asInstancePut().value() != receiver)) {
         DexField field = user.asFieldInstruction().getField();
-        if (field.clazz == newInstanceInsn.clazz && definition.lookupInstanceField(field) != null) {
+        if (field.clazz == eligibleClass &&
+            eligibleClassDefinition.lookupInstanceField(field) != null) {
           // Since class inliner currently only supports classes directly extending
           // java.lang.Object, we don't need to worry about fields defined in superclasses.
           continue;
@@ -184,10 +300,10 @@
         return null; // Not eligible.
       }
 
-      // Eligible constructor call.
-      if (user.isInvokeDirect()) {
+      // Eligible constructor call (for new instance roots only).
+      if (user.isInvokeDirect() && root.isNewInstance()) {
         InliningInfo inliningInfo = isEligibleConstructorCall(appInfo, method,
-            user.asInvokeDirect(), receiver, clazz, isProcessedConcurrently);
+            user.asInvokeDirect(), receiver, eligibleClass, isProcessedConcurrently);
         if (inliningInfo != null) {
           methodCalls.put(user.asInvokeDirect(), inliningInfo);
           continue;
@@ -199,7 +315,7 @@
       if (user.isInvokeVirtual() || user.isInvokeInterface()) {
         InliningInfo inliningInfo = isEligibleMethodCall(
             appInfo, method, user.asInvokeMethodWithReceiver(),
-            receiver, clazz, isProcessedConcurrently);
+            receiver, eligibleClass, isProcessedConcurrently);
         if (inliningInfo != null) {
           methodCalls.put(user.asInvokeMethodWithReceiver(), inliningInfo);
           continue;
@@ -215,11 +331,12 @@
   // Remove call to superclass initializer, replace field reads with appropriate
   // values, insert phis when needed.
   private void removeSuperClassInitializerAndFieldReads(
-      IRCode code, NewInstance newInstance, Value eligibleInstance) {
+      IRCode code, Instruction root, Value eligibleInstance) {
     Map<DexField, FieldValueHelper> fieldHelpers = new IdentityHashMap<>();
     for (Instruction user : eligibleInstance.uniqueUsers()) {
       // Remove the call to superclass constructor.
-      if (user.isInvokeDirect() &&
+      if (root.isNewInstance() &&
+          user.isInvokeDirect() &&
           user.asInvokeDirect().getInvokedMethod() == factory.objectMethods.constructor) {
         removeInstruction(user);
         continue;
@@ -227,7 +344,7 @@
 
       if (user.isInstanceGet()) {
         // Replace a field read with appropriate value.
-        replaceFieldRead(code, newInstance, user.asInstanceGet(), fieldHelpers);
+        replaceFieldRead(code, root, user.asInstanceGet(), fieldHelpers);
         continue;
       }
 
@@ -261,13 +378,24 @@
     return totalSize;
   }
 
-  private void replaceFieldRead(IRCode code, NewInstance newInstance,
+  private boolean instructionLimitExempt(
+      DexClass eligibleClassDefinition, AppInfoWithLiveness appInfo) {
+    if (appInfo.isPinned(eligibleClassDefinition.type)) {
+      return false;
+    }
+    KotlinInfo kotlinInfo = eligibleClassDefinition.getKotlinInfo();
+    return kotlinInfo != null &&
+        kotlinInfo.isSyntheticClass() &&
+        kotlinInfo.asSyntheticClass().isLambda();
+  }
+
+  private void replaceFieldRead(IRCode code, Instruction root,
       InstanceGet fieldRead, Map<DexField, FieldValueHelper> fieldHelpers) {
 
     Value value = fieldRead.outValue();
     if (value != null) {
       FieldValueHelper helper = fieldHelpers.computeIfAbsent(
-          fieldRead.getField(), field -> new FieldValueHelper(field, code, newInstance));
+          fieldRead.getField(), field -> new FieldValueHelper(field, code, root));
       Value newValue = helper.getValueForFieldRead(fieldRead.getBlock(), fieldRead);
       value.replaceUsers(newValue);
       for (FieldValueHelper fieldValueHelper : fieldHelpers.values()) {
@@ -282,16 +410,16 @@
   private static final class FieldValueHelper {
     final DexField field;
     final IRCode code;
-    final NewInstance newInstance;
+    final Instruction root;
 
     private Value defaultValue = null;
     private final Map<BasicBlock, Value> ins = new IdentityHashMap<>();
     private final Map<BasicBlock, Value> outs = new IdentityHashMap<>();
 
-    private FieldValueHelper(DexField field, IRCode code, NewInstance newInstance) {
+    private FieldValueHelper(DexField field, IRCode code, Instruction root) {
       this.field = field;
       this.code = code;
-      this.newInstance = newInstance;
+      this.root = root;
     }
 
     void replaceValue(Value oldValue, Value newValue) {
@@ -370,10 +498,10 @@
         Instruction instruction = iterator.previous();
         assert instruction != null;
 
-        if (instruction == newInstance ||
+        if (instruction == root ||
             (instruction.isInstancePut() &&
                 instruction.asInstancePut().getField() == field &&
-                instruction.asInstancePut().object() == newInstance.outValue())) {
+                instruction.asInstancePut().object() == root.outValue())) {
           valueProducingInsn = instruction;
           break;
         }
@@ -386,14 +514,14 @@
         return valueProducingInsn.asInstancePut().value();
       }
 
-      assert newInstance == valueProducingInsn;
+      assert root == valueProducingInsn;
       if (defaultValue == null) {
         // If we met newInstance it means that default value is supposed to be used.
         defaultValue = code.createValue(ValueType.fromDexType(field.type));
         ConstNumber defaultValueInsn = new ConstNumber(defaultValue, 0);
-        defaultValueInsn.setPosition(newInstance.getPosition());
+        defaultValueInsn.setPosition(root.getPosition());
         LinkedList<Instruction> instructions = block.getInstructions();
-        instructions.add(instructions.indexOf(newInstance) + 1, defaultValueInsn);
+        instructions.add(instructions.indexOf(root) + 1, defaultValueInsn);
         defaultValueInsn.setBlock(block);
       }
       return defaultValue;
@@ -539,7 +667,7 @@
   //   - is not an abstract class or interface
   //   - directly extends java.lang.Object
   //   - does not declare finalizer
-  //   - does not trigger any static initializers
+  //   - does not trigger any static initializers except for its own
   private boolean computeClassEligible(AppInfo appInfo, DexType clazz) {
     DexClass definition = appInfo.definitionFor(clazz);
     if (definition == null || definition.isLibraryClass() ||
@@ -561,6 +689,6 @@
     }
 
     // Check for static initializers in this class or any of interfaces it implements.
-    return !appInfo.canTriggerStaticInitializer(clazz);
+    return !appInfo.canTriggerStaticInitializer(clazz, true);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
index 56089f4..492bcd0 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
@@ -46,14 +46,14 @@
         assert !method.accessFlags.isPrivate() && !method.accessFlags.isConstructor();
         if (kind == InvokeKind.STATIC) {
           assert method.accessFlags.isStatic();
-          DexMethod actualTarget = lense.lookupMethod(target, method, Type.STATIC);
+          DexMethod actualTarget = lense.lookupMethod(target, method, Type.STATIC).getMethod();
           DexEncodedMethod targetMethod = appInfo.lookupStaticTarget(actualTarget);
           if (targetMethod != null) {
             addForwarding(method, targetMethod);
           }
         } else if (kind == InvokeKind.VIRTUAL) {
           // TODO(herhut): Add support for bridges with multiple targets.
-          DexMethod actualTarget = lense.lookupMethod(target, method, Type.VIRTUAL);
+          DexMethod actualTarget = lense.lookupMethod(target, method, Type.VIRTUAL).getMethod();
           DexEncodedMethod targetMethod = appInfo.lookupSingleVirtualTarget(actualTarget);
           if (targetMethod != null) {
             addForwarding(method, targetMethod);
@@ -93,15 +93,16 @@
     }
 
     @Override
-    public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
-      DexMethod previous = previousLense.lookupMethod(method, context, type);
-      DexMethod bridge = bridgeTargetToBridgeMap.get(previous);
+    public GraphLenseLookupResult lookupMethod(
+        DexMethod method, DexEncodedMethod context, Type type) {
+      GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
+      DexMethod bridge = bridgeTargetToBridgeMap.get(previous.getMethod());
       // Do not forward calls from a bridge method to itself while the bridge method is still
       // a bridge.
       if (bridge == null || (context.accessFlags.isBridge() && bridge == context.method)) {
         return previous;
       }
-      return bridge;
+      return new GraphLenseLookupResult(bridge, type);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index fc408e3..da7a787 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -26,12 +26,13 @@
 
   private final AppInfoWithLiveness appInfo;
   private final GraphLense lense;
-  private final GraphLense.Builder builder = GraphLense.builder();
+  private final MemberRebindingLense.Builder builder;
 
   public MemberRebindingAnalysis(AppInfoWithLiveness appInfo, GraphLense lense) {
     assert lense.isContextFreeForMethods();
     this.appInfo = appInfo;
     this.lense = lense;
+    this.builder = MemberRebindingLense.builder(appInfo);
   }
 
   private DexMethod validTargetFor(DexMethod target, DexMethod original) {
@@ -250,6 +251,6 @@
         mergeFieldAccessContexts(appInfo.instanceFieldReads, appInfo.instanceFieldWrites),
         appInfo::resolveFieldOn, DexClass::lookupField);
 
-    return builder.build(appInfo.dexItemFactory, lense);
+    return builder.build(lense);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
new file mode 100644
index 0000000..4ee0cee
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
@@ -0,0 +1,56 @@
+// 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.optimize;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
+public class MemberRebindingLense extends NestedGraphLense {
+  public static class Builder extends NestedGraphLense.Builder {
+    private final AppInfo appInfo;
+
+    protected Builder(AppInfo appInfo) {
+      this.appInfo = appInfo;
+    }
+
+    public GraphLense build(GraphLense previousLense) {
+      assert typeMap.isEmpty();
+      if (methodMap.isEmpty() && fieldMap.isEmpty()) {
+        return previousLense;
+      }
+      return new MemberRebindingLense(appInfo, methodMap, fieldMap, previousLense);
+    }
+  }
+
+  private final AppInfo appInfo;
+
+  public MemberRebindingLense(
+      AppInfo appInfo,
+      Map<DexMethod, DexMethod> methodMap,
+      Map<DexField, DexField> fieldMap,
+      GraphLense previousLense) {
+    super(ImmutableMap.of(), methodMap, fieldMap, previousLense, appInfo.dexItemFactory);
+    this.appInfo = appInfo;
+  }
+
+  public static Builder builder(AppInfo appInfo) {
+    return new Builder(appInfo);
+  }
+
+
+  @Override
+  protected Type mapInvocationType(
+      DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+    return super.mapVirtualInterfaceInvocationTypes(
+        appInfo, newMethod, originalMethod, context, type);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index b11e33d..f0519a1 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -321,7 +321,7 @@
 
     // The resulting graph lense that should be used after class merging.
     VerticalClassMergerGraphLense.Builder renamedMembersLense =
-        new VerticalClassMergerGraphLense.Builder();
+        VerticalClassMergerGraphLense.builder(appInfo);
 
     Iterator<DexProgramClass> classIterator = classes.iterator();
 
@@ -490,7 +490,7 @@
     private final DexClass source;
     private final DexClass target;
     private final VerticalClassMergerGraphLense.Builder deferredRenamings =
-        new VerticalClassMergerGraphLense.Builder();
+        VerticalClassMergerGraphLense.builder(appInfo);
     private boolean abortMerge = false;
 
     private ClassMerger(DexClass source, DexClass target) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 942a857..99fb63a 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -4,11 +4,13 @@
 
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -40,47 +42,57 @@
 // invocation will hit the same implementation as the original super.m() call.
 //
 // For the invocation "invoke-virtual A.m()" in B.m2, this graph lense will return the method B.m.
-public class VerticalClassMergerGraphLense extends GraphLense {
-  private final GraphLense previousLense;
+public class VerticalClassMergerGraphLense extends NestedGraphLense {
+  private final AppInfo appInfo;
 
-  private final Map<DexField, DexField> fieldMap;
-  private final Map<DexMethod, DexMethod> methodMap;
   private final Set<DexMethod> mergedMethods;
   private final Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps;
 
   public VerticalClassMergerGraphLense(
+      AppInfo appInfo,
       Map<DexField, DexField> fieldMap,
       Map<DexMethod, DexMethod> methodMap,
       Set<DexMethod> mergedMethods,
       Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps,
       GraphLense previousLense) {
-    this.previousLense = previousLense;
-    this.fieldMap = fieldMap;
-    this.methodMap = methodMap;
+    super(ImmutableMap.of(), methodMap, fieldMap, previousLense, appInfo.dexItemFactory);
+    this.appInfo = appInfo;
     this.mergedMethods = mergedMethods;
     this.contextualVirtualToDirectMethodMaps = contextualVirtualToDirectMethodMaps;
   }
 
-  @Override
-  public DexType lookupType(DexType type) {
-    return previousLense.lookupType(type);
+  public static Builder builder(AppInfo appInfo) {
+    return new Builder(appInfo);
   }
 
   @Override
-  public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
+  public GraphLenseLookupResult lookupMethod(
+      DexMethod method, DexEncodedMethod context, Type type) {
     assert isContextFreeForMethod(method) || (context != null && type != null);
-    DexMethod previous = previousLense.lookupMethod(method, context, type);
-    if (type == Type.SUPER && !mergedMethods.contains(context.method)) {
+    GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
+    if (previous.getType() == Type.SUPER && !mergedMethods.contains(context.method)) {
       Map<DexMethod, DexMethod> virtualToDirectMethodMap =
           contextualVirtualToDirectMethodMaps.get(context.method.holder);
       if (virtualToDirectMethodMap != null) {
-        DexMethod directMethod = virtualToDirectMethodMap.get(previous);
+        DexMethod directMethod = virtualToDirectMethodMap.get(previous.getMethod());
         if (directMethod != null) {
-          return directMethod;
+          // If the super class A of the enclosing class B (i.e., context.method.holder)
+          // has been merged into B during vertical class merging, and this invoke-super instruction
+          // was resolving to a method in A, then the target method has been changed to a direct
+          // method and moved into B, so that we need to use an invoke-direct instruction instead of
+          // invoke-super.
+          return new GraphLenseLookupResult(directMethod, Type.DIRECT);
         }
       }
     }
-    return methodMap.getOrDefault(previous, previous);
+    return super.lookupMethod(previous.getMethod(), context, previous.getType());
+  }
+
+  @Override
+  protected Type mapInvocationType(
+      DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+    return super.mapVirtualInterfaceInvocationTypes(
+        appInfo, newMethod, originalMethod, context, type);
   }
 
   @Override
@@ -100,12 +112,6 @@
   }
 
   @Override
-  public DexField lookupField(DexField field) {
-    DexField previous = previousLense.lookupField(field);
-    return fieldMap.getOrDefault(previous, previous);
-  }
-
-  @Override
   public boolean isContextFreeForMethods() {
     return contextualVirtualToDirectMethodMaps.isEmpty() && previousLense.isContextFreeForMethods();
   }
@@ -126,6 +132,7 @@
   }
 
   public static class Builder {
+    private final AppInfo appInfo;
 
     private final ImmutableMap.Builder<DexField, DexField> fieldMapBuilder = ImmutableMap.builder();
     private final ImmutableMap.Builder<DexMethod, DexMethod> methodMapBuilder =
@@ -134,7 +141,9 @@
     private final Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps =
         new HashMap<>();
 
-    public Builder() {}
+    private Builder(AppInfo appInfo) {
+      this.appInfo = appInfo;
+    }
 
     public GraphLense build(GraphLense previousLense) {
       Map<DexField, DexField> fieldMap = fieldMapBuilder.build();
@@ -145,6 +154,7 @@
         return previousLense;
       }
       return new VerticalClassMergerGraphLense(
+          appInfo,
           fieldMap,
           methodMap,
           mergedMethodsBuilder.build(),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 38768cd..51a5262 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -16,13 +16,17 @@
 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.Instruction;
 import com.android.tools.r8.code.NewInstance;
+import com.android.tools.r8.code.Sget;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.ir.optimize.classinliner.builders.BuildersTestClass;
 import com.android.tools.r8.ir.optimize.classinliner.builders.ControlFlow;
 import com.android.tools.r8.ir.optimize.classinliner.builders.Pair;
 import com.android.tools.r8.ir.optimize.classinliner.builders.PairBuilder;
 import com.android.tools.r8.ir.optimize.classinliner.builders.Tuple;
+import com.android.tools.r8.ir.optimize.classinliner.code.C;
+import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
@@ -46,6 +50,7 @@
 import java.util.Collections;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -67,64 +72,64 @@
         ToolHelper.getClassAsBytes(CycleReferenceBA.class),
         ToolHelper.getClassAsBytes(ClassWithFinal.class)
     };
-    String main = TrivialTestClass.class.getCanonicalName();
-    ProcessResult javaOutput = runOnJavaRaw(main, classes);
-    assertEquals(0, javaOutput.exitCode);
-
     AndroidApp app = runR8(buildAndroidApp(classes), TrivialTestClass.class);
 
+    String javaOutput = runOnJava(TrivialTestClass.class);
+    String artOutput = runOnArt(app, TrivialTestClass.class);
+    assertEquals(javaOutput, artOutput);
+
     DexInspector inspector = new DexInspector(app);
     ClassSubject clazz = inspector.clazz(TrivialTestClass.class);
 
     assertEquals(
         Collections.singleton("java.lang.StringBuilder"),
-        collectNewInstanceTypes(clazz, "testInner"));
+        collectTypes(clazz, "testInner", "void"));
 
     assertEquals(
         Collections.emptySet(),
-        collectNewInstanceTypes(clazz, "testConstructorMapping1"));
+        collectTypes(clazz, "testConstructorMapping1", "void"));
 
     assertEquals(
         Collections.emptySet(),
-        collectNewInstanceTypes(clazz, "testConstructorMapping2"));
+        collectTypes(clazz, "testConstructorMapping2", "void"));
 
     assertEquals(
         Collections.singleton("java.lang.StringBuilder"),
-        collectNewInstanceTypes(clazz, "testConstructorMapping3"));
+        collectTypes(clazz, "testConstructorMapping3", "void"));
 
     assertEquals(
         Collections.emptySet(),
-        collectNewInstanceTypes(clazz, "testEmptyClass"));
+        collectTypes(clazz, "testEmptyClass", "void"));
 
     assertEquals(
         Collections.singleton(
             "com.android.tools.r8.ir.optimize.classinliner.trivial.EmptyClassWithInitializer"),
-        collectNewInstanceTypes(clazz, "testEmptyClassWithInitializer"));
+        collectTypes(clazz, "testEmptyClassWithInitializer", "void"));
 
     assertEquals(
         Collections.singleton(
             "com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal"),
-        collectNewInstanceTypes(clazz, "testClassWithFinalizer"));
+        collectTypes(clazz, "testClassWithFinalizer", "void"));
 
     assertEquals(
         Collections.emptySet(),
-        collectNewInstanceTypes(clazz, "testCallOnIface1"));
+        collectTypes(clazz, "testCallOnIface1", "void"));
 
     assertEquals(
         Collections.singleton(
             "com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2Impl"),
-        collectNewInstanceTypes(clazz, "testCallOnIface2"));
+        collectTypes(clazz, "testCallOnIface2", "void"));
 
     assertEquals(
         Sets.newHashSet(
             "com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB",
             "java.lang.StringBuilder"),
-        collectNewInstanceTypes(clazz, "testCycles"));
+        collectTypes(clazz, "testCycles", "void"));
 
     assertEquals(
         Sets.newHashSet("java.lang.StringBuilder",
             "com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB"),
-        collectNewInstanceTypes(inspector.clazz(CycleReferenceAB.class), "foo", "int"));
+        collectTypes(inspector.clazz(CycleReferenceAB.class), "foo", "void", "int"));
 
     assertFalse(inspector.clazz(CycleReferenceBA.class).isPresent());
   }
@@ -139,12 +144,12 @@
         ToolHelper.getClassAsBytes(PairBuilder.class),
         ToolHelper.getClassAsBytes(ControlFlow.class),
     };
-    String main = BuildersTestClass.class.getCanonicalName();
-    ProcessResult javaOutput = runOnJavaRaw(main, classes);
-    assertEquals(0, javaOutput.exitCode);
-
     AndroidApp app = runR8(buildAndroidApp(classes), BuildersTestClass.class);
 
+    String javaOutput = runOnJava(BuildersTestClass.class);
+    String artOutput = runOnArt(app, BuildersTestClass.class);
+    assertEquals(javaOutput, artOutput);
+
     DexInspector inspector = new DexInspector(app);
     ClassSubject clazz = inspector.clazz(BuildersTestClass.class);
 
@@ -152,7 +157,7 @@
         Sets.newHashSet(
             "com.android.tools.r8.ir.optimize.classinliner.builders.Pair",
             "java.lang.StringBuilder"),
-        collectNewInstanceTypes(clazz, "testSimpleBuilder"));
+        collectTypes(clazz, "testSimpleBuilder", "void"));
 
     // Note that Pair created instances were also inlined in the following method since
     // we use 'System.out.println(pX.toString())', if we used 'System.out.println(pX)'
@@ -160,25 +165,25 @@
     // would make it not eligible for inlining.
     assertEquals(
         Collections.singleton("java.lang.StringBuilder"),
-        collectNewInstanceTypes(clazz, "testSimpleBuilderWithMultipleBuilds"));
+        collectTypes(clazz, "testSimpleBuilderWithMultipleBuilds", "void"));
 
     assertFalse(inspector.clazz(PairBuilder.class).isPresent());
 
     assertEquals(
         Collections.singleton("java.lang.StringBuilder"),
-        collectNewInstanceTypes(clazz, "testBuilderConstructors"));
+        collectTypes(clazz, "testBuilderConstructors", "void"));
 
     assertFalse(inspector.clazz(Tuple.class).isPresent());
 
     assertEquals(
         Collections.singleton("java.lang.StringBuilder"),
-        collectNewInstanceTypes(clazz, "testWithControlFlow"));
+        collectTypes(clazz, "testWithControlFlow", "void"));
 
     assertFalse(inspector.clazz(ControlFlow.class).isPresent());
 
     assertEquals(
         Collections.emptySet(),
-        collectNewInstanceTypes(clazz, "testWithMoreControlFlow"));
+        collectTypes(clazz, "testWithMoreControlFlow", "void"));
 
     assertFalse(inspector.clazz(BuildersTestClass.Pos.class).isPresent());
   }
@@ -216,14 +221,65 @@
     assertThat(artResult.stderr, containsString("IncompatibleClassChangeError"));
   }
 
-  private Set<String> collectNewInstanceTypes(
-      ClassSubject clazz, String methodName, String... params) {
+  @Test
+  public void testCodeSample() throws Exception {
+    byte[][] classes = {
+        ToolHelper.getClassAsBytes(C.class),
+        ToolHelper.getClassAsBytes(C.L.class),
+        ToolHelper.getClassAsBytes(C.F.class),
+        ToolHelper.getClassAsBytes(CodeTestClass.class)
+    };
+    AndroidApp app = runR8(buildAndroidApp(classes), CodeTestClass.class);
+
+    String javaOutput = runOnJava(CodeTestClass.class);
+    String artOutput = runOnArt(app, CodeTestClass.class);
+    assertEquals(javaOutput, artOutput);
+
+    DexInspector inspector = new DexInspector(app);
+    ClassSubject clazz = inspector.clazz(C.class);
+
+    assertEquals(
+        Collections.emptySet(),
+        collectTypes(clazz, "method1", "int"));
+
+    assertEquals(
+        Collections.emptySet(),
+        collectTypes(clazz, "method2", "int"));
+
+    assertEquals(
+        Collections.emptySet(),
+        collectTypes(clazz, "method3", "int"));
+
+    assertFalse(inspector.clazz(C.L.class).isPresent());
+    assertFalse(inspector.clazz(C.F.class).isPresent());
+  }
+
+  private Set<String> collectTypes(
+      ClassSubject clazz, String methodName, String retValue, String... params) {
+    return Stream.concat(
+        collectNewInstanceTypesWithRetValue(clazz, methodName, retValue, params),
+        collectStaticGetTypesWithRetValue(clazz, methodName, retValue, params)
+    ).collect(Collectors.toSet());
+  }
+
+  private Stream<String> collectNewInstanceTypesWithRetValue(
+      ClassSubject clazz, String methodName, String retValue, String... params) {
     assertNotNull(clazz);
-    MethodSignature signature = new MethodSignature(methodName, "void", params);
+    MethodSignature signature = new MethodSignature(methodName, retValue, params);
     DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
     return filterInstructionKind(code, NewInstance.class)
-        .map(insn -> ((NewInstance) insn).getType().toSourceString())
-        .collect(Collectors.toSet());
+        .map(insn -> ((NewInstance) insn).getType().toSourceString());
+  }
+
+  private Stream<String> collectStaticGetTypesWithRetValue(
+      ClassSubject clazz, String methodName, String retValue, String... params) {
+    assertNotNull(clazz);
+    MethodSignature signature = new MethodSignature(methodName, retValue, params);
+    DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
+    return filterInstructionKind(code, Sget.class)
+        .map(Instruction::getField)
+        .filter(field -> field.clazz == field.type)
+        .map(field -> field.clazz.toSourceString());
   }
 
   private AndroidApp runR8(AndroidApp app, Class mainClass) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java
new file mode 100644
index 0000000..38e75c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/C.java
@@ -0,0 +1,39 @@
+// 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.code;
+
+public class C {
+  public static class L {
+    public final int x;
+
+    public L(int x) {
+      this.x = x;
+    }
+
+    public int getX() {
+      return x;
+    }
+  }
+
+  public final static class F {
+    public final static F I = new F();
+
+    public int getX() {
+      return 123;
+    }
+  }
+
+  public synchronized static int method1() {
+    return new L(1).x;
+  }
+
+  public synchronized static int method2() {
+    return new L(1).getX();
+  }
+
+  public synchronized static int method3() {
+    return F.I.getX();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/CodeTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/CodeTestClass.java
new file mode 100644
index 0000000..eb5dc10
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/code/CodeTestClass.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.code;
+
+public class CodeTestClass {
+  public static void main(String[] args) {
+    System.out.println(C.method1());
+    System.out.println(C.method2());
+    System.out.println(C.method3());
+  }
+}
+
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
new file mode 100644
index 0000000..eaae740
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.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.kotlin;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.code.NewInstance;
+import com.android.tools.r8.code.SgetObject;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.Sets;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.Test;
+
+public class KotlinClassInlinerTest extends AbstractR8KotlinTestBase {
+  private static boolean isLambda(DexClass clazz) {
+    return !clazz.type.getPackageDescriptor().startsWith("kotlin") &&
+        (isKStyleLambdaOrGroup(clazz) || isJStyleLambdaOrGroup(clazz));
+  }
+
+  private static boolean isKStyleLambdaOrGroup(DexClass clazz) {
+    return clazz.superType.descriptor.toString().equals("Lkotlin/jvm/internal/Lambda;");
+  }
+
+  private static boolean isJStyleLambdaOrGroup(DexClass clazz) {
+    return clazz.superType.descriptor.toString().equals("Ljava/lang/Object;") &&
+        clazz.interfaces.size() == 1;
+  }
+
+  private static Predicate<DexType> createLambdaCheck(DexInspector inspector) {
+    Set<DexType> lambdaClasses = inspector.allClasses().stream()
+        .filter(clazz -> isLambda(clazz.getDexClass()))
+        .map(clazz -> clazz.getDexClass().type)
+        .collect(Collectors.toSet());
+    return lambdaClasses::contains;
+  }
+
+  @Test
+  public void testJStyleLambdas() throws Exception {
+    final String mainClassName = "class_inliner_lambda_j_style.MainKt";
+    runTest("class_inliner_lambda_j_style", mainClassName, (app) -> {
+      DexInspector inspector = new DexInspector(app);
+      Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
+      ClassSubject clazz = inspector.clazz(mainClassName);
+
+      assertEquals(
+          Sets.newHashSet(),
+          collectAccessedLambdaTypes(lambdaCheck, clazz, "testStateless"));
+
+      assertEquals(
+          Sets.newHashSet(
+              "class_inliner_lambda_j_style.MainKt$testStateful$3"),
+          collectAccessedLambdaTypes(lambdaCheck, clazz, "testStateful"));
+
+      assertFalse(
+          inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1").isPresent());
+
+      assertEquals(
+          Sets.newHashSet(
+              "class_inliner_lambda_j_style.MainKt$testStateful2$1"),
+          collectAccessedLambdaTypes(lambdaCheck, clazz, "testStateful2"));
+
+      assertEquals(
+          Sets.newHashSet(
+              "class_inliner_lambda_j_style.MainKt$testStateful3$1"),
+          collectAccessedLambdaTypes(lambdaCheck, clazz, "testStateful3"));
+    });
+  }
+
+  private Set<String> collectAccessedLambdaTypes(
+      Predicate<DexType> isLambdaType, ClassSubject clazz, String methodName, String... params) {
+    assertNotNull(clazz);
+    MethodSignature signature = new MethodSignature(methodName, "void", params);
+    DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
+    return Stream.concat(
+        filterInstructionKind(code, NewInstance.class)
+            .map(insn -> ((NewInstance) insn).getType()),
+        filterInstructionKind(code, SgetObject.class)
+            .map(insn -> insn.getField().getHolder())
+    )
+        .filter(isLambdaType)
+        .map(DexType::toSourceString)
+        .collect(Collectors.toSet());
+  }
+
+  @Override
+  protected void runTest(String folder, String mainClass,
+      AndroidAppInspector inspector) throws Exception {
+    runTest(
+        folder, mainClass, null,
+        options -> {
+          options.enableInlining = true;
+          options.enableClassInlining = true;
+          options.enableLambdaMerging = false;
+        }, inspector);
+  }
+}
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 6ebb08b..dc9b642 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -587,7 +587,7 @@
     final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.ObjectPropertiesKt", "objectProperties_usePrimitiveProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
       String propertyName = "primitiveProp";
@@ -614,7 +614,7 @@
     final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.ObjectPropertiesKt", "objectProperties_usePrivateProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
       String propertyName = "privateProp";
@@ -641,7 +641,7 @@
     final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.ObjectPropertiesKt", "objectProperties_useInternalProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
       String propertyName = "internalProp";
@@ -668,7 +668,7 @@
     final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.ObjectPropertiesKt", "objectProperties_usePublicProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
       String propertyName = "publicProp";
@@ -695,7 +695,7 @@
     final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.ObjectPropertiesKt", "objectProperties_useLateInitPrivateProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
       String propertyName = "privateLateInitProp";
@@ -722,7 +722,7 @@
     final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.ObjectPropertiesKt", "objectProperties_useLateInitInternalProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
       String propertyName = "internalLateInitProp";
@@ -745,7 +745,7 @@
     final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.ObjectPropertiesKt", "objectProperties_useLateInitPublicProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
       String propertyName = "publicLateInitProp";
@@ -768,7 +768,7 @@
     final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.FilePropertiesKt", "fileProperties_usePrimitiveProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
       String propertyName = "primitiveProp";
@@ -795,7 +795,7 @@
     final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.FilePropertiesKt", "fileProperties_usePrivateProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
       String propertyName = "privateProp";
@@ -821,7 +821,7 @@
     final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.FilePropertiesKt", "fileProperties_useInternalProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
       String propertyName = "internalProp";
@@ -847,7 +847,7 @@
     final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.FilePropertiesKt", "fileProperties_usePublicProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
       String propertyName = "publicProp";
@@ -874,7 +874,7 @@
     final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.FilePropertiesKt", "fileProperties_useLateInitPrivateProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject fileClass = checkClassIsKept(dexInspector, testedClass.getClassName());
       String propertyName = "privateLateInitProp";
@@ -900,7 +900,7 @@
     final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.FilePropertiesKt", "fileProperties_useLateInitInternalProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
       String propertyName = "internalLateInitProp";
@@ -924,7 +924,7 @@
     final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.FilePropertiesKt", "fileProperties_useLateInitPublicProp");
-    runTest(PACKAGE_NAME, mainClass, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
       String propertyName = "publicLateInitProp";
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
index 9edb509..61b1454 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -155,7 +155,8 @@
     runTest(
         mainClass,
         ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
-        keepMainProguardConfiguration(mainClass),
+        // Prevent SuperClass from being merged into SubClass.
+        keepMainProguardConfiguration(mainClass, ImmutableList.of("-keep class **.SuperClass")),
         this::checkAllClassesPresentWithDefaultConstructor);
   }
 
@@ -166,7 +167,8 @@
     runTest(
         mainClass,
         ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
-        keepMainProguardConfiguration(mainClass),
+        // Prevent SuperClass from being merged into SubClass.
+        keepMainProguardConfiguration(mainClass, ImmutableList.of("-keep class **.SuperClass")),
         this::checkAllClassesPresentWithDefaultConstructor);
   }
 
@@ -177,7 +179,8 @@
     runTest(
         mainClass,
         ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
-        keepMainProguardConfiguration(mainClass),
+        // Prevent SuperClass from being merged into SubClass.
+        keepMainProguardConfiguration(mainClass, ImmutableList.of("-keep class **.SuperClass")),
         // TODO(74423424): Proguard eliminates the check-cast on null.
         this::checkAllClassesPresentWithDefaultConstructor,
         this::checkOnlyMainPresent);
@@ -191,7 +194,9 @@
     runTest(
         mainClass,
         ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
-        keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
+        // Prevent SuperClass from being merged into SubClass.
+        keepMainProguardConfiguration(
+            mainClass, ImmutableList.of("-dontoptimize", "-keep class **.SuperClass")),
         this::checkAllClassesPresentWithDefaultConstructor);
   }
 
@@ -202,7 +207,8 @@
     runTest(
         mainClass,
         ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
-        keepMainProguardConfiguration(mainClass),
+        // Prevent SuperClass from being merged into SubClass.
+        keepMainProguardConfiguration(mainClass, ImmutableList.of("-keep class **.SuperClass")),
         this::checkAllClassesPresentWithDefaultConstructor);
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
index 73c3011..7480e2e 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
@@ -187,19 +187,20 @@
 
   @Test
   public void ifOnFieldInImplementer_withoutNthWildcard() throws Exception {
-    List<String> config = ImmutableList.of(
-        "-keep class **.MainUsesImpl {",
-        "  public static void main(java.lang.String[]);",
-        "}",
-        "-if class ** implements **.I {",
-        "  private <fields>;",
-        "}",
-        "-keep class **.D1",
-        "-if class ** implements **.I {",
-        "  public <fields>;",
-        "}",
-        "-keep class **.D2"
-    );
+    List<String> config =
+        ImmutableList.of(
+            "-keep class **.MainUsesImpl {",
+            "  public static void main(java.lang.String[]);",
+            "}",
+            "-keep class **.I", // Prevent I from being merged into Impl.
+            "-if class ** implements **.I {",
+            "  private <fields>;",
+            "}",
+            "-keep class **.D1",
+            "-if class ** implements **.I {",
+            "  public <fields>;",
+            "}",
+            "-keep class **.D2");
 
     DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
     verifyClassesAbsent(dexInspector, D2.class);
@@ -209,19 +210,20 @@
 
   @Test
   public void ifOnFieldInImplementer_withNthWildcard() throws Exception {
-    List<String> config = ImmutableList.of(
-        "-keep class **.MainUsesImpl {",
-        "  public static void main(java.lang.String[]);",
-        "}",
-        "-if class ** implements **.I {",
-        "  private <fields>;",
-        "}",
-        "-keep class <2>.D1",
-        "-if class ** implements **.I {",
-        "  public <fields>;",
-        "}",
-        "-keep class <2>.D2"
-    );
+    List<String> config =
+        ImmutableList.of(
+            "-keep class **.MainUsesImpl {",
+            "  public static void main(java.lang.String[]);",
+            "}",
+            "-keep class **.I", // Prevent I from being merged into Impl.
+            "-if class ** implements **.I {",
+            "  private <fields>;",
+            "}",
+            "-keep class <2>.D1",
+            "-if class ** implements **.I {",
+            "  public <fields>;",
+            "}",
+            "-keep class <2>.D2");
 
     DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
     verifyClassesAbsent(dexInspector, D2.class);
diff --git a/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/SamIface.java b/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/SamIface.java
new file mode 100644
index 0000000..bccef3e
--- /dev/null
+++ b/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/SamIface.java
@@ -0,0 +1,21 @@
+// 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 class_inliner_lambda_j_style;
+
+public interface SamIface {
+  String foo();
+
+  class Consumer {
+    public static void consume(SamIface iface) {
+      System.out.println(iface.foo());
+    }
+
+    public static void consumeBig(SamIface iface) {
+      System.out.println("Bigger than inline limit, class name: " + iface.getClass().getName());
+      System.out.println("Bigger than inline limit, result: '" + iface.foo() + "'");
+      consume(iface);
+    }
+  }
+}
diff --git a/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/main.kt b/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/main.kt
new file mode 100644
index 0000000..ebf26cd
--- /dev/null
+++ b/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/main.kt
@@ -0,0 +1,99 @@
+// 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 class_inliner_lambda_j_style
+
+private var COUNT = 0
+
+fun nextInt() = COUNT++
+fun next() = "${nextInt()}".padStart(3, '0')
+
+fun main(args: Array<String>) {
+    testStateless()
+    testStateful()
+    testStateful2()
+    testStateful3()
+}
+
+@Synchronized
+fun testStateless() {
+    SamIface.Consumer.consume { "123" }
+    SamIface.Consumer.consume { "ABC" }
+    SamIface.Consumer.consume {
+        var x = 0
+        println("A: ${x++}")
+        println("B: ${x++}")
+        println("C: ${x++}")
+        println("D: ${x++}")
+        println("E: ${x++}")
+        "outer + $x"
+    }
+}
+
+@Synchronized
+fun testStateful() {
+    var someVariable = 0
+
+    SamIface.Consumer.consume {
+        println("A: someVariable = $someVariable")
+        someVariable += 1
+        "B: someVariable = $someVariable"
+    }
+    SamIface.Consumer.consume {
+        SamIface.Consumer.consume {
+            println("E: someVariable = $someVariable")
+            someVariable += 1
+            "F: someVariable = $someVariable"
+        }
+        for (i in 1..20) {
+            someVariable += 1
+            if (i % 4 == 0) {
+                println("G: someVariable = $someVariable")
+            }
+        }
+        someVariable += 1
+        "H: someVariable = $someVariable"
+    }
+    SamIface.Consumer.consumeBig {
+        println("I: someVariable = $someVariable")
+        someVariable += 1
+        "J: someVariable = $someVariable"
+    }
+}
+
+@Synchronized
+fun testStateful2() {
+    var someVariable = 0
+    SamIface.Consumer.consumeBig {
+        println("[Z] A: someVariable = $someVariable")
+        someVariable += 1
+        println("[Z] B: someVariable = $someVariable")
+        someVariable += 1
+        println("[Z] C: someVariable = $someVariable")
+        someVariable += 1
+        println("[Z] D: someVariable = $someVariable")
+        someVariable += 1
+        println("[Z] E: someVariable = $someVariable")
+        someVariable += 1
+        "[Z] F: someVariable = $someVariable"
+    }
+}
+
+@Synchronized
+fun testStateful3() {
+    var someVariable = 0
+    SamIface.Consumer.consumeBig {
+        println("[W] A: someVariable = $someVariable")
+        someVariable += 1
+        println("[W] B: someVariable = $someVariable")
+        someVariable += 1
+        println("[W] C: someVariable = $someVariable")
+        someVariable += 1
+        println("[W] D: someVariable = $someVariable")
+        someVariable += 1
+        println("[W] E: someVariable = $someVariable")
+        someVariable += 1
+        "[W] F: someVariable = $someVariable"
+    }
+}