[JS IR BE] Move object instance creation lowerings to IR
This commit is contained in:
@@ -134,6 +134,7 @@ class JsIrBackendContext(
|
||||
private val coroutineIntrinsicsPackage = module.getPackage(COROUTINE_INTRINSICS_PACKAGE_FQNAME)
|
||||
|
||||
val enumEntryToGetInstanceFunction = mutableMapOf<IrEnumEntrySymbol, IrSimpleFunction>()
|
||||
val objectToGetInstanceFunction = mutableMapOf<IrClassSymbol, IrSimpleFunction>()
|
||||
val enumEntryExternalToInstanceField = mutableMapOf<IrEnumEntrySymbol, IrField>()
|
||||
val callableReferencesCache = mutableMapOf<CallableReferenceKey, IrSimpleFunction>()
|
||||
val secondaryConstructorToFactoryCache = mutableMapOf<IrConstructor, ConstructorPair>()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
+5
-14
@@ -87,22 +87,13 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
|
||||
override fun visitGetValue(expression: IrGetValue, context: JsGenerationContext): JsExpression =
|
||||
context.getNameForValueDeclaration(expression.symbol.owner).makeRef()
|
||||
|
||||
override fun visitGetObjectValue(expression: IrGetObjectValue, context: JsGenerationContext) = when (expression.symbol.owner.kind) {
|
||||
ClassKind.OBJECT -> {
|
||||
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))
|
||||
|
||||
+2
-3
@@ -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<JsFunction, J
|
||||
} else {
|
||||
context.getNameForMemberFunction(declaration)
|
||||
}
|
||||
return translateFunction(declaration, funcName, false, context)
|
||||
return translateFunction(declaration, funcName, context)
|
||||
}
|
||||
|
||||
override fun visitConstructor(declaration: IrConstructor, context: JsGenerationContext): JsFunction {
|
||||
assert(declaration.isPrimary)
|
||||
val funcName = context.getNameForConstructor(declaration)
|
||||
val constructedClass = declaration.parent as IrClass
|
||||
return translateFunction(declaration, funcName, constructedClass.isObject, context)
|
||||
return translateFunction(declaration, funcName, context)
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -116,7 +116,9 @@ class IrModuleToJsTransformer(
|
||||
val expression =
|
||||
if (declaration is IrClass && declaration.isObject) {
|
||||
// TODO: Use export names for properties
|
||||
defineProperty(internalModuleName.makeRef(), name.ident, getter = JsNameRef("${name.ident}_getInstance"))
|
||||
val instanceGetter = backendContext.objectToGetInstanceFunction[declaration.symbol]!!
|
||||
val instanceGetterName: JsName = context.getNameForStaticFunction(instanceGetter)
|
||||
defineProperty(internalModuleName.makeRef(), name.ident, getter = JsNameRef(instanceGetterName))
|
||||
} else {
|
||||
jsAssignment(JsNameRef(exportName, internalModuleName.makeRef()), name.makeRef())
|
||||
}
|
||||
|
||||
+2
-26
@@ -57,16 +57,15 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
|
||||
is IrClass -> {
|
||||
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<JsStatement> {
|
||||
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}")
|
||||
|
||||
+2
-11
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user