From 82455c849d38b21e75d1f6f6787c0bad9e3c18e8 Mon Sep 17 00:00:00 2001 From: Igor Yakovlev Date: Thu, 23 Dec 2021 19:38:56 +0100 Subject: [PATCH] [WASM] Lazy properties initialization --- .../jetbrains/kotlin/cli/js/K2JsIrCompiler.kt | 4 +- .../ir/backend/js/JsCommonBackendContext.kt | 1 + .../kotlin/ir/backend/js/JsIntrinsics.kt | 2 - .../ir/backend/js/JsIrBackendContext.kt | 12 ++-- .../backend/js/PropertyLazyInitialization.kt | 16 +++++ .../js/lower/PropertyLazyInitLowering.kt | 59 ++++++++++--------- .../kotlin/backend/wasm/WasmBackendContext.kt | 4 ++ .../kotlin/backend/wasm/WasmLoweringPhases.kt | 14 +++++ .../kotlin/backend/wasm/WasmSymbols.kt | 2 + .../jetbrains/kotlin/backend/wasm/compiler.kt | 3 +- .../box/properties/lazyInitialization.kt | 1 - .../lazyInitializationCyclicImports.kt | 1 - .../lazyInitializationSplitPerModule.kt | 1 - .../kotlin/js/testOld/BasicWasmBoxTest.kt | 9 +-- .../jsStubs.kt => src/kotlin/annotations.kt} | 7 ++- 15 files changed, 91 insertions(+), 45 deletions(-) create mode 100644 compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/PropertyLazyInitialization.kt rename libraries/stdlib/wasm/{stubs/jsStubs.kt => src/kotlin/annotations.kt} (72%) diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt index 99afaaef00a..0fae5c929c4 100644 --- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt +++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -317,13 +317,15 @@ class K2JsIrCompiler : CLICompiler() { sourceModule } + if (arguments.wasm) { val res = compileWasm( module, PhaseConfig(wasmPhases), IrFactoryImpl, exportedDeclarations = setOf(FqName("main")), - emitNameSection = arguments.wasmDebug, + emitNameSection = arguments.wasmDebug, + propertyLazyInitialization = arguments.irPropertyLazyInitialization, ) val outputWasmFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "wasm")!! outputWasmFile.writeBytes(res.wasm) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsCommonBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsCommonBackendContext.kt index f320034cbff..5eed646caa7 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsCommonBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsCommonBackendContext.kt @@ -30,6 +30,7 @@ interface JsCommonBackendContext : CommonBackendContext { override val mapping: JsMapping val reflectionSymbols: ReflectionSymbols + val propertyLazyInitialization: PropertyLazyInitialization override val inlineClassesUtils: JsCommonInlineClassesUtils 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 230b934ecf8..62c7e3e1091 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 @@ -294,8 +294,6 @@ 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("EagerInitialization")) - // 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/JsIrBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt index ec1e1c2d275..f496b01d008 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt @@ -56,7 +56,7 @@ class JsIrBackendContext( override val scriptMode: Boolean = false, override val es6mode: Boolean = false, val dceRuntimeDiagnostic: RuntimeDiagnostic? = null, - val propertyLazyInitialization: Boolean = false, + propertyLazyInitialization: Boolean = false, val baseClassIntoMetadata: Boolean = false, val safeExternalBoolean: Boolean = false, val safeExternalBooleanDiagnostic: RuntimeDiagnostic? = null, @@ -65,8 +65,6 @@ class JsIrBackendContext( val icCompatibleIr2Js: Boolean = false, ) : JsCommonBackendContext { - val fileToInitializationFuns: MutableMap = mutableMapOf() - val fileToInitializerPureness: MutableMap = mutableMapOf() val fieldToInitializer: MutableMap = mutableMapOf() val localClassNames: MutableMap = mutableMapOf() @@ -142,6 +140,11 @@ class JsIrBackendContext( val intrinsics: JsIntrinsics = JsIntrinsics(irBuiltIns, this) override val reflectionSymbols: ReflectionSymbols get() = intrinsics.reflectionSymbols + override val propertyLazyInitialization: PropertyLazyInitialization = PropertyLazyInitialization( + enabled = propertyLazyInitialization, + eagerInitialization = symbolTable.referenceClass(getJsInternalClass("EagerInitialization")) + ) + override val catchAllThrowableType: IrType get() = dynamicType @@ -207,7 +210,8 @@ class JsIrBackendContext( override val getContinuation = symbolTable.referenceSimpleFunction(getJsInternalFunction("getContinuation")) - override val coroutineContextGetter = symbolTable.referenceSimpleFunction(context.coroutineSymbols.coroutineContextProperty.getter!!) + override val coroutineContextGetter = + symbolTable.referenceSimpleFunction(context.coroutineSymbols.coroutineContextProperty.getter!!) override val suspendCoroutineUninterceptedOrReturn = symbolTable.referenceSimpleFunction(getJsInternalFunction(COROUTINE_SUSPEND_OR_RETURN_JS_NAME)) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/PropertyLazyInitialization.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/PropertyLazyInitialization.kt new file mode 100644 index 00000000000..f21f233a140 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/PropertyLazyInitialization.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.ir.backend.js + +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol + +class PropertyLazyInitialization(val enabled: Boolean, eagerInitialization: IrClassSymbol) { + val fileToInitializationFuns: MutableMap = mutableMapOf() + val fileToInitializerPureness: MutableMap = mutableMapOf() + val eagerInitialization: IrClassSymbol = eagerInitialization +} \ No newline at end of file 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 ee6315c62cb..bc5814b99ad 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 @@ -13,8 +13,7 @@ import org.jetbrains.kotlin.backend.common.ir.isTopLevel import org.jetbrains.kotlin.descriptors.DescriptorVisibilities.INTERNAL import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET -import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext -import org.jetbrains.kotlin.ir.backend.js.ir.JsIrArithBuilder +import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder import org.jetbrains.kotlin.ir.backend.js.utils.prependFunctionCall import org.jetbrains.kotlin.ir.builders.declarations.addFunction @@ -28,25 +27,23 @@ import kotlin.collections.component2 import kotlin.collections.set class PropertyLazyInitLowering( - private val context: JsIrBackendContext + private val context: JsCommonBackendContext ) : BodyLoweringPass { private val irBuiltIns get() = context.irBuiltIns - private val calculator = JsIrArithBuilder(context) - private val irFactory get() = context.irFactory private val fileToInitializationFuns - get() = context.fileToInitializationFuns + get() = context.propertyLazyInitialization.fileToInitializationFuns private val fileToInitializerPureness - get() = context.fileToInitializerPureness + get() = context.propertyLazyInitialization.fileToInitializerPureness override fun lower(irBody: IrBody, container: IrDeclaration) { - if (!context.propertyLazyInitialization) { + if (!context.propertyLazyInitialization.enabled) { return } @@ -132,32 +129,35 @@ class PropertyLazyInitLowering( body = irFactory.createBlockBody( UNDEFINED_OFFSET, UNDEFINED_OFFSET, - buildBodyWithIfGuard(initializers, initializedField) + listOf(buildBodyWithIfGuard(initializers, initializedField)) ) } private fun buildBodyWithIfGuard( initializers: Map, initializedField: IrField - ): List { - val statements = initializers - .map { (field, expression) -> - createIrSetField(field, expression) + ): IrStatement { + val statements = buildList { + val upGuard = createIrSetField( + initializedField, + JsIrBuilder.buildBoolean(context.irBuiltIns.booleanType, true) + ) + add(upGuard) + initializers.forEach { (field, expression) -> + add(createIrSetField(field, expression)) } - - val upGuard = createIrSetField( - initializedField, - JsIrBuilder.buildBoolean(context.irBuiltIns.booleanType, true) - ) + add(JsIrBuilder.buildBlock(irBuiltIns.unitType)) + } return JsIrBuilder.buildIfElse( type = irBuiltIns.unitType, - cond = calculator.not(createIrGetField(initializedField)), - thenBranch = JsIrBuilder.buildComposite( + cond = createIrGetField(initializedField), + thenBranch = JsIrBuilder.buildBlock(irBuiltIns.unitType), + elseBranch = JsIrBuilder.buildComposite( type = irBuiltIns.unitType, - statements = mutableListOf(upGuard).apply { addAll(statements) } + statements = statements ) - ).let { listOf(it) } + ) } companion object { @@ -188,14 +188,14 @@ private fun allFieldsInFilePure(fieldToInitializer: Collection): B } class RemoveInitializersForLazyProperties( - private val context: JsIrBackendContext + private val context: JsCommonBackendContext ) : DeclarationTransformer { private val fileToInitializerPureness - get() = context.fileToInitializerPureness + get() = context.propertyLazyInitialization.fileToInitializerPureness override fun transformFlat(declaration: IrDeclaration): List? { - if (!context.propertyLazyInitialization) { + if (!context.propertyLazyInitialization.enabled) { return null } @@ -235,7 +235,10 @@ class RemoveInitializersForLazyProperties( } } -private fun calculateFieldToExpression(declarations: Collection, context: JsIrBackendContext): Map = +private fun calculateFieldToExpression( + declarations: Collection, + context: JsCommonBackendContext +): Map = declarations .asSequence() .filter { it.isCompatibleDeclaration(context) } @@ -276,9 +279,9 @@ private fun IrDeclaration.propertyWithPersistentSafe(transform: IrDeclaration.() private fun IrDeclaration.withPersistentSafe(transform: IrDeclaration.() -> T?): T? = transform() -private fun IrDeclaration.isCompatibleDeclaration(context: JsIrBackendContext) = +private fun IrDeclaration.isCompatibleDeclaration(context: JsCommonBackendContext) = correspondingProperty?.let { - it.isForLazyInit() && !it.hasAnnotation(context.intrinsics.jsEagerInitializationAnnotationSymbol) + it.isForLazyInit() && !it.hasAnnotation(context.propertyLazyInitialization.eagerInitialization) } ?: true && withPersistentSafe { origin in compatibleOrigins } == true private val compatibleOrigins = listOf( diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt index 3bbfff6de6b..56e9b785c40 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt @@ -35,6 +35,7 @@ class WasmBackendContext( override val irBuiltIns: IrBuiltIns, val symbolTable: SymbolTable, val irModuleFragment: IrModuleFragment, + propertyLazyInitialization: Boolean, override val configuration: CompilerConfiguration, ) : JsCommonBackendContext { override val builtIns = module.builtIns @@ -104,6 +105,9 @@ class WasmBackendContext( val wasmSymbols: WasmSymbols = WasmSymbols(this@WasmBackendContext, symbolTable) override val reflectionSymbols: ReflectionSymbols get() = wasmSymbols.reflectionSymbols + override val propertyLazyInitialization: PropertyLazyInitialization = + PropertyLazyInitialization(enabled = propertyLazyInitialization, eagerInitialization = wasmSymbols.eagerInitialization) + override val ir = object : Ir(this, irModuleFragment) { override val symbols: Symbols = wasmSymbols override fun shouldGenerateHandlerParameterForDefaultBodyFun() = true diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt index cd38b058657..68d4b55923a 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt @@ -496,6 +496,18 @@ private val forLoopsLoweringPhase = makeWasmModulePhase( description = "[Optimization] For loops lowering" ) +private val propertyLazyInitLoweringPhase = makeWasmModulePhase( + ::PropertyLazyInitLowering, + name = "PropertyLazyInitLowering", + description = "Make property init as lazy" +) + +private val removeInitializersForLazyProperties = makeWasmModulePhase( + ::RemoveInitializersForLazyProperties, + name = "RemoveInitializersForLazyProperties", + description = "Remove property initializers if they was initialized lazily" +) + private val propertyAccessorInlinerLoweringPhase = makeWasmModulePhase( ::PropertyAccessorInlineLowering, name = "PropertyAccessorInlineLowering", @@ -578,6 +590,8 @@ val wasmPhases = NamedCompilerPhase( returnableBlockLoweringPhase then forLoopsLoweringPhase then + propertyLazyInitLoweringPhase then + removeInitializersForLazyProperties then propertyAccessorInlinerLoweringPhase then stringConcatenationLowering then diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt index 986893063d4..3c5e7ba8da8 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt @@ -63,6 +63,8 @@ class WasmSymbols( internal val reflectionSymbols: WasmReflectionSymbols = WasmReflectionSymbols() + internal val eagerInitialization: IrClassSymbol = getIrClass(FqName("kotlin.EagerInitialization")) + override val throwNullPointerException = getInternalFunction("THROW_NPE") override val throwISE = getInternalFunction("THROW_ISE") override val throwTypeCastException = getInternalFunction("THROW_CCE") diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt index 4afaa8f3a6b..18891a86e2a 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt @@ -29,6 +29,7 @@ fun compileWasm( phaseConfig: PhaseConfig, irFactory: IrFactory, exportedDeclarations: Set = emptySet(), + propertyLazyInitialization: Boolean, emitNameSection: Boolean = false, ): WasmCompilerResult { val mainModule = depsDescriptors.mainModule @@ -46,7 +47,7 @@ fun compileWasm( } val moduleDescriptor = moduleFragment.descriptor - val context = WasmBackendContext(moduleDescriptor, irBuiltIns, symbolTable, moduleFragment, configuration) + val context = WasmBackendContext(moduleDescriptor, irBuiltIns, symbolTable, moduleFragment, propertyLazyInitialization, configuration) // Load declarations referenced during `context` initialization allModules.forEach { diff --git a/compiler/testData/codegen/box/properties/lazyInitialization.kt b/compiler/testData/codegen/box/properties/lazyInitialization.kt index 41e6cafa8ee..40c96879535 100644 --- a/compiler/testData/codegen/box/properties/lazyInitialization.kt +++ b/compiler/testData/codegen/box/properties/lazyInitialization.kt @@ -1,5 +1,4 @@ // IGNORE_BACKEND: JS -// IGNORE_BACKEND: WASM // PROPERTY_LAZY_INITIALIZATION // FILE: A.kt diff --git a/compiler/testData/codegen/box/properties/lazyInitializationCyclicImports.kt b/compiler/testData/codegen/box/properties/lazyInitializationCyclicImports.kt index 163e0f00356..6315309063d 100644 --- a/compiler/testData/codegen/box/properties/lazyInitializationCyclicImports.kt +++ b/compiler/testData/codegen/box/properties/lazyInitializationCyclicImports.kt @@ -1,6 +1,5 @@ // IGNORE_BACKEND: JS // IGNORE_BACKEND: NATIVE -// IGNORE_BACKEND: WASM // PROPERTY_LAZY_INITIALIZATION // FILE: A.kt diff --git a/compiler/testData/codegen/box/properties/lazyInitializationSplitPerModule.kt b/compiler/testData/codegen/box/properties/lazyInitializationSplitPerModule.kt index 54fa15cb5d9..84bf1afdf4b 100644 --- a/compiler/testData/codegen/box/properties/lazyInitializationSplitPerModule.kt +++ b/compiler/testData/codegen/box/properties/lazyInitializationSplitPerModule.kt @@ -1,5 +1,4 @@ // IGNORE_BACKEND: JS -// IGNORE_BACKEND: WASM // SPLIT_PER_MODULE // PROPERTY_LAZY_INITIALIZATION diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testOld/BasicWasmBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/BasicWasmBoxTest.kt index ddd35fa86a7..a16ce9fc023 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/testOld/BasicWasmBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/BasicWasmBoxTest.kt @@ -32,10 +32,8 @@ import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.resolve.CompilerEnvironment -import org.jetbrains.kotlin.test.DebugMode -import org.jetbrains.kotlin.test.Directives -import org.jetbrains.kotlin.test.KotlinTestWithEnvironment -import org.jetbrains.kotlin.test.TestFiles +import org.jetbrains.kotlin.test.* +import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives import org.jetbrains.kotlin.test.util.KtTestUtil import java.io.Closeable import java.io.File @@ -178,11 +176,14 @@ abstract class BasicWasmBoxTest( AnalyzerWithCompilerReport(config.configuration) ) + val directives = KotlinTestUtils.parseDirectives(FileUtil.loadFile(testFile)) + val compilerResult = compileWasm( sourceModule, phaseConfig = phaseConfig, irFactory = IrFactoryImpl, exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))), + propertyLazyInitialization = directives.contains(JsEnvironmentConfigurationDirectives.PROPERTY_LAZY_INITIALIZATION.name), emitNameSection = true, ) diff --git a/libraries/stdlib/wasm/stubs/jsStubs.kt b/libraries/stdlib/wasm/src/kotlin/annotations.kt similarity index 72% rename from libraries/stdlib/wasm/stubs/jsStubs.kt rename to libraries/stdlib/wasm/src/kotlin/annotations.kt index 5cc316f6253..de6f517c1a0 100644 --- a/libraries/stdlib/wasm/stubs/jsStubs.kt +++ b/libraries/stdlib/wasm/src/kotlin/annotations.kt @@ -3,11 +3,14 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -package kotlin.js +package kotlin +/** + * Forces a top-level property to be initialized eagerly, opposed to lazily on the first access to file and/or property. + */ @ExperimentalStdlibApi @Retention(AnnotationRetention.BINARY) @Target(AnnotationTarget.PROPERTY) @SinceKotlin("1.6") @Deprecated("This annotation is a temporal migration assistance and may be removed in the future releases, please consider filing an issue about the case where it is needed") -public annotation class EagerInitialization +public annotation class EagerInitialization \ No newline at end of file