[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
This commit is contained in:
Roman Artemev
2021-08-09 20:23:46 +03:00
committed by TeamCityServer
parent b3dbca7ea6
commit d3ddeef67f
11 changed files with 312 additions and 1 deletions

View File

@@ -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

View File

@@ -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<Lowering>(
localClassesInInlineLambdasPhase,
localClassesInInlineFunctionsPhase,
localClassesExtractionFromInlineFunctionsPhase,
syntheticAccessorLoweringPhase,
functionInliningPhase,
copyInlineFunctionBodyLoweringPhase,
removeInlineDeclarationsWithReifiedTypeParametersLoweringPhase,

View File

@@ -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<IrSimpleFunction>) : 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<IrSimpleFunction, IrSimpleFunction>) : 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<IrSimpleFunction>()
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)
}
}

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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");

View File

@@ -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");

View File

@@ -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");

View File

@@ -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")