[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:
Alexander Korepanov
2022-12-19 11:55:52 +01:00
committed by Space Team
parent bd295df7d0
commit c502cae437
12 changed files with 155 additions and 111 deletions
@@ -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)
@@ -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()
@@ -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
}
}
@@ -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/");
@@ -0,0 +1,6 @@
inline fun funA(flag: Boolean = true): Int {
if (flag) {
return funB()
}
return 2
}
@@ -0,0 +1,6 @@
inline fun funA(flag: Boolean = false): Int {
if (flag) {
return funB()
}
return 2
}
@@ -0,0 +1,7 @@
inline fun funB(): Int {
val f = ::funA
if (false) {
return f(false)
}
return 0
}
@@ -0,0 +1,7 @@
inline fun funB(): Int {
val f = ::funA
if (false) {
return f(false)
}
return 1
}
@@ -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
@@ -0,0 +1,7 @@
fun box(stepId: Int): String {
val x = funA()
if (x != stepId) {
return "Fail: $x != $stepId"
}
return "OK"
}
@@ -0,0 +1,6 @@
STEP 0:
dependencies: lib1
added file: m.kt
STEP 1..2:
dependencies: lib1
updated imports: m.kt
@@ -0,0 +1,5 @@
MODULES: lib1, main
STEP 0..2:
libs: lib1, main
dirty js: lib1, main