diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SharedVariablesLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SharedVariablesLowering.kt index 1f5d54c6496..d0bb51e5a27 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SharedVariablesLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SharedVariablesLowering.kt @@ -27,7 +27,6 @@ import org.jetbrains.kotlin.ir.symbols.IrValueSymbol import org.jetbrains.kotlin.ir.symbols.IrVariableSymbol import org.jetbrains.kotlin.ir.util.dump import org.jetbrains.kotlin.ir.visitors.* -import java.util.* val sharedVariablesPhase = makeIrFilePhase( ::SharedVariablesLowering, @@ -77,7 +76,8 @@ class SharedVariablesLowering(val context: BackendContext) : FileLoweringPass { private fun collectSharedVariables() { irDeclaration.accept(object : IrElementVisitor { - val relevantVars = mutableSetOf() + val relevantVars = HashSet() + val relevantVals = HashSet() override fun visitElement(element: IrElement, data: IrDeclarationParent?) { element.acceptChildren(this, data) @@ -102,6 +102,12 @@ class SharedVariablesLowering(val context: BackendContext) : FileLoweringPass { if (declaration.isVar) { relevantVars.add(declaration) + } else if (declaration.initializer == null) { + // A val-variable can be initialized from another container (and thus can require shared variable transformation) + // in case that container is a lambda with a corresponding contract, e.g. with invocation kind EXACTLY_ONCE. + // Here, we collect all val-variables without immediate initializer to relevantVals, and later we copy only those + // variables which are initialized in a foreign container, to sharedVariables. + relevantVals.add(declaration) } } @@ -113,6 +119,15 @@ class SharedVariablesLowering(val context: BackendContext) : FileLoweringPass { sharedVariables.add(value) } } + + override fun visitSetVariable(expression: IrSetVariable, data: IrDeclarationParent?) { + super.visitSetVariable(expression, data) + + val variable = expression.symbol.owner + if (variable.initializer == null && variable.parent != data && variable in relevantVals) { + sharedVariables.add(variable) + } + } }, irDeclaration as? IrDeclarationParent ?: irDeclaration.parent) } @@ -160,4 +175,4 @@ class SharedVariablesLowering(val context: BackendContext) : FileLoweringPass { }) } } -} \ No newline at end of file +} diff --git a/compiler/testData/codegen/box/contracts/exactlyOnceNotInline.kt b/compiler/testData/codegen/box/contracts/exactlyOnceNotInline.kt index 2113228b7b5..ed2f2f30864 100644 --- a/compiler/testData/codegen/box/contracts/exactlyOnceNotInline.kt +++ b/compiler/testData/codegen/box/contracts/exactlyOnceNotInline.kt @@ -1,6 +1,6 @@ // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts -// IGNORE_BACKEND: JVM_IR, NATIVE, JS_IR +// IGNORE_BACKEND: NATIVE import kotlin.contracts.* @@ -18,4 +18,4 @@ fun box(): String { x = 1L } return if (x != 1L) "FAIL" else "OK" -} \ No newline at end of file +} diff --git a/compiler/testData/codegen/boxInline/contracts/cfgDependendValInitialization.kt b/compiler/testData/codegen/boxInline/contracts/cfgDependendValInitialization.kt index eb161c0cd6c..eb2a4673284 100644 --- a/compiler/testData/codegen/boxInline/contracts/cfgDependendValInitialization.kt +++ b/compiler/testData/codegen/boxInline/contracts/cfgDependendValInitialization.kt @@ -1,9 +1,9 @@ // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts -// IGNORE_BACKEND: JVM_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR // IGNORE_BACKEND: NATIVE + // FILE: 1.kt + package test import kotlin.contracts.* @@ -16,8 +16,8 @@ public inline fun myrun(block: () -> R): R { return block() } - // FILE: 2.kt + import test.* fun test(b: Boolean): Int { @@ -34,8 +34,7 @@ fun test(b: Boolean): Int { } fun box(): String { - if (test(true) != 1) return "Fail 1" if (test(false) != -1) return "Fail 2" return "OK" -} \ No newline at end of file +} diff --git a/compiler/testData/codegen/boxInline/contracts/complexInitializer.kt b/compiler/testData/codegen/boxInline/contracts/complexInitializer.kt index 6be08a70950..fad72c31a30 100644 --- a/compiler/testData/codegen/boxInline/contracts/complexInitializer.kt +++ b/compiler/testData/codegen/boxInline/contracts/complexInitializer.kt @@ -1,9 +1,9 @@ // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts -// IGNORE_BACKEND: JVM_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR // IGNORE_BACKEND: NATIVE + // FILE: 1.kt + package test import kotlin.contracts.* @@ -29,4 +29,4 @@ fun box(): String { x = if (cond()) test("OK") else test("fail") } return x -} \ No newline at end of file +} diff --git a/compiler/testData/codegen/boxInline/contracts/complexInitializerWithStackTransformation.kt b/compiler/testData/codegen/boxInline/contracts/complexInitializerWithStackTransformation.kt index da0517e5725..c5c809c9c49 100644 --- a/compiler/testData/codegen/boxInline/contracts/complexInitializerWithStackTransformation.kt +++ b/compiler/testData/codegen/boxInline/contracts/complexInitializerWithStackTransformation.kt @@ -1,9 +1,9 @@ // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts -// IGNORE_BACKEND: JVM_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR // IGNORE_BACKEND: NATIVE + // FILE: 1.kt + package test import kotlin.contracts.* @@ -28,4 +28,4 @@ fun box(): String { x = try { test("OK") } catch (e: Exception) { test("fail") } } return x -} \ No newline at end of file +} diff --git a/compiler/testData/codegen/boxInline/contracts/definiteLongValInitialization.kt b/compiler/testData/codegen/boxInline/contracts/definiteLongValInitialization.kt index 05e47954a10..cb74e451874 100644 --- a/compiler/testData/codegen/boxInline/contracts/definiteLongValInitialization.kt +++ b/compiler/testData/codegen/boxInline/contracts/definiteLongValInitialization.kt @@ -1,9 +1,9 @@ // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts -// IGNORE_BACKEND: JVM_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR // IGNORE_BACKEND: NATIVE + // FILE: 1.kt + package test import kotlin.contracts.* @@ -17,6 +17,7 @@ public inline fun myrun(block: () -> R): R { } // FILE: 2.kt + import test.* fun box(): String { @@ -25,4 +26,4 @@ fun box(): String { x = 42L } return if (x.inc() == 43L) "OK" else "Fail: ${x.inc()}" -} \ No newline at end of file +} diff --git a/compiler/testData/codegen/boxInline/contracts/definiteNestedValInitialization.kt b/compiler/testData/codegen/boxInline/contracts/definiteNestedValInitialization.kt index 1d721bb3100..92a797141a8 100644 --- a/compiler/testData/codegen/boxInline/contracts/definiteNestedValInitialization.kt +++ b/compiler/testData/codegen/boxInline/contracts/definiteNestedValInitialization.kt @@ -1,9 +1,9 @@ // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts -// IGNORE_BACKEND: JVM_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR // IGNORE_BACKEND: NATIVE + // FILE: 1.kt + package test import kotlin.contracts.* @@ -16,8 +16,8 @@ public inline fun myrun(block: () -> R): R { return block() } - // FILE: 2.kt + import test.* fun box(): String { @@ -28,4 +28,4 @@ fun box(): String { } } return if (x.inc() == 43) "OK" else "Fail: ${x.inc()}" -} \ No newline at end of file +} diff --git a/compiler/testData/codegen/boxInline/contracts/definiteValInitInInitializer.kt b/compiler/testData/codegen/boxInline/contracts/definiteValInitInInitializer.kt index f81ae90b17d..dfc0c8e8b0e 100644 --- a/compiler/testData/codegen/boxInline/contracts/definiteValInitInInitializer.kt +++ b/compiler/testData/codegen/boxInline/contracts/definiteValInitInInitializer.kt @@ -1,10 +1,10 @@ // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts -// IGNORE_BACKEND: JVM_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR // IGNORE_BACKEND: NATIVE // NO_CHECK_LAMBDA_INLINING + // FILE: 1.kt + package test import kotlin.contracts.* @@ -17,8 +17,8 @@ public inline fun myrun(block: () -> R): R { return block() } - // FILE: 2.kt + import test.* class A { @@ -36,4 +36,4 @@ class A { } } -fun box() = A().x \ No newline at end of file +fun box() = A().x diff --git a/compiler/testData/codegen/boxInline/contracts/definiteValInitialization.kt b/compiler/testData/codegen/boxInline/contracts/definiteValInitialization.kt index 7d51bea76ed..c626ec81cd9 100644 --- a/compiler/testData/codegen/boxInline/contracts/definiteValInitialization.kt +++ b/compiler/testData/codegen/boxInline/contracts/definiteValInitialization.kt @@ -1,9 +1,9 @@ // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts -// IGNORE_BACKEND: JVM_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR // IGNORE_BACKEND: NATIVE + // FILE: 1.kt + package test import kotlin.contracts.* @@ -16,8 +16,8 @@ public inline fun myrun(block: () -> R): R { return block() } - // FILE: 2.kt + import test.* fun box(): String { @@ -26,4 +26,4 @@ fun box(): String { x = 42 } return if (x.inc() == 43) "OK" else "Fail: ${x.inc()}" -} \ No newline at end of file +} diff --git a/compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline.kt b/compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline.kt index 27c1085f2b5..3b2cae45b67 100644 --- a/compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline.kt +++ b/compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline.kt @@ -1,8 +1,9 @@ // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts -// IGNORE_BACKEND: JVM_IR, NATIVE, JS_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR +// IGNORE_BACKEND: NATIVE + // FILE: 1.kt + package test import kotlin.contracts.* @@ -17,6 +18,7 @@ public inline fun myrun(crossinline block: () -> Unit): Unit { } // FILE: 2.kt + import test.* fun box(): String { @@ -25,4 +27,4 @@ fun box(): String { x = 42L } return if (x != 42L) "FAIL" else "OK" -} \ No newline at end of file +} diff --git a/compiler/testData/codegen/boxInline/contracts/exactlyOnceNoinline.kt b/compiler/testData/codegen/boxInline/contracts/exactlyOnceNoinline.kt index 3435f91b41c..84abbfe63e9 100644 --- a/compiler/testData/codegen/boxInline/contracts/exactlyOnceNoinline.kt +++ b/compiler/testData/codegen/boxInline/contracts/exactlyOnceNoinline.kt @@ -1,9 +1,10 @@ // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts -// IGNORE_BACKEND: JVM_IR, NATIVE, JS_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR +// IGNORE_BACKEND: NATIVE // NO_CHECK_LAMBDA_INLINING + // FILE: 1.kt + package test import kotlin.contracts.* @@ -17,6 +18,7 @@ public inline fun myrun(noinline block: () -> Unit): Unit { } // FILE: 2.kt + import test.* fun box(): String { @@ -25,4 +27,4 @@ fun box(): String { x = 42L } return if (x != 42L) "FAIL" else "OK" -} \ No newline at end of file +} diff --git a/compiler/testData/codegen/boxInline/contracts/nonLocalReturn.kt b/compiler/testData/codegen/boxInline/contracts/nonLocalReturn.kt index 3704947777a..f9731fff196 100644 --- a/compiler/testData/codegen/boxInline/contracts/nonLocalReturn.kt +++ b/compiler/testData/codegen/boxInline/contracts/nonLocalReturn.kt @@ -1,9 +1,9 @@ // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts -// IGNORE_BACKEND: JVM_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR // IGNORE_BACKEND: NATIVE + // FILE: 1.kt + package test import kotlin.contracts.* @@ -17,6 +17,7 @@ public inline fun myrun(block: () -> R): R { } // FILE: 2.kt + import test.* fun box(): String { @@ -26,4 +27,4 @@ fun box(): String { if (x == 42L) return "OK" } return "fail" -} \ No newline at end of file +} diff --git a/compiler/testData/codegen/boxInline/contracts/nonLocalReturnWithCycle.kt b/compiler/testData/codegen/boxInline/contracts/nonLocalReturnWithCycle.kt index bb4947e954b..e4b76bc6c06 100644 --- a/compiler/testData/codegen/boxInline/contracts/nonLocalReturnWithCycle.kt +++ b/compiler/testData/codegen/boxInline/contracts/nonLocalReturnWithCycle.kt @@ -1,11 +1,11 @@ // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts -// IGNORE_BACKEND: JVM_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR // IGNORE_BACKEND: NATIVE // WITH_RUNTIME // KJS_WITH_FULL_RUNTIME + // FILE: 1.kt + package test import kotlin.contracts.* @@ -19,6 +19,7 @@ public inline fun myrun(block: () -> R): R { } // FILE: 2.kt + import test.* fun test(xs: List): String { @@ -38,7 +39,6 @@ fun test(xs: List): String { return result } - fun box(): String { return test(listOf("O", "K", "fail")) -} \ No newline at end of file +} diff --git a/compiler/testData/codegen/boxInline/contracts/valInitializationAndUsageInNestedLambda.kt b/compiler/testData/codegen/boxInline/contracts/valInitializationAndUsageInNestedLambda.kt index d9e29c428b9..da2b88721ba 100644 --- a/compiler/testData/codegen/boxInline/contracts/valInitializationAndUsageInNestedLambda.kt +++ b/compiler/testData/codegen/boxInline/contracts/valInitializationAndUsageInNestedLambda.kt @@ -1,9 +1,9 @@ // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts -// IGNORE_BACKEND: JVM_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR // IGNORE_BACKEND: NATIVE + // FILE: 1.kt + package test import kotlin.contracts.* @@ -16,9 +16,9 @@ public inline fun myrun(block: () -> R): R { return block() } - // FILE: 2.kt // NO_CHECK_LAMBDA_INLINING + import test.* fun box(): String { @@ -30,4 +30,4 @@ fun box(): String { }() } return if (res == 42 && x.inc() == 43) "OK" else "Fail: ${x.inc()}" -} \ No newline at end of file +} diff --git a/compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedValInLambdaInitializedInside.kt b/compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedValInLambdaInitializedInside.kt new file mode 100644 index 00000000000..b4c9207bfc6 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedValInLambdaInitializedInside.kt @@ -0,0 +1,18 @@ +// WITH_RUNTIME +// IGNORE_BACKEND: JVM_IR + +// In JVM IR, SharedVariablesLowering transforms `x` into a shared variable to be able to update it from a lambda, +// which is a separate function (...$lambda-0). +// If we keep the existing representation of lambda bodies as separate functions in JVM IR, the only viable option to fix this test +// seems to support this case in the bytecode optimization pass CapturedVarsOptimizationMethodTransformer. + +fun box(): String { + val x: String + run { + x = "OK" + val y = x + } + return x +} + +// 0 ObjectRef diff --git a/compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedValInLambdaInitializedOutside.kt b/compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedValInLambdaInitializedOutside.kt new file mode 100644 index 00000000000..b28b0dd844c --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedValInLambdaInitializedOutside.kt @@ -0,0 +1,10 @@ +fun box(): String { + val x: String + x = "OK" + { + val y = x + }() + return x +} + +// 0 ObjectRef diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 557f148d245..5aeb3a2e7e3 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -777,6 +777,16 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { runTest("compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedInNoInlneInsideChainOfInlineFuns.kt"); } + @TestMetadata("capturedValInLambdaInitializedInside.kt") + public void testCapturedValInLambdaInitializedInside() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedValInLambdaInitializedInside.kt"); + } + + @TestMetadata("capturedValInLambdaInitializedOutside.kt") + public void testCapturedValInLambdaInitializedOutside() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedValInLambdaInitializedOutside.kt"); + } + @TestMetadata("capturedVarsOfSize2.kt") public void testCapturedVarsOfSize2() throws Exception { runTest("compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedVarsOfSize2.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java index cb92283c6a8..2304f3ee00d 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java @@ -777,6 +777,16 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest { runTest("compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedInNoInlneInsideChainOfInlineFuns.kt"); } + @TestMetadata("capturedValInLambdaInitializedInside.kt") + public void testCapturedValInLambdaInitializedInside() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedValInLambdaInitializedInside.kt"); + } + + @TestMetadata("capturedValInLambdaInitializedOutside.kt") + public void testCapturedValInLambdaInitializedOutside() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedValInLambdaInitializedOutside.kt"); + } + @TestMetadata("capturedVarsOfSize2.kt") public void testCapturedVarsOfSize2() throws Exception { runTest("compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedVarsOfSize2.kt");