Support kotlin k-style lambdas in class inliner

Extending class inliner to support k-style lambdas. The only significant
change is that now classes not directly extending java.lang.Object can be
considered eligible for inlining.

This is achieved by very restrictive rules for classes not directly
inherited from java.lang.Object, which is still wide enough to allow
k-style lambdas to become eligible.

This CL also includes following improvements:

 -- hints for kotlin method parameters suggesting they are not-null and,
    thus, checked inside the method for nullability; those checks will be
    removed after inlining if we know that we pass non-null argument.
    Note that these hints don't affect semantics and only used for
    inlining heuristics (see b/72972159)

 -- based on previous work we can now mark *some* singleton instance
    field reads as always returning non-null value

 -- fix propagation of never null flag via values. This is a temp
    solution I needed to make some things work, and AFAIK Jinseong
    is going to improve nullability handling soon

GMSCORE (no time impact):
  w/o: 28,543,136
    w: 28,537,188 (-6K)

Bug: 80135269
Bug: 72972159
Change-Id: Ifcae9fe13cefaea7a10a7abd3d80983dd090b505
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 362fc2c..83cb824 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -47,6 +47,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
@@ -677,6 +678,7 @@
     private ClassInlinerEligibility classInlinerEligibility = null;
     private TrivialInitializer trivialInitializerInfo = null;
     private ParameterUsagesInfo parametersUsages = null;
+    private BitSet kotlinNotNullParamHints = null;
 
     private OptimizationInfo() {
       // Intentionally left empty.
@@ -700,6 +702,14 @@
       return parametersUsages == null ? null : parametersUsages.getParameterUsage(parameter);
     }
 
+    public BitSet getKotlinNotNullParamHints() {
+      return kotlinNotNullParamHints;
+    }
+
+    public void setKotlinNotNullParamHints(BitSet hints) {
+      this.kotlinNotNullParamHints = hints;
+    }
+
     public boolean returnsArgument() {
       return returnedArgument != -1;
     }
@@ -843,6 +853,10 @@
     ensureMutableOI().setParameterUsages(parametersUsages);
   }
 
+  synchronized public void setKotlinNotNullParamHints(BitSet hints) {
+    ensureMutableOI().setKotlinNotNullParamHints(hints);
+  }
+
   synchronized public void setTrivialInitializer(TrivialInitializer info) {
     ensureMutableOI().setTrivialInitializer(info);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 1a43128..51e1891 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -35,6 +35,11 @@
     this.type = type;
   }
 
+  @Override
+  boolean computeNeverNull() {
+    return object().isNeverNull();
+  }
+
   public DexType getType() {
     return type;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 5d37ea6..62e518d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -68,6 +68,12 @@
     this.position = position;
   }
 
+  boolean computeNeverNull() {
+    // By default just return the flag from the out value since it never changes.
+    assert outValue != null;
+    return outValue.isNeverNull();
+  }
+
   public final void forceSetPosition(Position position) {
     this.position = position;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index f27153d..b095bb4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -29,6 +29,11 @@
     }
   }
 
+  @Override
+  boolean computeNeverNull() {
+    return src().isNeverNull();
+  }
+
   public Value dest() {
     return outValue;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 51850a0..ed09ee7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -57,7 +57,6 @@
     // period of time to break cycles. When the cycle has been resolved they are completed
     // exactly once by adding the operands.
     assert operands.isEmpty();
-    boolean canBeNull = false;
     if (block.getPredecessors().size() == 0) {
       throwUndefinedValueError();
     }
@@ -66,13 +65,10 @@
       // Since this read has been delayed we must provide the local info for the value.
       Value operand = builder.readRegister(register, type, pred, edgeType, getLocalInfo());
       operand.constrainType(type);
-      canBeNull |= operand.canBeNull();
       appendOperand(operand);
     }
-    if (!canBeNull) {
-      markNeverNull();
-    }
     removeTrivialPhi();
+    recomputeNeverNull();
   }
 
   public void addOperands(List<Value> operands) {
@@ -84,20 +80,24 @@
     // period of time to break cycles. When the cycle has been resolved they are completed
     // exactly once by adding the operands.
     assert this.operands.isEmpty();
-    boolean canBeNull = false;
     if (operands.size() == 0) {
       throwUndefinedValueError();
     }
     for (Value operand : operands) {
-      canBeNull |= operand.canBeNull();
       appendOperand(operand);
     }
-    if (!canBeNull) {
-      markNeverNull();
-    }
     if (removeTrivialPhi) {
       removeTrivialPhi();
     }
+    recomputeNeverNull();
+  }
+
+  // Implementation assumes that canBeNull may change to neverNull, but
+  // not other way around. This will need to be revised later.
+  void recomputeNeverNull() {
+    if (canBeNull() && operands.stream().allMatch(Value::isNeverNull)) {
+      markNeverNull();
+    }
   }
 
   public void addDebugValue(Value value) {
@@ -131,7 +131,10 @@
 
   public void removeOperand(int index) {
     operands.get(index).removePhiUser(this);
-    operands.remove(index);
+    Value value = operands.remove(index);
+    if (value.canBeNull()) {
+      recomputeNeverNull();
+    }
   }
 
   public void removeOperandsByIndex(List<Integer> operandsToRemove) {
@@ -147,6 +150,7 @@
       current = i + 1;
     }
     operands.addAll(copy.subList(current, copy.size()));
+    recomputeNeverNull();
   }
 
   public void replaceOperandAt(int predIndex, Value newValue) {
@@ -154,6 +158,9 @@
     operands.set(predIndex, newValue);
     newValue.addPhiUser(this);
     current.removePhiUser(this);
+    if (current.canBeNull() && newValue.isNeverNull()) {
+      recomputeNeverNull();
+    }
   }
 
   void replaceOperand(Value current, Value newValue) {
@@ -163,6 +170,9 @@
         newValue.addPhiUser(this);
       }
     }
