diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt index e4db6cf9652..45816b44981 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt @@ -282,6 +282,8 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC val doNotIntrinsifyAnnotationSymbol = context.symbolTable.referenceClass(context.getJsInternalClass("DoNotIntrinsify")) val jsFunAnnotationSymbol = context.symbolTable.referenceClass(context.getJsInternalClass("JsFun")) + val jsEagerInitializationAnnotationSymbol = context.symbolTable.referenceClass(context.getJsInternalClass("JsEagerInitialization")) + // TODO move CharSequence-related stiff to IntrinsifyCallsLowering val charSequenceClassSymbol = context.symbolTable.referenceClass(context.getClass(FqName("kotlin.CharSequence"))) val charSequenceLengthPropertyGetterSymbol by context.lazy2 { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyLazyInitLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyLazyInitLowering.kt index 781ccda36ee..6bd261b0db4 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyLazyInitLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyLazyInitLowering.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.ir.builders.declarations.buildField import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.persistent.PersistentIrElementBase import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.util.hasAnnotation import org.jetbrains.kotlin.name.Name import kotlin.collections.component1 import kotlin.collections.component2 @@ -51,7 +52,7 @@ class PropertyLazyInitLowering( if (container !is IrField && container !is IrSimpleFunction && container !is IrProperty) return - if (!container.isCompatibleDeclaration()) return + if (!container.isCompatibleDeclaration(context)) return val file = container.parent as? IrFile ?: return @@ -98,7 +99,8 @@ class PropertyLazyInitLowering( val declarations = file.declarations.toList() val fieldToInitializer = calculateFieldToExpression( - declarations + declarations, + context ) if (fieldToInitializer.isEmpty()) return null @@ -206,7 +208,7 @@ class RemoveInitializersForLazyProperties( if (declaration !is IrField) return null - if (!declaration.isCompatibleDeclaration()) return null + if (!declaration.isCompatibleDeclaration(context)) return null val file = declaration.parent as? IrFile ?: return null @@ -232,7 +234,7 @@ class RemoveInitializersForLazyProperties( private fun calculateFileFieldsPureness(file: IrFile): Boolean { val declarations = file.declarations.toList() - val expressions = calculateFieldToExpression(declarations) + val expressions = calculateFieldToExpression(declarations, context) .values val allFieldsInFilePure = allFieldsInFilePure(expressions) @@ -241,10 +243,10 @@ class RemoveInitializersForLazyProperties( } } -private fun calculateFieldToExpression(declarations: Collection): Map = +private fun calculateFieldToExpression(declarations: Collection, context: JsIrBackendContext): Map = declarations .asSequence() - .filter { it.isCompatibleDeclaration() } + .filter { it.isCompatibleDeclaration(context) } .map { it.correspondingProperty } .filterNotNull() .filter { it.isForLazyInit() } @@ -274,12 +276,19 @@ private val IrDeclaration.correspondingProperty: IrProperty? } private fun IrDeclaration.propertyWithPersistentSafe(transform: IrDeclaration.() -> IrProperty?): IrProperty? = + withPersistentSafe(transform) + +private fun IrDeclaration.withPersistentSafe(transform: IrDeclaration.() -> T?): T? = if (this !is PersistentIrElementBase<*> || this.createdOn <= this.factory.stageController.currentStage) { transform() } else null -private fun IrDeclaration.isCompatibleDeclaration() = - origin in compatibleOrigins +private fun IrDeclaration.isCompatibleDeclaration(context: JsIrBackendContext) = + (this as? IrField) + ?.correspondingProperty + ?.hasAnnotation(context.intrinsics.jsEagerInitializationAnnotationSymbol) != true && + withPersistentSafe { origin in compatibleOrigins } == true + private fun IrDeclaration.assertCompatibleDeclaration() { assert(this !is PersistentIrElementBase<*> || createdOn == 0) diff --git a/compiler/testData/codegen/box/properties/eagerInitializationGlobal1.kt b/compiler/testData/codegen/box/properties/eagerInitializationGlobal1.kt new file mode 100644 index 00000000000..c6890d9b61c --- /dev/null +++ b/compiler/testData/codegen/box/properties/eagerInitializationGlobal1.kt @@ -0,0 +1,27 @@ +// TARGET_BACKEND: JS_IR +// DONT_TARGET_EXACT_BACKEND: WASM +// PROPERTY_LAZY_INITIALIZATION + +// FILE: lib.kt +var z1 = false +var z2 = false + +// FILE: lib2.kt + +@OptIn(kotlin.ExperimentalStdlibApi::class) +@JsEagerInitialization +val x = foo() + +private fun foo(): Int { + z1 = true + return 42 +} + +// Will be initialized since [x]'s initializer calls a function from the file. +val y = run { z2 = true; 117 } + +// FILE: main.kt + +fun box(): String { + return return if (z1 && z2) "OK" else "fail" +} diff --git a/compiler/testData/codegen/box/properties/eagerInitializationGlobal2.kt b/compiler/testData/codegen/box/properties/eagerInitializationGlobal2.kt new file mode 100644 index 00000000000..225687d45a9 --- /dev/null +++ b/compiler/testData/codegen/box/properties/eagerInitializationGlobal2.kt @@ -0,0 +1,22 @@ +// TARGET_BACKEND: JS_IR +// DONT_TARGET_EXACT_BACKEND: WASM +// PROPERTY_LAZY_INITIALIZATION + +// FILE: lib.kt +var z1 = false +var z2 = false + +// FILE: lib2.kt + +@OptIn(kotlin.ExperimentalStdlibApi::class) +@JsEagerInitialization +val x = run { z1 = true; 42 } + +// Won't be initialized (cause no function from the file will be called during [x] initialization). +val y = run { z2 = true; 117 } + +// FILE: main.kt + +fun box(): String { + return if (z1 && !z2) "OK" else "fail" +} diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java index 9986fd489ea..c040c14597c 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java @@ -19842,6 +19842,16 @@ public class IrJsCodegenBoxES6TestGenerated extends AbstractIrJsCodegenBoxES6Tes runTest("compiler/testData/codegen/box/properties/complexPropertyInitializer.kt"); } + @TestMetadata("eagerInitializationGlobal1.kt") + public void testEagerInitializationGlobal1() throws Exception { + runTest("compiler/testData/codegen/box/properties/eagerInitializationGlobal1.kt"); + } + + @TestMetadata("eagerInitializationGlobal2.kt") + public void testEagerInitializationGlobal2() throws Exception { + runTest("compiler/testData/codegen/box/properties/eagerInitializationGlobal2.kt"); + } + @TestMetadata("field.kt") public void testField() throws Exception { runTest("compiler/testData/codegen/box/properties/field.kt"); diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java index e94e6835c81..14e3ab0accb 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java @@ -19248,6 +19248,16 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest { runTest("compiler/testData/codegen/box/properties/complexPropertyInitializer.kt"); } + @TestMetadata("eagerInitializationGlobal1.kt") + public void testEagerInitializationGlobal1() throws Exception { + runTest("compiler/testData/codegen/box/properties/eagerInitializationGlobal1.kt"); + } + + @TestMetadata("eagerInitializationGlobal2.kt") + public void testEagerInitializationGlobal2() throws Exception { + runTest("compiler/testData/codegen/box/properties/eagerInitializationGlobal2.kt"); + } + @TestMetadata("field.kt") public void testField() throws Exception { runTest("compiler/testData/codegen/box/properties/field.kt"); diff --git a/libraries/stdlib/js/src/kotlin/annotations.kt b/libraries/stdlib/js/src/kotlin/annotations.kt index 90ad9009fb6..eee14314a08 100644 --- a/libraries/stdlib/js/src/kotlin/annotations.kt +++ b/libraries/stdlib/js/src/kotlin/annotations.kt @@ -191,3 +191,11 @@ public annotation class JsQualifier(val value: String) @Target(CLASS, PROPERTY, FUNCTION, FILE) @SinceKotlin("1.3") public actual annotation class JsExport + +/** + * Forces a top-level property to be initialized eagerly, opposed to lazily on the first access to file and/or property. + */ +@Target(AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.BINARY) +@ExperimentalStdlibApi +public annotation class JsEagerInitialization