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") }