菜单

编译器中端

相关源文件

本页面介绍 Kotlin 编译器的中端,重点关注介于前端(PSI/FIR)和后端(代码生成)之间的 IR 转换和优化管道。

有关 IR 结构本身的详细信息,请参阅 IR 中间表示

目的与范围

Kotlin 编译器的中端负责转换和优化 Kotlin 代码的中间表示(IR)。其主要职责包括:

  1. 应用一系列转换(lowering)来简化高级 Kotlin 结构
  2. 通过函数内联和常量计算等操作优化代码
  3. 在不同阶段验证 IR 以确保正确性
  4. 为序列化或代码生成准备 IR

中端在前端生成的 IR 上运行,并输出优化的 IR,然后由后端处理以生成目标代码(JVM 字节码、JavaScript、原生代码或 WebAssembly)。

IR 转换管道

中端通过一系列称为“lowering”的转换来处理 IR。每个 lowering 专注于代码的特定方面。管道在所有平台(JVM、JS、Native、WASM)上都类似,但具有特定于平台的变体。

通用 lowering 管道

来源:[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]

关键 lowering 阶段

  1. lateinitPhase:转换 Kotlin 的 lateinit 属性。
  2. sharedVariablesLoweringPhase:处理 lambda 中捕获的变量。
  3. localClassesInInlineLambdasPhase:从内联 lambda 中提取局部类。
  4. arrayConstructorPhase:转换数组构造器。
  5. inlineOnlyPrivateFunctionsPhase:内联私有的内联函数。
  6. outerThisSpecialAccessorInInlineFunctionsPhase:为内联函数中的外部 this 引用创建访问器。
  7. syntheticAccessorGenerationPhase:为私有成员创建访问器。
  8. inlineAllFunctionsPhase:内联所有剩余的内联函数。
  9. constEvaluationPhase:在编译时计算常量表达式。

这些阶段中的每一个都实现为对 IR 树的转换,通常扩展 FileLoweringPassIrElementTransformerVoidWithContext

来源:[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]

主要组件

函数内联系统由几个关键类组成:

  1. FunctionInlining:协调内联过程的主要类。它遍历 IR 树并识别需要内联的函数调用。
  1. CallInlining:处理特定函数调用的实际内联,包括参数求值、参数替换和 return 语句的转换。
  1. 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]

两阶段内联

内联分两个阶段进行:

  1. 私有函数阶段:仅内联私有的内联函数。这是必要的,因为在生成合成访问器等其他转换之前,私有函数可能在所有调用站点都不可访问。
  1. 所有函数阶段:在生成合成访问器后,内联所有剩余的内联函数。

在这两个阶段之间,会生成合成访问器并进行验证。这种两阶段方法可确保所有内联函数都能正确内联。

来源:[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 类处理。该类执行以下步骤:

  1. 准备内联函数:复制函数体并处理类型参数。

  2. 求值参数:对内联函数的参数进行求值并存储在临时变量中。

  1. 创建内联函数块:一个表示内联函数的专用 IR 块。

  2. 转换 return 语句:重写 return 语句,使其从内联块返回,而不是从原始函数返回。

  3. 处理参数替换:将参数引用替换为相应的参数。

  4. 递归内联 lambda:如果函数接收 lambda 参数,也会对这些 lambda 进行内联。

结果是一个包含内联函数体的 IR 块,其中所有参数引用都已替换为参数,并且所有 return 语句都已重写为从内联块返回。

来源:[compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/FunctionInlining.kt:136-748]

处理可调用引用和 lambda

一个特殊的转换应用于传递给内联函数的k可调用引用和 lambda。这由 InlineCallableReferenceToLambdaPhase 处理。

此转换:

  1. 为被引用的函数创建一个包装器函数
  2. 设置参数转发
  3. 创建一个引用包装器函数的 lambda 块

来源:[compiler/ir/ir.inline/src/org/jetbrains/kotlin/ir/inline/InlineCallableReferenceToLambda.kt]

用于内联函数的 IR 序列化

内联函数需要在编译使用它们的其他模块时可用。这需要在 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]

JavaScript

JS 平台实现了自己的内联器和解析器。

来源:[compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt:226-235]

WebAssembly

WASM 平台有自己的实现,专注于 WebAssembly 特定的问题。

来源:[compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt:177-186]

结论

Kotlin 编译器的中端是一个用于转换和优化 IR 的复杂系统。函数内联是该过程的核心部分,它能带来性能改进和其他优化。中端以模块化的方式实现,跨平台共享通用组件,并根据需要进行特定于平台的调整。