This document combines a walkthrough of the RemoveRedundantCallsToStaticInitializersPhase with a critique of its algorithmic approach, discussing potential performance implications and alternatives.
This section provides a walkthrough of the phase definition and its core subdependencies in the Kotlin/Native compiler, based on the source code in LTO.kt.
The phase is defined in LTO.kt.
internal val RemoveRedundantCallsToStaticInitializersPhase = createSimpleNamedCompilerPhase<NativeGenerationState, RedundantCallsInput>( name = "RemoveRedundantCallsToStaticInitializersPhase", preactions = getDefaultIrActions(), postactions = getDefaultIrActions(), op = { generationState, input -> val context = generationState.context val moduleDFG = input.moduleDFG val callGraph = CallGraphBuilder( context, input.irModule, moduleDFG, devirtualizedCallSitesUnfoldFactor = Int.MAX_VALUE, nonDevirtualizedCallSitesUnfoldFactor = Int.MAX_VALUE ).build() val rootSet = DevirtualizationAnalysis.computeRootSet(context, input.irModule, moduleDFG) .mapNotNull { it.irFunction } .toSet() StaticInitializersOptimization.removeRedundantCalls(generationState, input.irModule, moduleDFG, callGraph, rootSet) } )
CallGraphBuilder. It sets both devirtualizedCallSitesUnfoldFactor and nonDevirtualizedCallSitesUnfoldFactor to Int.MAX_VALUE. This ensures that the analysis aggressively unfolds all possible targets of virtual calls, providing a very precise (but potentially large) call graph.DevirtualizationAnalysis.computeRootSet. This includes the program entry point, exported functions, and initializers.StaticInitializersOptimization.removeRedundantCalls to perform the actual analysis and transformation.CallGraphBuilder.build()Located in CallGraphBuilder.kt.
This method builds the call graph by tracing reachability from the root set. Because the unfold factors are set to Int.MAX_VALUE, it will resolve virtual calls to all possible overriding methods based on the type hierarchy, leading to a more complete graph.
DevirtualizationAnalysis.computeRootSet()Located in DevirtualizationAnalysis.kt.
This method identifies all entry points into the module, such as the main function, public or exported functions, static field initializers, and functions leaking through raw references.
StaticInitializersOptimization.removeRedundantCalls()Located in StaticInitializersOptimization.kt.
This is the core of the optimization. It performs a 3-phase interprocedural data flow analysis:
Finally, it modifies the IR to remove redundant calls or move them to call sites where they are actually needed.
During the discussion, we identified that focusing solely on profiling for micro-optimizations might be premature if the underlying algorithm is fundamentally too aggressive or flawed for scale.
The phase uses Int.MAX_VALUE for both devirtualizedCallSitesUnfoldFactor and nonDevirtualizedCallSitesUnfoldFactor.
The analysis runs in 3 phases and iterates to find a fixed point (fixed-point iteration over strongly connected components).
Profiling is still useful to diagnose the algorithm (e.g., to prove that graph construction dominates the time), but the solution to performance issues may lie in changing the algorithm parameters (like the unfold factors) rather than optimizing the code implementation itself.