+    if (current.canBeNull() && newValue.isNeverNull()) {
+      recomputeNeverNull();
+    }
   }
 
   void replaceDebugValue(Value current, Value newValue) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 05dd801..815a38f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -628,6 +628,17 @@
   public void markNeverNull() {
     assert !neverNull;
     neverNull = true;
+
+    // Propagate never null flag change.
+    for (Instruction user : users) {
+      Value value = user.outValue;
+      if (value != null && value.canBeNull() && user.computeNeverNull()) {
+        value.markNeverNull();
+      }
+    }
+    for (Phi phi : phiUsers) {
+      phi.recomputeNeverNull();
+    }
   }
 
   /**
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 479af89..dce8aa6 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
@@ -65,6 +65,7 @@
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ListMultimap;
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
@@ -630,6 +631,11 @@
     printC1VisualizerHeader(method);
     printMethod(code, "Initial IR (SSA)");
 
+    if (method.getCode() != null && method.getCode().isJarCode() &&
+        appInfo.definitionFor(method.method.holder).hasKotlinInfo()) {
+      computeKotlinNotNullParamHints(feedback, method, code);
+    }
+
     if (options.canHaveArtStringNewInitBug()) {
       CodeRewriter.ensureDirectStringNewToInit(code);
     }
@@ -655,7 +661,8 @@
     }
 
     if (memberValuePropagation != null) {
-      memberValuePropagation.rewriteWithConstantValues(code, method.method.holder);
+      memberValuePropagation.rewriteWithConstantValues(
+          code, method.method.holder, isProcessedConcurrently);
     }
     if (options.enableSwitchMapRemoval && appInfo.hasLiveness()) {
       codeRewriter.removeSwitchMaps(code);
@@ -779,6 +786,36 @@
     finalizeIR(method, code, feedback);
   }
 
+  private void computeKotlinNotNullParamHints(
+      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
+    // Try to infer Kotlin non-null parameter check to use it as a hint.
+    List<Value> arguments = code.collectArguments(true);
+    BitSet paramsCheckedForNull = new BitSet();
+    DexMethod checkParameterIsNotNull =
+        code.options.itemFactory.kotlin.intrinsics.checkParameterIsNotNull;
+    for (int index = 0; index < arguments.size(); index++) {
+      Value argument = arguments.get(index);
+      for (Instruction user : argument.uniqueUsers()) {
+        // To enforce parameter non-null requirement Kotlin uses intrinsic:
+        //    kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(param, message)
+        //
+        // with the following we simply look if the parameter is ever passed
+        // to the mentioned intrinsic as the first argument. We do it only for
+        // code coming from Java classfile, since after the method is rewritten
+        // by R8 this call gets inlined.
+        if (!user.isInvokeStatic() ||
+            user.asInvokeMethod().getInvokedMethod() != checkParameterIsNotNull ||
+            user.inValues().indexOf(argument) != 0) {
+          continue;
+        }
+        paramsCheckedForNull.set(index);
+      }
+    }
+    if (paramsCheckedForNull.length() > 0) {
+      feedback.setKotlinNotNullParamHints(method, paramsCheckedForNull);
+    }
+  }
+
   private void finalizeIR(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     code.traceBlocks();
     if (options.isGeneratingClassFiles()) {
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 7ed34ba..ad6d66d 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
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import java.util.BitSet;
 
 public interface OptimizationFeedback {
   void methodReturnsArgument(DexEncodedMethod method, int argument);
@@ -21,4 +22,5 @@
   void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibility eligibility);
   void setTrivialInitializer(DexEncodedMethod method, TrivialInitializer info);
   void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo);
+  void setKotlinNotNullParamHints(DexEncodedMethod method, BitSet hints);
 }
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 44588ab..326fa03 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
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import java.util.BitSet;
 
 public class OptimizationFeedbackDirect implements OptimizationFeedback {
 
@@ -62,4 +63,9 @@
   public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
     method.setParameterUsages(parameterUsagesInfo);
   }
+
+  @Override
+  public void setKotlinNotNullParamHints(DexEncodedMethod method, BitSet hints) {
+    method.setKotlinNotNullParamHints(hints);
+  }
 }
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 c2d6ede..359c90d 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
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import java.util.BitSet;
 
 public class OptimizationFeedbackIgnore implements OptimizationFeedback {
 
@@ -45,4 +46,8 @@
   @Override
   public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
   }
+
+  @Override
+  public void setKotlinNotNullParamHints(DexEncodedMethod method, BitSet hints) {
+  }
 }
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 7ea7f57..22e3c32 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
@@ -755,7 +755,9 @@
     //
     //  (1) as a receiver of reads/writes of instance fields of the holder class
     //  (2) as a return value
-    //  (3) as a receiver of a call to the superclass initializer
+    //  (3) as a receiver of a call to the superclass initializer. Note that we don't
+    //      check what is passed to superclass initializer as arguments, only check
+    //      that it is not the instance being initialized.
     //
     boolean instanceInitializer = method.isInstanceInitializer();
     if (method.accessFlags.isNative() ||
@@ -787,23 +789,25 @@
           (insn.isInstancePut() && insn.asInstancePut().object() == receiver)) {
         DexField field = insn.asFieldInstruction().getField();
         if (field.clazz == clazz.type && clazz.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.
+          // Require only accessing instance fields of the *current* class.
           continue;
         }
         return;
       }
 
-      // If this is an instance initializer allow one call
-      // to java.lang.Object.<init>() on 'this'.
-      if (instanceInitializer && insn.isInvokeDirect()) {
+      // If this is an instance initializer allow one call to superclass instance initializer.
+      if (insn.isInvokeDirect()) {
         InvokeDirect invokedDirect = insn.asInvokeDirect();
-        if (invokedDirect.getInvokedMethod() == dexItemFactory.objectMethods.constructor &&
-            invokedDirect.getReceiver() == receiver &&
-            !seenSuperInitCall) {
+        DexMethod invokedMethod = invokedDirect.getInvokedMethod();
+        if (dexItemFactory.isConstructor(invokedMethod) &&
+            invokedMethod.holder == clazz.superType &&
+            invokedDirect.inValues().lastIndexOf(receiver) == 0 &&
+            !seenSuperInitCall &&
+            instanceInitializer) {
           seenSuperInitCall = true;
           continue;
         }
+        // We don't support other direct calls yet.
         return;
       }
 
@@ -870,7 +874,7 @@
 
   // This method defines trivial instance initializer as follows:
   //
-  // ** The initializer may only call the initializer of the base class, which
+  // ** The initializer may call the initializer of the base class, which
   //    itself must be trivial.
   //
   // ** java.lang.Object.<init>() is considered trivial.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index b05fd4f..cc7f571 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -14,11 +14,14 @@
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokePolymorphic;
 import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CallSiteInformation;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.BitSet;
+import java.util.List;
 import java.util.ListIterator;
 import java.util.function.Predicate;
 
@@ -218,13 +221,33 @@
     if (reason == Reason.SIMPLE) {
       // If we are looking for a simple method, only inline if actually simple.
       Code code = candidate.getCode();
-      if (!code.estimatedSizeForInliningAtMost(inliningInstructionLimit)) {
+      int instructionLimit = computeInstructionLimit(invoke, candidate);
+      if (!code.estimatedSizeForInliningAtMost(instructionLimit)) {
         return false;
       }
     }
     return true;
   }
 
+  private int computeInstructionLimit(InvokeMethod invoke, DexEncodedMethod candidate) {
+    int instructionLimit = inliningInstructionLimit;
+    BitSet hints = candidate.getOptimizationInfo().getKotlinNotNullParamHints();
+    if (hints != null) {
+      List<Value> arguments = invoke.inValues();
+      if (invoke.isInvokeMethodWithReceiver()) {
+        arguments = arguments.subList(1, arguments.size());
+      }
+      for (int index = 0; index < arguments.size(); index++) {
+        Value argument = arguments.get(index);
+        if (argument.isNeverNull() && hints.get(index)) {
+          // 5-4 instructions per parameter check are expected to be removed.
+          instructionLimit += 4;
+        }
+      }
+    }
+    return instructionLimit;
+  }
+
   @Override
   public InlineAction computeForInvokeWithReceiver(
       InvokeMethodWithReceiver invoke, DexType invocationContext) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index cdfa2b1..f92eb80 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialClassInitializer;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexMethod;
@@ -23,6 +25,7 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardMemberRule;
+import java.util.function.Predicate;
 
 public class MemberValuePropagation {
 
@@ -113,7 +116,8 @@
    * <p>
    * Also assigns value ranges to values where possible.
    */
