diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java index 0badfa1988a..792ce30f6ac 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java @@ -22644,6 +22644,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT public void testEnumEntryArguments() throws Exception { runTest("compiler/testData/codegen/box/ir/privateSignatures/enumEntryArguments.kt"); } + + @Test + @TestMetadata("privateLeakThroughInline.kt") + public void testPrivateLeakThroughInline() throws Exception { + runTest("compiler/testData/codegen/box/ir/privateSignatures/privateLeakThroughInline.kt"); + } } @Nested diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index fbe1a0ee832..ad3777c3f33 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.JsSuspendArityStoreLo import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.JsSuspendFunctionsLowering import org.jetbrains.kotlin.ir.backend.js.lower.inline.CopyInlineFunctionBodyLowering import org.jetbrains.kotlin.ir.backend.js.lower.inline.RemoveInlineDeclarationsWithReifiedTypeParametersLowering +import org.jetbrains.kotlin.ir.backend.js.lower.inline.SyntheticAccessorLowering import org.jetbrains.kotlin.ir.backend.js.lower.inline.jsRecordExtractedLocalClasses import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrModuleFragment @@ -232,13 +233,20 @@ private val localClassesExtractionFromInlineFunctionsPhase = makeBodyLoweringPha prerequisite = setOf(localClassesInInlineFunctionsPhase) ) +private val syntheticAccessorLoweringPhase = makeBodyLoweringPhase( + ::SyntheticAccessorLowering, + name = "syntheticAccessorLoweringPhase", + description = "Wrap top level inline function to access through them from inline functions" +) + private val functionInliningPhase = makeBodyLoweringPhase( ::FunctionInlining, name = "FunctionInliningPhase", description = "Perform function inlining", prerequisite = setOf( expectDeclarationsRemovingPhase, sharedVariablesLoweringPhase, - localClassesInInlineLambdasPhase, localClassesExtractionFromInlineFunctionsPhase + localClassesInInlineLambdasPhase, localClassesExtractionFromInlineFunctionsPhase, + syntheticAccessorLoweringPhase ) ) @@ -750,6 +758,7 @@ private val loweringList = listOf( localClassesInInlineLambdasPhase, localClassesInInlineFunctionsPhase, localClassesExtractionFromInlineFunctionsPhase, + syntheticAccessorLoweringPhase, functionInliningPhase, copyInlineFunctionBodyLoweringPhase, removeInlineDeclarationsWithReifiedTypeParametersLoweringPhase, diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/inline/SyntheticAccessorLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/inline/SyntheticAccessorLowering.kt new file mode 100644 index 00000000000..be21f69205e --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/inline/SyntheticAccessorLowering.kt @@ -0,0 +1,219 @@ +/* + * Copyright 2010-2021 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.ir.backend.js.lower.inline + +import org.jetbrains.kotlin.backend.common.BodyLoweringPass +import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrReturnImpl +import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl +import org.jetbrains.kotlin.ir.types.defaultType +import org.jetbrains.kotlin.ir.types.impl.IrUninitializedType +import org.jetbrains.kotlin.ir.util.DeepCopyIrTreeWithSymbols +import org.jetbrains.kotlin.ir.util.DeepCopySymbolRemapper +import org.jetbrains.kotlin.ir.util.SimpleTypeRemapper +import org.jetbrains.kotlin.ir.util.withinScope +import org.jetbrains.kotlin.ir.visitors.* +import org.jetbrains.kotlin.name.Name + +class SyntheticAccessorLowering(private val context: CommonBackendContext) : BodyLoweringPass { + + private class CandidatesCollector(val candidates: MutableCollection) : IrElementVisitorVoid { + + private fun IrSimpleFunction.isTopLevelPrivate(): Boolean { + if (visibility != DescriptorVisibilities.PRIVATE) return false + return parent is IrFile + } + + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitSimpleFunction(declaration: IrSimpleFunction) { + if (declaration.isInline) { + super.visitSimpleFunction(declaration) + } + } + + override fun visitConstructor(declaration: IrConstructor) {} + + override fun visitProperty(declaration: IrProperty) {} + + override fun visitAnonymousInitializer(declaration: IrAnonymousInitializer) {} + + override fun visitMemberAccess(expression: IrMemberAccessExpression<*>) { + + super.visitMemberAccess(expression) + + val callee = expression.symbol.owner as? IrSimpleFunction + + if (callee != null) { + if (callee.isInline) return + + if (callee.isTopLevelPrivate()) { + candidates.add(callee) + } + } + } + } + + private class FunctionCopier(fileHash: String) { + fun copy(function: IrSimpleFunction): IrSimpleFunction { + function.acceptVoid(symbolRemapper) + return function.transform(copier, data = null) as IrSimpleFunction + } + + private val symbolRemapper = object : DeepCopySymbolRemapper() { + override fun visitSimpleFunction(declaration: IrSimpleFunction) { + remapSymbol(functions, declaration) { + IrSimpleFunctionSymbolImpl() + } + declaration.typeParameters.forEach { it.acceptVoid(this) } + declaration.extensionReceiverParameter?.acceptVoid(this) + declaration.valueParameters.forEach { it.acceptVoid(this) } + } + } + + private val typeRemapper = SimpleTypeRemapper(symbolRemapper) + + private val copier = object : DeepCopyIrTreeWithSymbols(symbolRemapper, typeRemapper) { + override fun visitSimpleFunction(declaration: IrSimpleFunction): IrSimpleFunction { + val newName = Name.identifier("${declaration.name.asString()}\$accessor\$$fileHash") + return declaration.factory.createFunction( + declaration.startOffset, declaration.endOffset, + mapDeclarationOrigin(declaration.origin), + symbolRemapper.getDeclaredFunction(declaration.symbol), + newName, + DescriptorVisibilities.INTERNAL, + declaration.modality, + IrUninitializedType, + isInline = declaration.isInline, + isExternal = declaration.isExternal, + isTailrec = declaration.isTailrec, + isSuspend = declaration.isSuspend, + isOperator = declaration.isOperator, + isInfix = declaration.isInfix, + isExpect = declaration.isExpect, + isFakeOverride = declaration.isFakeOverride, + containerSource = declaration.containerSource, + ).apply { + assert(declaration.overriddenSymbols.isEmpty()) { "Top level function overrides nothing" } + transformFunctionChildren(declaration) + } + } + + private fun IrSimpleFunction.transformFunctionChildren(declaration: IrSimpleFunction) { + copyTypeParametersFrom(declaration) + typeRemapper.withinScope(this) { + assert(declaration.dispatchReceiverParameter == null) { "Top level functions do not have dispatch receiver" } + extensionReceiverParameter = declaration.extensionReceiverParameter?.transform()?.also { + it.parent = this + } + returnType = typeRemapper.remapType(declaration.returnType) + valueParameters = declaration.valueParameters.transform() + valueParameters.forEach { it.parent = this } + typeParameters.forEach { it.parent = this } + } + } + } + } + + private fun IrSimpleFunction.createAccessor(fileHash: String): IrSimpleFunction { + val copier = FunctionCopier(fileHash) + val newFunction = copier.copy(this) + + val irCall = IrCallImpl(startOffset, endOffset, newFunction.returnType, symbol, typeParameters.size, valueParameters.size) + + newFunction.typeParameters.forEachIndexed { i, tp -> + irCall.putTypeArgument(i, tp.defaultType) + } + + newFunction.valueParameters.forEachIndexed { i, vp -> + irCall.putValueArgument(i, IrGetValueImpl(startOffset, endOffset, vp.type, vp.symbol)) + } + + irCall.extensionReceiver = newFunction.extensionReceiverParameter?.let { + IrGetValueImpl(startOffset, endOffset, it.type, it.symbol) + } + + val irReturn = IrReturnImpl(startOffset, endOffset, context.irBuiltIns.nothingType, newFunction.symbol, irCall) + + newFunction.body = context.irFactory.createBlockBody(startOffset, endOffset, listOf(irReturn)) + + return newFunction + } + + class CallSiteTransformer(private val functionMap: Map) : IrElementTransformerVoid() { + override fun visitCall(expression: IrCall): IrExpression { + expression.transformChildrenVoid() + + val callee = expression.symbol.owner + + functionMap[callee]?.let { newFunction -> + val newExpression = expression.run { + IrCallImpl(startOffset, endOffset, type, newFunction.symbol, typeArgumentsCount, valueArgumentsCount, origin) + } + + newExpression.copyTypeArgumentsFrom(expression) + newExpression.extensionReceiver = expression.extensionReceiver + for (i in 0 until expression.valueArgumentsCount) { + newExpression.putValueArgument(i, expression.getValueArgument(i)) + } + + return newExpression + } + + return expression + } + + override fun visitFunctionReference(expression: IrFunctionReference): IrExpression { + expression.transformChildrenVoid() + + val callee = expression.symbol.owner + + functionMap[callee]?.let { newFunction -> + val newExpression = expression.run { + // TODO: What has to be done with `reflectionTarget`? + IrFunctionReferenceImpl(startOffset, endOffset, type, newFunction.symbol, typeArgumentsCount, valueArgumentsCount) + } + + newExpression.copyTypeArgumentsFrom(expression) + newExpression.extensionReceiver = expression.extensionReceiver + for (i in 0 until expression.valueArgumentsCount) { + newExpression.putValueArgument(i, expression.getValueArgument(i)) + } + + return newExpression + } + + return expression + } + } + + override fun lower(irBody: IrBody, container: IrDeclaration) {} + + override fun lower(irFile: IrFile) { + val candidates = mutableListOf() + irFile.acceptChildrenVoid(CandidatesCollector(candidates)) + + if (candidates.isEmpty()) return + + val fileHash = irFile.fileEntry.name.hashCode().toUInt().toString(Character.MAX_RADIX) + val accessors = candidates.map { it.createAccessor(fileHash) } + val candidatesMap = candidates.zip(accessors).toMap() + + irFile.transformChildrenVoid(CallSiteTransformer(candidatesMap)) + + accessors.forEach { it.parent = irFile } + irFile.declarations.addAll(accessors) + } +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/ir/privateSignatures/privateLeakThroughInline.kt b/compiler/testData/codegen/box/ir/privateSignatures/privateLeakThroughInline.kt new file mode 100644 index 00000000000..cfc83116263 --- /dev/null +++ b/compiler/testData/codegen/box/ir/privateSignatures/privateLeakThroughInline.kt @@ -0,0 +1,40 @@ +// MODULE: lib +// FILE: f1.kt + +internal inline fun foo(): String = bar() + +private fun bar(): String { + return "11" +} + +class C { + internal inline fun fi(): String = bi() + + private fun bi(): String = "22" +} + +private fun dex(): String = "33" + +class CC { + internal inline fun fx(): String = dex() +} + +// FILE: f2.kt + +fun test1(): String = foo() + +fun test2(): String = C().fi() + +fun test3(): String = CC().fx() + + +// MODULE: main(lib) +// FILE: m.kt + +fun box(): String { + if (test1() != "11") return "FAIL 1" + if (test2() != "22") return "FAIL 2" + if (test3() != "33") return "FAIL 3" + + return "OK" +} \ No newline at end of file diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java index 65e51517466..f6e30e4d28f 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java @@ -22506,6 +22506,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { public void testEnumEntryArguments() throws Exception { runTest("compiler/testData/codegen/box/ir/privateSignatures/enumEntryArguments.kt"); } + + @Test + @TestMetadata("privateLeakThroughInline.kt") + public void testPrivateLeakThroughInline() throws Exception { + runTest("compiler/testData/codegen/box/ir/privateSignatures/privateLeakThroughInline.kt"); + } } @Nested diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java index 60b547f5070..f9265971ce2 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java @@ -22644,6 +22644,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes public void testEnumEntryArguments() throws Exception { runTest("compiler/testData/codegen/box/ir/privateSignatures/enumEntryArguments.kt"); } + + @Test + @TestMetadata("privateLeakThroughInline.kt") + public void testPrivateLeakThroughInline() throws Exception { + runTest("compiler/testData/codegen/box/ir/privateSignatures/privateLeakThroughInline.kt"); + } } @Nested diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 0abb71943c3..2a2e55ae8e4 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -18865,6 +18865,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes public void testEnumEntryArguments() throws Exception { runTest("compiler/testData/codegen/box/ir/privateSignatures/enumEntryArguments.kt"); } + + @TestMetadata("privateLeakThroughInline.kt") + public void testPrivateLeakThroughInline() throws Exception { + runTest("compiler/testData/codegen/box/ir/privateSignatures/privateLeakThroughInline.kt"); + } } @TestMetadata("compiler/testData/codegen/box/ir/serializationRegressions") diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java index f3a84849e14..85e31fcc64e 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java @@ -15980,6 +15980,11 @@ public class IrJsCodegenBoxES6TestGenerated extends AbstractIrJsCodegenBoxES6Tes runTest("compiler/testData/codegen/box/ir/privateSignatures/localFakeOverride.kt"); } + @TestMetadata("privateLeakThroughInline.kt") + public void testPrivateLeakThroughInline() throws Exception { + runTest("compiler/testData/codegen/box/ir/privateSignatures/privateLeakThroughInline.kt"); + } + @TestMetadata("topLevelPrivateDelegate.kt") public void testTopLevelPrivateDelegate() throws Exception { runTest("compiler/testData/codegen/box/ir/privateSignatures/topLevelPrivateDelegate.kt"); diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java index 8dda8e00d3c..d65e6f169b2 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java @@ -15386,6 +15386,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest { runTest("compiler/testData/codegen/box/ir/privateSignatures/localFakeOverride.kt"); } + @TestMetadata("privateLeakThroughInline.kt") + public void testPrivateLeakThroughInline() throws Exception { + runTest("compiler/testData/codegen/box/ir/privateSignatures/privateLeakThroughInline.kt"); + } + @TestMetadata("topLevelPrivateDelegate.kt") public void testTopLevelPrivateDelegate() throws Exception { runTest("compiler/testData/codegen/box/ir/privateSignatures/topLevelPrivateDelegate.kt"); diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index 00cf86f8d0f..00614d353b8 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -15451,6 +15451,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { runTest("compiler/testData/codegen/box/ir/privateSignatures/localFakeOverride.kt"); } + @TestMetadata("privateLeakThroughInline.kt") + public void testPrivateLeakThroughInline() throws Exception { + runTest("compiler/testData/codegen/box/ir/privateSignatures/privateLeakThroughInline.kt"); + } + @TestMetadata("topLevelPrivateDelegate.kt") public void testTopLevelPrivateDelegate() throws Exception { runTest("compiler/testData/codegen/box/ir/privateSignatures/topLevelPrivateDelegate.kt"); diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/wasm/semantics/IrCodegenBoxWasmTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/wasm/semantics/IrCodegenBoxWasmTestGenerated.java index 544c84537c1..5ed2def2af1 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/wasm/semantics/IrCodegenBoxWasmTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/wasm/semantics/IrCodegenBoxWasmTestGenerated.java @@ -9075,6 +9075,11 @@ public class IrCodegenBoxWasmTestGenerated extends AbstractIrCodegenBoxWasmTest public void testEnumEntryArguments() throws Exception { runTest("compiler/testData/codegen/box/ir/privateSignatures/enumEntryArguments.kt"); } + + @TestMetadata("privateLeakThroughInline.kt") + public void testPrivateLeakThroughInline() throws Exception { + runTest("compiler/testData/codegen/box/ir/privateSignatures/privateLeakThroughInline.kt"); + } } @TestMetadata("compiler/testData/codegen/box/ir/serializationRegressions")