From 5caad8779f4bbb0768da3bff4251cf8441099412 Mon Sep 17 00:00:00 2001 From: Igor Chevdar Date: Tue, 8 Sep 2020 12:18:57 +0500 Subject: [PATCH] [IR] Equals/hashCode for SAM implementers Fixes https://youtrack.jetbrains.com/issue/KT-39798 --- .../backend/konan/KonanLoweringPhases.kt | 29 +++--- .../kotlin/backend/konan/ToplevelPhases.kt | 8 +- .../jetbrains/kotlin/backend/konan/ir/Ir.kt | 2 +- .../konan/lower/FunctionReferenceLowering.kt | 91 ++++++++++++++++--- .../NativeSingleAbstractMethodLowering.kt | 5 +- .../kotlin/native/internal/FunctionAdapter.kt | 12 +++ 6 files changed, 111 insertions(+), 36 deletions(-) create mode 100644 runtime/src/main/kotlin/kotlin/native/internal/FunctionAdapter.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt index 3bd592cdb2a..0593293d2f2 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt @@ -251,19 +251,6 @@ internal val dataClassesPhase = makeKonanFileLoweringPhase( description = "Data classes lowering" ) -internal val singleAbstractMethodPhase = makeKonanFileLoweringPhase( - ::NativeSingleAbstractMethodLowering, - name = "SingleAbstractMethod", - description = "Replace SAM conversions with instances of interface-implementing classes" -) - -internal val builtinOperatorPhase = makeKonanFileLoweringPhase( - ::BuiltinOperatorLowering, - name = "BuiltinOperators", - description = "BuiltIn operators lowering", - prerequisite = setOf(defaultParameterExtentPhase, singleAbstractMethodPhase) -) - internal val finallyBlocksPhase = makeKonanFileLoweringPhase( ::FinallyBlocksLowering, name = "FinallyBlocks", @@ -294,7 +281,21 @@ internal val functionReferencePhase = makeKonanFileLoweringPhase( ::FunctionReferenceLowering, name = "FunctionReference", description = "Function references lowering", - prerequisite = setOf(delegationPhase) // TODO: make weak dependency on `testProcessorPhase` + prerequisite = setOf(delegationPhase, localFunctionsPhase) // TODO: make weak dependency on `testProcessorPhase` +) + +internal val singleAbstractMethodPhase = makeKonanFileLoweringPhase( + ::NativeSingleAbstractMethodLowering, + name = "SingleAbstractMethod", + description = "Replace SAM conversions with instances of interface-implementing classes", + prerequisite = setOf(functionReferencePhase) +) + +internal val builtinOperatorPhase = makeKonanFileLoweringPhase( + ::BuiltinOperatorLowering, + name = "BuiltinOperators", + description = "BuiltIn operators lowering", + prerequisite = setOf(defaultParameterExtentPhase, singleAbstractMethodPhase) ) internal val interopPhase = makeKonanFileLoweringPhase( diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt index a368fef6644..25b4e08bf5f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt @@ -369,14 +369,14 @@ internal val allLoweringsPhase = NamedCompilerPhase( defaultParameterExtentPhase, innerClassPhase, dataClassesPhase, - singleAbstractMethodPhase, ifNullExpressionsFusionPhase, - builtinOperatorPhase, - finallyBlocksPhase, testProcessorPhase, - enumClassPhase, delegationPhase, functionReferencePhase, + singleAbstractMethodPhase, + builtinOperatorPhase, + finallyBlocksPhase, + enumClassPhase, interopPhase, varargPhase, compileTimeEvaluatePhase, diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt index d05df0958e5..420a658e2ce 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt @@ -422,7 +422,7 @@ internal class KonanSymbols( } ) - override val functionAdapter: IrClassSymbol get() = error("Not supported yet") + override val functionAdapter = symbolTable.referenceClass(context.getKonanInternalClass("FunctionAdapter")) val refClass = symbolTable.referenceClass(context.getKonanInternalClass("Ref")) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt index 51631f9a692..3dd3d185383 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt @@ -94,6 +94,33 @@ internal class FunctionReferenceLowering(val context: Context): FileLoweringPass return result } + // Handle SAM conversions which wrap a function reference: + // 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) { + // By this point the lambda's function has been replaced with empty IrComposite by LocalDeclarationsLowering. + val statements = invokable.statements + require(statements.size == 2) + require((statements[0] as? IrComposite)?.statements?.isEmpty() == true) + statements[1] as IrFunctionReference + } else { + return super.visitTypeOperator(expression) + } + reference.transformChildrenVoid() + return transformFunctionReference(reference, expression.typeOperand) + } + return super.visitTypeOperator(expression) + } + override fun visitFunctionReference(expression: IrFunctionReference): IrExpression { expression.transformChildrenVoid(this) @@ -119,8 +146,12 @@ internal class FunctionReferenceLowering(val context: Context): FileLoweringPass return expression } + return transformFunctionReference(expression) + } + + fun transformFunctionReference(expression: IrFunctionReference, samSuperType: IrType? = null): IrExpression { val parent: IrDeclarationContainer = (currentClass?.irElement as? IrClass) ?: irFile - val loweredFunctionReference = FunctionReferenceBuilder(parent, expression).build() + val loweredFunctionReference = FunctionReferenceBuilder(parent, expression, samSuperType).build() generatedClasses.add(loweredFunctionReference.functionReferenceClass) val irBuilder = context.createIrBuilder(currentScope!!.scope.scopeOwnerSymbol, expression.startOffset, expression.endOffset) @@ -130,7 +161,8 @@ internal class FunctionReferenceLowering(val context: Context): FileLoweringPass } } } - }, null) + }, data = null) + irFile.declarations += generatedClasses } @@ -146,7 +178,8 @@ internal class FunctionReferenceLowering(val context: Context): FileLoweringPass private val continuationClassSymbol = getContinuationSymbol.owner.returnType.classifierOrFail as IrClassSymbol private inner class FunctionReferenceBuilder(val parent: IrDeclarationParent, - val functionReference: IrFunctionReference) { + val functionReference: IrFunctionReference, + val samSuperType: IrType?) { private val startOffset = functionReference.startOffset private val endOffset = functionReference.endOffset @@ -159,6 +192,12 @@ internal class FunctionReferenceLowering(val context: Context): FileLoweringPass typeParam.symbol to functionReference.getTypeArgument(typeParam.index)!! } + private val isLambda = functionReference.origin.isLambda + private val isKFunction = functionReference.type.isKFunction() + private val isKSuspendFunction = functionReference.type.isKSuspendFunction() + + private val samSuperClass = samSuperType?.let { it.classOrNull ?: error("Expected a class but was: ${it.render()}") } + private val adapteeCall: IrFunctionAccessExpression? = // TODO: Copied from JVM. if (referencedFunction.origin == IrDeclarationOrigin.ADAPTER_FOR_CALLABLE_REFERENCE) { @@ -228,10 +267,6 @@ internal class FunctionReferenceLowering(val context: Context): FileLoweringPass private val kSuspendFunctionImplSymbol = symbols.kSuspendFunctionImpl private val kSuspendFunctionImplConstructorSymbol = kSuspendFunctionImplSymbol.constructors.single() - val isLambda = functionReference.origin.isLambda - val isKFunction = functionReference.type.isKFunction() - val isKSuspendFunction = functionReference.type.isKSuspendFunction() - fun build(): BuiltFunctionReference { val numberOfParameters = unboundFunctionParameters.size val functionParameterTypes = unboundFunctionParameters.map { it.type } @@ -267,12 +302,32 @@ internal class FunctionReferenceLowering(val context: Context): FileLoweringPass } val constructor = buildConstructor() - if (!isKSuspendFunction) + val functionInvoke = if (isKSuspendFunction) + null + else buildInvokeMethod(functionClass.getInvokeFunction()) - suspendFunctionClass?.let { - val invokeMethod = buildInvokeMethod(it.getInvokeFunction()) - if (isKSuspendFunction) - invokeMethod.overriddenSymbols += functionClass.getInvokeFunction().symbol + val suspendFunctionInvoke = if (suspendFunctionClass == null) + null + else { + buildInvokeMethod(suspendFunctionClass.getInvokeFunction()).also { + if (isKSuspendFunction) + it.overriddenSymbols += functionClass.getInvokeFunction().symbol + } + } + samSuperType?.let { superTypes += it } + val sam = samSuperClass?.functions?.single { it.owner.modality == Modality.ABSTRACT } + if (sam != null) { + if (sam.owner.extensionReceiverParameter != null) + buildInvokeMethod(sam.owner) + else { + // The signatures of SAM and [invoke] coincide - no need to build additional function. + val properInvoke = if (sam.isSuspend) + suspendFunctionInvoke + else + functionInvoke + if (properInvoke != null) + properInvoke.overriddenSymbols += sam + } } functionReferenceClass.superTypes += superTypes @@ -320,7 +375,7 @@ internal class FunctionReferenceLowering(val context: Context): FileLoweringPass putValueArgument(1, irString(functionReferenceTarget.fullName)) putValueArgument(2, receiver) putValueArgument(3, irInt(arity)) - putValueArgument(4, irInt(getAdaptedCallableReferenceFlags())) + putValueArgument(4, irInt(getFlags())) putValueArgument(5, with(kTypeGenerator) { irKType(referencedFunction.returnType) }) } +IrInstanceInitializerCallImpl(startOffset, endOffset, functionReferenceClass.symbol, irBuiltIns.unitType) @@ -335,6 +390,9 @@ internal class FunctionReferenceLowering(val context: Context): FileLoweringPass private val IrFunction.fullName: String get() = parent.fqNameForIrSerialization.child(Name.identifier(functionName)).asString() + private fun getFlags() = + (if (referencedFunction.isSuspend) 1 else 0) + getAdaptedCallableReferenceFlags() shl 1 + private fun getAdaptedCallableReferenceFlags(): Int { if (adaptedReferenceOriginalTarget == null) return 0 @@ -386,6 +444,8 @@ internal class FunctionReferenceLowering(val context: Context): FileLoweringPass this.createDispatchReceiverParameter() + extensionReceiverParameter = superFunction.extensionReceiverParameter?.copyTo(function) + valueParameters += superFunction.valueParameters.mapIndexed { index, parameter -> parameter.copyTo(function, DECLARATION_ORIGIN_FUNCTION_REFERENCE_IMPL, index, type = parameter.type.substitute(typeArgumentsMap)) @@ -407,7 +467,10 @@ internal class FunctionReferenceLowering(val context: Context): FileLoweringPass argumentToPropertiesMap[parameter]!! ) else { - if (function.isSuspend && unboundIndex == valueParameters.size) + if (parameter == referencedFunction.extensionReceiverParameter + && extensionReceiverParameter != null) + irGet(extensionReceiverParameter!!) + else if (function.isSuspend && unboundIndex == valueParameters.size) // For suspend functions the last argument is continuation and it is implicit. irCall(getContinuationSymbol.owner, listOf(returnType)) else diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/NativeSingleAbstractMethodLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/NativeSingleAbstractMethodLowering.kt index 1e4f7878bb0..c7566a194eb 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/NativeSingleAbstractMethodLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/NativeSingleAbstractMethodLowering.kt @@ -23,6 +23,5 @@ internal class NativeSingleAbstractMethodLowering(context: Context) : SingleAbst return typeOperand.classOrNull?.defaultType ?: error("Unsupported SAM conversion: ${typeOperand.render()}") } - override val IrType.needEqualsHashCodeMethods: Boolean - get() = false -} + override val IrType.needEqualsHashCodeMethods get() = true +} \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/native/internal/FunctionAdapter.kt b/runtime/src/main/kotlin/kotlin/native/internal/FunctionAdapter.kt new file mode 100644 index 00000000000..66d48e693d8 --- /dev/null +++ b/runtime/src/main/kotlin/kotlin/native/internal/FunctionAdapter.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package kotlin.native.internal + +import kotlin.Function + +internal interface FunctionAdapter { + fun getFunctionDelegate(): Function<*> +} \ No newline at end of file