[JS IR BE] Secondary constructors lowering refactoring

* make it per-file
This commit is contained in:
Roman Artemev
2018-12-22 15:14:00 +03:00
committed by romanart
parent 927b94a6e0
commit c725f4ce32
4 changed files with 228 additions and 240 deletions
@@ -23,15 +23,13 @@ import org.jetbrains.kotlin.ir.SourceManager
import org.jetbrains.kotlin.ir.SourceRangeInfo
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.lower.CallableReferenceKey
import org.jetbrains.kotlin.ir.backend.js.lower.ConstructorPair
import org.jetbrains.kotlin.ir.backend.js.lower.inline.ModuleIndex
import org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrEnumEntrySymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.impl.IrDynamicTypeImpl
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.ir.util.getPropertyDeclaration
@@ -120,6 +118,7 @@ class JsIrBackendContext(
val enumEntryToGetInstanceFunction = mutableMapOf<IrEnumEntrySymbol, IrSimpleFunction>()
val enumEntryExternalToInstanceField = mutableMapOf<IrEnumEntrySymbol, IrField>()
val callableReferencesCache = mutableMapOf<CallableReferenceKey, IrSimpleFunction>()
val secondaryConstructorToFactoryCache = mutableMapOf<IrConstructor, ConstructorPair>()
val coroutineGetContext: IrFunctionSymbol
get() {
@@ -241,6 +240,13 @@ class JsIrBackendContext(
val primitiveClassProperties = primitiveClassesObject.declarations.filterIsInstance<IrProperty>()
val throwableClass = symbolTable.referenceClass(
getClass(JsIrBackendContext.KOTLIN_PACKAGE_FQN.child(Name.identifier("Throwable")))
).owner
val throwableConstructors = throwableClass.declarations.filterIsInstance<IrConstructor>()
val defaultThrowableCtor = throwableConstructors.single { it.valueParameters.size == 0 }
private fun referenceOperators() = OperatorNames.ALL.map { name ->
// TODO to replace KotlinType with IrType we need right equals on IrType
name to irBuiltIns.primitiveTypes.fold(mutableMapOf<KotlinType, IrFunctionSymbol>()) { m, t ->
@@ -246,15 +246,17 @@ private val TypeOperatorLoweringPhase = makeJsPhase(
prerequisite = setOf(BridgesConstructionPhase, RemoveInlineFunctionsWithReifiedTypeParametersLoweringPhase)
)
private val SecondaryCtorLoweringPhase = makeJsPhase(
{ context, module ->
SecondaryCtorLowering(context).run {
constructorProcessorLowering.runOnFilesPostfix(module.files + context.dependencies.flatMap { it.files })
constructorRedirectorLowering.lower(module)
}
},
name = "SecondaryCtorLoweringPhase",
description = "Generate static functions for each secondary constructor and replace usages",
private val SecondaryConstructorLoweringPhase = makeJsPhase(
{ context, module -> SecondaryConstructorLowering(context).lower(module) },
name = "SecondaryConstructorLoweringPhase",
description = "Generate static functions for each secondary constructor",
prerequisite = setOf(InnerClassesLoweringPhase)
)
private val SecondaryFactoryInjectorLoweringPhase = makeJsPhase(
{ context, module -> SecondaryFactoryInjectorLowering(context).lower(module) },
name = "SecondaryFactoryInjectorLoweringPhase",
description = "Replace usage of secondary constructor with corresponding static function",
prerequisite = setOf(InnerClassesLoweringPhase)
)
@@ -355,7 +357,8 @@ val jsPhases = listOf(
MultipleCatchesLoweringPhase,
BridgesConstructionPhase,
TypeOperatorLoweringPhase,
SecondaryCtorLoweringPhase,
SecondaryConstructorLoweringPhase,
SecondaryFactoryInjectorLoweringPhase,
InlineClassLoweringPhase,
AutoboxingTransformerPhase,
BlockDecomposerLoweringPhase,
@@ -5,10 +5,10 @@
package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.DeclarationContainerLoweringPass
import org.jetbrains.kotlin.backend.common.ClassLoweringPass
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.ir.copyTo
import org.jetbrains.kotlin.backend.common.ir.copyTypeParametersFrom
import org.jetbrains.kotlin.backend.common.runOnFilePostfix
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
@@ -19,101 +19,106 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrReturnImpl
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.transformDeclarationsFlat
import org.jetbrains.kotlin.ir.util.transformFlat
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
class SecondaryCtorLowering(val context: JsIrBackendContext) {
data class ConstructorPair(val delegate: IrSimpleFunction, val stub: IrSimpleFunction)
data class ConstructorPair(val delegate: IrSimpleFunctionSymbol, val stub: IrSimpleFunctionSymbol)
class SecondaryConstructorLowering(val context: JsIrBackendContext) : ClassLoweringPass {
private val oldCtorToNewMap = mutableMapOf<IrConstructorSymbol, ConstructorPair>()
private val oldCtorToNewMap = context.secondaryConstructorToFactoryCache
val constructorProcessorLowering = object : DeclarationContainerLoweringPass {
override fun lower(irDeclarationContainer: IrDeclarationContainer) {
irDeclarationContainer.declarations.filterIsInstance<IrClass>().forEach {
if (!it.isInline) // Inline classes are lowered separately
lowerClass(it)
}
}
}
override fun lower(irClass: IrClass) {
if (irClass.isInline) return
val constructorRedirectorLowering = object : DeclarationContainerLoweringPass {
override fun lower(irDeclarationContainer: IrDeclarationContainer) {
if (irDeclarationContainer is IrClass) {
if (!irDeclarationContainer.isInline) // Inline classes are lowered separately
updateConstructorDeclarations(irDeclarationContainer)
}
for (it in irDeclarationContainer.declarations) {
it.accept(CallsiteRedirectionTransformer(), null)
}
}
}
private fun updateConstructorDeclarations(irClass: IrClass) {
irClass.transformDeclarationsFlat {
irClass.declarations.transformFlat {
if (it is IrConstructor) {
oldCtorToNewMap[it.symbol]?.let { (newInit, newCreate) ->
listOf(newInit.owner, newCreate.owner)
}
if (it.isPrimary) null else transformConstructor(it, irClass)
} else null
}
}
private fun lowerClass(irClass: IrClass): List<IrSimpleFunction> {
val className = irClass.name.asString()
val oldConstructors = mutableListOf<IrConstructor>()
val newConstructors = mutableListOf<IrSimpleFunction>()
for (declaration in irClass.declarations) {
if (declaration is IrConstructor && !declaration.isPrimary) {
// TODO delegate name generation
val constructorName = "${className}_init"
// We should split secondary constructor into two functions,
// * Initializer which contains constructor's body and takes just created object as implicit param `$this`
// ** This function is also delegation constructor
// * Creation function which has same signature with original constructor,
// creates new object via `Object.create` builtIn and passes it to corresponding `Init` function
// In other words:
// Foo::constructor(...) {
// body
// }
// =>
// Foo_init_$Init$(..., $this) {
// body[ this = $this ]
// return $this
// }
// Foo_init_$Create$(...) {
// val t = Object.create(Foo.prototype);
// return Foo_init_$Init$(..., t)
// }
val newInitConstructor = createInitConstructor(declaration, irClass, constructorName, irClass.defaultType)
val newCreateConstructor = createCreateConstructor(declaration, newInitConstructor, constructorName, irClass.defaultType)
oldCtorToNewMap[declaration.symbol] = ConstructorPair(newInitConstructor.symbol, newCreateConstructor.symbol)
oldConstructors += declaration
newConstructors += newInitConstructor
newConstructors += newCreateConstructor
}
private fun transformConstructor(constructor: IrConstructor, irClass: IrClass): List<IrSimpleFunction> {
val stubs = oldCtorToNewMap.getOrPut(constructor) {
buildConstructorStubDeclarations(constructor, irClass)
}
return newConstructors
generateStubsBody(constructor, irClass, stubs)
return listOf(stubs.delegate, stubs.stub)
}
private fun generateStubsBody(constructor: IrConstructor, irClass: IrClass, stubs: ConstructorPair) {
// We should split secondary constructor into two functions,
// * Initializer which contains constructor's body and takes just created object as implicit param `$this`
// ** This function is also delegation constructor
// * Creation function which has same signature with original constructor,
// creates new object via `Object.create` builtIn and passes it to corresponding `Init` function
// In other words:
// Foo::constructor(...) {
// body
// }
// =>
// Foo_init_$Init$(..., $this) {
// body[ this = $this ]
// return $this
// }
// Foo_init_$Create$(...) {
// val t = Object.create(Foo.prototype);
// return Foo_init_$Init$(..., t)
// }
generateInitBody(constructor, irClass, stubs.delegate)
generateFactoryBody(constructor, irClass, stubs.stub, stubs.delegate)
}
private fun generateFactoryBody(constructor: IrConstructor, irClass: IrClass, stub: IrSimpleFunction, delegate: IrSimpleFunction) {
val type = irClass.defaultType
val createFunctionIntrinsic = context.intrinsics.jsObjectCreate
val irCreateCall = JsIrBuilder.buildCall(createFunctionIntrinsic.symbol, type, listOf(type))
val irDelegateCall = JsIrBuilder.buildCall(delegate.symbol, type).also { call ->
for (i in 0 until stub.typeParameters.size) {
call.putTypeArgument(i, stub.typeParameters[i].toIrType())
}
for (i in 0 until stub.valueParameters.size) {
call.putValueArgument(i, JsIrBuilder.buildGetValue(stub.valueParameters[i].symbol))
}
call.putValueArgument(constructor.valueParameters.size, irCreateCall)
}
val irReturn = JsIrBuilder.buildReturn(stub.symbol, irDelegateCall, context.irBuiltIns.nothingType)
stub.body = JsIrBuilder.buildBlockBody(listOf(irReturn))
}
private fun generateInitBody(constructor: IrConstructor, irClass: IrClass, delegate: IrSimpleFunction) {
val thisParam = delegate.valueParameters.last()
val oldThisReceiver = irClass.thisReceiver!!
val retStmt = JsIrBuilder.buildReturn(delegate.symbol, JsIrBuilder.buildGetValue(thisParam.symbol), context.irBuiltIns.nothingType)
val statements = (constructor.body!!.deepCopyWithSymbols(delegate) as IrStatementContainer).statements
val oldValueParameters = constructor.valueParameters + oldThisReceiver
// TODO: replace parameters as well
delegate.body = JsIrBuilder.buildBlockBody(statements + retStmt).apply {
transformChildrenVoid(ThisUsageReplaceTransformer(delegate.symbol, oldValueParameters.zip(delegate.valueParameters).toMap()))
}
}
private class ThisUsageReplaceTransformer(
val function: IrFunctionSymbol,
val symbolMapping: Map<IrValueParameter, IrValueParameter>
) :
IrElementTransformerVoid() {
) : IrElementTransformerVoid() {
val newThisSymbol = symbolMapping.values.last().symbol
@@ -125,154 +130,130 @@ class SecondaryCtorLowering(val context: JsIrBackendContext) {
IrGetValueImpl(expression.startOffset, expression.endOffset, newThisSymbol.owner.type, newThisSymbol)
)
override fun visitGetValue(expression: IrGetValue): IrExpression =
if (expression.symbol.owner in symbolMapping) IrGetValueImpl(
expression.startOffset,
expression.endOffset,
expression.type,
symbolMapping[expression.symbol.owner]!!.symbol,
expression.origin
) else {
expression
}
}
private fun createInitConstructor(
declaration: IrConstructor,
klass: IrClass,
name: String,
type: IrType
): IrSimpleFunction {
val thisParam = JsIrBuilder.buildValueParameter("\$this", declaration.valueParameters.size, type)
val oldThisReceiver = klass.thisReceiver!!
val functionName = "${name}_\$Init\$"
return JsIrBuilder.buildFunction(
functionName,
type,
declaration.parent,
declaration.visibility,
Modality.FINAL,
declaration.isInline,
declaration.isExternal
).also {
thisParam.run { parent = it }
val retStmt = JsIrBuilder.buildReturn(it.symbol, JsIrBuilder.buildGetValue(thisParam.symbol), context.irBuiltIns.nothingType)
val statements = (declaration.body!!.deepCopyWithSymbols(it) as IrStatementContainer).statements
it.copyTypeParametersFrom(declaration.parentAsClass)
val newValueParameters = declaration.valueParameters.map { p -> p.copyTo(it) }
it.valueParameters += (newValueParameters + thisParam)
val oldValueParameters = declaration.valueParameters + oldThisReceiver
// TODO: replace parameters as well
it.body = JsIrBuilder.buildBlockBody(statements + retStmt).apply {
transformChildrenVoid(ThisUsageReplaceTransformer(it.symbol, oldValueParameters.zip(it.valueParameters).toMap()))
}
}
}
private fun createCreateConstructor(
declaration: IrConstructor,
ctorImpl: IrSimpleFunction,
name: String,
type: IrType
): IrSimpleFunction {
val functionName = "${name}_\$Create\$"
return JsIrBuilder.buildFunction(
functionName,
type,
declaration.parent,
declaration.visibility,
Modality.FINAL,
declaration.isInline,
declaration.isExternal
).also {
it.copyTypeParametersFrom(declaration.parentAsClass)
it.valueParameters += declaration.valueParameters.map { p -> p.copyTo(it) }
val createFunctionIntrinsic = context.intrinsics.jsObjectCreate
val irCreateCall = JsIrBuilder.buildCall(createFunctionIntrinsic.symbol, type, listOf(type))
val irDelegateCall = JsIrBuilder.buildCall(ctorImpl.symbol, type).also { call ->
for (i in 0 until it.valueParameters.size) {
call.putValueArgument(i, JsIrBuilder.buildGetValue(it.valueParameters[i].symbol))
}
// valueParameters.forEachIndexed { i, p -> it.putValueArgument(i, JsIrBuilder.buildGetValue(p.symbol)) }
call.putValueArgument(declaration.valueParameters.size, irCreateCall)
// typeParameters.mapIndexed { i, t -> ctorImpl.typeParameters[i].descriptor -> }
}
val irReturn = JsIrBuilder.buildReturn(it.symbol, irDelegateCall, context.irBuiltIns.nothingType)
it.body = JsIrBuilder.buildBlockBody(listOf(irReturn))
}
}
inner class CallsiteRedirectionTransformer : IrElementTransformer<IrFunction?> {
override fun visitFunction(declaration: IrFunction, data: IrFunction?): IrStatement = super.visitFunction(declaration, declaration)
override fun visitCall(expression: IrCall, data: IrFunction?): IrElement {
super.visitCall(expression, data)
val target = expression.symbol.owner
if (target is IrConstructor) {
if (!target.isPrimary) {
val ctor = oldCtorToNewMap[target.symbol]
if (ctor != null) {
return redirectCall(expression, ctor.stub)
}
}
}
return expression
}
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall, data: IrFunction?): IrElement {
super.visitDelegatingConstructorCall(expression, data)
val target = expression.symbol
if (target.owner.isPrimary) {
// nothing to do here
return expression
}
val fromPrimary = data!! is IrConstructor
// TODO: what is `deserialized` constructor?
val ctor = oldCtorToNewMap[target] ?: return expression
val newCall = redirectCall(expression, ctor.delegate)
val readThis = if (fromPrimary) {
val thisKlass = expression.symbol.owner.parent as IrClass
val thisSymbol = thisKlass.thisReceiver!!.symbol
IrGetValueImpl(expression.startOffset, expression.endOffset, expression.type, thisSymbol)
} else {
IrGetValueImpl(expression.startOffset, expression.endOffset, expression.type, data.valueParameters.last().symbol)
}
newCall.putValueArgument(expression.valueArgumentsCount, readThis)
return newCall
}
private fun redirectCall(
call: IrFunctionAccessExpression,
newTarget: IrSimpleFunctionSymbol
) = IrCallImpl(call.startOffset, call.endOffset, call.type, newTarget).apply {
// TODO: Should constructors have type arguments
// copyTypeArgumentsFrom(call)
for (i in 0 until call.valueArgumentsCount) {
putValueArgument(i, call.getValueArgument(i))
}
}
override fun visitGetValue(expression: IrGetValue) = symbolMapping[expression.symbol.owner]?.let {
expression.run { IrGetValueImpl(startOffset, endOffset, type, it.symbol, origin) }
} ?: expression
}
}
private fun IrTypeParameter.toIrType() = IrSimpleTypeImpl(symbol, true, emptyList(), emptyList())
private fun buildInitDeclaration(constructor: IrConstructor, irClass: IrClass): IrSimpleFunction {
val type = irClass.defaultType
val constructorName = "${irClass.name}_init"
val functionName = "${constructorName}_\$Init\$"
return JsIrBuilder.buildFunction(
functionName,
type,
constructor.parent,
constructor.visibility,
Modality.FINAL,
constructor.isInline,
constructor.isExternal
).also {
it.copyTypeParametersFrom(constructor.parentAsClass)
constructor.valueParameters.mapTo(it.valueParameters) { p -> p.copyTo(it) }
it.valueParameters += JsIrBuilder.buildValueParameter("\$this", constructor.valueParameters.size, type).apply { parent = it }
}
}
private fun buildFactoryDeclaration(constructor: IrConstructor, irClass: IrClass): IrSimpleFunction {
val type = irClass.defaultType
val constructorName = "${irClass.name}_init"
val functionName = "${constructorName}_\$Create\$"
return JsIrBuilder.buildFunction(
functionName,
type,
constructor.parent,
constructor.visibility,
Modality.FINAL,
constructor.isInline,
constructor.isExternal
).also {
it.copyTypeParametersFrom(constructor.parentAsClass)
it.valueParameters += constructor.valueParameters.map { p -> p.copyTo(it) }
}
}
private fun buildConstructorStubDeclarations(constructor: IrConstructor, klass: IrClass) =
ConstructorPair(buildInitDeclaration(constructor, klass), buildFactoryDeclaration(constructor, klass))
class SecondaryFactoryInjectorLowering(val context: JsIrBackendContext) : FileLoweringPass {
override fun lower(irFile: IrFile) {
irFile.accept(CallsiteRedirectionTransformer(context), null)
}
}
private class CallsiteRedirectionTransformer(context: JsIrBackendContext) : IrElementTransformer<IrFunction?> {
private val oldCtorToNewMap = context.secondaryConstructorToFactoryCache
private val defaultThrowableConstructor = context.defaultThrowableCtor
private val IrFunction.isSecondaryConstructorCall
get() =
this is IrConstructor && !isPrimary && this != defaultThrowableConstructor && !isExternal && !parentAsClass.isInline
override fun visitFunction(declaration: IrFunction, data: IrFunction?): IrStatement = super.visitFunction(declaration, declaration)
override fun visitCall(expression: IrCall, data: IrFunction?): IrElement {
super.visitCall(expression, data)
val target = expression.symbol.owner
return if (target.isSecondaryConstructorCall) {
val constructor = target as IrConstructor
val ctor = oldCtorToNewMap.getOrPut(constructor) {
buildConstructorStubDeclarations(constructor, constructor.parentAsClass)
}
replaceSecondaryConstructorWithFactoryFunction(expression, ctor.stub.symbol, data)
} else expression
}
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall, data: IrFunction?): IrElement {
super.visitDelegatingConstructorCall(expression, data)
val target = expression.symbol.owner
return if (target.isSecondaryConstructorCall) {
val klass = target.parentAsClass
val ctor = oldCtorToNewMap.getOrPut(target) { buildConstructorStubDeclarations(target, klass) }
val newCall = replaceSecondaryConstructorWithFactoryFunction(expression, ctor.delegate.symbol, data)
val readThis = expression.run {
if (data!! is IrConstructor) {
IrGetValueImpl(startOffset, endOffset, type, klass.thisReceiver!!.symbol)
} else {
IrGetValueImpl(startOffset, endOffset, type, data.valueParameters.last().symbol)
}
}
newCall.apply { putValueArgument(expression.valueArgumentsCount, readThis) }
} else expression
}
private fun replaceSecondaryConstructorWithFactoryFunction(
call: IrFunctionAccessExpression,
newTarget: IrSimpleFunctionSymbol,
enclosing: IrFunction?
) = IrCallImpl(call.startOffset, call.endOffset, call.type, newTarget).apply {
// There are 3 possible cases how Type Parameters should be passed
if (call is IrCall) {
// if it was just a normal constructor cal via `IrCall` just copy arguments as is
copyTypeArgumentsFrom(call)
} else {
// if it is `IrDelegatingConstructorCall` figure out from constructor is called and extract parameters from here
// Since `IrConstructor` doesn't have TypeParameters we take them from its class
val typeParameters = enclosing?.let { if (it is IrConstructor) it.parentAsClass.typeParameters else it.typeParameters }!!
for (i in 0 until typeParameters.size) {
putTypeArgument(i, typeParameters[i].toIrType())
}
}
for (i in 0 until call.valueArgumentsCount) {
putValueArgument(i, call.getValueArgument(i))
}
}
}
@@ -44,12 +44,10 @@ class ThrowableSuccessorsLowering(context: JsIrBackendContext) : FileLoweringPas
private val causeName get() = JsIrBuilder.buildString(stringType, "cause")
private val nameName get() = JsIrBuilder.buildString(stringType, "name")
private val throwableClass = context.symbolTable.referenceClass(
context.getClass(JsIrBackendContext.KOTLIN_PACKAGE_FQN.child(Name.identifier("Throwable")))
).owner
private val throwableConstructors = throwableClass.declarations.filterIsInstance<IrConstructor>()
private val throwableClass = context.throwableClass
private val throwableConstructors = context.throwableConstructors
private val defaultCtor = throwableConstructors.single { it.valueParameters.size == 0 }
private val defaultCtor = context.defaultThrowableCtor
private val toString =
throwableClass.declarations.filterIsInstance<IrSimpleFunction>().single { it.name == Name.identifier("toString") }