diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/CacheUpdater.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/CacheUpdater.kt index 2e13aa9d149..25a2fe4a63f 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/CacheUpdater.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/CacheUpdater.kt @@ -493,8 +493,6 @@ class CacheUpdater( val stdlibDirtyFiles = dirtyFiles[stdlibFile] ?: return - signatureHashCalculator.updateInlineFunctionTransitiveHashes(listOf(stdlibIr)) - val updatedMetadata = KotlinSourceFileMutableMap() val idSignatureToFile = updatedMetadata.getExportedSignaturesAndAddMetadata(linker, stdlibIr, stdlibFile, stdlibDirtyFiles) @@ -636,9 +634,6 @@ class CacheUpdater( var lastDirtyFiles: KotlinSourceFileMap = dirtyFileExports while (true) { - stopwatch.startNext("Dependencies ($iterations) - calculating transitive hashes for inline functions") - updater.signatureHashCalculator.updateInlineFunctionTransitiveHashes(loadedIr.loadedFragments.values) - stopwatch.startNext("Dependencies ($iterations) - updating a dependency graph") val dirtyMetadata = updater.rebuildDirtySourceMetadata(loadedIr.linker, loadedIr.loadedFragments, lastDirtyFiles) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt index 94405309830..642f73aea16 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt @@ -112,7 +112,7 @@ internal fun CompilerConfiguration.configHashForIC() = HashCalculatorForIC().app update(languageVersionSettings.toString()) }.finalize() -internal fun IrSimpleFunction.irSimpleFunctionHashForIC() = HashCalculatorForIC().also { +internal fun IrFunction.irFunctionHashForIC() = HashCalculatorForIC().also { it.update(this) }.finalize() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IdSignatureHashCalculator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IdSignatureHashCalculator.kt index f7ca006d9b1..5179b3fc3d3 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IdSignatureHashCalculator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IdSignatureHashCalculator.kt @@ -14,129 +14,114 @@ import org.jetbrains.kotlin.ir.util.IdSignature import org.jetbrains.kotlin.ir.util.isFakeOverride import org.jetbrains.kotlin.ir.util.render import org.jetbrains.kotlin.ir.util.resolveFakeOverride -import org.jetbrains.kotlin.ir.visitors.IrElementVisitor import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptVoid - +import org.jetbrains.kotlin.utils.addToStdlib.ifTrue internal class IdSignatureHashCalculator { - private val flatHashes = hashMapOf() - private val inlineFunctionCallGraph = hashMapOf>() - private val processingFunctions = hashSetOf() - private val functionTransitiveHashes = hashMapOf() - private val fileAnnotationHashes = hashMapOf() + private val idSignatureSources = hashMapOf() + private val idSignatureHashes = hashMapOf() - private val allIdSignatureHashes = hashMapOf() + private val fileAnnotationHashes = hashMapOf() + private val inlineFunctionFlatHashes = hashMapOf() + + private val inlineFunctionDepends = hashMapOf>() private val IrFile.annotationsHash: ICHash get() = fileAnnotationHashes.getOrPut(this, ::irAnnotationContainerHashForIC) - private inner class FlatHashCalculator : IrElementVisitorVoid { - private var fileAnnotationsHash = ICHash() - - override fun visitElement(element: IrElement) { - element.acceptChildrenVoid(this) - } - - override fun visitFile(declaration: IrFile, data: Nothing?) { - fileAnnotationsHash = declaration.annotationsHash - declaration.acceptChildrenVoid(this) - } - - override fun visitSimpleFunction(declaration: IrSimpleFunction) { - if (declaration.isInline) { - if (declaration in flatHashes) { - return - } - - val inlineFunctionFlatHash = if (declaration.isFakeOverride) { - declaration.resolveFakeOverride()?.irSimpleFunctionHashForIC() - ?: icError("can not resolve fake override for ${declaration.render()}") - } else { - declaration.irSimpleFunctionHashForIC() - } - - flatHashes[declaration] = fileAnnotationsHash.combineWith(inlineFunctionFlatHash) - } - // go deeper since local inline special declarations (like a reference adaptor) may appear - declaration.acceptChildrenVoid(this) - } - } - - private inner class InlineFunctionCallGraphBuilder : IrElementVisitor> { - override fun visitElement(element: IrElement, data: MutableSet) { - element.acceptChildren(this, data) - } - - override fun visitSimpleFunction(declaration: IrSimpleFunction, data: MutableSet) { - if (declaration in inlineFunctionCallGraph) { - return - } - val newGraph = mutableSetOf() - inlineFunctionCallGraph[declaration] = newGraph - declaration.acceptChildren(this, newGraph) - } - - override fun visitCall(expression: IrCall, data: MutableSet) { - val callee = expression.symbol.owner - if (callee.isInline) { - data += callee - } - expression.acceptChildren(this, data) - } - - override fun visitFunctionReference(expression: IrFunctionReference, data: MutableSet) { - val reference = expression.symbol.owner - if (reference.isInline) { - // this if is fine, because fake overrides are not inlined as function reference calls even as inline function args - if (!reference.isFakeOverride) { - data += reference - } - } - expression.acceptChildren(this, data) - } - } - - private fun getInlineFunctionTransitiveHash(f: IrFunction): ICHash = functionTransitiveHashes.getOrPut(f) { - if (!processingFunctions.add(f)) { - icError("inline circle through function ${f.render()} detected") - } - val callees = inlineFunctionCallGraph[f] ?: icError("inline function is missed in inline graph ${f.render()}") - val flatHash = flatHashes[f] ?: icError("no flat hash for ${f.render()}") - var functionInlineHash = flatHash - for (callee in callees) { - functionInlineHash = functionInlineHash.combineWith(getInlineFunctionTransitiveHash(callee)) - } - processingFunctions.remove(f) - f.symbol.signature?.let { allIdSignatureHashes[it] = functionInlineHash } - functionInlineHash - } - - private fun updateTransitiveHashesByCallGraph() { - for ((f, callees) in inlineFunctionCallGraph.entries) { - if (f.isInline) { - getInlineFunctionTransitiveHash(f) + private val IrFunction.inlineFunctionFlatHash: ICHash + get() = inlineFunctionFlatHashes.getOrPut(this) { + val flatHash = if (isFakeOverride && this is IrSimpleFunction) { + resolveFakeOverride()?.irFunctionHashForIC() + ?: icError("can not resolve fake override for ${render()}") } else { - callees.forEach(::getInlineFunctionTransitiveHash) + irFunctionHashForIC() } + symbol.calculateSymbolHash().combineWith(flatHash) } + + private val IrFunction.inlineDepends: Collection + get() = inlineFunctionDepends.getOrPut(this) { + val usedInlineFunctions = linkedSetOf() + + acceptVoid(object : IrElementVisitorVoid { + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitCall(expression: IrCall) { + val callee = expression.symbol.owner + if (callee.isInline) { + usedInlineFunctions += callee + } + expression.acceptChildrenVoid(this) + } + + override fun visitFunctionReference(expression: IrFunctionReference) { + val reference = expression.symbol.owner + if (reference.isInline) { + // this if is fine, because fake overrides are not inlined as function reference calls even as inline function args + if (!reference.isFakeOverride) { + usedInlineFunctions += reference + } + } + expression.acceptChildrenVoid(this) + } + }) + + usedInlineFunctions + } + + private fun IrSymbol.calculateSymbolHash(): ICHash { + var srcIrFile = signature?.let { sig -> idSignatureSources[sig]?.srcIrFile } + if (srcIrFile == null) { + var parentDeclaration = (owner as? IrDeclaration)?.parent + while (parentDeclaration is IrDeclaration) { + parentDeclaration = parentDeclaration.parent + } + srcIrFile = parentDeclaration as? IrFile + } + + val fileAnnotationsHash = srcIrFile?.annotationsHash ?: ICHash() + return fileAnnotationsHash.combineWith(irSymbolHashForIC()) } - fun updateInlineFunctionTransitiveHashes(fragments: Collection) { - fragments.forEach { it.acceptVoid(FlatHashCalculator()) } - fragments.forEach { it.acceptChildren(InlineFunctionCallGraphBuilder(), mutableSetOf()) } - updateTransitiveHashesByCallGraph() + private fun IrFunction.calculateInlineFunctionTransitiveHash(): ICHash { + var transitiveHash = inlineFunctionFlatHash + val transitiveDepends = hashSetOf(this) + val newDependsStack = transitiveDepends.toMutableList() + + while (newDependsStack.isNotEmpty()) { + newDependsStack.removeLast().inlineDepends.forEach { inlineFunction -> + if (transitiveDepends.add(inlineFunction)) { + newDependsStack += inlineFunction + transitiveHash = transitiveHash.combineWith(inlineFunction.inlineFunctionFlatHash) + } + } + } + + return transitiveHash } fun addAllSignatureSymbols(idSignatureToFile: Map) { - for ((signature, signatureSrc) in idSignatureToFile) { - if (signature !in allIdSignatureHashes) { - val fileAnnotationsHash = signatureSrc.srcIrFile.annotationsHash - allIdSignatureHashes[signature] = fileAnnotationsHash.combineWith(signatureSrc.symbol.irSymbolHashForIC()) - } - } + idSignatureSources += idSignatureToFile } - operator fun get(signature: IdSignature) = allIdSignatureHashes[signature] + operator fun get(signature: IdSignature): ICHash? { + val hash = idSignatureHashes[signature] + if (hash != null) { + return hash + } + + val signatureSymbol = idSignatureSources[signature]?.symbol ?: return null + + val signatureHash = (signatureSymbol.owner as? IrFunction)?.let { function -> + function.isInline.ifTrue { function.calculateInlineFunctionTransitiveHash() } + } ?: signatureSymbol.calculateSymbolHash() + + idSignatureHashes[signature] = signatureHash + return signatureHash + } } diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/InvalidationTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/InvalidationTestGenerated.java index d6a9b5d9b0a..740dc517ab2 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/InvalidationTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/InvalidationTestGenerated.java @@ -205,6 +205,11 @@ public class InvalidationTestGenerated extends AbstractInvalidationTest { runTest("js/js.translator/testData/incremental/invalidation/inlineFunctionAsParam/"); } + @TestMetadata("inlineFunctionCircleUsage") + public void testInlineFunctionCircleUsage() throws Exception { + runTest("js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/"); + } + @TestMetadata("inlineFunctionDefaultParams") public void testInlineFunctionDefaultParams() throws Exception { runTest("js/js.translator/testData/incremental/invalidation/inlineFunctionDefaultParams/"); diff --git a/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/lib1/l1a.0.kt b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/lib1/l1a.0.kt new file mode 100644 index 00000000000..ea7876904f4 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/lib1/l1a.0.kt @@ -0,0 +1,6 @@ +inline fun funA(flag: Boolean = true): Int { + if (flag) { + return funB() + } + return 2 +} diff --git a/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/lib1/l1a.2.kt b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/lib1/l1a.2.kt new file mode 100644 index 00000000000..3ce25eff57b --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/lib1/l1a.2.kt @@ -0,0 +1,6 @@ +inline fun funA(flag: Boolean = false): Int { + if (flag) { + return funB() + } + return 2 +} diff --git a/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/lib1/l1b.0.kt b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/lib1/l1b.0.kt new file mode 100644 index 00000000000..58b10bb2867 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/lib1/l1b.0.kt @@ -0,0 +1,7 @@ +inline fun funB(): Int { + val f = ::funA + if (false) { + return f(false) + } + return 0 +} diff --git a/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/lib1/l1b.1.kt b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/lib1/l1b.1.kt new file mode 100644 index 00000000000..e97fbc2d1dd --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/lib1/l1b.1.kt @@ -0,0 +1,7 @@ +inline fun funB(): Int { + val f = ::funA + if (false) { + return f(false) + } + return 1 +} diff --git a/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/lib1/module.info b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/lib1/module.info new file mode 100644 index 00000000000..0e688c3a216 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/lib1/module.info @@ -0,0 +1,15 @@ +STEP 0: + modifications: + U : l1a.0.kt -> l1a.kt + U : l1b.0.kt -> l1b.kt + added file: l1a.kt, l1b.kt +STEP 1: + modifications: + U : l1b.1.kt -> l1b.kt + modified ir: l1b.kt + updated imports: l1a.kt +STEP 2: + modifications: + U : l1a.2.kt -> l1a.kt + modified ir: l1a.kt + updated imports: l1b.kt diff --git a/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/main/m.kt b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/main/m.kt new file mode 100644 index 00000000000..20cacb21a8a --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/main/m.kt @@ -0,0 +1,7 @@ +fun box(stepId: Int): String { + val x = funA() + if (x != stepId) { + return "Fail: $x != $stepId" + } + return "OK" +} diff --git a/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/main/module.info b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/main/module.info new file mode 100644 index 00000000000..86cd47f6b2c --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/main/module.info @@ -0,0 +1,6 @@ +STEP 0: + dependencies: lib1 + added file: m.kt +STEP 1..2: + dependencies: lib1 + updated imports: m.kt diff --git a/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/project.info b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/project.info new file mode 100644 index 00000000000..912abf292a5 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/inlineFunctionCircleUsage/project.info @@ -0,0 +1,5 @@ +MODULES: lib1, main + +STEP 0..2: + libs: lib1, main + dirty js: lib1, main