[JS IR IC] Calculate symbol hashes on demand
Optimization: often we don't need all hashes for all available symbols. This patch allows the calculation of the symbol hash only on demand. This patch also allows cycles for the inline function dependency graph in the IC infrastructure. Note that the inline function cycles may crash the inliner, however it is not the IC infrastructure responsibility to check the cycles.
This commit is contained in:
committed by
Space Team
parent
bd295df7d0
commit
c502cae437
@@ -493,8 +493,6 @@ class CacheUpdater(
|
||||
|
||||
val stdlibDirtyFiles = dirtyFiles[stdlibFile] ?: return
|
||||
|
||||
signatureHashCalculator.updateInlineFunctionTransitiveHashes(listOf(stdlibIr))
|
||||
|
||||
val updatedMetadata = KotlinSourceFileMutableMap<DirtyFileMetadata>()
|
||||
val idSignatureToFile = updatedMetadata.getExportedSignaturesAndAddMetadata(linker, stdlibIr, stdlibFile, stdlibDirtyFiles)
|
||||
|
||||
@@ -636,9 +634,6 @@ class CacheUpdater(
|
||||
var lastDirtyFiles: KotlinSourceFileMap<KotlinSourceFileExports> = 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)
|
||||
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
|
||||
+90
-105
@@ -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<IrFunction, ICHash>()
|
||||
private val inlineFunctionCallGraph = hashMapOf<IrFunction, Set<IrFunction>>()
|
||||
private val processingFunctions = hashSetOf<IrFunction>()
|
||||
private val functionTransitiveHashes = hashMapOf<IrFunction, ICHash>()
|
||||
private val fileAnnotationHashes = hashMapOf<IrFile, ICHash>()
|
||||
private val idSignatureSources = hashMapOf<IdSignature, IdSignatureSource>()
|
||||
private val idSignatureHashes = hashMapOf<IdSignature, ICHash>()
|
||||
|
||||
private val allIdSignatureHashes = hashMapOf<IdSignature, ICHash>()
|
||||
private val fileAnnotationHashes = hashMapOf<IrFile, ICHash>()
|
||||
private val inlineFunctionFlatHashes = hashMapOf<IrFunction, ICHash>()
|
||||
|
||||
private val inlineFunctionDepends = hashMapOf<IrFunction, LinkedHashSet<IrFunction>>()
|
||||
|
||||
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<Unit, MutableSet<IrFunction>> {
|
||||
override fun visitElement(element: IrElement, data: MutableSet<IrFunction>) {
|
||||
element.acceptChildren(this, data)
|
||||
}
|
||||
|
||||
override fun visitSimpleFunction(declaration: IrSimpleFunction, data: MutableSet<IrFunction>) {
|
||||
if (declaration in inlineFunctionCallGraph) {
|
||||
return
|
||||
}
|
||||
val newGraph = mutableSetOf<IrFunction>()
|
||||
inlineFunctionCallGraph[declaration] = newGraph
|
||||
declaration.acceptChildren(this, newGraph)
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall, data: MutableSet<IrFunction>) {
|
||||
val callee = expression.symbol.owner
|
||||
if (callee.isInline) {
|
||||
data += callee
|
||||
}
|
||||
expression.acceptChildren(this, data)
|
||||
}
|
||||
|
||||
override fun visitFunctionReference(expression: IrFunctionReference, data: MutableSet<IrFunction>) {
|
||||
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<IrFunction>
|
||||
get() = inlineFunctionDepends.getOrPut(this) {
|
||||
val usedInlineFunctions = linkedSetOf<IrFunction>()
|
||||
|
||||
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<IrModuleFragment>) {
|
||||
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<IdSignature, IdSignatureSource>) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -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/");
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
inline fun funA(flag: Boolean = true): Int {
|
||||
if (flag) {
|
||||
return funB()
|
||||
}
|
||||
return 2
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
inline fun funA(flag: Boolean = false): Int {
|
||||
if (flag) {
|
||||
return funB()
|
||||
}
|
||||
return 2
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
inline fun funB(): Int {
|
||||
val f = ::funA
|
||||
if (false) {
|
||||
return f(false)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
inline fun funB(): Int {
|
||||
val f = ::funA
|
||||
if (false) {
|
||||
return f(false)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
Vendored
+15
@@ -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
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fun box(stepId: Int): String {
|
||||
val x = funA()
|
||||
if (x != stepId) {
|
||||
return "Fail: $x != $stepId"
|
||||
}
|
||||
return "OK"
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
STEP 0:
|
||||
dependencies: lib1
|
||||
added file: m.kt
|
||||
STEP 1..2:
|
||||
dependencies: lib1
|
||||
updated imports: m.kt
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
MODULES: lib1, main
|
||||
|
||||
STEP 0..2:
|
||||
libs: lib1, main
|
||||
dirty js: lib1, main
|
||||
Reference in New Issue
Block a user