Skip to content

Commit 075f911

Browse files
projediSpace Team
authored andcommitted
[K/N] Implement pass profiling in LLVMKotlinRunPasses
^KT-79381
1 parent b4cc727 commit 075f911

File tree

18 files changed

+351
-34
lines changed

18 files changed

+351
-34
lines changed

kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/NativeGenerationState.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.backend.konan.serialization.SerializedClassFields
2121
import org.jetbrains.kotlin.backend.konan.serialization.SerializedEagerInitializedFile
2222
import org.jetbrains.kotlin.backend.konan.serialization.SerializedInlineFunctionReference
2323
import org.jetbrains.kotlin.ir.declarations.*
24+
import org.jetbrains.kotlin.util.PerformanceManager
2425

2526
internal class FileLowerState {
2627
private var functionReferenceCount = 0
@@ -59,6 +60,7 @@ internal class NativeGenerationState(
5960
val llvmModuleSpecification: LlvmModuleSpecification,
6061
val outputFiles: OutputFiles,
6162
val llvmModuleName: String,
63+
override val performanceManager: PerformanceManager?,
6264
) : BasicPhaseContext(config), BackendContextHolder, LlvmIrHolder, BitcodePostProcessingContext {
6365
val outputFile = outputFiles.mainFileName
6466

kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/OptimizationPipeline.kt

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.backend.konan.driver.PhaseContext
1313
import org.jetbrains.kotlin.backend.konan.llvm.*
1414
import org.jetbrains.kotlin.config.nativeBinaryOptions.StackProtectorMode
1515
import org.jetbrains.kotlin.konan.target.*
16+
import org.jetbrains.kotlin.util.PerformanceManager
1617
import java.io.Closeable
1718

1819
enum class LlvmOptimizationLevel(val value: Int) {
@@ -182,7 +183,8 @@ internal fun createLTOFinalPipelineConfig(
182183

183184
abstract class LlvmOptimizationPipeline(
184185
private val config: LlvmPipelineConfig,
185-
private val logger: LoggingContext? = null
186+
private val performanceManager: PerformanceManager?,
187+
private val logger: LoggingContext? = null,
186188
) : Closeable {
187189
open fun executeCustomPreprocessing(config: LlvmPipelineConfig, module: LLVMModuleRef) {}
188190

@@ -220,7 +222,7 @@ abstract class LlvmOptimizationPipeline(
220222
val passDescription = passes.joinToString(",")
221223
logger?.log {
222224
"""
223-
Running ${pipelineName} with the following parameters:
225+
Running $pipelineName with the following parameters:
224226
target_triple: ${config.targetTriple}
225227
cpu_model: ${config.cpuModel}
226228
cpu_features: ${config.cpuFeatures}
@@ -231,15 +233,21 @@ abstract class LlvmOptimizationPipeline(
231233
""".trimIndent()
232234
}
233235
if (passDescription.isEmpty()) return
234-
val errorCode = LLVMKotlinRunPasses(
235-
llvmModule,
236-
passDescription,
237-
targetMachine,
238-
InlinerThreshold = config.inlineThreshold ?: -1,
239-
)
236+
val (errorCode, profile) = withLLVMPassesProfile(performanceManager != null, pipelineName) {
237+
LLVMKotlinRunPasses(
238+
llvmModule,
239+
passDescription,
240+
targetMachine,
241+
InlinerThreshold = config.inlineThreshold ?: -1,
242+
Profile = it,
243+
)
244+
}
240245
require(errorCode == null) {
241246
LLVMGetErrorMessage(errorCode)!!.toKString()
242247
}
248+
profile?.let {
249+
performanceManager?.addLlvmPassesProfile(it)
250+
}
243251
if (config.timePasses) {
244252
LLVMPrintAllTimersToStdOut()
245253
LLVMClearAllTimers()
@@ -272,9 +280,9 @@ abstract class LlvmOptimizationPipeline(
272280
}
273281
}
274282

275-
class MandatoryOptimizationPipeline(config: LlvmPipelineConfig, logger: LoggingContext? = null) :
276-
LlvmOptimizationPipeline(config, logger) {
277-
override val pipelineName = "New PM Mandatory llvm optimizations"
283+
class MandatoryOptimizationPipeline(config: LlvmPipelineConfig, performanceManager: PerformanceManager?, logger: LoggingContext? = null) :
284+
LlvmOptimizationPipeline(config, performanceManager, logger) {
285+
override val pipelineName = "llvm-mandatory"
278286
override val passes = buildList {
279287
if (config.objCPasses) {
280288
// Lower ObjC ARC intrinsics (e.g. `@llvm.objc.clang.arc.use(...)`).
@@ -293,15 +301,15 @@ class MandatoryOptimizationPipeline(config: LlvmPipelineConfig, logger: LoggingC
293301
}
294302
}
295303

296-
class ModuleOptimizationPipeline(config: LlvmPipelineConfig, logger: LoggingContext? = null) :
297-
LlvmOptimizationPipeline(config, logger) {
298-
override val pipelineName = "New PM Module LLVM optimizations"
304+
class ModuleOptimizationPipeline(config: LlvmPipelineConfig, performanceManager: PerformanceManager?, logger: LoggingContext? = null) :
305+
LlvmOptimizationPipeline(config, performanceManager, logger) {
306+
override val pipelineName = "llvm-default"
299307
override val passes = listOf(config.modulePasses ?: "default<$optimizationFlag>")
300308
}
301309

302-
class LTOOptimizationPipeline(config: LlvmPipelineConfig, logger: LoggingContext? = null) :
303-
LlvmOptimizationPipeline(config, logger) {
304-
override val pipelineName = "New PM LTO LLVM optimizations"
310+
class LTOOptimizationPipeline(config: LlvmPipelineConfig, performanceManager: PerformanceManager?, logger: LoggingContext? = null) :
311+
LlvmOptimizationPipeline(config, performanceManager, logger) {
312+
override val pipelineName = "llvm-lto"
305313
override val passes =
306314
if (config.ltoPasses != null) listOf(config.ltoPasses)
307315
else buildList {
@@ -318,9 +326,9 @@ class LTOOptimizationPipeline(config: LlvmPipelineConfig, logger: LoggingContext
318326
}
319327
}
320328

321-
class ThreadSanitizerPipeline(config: LlvmPipelineConfig, logger: LoggingContext? = null) :
322-
LlvmOptimizationPipeline(config, logger) {
323-
override val pipelineName = "New PM thread sanitizer"
329+
class ThreadSanitizerPipeline(config: LlvmPipelineConfig, performanceManager: PerformanceManager?, logger: LoggingContext? = null) :
330+
LlvmOptimizationPipeline(config, performanceManager, logger) {
331+
override val pipelineName = "llvm-tsan"
324332
override val passes = listOf("tsan-module,function(tsan)")
325333

326334
override fun executeCustomPreprocessing(config: LlvmPipelineConfig, module: LLVMModuleRef) {

kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/Machinery.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ import org.jetbrains.kotlin.config.phaser.NamedCompilerPhase
1515
import org.jetbrains.kotlin.backend.konan.ConfigChecks
1616
import org.jetbrains.kotlin.backend.konan.KonanConfig
1717
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
18+
import org.jetbrains.kotlin.cli.common.perfManager
1819
import org.jetbrains.kotlin.config.CommonConfigurationKeys
20+
import org.jetbrains.kotlin.util.PerformanceManager
21+
22+
internal interface PerformanceManagerContext {
23+
val performanceManager: PerformanceManager?
24+
}
1925

2026
/**
2127
* Context is a set of resources that is shared between different phases. PhaseContext is a "minimal context",
@@ -35,7 +41,7 @@ import org.jetbrains.kotlin.config.CommonConfigurationKeys
3541
* * On the other hand, middle- and bitcode phases are hard to decouple due to the way the code was written many years ago.
3642
* It will take some time to rewrite it properly.
3743
*/
38-
internal interface PhaseContext : LoggingContext, ConfigChecks, ErrorReportingContext, DisposableContext
44+
internal interface PhaseContext : LoggingContext, ConfigChecks, ErrorReportingContext, DisposableContext, PerformanceManagerContext
3945

4046
internal open class BasicPhaseContext(
4147
override val config: KonanConfig,
@@ -48,6 +54,9 @@ internal open class BasicPhaseContext(
4854
override fun dispose() {
4955

5056
}
57+
58+
override val performanceManager: PerformanceManager?
59+
get() = config.configuration.perfManager
5160
}
5261

5362
internal fun PhaseEngine.Companion.startTopLevel(config: KonanConfig, body: (PhaseEngine<PhaseContext>) -> Unit) {

kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Bitcode.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.backend.konan.llvm.verifyModule
2929
import org.jetbrains.kotlin.backend.konan.optimizations.RemoveRedundantSafepointsPass
3030
import org.jetbrains.kotlin.backend.konan.optimizations.removeMultipleThreadDataLoads
3131
import org.jetbrains.kotlin.config.nativeBinaryOptions.SanitizerKind
32+
import org.jetbrains.kotlin.util.PerformanceManager
3233
import java.io.File
3334
import kotlin.sequences.forEach
3435

@@ -68,15 +69,16 @@ internal val RewriteExternalCallsCheckerGlobals = createSimpleNamedCompilerPhase
6869

6970
internal class OptimizationState(
7071
konanConfig: KonanConfig,
71-
val llvmConfig: LlvmPipelineConfig
72+
val llvmConfig: LlvmPipelineConfig,
73+
override val performanceManager: PerformanceManager?,
7274
) : BasicPhaseContext(konanConfig)
7375

74-
internal fun optimizationPipelinePass(name: String, pipeline: (LlvmPipelineConfig, LoggingContext) -> LlvmOptimizationPipeline) =
76+
internal fun optimizationPipelinePass(name: String, pipeline: (LlvmPipelineConfig, PerformanceManager?, LoggingContext) -> LlvmOptimizationPipeline) =
7577
createSimpleNamedCompilerPhase<OptimizationState, LLVMModuleRef>(
7678
name = name,
7779
postactions = getDefaultLlvmModuleActions(),
7880
) { context, module ->
79-
pipeline(context.llvmConfig, context).use {
81+
pipeline(context.llvmConfig, context.performanceManager, context).use {
8082
it.execute(module)
8183
}
8284
}
@@ -165,7 +167,7 @@ internal fun <T : BitcodePostProcessingContext> PhaseEngine<T>.runBitcodePostPro
165167
closedWorld = context.config.isFinalBinary,
166168
timePasses = context.config.phaseConfig.needProfiling,
167169
)
168-
useContext(OptimizationState(context.config, optimizationConfig)) {
170+
useContext(OptimizationState(context.config, optimizationConfig, context.performanceManager)) {
169171
val module = this@runBitcodePostProcessing.context.llvmModule
170172
it.runPhase(StackProtectorPhase, module)
171173
it.runPhase(MandatoryBitcodeLLVMPostprocessingPhase, module)

kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/TopLevelPhases.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ internal fun <C : PhaseContext> PhaseEngine<C>.runBackend(backendContext: Contex
9595
val outputFiles = OutputFiles(outputPath, config.target, config.produce)
9696
val generationState = NativeGenerationState(context.config, backendContext,
9797
fragment.cacheDeserializationStrategy, fragment.dependenciesTracker, fragment.llvmModuleSpecification, outputFiles,
98-
llvmModuleName = "out" // TODO: Currently, all llvm modules are named as "out" which might lead to collisions.
98+
llvmModuleName = "out", // TODO: Currently, all llvm modules are named as "out" which might lead to collisions.
99+
fragment.performanceManager
99100
)
100101
try {
101102
val module = fragment.irModule
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
4+
*/
5+
6+
package org.jetbrains.kotlin.backend.konan.llvm
7+
8+
import kotlinx.cinterop.*
9+
import llvm.LLVMKotlinDisposePassesProfile
10+
import llvm.LLVMKotlinPassesProfileAsString
11+
import llvm.LLVMKotlinPassesProfileRef
12+
import llvm.LLVMKotlinPassesProfileRefVar
13+
import org.jetbrains.kotlin.util.DynamicStats
14+
import org.jetbrains.kotlin.util.PerformanceManager
15+
import org.jetbrains.kotlin.util.PhaseType
16+
import org.jetbrains.kotlin.util.PlatformType
17+
import org.jetbrains.kotlin.util.Time
18+
import org.jetbrains.kotlin.util.UnitStats
19+
20+
internal data class LlvmPassProfile(val pass: String, val duration: Time)
21+
22+
private fun LLVMKotlinPassesProfileRef.parse(pipelineName: String) = LLVMKotlinPassesProfileAsString(this)!!
23+
.toKString()
24+
.lineSequence()
25+
.filter { it.isNotEmpty() }
26+
.map {
27+
val line = it.split("\t")
28+
check(line.size == 2) {
29+
"Expected line `$it` to have exactly 2 tab-separated columns"
30+
}
31+
val pass = "$pipelineName.${line[0]}"
32+
val nanos = line[1].toLong()
33+
// Computing user and system times for each llvm phase gives a big overhead.
34+
val userNanos = 0L
35+
val cpuNanos = 0L
36+
LlvmPassProfile(pass, Time(nanos, userNanos, cpuNanos))
37+
}
38+
39+
@JvmInline
40+
internal value class LlvmPassesProfile private constructor(val entries: List<LlvmPassProfile>) {
41+
constructor(profile: LLVMKotlinPassesProfileRef, pipelineName: String) : this(profile.parse(pipelineName).toList())
42+
}
43+
44+
/**
45+
* Run [block] that expects [LLVMKotlinPassesProfileRef] as output variable and return its result augmented by the parsed profile.
46+
*
47+
* @param enabled when false, no profile will be computed
48+
* @param pipelineName will be prepended to each phase name
49+
*/
50+
internal inline fun <T> withLLVMPassesProfile(
51+
enabled: Boolean,
52+
pipelineName: String,
53+
block: (CValuesRef<LLVMKotlinPassesProfileRefVar>?) -> T,
54+
): Pair<T, LlvmPassesProfile?> {
55+
if (!enabled) {
56+
return block(null) to null
57+
}
58+
return memScoped {
59+
val profileRef = alloc<LLVMKotlinPassesProfileRefVar>().also {
60+
it.value = null
61+
}
62+
val result = block(profileRef.ptr)
63+
val profile = profileRef.value
64+
defer {
65+
LLVMKotlinDisposePassesProfile(profile)
66+
}
67+
result to LlvmPassesProfile(profile!!, pipelineName)
68+
}
69+
}
70+
71+
internal fun PerformanceManager.addLlvmPassesProfile(profile: LlvmPassesProfile) {
72+
addOtherUnitStats(UnitStats(
73+
name = "$presentableName (llvm)",
74+
outputKind = null,
75+
platform = PlatformType.Native,
76+
filesCount = 0,
77+
linesCount = 0,
78+
initStats = null,
79+
analysisStats = null,
80+
translationToIrStats = null,
81+
irPreLoweringStats = null,
82+
irSerializationStats = null,
83+
klibWritingStats = null,
84+
irLoweringStats = null,
85+
backendStats = null,
86+
dynamicStats = profile.entries.map { (pass, duration) ->
87+
DynamicStats(PhaseType.Backend, pass, duration)
88+
}
89+
))
90+
}

kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/runtime/RuntimeLinkage.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ internal fun linkRuntimeModules(generationState: NativeGenerationState, runtimeN
4040
}
4141
val config = createLTOPipelineConfigForRuntime(generationState)
4242

43-
MandatoryOptimizationPipeline(config, generationState).use {
43+
MandatoryOptimizationPipeline(config, generationState.performanceManager, generationState).use {
4444
it.execute(runtimeModule)
4545
}
46-
ModuleOptimizationPipeline(config, generationState).use {
46+
ModuleOptimizationPipeline(config, generationState.performanceManager, generationState).use {
4747
it.execute(runtimeModule)
4848
}
4949

kotlin-native/libllvmext/src/main/cpp/CAPIExtensions.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include <llvm/Support/Timer.h>
1010
#include <llvm/Transforms/Utils/Cloning.h>
1111

12+
#include "PassesProfileHandler.h"
13+
1214
using namespace llvm;
1315

1416
namespace {
@@ -57,8 +59,10 @@ extern "C" LLVMErrorRef LLVMKotlinRunPasses(
5759
LLVMModuleRef M,
5860
const char *Passes,
5961
LLVMTargetMachineRef TM,
60-
int InlinerThreshold
62+
int InlinerThreshold,
63+
LLVMKotlinPassesProfileRef* Profile
6164
) {
65+
// Implementation is taken from https://github.com/Kotlin/llvm-project/blob/0fa53d5183ec3c0654631d719dd6dfa7a270ca98/llvm/lib/Passes/PassBuilderBindings.cpp#L47
6266
TargetMachine *Machine = unwrap(TM);
6367
Module *Mod = unwrap(M);
6468

@@ -81,10 +85,20 @@ extern "C" LLVMErrorRef LLVMKotlinRunPasses(
8185
StandardInstrumentations SI(Mod->getContext(), false, false);
8286
SI.registerCallbacks(PIC, &MAM);
8387

88+
PassesProfileHandler PPH(Profile != nullptr);
89+
// Putting last to make this the last callback for before* events;
90+
// the handler will additionally make sure its after* events are handled before anything else.
91+
// This makes it so the profile tracks phases only, ignoring other callbacks.
92+
PPH.registerCallbacks(PIC);
93+
8494
ModulePassManager MPM;
8595
if (auto Err = PB.parsePassPipeline(MPM, Passes))
8696
return wrap(std::move(Err));
8797
MPM.run(*Mod, MAM);
8898

99+
if (Profile != nullptr) {
100+
*Profile = wrap(new PassesProfile(PPH.serialize()));
101+
}
102+
89103
return LLVMErrorSuccess;
90104
}

0 commit comments

Comments
 (0)