[JS IR] Add annotation for eager property initialization

This commit is contained in:
Ilya Goncharov
2021-09-16 19:18:39 +03:00
committed by Space
parent b5fb0d9f33
commit c2cf221965
7 changed files with 96 additions and 8 deletions
@@ -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 {
@@ -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<IrDeclaration>): Map<IrField, IrExpression> =
private fun calculateFieldToExpression(declarations: Collection<IrDeclaration>, context: JsIrBackendContext): Map<IrField, IrExpression> =
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 <T> 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)
@@ -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"
}
@@ -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"
}
@@ -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");
@@ -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");
@@ -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