From 93d414ac0191cae2cdda8cbaf9bc490cc829fc23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Sch=C3=A4fer?= Date: Tue, 23 Jul 2019 14:09:39 +0200 Subject: [PATCH] JVM_IR: Change CallableReferenceLowering to match the JVM backend In particular, the JVM backend inlines the bodies of lambdas into the generated lambda subclasses and stores bound receivers in the receiver field of CallableReference. Both are required for reflection. --- .../kotlin/backend/jvm/DeclarationOrigins.kt | 1 + .../kotlin/backend/jvm/JvmSymbols.kt | 2 + .../backend/jvm/codegen/IrInlineCodegen.kt | 1 + .../jvm/lower/CallableReferenceLowering.kt | 567 ++++++++++-------- 4 files changed, 321 insertions(+), 250 deletions(-) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt index 0e376e64356..94b2588f8c9 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt @@ -27,6 +27,7 @@ interface JvmLoweredDeclarationOrigin : IrDeclarationOrigin { object DEFAULT_IMPLS_BRIDGE : IrDeclarationOriginImpl("DEFAULT_IMPLS_BRIDGE") object DEFAULT_IMPLS_BRIDGE_TO_SYNTHETIC : IrDeclarationOriginImpl("DEFAULT_IMPLS_BRIDGE_TO_SYNTHETIC", isSynthetic = true) object FIELD_FOR_OUTER_THIS : IrDeclarationOriginImpl("FIELD_FOR_OUTER_THIS") + object LAMBDA_IMPL : IrDeclarationOriginImpl("LAMBDA_IMPL") object FUNCTION_REFERENCE_IMPL : IrDeclarationOriginImpl("FUNCTION_REFERENCE_IMPL", isSynthetic = true) object SYNTHETIC_ACCESSOR : IrDeclarationOriginImpl("SYNTHETIC_ACCESSOR", isSynthetic = true) object TO_ARRAY : IrDeclarationOriginImpl("TO_ARRAY") diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt index 3b25af7f98b..0976793bcd6 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt @@ -136,6 +136,8 @@ class JvmSymbols( addValueParameter("receiver", irBuiltIns.anyNType) } + klass.addField("receiver", irBuiltIns.anyNType, Visibilities.PROTECTED) + generateCallableReferenceMethods(klass) }.symbol diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt index 39eb52d76fb..be05fb44b01 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt @@ -157,6 +157,7 @@ class IrExpressionLambdaImpl( when (ir) { is IrGetValue -> capturedParamDesc(ir.descriptor.name.asString(), typeMapper.mapType(ir.type)) is IrConst<*> -> capturedParamDesc(BOUND_REFERENCE_RECEIVER, typeMapper.mapType(ir.type)) + is IrGetField -> capturedParamDesc(ir.descriptor.name.asString(), typeMapper.mapType(ir.type)) else -> error("Unrecognized expression: ${ir.dump()}") } ) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CallableReferenceLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CallableReferenceLowering.kt index 9d99cbe27b8..033fbe55950 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CallableReferenceLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CallableReferenceLowering.kt @@ -1,24 +1,12 @@ /* - * Copyright 2010-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 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.backend.jvm.lower import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext -import org.jetbrains.kotlin.backend.common.descriptors.isFunctionOrKFunctionType import org.jetbrains.kotlin.backend.common.ir.copyTo import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor import org.jetbrains.kotlin.backend.common.lower.createIrBuilder @@ -32,12 +20,14 @@ import org.jetbrains.kotlin.backend.jvm.codegen.isInlineIrExpression import org.jetbrains.kotlin.backend.jvm.ir.isInlineParameter import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor import org.jetbrains.kotlin.codegen.PropertyReferenceCodegen +import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.builders.declarations.addConstructor -import org.jetbrains.kotlin.ir.builders.declarations.addField import org.jetbrains.kotlin.ir.builders.declarations.addFunction +import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter import org.jetbrains.kotlin.ir.builders.declarations.buildClass import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* @@ -45,12 +35,17 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl -import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.types.IrSimpleType +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.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.load.java.JavaVisibilities -import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.utils.addIfNotNull internal val callableReferencePhase = makeIrFilePhase( ::CallableReferenceLowering, @@ -58,125 +53,117 @@ internal val callableReferencePhase = makeIrFilePhase( description = "Handle callable references" ) -//Originally was copied from K/Native -internal class CallableReferenceLowering(val context: JvmBackendContext) : FileLoweringPass { - private val inlineLambdaReferences = mutableSetOf() +// Originally copied from K/Native +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. + // We also implicitly ignore all suspend function references by only dealing with subclasses of (K)Function and not + // (K)SuspendFunction. + private val ignoredFunctionReferences = mutableSetOf() - override fun lower(irFile: IrFile) { - irFile.transformChildrenVoid(object : IrElementTransformerVoidWithContext() { - override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression { - if (expression.operator == IrTypeOperator.SAM_CONVERSION) { - val invokable = expression.argument - if (invokable is IrFunctionReference) { - inlineLambdaReferences += invokable - } else if (invokable is IrBlock && invokable.statements.last() is IrFunctionReference) { - inlineLambdaReferences += invokable.statements.last() as IrFunctionReference - } - } - return super.visitTypeOperator(expression) - } + private val IrFunctionReference.isIgnored: Boolean + get() = !type.isFunctionOrKFunction() || ignoredFunctionReferences.contains(this) - override fun visitFunctionAccess(expression: IrFunctionAccessExpression): IrExpression { - val callee = expression.symbol.owner - if (callee.isInlineFunctionCall(context)) { - //TODO: more wise filtering - for (valueParameter in callee.valueParameters) { - if (valueParameter.isInlineParameter()) { - expression.getValueArgument(valueParameter.index)?.let { - if (isInlineIrExpression(it)) { - inlineLambdaReferences += (it as IrBlock).statements.filterIsInstance() - } - } - } - } - } + override fun lower(irFile: IrFile) = irFile.transformChildrenVoid(this) - val argumentsCount = expression.valueArgumentsCount - // Change calls to FunctionN with large N to varargs calls. - val newCall = if (argumentsCount >= FunctionInvokeDescriptor.Factory.BIG_ARITY && - callee.parentAsClass.defaultType.isFunctionOrKFunction() - ) { - val vararg = IrVarargImpl( - UNDEFINED_OFFSET, UNDEFINED_OFFSET, - context.ir.symbols.array.typeWith(context.irBuiltIns.anyNType), - context.irBuiltIns.anyNType, - (0 until argumentsCount).map { i -> expression.getValueArgument(i)!! } - ) - val invokeFun = context.ir.symbols.functionN.owner.declarations.single { - it is IrSimpleFunction && it.name.asString() == "invoke" - } as IrSimpleFunction + // Change calls to big arity invoke functions to vararg calls. + override fun visitFunctionAccess(expression: IrFunctionAccessExpression): IrExpression { + markInlineFunctionReferences(expression) + expression.transformChildrenVoid(this) - IrCallImpl( - UNDEFINED_OFFSET, UNDEFINED_OFFSET, - expression.type, - invokeFun.symbol, invokeFun.descriptor, - 1, - expression.origin, - (expression as? IrCall)?.superQualifier?.let { context.ir.symbols.externalSymbolTable.referenceClass(it) } - ).apply { - putTypeArgument(0, expression.type) - dispatchReceiver = expression.dispatchReceiver - extensionReceiver = expression.extensionReceiver - putValueArgument(0, vararg) - } - } else expression + if (expression.valueArgumentsCount < FunctionInvokeDescriptor.BIG_ARITY || + !expression.symbol.owner.parentAsClass.defaultType.isFunctionOrKFunction()) + return expression - //TODO: clean - return super.visitFunctionAccess(newCall) - } - - override fun visitFunctionReference(expression: IrFunctionReference): IrExpression { - expression.transformChildrenVoid(this) - - if (!expression.type.toKotlinType().isFunctionOrKFunctionType || inlineLambdaReferences.contains(expression)) { - // Not a subject of this lowering. - return expression - } - - val currentDeclarationParent = allScopes.map { it.irElement }.last { it is IrDeclarationParent } as IrDeclarationParent - val loweredFunctionReference = FunctionReferenceBuilder(currentDeclarationParent, expression).build() - return context.createIrBuilder(currentScope!!.scope.scopeOwnerSymbol).irBlock(expression) { - +loweredFunctionReference.functionReferenceClass - +irCall(loweredFunctionReference.functionReferenceConstructor.symbol).apply { - expression.getArguments().forEachIndexed { index, argument -> - putValueArgument(index, argument.second) - } - } - } - } - }) + return IrCallImpl( + expression.startOffset, expression.endOffset, + expression.type, functionNInvokeFun.symbol, functionNInvokeFun.descriptor, + 1, expression.origin + ).apply { + putTypeArgument(0, expression.type) + dispatchReceiver = expression.dispatchReceiver + extensionReceiver = expression.extensionReceiver + val vararg = IrVarargImpl( + UNDEFINED_OFFSET, UNDEFINED_OFFSET, + context.ir.symbols.array.typeWith(context.irBuiltIns.anyNType), + context.irBuiltIns.anyNType, + (0 until expression.valueArgumentsCount).map { expression.getValueArgument(it)!! } + ) + putValueArgument(0, vararg) + } } - private val arrayGetFun by lazy { - context.irBuiltIns.arrayClass.owner.functions.find { it.name.asString() == "get" }!! + private fun markInlineFunctionReferences(expression: IrFunctionAccessExpression) { + val function = expression.symbol.owner + if (!function.isInlineFunctionCall(context)) + return + + for (parameter in function.valueParameters) { + if (!parameter.isInlineParameter()) + continue + + val valueArgument = expression.getValueArgument(parameter.index) ?: continue + if (!isInlineIrExpression(valueArgument)) + continue + + if (valueArgument is IrFunctionReference) { + ignoredFunctionReferences.add(valueArgument) + } else if (valueArgument is IrBlock) { + ignoredFunctionReferences.addIfNotNull(valueArgument.statements.filterIsInstance().singleOrNull()) + } + } } - private val arraySizeProperty by lazy { - context.irBuiltIns.arrayClass.owner.properties.find { it.name.toString() == "size" }!! + // 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) } - private class BuiltFunctionReference( - val functionReferenceClass: IrClass, - val functionReferenceConstructor: IrConstructor - ) + override fun visitBlock(expression: IrBlock): IrExpression { + if (!expression.origin.isLambda) + return super.visitBlock(expression) - private inner class FunctionReferenceBuilder( - val referenceParent: IrDeclarationParent, - val irFunctionReference: IrFunctionReference - ) { + val reference = expression.statements.last() as IrFunctionReference + if (reference.isIgnored) + return super.visitBlock(expression) - private val isLambda = irFunctionReference.origin == IrStatementOrigin.LAMBDA + expression.statements.dropLast(1).forEach { it.transform(this, null) } + reference.transformChildrenVoid(this) + return FunctionReferenceBuilder(reference).build() + } + + override fun visitFunctionReference(expression: IrFunctionReference): IrExpression { + expression.transformChildrenVoid(this) + return if (expression.isIgnored) expression else FunctionReferenceBuilder(expression).build() + } + + private inner class FunctionReferenceBuilder(val irFunctionReference: IrFunctionReference) { + private val isLambda = irFunctionReference.origin.isLambda private val functionReferenceOrLambda = if (isLambda) context.ir.symbols.lambdaClass else context.ir.symbols.functionReference private val callee = irFunctionReference.symbol.owner - private val calleeParameters = callee.explicitParameters - private val boundCalleeParameters = irFunctionReference.getArgumentsWithIr().map { it.first } + + // Only function references can bind a receiver and even then we can only bind either an extension or a dispatch receiver. + // However, when we bind a value of an inline class type as a receiver, the receiver will turn into an argument of + // the function in question. Yet we still need to record it as the "receiver" in CallableReference in order for reflection + // to work correctly. + private val boundReceiver: Pair? = irFunctionReference.getArgumentsWithIr().singleOrNull() // The type of the reference is KFunction - private val argumentTypes = (irFunctionReference.type as IrSimpleType).arguments.dropLast(1).map { (it as IrTypeProjection).type } - private val returnType = ((irFunctionReference.type as IrSimpleType).arguments.last() as IrTypeProjection).type - private val useVararg = (argumentTypes.size >= FunctionInvokeDescriptor.Factory.BIG_ARITY) + private val parameterTypes = (irFunctionReference.type as IrSimpleType).arguments.map { (it as IrTypeProjection).type } + private val argumentTypes = parameterTypes.dropLast(1) + private val returnType = parameterTypes.last() + + private val useVararg + get() = argumentTypes.size >= FunctionInvokeDescriptor.BIG_ARITY private val typeParameters = if (callee is IrConstructor) callee.parentAsClass.typeParameters + callee.typeParameters @@ -189,118 +176,212 @@ internal class CallableReferenceLowering(val context: JvmBackendContext) : FileL private val functionReferenceClass = buildClass { setSourceRange(irFunctionReference) visibility = Visibilities.LOCAL - origin = JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL + // A callable reference results in a synthetic class, while a lambda is not synthetic. + origin = if (isLambda) JvmLoweredDeclarationOrigin.LAMBDA_IMPL else JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL name = Name.special("") }.apply { - parent = referenceParent + parent = currentDeclarationParent superTypes += functionReferenceOrLambda.owner.defaultType createImplicitParameterDeclarationWithWrappedDescriptor() copyAttributes(irFunctionReference) } - private val argumentToFieldMap = boundCalleeParameters.associateWith { parameter -> - // TODO: do not store receivers to fields and get rid of this - val safeName = parameter.name.takeUnless(Name::isSpecial) ?: parameter.name.asString().let { name -> - Name.identifier("$${name.substring(1, name.length - 1)}") - } - buildField(safeName, parameter.type) - } - - fun build(): BuiltFunctionReference { - val actualFunctionClass = if (useVararg) - context.ir.symbols.functionN - else - context.ir.symbols.getJvmFunctionClass(argumentTypes.size) - functionReferenceClass.superTypes += - actualFunctionClass.typeWith(if (useVararg) listOf(returnType) else argumentTypes + returnType) - - var suspendFunctionClass: IrClass? = null - val lastParameterType = (calleeParameters - boundCalleeParameters).lastOrNull()?.type - if (lastParameterType is IrSimpleType && - lastParameterType.classOrNull?.owner?.fqNameWhenAvailable?.asString() == "kotlin.coroutines.experimental.Continuation" - ) { - // If the last parameter is Continuation<> inherit from SuspendFunction. - suspendFunctionClass = context.getTopLevelClass(FqName("kotlin.coroutines.SuspendFunction${argumentTypes.size - 1}")).owner - val continuationType = (lastParameterType.arguments.single() as IrTypeProjection).type - functionReferenceClass.superTypes += suspendFunctionClass.typeWith(argumentTypes + continuationType) - } - + fun build(): IrExpression { val constructor = createConstructor() - createInvokeMethod(actualFunctionClass.owner.functions.find { it.name.asString() == "invoke" }!!) + val invokeMethod = createInvokeMethod() + createBridge(invokeMethod) if (!isLambda) { - createGetSignatureMethod(functionReferenceOrLambda.owner.functions.find { it.name.asString() == "getSignature" }!!) - createGetNameMethod(functionReferenceOrLambda.owner.functions.find { it.name.asString() == "getName" }!!) - createGetOwnerMethod(functionReferenceOrLambda.owner.functions.find { it.name.asString() == "getOwner" }!!) - if (suspendFunctionClass != null) { - createInvokeMethod(suspendFunctionClass.functions.find { it.name.asString() == "invoke" }!!) - } + createGetSignatureMethod(functionGetSignature) + createGetNameMethod(functionGetName) + createGetOwnerMethod(functionGetOwner) } - return BuiltFunctionReference(functionReferenceClass, constructor) + return context.createIrBuilder(currentScope!!.scope.scopeOwnerSymbol).irBlock(irFunctionReference) { + +functionReferenceClass + +irCall(constructor.symbol).apply { + boundReceiver?.second?.let { putValueArgument(0, it) } + } + } } private fun createConstructor(): IrConstructor = functionReferenceClass.addConstructor { setSourceRange(irFunctionReference) origin = JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL - visibility = Visibilities.PUBLIC + visibility = if (inInlineFunctionScope) Visibilities.PUBLIC else JavaVisibilities.PACKAGE_VISIBILITY returnType = functionReferenceClass.defaultType isPrimary = true }.apply { - for (param in boundCalleeParameters) { + // Add receiver parameter for bound function references + boundReceiver?.first?.let { param -> valueParameters += param.copyTo( - this, + irFunction = this, index = valueParameters.size, type = param.type.substitute(typeArgumentsMap) ) } - // The syntax (object::method) only allows to bind one of them. - val hasReceiver = irFunctionReference.dispatchReceiver != null || irFunctionReference.extensionReceiver != null - val kFunctionRefConstructor = - functionReferenceOrLambda.owner.constructors.single { it.valueParameters.size == if (hasReceiver) 2 else 1 } + // Super constructor: + // - For function references with bound receivers, accepts arity and receiver + // - For lambdas and function references without bound receivers, accepts arity + val kFunctionRefConstructor = functionReferenceOrLambda.owner.constructors.single { + it.valueParameters.size == if (boundReceiver != null) 2 else 1 + } body = context.createIrBuilder(symbol).irBlockBody(startOffset, endOffset) { +irDelegatingConstructorCall(kFunctionRefConstructor).apply { putValueArgument(0, irInt(argumentTypes.size)) - if (hasReceiver) { - putValueArgument(1, irGet(valueParameters[0])) - } - } - - // Save all arguments to fields. - // TODO don't write receiver again: use it from base class - boundCalleeParameters.forEachIndexed { index, it -> - +irSetField( - irGet(functionReferenceClass.thisReceiver!!), - argumentToFieldMap[it]!!, - irGet(valueParameters[index]) - ) + if (boundReceiver != null) + putValueArgument(1, irGet(valueParameters.first())) } +IrInstanceInitializerCallImpl(startOffset, endOffset, functionReferenceClass.symbol, context.irBuiltIns.unitType) } } - private fun createInvokeMethod(superFunction: IrSimpleFunction): IrSimpleFunction = - buildOverride(superFunction, callee.returnType).apply { - annotations.addAll(callee.annotations) + private fun createInvokeMethod(): IrSimpleFunction = + functionReferenceClass.addFunction { + name = Name.identifier("invoke") + returnType = callee.returnType + isSuspend = callee.isSuspend + }.apply { + dispatchReceiverParameter = parentAsClass.thisReceiver!!.copyTo(this) + if (isLambda) createLambdaInvokeMethod() else createFunctionReferenceInvokeMethod() + } - if (useVararg) { - valueParameters.add(superFunction.valueParameters[0].copyTo(this)) - } else { - for ((parameter, type) in superFunction.valueParameters.zip(argumentTypes)) { - valueParameters += parameter.copyTo(this, type = type) - } + // Inline the body of an anonymous function into the generated lambda subclass. + private fun IrSimpleFunction.createLambdaInvokeMethod() { + annotations += callee.annotations + val valueParameterMap = callee.explicitParameters.withIndex().associate { (index, param) -> + param to param.copyTo(this, index = index) + } + valueParameters += valueParameterMap.values + + body = context.createIrBuilder(symbol).irBlockBody(startOffset, endOffset) { + callee.body?.statements?.forEach { statement -> + +statement.transform(object : IrElementTransformerVoid() { + override fun visitGetValue(expression: IrGetValue): IrExpression { + val replacement = valueParameterMap[expression.symbol.owner] + ?: return super.visitGetValue(expression) + + at(expression.startOffset, expression.endOffset) + return irGet(replacement) + } + + override fun visitReturn(expression: IrReturn): IrExpression = + if (expression.returnTargetSymbol != callee.symbol) { + super.visitReturn(expression) + } else { + at(expression.startOffset, expression.endOffset) + irReturn(expression.value.transform(this, null)) + } + + override fun visitDeclaration(declaration: IrDeclaration): IrStatement { + if (declaration.parent == callee) + declaration.parent = this@createLambdaInvokeMethod + return super.visitDeclaration(declaration) + } + }, null) } + } + } + + private fun IrSimpleFunction.createFunctionReferenceInvokeMethod() { + for ((index, argumentType) in argumentTypes.withIndex()) { + addValueParameter { + name = Name.identifier("p$index") + type = argumentType + } + } + + body = context.createIrBuilder(symbol).run { + var unboundIndex = 0 + irExprBody(irCall(callee).apply { + for ((typeParameter, typeArgument) in typeArgumentsMap) { + putTypeArgument(typeParameter.owner.index, typeArgument) + } + + for (parameter in callee.explicitParameters) { + when { + boundReceiver?.first == parameter -> + // Bound receiver parameter + irImplicitCast( + irGetField(irGet(dispatchReceiverParameter!!), functionReferenceReceiverField), + boundReceiver.second.type + ) + + unboundIndex >= argumentTypes.size -> + // Unbound, but out of range - empty vararg or default value. + // TODO For suspend functions the last argument is continuation and it is implicit: + // irCall(getContinuationSymbol, listOf(ourSymbol.descriptor.returnType!!)) + null + + // If a vararg parameter corresponds to exactly one KFunction argument, which is an array, that array + // is forwarded as a spread. In all other cases, excess arguments are packed into a new array. + // + // fun f(x: (Int, Array) -> 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] + // f(::h) == g(::h) + // + parameter.isVararg && (unboundIndex < argumentTypes.size - 1 || argumentTypes.last() != parameter.type) -> + IrVarargImpl( + startOffset, endOffset, parameter.type, parameter.varargElementType!!, + (unboundIndex until argumentTypes.size).map { irGet(valueParameters[unboundIndex++]) } + ) + + else -> + irGet(valueParameters[unboundIndex++]) + }?.let { putArgument(callee, parameter, it) } + } + }) + } + } + + // Build a bridge to the monomorphic invoke method. This is more elaborate than the usual BridgeLowering, + // since we have special handling for functions with large arity (> 22 arguments). Large arity functions + // are translated to functions with a single vararg argument, which checks the number of arguments and types + // dynamically. + private fun createBridge(invoke: IrSimpleFunction) { + // Add supertypes + val actualFunctionClass = if (useVararg) + context.ir.symbols.functionN + else + context.ir.symbols.getJvmFunctionClass(argumentTypes.size) + + functionReferenceClass.superTypes += actualFunctionClass.typeWith(if (useVararg) listOf(returnType) else parameterTypes) + val superFunction = actualFunctionClass.owner.functions.find { it.name.asString() == "invoke" }!! + + // Only add a bridge method when necessary + if (context.state.typeMapper.mapAsmMethod(superFunction.descriptor) == + context.state.typeMapper.mapAsmMethod(invoke.descriptor) + ) { + invoke.overriddenSymbols += superFunction.symbol + return + } + + // Add the invoke bridge + functionReferenceClass.addFunction { + name = superFunction.name + returnType = context.irBuiltIns.anyNType + modality = Modality.FINAL + visibility = Visibilities.PUBLIC + origin = IrDeclarationOrigin.BRIDGE + }.apply { + overriddenSymbols += superFunction.symbol + dispatchReceiverParameter = parentAsClass.thisReceiver!!.copyTo(this) + if (useVararg) + valueParameters += superFunction.valueParameters[0].copyTo(this) + else + superFunction.valueParameters.forEach { valueParameters += it.copyTo(this, type = context.irBuiltIns.anyNType) } body = context.createIrBuilder(symbol).irBlockBody(startOffset, endOffset) { + // Check the number of arguments for large arity functions if (useVararg) { - val varargParam = valueParameters.single() +irIfThen( irNotEquals( irCall(arraySizeProperty.getter!!).apply { - dispatchReceiver = irGet(varargParam) + dispatchReceiver = irGet(valueParameters.single()) }, irInt(argumentTypes.size) ), @@ -310,74 +391,26 @@ internal class CallableReferenceLowering(val context: JvmBackendContext) : FileL ) } - var unboundIndex = 0 - fun consumeNextArgument() = if (useVararg) { - val type = argumentTypes[unboundIndex] - irBlock(resultType = type) { - val argArray = irGet(valueParameters.single()) - val argIndex = irInt(unboundIndex++) - val argValue = irTemporary(irCallOp(arrayGetFun.symbol, context.irBuiltIns.anyNType, argArray, argIndex)) - +irIfThen( - irNotIs(irGet(argValue), type), - irCall(context.irBuiltIns.illegalArgumentExceptionSymbol).apply { - putValueArgument(0, irString("Wrong type, expected $type")) - } - ) - +irImplicitCast(irGet(argValue), type) + +irReturn(irCall(invoke).apply { + dispatchReceiver = irGet(dispatchReceiverParameter!!) + + for (parameter in invoke.valueParameters) { + val index = parameter.index + + val argument = if (useVararg) { + val argArray = irGet(valueParameters.single()) + val argIndex = irInt(index) + irCallOp(arrayGetFun.symbol, context.irBuiltIns.anyNType, argArray, argIndex) + } else { + irGet(valueParameters[index]) + } + + putValueArgument(index, irImplicitCast(argument, argumentTypes[index])) } - } else { - irGet(valueParameters[unboundIndex++]) - } - - val delegation = irCall(irFunctionReference.symbol).apply { - for ((typeParameter, typeArgument) in typeArgumentsMap) { - putTypeArgument(typeParameter.owner.index, typeArgument) - } - - for (parameter in calleeParameters) { - when { - argumentToFieldMap.contains(parameter) -> - // Bound parameter - read from field. - irGetField(irGet(dispatchReceiverParameter!!), argumentToFieldMap[parameter]!!) - - unboundIndex >= argumentTypes.size -> - // Unbound, but out of range - empty vararg or default value. - // TODO For suspend functions the last argument is continuation and it is implicit: - // irCall(getContinuationSymbol, listOf(ourSymbol.descriptor.returnType!!)) - null - - // If a vararg parameter corresponds to exactly one KFunction argument, which is an array, that array - // is forwarded as a spread. In all other cases, excess arguments are packed into a new array. - // - // fun f(x: (Int, Array) -> 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] - // f(::h) == g(::h) - // - parameter.isVararg && (unboundIndex < argumentTypes.size - 1 || argumentTypes.last() != parameter.type) -> - IrVarargImpl( - startOffset, endOffset, parameter.type, parameter.varargElementType!!, - (unboundIndex until argumentTypes.size).map { consumeNextArgument() } - ) - - else -> - consumeNextArgument() - }?.let { putArgument(callee, parameter, it) } - } - } - +irReturn(delegation) + }) } } - - private fun buildField(fieldName: Name, fieldType: IrType): IrField = - functionReferenceClass.addField { - setSourceRange(irFunctionReference) - origin = JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL - name = fieldName - type = fieldType - visibility = JavaVisibilities.PACKAGE_VISIBILITY - isFinal = true - } + } private fun buildOverride(superFunction: IrSimpleFunction, newReturnType: IrType = superFunction.returnType): IrSimpleFunction = functionReferenceClass.addFunction { @@ -447,4 +480,38 @@ internal class CallableReferenceLowering(val context: JvmBackendContext) : FileL } } } + + private val IrStatementOrigin?.isLambda + get() = this == IrStatementOrigin.LAMBDA || this == IrStatementOrigin.ANONYMOUS_FUNCTION + + private val currentDeclarationParent + get() = allScopes.last { it.irElement is IrDeclarationParent }.irElement as IrDeclarationParent + + private val inInlineFunctionScope: Boolean + get() = allScopes.any { scope -> (scope.irElement as? IrFunction)?.isInline ?: false } + + private fun IrClassSymbol.functionByName(name: String) = owner.functions.single { it.name.asString() == name } + + private val arraySizeProperty by lazy { + context.irBuiltIns.arrayClass.owner.properties.single { it.name.toString() == "size" } + } + + private val arrayGetFun by lazy { + context.irBuiltIns.arrayClass.functionByName("get") + } + + private val functionReferenceReceiverField = + context.ir.symbols.functionReference.owner.declarations.single { it is IrField && it.name.toString() == "receiver" } as IrField + + private val functionGetSignature = + context.ir.symbols.functionReference.functionByName("getSignature") + + private val functionGetName = + context.ir.symbols.functionReference.functionByName("getName") + + private val functionGetOwner = + context.ir.symbols.functionReference.functionByName("getOwner") + + private val functionNInvokeFun = + context.ir.symbols.functionN.functionByName("invoke") }