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)
+}