[JS IR] Use a copy of an original inline function for inlining

If inline function A calls another inline function B,
 we must use the original version of inline function A for inlining,
 which doesn’t have inlined function B. Because during the inlining
 process, we remap all occurrences of inline function A
 to a temporary copy of function A, and if the function B
 somehow uses function A (e.g. callable reference),
 the built IR will have a reference to the temporary function,
 not the original one. All these things lead to broken cross-module references.

 This patch saves the original versions of all inline functions
 before inlining and provides them during the inline process.

^KT-55930 Fixed
This commit is contained in:
Alexander Korepanov
2023-01-13 16:07:00 +01:00
committed by Space Team
parent 82d934d873
commit a5c8e30bb1
9 changed files with 110 additions and 7 deletions
@@ -42,6 +42,7 @@ fun IrExpression.isAdaptedFunctionReference() =
interface InlineFunctionResolver {
fun getFunctionDeclaration(symbol: IrFunctionSymbol): IrFunction
fun getFunctionSymbol(irFunction: IrFunction): IrFunctionSymbol
}
fun IrFunction.isTopLevelInPackage(name: String, packageName: String): Boolean {
@@ -72,6 +73,10 @@ open class DefaultInlineFunctionResolver(open val context: CommonBackendContext)
else -> (symbol.owner as? IrSimpleFunction)?.resolveFakeOverride() ?: symbol.owner
}
}
override fun getFunctionSymbol(irFunction: IrFunction): IrFunctionSymbol {
return irFunction.symbol
}
}
class FunctionInlining(
@@ -206,7 +211,7 @@ class FunctionInlining(
symbol = irReturnableBlockSymbol,
origin = null,
statements = newStatements,
inlineFunctionSymbol = callee.symbol
inlineFunctionSymbol = inlineFunctionResolver.getFunctionSymbol(callee)
).apply {
transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitReturn(expression: IrReturn): IrExpression {
@@ -254,10 +254,10 @@ private val wrapInlineDeclarationsWithReifiedTypeParametersLowering = makeBodyLo
description = "Wrap inline declarations with reified type parameters"
)
private val functionInliningPhase = makeBodyLoweringPhase(
{ FunctionInlining(it, it.innerClassesSupport) },
name = "FunctionInliningPhase",
description = "Perform function inlining",
private val saveInlineFunctionsBeforeInlining = makeDeclarationTransformerPhase(
::SaveInlineFunctionsBeforeInlining,
name = "SaveInlineFunctionsBeforeInlining",
description = "Save inline function before inlining",
prerequisite = setOf(
expectDeclarationsRemovingPhase, sharedVariablesLoweringPhase,
localClassesInInlineLambdasPhase, localClassesExtractionFromInlineFunctionsPhase,
@@ -265,6 +265,13 @@ private val functionInliningPhase = makeBodyLoweringPhase(
)
)
private val functionInliningPhase = makeBodyLoweringPhase(
{ FunctionInlining(it, JsInlineFunctionResolver(it), it.innerClassesSupport) },
name = "FunctionInliningPhase",
description = "Perform function inlining",
prerequisite = setOf(saveInlineFunctionsBeforeInlining)
)
private val copyInlineFunctionBodyLoweringPhase = makeDeclarationTransformerPhase(
::CopyInlineFunctionBodyLowering,
name = "CopyInlineFunctionBody",
@@ -843,6 +850,7 @@ val loweringList = listOf<Lowering>(
localClassesExtractionFromInlineFunctionsPhase,
syntheticAccessorLoweringPhase,
wrapInlineDeclarationsWithReifiedTypeParametersLowering,
saveInlineFunctionsBeforeInlining,
functionInliningPhase,
copyInlineFunctionBodyLoweringPhase,
removeInlineDeclarationsWithReifiedTypeParametersLoweringPhase,
@@ -33,6 +33,8 @@ class JsMapping : DefaultMapping() {
val suspendArityStore = DefaultDelegateFactory.newDeclarationToDeclarationCollectionMapping<IrClass, Collection<IrSimpleFunction>>()
val inlineFunctionsBeforeInlining = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrFunction, IrFunction>()
// Wasm mappings
val wasmJsInteropFunctionToWrapper =
DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrSimpleFunction, IrSimpleFunction>()
@@ -45,4 +47,4 @@ class JsMapping : DefaultMapping() {
val wasmExternalClassToInstanceCheck =
DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrSimpleFunction>()
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2023 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.DeclarationTransformer
import org.jetbrains.kotlin.backend.common.lower.inline.DefaultInlineFunctionResolver
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.deepCopyWithVariables
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
internal class SaveInlineFunctionsBeforeInlining(context: JsIrBackendContext) : DeclarationTransformer {
private val inlineFunctionsBeforeInlining = context.mapping.inlineFunctionsBeforeInlining
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (declaration is IrFunction && declaration.isInline) {
inlineFunctionsBeforeInlining[declaration] = declaration.deepCopyWithVariables().also {
it.patchDeclarationParents(declaration.parent)
}
}
return null
}
}
internal class JsInlineFunctionResolver(context: JsIrBackendContext) : DefaultInlineFunctionResolver(context) {
private val inlineFunctionsBeforeInlining = context.mapping.inlineFunctionsBeforeInlining
private val inlineFunctionsBeforeInliningSymbols = hashMapOf<IrFunction, IrFunctionSymbol>()
override fun getFunctionDeclaration(symbol: IrFunctionSymbol): IrFunction {
val function = super.getFunctionDeclaration(symbol)
val functionBeforeInlining = inlineFunctionsBeforeInlining[function] ?: return function
inlineFunctionsBeforeInliningSymbols[functionBeforeInlining] = function.symbol
return functionBeforeInlining
}
override fun getFunctionSymbol(irFunction: IrFunction): IrFunctionSymbol {
return inlineFunctionsBeforeInliningSymbols[irFunction] ?: super.getFunctionSymbol(irFunction)
}
}
@@ -117,7 +117,8 @@ class CrossModuleDependenciesResolver(
if (tag in header.optionalCrossModuleImports) {
continue
}
error("Internal error: cannot find external signature '$tag' for module ${header.moduleName}")
val name = header.nameBindings[tag] ?: "<unknown name>"
error("Internal error: cannot find external signature '$tag' for name '$name' in module ${header.moduleName}")
}
builder.imports += CrossModuleRef(fromModuleBuilder, tag)
@@ -5162,6 +5162,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
runTest("js/js.translator/testData/box/inline/privateProperty.kt");
}
@Test
@TestMetadata("recursiveDependency.kt")
public void testRecursiveDependency() throws Exception {
runTest("js/js.translator/testData/box/inline/recursiveDependency.kt");
}
@Test
@TestMetadata("rootConstructor.kt")
public void testRootConstructor() throws Exception {
@@ -5784,6 +5784,12 @@ public class FirJsBoxTestGenerated extends AbstractFirJsBoxTest {
runTest("js/js.translator/testData/box/inline/privateProperty.kt");
}
@Test
@TestMetadata("recursiveDependency.kt")
public void testRecursiveDependency() throws Exception {
runTest("js/js.translator/testData/box/inline/recursiveDependency.kt");
}
@Test
@TestMetadata("rootConstructor.kt")
public void testRootConstructor() throws Exception {
@@ -5784,6 +5784,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
runTest("js/js.translator/testData/box/inline/privateProperty.kt");
}
@Test
@TestMetadata("recursiveDependency.kt")
public void testRecursiveDependency() throws Exception {
runTest("js/js.translator/testData/box/inline/recursiveDependency.kt");
}
@Test
@TestMetadata("rootConstructor.kt")
public void testRootConstructor() throws Exception {
@@ -0,0 +1,24 @@
// IGNORE_BACKEND: JS
// MODULE: lib
// FILE: A.kt
inline fun funA(flag: Boolean): String {
if (flag) {
return funB(flag)
}
return "OK"
}
// FILE: B.kt
inline fun funB(flag: Boolean): String {
val f = ::funA
if (flag) {
return f(false)
}
return "NOT OK"
}
// MODULE: main(lib)
// FILE: main.kt
fun box(): String {
return funA(true)
}