菜单

IR 序列化

相关源文件

本文档详细介绍了中间表示(IR)如何序列化为二进制格式,以便在多模块编译、增量编译和创建 Kotlin 库(KLIB)产物中使用。有关 IR 结构本身的信息,请参阅 IR(中间表示)。有关 IR 中的函数内联,请参阅 函数内联

IR 序列化概述

IR 序列化是 Kotlin 编译器的一个关键组成部分,它支持

  1. 多模块编译:允许独立编译具有依赖关系的模块
  2. 增量编译:支持缓存和重用之前编译过的代码
  3. 库分发:将序列化的 IR 作为 KLIB 格式的一部分进行生成
  4. 跨平台兼容性:在不同编译器后端之间标准化 IR 交换

序列化系统使用 Protocol Buffers 将内存中的 IR 结构转换为二进制格式,保留了之后反序列化所需的所有必要信息。

来源:compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/serializeModuleIntoKlib.kt compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrModuleSerializer.kt

序列化架构

IR 序列化系统围绕几个关键组件构建,这些组件协同工作以序列化 IR 结构的各个方面

关键组件

  1. IrModuleSerializer:顶层组件,负责序列化整个 IR 模块
  2. IrFileSerializer:处理单个 IR 文件的序列化
  3. DeclarationTable:将 IR 声明映射到其唯一的 ID 签名
  4. IrSerializationSettings:配置序列化过程的各个方面
  5. Protocol Buffer 消息:定义序列化 IR 实体的二进制格式

来源:compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrFileSerializer.kt114-308 compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/DeclarationTable.kt21-86 compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrSerializationSettings.kt19-46 compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/lower/serialization/ir/JsIrModuleSerializer.kt15-26 compiler/ir/serialization.native/src/org/jetbrains/kotlin/backend/konan/serialization/KonanIrModuleSerializer.kt15-34

序列化设置和配置

序列化过程由各种设置控制,这些设置会影响序列化的内容以及序列化方式

这些设置支持多种重要用例

设置目的
publicAbiOnly如果为 true,则创建“头 KLIB”,其中仅包含公共 API 声明
bodiesOnlyForInlines在 JVM 后端中使用,仅为内联函数序列化函数体
abiCompatibilityLevel控制 KLIB 消费者的 ABI 版本兼容性
reuseExistingSignaturesForSymbols针对 IR 内联器集成的优化

来源:compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrSerializationSettings.kt36-46 compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/lower/serialization/ir/JsIrModuleSerializer.kt15-26 compiler/ir/serialization.native/src/org/jetbrains/kotlin/backend/konan/serialization/KonanIrModuleSerializer.kt15-34

ID 签名

ID 签名是 IR 序列化的基本组成部分,为声明提供唯一的标识符,可用于在模块边界之间引用它们。这些签名对于多模块编译期间的链接至关重要。

不同类型的签名用于不同类型的声明

签名类型用于示例
CommonSignature公共声明顶层函数、公共类成员
FileLocalSignature文件私有声明私有顶层函数
AccessorSignature属性访问器getter 和 setter
ScopeLocalDeclaration局部变量函数体内的变量
CompositeSignature嵌套声明内部类、类型参数
FileSignature文件级实体包片段

签名系统旨在处理复杂的场景,例如

  • 不同包中具有相同名称的声明
  • 不同文件中具有相同名称的私有声明
  • 类和函数中的嵌套声明
  • 需要引用其属性的属性访问器

来源:compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IdSignature.kt222-1116 compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/signature/IdSignatureComputers.kt22-265 compiler/ir/serialization.common/src/KotlinIr.proto33-83

序列化过程

序列化过程将内存中的 IR 数据结构转换为二进制 Protocol Buffer 格式

序列化过程的关键方面

  1. 声明序列化:

    • 每个声明(类、函数、属性等)都被序列化为 ProtoDeclaration
    • 对其他声明的引用被序列化为符号引用,使用 ID 签名
  2. 类型序列化:

    • 类型被序列化为 ProtoType 消息
    • 类型引用使用 ID 签名来引用分类器声明
    • 执行类型去重以减小输出大小
  3. 函数体序列化:

    • 函数体被序列化为表达式树
    • 函数体内的局部声明被特殊处理
    • 对于头 KLIB,除了内联函数之外,可能会跳过函数体
  4. 文件结构:

    • 保留文件元数据,包括文件路径和行信息
    • 跟踪显式导出,用于编译器所需的声明

来源:compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrFileSerializer.kt207-1134 compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/serializeModuleIntoKlib.kt103-202

反序列化过程

