[JS IR BE] Move object instance creation lowerings to IR

This commit is contained in:
Svyatoslav Kuzmich
2019-06-28 18:51:55 +03:00
parent 5dcb0ee016
commit 6d1f893a5a
8 changed files with 120 additions and 55 deletions
@@ -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
)
}
@@ -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))
@@ -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)
}
}
@@ -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())
}
@@ -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}")
@@ -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())