[WASM] Lazy properties initialization

This commit is contained in:
Igor Yakovlev
2021-12-23 19:38:56 +01:00
committed by igoriakovlev
parent e58d4163ad
commit 82455c849d
15 changed files with 91 additions and 45 deletions
@@ -317,13 +317,15 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
sourceModule sourceModule
} }
if (arguments.wasm) { if (arguments.wasm) {
val res = compileWasm( val res = compileWasm(
module, module,
PhaseConfig(wasmPhases), PhaseConfig(wasmPhases),
IrFactoryImpl, IrFactoryImpl,
exportedDeclarations = setOf(FqName("main")), exportedDeclarations = setOf(FqName("main")),
emitNameSection = arguments.wasmDebug, emitNameSection = arguments.wasmDebug,
propertyLazyInitialization = arguments.irPropertyLazyInitialization,
) )
val outputWasmFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "wasm")!! val outputWasmFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "wasm")!!
outputWasmFile.writeBytes(res.wasm) outputWasmFile.writeBytes(res.wasm)
@@ -30,6 +30,7 @@ interface JsCommonBackendContext : CommonBackendContext {
override val mapping: JsMapping override val mapping: JsMapping
val reflectionSymbols: ReflectionSymbols val reflectionSymbols: ReflectionSymbols
val propertyLazyInitialization: PropertyLazyInitialization
override val inlineClassesUtils: JsCommonInlineClassesUtils override val inlineClassesUtils: JsCommonInlineClassesUtils
@@ -294,8 +294,6 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
val doNotIntrinsifyAnnotationSymbol = context.symbolTable.referenceClass(context.getJsInternalClass("DoNotIntrinsify")) val doNotIntrinsifyAnnotationSymbol = context.symbolTable.referenceClass(context.getJsInternalClass("DoNotIntrinsify"))
val jsFunAnnotationSymbol = context.symbolTable.referenceClass(context.getJsInternalClass("JsFun")) val jsFunAnnotationSymbol = context.symbolTable.referenceClass(context.getJsInternalClass("JsFun"))
val jsEagerInitializationAnnotationSymbol = context.symbolTable.referenceClass(context.getJsInternalClass("EagerInitialization"))
// TODO move CharSequence-related stiff to IntrinsifyCallsLowering // TODO move CharSequence-related stiff to IntrinsifyCallsLowering
val charSequenceClassSymbol = context.symbolTable.referenceClass(context.getClass(FqName("kotlin.CharSequence"))) val charSequenceClassSymbol = context.symbolTable.referenceClass(context.getClass(FqName("kotlin.CharSequence")))
val charSequenceLengthPropertyGetterSymbol by context.lazy2 { val charSequenceLengthPropertyGetterSymbol by context.lazy2 {
@@ -56,7 +56,7 @@ class JsIrBackendContext(
override val scriptMode: Boolean = false, override val scriptMode: Boolean = false,
override val es6mode: Boolean = false, override val es6mode: Boolean = false,
val dceRuntimeDiagnostic: RuntimeDiagnostic? = null, val dceRuntimeDiagnostic: RuntimeDiagnostic? = null,
val propertyLazyInitialization: Boolean = false, propertyLazyInitialization: Boolean = false,
val baseClassIntoMetadata: Boolean = false, val baseClassIntoMetadata: Boolean = false,
val safeExternalBoolean: Boolean = false, val safeExternalBoolean: Boolean = false,
val safeExternalBooleanDiagnostic: RuntimeDiagnostic? = null, val safeExternalBooleanDiagnostic: RuntimeDiagnostic? = null,
@@ -65,8 +65,6 @@ class JsIrBackendContext(
val icCompatibleIr2Js: Boolean = false, val icCompatibleIr2Js: Boolean = false,
) : JsCommonBackendContext { ) : JsCommonBackendContext {
val fileToInitializationFuns: MutableMap<IrFile, IrSimpleFunction?> = mutableMapOf()
val fileToInitializerPureness: MutableMap<IrFile, Boolean> = mutableMapOf()
val fieldToInitializer: MutableMap<IrField, IrExpression> = mutableMapOf() val fieldToInitializer: MutableMap<IrField, IrExpression> = mutableMapOf()
val localClassNames: MutableMap<IrClass, String> = mutableMapOf() val localClassNames: MutableMap<IrClass, String> = mutableMapOf()
@@ -142,6 +140,11 @@ class JsIrBackendContext(
val intrinsics: JsIntrinsics = JsIntrinsics(irBuiltIns, this) val intrinsics: JsIntrinsics = JsIntrinsics(irBuiltIns, this)
override val reflectionSymbols: ReflectionSymbols get() = intrinsics.reflectionSymbols override val reflectionSymbols: ReflectionSymbols get() = intrinsics.reflectionSymbols
override val propertyLazyInitialization: PropertyLazyInitialization = PropertyLazyInitialization(
enabled = propertyLazyInitialization,
eagerInitialization = symbolTable.referenceClass(getJsInternalClass("EagerInitialization"))
)
override val catchAllThrowableType: IrType override val catchAllThrowableType: IrType
get() = dynamicType get() = dynamicType
@@ -207,7 +210,8 @@ class JsIrBackendContext(
override val getContinuation = symbolTable.referenceSimpleFunction(getJsInternalFunction("getContinuation")) 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 = override val suspendCoroutineUninterceptedOrReturn =
symbolTable.referenceSimpleFunction(getJsInternalFunction(COROUTINE_SUSPEND_OR_RETURN_JS_NAME)) symbolTable.referenceSimpleFunction(getJsInternalFunction(COROUTINE_SUSPEND_OR_RETURN_JS_NAME))
@@ -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<IrFile, IrSimpleFunction?> = mutableMapOf()
val fileToInitializerPureness: MutableMap<IrFile, Boolean> = mutableMapOf()
val eagerInitialization: IrClassSymbol = eagerInitialization
}
@@ -13,8 +13,7 @@ import org.jetbrains.kotlin.backend.common.ir.isTopLevel
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities.INTERNAL import org.jetbrains.kotlin.descriptors.DescriptorVisibilities.INTERNAL
import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrArithBuilder
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.prependFunctionCall import org.jetbrains.kotlin.ir.backend.js.utils.prependFunctionCall
import org.jetbrains.kotlin.ir.builders.declarations.addFunction import org.jetbrains.kotlin.ir.builders.declarations.addFunction
@@ -28,25 +27,23 @@ import kotlin.collections.component2
import kotlin.collections.set import kotlin.collections.set
class PropertyLazyInitLowering( class PropertyLazyInitLowering(
private val context: JsIrBackendContext private val context: JsCommonBackendContext
) : BodyLoweringPass { ) : BodyLoweringPass {
private val irBuiltIns private val irBuiltIns
get() = context.irBuiltIns get() = context.irBuiltIns
private val calculator = JsIrArithBuilder(context)
private val irFactory private val irFactory
get() = context.irFactory get() = context.irFactory
private val fileToInitializationFuns private val fileToInitializationFuns
get() = context.fileToInitializationFuns get() = context.propertyLazyInitialization.fileToInitializationFuns
private val fileToInitializerPureness private val fileToInitializerPureness
get() = context.fileToInitializerPureness get() = context.propertyLazyInitialization.fileToInitializerPureness
override fun lower(irBody: IrBody, container: IrDeclaration) { override fun lower(irBody: IrBody, container: IrDeclaration) {
if (!context.propertyLazyInitialization) { if (!context.propertyLazyInitialization.enabled) {
return return
} }
@@ -132,32 +129,35 @@ class PropertyLazyInitLowering(
body = irFactory.createBlockBody( body = irFactory.createBlockBody(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
buildBodyWithIfGuard(initializers, initializedField) listOf(buildBodyWithIfGuard(initializers, initializedField))
) )
} }
private fun buildBodyWithIfGuard( private fun buildBodyWithIfGuard(
initializers: Map<IrField, IrExpression>, initializers: Map<IrField, IrExpression>,
initializedField: IrField initializedField: IrField
): List<IrStatement> { ): IrStatement {
val statements = initializers val statements = buildList<IrStatement> {
.map { (field, expression) -> val upGuard = createIrSetField(
createIrSetField(field, expression) initializedField,
JsIrBuilder.buildBoolean(context.irBuiltIns.booleanType, true)
)
add(upGuard)
initializers.forEach { (field, expression) ->
add(createIrSetField(field, expression))
} }
add(JsIrBuilder.buildBlock(irBuiltIns.unitType))
val upGuard = createIrSetField( }
initializedField,
JsIrBuilder.buildBoolean(context.irBuiltIns.booleanType, true)
)
return JsIrBuilder.buildIfElse( return JsIrBuilder.buildIfElse(
type = irBuiltIns.unitType, type = irBuiltIns.unitType,
cond = calculator.not(createIrGetField(initializedField)), cond = createIrGetField(initializedField),
thenBranch = JsIrBuilder.buildComposite( thenBranch = JsIrBuilder.buildBlock(irBuiltIns.unitType),
elseBranch = JsIrBuilder.buildComposite(
type = irBuiltIns.unitType, type = irBuiltIns.unitType,
statements = mutableListOf(upGuard).apply { addAll(statements) } statements = statements
) )
).let { listOf(it) } )
} }
companion object { companion object {
@@ -188,14 +188,14 @@ private fun allFieldsInFilePure(fieldToInitializer: Collection<IrExpression>): B
} }
class RemoveInitializersForLazyProperties( class RemoveInitializersForLazyProperties(
private val context: JsIrBackendContext private val context: JsCommonBackendContext
) : DeclarationTransformer { ) : DeclarationTransformer {
private val fileToInitializerPureness private val fileToInitializerPureness
get() = context.fileToInitializerPureness get() = context.propertyLazyInitialization.fileToInitializerPureness
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? { override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (!context.propertyLazyInitialization) { if (!context.propertyLazyInitialization.enabled) {
return null return null
} }
@@ -235,7 +235,10 @@ class RemoveInitializersForLazyProperties(
} }
} }
private fun calculateFieldToExpression(declarations: Collection<IrDeclaration>, context: JsIrBackendContext): Map<IrField, IrExpression> = private fun calculateFieldToExpression(
declarations: Collection<IrDeclaration>,
context: JsCommonBackendContext
): Map<IrField, IrExpression> =
declarations declarations
.asSequence() .asSequence()
.filter { it.isCompatibleDeclaration(context) } .filter { it.isCompatibleDeclaration(context) }
@@ -276,9 +279,9 @@ private fun IrDeclaration.propertyWithPersistentSafe(transform: IrDeclaration.()
private fun <T> IrDeclaration.withPersistentSafe(transform: IrDeclaration.() -> T?): T? = private fun <T> IrDeclaration.withPersistentSafe(transform: IrDeclaration.() -> T?): T? =
transform() transform()
private fun IrDeclaration.isCompatibleDeclaration(context: JsIrBackendContext) = private fun IrDeclaration.isCompatibleDeclaration(context: JsCommonBackendContext) =
correspondingProperty?.let { correspondingProperty?.let {
it.isForLazyInit() && !it.hasAnnotation(context.intrinsics.jsEagerInitializationAnnotationSymbol) it.isForLazyInit() && !it.hasAnnotation(context.propertyLazyInitialization.eagerInitialization)
} ?: true && withPersistentSafe { origin in compatibleOrigins } == true } ?: true && withPersistentSafe { origin in compatibleOrigins } == true
private val compatibleOrigins = listOf( private val compatibleOrigins = listOf(
@@ -35,6 +35,7 @@ class WasmBackendContext(
override val irBuiltIns: IrBuiltIns, override val irBuiltIns: IrBuiltIns,
val symbolTable: SymbolTable, val symbolTable: SymbolTable,
val irModuleFragment: IrModuleFragment, val irModuleFragment: IrModuleFragment,
propertyLazyInitialization: Boolean,
override val configuration: CompilerConfiguration, override val configuration: CompilerConfiguration,
) : JsCommonBackendContext { ) : JsCommonBackendContext {
override val builtIns = module.builtIns override val builtIns = module.builtIns
@@ -104,6 +105,9 @@ class WasmBackendContext(
val wasmSymbols: WasmSymbols = WasmSymbols(this@WasmBackendContext, symbolTable) val wasmSymbols: WasmSymbols = WasmSymbols(this@WasmBackendContext, symbolTable)
override val reflectionSymbols: ReflectionSymbols get() = wasmSymbols.reflectionSymbols override val reflectionSymbols: ReflectionSymbols get() = wasmSymbols.reflectionSymbols
override val propertyLazyInitialization: PropertyLazyInitialization =
PropertyLazyInitialization(enabled = propertyLazyInitialization, eagerInitialization = wasmSymbols.eagerInitialization)
override val ir = object : Ir<WasmBackendContext>(this, irModuleFragment) { override val ir = object : Ir<WasmBackendContext>(this, irModuleFragment) {
override val symbols: Symbols<WasmBackendContext> = wasmSymbols override val symbols: Symbols<WasmBackendContext> = wasmSymbols
override fun shouldGenerateHandlerParameterForDefaultBodyFun() = true override fun shouldGenerateHandlerParameterForDefaultBodyFun() = true
@@ -496,6 +496,18 @@ private val forLoopsLoweringPhase = makeWasmModulePhase(
description = "[Optimization] For loops lowering" 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( private val propertyAccessorInlinerLoweringPhase = makeWasmModulePhase(
::PropertyAccessorInlineLowering, ::PropertyAccessorInlineLowering,
name = "PropertyAccessorInlineLowering", name = "PropertyAccessorInlineLowering",
@@ -578,6 +590,8 @@ val wasmPhases = NamedCompilerPhase(
returnableBlockLoweringPhase then returnableBlockLoweringPhase then
forLoopsLoweringPhase then forLoopsLoweringPhase then
propertyLazyInitLoweringPhase then
removeInitializersForLazyProperties then
propertyAccessorInlinerLoweringPhase then propertyAccessorInlinerLoweringPhase then
stringConcatenationLowering then stringConcatenationLowering then
@@ -63,6 +63,8 @@ class WasmSymbols(
internal val reflectionSymbols: WasmReflectionSymbols = WasmReflectionSymbols() internal val reflectionSymbols: WasmReflectionSymbols = WasmReflectionSymbols()
internal val eagerInitialization: IrClassSymbol = getIrClass(FqName("kotlin.EagerInitialization"))
override val throwNullPointerException = getInternalFunction("THROW_NPE") override val throwNullPointerException = getInternalFunction("THROW_NPE")
override val throwISE = getInternalFunction("THROW_ISE") override val throwISE = getInternalFunction("THROW_ISE")
override val throwTypeCastException = getInternalFunction("THROW_CCE") override val throwTypeCastException = getInternalFunction("THROW_CCE")
@@ -29,6 +29,7 @@ fun compileWasm(
phaseConfig: PhaseConfig, phaseConfig: PhaseConfig,
irFactory: IrFactory, irFactory: IrFactory,
exportedDeclarations: Set<FqName> = emptySet(), exportedDeclarations: Set<FqName> = emptySet(),
propertyLazyInitialization: Boolean,
emitNameSection: Boolean = false, emitNameSection: Boolean = false,
): WasmCompilerResult { ): WasmCompilerResult {
val mainModule = depsDescriptors.mainModule val mainModule = depsDescriptors.mainModule
@@ -46,7 +47,7 @@ fun compileWasm(
} }
val moduleDescriptor = moduleFragment.descriptor 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 // Load declarations referenced during `context` initialization
allModules.forEach { allModules.forEach {
@@ -1,5 +1,4 @@
// IGNORE_BACKEND: JS // IGNORE_BACKEND: JS
// IGNORE_BACKEND: WASM
// PROPERTY_LAZY_INITIALIZATION // PROPERTY_LAZY_INITIALIZATION
// FILE: A.kt // FILE: A.kt
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: JS // IGNORE_BACKEND: JS
// IGNORE_BACKEND: NATIVE // IGNORE_BACKEND: NATIVE
// IGNORE_BACKEND: WASM
// PROPERTY_LAZY_INITIALIZATION // PROPERTY_LAZY_INITIALIZATION
// FILE: A.kt // FILE: A.kt
@@ -1,5 +1,4 @@
// IGNORE_BACKEND: JS // IGNORE_BACKEND: JS
// IGNORE_BACKEND: WASM
// SPLIT_PER_MODULE // SPLIT_PER_MODULE
// PROPERTY_LAZY_INITIALIZATION // PROPERTY_LAZY_INITIALIZATION
@@ -32,10 +32,8 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.resolve.CompilerEnvironment import org.jetbrains.kotlin.resolve.CompilerEnvironment
import org.jetbrains.kotlin.test.DebugMode import org.jetbrains.kotlin.test.*
import org.jetbrains.kotlin.test.Directives import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
import org.jetbrains.kotlin.test.TestFiles
import org.jetbrains.kotlin.test.util.KtTestUtil import org.jetbrains.kotlin.test.util.KtTestUtil
import java.io.Closeable import java.io.Closeable
import java.io.File import java.io.File
@@ -178,11 +176,14 @@ abstract class BasicWasmBoxTest(
AnalyzerWithCompilerReport(config.configuration) AnalyzerWithCompilerReport(config.configuration)
) )
val directives = KotlinTestUtils.parseDirectives(FileUtil.loadFile(testFile))
val compilerResult = compileWasm( val compilerResult = compileWasm(
sourceModule, sourceModule,
phaseConfig = phaseConfig, phaseConfig = phaseConfig,
irFactory = IrFactoryImpl, irFactory = IrFactoryImpl,
exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))), exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))),
propertyLazyInitialization = directives.contains(JsEnvironmentConfigurationDirectives.PROPERTY_LAZY_INITIALIZATION.name),
emitNameSection = true, emitNameSection = true,
) )
@@ -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. * 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 @ExperimentalStdlibApi
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.PROPERTY) @Target(AnnotationTarget.PROPERTY)
@SinceKotlin("1.6") @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") @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