JVM IR: Fixes in SAM lowering
This commit is contained in:
committed by
max-kammerer
parent
829102b3e3
commit
c77fa1ecd6
+93
-67
@@ -10,7 +10,6 @@ import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
|
|||||||
import org.jetbrains.kotlin.backend.common.ir.copyTo
|
import org.jetbrains.kotlin.backend.common.ir.copyTo
|
||||||
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
|
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
|
||||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||||
import org.jetbrains.kotlin.backend.common.lower.irBlock
|
|
||||||
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
||||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||||
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
||||||
@@ -20,6 +19,7 @@ import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder
|
|||||||
import org.jetbrains.kotlin.backend.jvm.ir.irArray
|
import org.jetbrains.kotlin.backend.jvm.ir.irArray
|
||||||
import org.jetbrains.kotlin.backend.jvm.ir.isInlineParameter
|
import org.jetbrains.kotlin.backend.jvm.ir.isInlineParameter
|
||||||
import org.jetbrains.kotlin.codegen.PropertyReferenceCodegen
|
import org.jetbrains.kotlin.codegen.PropertyReferenceCodegen
|
||||||
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||||
import org.jetbrains.kotlin.ir.IrStatement
|
import org.jetbrains.kotlin.ir.IrStatement
|
||||||
import org.jetbrains.kotlin.ir.builders.*
|
import org.jetbrains.kotlin.ir.builders.*
|
||||||
@@ -32,10 +32,7 @@ import org.jetbrains.kotlin.ir.expressions.*
|
|||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||||
import org.jetbrains.kotlin.ir.types.IrSimpleType
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
import org.jetbrains.kotlin.ir.types.IrType
|
|
||||||
import org.jetbrains.kotlin.ir.types.IrTypeProjection
|
|
||||||
import org.jetbrains.kotlin.ir.types.typeWith
|
|
||||||
import org.jetbrains.kotlin.ir.util.*
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||||
@@ -51,9 +48,7 @@ internal val callableReferencePhase = makeIrFilePhase(
|
|||||||
|
|
||||||
// Originally copied from K/Native
|
// Originally copied from K/Native
|
||||||
internal class CallableReferenceLowering(private val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() {
|
internal class CallableReferenceLowering(private val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() {
|
||||||
// This pass ignores function references used in inline arguments to inline functions references or in SAM conversions.
|
// This pass ignores suspend function references and function references used in inline arguments to inline functions.
|
||||||
// We also implicitly ignore all suspend function references by only dealing with subclasses of (K)Function and not
|
|
||||||
// (K)SuspendFunction.
|
|
||||||
private val ignoredFunctionReferences = mutableSetOf<IrFunctionReference>()
|
private val ignoredFunctionReferences = mutableSetOf<IrFunctionReference>()
|
||||||
|
|
||||||
private val IrFunctionReference.isIgnored: Boolean
|
private val IrFunctionReference.isIgnored: Boolean
|
||||||
@@ -83,19 +78,6 @@ internal class CallableReferenceLowering(private val context: JvmBackendContext)
|
|||||||
return super.visitFunctionAccess(expression)
|
return super.visitFunctionAccess(expression)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore function references handled in SAM conversion
|
|
||||||
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
|
|
||||||
if (expression.operator == IrTypeOperator.SAM_CONVERSION) {
|
|
||||||
val invokable = expression.argument
|
|
||||||
if (invokable is IrFunctionReference) {
|
|
||||||
ignoredFunctionReferences += invokable
|
|
||||||
} else if (invokable is IrBlock && invokable.statements.last() is IrFunctionReference) {
|
|
||||||
ignoredFunctionReferences += invokable.statements.last() as IrFunctionReference
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visitTypeOperator(expression)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitBlock(expression: IrBlock): IrExpression {
|
override fun visitBlock(expression: IrBlock): IrExpression {
|
||||||
if (!expression.origin.isLambda)
|
if (!expression.origin.isLambda)
|
||||||
return super.visitBlock(expression)
|
return super.visitBlock(expression)
|
||||||
@@ -114,10 +96,31 @@ internal class CallableReferenceLowering(private val context: JvmBackendContext)
|
|||||||
return if (expression.isIgnored) expression else FunctionReferenceBuilder(expression).build()
|
return if (expression.isIgnored) expression else FunctionReferenceBuilder(expression).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class FunctionReferenceBuilder(val irFunctionReference: IrFunctionReference) {
|
// Handle SAM conversions which wrap a function reference:
|
||||||
private val isLambda = irFunctionReference.origin.isLambda
|
// class sam$n(private val receiver: R) : Interface { override fun method(...) = receiver.target(...) }
|
||||||
|
//
|
||||||
|
// This avoids materializing an invokable KFunction representing, thus producing one less class.
|
||||||
|
// This is actually very common, as `Interface { something }` is a local function + a SAM-conversion
|
||||||
|
// of a reference to it into an implementation.
|
||||||
|
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
|
||||||
|
if (expression.operator == IrTypeOperator.SAM_CONVERSION) {
|
||||||
|
val invokable = expression.argument
|
||||||
|
val reference = if (invokable is IrFunctionReference) {
|
||||||
|
invokable
|
||||||
|
} else if (invokable is IrBlock && invokable.origin.isLambda && invokable.statements.last() is IrFunctionReference) {
|
||||||
|
invokable.statements.dropLast(1).forEach { it.transform(this, null) }
|
||||||
|
invokable.statements.last() as IrFunctionReference
|
||||||
|
} else {
|
||||||
|
return super.visitTypeOperator(expression)
|
||||||
|
}
|
||||||
|
reference.transformChildrenVoid()
|
||||||
|
return FunctionReferenceBuilder(reference, expression.typeOperand).build()
|
||||||
|
}
|
||||||
|
return super.visitTypeOperator(expression)
|
||||||
|
}
|
||||||
|
|
||||||
private val functionReferenceOrLambda = if (isLambda) context.ir.symbols.lambdaClass else context.ir.symbols.functionReference
|
private inner class FunctionReferenceBuilder(val irFunctionReference: IrFunctionReference, val samSuperType: IrType? = null) {
|
||||||
|
private val isLambda = irFunctionReference.origin.isLambda
|
||||||
|
|
||||||
private val callee = irFunctionReference.symbol.owner
|
private val callee = irFunctionReference.symbol.owner
|
||||||
|
|
||||||
@@ -133,38 +136,46 @@ internal class CallableReferenceLowering(private val context: JvmBackendContext)
|
|||||||
|
|
||||||
private val typeArgumentsMap = irFunctionReference.typeSubstitutionMap
|
private val typeArgumentsMap = irFunctionReference.typeSubstitutionMap
|
||||||
|
|
||||||
|
private val functionSuperClass =
|
||||||
|
samSuperType?.classOrNull ?: context.ir.symbols.getJvmFunctionClass(argumentTypes.size)
|
||||||
|
private val superMethod =
|
||||||
|
functionSuperClass.functions.single { it.owner.modality == Modality.ABSTRACT }
|
||||||
|
private val superType =
|
||||||
|
samSuperType ?: (if (isLambda) context.ir.symbols.lambdaClass else context.ir.symbols.functionReference).owner.typeWith()
|
||||||
|
|
||||||
private val functionReferenceClass = buildClass {
|
private val functionReferenceClass = buildClass {
|
||||||
setSourceRange(irFunctionReference)
|
setSourceRange(irFunctionReference)
|
||||||
visibility = Visibilities.LOCAL
|
visibility = Visibilities.LOCAL
|
||||||
// A callable reference results in a synthetic class, while a lambda is not synthetic.
|
// A callable reference results in a synthetic class, while a lambda is not synthetic.
|
||||||
|
// We don't produce GENERATED_SAM_IMPLEMENTATION, which is always synthetic.
|
||||||
origin = if (isLambda) JvmLoweredDeclarationOrigin.LAMBDA_IMPL else JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL
|
origin = if (isLambda) JvmLoweredDeclarationOrigin.LAMBDA_IMPL else JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL
|
||||||
name = Name.special("<function reference to ${callee.fqNameWhenAvailable}>")
|
name = Name.special("<function reference to ${callee.fqNameWhenAvailable}>")
|
||||||
}.apply {
|
}.apply {
|
||||||
parent = currentDeclarationParent
|
parent = currentDeclarationParent
|
||||||
superTypes += functionReferenceOrLambda.owner.defaultType
|
superTypes += superType
|
||||||
|
if (samSuperType == null)
|
||||||
|
superTypes += functionSuperClass.typeWith(parameterTypes)
|
||||||
createImplicitParameterDeclarationWithWrappedDescriptor()
|
createImplicitParameterDeclarationWithWrappedDescriptor()
|
||||||
copyAttributes(irFunctionReference)
|
copyAttributes(irFunctionReference)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build(): IrExpression {
|
fun build(): IrExpression = context.createIrBuilder(currentScope!!.scope.scopeOwnerSymbol).irBlock {
|
||||||
val constructor = createConstructor()
|
val constructor = createConstructor()
|
||||||
val invokeMethod = createInvokeMethod()
|
createInvokeMethod(
|
||||||
|
if (samSuperType != null && boundReceiver != null) {
|
||||||
|
irTemporary(boundReceiver.second)
|
||||||
|
} else null
|
||||||
|
)
|
||||||
|
|
||||||
val functionClass = context.ir.symbols.getJvmFunctionClass(argumentTypes.size)
|
if (!isLambda && samSuperType == null) {
|
||||||
functionReferenceClass.superTypes += functionClass.typeWith(parameterTypes)
|
|
||||||
invokeMethod.overriddenSymbols += functionClass.functions.single { it.owner.name.asString() == "invoke" }
|
|
||||||
|
|
||||||
if (!isLambda) {
|
|
||||||
createGetSignatureMethod(functionGetSignature)
|
createGetSignatureMethod(functionGetSignature)
|
||||||
createGetNameMethod(functionGetName)
|
createGetNameMethod(functionGetName)
|
||||||
createGetOwnerMethod(functionGetOwner)
|
createGetOwnerMethod(functionGetOwner)
|
||||||
}
|
}
|
||||||
|
|
||||||
return context.createIrBuilder(currentScope!!.scope.scopeOwnerSymbol).irBlock(irFunctionReference) {
|
+functionReferenceClass
|
||||||
+functionReferenceClass
|
+irCall(constructor.symbol).apply {
|
||||||
+irCall(constructor.symbol).apply {
|
if (valueArgumentsCount > 0) putValueArgument(0, boundReceiver!!.second)
|
||||||
boundReceiver?.second?.let { putValueArgument(0, it) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,39 +187,49 @@ internal class CallableReferenceLowering(private val context: JvmBackendContext)
|
|||||||
isPrimary = true
|
isPrimary = true
|
||||||
}.apply {
|
}.apply {
|
||||||
// Add receiver parameter for bound function references
|
// Add receiver parameter for bound function references
|
||||||
boundReceiver?.first?.let { param ->
|
if (samSuperType == null) {
|
||||||
valueParameters += param.copyTo(
|
boundReceiver?.first?.let { param ->
|
||||||
irFunction = this,
|
valueParameters += param.copyTo(
|
||||||
index = valueParameters.size,
|
irFunction = this,
|
||||||
type = param.type.substitute(typeArgumentsMap)
|
index = 0,
|
||||||
)
|
type = param.type.substitute(typeArgumentsMap)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Super constructor:
|
// Super constructor:
|
||||||
|
// - For SAM references, the super class is Any
|
||||||
// - For function references with bound receivers, accepts arity and receiver
|
// - For function references with bound receivers, accepts arity and receiver
|
||||||
// - For lambdas and function references without bound receivers, accepts arity
|
// - For lambdas and function references without bound receivers, accepts arity
|
||||||
val kFunctionRefConstructor = functionReferenceOrLambda.owner.constructors.single {
|
val constructor = if (samSuperType != null) {
|
||||||
it.valueParameters.size == if (boundReceiver != null) 2 else 1
|
context.irBuiltIns.anyClass.owner.constructors.single()
|
||||||
|
} else {
|
||||||
|
superType.getClass()!!.constructors.single {
|
||||||
|
it.valueParameters.size == if (boundReceiver != null) 2 else 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body = context.createIrBuilder(symbol).irBlockBody(startOffset, endOffset) {
|
body = context.createIrBuilder(symbol).irBlockBody(startOffset, endOffset) {
|
||||||
+irDelegatingConstructorCall(kFunctionRefConstructor).apply {
|
+irDelegatingConstructorCall(constructor).apply {
|
||||||
putValueArgument(0, irInt(argumentTypes.size))
|
if (samSuperType == null) {
|
||||||
if (boundReceiver != null)
|
putValueArgument(0, irInt(argumentTypes.size))
|
||||||
putValueArgument(1, irGet(valueParameters.first()))
|
if (boundReceiver != null)
|
||||||
|
putValueArgument(1, irGet(valueParameters.first()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+IrInstanceInitializerCallImpl(startOffset, endOffset, functionReferenceClass.symbol, context.irBuiltIns.unitType)
|
+IrInstanceInitializerCallImpl(startOffset, endOffset, functionReferenceClass.symbol, context.irBuiltIns.unitType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createInvokeMethod(): IrSimpleFunction =
|
private fun createInvokeMethod(receiverVar: IrValueDeclaration?): IrSimpleFunction =
|
||||||
functionReferenceClass.addFunction {
|
functionReferenceClass.addFunction {
|
||||||
name = Name.identifier("invoke")
|
name = superMethod.owner.name
|
||||||
returnType = callee.returnType
|
returnType = callee.returnType
|
||||||
isSuspend = callee.isSuspend
|
isSuspend = callee.isSuspend
|
||||||
}.apply {
|
}.apply {
|
||||||
|
overriddenSymbols += superMethod
|
||||||
dispatchReceiverParameter = parentAsClass.thisReceiver!!.copyTo(this)
|
dispatchReceiverParameter = parentAsClass.thisReceiver!!.copyTo(this)
|
||||||
if (isLambda) createLambdaInvokeMethod() else createFunctionReferenceInvokeMethod()
|
if (isLambda) createLambdaInvokeMethod() else createFunctionReferenceInvokeMethod(receiverVar)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inline the body of an anonymous function into the generated lambda subclass.
|
// Inline the body of an anonymous function into the generated lambda subclass.
|
||||||
@@ -248,7 +269,7 @@ internal class CallableReferenceLowering(private val context: JvmBackendContext)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrSimpleFunction.createFunctionReferenceInvokeMethod() {
|
private fun IrSimpleFunction.createFunctionReferenceInvokeMethod(receiver: IrValueDeclaration?) {
|
||||||
for ((index, argumentType) in argumentTypes.withIndex()) {
|
for ((index, argumentType) in argumentTypes.withIndex()) {
|
||||||
addValueParameter {
|
addValueParameter {
|
||||||
name = Name.identifier("p$index")
|
name = Name.identifier("p$index")
|
||||||
@@ -266,22 +287,31 @@ internal class CallableReferenceLowering(private val context: JvmBackendContext)
|
|||||||
for (parameter in callee.explicitParameters) {
|
for (parameter in callee.explicitParameters) {
|
||||||
when {
|
when {
|
||||||
boundReceiver?.first == parameter ->
|
boundReceiver?.first == parameter ->
|
||||||
// Bound receiver parameter
|
// Bound receiver parameter. For function references, this is stored in a field of the superclass.
|
||||||
irImplicitCast(
|
// For sam references, we just capture the value in a local variable and LocalDeclarationsLowering
|
||||||
irGetField(irGet(dispatchReceiverParameter!!), functionReferenceReceiverField),
|
// will put it into a field.
|
||||||
boundReceiver.second.type
|
if (samSuperType == null)
|
||||||
)
|
irImplicitCast(
|
||||||
|
irGetField(irGet(dispatchReceiverParameter!!), functionReferenceReceiverField),
|
||||||
|
boundReceiver.second.type
|
||||||
|
)
|
||||||
|
else
|
||||||
|
irGet(receiver!!)
|
||||||
|
|
||||||
// If a vararg parameter corresponds to exactly one KFunction argument, which is an array, that array
|
// If a vararg parameter corresponds to exactly one KFunction argument, which is an array, that array
|
||||||
// is forwarded as is. In all other cases, excess arguments are packed into a new array.
|
// is forwarded as is.
|
||||||
//
|
//
|
||||||
// fun f(x: (Int, Array<String>) -> String) = x(0, arrayOf("OK", "FAIL"))
|
// fun f(x: (Int, Array<String>) -> String) = x(0, arrayOf("OK", "FAIL"))
|
||||||
// fun g(x: (Int, String, String) -> String) = x(0, "OK", "FAIL")
|
|
||||||
// fun h(i: Int, vararg xs: String) = xs[i]
|
// fun h(i: Int, vararg xs: String) = xs[i]
|
||||||
// f(::h) == g(::h)
|
// f(::h)
|
||||||
//
|
//
|
||||||
parameter.isVararg && unboundIndex < argumentTypes.size && parameter.type == valueParameters[unboundIndex].type ->
|
parameter.isVararg && unboundIndex < argumentTypes.size && parameter.type == valueParameters[unboundIndex].type ->
|
||||||
irArray(parameter.type) { addSpread(irGet(valueParameters[unboundIndex++])) }
|
irGet(valueParameters[unboundIndex++])
|
||||||
|
// In all other cases, excess arguments are packed into a new array.
|
||||||
|
//
|
||||||
|
// fun g(x: (Int, String, String) -> String) = x(0, "OK", "FAIL")
|
||||||
|
// f(::h) == g(::h)
|
||||||
|
//
|
||||||
parameter.isVararg && (unboundIndex < argumentTypes.size || !parameter.hasDefaultValue()) ->
|
parameter.isVararg && (unboundIndex < argumentTypes.size || !parameter.hasDefaultValue()) ->
|
||||||
irArray(parameter.type) {
|
irArray(parameter.type) {
|
||||||
(unboundIndex until argumentTypes.size).forEach { +irGet(valueParameters[unboundIndex++]) }
|
(unboundIndex until argumentTypes.size).forEach { +irGet(valueParameters[unboundIndex++]) }
|
||||||
@@ -341,11 +371,7 @@ internal class CallableReferenceLowering(private val context: JvmBackendContext)
|
|||||||
companion object {
|
companion object {
|
||||||
private fun IrBuilderWithScope.kClassReference(classType: IrType) =
|
private fun IrBuilderWithScope.kClassReference(classType: IrType) =
|
||||||
IrClassReferenceImpl(
|
IrClassReferenceImpl(
|
||||||
startOffset,
|
startOffset, endOffset, context.irBuiltIns.kClassClass.typeWith(), context.irBuiltIns.kClassClass, classType
|
||||||
endOffset,
|
|
||||||
context.irBuiltIns.kClassClass.typeWith(),
|
|
||||||
context.irBuiltIns.kClassClass,
|
|
||||||
classType
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun IrBuilderWithScope.kClassToJavaClass(kClassReference: IrExpression, context: JvmBackendContext) =
|
private fun IrBuilderWithScope.kClassToJavaClass(kClassReference: IrExpression, context: JvmBackendContext) =
|
||||||
|
|||||||
+44
-126
@@ -20,8 +20,12 @@ import org.jetbrains.kotlin.descriptors.Modality
|
|||||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||||
import org.jetbrains.kotlin.ir.builders.*
|
import org.jetbrains.kotlin.ir.builders.*
|
||||||
import org.jetbrains.kotlin.ir.builders.declarations.*
|
import org.jetbrains.kotlin.ir.builders.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.IrAttributeContainer
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.copyAttributes
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl
|
||||||
import org.jetbrains.kotlin.ir.types.IrType
|
import org.jetbrains.kotlin.ir.types.IrType
|
||||||
import org.jetbrains.kotlin.ir.types.classifierOrFail
|
import org.jetbrains.kotlin.ir.types.classifierOrFail
|
||||||
@@ -44,7 +48,6 @@ internal val singleAbstractMethodPhase = makeIrFilePhase(
|
|||||||
class SingleAbstractMethodLowering(val context: CommonBackendContext) : ClassLoweringPass {
|
class SingleAbstractMethodLowering(val context: CommonBackendContext) : ClassLoweringPass {
|
||||||
override fun lower(irClass: IrClass) {
|
override fun lower(irClass: IrClass) {
|
||||||
val cachedImplementations = mutableMapOf<Pair<IrType, IrType>, IrClass>()
|
val cachedImplementations = mutableMapOf<Pair<IrType, IrType>, IrClass>()
|
||||||
val localImplementations = mutableListOf<IrClass>()
|
|
||||||
irClass.transformChildrenVoid(object : IrElementTransformerVoidWithContext() {
|
irClass.transformChildrenVoid(object : IrElementTransformerVoidWithContext() {
|
||||||
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
|
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
|
||||||
if (expression.operator != IrTypeOperator.SAM_CONVERSION)
|
if (expression.operator != IrTypeOperator.SAM_CONVERSION)
|
||||||
@@ -53,32 +56,32 @@ class SingleAbstractMethodLowering(val context: CommonBackendContext) : ClassLow
|
|||||||
val invokable = expression.argument.transform(this, null)
|
val invokable = expression.argument.transform(this, null)
|
||||||
// Running in the class context, so empty scope means this is a field/anonymous initializer.
|
// Running in the class context, so empty scope means this is a field/anonymous initializer.
|
||||||
val scopeOwnerSymbol = currentScope?.scope?.scopeOwnerSymbol ?: irClass.symbol
|
val scopeOwnerSymbol = currentScope?.scope?.scopeOwnerSymbol ?: irClass.symbol
|
||||||
return context.createIrBuilder(scopeOwnerSymbol).irBlock(invokable, null, superType) {
|
context.createIrBuilder(scopeOwnerSymbol).apply {
|
||||||
// Not an exhaustive check, but enough to cover the most common cases.
|
// Do not generate a wrapper class for null, it has no invoke() anyway.
|
||||||
if (invokable is IrFunctionReference) {
|
if (invokable.isNullConst())
|
||||||
// Runnable(::function)
|
return invokable
|
||||||
+createFunctionProxyInstance(superType, invokable)
|
|
||||||
} else if (invokable is IrBlock && invokable.statements.last() is IrFunctionReference) {
|
val implementation = cachedImplementations.getOrPut(superType to invokable.type) {
|
||||||
// Runnable { lambda }
|
createObjectProxy(superType, invokable.type, expression)
|
||||||
for (statement in invokable.statements.dropLast(1))
|
}
|
||||||
+statement
|
|
||||||
+createFunctionProxyInstance(superType, invokable.statements.last() as IrFunctionReference)
|
return if (superType.isNullable() && invokable.type.isNullable()) {
|
||||||
|
irBlock(invokable, null, superType) {
|
||||||
|
val invokableVariable = irTemporary(invokable)
|
||||||
|
val instance = irCall(implementation.constructors.single()).apply {
|
||||||
|
putValueArgument(0, irGet(invokableVariable))
|
||||||
|
}
|
||||||
|
irIfNull(superType, irGet(invokableVariable), irNull(), instance)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fall back to materializing an invokable object.
|
irCall(implementation.constructors.single()).apply { putValueArgument(0, invokable) }
|
||||||
+createObjectProxyInstance(superType, invokable, expression)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct a class that implements the specified SAM interface and contains a bunch of fields
|
// Construct a class that wraps an invokable object into an implementation of an interface:
|
||||||
// in which the arguments to the constructor are stored:
|
// class sam$n(private val invokable: F) : Interface { override fun method(...) = invokable(...) }
|
||||||
// class sam$n(private val arg0: T0, ...) { override fun method(...) = <whatever buildOverride returns> }
|
private fun createObjectProxy(superType: IrType, invokableType: IrType, attributeOwner: IrAttributeContainer): IrClass {
|
||||||
private fun implement(
|
|
||||||
superType: IrType,
|
|
||||||
parameters: List<IrType>,
|
|
||||||
attributeOwner: IrAttributeContainer,
|
|
||||||
buildOverride: IrSimpleFunction.(List<IrField>) -> IrBody
|
|
||||||
): IrClass {
|
|
||||||
val superClass = superType.classifierOrFail.owner as IrClass
|
val superClass = superType.classifierOrFail.owner as IrClass
|
||||||
// The language documentation prohibits casting lambdas to classes, but if it was allowed,
|
// The language documentation prohibits casting lambdas to classes, but if it was allowed,
|
||||||
// the `irDelegatingConstructorCall` in the constructor below would need to be modified.
|
// the `irDelegatingConstructorCall` in the constructor below would need to be modified.
|
||||||
@@ -94,30 +97,26 @@ class SingleAbstractMethodLowering(val context: CommonBackendContext) : ClassLow
|
|||||||
superTypes += superType
|
superTypes += superType
|
||||||
}.copyAttributes(attributeOwner)
|
}.copyAttributes(attributeOwner)
|
||||||
|
|
||||||
val fields = parameters.mapIndexed { i, fieldType ->
|
val field = subclass.addField {
|
||||||
subclass.addField {
|
name = Name.identifier("arg0")
|
||||||
name = Name.identifier("arg$i")
|
type = invokableType
|
||||||
type = fieldType
|
origin = subclass.origin
|
||||||
origin = subclass.origin
|
visibility = Visibilities.PRIVATE
|
||||||
visibility = Visibilities.PRIVATE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subclass.addConstructor {
|
subclass.addConstructor {
|
||||||
origin = subclass.origin
|
origin = subclass.origin
|
||||||
isPrimary = true
|
isPrimary = true
|
||||||
}.apply {
|
}.apply {
|
||||||
for (field in fields) {
|
val parameter = addValueParameter {
|
||||||
addValueParameter {
|
name = field.name
|
||||||
name = field.name
|
type = field.type
|
||||||
type = field.type
|
origin = subclass.origin
|
||||||
origin = subclass.origin
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body = context.createIrBuilder(symbol).irBlockBody(startOffset, endOffset) {
|
body = context.createIrBuilder(symbol).irBlockBody(startOffset, endOffset) {
|
||||||
+irDelegatingConstructorCall(context.irBuiltIns.anyClass.owner.constructors.single())
|
+irDelegatingConstructorCall(context.irBuiltIns.anyClass.owner.constructors.single())
|
||||||
for ((field, parameter) in fields.zip(valueParameters))
|
+irSetField(irGet(subclass.thisReceiver!!), field, irGet(parameter))
|
||||||
+irSetField(irGet(subclass.thisReceiver!!), field, irGet(parameter))
|
|
||||||
+IrInstanceInitializerCallImpl(startOffset, endOffset, subclass.symbol, context.irBuiltIns.unitType)
|
+IrInstanceInitializerCallImpl(startOffset, endOffset, subclass.symbol, context.irBuiltIns.unitType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,102 +128,21 @@ class SingleAbstractMethodLowering(val context: CommonBackendContext) : ClassLow
|
|||||||
visibility = superMethod.visibility
|
visibility = superMethod.visibility
|
||||||
origin = subclass.origin
|
origin = subclass.origin
|
||||||
}.apply {
|
}.apply {
|
||||||
overriddenSymbols.add(superMethod.symbol)
|
overriddenSymbols += superMethod.symbol
|
||||||
dispatchReceiverParameter = subclass.thisReceiver!!.copyTo(this)
|
dispatchReceiverParameter = subclass.thisReceiver!!.copyTo(this)
|
||||||
superMethod.valueParameters.mapTo(valueParameters) { it.copyTo(this) }
|
superMethod.valueParameters.mapTo(valueParameters) { it.copyTo(this) }
|
||||||
body = buildOverride(fields)
|
val invokableClass = invokableType.classifierOrFail.owner as IrClass
|
||||||
}
|
body = context.createIrBuilder(symbol).run {
|
||||||
|
irExprBody(irCall(invokableClass.functions.single { it.name == OperatorNameConventions.INVOKE }).apply {
|
||||||
return subclass
|
dispatchReceiver = irGetField(irGet(dispatchReceiverParameter!!), field)
|
||||||
}
|
|
||||||
|
|
||||||
// Construct a class that wraps an invokable object into an implementation of an interface:
|
|
||||||
// class sam$n(private val invokable: F) : Interface { override fun method(...) = invokable(...) }
|
|
||||||
private fun createObjectProxy(superType: IrType, invokableType: IrType, attributeOwner: IrAttributeContainer): IrClass =
|
|
||||||
implement(superType, listOf(invokableType), attributeOwner) { fields ->
|
|
||||||
context.createIrBuilder(symbol).irBlockBody(startOffset, endOffset) {
|
|
||||||
val invokableClass = invokableType.classifierOrFail.owner as IrClass
|
|
||||||
+irReturn(irCall(invokableClass.functions.single { it.name == OperatorNameConventions.INVOKE }).apply {
|
|
||||||
dispatchReceiver = irGetField(irGet(dispatchReceiverParameter!!), fields[0])
|
|
||||||
valueParameters.forEachIndexed { i, parameter -> putValueArgument(i, irGet(parameter)) }
|
valueParameters.forEachIndexed { i, parameter -> putValueArgument(i, irGet(parameter)) }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrBlockBuilder.createObjectProxyInstance(
|
return subclass
|
||||||
superType: IrType, invokable: IrExpression, attributeOwner: IrAttributeContainer
|
|
||||||
): IrExpression {
|
|
||||||
// Do not generate a wrapper class for null, it has no invoke() anyway.
|
|
||||||
if (invokable.isNullConst())
|
|
||||||
return invokable
|
|
||||||
val implementation = cachedImplementations.getOrPut(superType to invokable.type) {
|
|
||||||
createObjectProxy(superType, invokable.type, attributeOwner)
|
|
||||||
}
|
|
||||||
return if (superType.isNullable() && invokable.type.isNullable()) {
|
|
||||||
val invokableVariable = irTemporary(invokable)
|
|
||||||
val instance = irCall(implementation.constructors.single()).apply { putValueArgument(0, irGet(invokableVariable)) }
|
|
||||||
irIfNull(superType, irGet(invokableVariable), irNull(), instance)
|
|
||||||
} else {
|
|
||||||
irCall(implementation.constructors.single()).apply { putValueArgument(0, invokable) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This lowering is located after LocalDeclarationsLowering. On the one hand, this allows us to cache
|
|
||||||
// object-wrapping classes without checking if the types are local declarations or not. On the other hand,
|
|
||||||
// this means we need to manually create fields for all bound arguments when wrapping callable references.
|
|
||||||
private val IrFunctionReference.arguments: List<IrExpression?>
|
|
||||||
get() = listOf(dispatchReceiver).filter { symbol.owner.dispatchReceiverParameter != null } +
|
|
||||||
listOf(extensionReceiver).filter { symbol.owner.extensionReceiverParameter != null } +
|
|
||||||
(0 until valueArgumentsCount).map(::getValueArgument)
|
|
||||||
|
|
||||||
// Construct a class that forwards method calls to an existing function/method:
|
|
||||||
// class sam$n(private val receiver: R) : Interface { override fun method(...) = receiver.target(...) }
|
|
||||||
//
|
|
||||||
// Unlike the above variant, this avoids materializing an invokable KFunction representing
|
|
||||||
// the target in CallableReferenceLowering, thus producing one less class. This is actually very
|
|
||||||
// common, as `Interface { something }` is a local function + a SAM-conversion of a reference
|
|
||||||
// to it into an implementation.
|
|
||||||
private fun createFunctionProxy(superType: IrType, reference: IrFunctionReference): IrClass =
|
|
||||||
implement(superType, reference.arguments.mapNotNull { it?.type }, reference) { fields ->
|
|
||||||
context.createIrBuilder(symbol).irBlockBody(startOffset, endOffset) {
|
|
||||||
+irReturn(irCall(reference.symbol).apply {
|
|
||||||
var boundOffset = 0
|
|
||||||
var offset = 0
|
|
||||||
val arguments = reference.arguments.map {
|
|
||||||
if (it != null)
|
|
||||||
irGetField(irGet(dispatchReceiverParameter!!), fields[boundOffset++])
|
|
||||||
else
|
|
||||||
irGet(valueParameters[offset++])
|
|
||||||
}.toMutableList()
|
|
||||||
if (reference.symbol.owner.dispatchReceiverParameter != null) {
|
|
||||||
dispatchReceiver = arguments.removeAt(0)
|
|
||||||
}
|
|
||||||
if (reference.symbol.owner.extensionReceiverParameter != null) {
|
|
||||||
extensionReceiver = arguments.removeAt(0)
|
|
||||||
}
|
|
||||||
arguments.forEachIndexed(::putValueArgument)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun IrBlockBuilder.createFunctionProxyInstance(superType: IrType, reference: IrFunctionReference): IrExpression {
|
|
||||||
val implementation = createFunctionProxy(superType, reference)
|
|
||||||
if (reference.origin == IrStatementOrigin.ANONYMOUS_FUNCTION || reference.origin == IrStatementOrigin.LAMBDA) {
|
|
||||||
val target = reference.symbol.owner
|
|
||||||
implementation.functions.single().apply {
|
|
||||||
annotations.addAll(target.annotations)
|
|
||||||
valueParameters.forEachIndexed { i, p ->
|
|
||||||
p.annotations.addAll(target.valueParameters[i].annotations)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
localImplementations += implementation
|
|
||||||
return irCall(implementation.constructors.single()).apply {
|
|
||||||
reference.arguments.filterNotNull().forEachIndexed(::putValueArgument)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
irClass.declarations += cachedImplementations.values
|
irClass.declarations += cachedImplementations.values
|
||||||
irClass.declarations += localImplementations
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-1
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
|
|||||||
Reference in New Issue
Block a user