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 fecb09b1c93..f0ea120a56c 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 @@ -134,6 +134,7 @@ class JsIrBackendContext( private val coroutineIntrinsicsPackage = module.getPackage(COROUTINE_INTRINSICS_PACKAGE_FQNAME) val enumEntryToGetInstanceFunction = mutableMapOf() + val objectToGetInstanceFunction = mutableMapOf() val enumEntryExternalToInstanceField = mutableMapOf() val callableReferencesCache = mutableMapOf() val secondaryConstructorToFactoryCache = mutableMapOf() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index 3dcb4f54f36..1171e7e9919 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -364,6 +364,17 @@ private val staticMembersLoweringPhase = makeJsModulePhase( description = "Move static member declarations to top-level" ) +private val objectDeclarationLoweringPhase = makeJsModulePhase( + ::ObjectDeclarationLowering, + name = "ObjectDeclarationLowering", + description = "Create lazy object instance generator functions" +) + +private val objectUsageLoweringPhase = makeJsModulePhase( + ::ObjectUsageLowering, + name = "ObjectUsageLowering", + description = "Transform IrGetObjectValue into instance generator call" +) val jsPhases = namedIrModulePhase( name = "IrModuleLowering", @@ -410,6 +421,8 @@ val jsPhases = namedIrModulePhase( blockDecomposerLoweringPhase then primitiveCompanionLoweringPhase then constLoweringPhase then + objectDeclarationLoweringPhase then + objectUsageLoweringPhase then callsLoweringPhase then staticMembersLoweringPhase then validateIrAfterLowering diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ObjectLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ObjectLowering.kt new file mode 100644 index 00000000000..999e891eacc --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ObjectLowering.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2010-2019 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.lower + +import org.jetbrains.kotlin.backend.common.DeclarationContainerLoweringPass +import org.jetbrains.kotlin.backend.common.FileLoweringPass +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.lower.irBlockBody +import org.jetbrains.kotlin.backend.common.lower.irIfThen +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext +import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.builders.declarations.buildField +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrDeclarationContainer +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.expressions.IrBlockBody +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrGetObjectValue +import org.jetbrains.kotlin.ir.types.makeNullable +import org.jetbrains.kotlin.ir.util.defaultType +import org.jetbrains.kotlin.ir.util.isEffectivelyExternal +import org.jetbrains.kotlin.ir.util.primaryConstructor +import org.jetbrains.kotlin.ir.util.transformDeclarationsFlat +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.name.Name + +class ObjectDeclarationLowering(val context: JsIrBackendContext) : DeclarationContainerLoweringPass { + override fun lower(irDeclarationContainer: IrDeclarationContainer) { + irDeclarationContainer.transformDeclarationsFlat { declaration -> + if (declaration !is IrClass || declaration.kind != ClassKind.OBJECT || declaration.isEffectivelyExternal()) + return@transformDeclarationsFlat null + + val getInstanceFun = getOrCreateGetInstanceFunction(context, declaration) + + val instanceField = buildField { + name = Name.identifier(declaration.name.asString() + "_instance") + type = declaration.defaultType.makeNullable() + isStatic = true + }.apply { + parent = declaration.parent + initializer = null // Initialized with 'undefined' + } + val primaryConstructor = declaration.primaryConstructor!! + val body = primaryConstructor.body as IrBlockBody + + // Initialize instance field in the beginning of the constructor because it can be used inside the constructor later + val initInstanceField = context.createIrBuilder(primaryConstructor.symbol).buildStatement(UNDEFINED_OFFSET, UNDEFINED_OFFSET) { + irSetField(null, instanceField, irGet(declaration.thisReceiver!!)) + } + body.statements.add(0, initInstanceField) + + getInstanceFun.body = context.createIrBuilder(getInstanceFun.symbol).irBlockBody(getInstanceFun) { + +irIfThen( + irEqualsNull(irGetField(null, instanceField)), + // Instance field initialized inside constructor + irCallConstructor(primaryConstructor.symbol, emptyList()) + ) + +irReturn(irGetField(null, instanceField)) + } + + listOf(declaration, instanceField, getInstanceFun) + } + } +} + +class ObjectUsageLowering(val context: JsIrBackendContext) : FileLoweringPass { + override fun lower(irFile: IrFile) { + irFile.transformChildrenVoid(object : IrElementTransformerVoid() { + override fun visitGetObjectValue(expression: IrGetObjectValue): IrExpression { + val obj: IrClass = expression.symbol.owner + if (obj.isEffectivelyExternal()) return expression + return JsIrBuilder.buildCall(getOrCreateGetInstanceFunction(context, obj).symbol) + } + }) + } +} + +fun getOrCreateGetInstanceFunction(context: JsIrBackendContext, obj: IrClass) = + context.objectToGetInstanceFunction.getOrPut(obj.symbol) { + JsIrBuilder.buildFunction( + obj.name.asString() + "_getInstance", + returnType = obj.defaultType, + parent = obj.parent + ) + } \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt index 2555d9812b0..8dfbe988647 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt @@ -87,22 +87,13 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer { - val obj = expression.symbol.owner - if (obj.isEffectivelyExternal()) { - context.getRefForExternalClass(obj) - } else { - val className = context.getNameForClass(expression.symbol.owner) - // TODO: Don't use implicit naming - val getInstanceName = className.ident + "_getInstance" - JsInvocation(JsNameRef(getInstanceName)) - } - } - else -> TODO() + override fun visitGetObjectValue(expression: IrGetObjectValue, context: JsGenerationContext): JsExpression { + val obj = expression.symbol.owner + assert(obj.kind == ClassKind.OBJECT) + assert(obj.isEffectivelyExternal()) { "Non external IrGetObjectValue must be lowered" } + return context.getRefForExternalClass(obj) } - override fun visitSetField(expression: IrSetField, context: JsGenerationContext): JsExpression { val fieldName = context.getNameForField(expression.symbol.owner) val dest = JsNameRef(fieldName, expression.receiver?.accept(this, context)) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrFunctionToJsTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrFunctionToJsTransformer.kt index b4613ef8015..f296a9512c9 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrFunctionToJsTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrFunctionToJsTransformer.kt @@ -9,7 +9,6 @@ import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrConstructor import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction -import org.jetbrains.kotlin.ir.util.isObject import org.jetbrains.kotlin.js.backend.ast.JsFunction @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") @@ -20,13 +19,13 @@ class IrFunctionToJsTransformer : BaseIrElementToJsNodeTransformer { classBlock.statements += JsClassGenerator(declaration, context).generate() } - is IrVariable -> { - classBlock.statements += declaration.accept(transformer, context) + is IrField -> { } else -> { + error("Unexpected declaration in class: $declaration") } } } classBlock.statements += generateClassMetadata() - irClass.onlyIf({ kind == ClassKind.OBJECT }) { classBlock.statements += maybeGenerateObjectInstance() } if (irClass.superTypes.any { it.isThrowable() }) { classBlock.statements += generateThrowableProperties() } else if (!irClass.defaultType.isThrowable()) { @@ -180,29 +179,6 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo return (throwableAccessorImpl(irClass.defaultType).classifierOrFail as IrClassSymbol).owner } - private fun maybeGenerateObjectInstance(): List { - val instanceVarName = className.objectInstanceName() - val getInstanceFunName = "${className.ident}_getInstance" - val jsVarNode = JsName(instanceVarName) // TODO: Use namer? - val varStmt = JsVars(JsVars.JsVar(jsVarNode)) - val function = generateGetInstanceFunction(jsVarNode, getInstanceFunName) - return listOf(varStmt, function.makeStmt()) - } - - private fun generateGetInstanceFunction(instanceVar: JsName, instanceFunName: String): JsFunction { - val functionBody = JsBlock() - val func = JsFunction(emptyScope, functionBody, "getInstance") - func.name = JsName(instanceFunName) // TODO: Use namer? - - functionBody.statements += JsIf( - JsBinaryOperation(JsBinaryOperator.REF_EQ, instanceVar.makeRef(), JsPrefixOperation(JsUnaryOperator.VOID, JsIntLiteral(0))), - jsAssignment(instanceVar.makeRef(), JsNew(classNameRef)).makeStmt() - ) - functionBody.statements += JsReturn(instanceVar.makeRef()) - - return func - } - private fun maybeGeneratePrimaryConstructor() { if (!irClass.declarations.any { it is IrConstructor }) { val func = JsFunction(emptyScope, JsBlock(), "Ctor for ${irClass.name}") diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt index cd4abaf3537..4b4f400093d 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt @@ -42,18 +42,12 @@ fun jsAssignment(left: JsExpression, right: JsExpression) = JsBinaryOperation(Js fun prototypeOf(classNameRef: JsExpression) = JsNameRef(Namer.PROTOTYPE_NAME, classNameRef) -fun translateFunction(declaration: IrFunction, name: JsName?, isObjectConstructor: Boolean, context: JsGenerationContext): JsFunction { +fun translateFunction(declaration: IrFunction, name: JsName?, context: JsGenerationContext): JsFunction { val functionContext = context.newDeclaration(declaration) val functionParams = declaration.valueParameters.map { functionContext.getNameForValueDeclaration(it) } val body = declaration.body?.accept(IrElementToJsStatementTransformer(), functionContext) as? JsBlock ?: JsBlock() - val functionBody = if (isObjectConstructor) { - val instanceName = JsName(name!!.objectInstanceName()) - val assignObject = jsAssignment(JsNameRef(instanceName), JsThisRef()) - JsBlock(assignObject.makeStmt(), body) - } else body - - val function = JsFunction(emptyScope, functionBody, "member function ${name ?: "annon"}") + val function = JsFunction(emptyScope, body, "member function ${name ?: "annon"}") function.name = name @@ -91,9 +85,6 @@ fun translateCallArguments(expression: IrMemberAccessExpression, context: JsGene fun JsStatement.asBlock() = this as? JsBlock ?: JsBlock(this) -// TODO: Don't use implicit name conventions -fun JsName.objectInstanceName() = "${ident}_instance" - fun defineProperty(receiver: JsExpression, name: String, value: () -> JsExpression): JsInvocation { val objectDefineProperty = JsNameRef("defineProperty", Namer.JS_OBJECT) return JsInvocation(objectDefineProperty, receiver, JsStringLiteral(name), value())