From d3ddeef67fb2df3a9b631671ca5aa70530be27c3 Mon Sep 17 00:00:00 2001 From: Roman Artemev Date: Mon, 9 Aug 2021 20:23:46 +0300 Subject: [PATCH] [JS IR] Wrap private top level function with internal accessor stub In case internal inline function references private top level function after inline such function (T.L.P.) couldn't be referenced in klib and IC cache. So create internally visible accessors for P.T.L. function similar to what JVM backend does. - add box test --- .../FirBlackBoxCodegenTestGenerated.java | 6 + .../kotlin/ir/backend/js/JsLoweringPhases.kt | 11 +- .../lower/inline/SyntheticAccessorLowering.kt | 219 ++++++++++++++++++ .../privateLeakThroughInline.kt | 40 ++++ .../codegen/BlackBoxCodegenTestGenerated.java | 6 + .../IrBlackBoxCodegenTestGenerated.java | 6 + .../LightAnalysisModeTestGenerated.java | 5 + .../IrJsCodegenBoxES6TestGenerated.java | 5 + .../IrJsCodegenBoxTestGenerated.java | 5 + .../semantics/JsCodegenBoxTestGenerated.java | 5 + .../IrCodegenBoxWasmTestGenerated.java | 5 + 11 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/inline/SyntheticAccessorLowering.kt create mode 100644 compiler/testData/codegen/box/ir/privateSignatures/privateLeakThroughInline.kt 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")