[JS IR] Inline lambda class into its interop wrapper

This commit is contained in:
Roman Artemev
2021-09-17 19:13:59 +07:00
committed by TeamCityServer
parent a1b223e33d
commit 7ec593b22a
@@ -8,33 +8,39 @@ package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.backend.common.ir.copyTo
import org.jetbrains.kotlin.backend.common.ir.copyToWithoutSuperTypes
import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins
import org.jetbrains.kotlin.backend.common.lower.LoweredStatementOrigins
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
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.JsStatementOrigins
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.util.file
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.cast
class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLoweringPass {
private val newDeclarations = mutableListOf<IrDeclaration>()
private lateinit var implicitDeclarationFile: IrFile //= context.implicitDeclarationFile
private lateinit var implicitDeclarationFile: IrFile
private val transformedLambdas = mutableMapOf<IrConstructorSymbol, IrSimpleFunctionSymbol>()
override fun lower(irBody: IrBody, container: IrDeclaration) {
newDeclarations.clear()
implicitDeclarationFile = container.file // TODO
irBody.transformChildrenVoid(CallableReferenceLowerTransformer())
implicitDeclarationFile.declarations += newDeclarations
implicitDeclarationFile.declarations.addAll(newDeclarations)
}
inner class CallableReferenceLowerTransformer : IrElementTransformerVoid() {
@@ -48,11 +54,18 @@ class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLo
}
private fun transformToJavaScriptFunction(expression: IrConstructorCall): IrExpression {
// TODO: perform inlining
val factory = buildFactoryFunction(expression)
newDeclarations += factory
val irConstructor = expression.symbol
// There could be more than one lambda instantiation so don't create redundant copies
// For testcase take a look into `boxInline/suspend/twiceRegeneratedAnonymousObject.kt`
val factory = transformedLambdas.getOrPut(irConstructor) {
buildFactoryFunction(expression).also { f ->
newDeclarations += f
}.symbol
}
val newCall = expression.run {
IrCallImpl(startOffset, endOffset, type, factory.symbol, typeArgumentsCount, valueArgumentsCount, origin)
IrCallImpl(startOffset, endOffset, type, factory, typeArgumentsCount, valueArgumentsCount, origin)
}
newCall.dispatchReceiver = expression.dispatchReceiver
@@ -69,6 +82,45 @@ class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLo
return newCall
}
private fun inlineLambdaBody(
lambdaDeclaration: IrSimpleFunction,
invokeFun: IrSimpleFunction,
invokeMapping: Map<IrValueParameterSymbol, IrValueParameterSymbol>,
factoryMapping: Map<IrFieldSymbol, IrValueParameterSymbol>
): IrBlockBody {
val body = invokeFun.body ?: error("invoke() method has to have a body")
fun IrExpression.getValue(d: IrValueSymbol): IrExpression = IrGetValueImpl(startOffset, endOffset, d)
// TODO: remap type parameters???
body.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitGetField(expression: IrGetField): IrExpression {
expression.transformChildrenVoid()
val parameter = factoryMapping[expression.symbol] ?: return expression
return expression.getValue(parameter)
}
override fun visitGetValue(expression: IrGetValue): IrExpression {
expression.transformChildrenVoid()
val parameter = invokeMapping[expression.symbol] ?: return expression
return expression.getValue(parameter)
}
override fun visitReturn(expression: IrReturn): IrExpression {
expression.transformChildrenVoid()
if (expression.returnTargetSymbol != invokeFun.symbol) return expression
return expression.run {
IrReturnImpl(startOffset, endOffset, type, lambdaDeclaration.symbol, value)
}
}
})
// Fix parents of declarations inside body
body.patchDeclarationParents(lambdaDeclaration)
return body as IrBlockBody
}
private fun buildLambdaBody(instance: IrVariable, lambdaDeclaration: IrSimpleFunction, invokeFun: IrSimpleFunction): IrBlockBody {
val invokeExpression = IrCallImpl(
UNDEFINED_OFFSET,
@@ -103,6 +155,27 @@ class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLo
)
}
private fun capturedFieldsToParametersMap(constructor: IrConstructor, factoryFunction: IrSimpleFunction): Map<IrFieldSymbol, IrValueParameterSymbol> {
val statements = constructor.body?.let { it.cast<IrBlockBody>().statements } ?: error("Expecting Body for function ref constructor")
val fieldSetters = statements.filterIsInstance<IrSetField>()
.filter { it.origin == LoweredStatementOrigins.STATEMENT_ORIGIN_INITIALIZER_OF_FIELD_FOR_CAPTURED_VALUE }
fun remapVP(vp: IrValueParameterSymbol): IrValueParameterSymbol {
return factoryFunction.valueParameters[vp.owner.index].symbol
}
return fieldSetters.associate { it.symbol to remapVP(it.value.cast<IrGetValue>().symbol.cast()) }
}
private fun extractReferenceReflectionName(getter: IrSimpleFunction): IrExpression {
val body = getter.body?.cast<IrBlockBody>() ?: error("Expected body for ${getter.render()}")
val statements = body.statements
val returnStmt = statements[0] as IrReturn
return returnStmt.value
}
private fun buildFactoryBody(factoryFunction: IrSimpleFunction, expression: IrConstructorCall): IrBlockBody {
val constructor = expression.symbol.owner
val lambdaClass = constructor.parentAsClass
@@ -125,26 +198,57 @@ class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLo
lambdaDeclaration.valueParameters = superInvokeFun.valueParameters.map { it.copyTo(lambdaDeclaration) }
val statements = ArrayList<IrStatement>(4)
val isSuspendLambda = invokeFun.overriddenSymbols.any { it.owner.isSuspend }
val instanceVal = JsIrBuilder.buildVar(expression.type, factoryFunction, "i").apply {
initializer = expression.run {
val newCtorCall = IrConstructorCallImpl(startOffset, endOffset, type, symbol, typeArgumentsCount, constructorTypeArgumentsCount, valueArgumentsCount, origin)
// TODO: forward type arguments
assert(expression.dispatchReceiver == null)
assert(expression.extensionReceiver == null)
if (isSuspendLambda) {
// Due to suspend lambda is a class itself it's not easy to inline it correctly and moreover I see no reason to do so
val instanceVal = JsIrBuilder.buildVar(expression.type, factoryFunction, "i").apply {
initializer = expression.run {
val newCtorCall = IrConstructorCallImpl(
startOffset,
endOffset,
type,
symbol,
typeArgumentsCount,
constructorTypeArgumentsCount,
valueArgumentsCount,
origin
)
// TODO: forward type arguments
assert(expression.dispatchReceiver == null)
assert(expression.extensionReceiver == null)
for ((i, vp) in factoryFunction.valueParameters.withIndex()) {
newCtorCall.putValueArgument(i, IrGetValueImpl(startOffset, endOffset, vp.type, vp.symbol))
for ((i, vp) in factoryFunction.valueParameters.withIndex()) {
newCtorCall.putValueArgument(i, IrGetValueImpl(startOffset, endOffset, vp.type, vp.symbol))
}
newCtorCall
}
newCtorCall
}
statements.add(instanceVal)
lambdaDeclaration.body = buildLambdaBody(instanceVal, lambdaDeclaration, invokeFun)
} else {
val fieldToParameterMapping = capturedFieldsToParametersMap(constructor, factoryFunction)
val oldToNewInvokeParametersMapping = mutableMapOf<IrValueParameterSymbol, IrValueParameterSymbol>()
invokeFun.valueParameters.forEach {
oldToNewInvokeParametersMapping[it.symbol] = lambdaDeclaration.valueParameters[it.index].symbol
}
lambdaDeclaration.body =
inlineLambdaBody(lambdaDeclaration, invokeFun, oldToNewInvokeParametersMapping, fieldToParameterMapping)
val classContainer = lambdaClass.parent as IrDeclarationContainer
// lambdas could contain another lambdas and local classes in so let do not lose them
val lambdaInnerClasses =
lambdaClass.declarations.filter { it is IrClass || (it is IrSimpleFunction && it.dispatchReceiverParameter == null) }
classContainer.declarations.remove(lambdaClass)
classContainer.declarations.addAll(lambdaInnerClasses)
lambdaInnerClasses.forEach { it.parent = classContainer }
}
statements.add(instanceVal)
lambdaDeclaration.body = buildLambdaBody(instanceVal, lambdaDeclaration, invokeFun)
val functionExpression =
expression.run { IrFunctionExpressionImpl(startOffset, endOffset, type, lambdaDeclaration, expression.origin!!) }
@@ -155,19 +259,7 @@ class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLo
statements.add(tmpVar)
if (nameGetter != null) {
statements.add(setDynamicProperty(tmpVar.symbol, Namer.KCALLABLE_NAME,
IrCallImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
nameGetter.returnType,
nameGetter.symbol,
0,
0,
JsStatementOrigins.CALLABLE_REFERENCE_INVOKE
).apply {
dispatchReceiver = JsIrBuilder.buildGetValue(instanceVal.symbol)
}
))
statements.add(setDynamicProperty(tmpVar.symbol, Namer.KCALLABLE_NAME, extractReferenceReflectionName(nameGetter)))
}
if (lambdaDeclaration.isSuspend) {