|
| 1 | +[//]: # (title: Tips for customizing LLVM backend) |
| 2 | +<primary-label ref="advanced"/> |
| 3 | + |
| 4 | +The Kotlin/Native compiler uses [LLVM](https://llvm.org/) to optimize and generate binary executables for different target platforms. |
| 5 | +A noticeable part of the compilation time is also spent in LLVM, and for large apps, this can end up |
| 6 | +taking an unacceptably long time. |
| 7 | + |
| 8 | +You can customize how Kotlin/Native uses LLVM and adjust the list of optimization passes. |
| 9 | + |
| 10 | +## Examine the build log |
| 11 | + |
| 12 | +Let's take a look at the build log to understand how much compilation time is spent on LLVM optimization passes: |
| 13 | + |
| 14 | +1. Run the `linkRelease*` Gradle task with `-Pkotlin.internal.compiler.arguments.log.level=warning` option to make Gradle |
| 15 | + output LLVM profiling details, for example: |
| 16 | + |
| 17 | + ```bash |
| 18 | + ./gradlew linkReleaseExecutableMacosArm64 -Pkotlin.internal.compiler.arguments.log.level=warning |
| 19 | + ``` |
| 20 | + |
| 21 | + While executing, the task prints the necessary compiler arguments, for example: |
| 22 | + |
| 23 | + ```none |
| 24 | + > Task :linkReleaseExecutableMacosArm64 |
| 25 | + Run in-process tool "konanc" |
| 26 | + Entry point method = org.jetbrains.kotlin.cli.utilities.MainKt.daemonMain |
| 27 | + Classpath = [ |
| 28 | + /Users/user/.konan/kotlin-native-prebuilt-macos-aarch64-2.2.0/konan/lib/kotlin-native-compiler-embeddable.jar |
| 29 | + /Users/user/.konan/kotlin-native-prebuilt-macos-aarch64-2.2.0/konan/lib/trove4j.jar |
| 30 | + ] |
| 31 | + Arguments = [ |
| 32 | + -Xinclude=... |
| 33 | + -library |
| 34 | + /Users/user/.konan/kotlin-native-prebuilt-macos-aarch64-2.2.0/klib/common/stdlib |
| 35 | + -no-endorsed-libs |
| 36 | + -nostdlib |
| 37 | + ... |
| 38 | + ] |
| 39 | + ``` |
| 40 | + |
| 41 | +2. Run the command line compiler with the provided arguments plus the `-Xprofile-phases` argument, for example: |
| 42 | + |
| 43 | + ```bash |
| 44 | + /Users/user/.konan/kotlin-native-prebuilt-macos-aarch64-2.2.0/bin/kotlinc-native \ |
| 45 | + -Xinclude=... \ |
| 46 | + -library /Users/user/.konan/kotlin-native-prebuilt-macos-aarch64-2.2.0/klib/common/stdlib \ |
| 47 | + ... \ |
| 48 | + -Xprofile-phases |
| 49 | + ``` |
| 50 | + |
| 51 | +3. Examine the generated output in the build log. The log can contain tens of thousands of lines; sections with LLVM |
| 52 | + profiling are at the end. |
| 53 | + |
| 54 | +Here is an excerpt from such a run of a simple Kotlin/Native program: |
| 55 | + |
| 56 | +```none |
| 57 | +Frontend: 275 msec |
| 58 | +PsiToIr: 1186 msec |
| 59 | +... |
| 60 | +... 30k lines |
| 61 | +... |
| 62 | +LinkBitcodeDependencies: 476 msec |
| 63 | +StackProtectorPhase: 0 msec |
| 64 | +MandatoryBitcodeLLVMPostprocessingPhase: 2 msec |
| 65 | +===-------------------------------------------------------------------------=== |
| 66 | + Pass execution timing report |
| 67 | +===-------------------------------------------------------------------------=== |
| 68 | + Total Execution Time: 6.7726 seconds (6.7192 wall clock) |
| 69 | +
|
| 70 | + ---User Time--- --System Time-- --User+System-- ---Wall Time--- --- Name --- |
| 71 | + 0.9778 ( 22.4%) 0.5043 ( 21.0%) 1.4821 ( 21.9%) 1.4628 ( 21.8%) InstCombinePass |
| 72 | + 0.3827 ( 8.8%) 0.2497 ( 10.4%) 0.6323 ( 9.3%) 0.6283 ( 9.4%) InlinerPass |
| 73 | + 0.2815 ( 6.4%) 0.1792 ( 7.5%) 0.4608 ( 6.8%) 0.4555 ( 6.8%) SimplifyCFGPass |
| 74 | +... |
| 75 | + 0.6444 (100.0%) 0.5474 (100.0%) 1.1917 (100.0%) 1.1870 (100.0%) Total |
| 76 | +
|
| 77 | +ModuleBitcodeOptimization: 8118 msec |
| 78 | +... |
| 79 | +LTOBitcodeOptimization: 1399 msec |
| 80 | +... |
| 81 | +``` |
| 82 | + |
| 83 | +The Kotlin/Native compiler runs two separate sequences of LLVM optimizations: the module passes and the link-time |
| 84 | +passes. For a typical compilation, the two pipelines are run back to back, and the only real distinction is in which |
| 85 | +LLVM optimization passes they run. |
| 86 | + |
| 87 | +In the log above, the two LLVM optimizations are `ModuleBitcodeOptimization` and `LTOBitcodeOptimization`. |
| 88 | +The formatted tables are the optimizations' output with timing for each pass. |
| 89 | + |
| 90 | +## Customize LLVM optimization passes |
| 91 | + |
| 92 | +If one of the passes above seems unreasonably long, you can skip it. However, this might hurt runtime performance, |
| 93 | +so you should check for changes in the benchmarks' performance afterward. |
| 94 | + |
| 95 | +Currently, there is no direct way to [disable a given pass](https://youtrack.jetbrains.com/issue/KT-69212). |
| 96 | +However, you can provide a new list of passes to run by using the following compiler options: |
| 97 | + |
| 98 | +| **Option** | **Default value for release binary** | |
| 99 | +|------------------------|--------------------------------------| |
| 100 | +| `-Xllvm-module-passes` | `"default<O3>"` | |
| 101 | +| `-Xllvm-lto-passes` | `"internalize,globaldce,lto<O3>"` | |
| 102 | + |
| 103 | +The default values are unfolded to a long list of actual passes, from which you need to exclude the undesired ones. |
| 104 | + |
| 105 | +To get the list of actual passes, run the [`opt`](https://llvm.org/docs/CommandGuide/opt.html) tool, which is |
| 106 | +automatically downloaded with the LLVM distribution to the |
| 107 | +`~/.konan/dependencies/llvm-{VERSION}-{ARCH}-{OS}-dev-{BUILD}/bin` directory. |
| 108 | + |
| 109 | +For example, to get the list of the link-time passes, run: |
| 110 | + |
| 111 | +```bash |
| 112 | +opt -print-pipeline-passes -passes="internalize,globaldce,lto<O3>" < /dev/null |
| 113 | +``` |
| 114 | + |
| 115 | +This outputs a warning and a long list of passes, which depends on the LLVM version. |
| 116 | + |
| 117 | +There are two differences between the list of passes from the `opt` tool and the passes that Kotlin/Native |
| 118 | +compiler actually runs: |
| 119 | + |
| 120 | +* Since `opt` is a debug tool, it includes one or more `verify` passes, which are not normally run. |
| 121 | +* Kotlin/Native disables the `devirt` passes since the Kotlin compiler already does them itself. |
| 122 | + |
| 123 | +After disabling any passes, always rerun performance tests to check if the runtime performance degradation is acceptable. |
0 commit comments