本页面介绍 Kotlin 编译器的中端,重点关注介于前端(PSI/FIR)和后端(代码生成)之间的 IR 转换和优化管道。
有关 IR 结构本身的详细信息,请参阅 IR 中间表示。
Kotlin 编译器的中端负责转换和优化 Kotlin 代码的中间表示(IR)。其主要职责包括:
中端在前端生成的 IR 上运行,并输出优化的 IR,然后由后端处理以生成目标代码(JVM 字节码、JavaScript、原生代码或 WebAssembly)。
中端通过一系列称为“lowering”的转换来处理 IR。每个 lowering 专注于代码的特定方面。管道在所有平台(JVM、JS、Native、WASM)上都类似,但具有特定于平台的变体。
来源:[kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/NativeLoweringPhases.kt], [compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt], [compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt]
这些阶段中的每一个都实现为对 IR 树的转换,通常扩展 FileLoweringPass 或 IrElementTransformerVoidWithContext。
来源:[compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/CommonLoweringPhases.kt], [kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/NativeLoweringPhases.kt]
函数内联是中端中最复杂和最重要的转换之一。它用实际的函数体替换标记为 inline 的函数的调用。
来源:[compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/FunctionInlining.kt]
函数内联系统由几个关键类组成:
FunctionInlining:协调内联过程的主要类。它遍历 IR 树并识别需要内联的函数调用。CallInlining:处理特定函数调用的实际内联,包括参数求值、参数替换和 return 语句的转换。InlineFunctionResolver:确定要内联哪些函数以及如何处理它们的接口。来源:[compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/FunctionInlining.kt:38-134], [compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/FunctionInlining.kt:136-748], [compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/InlineFunctionResolver.kt]
内联分两个阶段进行:
在这两个阶段之间,会生成合成访问器并进行验证。这种两阶段方法可确保所有内联函数都能正确内联。
来源:[compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt:154-186], [compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt:200-235], [kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/NativeLoweringPhases.kt:339-366]
函数调用的实际内联由 CallInlining 类处理。该类执行以下步骤:
准备内联函数:复制函数体并处理类型参数。
求值参数:对内联函数的参数进行求值并存储在临时变量中。
创建内联函数块:一个表示内联函数的专用 IR 块。
转换 return 语句:重写 return 语句,使其从内联块返回,而不是从原始函数返回。
处理参数替换:将参数引用替换为相应的参数。
递归内联 lambda:如果函数接收 lambda 参数,也会对这些 lambda 进行内联。
结果是一个包含内联函数体的 IR 块,其中所有参数引用都已替换为参数,并且所有 return 语句都已重写为从内联块返回。
来源:[compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/FunctionInlining.kt:136-748]
一个特殊的转换应用于传递给内联函数的k可调用引用和 lambda。这由 InlineCallableReferenceToLambdaPhase 处理。
此转换:
来源:[compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/InlineCallableReferenceToLambda.kt]
内联函数需要在编译使用它们的其他模块时可用。这需要在 IR 序列化过程中进行特殊处理。
来源:[compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrFileSerializer.kt], [compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/NonLinkingIrInlineFunctionDeserializer.kt]
序列化过程由 IrSerializationSettings 控制,该设置决定要序列化什么。
主要设置包括
publicAbiOnly:是否仅序列化公共 API。bodiesOnlyForInlines:是否仅序列化内联函数的函数体。reuseExistingSignaturesForSymbols:是否为符号重用现有签名。来源:[compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrSerializationSettings.kt]
IrFileSerializer 类即使在仅序列化公共 API 时也会序列化内联函数的函数体。
对于反序列化,NonLinkingIrInlineFunctionDeserializer 会按需反序列化内联函数的函数体。
来源:[compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrFileSerializer.kt], [compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/NonLinkingIrInlineFunctionDeserializer.kt]
每个平台(JVM、JS、Native、WASM)都有其自己的中端管道实现,并针对目标平台进行了特定调整。
Native 平台使用 NativeIrInliner 来处理函数内联。
来源:[kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/NativeIrInliner.kt]
JS 平台实现了自己的内联器和解析器。
来源:[compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt:226-235]
WASM 平台有自己的实现,专注于 WebAssembly 特定的问题。
来源:[compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt:177-186]
Kotlin 编译器的中端是一个用于转换和优化 IR 的复杂系统。函数内联是该过程的核心部分,它能带来性能改进和其他优化。中端以模块化的方式实现,跨平台共享通用组件,并根据需要进行特定于平台的调整。