From 2fa2727935e4db3d13521392985688d052bcbd0d Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Thu, 8 Dec 2016 16:58:10 +0700 Subject: [PATCH] backend.konan: implement callable reference lowering --- .../kotlin/backend/konan/DescriptorUtils.kt | 14 ++ .../jetbrains/kotlin/backend/konan/IrUtils.kt | 62 ++++++ .../kotlin/backend/konan/KonanLower.kt | 2 + .../kotlin/backend/konan/llvm/IrToBitcode.kt | 28 +-- .../konan/lower/CallableReferenceLowering.kt | 203 ++++++++++++++++++ .../main/kotlin/konan/internal/Function.kt | 23 ++ 6 files changed, 305 insertions(+), 27 deletions(-) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/IrUtils.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/CallableReferenceLowering.kt create mode 100644 runtime/src/main/kotlin/konan/internal/Function.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/DescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/DescriptorUtils.kt index ce9fb3faaa5..b9e40e97cf8 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/DescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/DescriptorUtils.kt @@ -7,6 +7,8 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.OverridingUtil import org.jetbrains.kotlin.resolve.descriptorUtil.* +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeUtils /** * List of all implemented interfaces (including those which implemented by a super class) @@ -83,6 +85,18 @@ internal fun KonanBuiltIns.getKonanInternalClass(name: String): ClassDescriptor return classifier as ClassDescriptor } +private val UNBOUND_CALLABLE_REFERENCE = "UnboundCallableReference" + +internal val KonanBuiltIns.unboundCallableReferenceType: KotlinType + get() = this.getKonanInternalClass(UNBOUND_CALLABLE_REFERENCE).defaultType + +internal fun KotlinType.isUnboundCallableReference(): Boolean { + val classDescriptor = TypeUtils.getClassDescriptor(this) ?: return false + + return !this.isMarkedNullable && + classDescriptor.fqNameUnsafe.asString() == "konan.internal.$UNBOUND_CALLABLE_REFERENCE" +} + internal val CallableDescriptor.allValueParameters: List get() { val constructorReceiver = if (this is ConstructorDescriptor) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/IrUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/IrUtils.kt new file mode 100644 index 00000000000..e96ea89d19b --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/IrUtils.kt @@ -0,0 +1,62 @@ +package org.jetbrains.kotlin.backend.konan + +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.ir.expressions.* + + +/** + * Binds the arguments explicitly represented in the IR to the parameters of the accessed function. + * The arguments are to be evaluated in the same order as they appear in the resulting list. + */ +internal fun IrMemberAccessExpression.getArguments(): List> { + val res = mutableListOf>() + val descriptor = descriptor + + // TODO: ensure the order below corresponds to the one defined in Kotlin specs. + + dispatchReceiver?.let { + res += (descriptor.dispatchReceiverParameter!! to it) + } + + extensionReceiver?.let { + res += (descriptor.extensionReceiverParameter!! to it) + } + + descriptor.valueParameters.forEach { + val arg = getValueArgument(it.index) + if (arg != null) { + res += (it to arg) + } + } + + return res +} + +/** + * Sets arguments that are specified by given mapping of parameters. + */ +internal fun IrMemberAccessExpression.addArguments(args: Map) { + descriptor.dispatchReceiverParameter?.let { + val arg = args[it] + if (arg != null) { + this.dispatchReceiver = arg + } + } + + descriptor.extensionReceiverParameter?.let { + val arg = args[it] + if (arg != null) { + this.extensionReceiver = arg + } + } + + descriptor.valueParameters.forEach { + val arg = args[it] + if (arg != null) { + this.putValueArgument(it.index, arg) + } + } +} + +internal fun IrMemberAccessExpression.addArguments(args: List>) = + this.addArguments(args.toMap()) \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt index 5fd846dd2c7..89874152e06 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt @@ -3,6 +3,7 @@ package org.jetbrains.kotlin.backend.konan import org.jetbrains.kotlin.backend.common.lower.LocalFunctionsLowering import org.jetbrains.kotlin.backend.common.lower.SharedVariablesLowering import org.jetbrains.kotlin.backend.common.runOnFilePostfix +import org.jetbrains.kotlin.backend.konan.lower.CallableReferenceLowering import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrModuleFragment @@ -17,5 +18,6 @@ internal class KonanLower(val context: KonanBackendContext) { fun lower(irFile: IrFile) { SharedVariablesLowering(context).runOnFilePostfix(irFile) LocalFunctionsLowering(context).runOnFilePostfix(irFile) + CallableReferenceLowering(context).runOnFilePostfix(irFile) } } \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index fb94635c58e..fb456d09b56 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -1530,39 +1530,13 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid } } - /** - * Binds the arguments of [expression] explicitly represented in the IR to the parameters of accessed function. - * The arguments should be further evaluated in the same order as they appear in the resulting list. - */ - private fun bindExplicitArgs(expression: IrMemberAccessExpression): List> { - val res = mutableListOf>() - val descriptor = expression.descriptor - - // TODO: ensure the order below corresponds to the one defined in Kotlin specs. - - expression.dispatchReceiver?.let { - res += (descriptor.dispatchReceiverParameter!! to it) - } - - expression.extensionReceiver?.let { - res += (descriptor.extensionReceiverParameter!! to it) - } - - descriptor.valueParameters.map { - //println(it.index) - res += (it to expression.getValueArgument(it.index)!!) - } - - return res - } - /** * Evaluates all arguments of [expression] that are explicitly represented in the IR. * Returns results in the same order as LLVM function expects, assuming that all explicit arguments * exactly correspond to a tail of LLVM parameters. */ private fun evaluateExplicitArgs(expression: IrMemberAccessExpression): List { - val evaluatedArgs = bindExplicitArgs(expression).map { (param, argExpr) -> + val evaluatedArgs = expression.getArguments().map { (param, argExpr) -> param to evaluateExpression("arg", argExpr)!! }.toMap() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/CallableReferenceLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/CallableReferenceLowering.kt new file mode 100644 index 00000000000..cee5aa189ba --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/CallableReferenceLowering.kt @@ -0,0 +1,203 @@ +package org.jetbrains.kotlin.backend.konan.lower + +import org.jetbrains.kotlin.backend.common.DeclarationContainerLoweringPass +import org.jetbrains.kotlin.backend.konan.* +import org.jetbrains.kotlin.builtins.isFunctionType +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.IrDeclarationContainer +import org.jetbrains.kotlin.ir.declarations.IrDeclarationOriginImpl +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl +import org.jetbrains.kotlin.ir.expressions.IrCallableReference +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.impl.* +import org.jetbrains.kotlin.ir.util.transformFlat +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.utils.addToStdlib.singletonList + +/** + * This lowering pass lowers all [IrCallableReference]s to unbound ones. + */ +internal class CallableReferenceLowering(val context: KonanBackendContext) : DeclarationContainerLoweringPass { + override fun lower(irDeclarationContainer: IrDeclarationContainer) { + irDeclarationContainer.declarations.transformFlat { memberDeclaration -> + if (memberDeclaration is IrFunction) + lowerCallableReferences(context, memberDeclaration) + else + null + } + } +} + +/** + * Replaces all callable references in the function body to unbound ones. + * Returns the list of this function and all created ones. + */ +private fun lowerCallableReferences(context: KonanBackendContext, function: IrFunction): List { + val transformer = CallableReferencesUnbinder(context, function) + function.transformChildrenVoid(transformer) + return function.singletonList() + transformer.createdFunctions +} + +/** + * Replaces all callable references in the function body to unbound ones. + * Adds all created functions to [createdFunctions]. + */ +private class CallableReferencesUnbinder(val context: KonanBackendContext, + val function: IrFunction) : IrElementTransformerVoid() { + + val createdFunctions = mutableListOf() + + override fun visitElement(element: IrElement): IrElement { + return super.visitElement(element) + } + + private val unboundCallableReferenceType by lazy { + context.builtIns.getKonanInternalClass("UnboundCallableReference").defaultType + } + + private val simpleFunctionImplClass by lazy { + context.builtIns.getKonanInternalClass("SimpleFunctionImpl") + } + + override fun visitCallableReference(expression: IrCallableReference): IrExpression { + assert (expression.type.isFunctionType) + + val descriptor = expression.descriptor + val boundArgs = expression.getArguments() + val boundParams = boundArgs.map { it.first } + val unboundParams = descriptor.allValueParameters - boundParams + + val startOffset = expression.startOffset + val endOffset = expression.endOffset + + // The code below creates a call to `SimpleFunctionImpl` constructor which creates the value to be used + // as required callable reference. + + // Create the function to be used as `unboundRef` of `SimpleFunctionImpl`: + val newFunction = createSimpleFunctionImplTarget(descriptor, unboundParams, boundParams, startOffset, endOffset) + + createdFunctions.add(newFunction) + + // Create the call to constructor and its arguments: + + val unboundRef = IrCallableReferenceImpl(startOffset, endOffset, + unboundCallableReferenceType, newFunction.descriptor, null) + val simpleFunctionImplClassConstructor = simpleFunctionImplClass.unsubstitutedPrimaryConstructor!! + + val boundArgsVarargParam = simpleFunctionImplClassConstructor.valueParameters[1] + val boundArgsVararg = IrVarargImpl( + startOffset, endOffset, + boundArgsVarargParam.type, + boundArgsVarargParam.varargElementType!!, + boundArgs.map { it.second } + ) + + return IrCallImpl(startOffset, endOffset, simpleFunctionImplClassConstructor).apply { + putValueArgument(0, unboundRef) + putValueArgument(1, boundArgsVararg) + } + } + + /** + * For given function [descriptor] creates the function which calls [descriptor] but + * takes all [boundParams] packed into `SimpleFunctionImpl` (and all [unboundParams] as is). + */ + private fun createSimpleFunctionImplTarget(descriptor: CallableDescriptor, + unboundParams: List, + boundParams: List, + startOffset: Int, endOffset: Int): IrFunctionImpl { + + // Create new function descriptor: + val newDescriptor = createSimpleFunctionImplTargetDescriptor(descriptor, unboundParams) + + // Create new function declaration: + + val origin = object : IrDeclarationOriginImpl("FUNCTION_FOR_CALLABLE_REFERENCE") {} + + val simpleFunctionImplBoundArgsGetter = + simpleFunctionImplClass.defaultType.memberScope.getContributedDescriptors() + .filterIsInstance().filter { it.name.asString() == "boundArgs" } + .single().getter!! + + val boundArgsArray = IrCallImpl(startOffset, endOffset, simpleFunctionImplBoundArgsGetter, null).apply { + dispatchReceiver = IrGetValueImpl(startOffset, endOffset, newDescriptor.valueParameters[0]) + } + // TODO: do not call boundArgs getter for each bound argument. + + val arrayGet = boundArgsArray.type.memberScope.getContributedDescriptors() + .filterIsInstance().filter { it.name.asString() == "get" }.single() + + // TODO: some handling for type parameters is probably required here. + + // Call to old function from new function: + val call = IrCallImpl(startOffset, endOffset, descriptor) + + // unboundParams are received as all new function parameters except first: + val newUnboundParams = newDescriptor.valueParameters.drop(1) + assert (unboundParams.size == newUnboundParams.size) + call.addArguments(unboundParams.mapIndexed { index, param -> + param to IrGetValueImpl(startOffset, endOffset, newUnboundParams[index]) + }) + + // boundParams are received in `SimpleFunctionImpl.boundArgs`: + call.addArguments(boundParams.mapIndexed { index, param -> + param to IrCallImpl(startOffset, endOffset, arrayGet).apply { + dispatchReceiver = boundArgsArray + putValueArgument(0, IrConstImpl.int(startOffset, endOffset, context.builtIns.intType, index)) + } + }) + + val ret = IrReturnImpl(startOffset, endOffset, newDescriptor, call) + + val newFunction = IrFunctionImpl( + startOffset, endOffset, origin, + newDescriptor, + IrBlockBodyImpl(startOffset, endOffset, listOf(ret)) + ) + + return newFunction + } + + /** + * Creates descriptor for [createSimpleFunctionImplTarget]. + */ + private fun createSimpleFunctionImplTargetDescriptor(descriptor: CallableDescriptor, + unboundParams: List): FunctionDescriptor { + + val newName = Name.identifier(descriptor.hashCode().toString()) // FIXME + + val newDescriptor = SimpleFunctionDescriptorImpl.create( + function.descriptor.containingDeclaration, Annotations.EMPTY, + newName, CallableMemberDescriptor.Kind.SYNTHESIZED, SourceElement.NO_SOURCE) + + val simpleFunctionImplType = simpleFunctionImplClass.defaultType + + // Maps each unbound param of original function to parameter of new function + val newUnboundParams = unboundParams.mapIndexed { index, param -> + ValueParameterDescriptorImpl(newDescriptor, null, index + 1, + Annotations.EMPTY, param.name, param.type, + false, false, false, false, + (param as? ValueParameterDescriptor)?.varargElementType, + SourceElement.NO_SOURCE) + } + + // `: SimpleFunctionImpl` + val functionParameter = ValueParameterDescriptorImpl(newDescriptor, null, 0, Annotations.EMPTY, + Name.special(""), + simpleFunctionImplType, false, false, false, false, null, SourceElement.NO_SOURCE) + + val newValueParameters = listOf(functionParameter) + newUnboundParams + + newDescriptor.initialize(null, null, descriptor.typeParameters, newValueParameters, + descriptor.returnType, Modality.FINAL, Visibilities.PRIVATE) + + return newDescriptor + } +} \ No newline at end of file diff --git a/runtime/src/main/kotlin/konan/internal/Function.kt b/runtime/src/main/kotlin/konan/internal/Function.kt new file mode 100644 index 00000000000..bbf99bed16d --- /dev/null +++ b/runtime/src/main/kotlin/konan/internal/Function.kt @@ -0,0 +1,23 @@ +package konan.internal + +/** + * Represents the reference to callable without any bound arguments. + * It is supposed to be represented at runtime as native pointer to code. + * + * TODO: it seems to be very closely related to native interop types. + */ +abstract class UnboundCallableReference internal constructor() + +/** + * Values of function types are represented at runtime as instances of this class (and its subclasses). + * + * [FunctionImpl] is a reference to callable with itself as single bound argument, + * i.e. when calling this function with arguments `args`, [unboundRef] is called with arguments `(this, *args)`. + * So the instance is supposed to contain all implicit (bound) arguments of target function, e.g. captured values. + */ +open class FunctionImpl(val unboundRef: UnboundCallableReference) + +/** + * Simple [FunctionImpl] implementation: all bound arguments of target callable to be placed into [boundArgs]. + */ +class SimpleFunctionImpl(unboundRef: UnboundCallableReference, vararg val boundArgs: Any?) : FunctionImpl(unboundRef) \ No newline at end of file