反序列化过程将 IR 数据结构从其二进制格式重建为 IR 数据结构

反序列化过程的关键方面

  1. 两阶段反序列化:

    • 第一阶段:为所有声明声明符号
    • 第二阶段:填充函数体并解析引用
  2. 符号解析:

    • 使用 ID 签名来查找或创建相应的符号
    • 外部引用最初可能被解析为未绑定的符号
  3. 函数体反序列化:

    • 函数体在需要时被惰性反序列化
    • 局部声明在函数体反序列化期间创建
  4. 去重:

    • 类型和其他元素可能在反序列化期间被去重
    • 这确保了类型标识得以保留
  5. 部分链接支持:

    • 在某些情况下,缺失的符号可能表示为存根
    • 这使得即使在依赖项不完整的情况下也能继续编译

来源:compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrFileDeserializer.kt32-275 compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrDeclarationDeserializer.kt64-391 compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrBodyDeserializer.kt85-321

与 KLIB 格式的集成

IR 序列化是 KLIB(Kotlin 库)格式的关键组成部分

创建 KLIB 包括以下步骤

  1. 元数据序列化:序列化关于声明的高层元数据
  2. IR 序列化:序列化完整的 IR 以供代码生成
  3. 清单生成:创建包含库信息的清单
  4. 打包:创建目录结构或压缩的存档

serializeModuleIntoKlib 函数协调此过程

特殊的 KLIB 变体包括

  1. 头 KLIBs:

    • 使用 publicAbiOnly = true 创建
    • 仅包含公共 API,不包含实现细节
    • 用于 IDE 和编译,无需访问实现
  2. 特定平台的 KLIBs:

    • JS KLIBs 包含特定于 JavaScript 的元数据
    • Native KLIBs 包含原生互操作信息
    • JVM KLIBs 通常仅用于内联函数

来源: compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/serializeModuleIntoKlib.kt103-202 compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt80-112 kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/WriteKlib.kt29-92

平台特定序列化

每个 Kotlin 后端都有专门的序列化组件来处理平台特定的问题

JVM 特定序列化

  • 支持 Android 的仅内联模式
  • 为增量编译提供基于类的序列化
  • 对 JVM 特定的元数据进行特殊处理

JavaScript 特定序列化

  • 处理 JavaScript 模块格式问题
  • 跟踪导出的声明以实现 JavaScript 互操作
  • 在序列化的输出中包含 JS 特定的元数据

Native 特定序列化

  • 过滤掉 C 结构体和枚举 IR(在编译期间重新生成)
  • 支持 Native 特定的注解和导出
  • 处理与原生库的互操作

来源: compiler/ir/serialization.jvm/src/org/jetbrains/kotlin/backend/jvm/serialization/JvmIrSerializerSession.kt20-128 compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/lower/serialization/ir/JsIrModuleSerializer.kt15-26 compiler/ir/serialization.native/src/org/jetbrains/kotlin/backend/konan/serialization/KonanIrModuleSerializer.kt15-34

在增量编译中使用

IR 序列化在增量编译中起着关键作用

IR 增量编译的关键方面

  1. 文件粒度:系统在文件级别跟踪更改
  2. 序列化 IR 缓存:未更改文件的 IR 被缓存和重用
  3. 依赖跟踪:影响 API 的更改会触发依赖模块的重新编译
  4. 指纹识别:序列化的 IR 用于计算指纹以检测更改
  5. 构建系统集成:向 Gradle 等构建系统提供信息

JavaScript 后端尤其包含详细的增量编译支持

这些指纹存储在 KLIB manifest 中,用于检测跨编译会话的更改。

来源: compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt389-516 js/js.tests/test/org/jetbrains/kotlin/benchmarks/GenerateIrRuntime.kt189-231

总结

IR 序列化是 Kotlin 编译器基础设施的基本组成部分,它支持几项关键功能

  1. 多模块编译:允许模块单独编译并链接在一起
  2. 增量编译:实现仅对修改后的代码进行有效重新编译
  3. 库分发:提供分发已编译 Kotlin 代码的标准格式
  4. 跨平台一致性:确保在不同 Kotlin 平台上的行为一致

序列化系统由多个层组成,处理不同的方面

  • 管理声明和签名之间映射的声明表
  • 为声明提供唯一标识符的 ID 签名
  • 用于高效二进制编码的 Protocol Buffer 序列化
  • 针对 JVM、JS 和 Native 问题的平台特定扩展

理解 IR 序列化对于处理 Kotlin 编译器内部至关重要,尤其是在处理跨模块依赖、增量编译和 KLIB 操作时。