-  public void rewriteWithConstantValues(IRCode code, DexType callingContext) {
+  public void rewriteWithConstantValues(
+      IRCode code, DexType callingContext, Predicate<DexEncodedMethod> isProcessedConcurrently) {
     InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
       Instruction current = iterator.next();
@@ -155,7 +159,7 @@
         if (!invokeReplaced && invoke.outValue() != null) {
           DexEncodedMethod target = invoke.computeSingleTarget(appInfo);
           if (target != null) {
-            if (target.getOptimizationInfo().neverReturnsNull()) {
+            if (target.getOptimizationInfo().neverReturnsNull() && invoke.outValue().canBeNull()) {
               invoke.outValue().markNeverNull();
             }
             if (target.getOptimizationInfo().returnsConstant()) {
@@ -182,12 +186,11 @@
       } else if (current.isStaticGet()) {
         StaticGet staticGet = current.asStaticGet();
         DexField field = staticGet.getField();
-        Instruction replacement = null;
         DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
         ProguardMemberRuleLookup lookup = null;
         if (target != null) {
           // Check if a this value is known const.
-          replacement = target.valueAsConstInstruction(appInfo, staticGet.dest());
+          Instruction replacement = target.valueAsConstInstruction(appInfo, staticGet.dest());
           if (replacement == null) {
             lookup = lookupMemberRule(target);
             if (lookup != null) {
@@ -207,6 +210,37 @@
             } else {
               iterator.replaceCurrentInstruction(replacement);
             }
+          } else if (staticGet.dest() != null) {
+            // In case the class holder of this static field satisfying following criteria:
+            //   -- cannot trigger other static initializer except for its own
+            //   -- is final
+            //   -- has a class initializer which is classified as trivial
+            //      (see CodeRewriter::computeClassInitializerInfo) and
+            //      initializes the field being accessed
+            //
+            // ... and the field itself is not pinned by keep rules (in which case it might
+            // be updated outside the class constructor, e.g. via reflections), it is safe
+            // to assume that the static-get instruction reads the value it initialized value
+            // in class initializer and is never null.
+            //
+            DexClass holderDefinition = appInfo.definitionFor(field.getHolder());
+            if (holderDefinition != null &&
+                holderDefinition.accessFlags.isFinal() &&
+                !appInfo.canTriggerStaticInitializer(field.getHolder(), true)) {
+
+              Value outValue = staticGet.dest();
+              DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
+              if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
+                TrivialInitializer info =
+                    classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
+                if (info != null &&
+                    ((TrivialClassInitializer) info).field == field &&
+                    !appInfo.isPinned(field) &&
+                    outValue.canBeNull()) {
+                  outValue.markNeverNull();
+                }
+              }
+            }
           }
         }
       } else if (current.isStaticPut()) {
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 b28158a..219b5b1 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
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.google.common.collect.Streams;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -127,31 +128,51 @@
         .filter(insn -> insn.isNewInstance() || insn.isStaticGet())
         .collect(Collectors.toList());
 
-    for (Instruction root : roots) {
-      InlineCandidateProcessor processor =
-          new InlineCandidateProcessor(factory, appInfo,
-              type -> isClassEligible(appInfo, type),
-              isProcessedConcurrently, method, root);
+    // We loop inlining iterations until there was no inlining, but still use same set
+    // of roots to avoid infinite inlining. Looping makes possible for some roots to
+    // become eligible after other roots are inlined.
 
-      // Assess eligibility and compute inlining of direct calls and extra methods needed.
-      if (!processor.isInstanceEligible() ||
-          !processor.isClassAndUsageEligible() ||
-          !processor.areInstanceUsersEligible(defaultOracle)) {
-        continue;
+    boolean repeat;
+    do {
+      repeat = false;
+
+      Iterator<Instruction> rootsIterator = roots.iterator();
+      while (rootsIterator.hasNext()) {
+        Instruction root = rootsIterator.next();
+        InlineCandidateProcessor processor =
+            new InlineCandidateProcessor(factory, appInfo,
+                type -> isClassEligible(appInfo, type),
+                isProcessedConcurrently, method, root);
+
+        // Assess eligibility of instance and class.
+        if (!processor.isInstanceEligible() ||
+            !processor.isClassAndUsageEligible()) {
+          // This root will never be inlined.
+          rootsIterator.remove();
+          continue;
+        }
+
+        // Assess users eligibility and compute inlining of direct calls and extra methods needed.
+        if (!processor.areInstanceUsersEligible(defaultOracle)) {
+          // This root may succeed if users change in future.
+          continue;
+        }
+
+        // Is inlining allowed.
+        if (processor.getEstimatedCombinedSizeForInlining() >= totalMethodInstructionLimit) {
+          continue;
+        }
+
+        // Inline the class instance.
+        processor.processInlining(code, inliner);
+
+        // Restore normality.
+        code.removeAllTrivialPhis();
+        assert code.isConsistentSSA();
+        rootsIterator.remove();
+        repeat = true;
       }
-
-      // Is inlining allowed.
-      if (processor.getEstimatedCombinedSizeForInlining() >= totalMethodInstructionLimit) {
-        continue;
-      }
-
-      // Inline the class instance.
-      processor.processInlining(code, inliner);
-
-      // Restore normality.
-      code.removeAllTrivialPhis();
-      assert code.isConsistentSSA();
-    }
+    } while (repeat);
   }
 
   private boolean isClassEligible(AppInfo appInfo, DexType clazz) {
@@ -167,7 +188,6 @@
 
   // Class is eligible for this optimization. Eligibility implementation:
   //   - is not an abstract class or interface
-  //   - directly extends java.lang.Object
   //   - does not declare finalizer
   //   - does not trigger any static initializers except for its own
   private boolean computeClassEligible(AppInfo appInfo, DexType clazz) {
@@ -177,11 +197,6 @@
       return false;
     }
 
-    // Must directly extend Object.
-    if (definition.superType != factory.objectType) {
-      return false;
-    }
-
     // Class must not define finalizer.
     for (DexEncodedMethod method : definition.virtualMethods()) {
       if (method.method.name == factory.finalizeMethodName &&
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 9c553bd..4d6e3b1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod.ParameterUsagesInfo.SingleCallOfArgumentMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialClassInitializer;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialInstanceInitializer;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -21,6 +22,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Invoke.Type;
@@ -114,6 +116,10 @@
     }
 
     if (isNewInstance()) {
+      // NOTE: if the eligible class does not directly extend java.lang.Object,
+      // we also have to guarantee that it is initialized with initializer classified as
+      // TrivialInstanceInitializer. This will be checked in areInstanceUsersEligible(...).
+
       // There must be no static initializer on the class itself.
       return !eligibleClassDefinition.hasClassInitializer();
     }
@@ -238,6 +244,16 @@
         }
       }
 
+      // Allow some IF instructions.
+      if (user.isIf()) {
+        If ifInsn = user.asIf();
+        If.Type type = ifInsn.getType();
+        if (ifInsn.isZeroTest() && (type == If.Type.EQ || type == If.Type.NE)) {
+          // Allow ==/!= null tests, we know that the instance is a non-null value.
+          continue;
+        }
+      }
+
       return false;  // Not eligible.
     }
     return true;
@@ -256,7 +272,8 @@
     replaceUsagesAsUnusedArgument(code);
     forceInlineExtraMethodInvocations(inliner);
     forceInlineDirectMethodInvocations(inliner);
-    removeSuperClassInitializerAndFieldReads(code);
+    removeMiscUsages(code);
+    removeFieldReads(code);
     removeFieldWrites();
     removeInstruction(root);
   }
@@ -308,19 +325,58 @@
     }
   }
 
-  // Remove call to superclass initializer, replace field reads with appropriate
-  // values, insert phis when needed.
-  private void removeSuperClassInitializerAndFieldReads(IRCode code) {
-    Map<DexField, FieldValueHelper> fieldHelpers = new IdentityHashMap<>();
+  // Remove miscellaneous users before handling field reads.
+  private void removeMiscUsages(IRCode code) {
+    boolean needToRemoveUnreachableBlocks = false;
     for (Instruction user : eligibleInstance.uniqueUsers()) {
       // Remove the call to superclass constructor.
       if (root.isNewInstance() &&
           user.isInvokeDirect() &&
-          user.asInvokeDirect().getInvokedMethod() == factory.objectMethods.constructor) {
+          factory.isConstructor(user.asInvokeDirect().getInvokedMethod()) &&
+          user.asInvokeDirect().getInvokedMethod().holder == eligibleClassDefinition.superType) {
         removeInstruction(user);
         continue;
       }
 
+      if (user.isIf()) {
+        If ifInsn = user.asIf();
+        assert ifInsn.isZeroTest()
+            : "Unexpected usage in non-zero-test IF instruction: " + user;
+        BasicBlock block = user.getBlock();
+        If.Type type = ifInsn.getType();
+        assert type == If.Type.EQ || type == If.Type.NE
+            : "Unexpected type in zero-test IF instruction: " + user;
+        BasicBlock newBlock = type == If.Type.EQ
+            ? ifInsn.fallthroughBlock() : ifInsn.getTrueTarget();
+        BasicBlock blockToRemove = type == If.Type.EQ
+            ? ifInsn.getTrueTarget() : ifInsn.fallthroughBlock();
+        assert newBlock != blockToRemove;
+
+        block.replaceSuccessor(blockToRemove, newBlock);
+        blockToRemove.removePredecessor(block);
+        assert block.exit().isGoto();
+        assert block.exit().asGoto().getTarget() == newBlock;
+        needToRemoveUnreachableBlocks = true;
+        continue;
+      }
+
+      if (user.isInstanceGet() || user.isInstancePut()) {
+        // Leave field reads/writes until next steps.
+        continue;
+      }
+
+      throw new Unreachable("Unexpected usage left after method inlining: " + user);
+    }
+
+    if (needToRemoveUnreachableBlocks) {
+      code.removeUnreachableBlocks();
+    }
+  }
+
+  // Replace field reads with appropriate values, insert phis when needed.
+  private void removeFieldReads(IRCode code) {
+    Map<DexField, FieldValueHelper> fieldHelpers = new IdentityHashMap<>();
+    for (Instruction user : eligibleInstance.uniqueUsers()) {
       if (user.isInstanceGet()) {
         // Replace a field read with appropriate value.
         replaceFieldRead(code, user.asInstanceGet(), fieldHelpers);
@@ -385,6 +441,19 @@
       return null;
     }
 
+    // If the superclass of the initializer is NOT java.lang.Object, the super class
+    // initializer being called must be classified as TrivialInstanceInitializer.
+    //
+    // NOTE: since we already classified the class as eligible, it does not have
+    //       any class initializers in superclass chain or in superinterfaces, see
+    //       details in ClassInliner::computeClassEligible(...).
+    if (eligibleClassDefinition.superType != factory.objectType) {
+      TrivialInitializer info = definition.getOptimizationInfo().getTrivialInitializerInfo();
+      if (!(info instanceof TrivialInstanceInitializer)) {
+        return null;
+      }
+    }
+
     if (!definition.isInliningCandidate(method, Reason.SIMPLE, appInfo)) {
       // We won't be able to inline it here.
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 0af41b1..05c7b92 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -80,6 +80,9 @@
     public final DexType type = factory.createType("Lkotlin/jvm/internal/Intrinsics;");
     public final DexMethod throwParameterIsNullException = factory.createMethod(type,
         factory.createProto(factory.voidType, factory.stringType), "throwParameterIsNullException");
+    public final DexMethod checkParameterIsNotNull = factory.createMethod(type,
+        factory.createProto(factory.voidType, factory.objectType, factory.stringType),
+        "checkParameterIsNotNull");
     public final DexMethod throwNpe = factory.createMethod(
         type, factory.createProto(factory.voidType), "throwNpe");
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
index be7ee92..f894322 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
@@ -89,7 +89,7 @@
         "-keep class B { *; }",
         "-keep class C { *; }",
         "-dontshrink");
-    AndroidApp app = compileWithR8(builder, pgConfigs, null);
+    AndroidApp app = compileWithR8(builder, pgConfigs, opts -> opts.enableClassInlining = false);
 
     DexEncodedMethod method = getMethod(app, CLASS_NAME, main);
     assertNotNull(method);
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index f99934e..fd78cb7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -231,7 +231,7 @@
     }
 
     // Build classpath for compilation (and java execution)
-    assert classpath.isEmpty();
+    classpath.clear();
     classpath.add(getKotlinJarFile(folder));
     classpath.add(getJavaJarFile(folder));
     classpath.addAll(extraClasspath);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 46af30d..a690429 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -7,7 +7,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.SgetObject;
 import com.android.tools.r8.graph.DexClass;
@@ -16,14 +18,26 @@
 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.ImmutableList;
 import com.google.common.collect.Sets;
+import java.util.Collection;
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
 
 public class KotlinClassInlinerTest extends AbstractR8KotlinTestBase {
+  @Parameters(name = "allowAccessModification: {0} target: {1}")
+  public static Collection<Object[]> data() {
+    ImmutableList.Builder<Object[]> builder = new ImmutableList.Builder<>();
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      builder.add(new Object[]{Boolean.TRUE, targetVersion});
+    }
+    return builder.build();
+  }
+
   private static boolean isLambda(DexClass clazz) {
     return !clazz.type.getPackageDescriptor().startsWith("kotlin") &&
         (isKStyleLambdaOrGroup(clazz) || isJStyleLambdaOrGroup(clazz));
@@ -49,7 +63,17 @@
   @Test
   public void testJStyleLambdas() throws Exception {
     final String mainClassName = "class_inliner_lambda_j_style.MainKt";
-    runTest("class_inliner_lambda_j_style", mainClassName, (app) -> {
+    runTest("class_inliner_lambda_j_style", mainClassName, false, (app) -> {
+      DexInspector inspector = new DexInspector(app);
+      assertTrue(
+          inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1").isPresent());
+      assertTrue(
+          inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful2$1").isPresent());
+      assertTrue(
+          inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful3$1").isPresent());
+    });
+
+    runTest("class_inliner_lambda_j_style", mainClassName, true, (app) -> {
       DexInspector inspector = new DexInspector(app);
       Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
       ClassSubject clazz = inspector.clazz(mainClassName);
@@ -81,6 +105,40 @@
     });
   }
 
+  @Test
+  public void testKStyleLambdas() throws Exception {
+    final String mainClassName = "class_inliner_lambda_k_style.MainKt";
+    runTest("class_inliner_lambda_k_style", mainClassName, false, (app) -> {
+      DexInspector inspector = new DexInspector(app);
+      assertTrue(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1").isPresent());
+      assertTrue(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1").isPresent());
+    });
+
+    runTest("class_inliner_lambda_k_style", mainClassName, true, (app) -> {
+      DexInspector inspector = new DexInspector(app);
+      Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
+      ClassSubject clazz = inspector.clazz(mainClassName);
+
+      assertEquals(
+          Sets.newHashSet(),
+          collectAccessedLambdaTypes(lambdaCheck, clazz,
+              "testKotlinSequencesStateless", "kotlin.sequences.Sequence"));
+
+      assertFalse(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1").isPresent());
+
+      assertEquals(
+          Sets.newHashSet(),
+          collectAccessedLambdaTypes(lambdaCheck, clazz,
+              "testKotlinSequencesStateful", "int", "int", "kotlin.sequences.Sequence"));
+
+      assertFalse(inspector.clazz(
+          "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1").isPresent());
+    });
+  }
+
   private Set<String> collectAccessedLambdaTypes(
       Predicate<DexType> isLambdaType, ClassSubject clazz, String methodName, String... params) {
     assertNotNull(clazz);
@@ -97,14 +155,13 @@
         .collect(Collectors.toSet());
   }
 
-  @Override
   protected void runTest(String folder, String mainClass,
-      AndroidAppInspector inspector) throws Exception {
+      boolean enabled, AndroidAppInspector inspector) throws Exception {
     runTest(
         folder, mainClass, null,
         options -> {
           options.enableInlining = true;
-          options.enableClassInlining = true;
+          options.enableClassInlining = enabled;
           options.enableLambdaMerging = false;
         }, inspector);
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
index ee9065f..08d43ae 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -14,12 +14,14 @@
 import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Lists;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
 import org.junit.Test;
 
 public class KotlinLambdaMergingTest extends AbstractR8KotlinTestBase {
@@ -29,6 +31,7 @@
       "-keepattributes InnerClasses,EnclosingMethod\n";
   private static final String KEEP_SIGNATURE_INNER_ENCLOSING =
       "-keepattributes Signature,InnerClasses,EnclosingMethod\n";
+  private Consumer<InternalOptions> optionsModifier = opts -> opts.enableClassInlining = false;
 
   abstract static class LambdaOrGroup {
     abstract boolean match(DexClass clazz);
@@ -252,7 +255,7 @@
   @Test
   public void testTrivialKs() throws Exception {
     final String mainClassName = "lambdas_kstyle_trivial.MainKt";
-    runTest("lambdas_kstyle_trivial", mainClassName, (app) -> {
+    runTest("lambdas_kstyle_trivial", mainClassName, optionsModifier, (app) -> {
       Verifier verifier = new Verifier(app);
       String pkg = "lambdas_kstyle_trivial";
 
@@ -293,7 +296,7 @@
   @Test
   public void testCapturesKs() throws Exception {
     final String mainClassName = "lambdas_kstyle_captures.MainKt";
-    runTest("lambdas_kstyle_captures", mainClassName, (app) -> {
+    runTest("lambdas_kstyle_captures", mainClassName, optionsModifier, (app) -> {
       Verifier verifier = new Verifier(app);
       String pkg = "lambdas_kstyle_captures";
       String grpPkg = allowAccessModification ? "" : pkg;
@@ -318,7 +321,7 @@
   @Test
   public void testGenericsNoSignatureKs() throws Exception {
     final String mainClassName = "lambdas_kstyle_generics.MainKt";
-    runTest("lambdas_kstyle_generics", mainClassName, (app) -> {
+    runTest("lambdas_kstyle_generics", mainClassName, optionsModifier, (app) -> {
       Verifier verifier = new Verifier(app);
       String pkg = "lambdas_kstyle_generics";
       String grpPkg = allowAccessModification ? "" : pkg;
@@ -339,55 +342,57 @@
   @Test
   public void testInnerClassesAndEnclosingMethodsKs() throws Exception {
     final String mainClassName = "lambdas_kstyle_generics.MainKt";
-    runTest("lambdas_kstyle_generics", mainClassName, KEEP_INNER_AND_ENCLOSING, (app) -> {
-      Verifier verifier = new Verifier(app);
-      String pkg = "lambdas_kstyle_generics";
-      String grpPkg = allowAccessModification ? "" : pkg;
+    runTest("lambdas_kstyle_generics", mainClassName,
+        KEEP_INNER_AND_ENCLOSING, optionsModifier, (app) -> {
+          Verifier verifier = new Verifier(app);
+          String pkg = "lambdas_kstyle_generics";
+          String grpPkg = allowAccessModification ? "" : pkg;
 
-      verifier.assertLambdaGroups(
-          kstyle(grpPkg, 1, 3), // Group for Any
-          kstyle(grpPkg, "L", 1), // Group for Beta   // First
-          kstyle(grpPkg, "L", 1), // Group for Beta   // Second
-          kstyle(grpPkg, "LS", 1), // Group for Gamma // First
-          kstyle(grpPkg, "LS", 1), // Group for Gamma // Second
-          kstyle(grpPkg, 1, 2)  // Group for int
-      );
+          verifier.assertLambdaGroups(
+              kstyle(grpPkg, 1, 3), // Group for Any
+              kstyle(grpPkg, "L", 1), // Group for Beta   // First
+              kstyle(grpPkg, "L", 1), // Group for Beta   // Second
+              kstyle(grpPkg, "LS", 1), // Group for Gamma // First
+              kstyle(grpPkg, "LS", 1), // Group for Gamma // Second
+              kstyle(grpPkg, 1, 2)  // Group for int
+          );
 
-      verifier.assertLambdas(
-          new Lambda(pkg, "MainKt$main$4", 1)
-      );
-    });
+          verifier.assertLambdas(
+              new Lambda(pkg, "MainKt$main$4", 1)
+          );
+        });
   }
 
   @Test
   public void testGenericsSignatureInnerEnclosingKs() throws Exception {
     final String mainClassName = "lambdas_kstyle_generics.MainKt";
-    runTest("lambdas_kstyle_generics", mainClassName, KEEP_SIGNATURE_INNER_ENCLOSING, (app) -> {
-      Verifier verifier = new Verifier(app);
-      String pkg = "lambdas_kstyle_generics";
-      String grpPkg = allowAccessModification ? "" : pkg;
+    runTest("lambdas_kstyle_generics", mainClassName,
+        KEEP_SIGNATURE_INNER_ENCLOSING, optionsModifier, (app) -> {
+          Verifier verifier = new Verifier(app);
+          String pkg = "lambdas_kstyle_generics";
+          String grpPkg = allowAccessModification ? "" : pkg;
 
-      verifier.assertLambdaGroups(
-          kstyle(grpPkg, 1, 3), // Group for Any
-          kstyle(grpPkg, "L", 1), // Group for Beta in First
-          kstyle(grpPkg, "L", 1), // Group for Beta in Second
-          kstyle(grpPkg, "LS", 1), // Group for Gamma<String> in First
-          kstyle(grpPkg, "LS", 1), // Group for Gamma<Integer> in First
-          kstyle(grpPkg, "LS", 1), // Group for Gamma<String> in Second
-          kstyle(grpPkg, "LS", 1), // Group for Gamma<Integer> in Second
-          kstyle(grpPkg, 1, 2)  // Group for int
-      );
+          verifier.assertLambdaGroups(
+              kstyle(grpPkg, 1, 3), // Group for Any
+              kstyle(grpPkg, "L", 1), // Group for Beta in First
+              kstyle(grpPkg, "L", 1), // Group for Beta in Second
+              kstyle(grpPkg, "LS", 1), // Group for Gamma<String> in First
+              kstyle(grpPkg, "LS", 1), // Group for Gamma<Integer> in First
+              kstyle(grpPkg, "LS", 1), // Group for Gamma<String> in Second
+              kstyle(grpPkg, "LS", 1), // Group for Gamma<Integer> in Second
+              kstyle(grpPkg, 1, 2)  // Group for int
+          );
 
-      verifier.assertLambdas(
-          new Lambda(pkg, "MainKt$main$4", 1)
-      );
-    });
+          verifier.assertLambdas(
+              new Lambda(pkg, "MainKt$main$4", 1)
+          );
+        });
   }
 
   @Test
   public void testTrivialJs() throws Exception {
     final String mainClassName = "lambdas_jstyle_trivial.MainKt";
-    runTest("lambdas_jstyle_trivial", mainClassName, (app) -> {
+    runTest("lambdas_jstyle_trivial", mainClassName, optionsModifier, (app) -> {
       Verifier verifier = new Verifier(app);
       String pkg = "lambdas_jstyle_trivial";
       String grp = allowAccessModification ? "" : pkg;
@@ -435,7 +440,7 @@
   @Test
   public void testSingleton() throws Exception {
     final String mainClassName = "lambdas_singleton.MainKt";
-    runTest("lambdas_singleton", mainClassName, (app) -> {
+    runTest("lambdas_singleton", mainClassName, optionsModifier, (app) -> {
       Verifier verifier = new Verifier(app);
       String pkg = "lambdas_singleton";
       String grp = allowAccessModification ? "" : pkg;
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 61b1454..d8615e0 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,8 +155,10 @@
     runTest(
         mainClass,
         ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
-        // Prevent SuperClass from being merged into SubClass.
-        keepMainProguardConfiguration(mainClass, ImmutableList.of("-keep class **.SuperClass")),
+        // Prevent SuperClass from being merged into SubClass and keep both
+        // SuperClass and SubClass after instantiation is inlined.
+        keepMainProguardConfiguration(mainClass, ImmutableList.of(
+            "-keep class **.SuperClass", "-keep class **.SubClass")),
         this::checkAllClassesPresentWithDefaultConstructor);
   }
 
diff --git a/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt b/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt
new file mode 100644
index 0000000..136f72d
--- /dev/null
+++ b/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt
@@ -0,0 +1,49 @@
+// 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_k_style
+
+private var COUNT = 0
+
+fun next() = "${COUNT++}".padStart(3, '0')
+
+fun main(args: Array<String>) {
+    testKotlinSequencesStateless(produceSequence(10))
+    testKotlinSequencesStateful(5, 2, produceSequence(10))
+}
+
+data class Record(val foo: String, val good: Boolean)
+
+@Synchronized
+fun testKotlinSequencesStateless(strings: Sequence<String>) {
+    useRecord()
+    // Stateless k-style lambda
+    strings.map { Record(it, false) }.forEach { println(it) }
+}
+
+@Synchronized
+fun testKotlinSequencesStateful(a: Int, b: Int, strings: Sequence<String>) {
+    useRecord()
+    // Big stateful k-style lambda
+    val capture = next()
+    strings.map {
+        val x = it.toInt()
+        val y = a + x
+        val z = capture.toInt() + b
+        println("logging $x/$y/$z") // Intentional
+        Record(it, y % z == 0)
+    }.forEach {
+        println(it)
+    }
+}
+
+private fun produceSequence(size: Int): Sequence<String> {
+    var count = size
+    return generateSequence { if (count-- > 0) next() else null }
+}
+
+// Need this to make sure testKotlinSequenceXXX is not processed
+// concurrently with invoke() on lambdas.
+fun useRecord() = useRecord2()
+fun useRecord2() = Record("", true)