From 2b4424b56485361e4c118211f10db6216bb24e3e Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Wed, 21 Aug 2019 15:10:14 +0200 Subject: [PATCH] JVM IR: replace function accesses in multi-file parts Any access to a function from a multi-file part needs to be replaced with the access to the corresponding public method (if it exists) from the facade class. Note that this has no immediate effect because we use KotlinTypeMapper for mapping calls, and it understands that a call to a function from the part must actually be generated into a call to the function from the facade in the bytecode. This commit merely changes the IR to better reflect what's generated in the final bytecode, and to be able to use simplified IR-based method signature mapping instead of the legacy KotlinTypeMapper in the future. --- .../kotlin/backend/jvm/JvmBackendContext.kt | 1 + .../jvm/codegen/IrSourceCompilerForInline.kt | 10 +++- .../jvm/lower/GenerateMultifileFacades.kt | 54 ++++++++++++++++--- .../multifileClasses/defaultArguments.kt | 15 ++++++ .../BlackBoxInlineCodegenTestGenerated.java | 5 ++ ...otlinAgainstInlineKotlinTestGenerated.java | 5 ++ .../IrBlackBoxInlineCodegenTestGenerated.java | 5 ++ 7 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 compiler/testData/codegen/boxInline/multifileClasses/defaultArguments.kt diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt index afb792d4a19..505d7fdbcaa 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt @@ -68,6 +68,7 @@ class JvmBackendContext( internal val multifileFacadesToAdd = mutableMapOf>() internal val multifileFacadeForPart = mutableMapOf() + internal val multifileFacadeMemberToPartMember = mutableMapOf() override var inVerbosePhase: Boolean = false diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrSourceCompilerForInline.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrSourceCompilerForInline.kt index 5615a45a0bb..ce9c4d8743b 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrSourceCompilerForInline.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrSourceCompilerForInline.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.backend.jvm.codegen import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import org.jetbrains.kotlin.backend.common.ir.ir2string +import org.jetbrains.kotlin.backend.jvm.lower.MultifileFacadeFileEntry import org.jetbrains.kotlin.codegen.BaseExpressionCodegen import org.jetbrains.kotlin.codegen.ClassBuilder import org.jetbrains.kotlin.codegen.OwnerKind @@ -23,6 +24,7 @@ import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression import org.jetbrains.kotlin.ir.util.parentAsClass +import org.jetbrains.kotlin.ir.util.render import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature @@ -102,9 +104,10 @@ class IrSourceCompilerForInline( private fun getFunctionToInline(call: IrCall, jvmSignature: JvmMethodSignature, callDefault: Boolean): IrFunction { val callee = call.symbol.owner + val parent = callee.parentAsClass if (callDefault) { /*TODO: get rid of hack*/ - return callee.parentAsClass.declarations.filterIsInstance().single { + return parent.declarations.filterIsInstance().single { it.descriptor.name.asString() == jvmSignature.asmMethod.name + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX && codegen.context.methodSignatureMapper.mapSignatureSkipGeneric(callee).asmMethod.descriptor.startsWith( jvmSignature.asmMethod.descriptor.substringBeforeLast(')') @@ -112,6 +115,11 @@ class IrSourceCompilerForInline( } } + if (parent.fileParent.fileEntry is MultifileFacadeFileEntry) { + return (codegen.context.multifileFacadeMemberToPartMember[callee.symbol] + ?: error("Function from a multi-file facade without the link to the function in the part: ${callee.render()}")).owner + } + return callee } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/GenerateMultifileFacades.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/GenerateMultifileFacades.kt index 9ea586fca17..1609aeb8332 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/GenerateMultifileFacades.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/GenerateMultifileFacades.kt @@ -32,10 +32,14 @@ import org.jetbrains.kotlin.ir.builders.irReturn import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.impl.IrFieldImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl +import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrGetField +import org.jetbrains.kotlin.ir.expressions.copyTypeArgumentsFrom +import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol +import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols import org.jetbrains.kotlin.ir.util.dump import org.jetbrains.kotlin.ir.util.transformFlat @@ -55,9 +59,17 @@ internal val generateMultifileFacadesPhase = namedIrModulePhase( input: IrModuleFragment ): IrModuleFragment { val movedFields = mutableMapOf() - input.files.addAll(generateMultifileFacades(input.descriptor, context, movedFields)) + val functionDelegates = mutableMapOf() + + input.files.addAll(generateMultifileFacades(input.descriptor, context, movedFields, functionDelegates)) + UpdateFieldCallSites(movedFields).lower(input) + UpdateFunctionCallSites(functionDelegates).lower(input) + context.multifileFacadesToAdd.clear() + + functionDelegates.entries.associateTo(context.multifileFacadeMemberToPartMember) { (member, newMember) -> newMember to member } + return input } } @@ -86,7 +98,8 @@ internal class MultifileFacadeFileEntry( private fun generateMultifileFacades( module: ModuleDescriptor, context: JvmBackendContext, - movedFields: MutableMap + movedFields: MutableMap, + functionDelegates: MutableMap ): List = context.multifileFacadesToAdd.map { (jvmClassName, partClasses) -> val fileEntry = MultifileFacadeFileEntry(jvmClassName, partClasses.map(IrClass::fileParent)) @@ -106,7 +119,12 @@ private fun generateMultifileFacades( moveFieldsOfConstProperties(partClass, facadeClass, movedFields) for (member in partClass.declarations) { - member.createMultifileDelegateIfNeeded(context, facadeClass) + if (member is IrFunction) { + val newMember = member.createMultifileDelegateIfNeeded(context, facadeClass) + if (newMember != null) { + functionDelegates[member.symbol] = newMember.symbol + } + } } } @@ -135,12 +153,11 @@ private fun IrField.shouldMoveToFacade(): Boolean { return property != null && property.isConst && !Visibilities.isPrivate(visibility) } -private fun IrDeclaration.createMultifileDelegateIfNeeded(context: JvmBackendContext, facadeClass: IrClass) { - if (this !is IrFunction || - Visibilities.isPrivate(visibility) || +private fun IrFunction.createMultifileDelegateIfNeeded(context: JvmBackendContext, facadeClass: IrClass): IrFunction? { + if (Visibilities.isPrivate(visibility) || name == InitializersLowering.clinitName || origin == JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR - ) return + ) return null // TODO: perform copy of the signature only, without body val function = deepCopyWithSymbols(facadeClass) @@ -158,6 +175,8 @@ private fun IrDeclaration.createMultifileDelegateIfNeeded(context: JvmBackendCon function.origin = JvmLoweredDeclarationOrigin.MULTIFILE_BRIDGE facadeClass.declarations.add(function) + + return function } // This deep copy is needed while we still use KotlinTypeMapper to map signatures in method calls. Without it, KotlinTypeMapper takes @@ -190,3 +209,24 @@ private class UpdateFieldCallSites( } } } + +private class UpdateFunctionCallSites( + private val functionDelegates: MutableMap +) : FileLoweringPass, IrElementTransformerVoid() { + override fun lower(irFile: IrFile) { + irFile.transformChildrenVoid(this) + } + + override fun visitCall(expression: IrCall): IrExpression { + val newFunction = functionDelegates[expression.symbol] ?: return super.visitCall(expression) + return expression.run { + IrCallImpl(startOffset, endOffset, type, newFunction).apply { + copyTypeArgumentsFrom(expression) + extensionReceiver = expression.extensionReceiver?.transform(this@UpdateFunctionCallSites, null) + for (i in 0 until valueArgumentsCount) { + putValueArgument(i, expression.getValueArgument(i)?.transform(this@UpdateFunctionCallSites, null)) + } + } + } + } +} diff --git a/compiler/testData/codegen/boxInline/multifileClasses/defaultArguments.kt b/compiler/testData/codegen/boxInline/multifileClasses/defaultArguments.kt new file mode 100644 index 00000000000..c5896c950a9 --- /dev/null +++ b/compiler/testData/codegen/boxInline/multifileClasses/defaultArguments.kt @@ -0,0 +1,15 @@ +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// FILE: 1.kt + +@file:JvmName("Facade") +@file:JvmMultifileClass + +inline fun foo(o: String, k: String = "K", body: (String) -> String): String = + o + bar(body(k)) + +fun bar(x: String): String = x + +// FILE: 2.kt + +fun box(): String = foo("O") { it } diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java index cbd6f92c991..354752321f5 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java @@ -1924,6 +1924,11 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/multifileClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); } + @TestMetadata("defaultArguments.kt") + public void testDefaultArguments() throws Exception { + runTest("compiler/testData/codegen/boxInline/multifileClasses/defaultArguments.kt"); + } + @TestMetadata("inlineFromOptimizedMultifileClass.kt") public void testInlineFromOptimizedMultifileClass() throws Exception { runTest("compiler/testData/codegen/boxInline/multifileClasses/inlineFromOptimizedMultifileClass.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java index 653cee82a67..dcc6da5f24a 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java @@ -1924,6 +1924,11 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/multifileClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); } + @TestMetadata("defaultArguments.kt") + public void testDefaultArguments() throws Exception { + runTest("compiler/testData/codegen/boxInline/multifileClasses/defaultArguments.kt"); + } + @TestMetadata("inlineFromOptimizedMultifileClass.kt") public void testInlineFromOptimizedMultifileClass() throws Exception { runTest("compiler/testData/codegen/boxInline/multifileClasses/inlineFromOptimizedMultifileClass.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java index 13b6cbce34e..438a62e7260 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java @@ -1924,6 +1924,11 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/multifileClasses"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true); } + @TestMetadata("defaultArguments.kt") + public void testDefaultArguments() throws Exception { + runTest("compiler/testData/codegen/boxInline/multifileClasses/defaultArguments.kt"); + } + @TestMetadata("inlineFromOptimizedMultifileClass.kt") public void testInlineFromOptimizedMultifileClass() throws Exception { runTest("compiler/testData/codegen/boxInline/multifileClasses/inlineFromOptimizedMultifileClass.kt");