Version 1.3.30

Merge: Honor force inlining in the default inlining oracle
CL: https://r8-review.googlesource.com/c/r8/+/29681

Merge: Reproduce j-style lambda merger with self-recursion.
CL: https://r8-review.googlesource.com/c/r8/+/29481

Bug: 118067353, 74189420
Change-Id: I3adcfd810c089910511c3f3bcc331aa72d29437c
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 7c7e830..752bd26 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.29";
+  public static final String LABEL = "1.3.30";
 
   private Version() {
   }
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 e655c0e..d8d3823 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
@@ -17,6 +17,7 @@
 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.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.utils.InternalOptions;
@@ -227,6 +228,14 @@
       Code code = candidate.getCode();
       int instructionLimit = computeInstructionLimit(invoke, candidate);
       if (!code.estimatedSizeForInliningAtMost(instructionLimit)) {
+        if (info != null) {
+          info.exclude(
+              invoke,
+              "instruction limit exceeds: "
+                  + code.estimatedSizeForInlining()
+                  + " <= "
+                  + instructionLimit);
+        }
         return false;
       }
     }
@@ -357,13 +366,59 @@
   }
 
   @Override
-  public boolean exceededAllowance() {
+  public boolean stillHasBudget() {
+    return instructionAllowance > 0;
+  }
+
+  @Override
+  public boolean willExceedBudget(InlineeWithReason inlinee, BasicBlock block) {
+    if (inlinee.reason.mustBeInlined()) {
+      return false;
+    }
+
+    if (block.hasCatchHandlers() && inlinee.reason != Reason.FORCE) {
+      // Inlining could lead to an explosion of move-exception and resolution moves. As an
+      // example, consider the following piece of code.
+      //   try {
+      //     ...
+      //     foo();
+      //     ...
+      //   } catch (A e) { ... }
+      //   } catch (B e) { ... }
+      //   } catch (C e) { ... }
+      //
+      // The generated code for the above example will have a move-exception instruction
+      // for each of the three catch handlers. Furthermore, the blocks with these move-
+      // exception instructions may require a number of resolution moves to setup the
+      // register state for the catch handlers. When inlining foo(), the generated code
+      // will have a move-exception instruction *for each of the instructions in foo()
+      // that can throw*, along with the necessary resolution moves for each exception-
+      // edge. We therefore abort inlining if the number of exception-edges explode.
+      int numberOfThrowingInstructionsInInlinee = 0;
+      for (BasicBlock inlineeBlock : inlinee.code.blocks) {
+        numberOfThrowingInstructionsInInlinee += inlineeBlock.numberOfThrowingInstructions();
+      }
+      // Estimate the number of "control flow resolution blocks", where we will insert a
+      // move-exception instruction (if needed), along with all the resolution moves that
+      // will be needed to setup the register state for the catch handler.
+      int estimatedNumberOfControlFlowResolutionBlocks =
+          numberOfThrowingInstructionsInInlinee * block.numberOfCatchHandlers();
+      // Abort if inlining could lead to an explosion in the number of control flow
+      // resolution blocks that setup the register state before the actual catch handler.
+      if (estimatedNumberOfControlFlowResolutionBlocks
+          >= options.inliningControlFlowResolutionBlocksThreshold) {
+        return true;
+      }
+    }
+
+    // Allow the first method to consume more than the allowance to be inlined.
     return instructionAllowance < 0;
   }
 
   @Override
-  public void markInlined(IRCode inlinee) {
-    instructionAllowance -= inliner.numberOfInstructions(inlinee);
+  public void markInlined(InlineeWithReason inlinee) {
+    // TODO(118734615): All inlining use from the budget - should that only be SIMPLE?
+    instructionAllowance -= inliner.numberOfInstructions(inlinee.code);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index ca82166..6890dd8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.code.InvokePolymorphic;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
+import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import java.util.ListIterator;
 import java.util.Map;
@@ -78,15 +79,19 @@
       IRCode inlinee, ListIterator<BasicBlock> blockIterator, BasicBlock block) {}
 
   @Override
-  public boolean exceededAllowance() {
-    return false; // Never exceeds allowance.
+  public boolean stillHasBudget() {
+    return true; // Unlimited allowance.
   }
 
   @Override
-  public void markInlined(IRCode inlinee) {
+  public boolean willExceedBudget(InlineeWithReason inlinee, BasicBlock block) {
+    return false; // Unlimited allowance.
   }
 
   @Override
+  public void markInlined(InlineeWithReason inlinee) {}
+
+  @Override
   public DexType getReceiverTypeIfKnown(InvokeMethod invoke) {
     assert invoke.isInvokeMethodWithReceiver();
     Inliner.InliningInfo info = invokesToInline.get(invoke.asInvokeMethodWithReceiver());
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 fcb7293..17ba5d7 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
@@ -42,10 +42,6 @@
 import java.util.stream.Collectors;
 
 public class Inliner {
-  private static final int INITIAL_INLINING_INSTRUCTION_ALLOWANCE = 1500;
-
-  // Threshold found empirically by testing on GMS Core.
-  private static final int CONTROL_FLOW_RESOLUTION_BLOCKS_THRESHOLD = 15;
 
   private final IRConverter converter;
   protected final AppInfoWithLiveness appInfo;
@@ -391,7 +387,12 @@
     ALWAYS,        // Inlinee is marked for inlining due to alwaysinline directive.
     SINGLE_CALLER, // Inlinee has precisely one caller.
     DUAL_CALLER,   // Inlinee has precisely two callers.
-    SIMPLE,        // Inlinee has simple code suitable for inlining.
+    SIMPLE;        // Inlinee has simple code suitable for inlining.
+
+    public boolean mustBeInlined() {
+      // TODO(118734615): Include SINGLE_CALLER and DUAL_CALLER here as well?
+      return this == FORCE || this == ALWAYS;
+    }
   }
 
   static public class InlineAction {
@@ -406,11 +407,7 @@
       this.reason = reason;
     }
 
-    boolean ignoreInstructionBudget() {
-      return reason != Reason.SIMPLE;
-    }
-
-    public IRCode buildInliningIR(
+    public InlineeWithReason buildInliningIR(
         ValueNumberGenerator generator,
         AppInfoWithSubtyping appInfo,
         GraphLense graphLense,
@@ -423,7 +420,18 @@
       if (!target.isProcessed()) {
         new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
       }
-      return code;
+      return new InlineeWithReason(code, reason);
+    }
+  }
+
+  public static class InlineeWithReason {
+
+    final Reason reason;
+    final IRCode code;
+
+    InlineeWithReason(IRCode code, Reason reason) {
+      this.code = code;
+      this.reason = reason;
     }
   }
 
@@ -538,11 +546,14 @@
       Predicate<DexEncodedMethod> isProcessedConcurrently,
       CallSiteInformation callSiteInformation) {
 
-    DefaultInliningOracle oracle = createDefaultOracle(
-        method, code,
-        isProcessedConcurrently, callSiteInformation,
-        options.inliningInstructionLimit,
-        INITIAL_INLINING_INSTRUCTION_ALLOWANCE - numberOfInstructions(code));
+    DefaultInliningOracle oracle =
+        createDefaultOracle(
+            method,
+            code,
+            isProcessedConcurrently,
+            callSiteInformation,
+            options.inliningInstructionLimit,
+            options.inliningInstructionAllowance - numberOfInstructions(code));
 
     performInliningImpl(oracle, oracle, method, code);
   }
@@ -568,24 +579,23 @@
 
   private void performInliningImpl(
       InliningStrategy strategy, InliningOracle oracle, DexEncodedMethod method, IRCode code) {
-    if (strategy.exceededAllowance()) {
-      return;
-    }
-
     List<BasicBlock> blocksToRemove = new ArrayList<>();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
-    while (blockIterator.hasNext() && !strategy.exceededAllowance()) {
+    while (blockIterator.hasNext()) {
       BasicBlock block = blockIterator.next();
       if (blocksToRemove.contains(block)) {
         continue;
       }
       InstructionListIterator iterator = block.listIterator();
-      while (iterator.hasNext() && !strategy.exceededAllowance()) {
+      while (iterator.hasNext()) {
         Instruction current = iterator.next();
         if (current.isInvokeMethod()) {
           InvokeMethod invoke = current.asInvokeMethod();
           InlineAction result = invoke.computeInlining(oracle, method.method.holder);
           if (result != null) {
+            if (!(strategy.stillHasBudget() || result.reason.mustBeInlined())) {
+              continue;
+            }
             DexEncodedMethod target = result.target;
             Position invokePosition = invoke.getPosition();
             if (invokePosition.method == null) {
@@ -596,53 +606,26 @@
                 || invokePosition.getOutermostCaller().method
                     == converter.getGraphLense().getOriginalMethodSignature(method.method);
 
-            IRCode inlinee =
-                result.buildInliningIR(code.valueNumberGenerator,
-                    appInfo, converter.getGraphLense(), options, invokePosition);
+            InlineeWithReason inlinee =
+                result.buildInliningIR(
+                    code.valueNumberGenerator,
+                    appInfo,
+                    converter.getGraphLense(),
+                    options,
+                    invokePosition);
+
             if (inlinee != null) {
-              if (block.hasCatchHandlers() && !(oracle instanceof ForcedInliningOracle)) {
-                // Inlining could lead to an explosion of move-exception and resolution moves. As an
-                // example, consider the following piece of code.
-                //   try {
-                //     ...
-                //     foo();
-                //     ...
-                //   } catch (A e) { ... }
-                //   } catch (B e) { ... }
-                //   } catch (C e) { ... }
-                //
-                // The generated code for the above example will have a move-exception instruction
-                // for each of the three catch handlers. Furthermore, the blocks with these move-
-                // exception instructions may require a number of resolution moves to setup the
-                // register state for the catch handlers. When inlining foo(), the generated code
-                // will have a move-exception instruction *for each of the instructions in foo()
-                // that can throw*, along with the necessary resolution moves for each exception-
-                // edge. We therefore abort inlining if the number of exception-edges explode.
-                int numberOfThrowingInstructionsInInlinee = 0;
-                for (BasicBlock inlineeBlock : inlinee.blocks) {
-                  numberOfThrowingInstructionsInInlinee +=
-                      inlineeBlock.numberOfThrowingInstructions();
-                }
-                // Estimate the number of "control flow resolution blocks", where we will insert a
-                // move-exception instruction (if needed), along with all the resolution moves that
-                // will be needed to setup the register state for the catch handler.
-                int estimatedNumberOfControlFlowResolutionBlocks =
-                    numberOfThrowingInstructionsInInlinee * block.numberOfCatchHandlers();
-                // Abort if inlining could lead to an explosion in the number of control flow
-                // resolution blocks that setup the register state before the actual catch handler.
-                if (estimatedNumberOfControlFlowResolutionBlocks
-                    >= CONTROL_FLOW_RESOLUTION_BLOCKS_THRESHOLD) {
-                  continue;
-                }
+              if (strategy.willExceedBudget(inlinee, block)) {
+                continue;
               }
 
               // If this code did not go through the full pipeline, apply inlining to make sure
               // that force inline targets get processed.
-              strategy.ensureMethodProcessed(target, inlinee);
+              strategy.ensureMethodProcessed(target, inlinee.code);
 
               // Make sure constructor inlining is legal.
               assert !target.isClassInitializer();
-              if (!strategy.isValidTarget(invoke, target, inlinee)) {
+              if (!strategy.isValidTarget(invoke, target, inlinee.code)) {
                 continue;
               }
               DexType downcast = getDowncastTypeIfNeeded(strategy, invoke, target);
@@ -650,20 +633,18 @@
               // Back up before the invoke instruction.
               iterator.previous();
               strategy.markInlined(inlinee);
-              if (!strategy.exceededAllowance() || result.ignoreInstructionBudget()) {
-                iterator.inlineInvoke(
-                    appInfo, code, inlinee, blockIterator, blocksToRemove, downcast);
-                strategy.updateTypeInformationIfNeeded(inlinee, blockIterator, block);
+              iterator.inlineInvoke(
+                  appInfo, code, inlinee.code, blockIterator, blocksToRemove, downcast);
+              strategy.updateTypeInformationIfNeeded(inlinee.code, blockIterator, block);
 
-                // If we inlined the invoke from a bridge method, it is no longer a bridge method.
-                if (method.accessFlags.isBridge()) {
-                  method.accessFlags.unsetSynthetic();
-                  method.accessFlags.unsetBridge();
-                }
-
-                method.copyMetadataFromInlinee(target);
-                code.copyMetadataFromInlinee(inlinee);
+              // If we inlined the invoke from a bridge method, it is no longer a bridge method.
+              if (method.accessFlags.isBridge()) {
+                method.accessFlags.unsetSynthetic();
+                method.accessFlags.unsetBridge();
               }
+
+              method.copyMetadataFromInlinee(target);
+              code.copyMetadataFromInlinee(inlinee.code);
             }
           }
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
index 90554f6..7dc2b62 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -9,12 +9,23 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import java.util.ListIterator;
 
 interface InliningStrategy {
-  boolean exceededAllowance();
 
-  void markInlined(IRCode inlinee);
+  /** Return true if there is still budget for inlining into this method. */
+  boolean stillHasBudget();
+
+  /**
+   * Check if the inlinee will exceed the the budget for inlining size into current method.
+   *
+   * <p>Return true if the strategy will *not* allow inlining.
+   */
+  boolean willExceedBudget(InlineeWithReason inlinee, BasicBlock block);
+
+  /** Inform the strategy that the inlinee has been inlined. */
+  void markInlined(InlineeWithReason inlinee);
 
   void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee);
 
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 332b0d3..2612d0b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -106,7 +106,14 @@
   public boolean enableClassInlining = true;
   public boolean enableClassStaticizer = true;
   public int classInliningInstructionLimit = 50;
+  // This defines the limit of instructions in the inlinee
   public int inliningInstructionLimit = 5;
+  // This defines how many instructions of inlinees we can inlinee overall.
+  public int inliningInstructionAllowance = 1500;
+  // Maximum number of control flow resolution blocks that setup the register state before
+  // the actual catch handler allowed when inlining. Threshold found empirically by testing on
+  // GMS Core.
+  public int inliningControlFlowResolutionBlocksThreshold = 15;
   public boolean enableSwitchMapRemoval = true;
   public final OutlineOptions outline = new OutlineOptions();
   public boolean enableValuePropagation = true;
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 060d87b..b76e947 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -11,6 +11,7 @@
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
@@ -19,6 +20,7 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
@@ -239,7 +241,12 @@
     // Build with R8
     AndroidApp.Builder builder = AndroidApp.builder();
     builder.addProgramFiles(classpath);
-    AndroidApp app = compileWithR8(builder.build(), proguardRules, optionsConsumer);
+    R8Command.Builder commandBuilder =
+        ToolHelper.prepareR8CommandBuilder(builder.build(), emptyConsumer(Backend.DEX))
+            .addLibraryFiles(runtimeJar(Backend.DEX))
+            .addProguardConfiguration(ImmutableList.of(proguardRules), Origin.unknown());
+    ToolHelper.allowTestProguardOptions(commandBuilder);
+    AndroidApp app = ToolHelper.runR8(commandBuilder.build(), optionsConsumer);
 
     // Materialize file for execution.
     Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
@@ -258,7 +265,9 @@
     }
     assertEquals("JVM and ART output differ", javaResult.stdout, artOutput);
 
-    inspector.inspectApp(app);
+    if (inspector != null) {
+      inspector.inspectApp(app);
+    }
   }
 
   protected void checkClassExistsInInput(String className) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithFailedInliningTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithFailedInliningTest.java
new file mode 100644
index 0000000..f17a729
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithFailedInliningTest.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.function.Consumer;
+import org.junit.Test;
+
+public class KotlinLambdaMergingWithFailedInliningTest extends AbstractR8KotlinTestBase {
+  private Consumer<InternalOptions> optionsModifier =
+      o -> {
+        o.enableTreeShaking = true;
+        o.enableMinification = true;
+        o.enableInlining = true;
+        o.enableClassInlining = true;
+        o.enableLambdaMerging = true;
+        o.inliningInstructionAllowance = 3;
+      };
+
+  @Test
+  public void testJStyleRunnable() throws Exception {
+    final String mainClassName = "lambdas_jstyle_runnable.MainKt";
+    runTest("lambdas_jstyle_runnable", mainClassName, optionsModifier, null);
+  }
+}
diff --git a/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/Implementer1.kt b/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/Implementer1.kt
new file mode 100644
index 0000000..fb88996
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/Implementer1.kt
@@ -0,0 +1,19 @@
+// 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 lambdas_jstyle_runnable
+
+private val liveChecker = object : Runnable {
+  override fun run() {
+    if (!Thread.currentThread().isInterrupted) {
+      publish("liveChecker")
+    }
+  }
+}
+
+class Implementer1 {
+  fun getRunnable() : Runnable {
+    return liveChecker
+  }
+}
diff --git a/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/Implementer2.kt b/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/Implementer2.kt
new file mode 100644
index 0000000..034caca
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/Implementer2.kt
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package lambdas_jstyle_runnable
+
+private val statusRunnable = Runnable {
+  if (!Thread.currentThread().isInterrupted) {
+    publish("statusRunnable")
+  }
+}
+
+class Implementer2 {
+  fun getRunnable() : Runnable {
+    return statusRunnable
+  }
+}
diff --git a/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/RunnableRunner.java b/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/RunnableRunner.java
new file mode 100644
index 0000000..6ccdf16
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/RunnableRunner.java
@@ -0,0 +1,52 @@
+// 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 lambdas_jstyle_runnable;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class RunnableRunner {
+
+  private ThreadPoolExecutor executor;
+  private List<Future> futures;
+
+  RunnableRunner() {
+    executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
+    futures = new ArrayList<Future>();
+  }
+
+  void submit(Runnable runnable) {
+    futures.add(executor.submit(runnable));
+  }
+
+  int size() {
+    return executor.getActiveCount();
+  }
+
+  void waitFutures() {
+    Iterator<Future> it = futures.iterator();
+    try {
+      while (it.hasNext()) {
+        it.next().get(1, TimeUnit.MILLISECONDS);
+      }
+    } catch (Exception e) {
+      // Ignore for testing.
+    } finally {
+      while (it.hasNext()) {
+        try {
+          it.next().get();
+        } catch (Throwable t) {
+          // Ignore too.
+        }
+      }
+      executor.shutdownNow();
+    }
+  }
+}
diff --git a/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/inner/Implementer3.kt b/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/inner/Implementer3.kt
new file mode 100644
index 0000000..b65cff6
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/inner/Implementer3.kt
@@ -0,0 +1,22 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package lambdas_jstyle_runnable.inner
+
+import lambdas_jstyle_runnable.publish
+
+private val innerRunnable = Runnable {
+  if (!Thread.currentThread().isInterrupted) {
+    publish("innerRunnable")
+  }
+}
+
+open class Implementer3(private val priority : Int) : Runnable {
+  override fun run() {
+    Thread.currentThread().priority = priority
+    innerRunnable.run()
+  }
+}
+
+class Delegator3(priority: Int) : Implementer3(priority)
\ No newline at end of file
diff --git a/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/inner/Implementer4.kt b/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/inner/Implementer4.kt
new file mode 100644
index 0000000..6e59424
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/inner/Implementer4.kt
@@ -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 lambdas_jstyle_runnable.inner
+
+import lambdas_jstyle_runnable.publish
+
+private val reachableChecker : Runnable by lazy {
+  Runnable {
+    if (!Thread.currentThread().isInterrupted) {
+      publish("reachableChecker")
+    }
+  }
+}
+
+class Implementer4 {
+  fun getRunnable() : Runnable {
+    return reachableChecker
+  }
+}
\ No newline at end of file
diff --git a/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/main.kt b/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/main.kt
new file mode 100644
index 0000000..ef5d5cf
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_jstyle_runnable/main.kt
@@ -0,0 +1,29 @@
+// 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 lambdas_jstyle_runnable
+
+import lambdas_jstyle_runnable.inner.Delegator3
+import lambdas_jstyle_runnable.inner.Implementer4
+import java.util.ArrayDeque
+import java.util.Queue
+
+private val queue : Queue<String> = ArrayDeque<String>()
+
+fun publish(message: String) {
+  queue.add(message)
+}
+
+fun main(args: Array<String>) {
+  assert(queue.isEmpty())
+  val runner = RunnableRunner()
+  runner.submit(Implementer1().getRunnable())
+  runner.submit(Implementer2().getRunnable())
+  runner.submit(Delegator3(Thread.currentThread().priority))
+  if (runner.size() > 2) {
+    runner.submit(Implementer4().getRunnable())
+  }
+  runner.waitFutures()
+  assert(queue.size == 3)
+}