mirror of
https://github.com/jlengrand/kotlin.git
synced 2026-03-10 08:31:29 +00:00
[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:
committed by
TeamCityServer
parent
b3dbca7ea6
commit
d3ddeef67f
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
40
compiler/testData/codegen/box/ir/privateSignatures/privateLeakThroughInline.kt
vendored
Normal file
40
compiler/testData/codegen/box/ir/privateSignatures/privateLeakThroughInline.kt
vendored
Normal 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"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user