Skip to content

LLVM 与字节码

源:Kotlin/Native Compiler

Kotlin/Native 使用 LLVM 作为后端。理解 LLVM IR 和编译流程有助于性能优化和问题诊断。

编译流程

完整Pipeline

Kotlin 源码 (.kt)

K2 Frontend
  • 词法分析
  • 语法分析
  • 类型检查

Kotlin IR (中间表示)
  • 平台无关的IR
  • 经过lowering转换

LLVM IR Generator
  • 将Kotlin IR转为LLVM IR

LLVM IR (.ll / .bc)
  • 人类可读: .ll
  • 二进制格式: .bc (bitcode)

LLVM Optimization Passes
  • 模块级优化
  • 链接时优化 (LTO)

LLVM Backend
  • 目标代码生成

机器码 (.o)

链接器 (ld/lld/mold)
  • 链接系统库
  • 解析符号

可执行文件或库

查看中间产物

bash
# 保留临时文件
kotlinc-native -Xtemporary-files-dir=build/tmp main.kt -o app

# 查看生成的文件
ls -lh build/tmp/
# main.kt.ll        # LLVM IR (文本格式)
# main.kt.bc        # LLVM Bitcode (二进制)
# main.kt.o         # 目标文件
# main.kt_api.h     # C API 头文件(如果生成)

LLVM IR

IR 结构示例

llvm
; ModuleID = 'main.kt'
source_filename = "main.kt"
target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128"
target triple = "arm64-apple-macosx11.0.0"

; Kotlin函数的IR表示
define i32 @"kfun:com.example#add(kotlin.Int,kotlin.Int)kotlin.Int"(i32 %a, i32 %b) #0 {
entry:
  %result = add nsw i32 %a, %b
  ret i32 %result
}

; Main 函数
define void @"kfun:main#internal"() #0 {
entry:
  %call = call i32 @"kfun:com.example#add"(i32 10, i32 20)
  call void @"kfun:kotlin.io#println(kotlin.Int)"(i32 %call)
  ret void
}

; 字符串常量
@_str.Hello = private unnamed_addr constant [6 x i8] c"Hello\00"

attributes #0 = { nounwind }

查看IR

bash
# 生成可读IR
kotlinc-native -Xtemporary-files-dir=. main.kt -o app

# 查看LLVM IR
cat main.kt.ll

# 查看特定函数的IR
cat main.kt.ll | grep -A 20 "kfun:main"

# 使用LLVM工具格式化
llvm-dis main.kt.bc -o main_formatted.ll

LLVM 优化

优化Passes分类

Kotlin/Native 执行两个序列的LLVM优化

  1. 模块级优化(Module Passes)
  2. 链接时优化(Link-Time Passes, LTO)
Pass类别优化内容影响
IPO (Interprocedural)跨函数分析优化内联、死代码消除
循环优化循环展开、向量化加速循环密集代码
标量优化常量折叠、强度削减减少指令数
向量化SIMD指令使用并行计算加速
指令组合合并多条指令减少指令数

Debug vs Release优化

bash
# 无优化
kotlinc-native -opt=none main.kt -o debug_app

# 编译时间: 基准 (例如5s)
# 运行性能: 1x
# 二进制大小: 最大
bash
# 完整优化 + LTO
kotlinc-native -opt main.kt -o release_app

# 编译时间: 10-15x 慢于debug
# 运行性能: 2-5x 快于debug
# 二进制大小: 减少30-50%

优化效果对比

kotlin
// 尾递归优化示例
tailrec fun factorial(n: Long, acc: Long = 1): Long {
    return if (n <= 1) acc
    else factorial(n - 1, n * acc)
}
llvm
define i64 @factorial(i64 %n, i64 %acc) {
entry:
  %cmp = icmp sle i64 %n, 1
  br i1 %cmp, label %if.then, label %if.else

if.then:
  ret i64 %acc

if.else:
  %sub = sub i64 %n, 1
  %mul = mul i64 %n, %acc
  %call = call i64 @factorial(i64 %sub, i64 %mul)
  ret i64 %call
}
llvm
; 尾调用优化为循环
define i64 @factorial(i64 %n, i64 %acc) {
entry:
  br label %tailrecurse

tailrecurse:
  %n.tr = phi i64 [ %n, %entry ], [ %sub, %if.else ]
  %acc.tr = phi i64 [ %acc, %entry ], [ %mul, %if.else ]
  %cmp = icmp sle i64 %n.tr, 1
  br i1 %cmp, label %return, label %if.else

if.else:
  %sub = sub i64 %n.tr, 1
  %mul = mul i64 %n.tr, %acc.tr
  br label %tailrecurse

return:
  ret i64 %acc.tr
}

Bitcode

生成Bitcode

bash
# 嵌入Bitcode (iOS App Store需要)
kotlinc-native -Xembed-bitcode main.kt -o app

# 仅标记Bitcode(快速)
kotlinc-native -Xembed-bitcode-marker main.kt

# 检查生成的bitcode
otool -l app.kexe | grep __LLVM

Bitcode用途iOS/macOS

  • App Store优化 - Apple可为不同设备重新编译
  • 未来兼容性 - 支持未来的CPU架构
  • 调试符号 - 保留完整调试信息

性能分析

编译阶段计时

bash
# 启用阶段计时
kotlinc-native -Xtime-phases main.kt -o app

# 输出示例:
# Phase: Frontend = 1200ms
# Phase: IR_GENERATION = 800ms
# Phase: LLVM_OPT = 4500ms
# Phase: LLVM_CODEGEN = 2100ms
# Phase: LINK = 900ms

理解 LLVM IR 和优化流程有助于深入优化性能。日常开发使用 Debug 构建,生产发布使用 -opt -Xlto 获得最佳性能。