[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
}
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)
@@ -30,6 +30,7 @@ interface JsCommonBackendContext : CommonBackendContext {
override val mapping: JsMapping
val reflectionSymbols: ReflectionSymbols
val propertyLazyInitialization: PropertyLazyInitialization
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 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 {
@@ -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<IrFile, IrSimpleFunction?> = mutableMapOf()
val fileToInitializerPureness: MutableMap<IrFile, Boolean> = mutableMapOf()
val fieldToInitializer: MutableMap<IrField, IrExpression> = mutableMapOf()
val localClassNames: MutableMap<IrClass, String> = 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))
@@ -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.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<IrField, IrExpression>,
initializedField: IrField
): List<IrStatement> {
val statements = initializers
.map { (field, expression) ->
createIrSetField(field, expression)
): IrStatement {
val statements = buildList<IrStatement> {
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<IrExpression>): 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<IrDeclaration>? {
if (!context.propertyLazyInitialization) {
if (!context.propertyLazyInitialization.enabled) {
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
.asSequence()
.filter { it.isCompatibleDeclaration(context) }
@@ -276,9 +279,9 @@ private fun IrDeclaration.propertyWithPersistentSafe(transform: IrDeclaration.()
private fun <T> 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(
@@ -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<WasmBackendContext>(this, irModuleFragment) {
override val symbols: Symbols<WasmBackendContext> = wasmSymbols
override fun shouldGenerateHandlerParameterForDefaultBodyFun() = true
@@ -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
@@ -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")
@@ -29,6 +29,7 @@ fun compileWasm(
phaseConfig: PhaseConfig,
irFactory: IrFactory,
exportedDeclarations: Set<FqName> = 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 {
@@ -1,5 +1,4 @@
// IGNORE_BACKEND: JS
// IGNORE_BACKEND: WASM
// PROPERTY_LAZY_INITIALIZATION
// FILE: A.kt
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: JS
// IGNORE_BACKEND: NATIVE
// IGNORE_BACKEND: WASM
// PROPERTY_LAZY_INITIALIZATION
// FILE: A.kt
@@ -1,5 +1,4 @@
// IGNORE_BACKEND: JS
// IGNORE_BACKEND: WASM
// SPLIT_PER_MODULE
// PROPERTY_LAZY_INITIALIZATION
@@ -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,
)
@@ -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