IR: create shared variables for val-variables when needed

This is possible when a lambda's contract guarantees initialization of a
variable.
This commit is contained in:
Alexander Udalov
2019-11-04 20:13:25 +01:00
parent 5c4b47c336
commit 66e19b13ce
18 changed files with 116 additions and 48 deletions
@@ -27,7 +27,6 @@ import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.symbols.IrVariableSymbol import org.jetbrains.kotlin.ir.symbols.IrVariableSymbol
import org.jetbrains.kotlin.ir.util.dump import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.visitors.* import org.jetbrains.kotlin.ir.visitors.*
import java.util.*
val sharedVariablesPhase = makeIrFilePhase( val sharedVariablesPhase = makeIrFilePhase(
::SharedVariablesLowering, ::SharedVariablesLowering,
@@ -77,7 +76,8 @@ class SharedVariablesLowering(val context: BackendContext) : FileLoweringPass {
private fun collectSharedVariables() { private fun collectSharedVariables() {
irDeclaration.accept(object : IrElementVisitor<Unit, IrDeclarationParent?> { irDeclaration.accept(object : IrElementVisitor<Unit, IrDeclarationParent?> {
val relevantVars = mutableSetOf<IrVariable>() val relevantVars = HashSet<IrVariable>()
val relevantVals = HashSet<IrVariable>()
override fun visitElement(element: IrElement, data: IrDeclarationParent?) { override fun visitElement(element: IrElement, data: IrDeclarationParent?) {
element.acceptChildren(this, data) element.acceptChildren(this, data)
@@ -102,6 +102,12 @@ class SharedVariablesLowering(val context: BackendContext) : FileLoweringPass {
if (declaration.isVar) { if (declaration.isVar) {
relevantVars.add(declaration) 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) 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) }, irDeclaration as? IrDeclarationParent ?: irDeclaration.parent)
} }
@@ -1,6 +1,6 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: JVM_IR, NATIVE, JS_IR // IGNORE_BACKEND: NATIVE
import kotlin.contracts.* import kotlin.contracts.*
@@ -1,9 +1,9 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// IGNORE_BACKEND: NATIVE // IGNORE_BACKEND: NATIVE
// FILE: 1.kt // FILE: 1.kt
package test package test
import kotlin.contracts.* import kotlin.contracts.*
@@ -16,8 +16,8 @@ public inline fun <R> myrun(block: () -> R): R {
return block() return block()
} }
// FILE: 2.kt // FILE: 2.kt
import test.* import test.*
fun test(b: Boolean): Int { fun test(b: Boolean): Int {
@@ -34,7 +34,6 @@ fun test(b: Boolean): Int {
} }
fun box(): String { fun box(): String {
if (test(true) != 1) return "Fail 1" if (test(true) != 1) return "Fail 1"
if (test(false) != -1) return "Fail 2" if (test(false) != -1) return "Fail 2"
return "OK" return "OK"
@@ -1,9 +1,9 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// IGNORE_BACKEND: NATIVE // IGNORE_BACKEND: NATIVE
// FILE: 1.kt // FILE: 1.kt
package test package test
import kotlin.contracts.* import kotlin.contracts.*
@@ -1,9 +1,9 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// IGNORE_BACKEND: NATIVE // IGNORE_BACKEND: NATIVE
// FILE: 1.kt // FILE: 1.kt
package test package test
import kotlin.contracts.* import kotlin.contracts.*
@@ -1,9 +1,9 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// IGNORE_BACKEND: NATIVE // IGNORE_BACKEND: NATIVE
// FILE: 1.kt // FILE: 1.kt
package test package test
import kotlin.contracts.* import kotlin.contracts.*
@@ -17,6 +17,7 @@ public inline fun <R> myrun(block: () -> R): R {
} }
// FILE: 2.kt // FILE: 2.kt
import test.* import test.*
fun box(): String { fun box(): String {
@@ -1,9 +1,9 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// IGNORE_BACKEND: NATIVE // IGNORE_BACKEND: NATIVE
// FILE: 1.kt // FILE: 1.kt
package test package test
import kotlin.contracts.* import kotlin.contracts.*
@@ -16,8 +16,8 @@ public inline fun <R> myrun(block: () -> R): R {
return block() return block()
} }
// FILE: 2.kt // FILE: 2.kt
import test.* import test.*
fun box(): String { fun box(): String {
@@ -1,10 +1,10 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// IGNORE_BACKEND: NATIVE // IGNORE_BACKEND: NATIVE
// NO_CHECK_LAMBDA_INLINING // NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
import kotlin.contracts.* import kotlin.contracts.*
@@ -17,8 +17,8 @@ public inline fun <R> myrun(block: () -> R): R {
return block() return block()
} }
// FILE: 2.kt // FILE: 2.kt
import test.* import test.*
class A { class A {
@@ -1,9 +1,9 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// IGNORE_BACKEND: NATIVE // IGNORE_BACKEND: NATIVE
// FILE: 1.kt // FILE: 1.kt
package test package test
import kotlin.contracts.* import kotlin.contracts.*
@@ -16,8 +16,8 @@ public inline fun <R> myrun(block: () -> R): R {
return block() return block()
} }
// FILE: 2.kt // FILE: 2.kt
import test.* import test.*
fun box(): String { fun box(): String {
@@ -1,8 +1,9 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: JVM_IR, NATIVE, JS_IR // IGNORE_BACKEND: NATIVE
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// FILE: 1.kt // FILE: 1.kt
package test package test
import kotlin.contracts.* import kotlin.contracts.*
@@ -17,6 +18,7 @@ public inline fun myrun(crossinline block: () -> Unit): Unit {
} }
// FILE: 2.kt // FILE: 2.kt
import test.* import test.*
fun box(): String { fun box(): String {
@@ -1,9 +1,10 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: JVM_IR, NATIVE, JS_IR // IGNORE_BACKEND: NATIVE
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// NO_CHECK_LAMBDA_INLINING // NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
import kotlin.contracts.* import kotlin.contracts.*
@@ -17,6 +18,7 @@ public inline fun myrun(noinline block: () -> Unit): Unit {
} }
// FILE: 2.kt // FILE: 2.kt
import test.* import test.*
fun box(): String { fun box(): String {
@@ -1,9 +1,9 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// IGNORE_BACKEND: NATIVE // IGNORE_BACKEND: NATIVE
// FILE: 1.kt // FILE: 1.kt
package test package test
import kotlin.contracts.* import kotlin.contracts.*
@@ -17,6 +17,7 @@ public inline fun <R> myrun(block: () -> R): R {
} }
// FILE: 2.kt // FILE: 2.kt
import test.* import test.*
fun box(): String { fun box(): String {
@@ -1,11 +1,11 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// IGNORE_BACKEND: NATIVE // IGNORE_BACKEND: NATIVE
// WITH_RUNTIME // WITH_RUNTIME
// KJS_WITH_FULL_RUNTIME // KJS_WITH_FULL_RUNTIME
// FILE: 1.kt // FILE: 1.kt
package test package test
import kotlin.contracts.* import kotlin.contracts.*
@@ -19,6 +19,7 @@ public inline fun <R> myrun(block: () -> R): R {
} }
// FILE: 2.kt // FILE: 2.kt
import test.* import test.*
fun test(xs: List<String>): String { fun test(xs: List<String>): String {
@@ -38,7 +39,6 @@ fun test(xs: List<String>): String {
return result return result
} }
fun box(): String { fun box(): String {
return test(listOf("O", "K", "fail")) return test(listOf("O", "K", "fail"))
} }
@@ -1,9 +1,9 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts // !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// IGNORE_BACKEND: NATIVE // IGNORE_BACKEND: NATIVE
// FILE: 1.kt // FILE: 1.kt
package test package test
import kotlin.contracts.* import kotlin.contracts.*
@@ -16,9 +16,9 @@ public inline fun <R> myrun(block: () -> R): R {
return block() return block()
} }
// FILE: 2.kt // FILE: 2.kt
// NO_CHECK_LAMBDA_INLINING // NO_CHECK_LAMBDA_INLINING
import test.* import test.*
fun box(): String { fun box(): String {
@@ -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
@@ -0,0 +1,10 @@
fun box(): String {
val x: String
x = "OK"
{
val y = x
}()
return x
}
// 0 ObjectRef
@@ -777,6 +777,16 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedInNoInlneInsideChainOfInlineFuns.kt"); 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") @TestMetadata("capturedVarsOfSize2.kt")
public void testCapturedVarsOfSize2() throws Exception { public void testCapturedVarsOfSize2() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedVarsOfSize2.kt"); runTest("compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedVarsOfSize2.kt");
@@ -777,6 +777,16 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedInNoInlneInsideChainOfInlineFuns.kt"); 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") @TestMetadata("capturedVarsOfSize2.kt")
public void testCapturedVarsOfSize2() throws Exception { public void testCapturedVarsOfSize2() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedVarsOfSize2.kt"); runTest("compiler/testData/codegen/bytecodeText/capturedVarsOptimization/capturedVarsOfSize2.kt");