K2 编译器 - Symbol(符号系统)

符号系统 Symbol 是Kotlin K2编译器的核心抽象,为编译器各个阶段提供统一的代码表示方式,是学习编译器的基础。K2编译器采用两套符号系统:FIR(Frontend Intermediate Representation)符号系统负责语义分析,理解代码含义;IR(Intermediate Representation)符号系统负责代码生成,将语义转换为可执行代码。

本文通过一个简单的Person.greet()方法示例,展示两套符号系统如何协同工作,帮助读者建立对K2编译器符号系统的整体认识。

版本说明:本文基于Kotlin 2.0+版本的K2编译器进行分析。K2编译器从Kotlin 1.9开始作为Alpha版本引入,在Kotlin 2.0中成为稳定版本。文中涉及的FIR和IR符号系统是K2编译器的核心组件,与旧版K1编译器的实现有显著差异。


1. 符号系统概述

符号系统是编译器中的核心抽象,提供统一的方式来引用和管理程序中的各种声明(类、函数、属性等)。在Kotlin编译器中存在两套符号系统:

  • FIR符号系统:用于前端语义分析和类型检查
  • IR符号系统:用于后端代码生成和优化

1.1 符号示例

为了更好地理解符号系统,我们以一个简单的函数为例,展示它在编译过程中的不同形态:

说明:为了更直观地理解符号,下面的示例仅保留了核心内容。

示例:Kotlin源码

Persongreet方法为例:

kotlin 复制代码
package com.example

class Person(val name: String) {
    fun greet(): String {
        return "Hello, $name!"
    }
}

FIR符号表示

kotlin 复制代码
class FirFunctionSymbol {
    val callableId: CallableId  // "com.example.Person.greet"
    val fir: FirFunction        // FIR方法声明
}

// FIR方法声明详细结构
class FirFunction {
    val name: Name                    // "greet"
    val returnTypeRef: FirTypeRef     // String类型引用
    val valueParameters: List<FirValueParameter> // 参数列表(本例为空)
    val body: FirBlock?               // 方法体
}

IR符号表示

kotlin 复制代码
class IrFunctionSymbol {
    val signature: IdSignature  // "com.example.Person.greet"的二进制签名
    val owner: IrFunction       // IR方法声明
}

// IR方法声明详细结构
class IrFunction {
    val name: Name                    // "greet"
    val returnType: IrType            // String类型
    val valueParameters: List<IrValueParameter> // 参数列表(本例为空)
    val body: IrBody?                 // 方法体
}

2. 符号:描述代码的代码

2.1 符号的本质

符号系统本质上是"描述代码的代码"------它不是程序的执行逻辑,而是程序结构的抽象描述。就像我们用文字描述一幅画一样,符号用数据结构描述程序的结构。

2.2 FIR和IR:编译器的架构分层

编译器采用分层架构,每层有不同的职责定位:

说明:由于源码类型关系复杂,同一个接口/抽象类子类繁多不利于理解,所以示例忽略了具体的实现类,没有严格遵循源码的类型。

FIR层:前端语义分析

kotlin 复制代码
// 完整的greet方法在FIR中的表示
FirSimpleFunction(
    name = "greet",
    returnTypeRef = FirResolvedTypeRef(StringType),
    body = FirBlock(
        statements = [
            FirReturnExpression(
                result = FirStringConcatenationCall( // 字符串拼接函数
                    arguments = [
                        FirConstExpression("Hello, "), // 参数1:字符串常量
                        FirPropertyAccessExpression(  // 参数2:属性访问
                            calleeReference = FirResolvedNamedReference(
                                name = "name",
                                resolvedSymbol = FirPropertySymbol(  // name属性的符号
                                    callableId = CallableId("com.example.Person.name"),
                                    fir = FirProperty(  // name属性的FIR声明
                                        name = "name",
                                        returnTypeRef = FirResolvedTypeRef(StringType),
                                        isVal = true,  // "这是只读属性"
                                        initializer = FirParameterReference(...)  // "来自构造函数参数"
                                    )
                                )
                            ),
                            typeRef = FirResolvedTypeRef(StringType)  // 确定类型为String
                        ),
                        FirConstExpression("!") // 参数3:字符串常量
                    ]
                )
            )
        ]
    )
)

IR层:后端代码生成

kotlin 复制代码
// 完整的greet方法在IR中的表示
IrSimpleFunction(
    name = "greet",
    returnType = IrType.String,
    body = IrBlockBody(
        statements = [
            IrReturn(
                value = IrStringConcatenation(
                    arguments = [
                        IrConst(value = "Hello, "),        // 直接的常量
                        IrGetValue(                         // 简化的字段访问
                            symbol = IrFieldSymbol(         // name字段的符号
                                signature = IdSignature.CommonSignature("com.example.Person.name"),
                                owner = IrField(            // name字段的IR声明
                                    name = "name",
                                    type = IrType.String,
                                    visibility = DescriptorVisibilities.PUBLIC,   // "运行时访问权限"
                                    isFinal = true,         // "运行时是否可变"
                                    isStatic = false        // 实例字段
                                )
                            ),
                            type = IrType.String            // 运行时类型
                        ),
                        IrConst(value = "!")
                    ]
                )
            )
        ]
    )
)

关键差异分析:变量引用的不同处理方式

通过对比完整的方法表示,我们发现:

  • 基础结构相似:方法名、返回类型、常量等在两套系统中表示方式基本一致
  • 核心差异在变量引用 :对于$name这个变量引用,两套系统的处理方式截然不同

FIR:深度语义分析

kotlin 复制代码
// FIR需要完整理解:这个name究竟是什么?
FirPropertyAccessExpression(  // 识别为"属性访问表达式"
    calleeReference = FirResolvedNamedReference(  // 解析引用目标
        name = "name",
        resolvedSymbol = FirPropertySymbol(  // 确定是属性符号
            callableId = CallableId("com.example.Person.name"),  // 全限定名
            fir = FirProperty(  // 属性的完整定义
                isVal = true,  // 语义:只读属性
                returnTypeRef = FirResolvedTypeRef(StringType),  // 类型信息
                initializer = FirParameterReference(...)  // 来源:构造函数参数
            )
        )
    ),
    typeRef = FirResolvedTypeRef(StringType)  // 表达式结果类型
)
// FIR保存了完整的"身份证":这是什么、从哪来、为什么可以访问

IR:直接执行指令

kotlin 复制代码
// IR只关心:如何快速获取这个值?
IrGetValue(  // 简化为"取值指令"
    symbol = IrFieldSymbol(  // 直接指向字段
        signature = IdSignature.CommonSignature("com.example.Person.name"),
        owner = IrField(  // 字段的运行时信息
            name = "name",
            type = IrType.String,  // 运行时类型
            visibility = PUBLIC,   // 访问权限检查
            isFinal = true,        // 是否可变
            isStatic = false       // 实例字段
        )
    ),
    type = IrType.String  // 指令结果类型
)
// IR只保留了"操作手册":怎么访问、需要什么权限、返回什么类型

name属性引用符号数量对比

  • FIR :一个简单的属性访问需要多个符号协作
    • FirPropertyAccessExpression - 属性访问表达式
    • FirResolvedNamedReference - 解析后的名称引用
    • FirPropertySymbol - 属性符号
    • FirProperty - 属性声明
    • FirParameterReference - 参数引用
  • IR :同样的操作只需要少数几个符号
    • IrGetValue - 取值指令
    • IrFieldSymbol - 字段符号
    • IrField - 字段声明

本质区别总结

方面 FIR(语义分析) IR(代码生成)
目的 理解代码含义,验证正确性 生成可执行代码
信息类型 语义信息(为什么这样写) 执行信息(怎么运行)
复杂度 复杂,保留所有语义细节 简化,只保留执行必需
举例 "这是只读属性,来自构造参数" "从字段读取值"

从上面的分析还可以推断出一个结论:相比IR,FIR的符号建模更为精细,符号数量也更多

2.3 类比:建筑师的两套图纸

想象建造一栋房子需要两套不同的图纸:

设计图(类似FIR)

  • 详细标注房间用途、采光要求、美学设计
  • 包含大量设计理念和约束条件
  • 帮助理解"为什么这样设计"
  • 用于审批、沟通、验证合规性

施工图(类似IR)

  • 精确的尺寸、材料规格、施工步骤
  • 去除设计理念,专注实施细节
  • 帮助回答"如何具体建造"
  • 用于指导工人、采购材料、质量控制

3. 两套符号系统的关系与转换

FIR和IR符号系统不是独立工作的,而是在编译流程中协同配合。FIR符号承担前端的语义分析任务,IR符号负责后端的代码生成工作。两者通过FIR2IR转换器建立映射关系,实现从语义世界到执行世界的桥梁。

markdown 复制代码
Kotlin源码 → LightTree → FIR构建 → FIR2IR转换 → IR优化 → 目标代码生成
     ↓          ↓         ↓          ↓         ↓         ↓
   源码解析   轻量语法树   FIR符号    符号映射    IR符号    平台代码

这种设计实现了编译器的关注点分离:让FIR专注于"理解代码",让IR专注于"生成代码"。

4. 符号系统在编译流程中的作用

符号系统不仅仅是编译器的数据结构,更是编译器各个阶段协同工作的核心机制。通过我们的Person.greet()示例,可以清楚地看到符号系统如何在编译的每个关键环节发挥作用。

4.1 FIR阶段:语义理解的基石

在FIR阶段,符号系统的主要任务是理解代码的语义。让我们看看编译器如何处理我们的示例:

符号构建与类型推断

当编译器遇到"Hello, $name!"时,FIR符号系统的工作过程:

kotlin 复制代码
// 详细的类型处理过程:

// 步骤1:符号查找与类型解析
class Person(val name: String)  // ← name的类型在这里明确声明
//           ^^^^^^^^  ^^^^^^^
//           FIR符号系统记录:name: String

// 步骤2:表达式类型推断  
"Hello, $name!"  // ← 这里需要推断整个表达式的类型

FirStringConcatenationCall(
    arguments = [
        "Hello, ",  // 已知:String
        name,       // 已知:String(从符号获取)
        "!"         // 已知:String
    ]
)
// 推断规则:String + String + String = String

// 步骤3:类型兼容性检查
fun greet(): String = "Hello, $name!"
//           ^^^^^^   ^^^^^^^^^^^^^^^
//           期望类型  实际类型(推断得出)
//           类型检查:String = String ✓

这里涉及两个不同的过程:

  1. 符号类型解析name的类型来自其声明val name: String,不需要推断
  2. 表达式类型推断 :字符串拼接的结果类型通过分析所有参数类型推断得出
    • 扩展思考:如果name不是String类型会有什么不同?(本文暂不作分析)

4.2 IR阶段:执行优化的引擎

在IR阶段,符号系统的焦点转向如何高效执行代码,IR符号将属性访问优化为直接的字段读取,符号声明数量减少,构建开销和理解成本也大大降低。

4.3 插件支持的差异

由于FIR和IR符号系统承担着不同的编译职责,因此它们对编译器插件的支持方式也存在显著差异:

插件支持差异对比

方面 FIR插件 IR插件
作用阶段 语义分析阶段 代码生成阶段
操作对象 声明、类型、符号引用 函数体、表达式、指令
主要能力 生成新声明、修改类型关系 修改代码实现、优化执行逻辑
查找机制 基于谓词的声明查找 基于访问者模式的遍历
API稳定性 相对稳定(K2专用) 不稳定(跨版本兼容性差)
典型用例 序列化、依赖注入、元编程 性能优化、代码插桩、平台适配

5. 主要源码文件

5.1 FIR符号系统相关源码

  • compiler/fir/tree/src/org/jetbrains/kotlin/fir/symbols/FirBasedSymbol.kt - FIR符号基础接口
  • compiler/fir/tree/src/org/jetbrains/kotlin/fir/symbols/impl/FirCallableSymbol.kt - 可调用符号实现
  • compiler/fir/tree/src/org/jetbrains/kotlin/fir/symbols/impl/FirClassLikeSymbol.kt - 类符号实现
  • compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/FirExpression.kt - FIR表达式定义
  • compiler/fir/light-tree2fir/src/ - LightTree到FIR的转换

5.2 IR符号系统相关源码

  • compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/symbols/IrSymbol.kt - IR符号基础接口
  • compiler/ir/ir.tree/gen/org/jetbrains/kotlin/ir/symbols/IrSymbol.kt - IR符号生成接口
  • compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/symbols/impl/IrSymbolImpl.kt - IR符号实现

5.3 FIR2IR转换相关源码

  • compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrConverter.kt - FIR到IR转换器
  • compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrDeclarationStorage.kt - 声明存储
  • compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrClassifierStorage.kt - 分类器存储

本文档是符号系统系列的第一篇,后续将深入讲解FIR符号系统和IR符号系统的具体实现。

相关推荐
phoneixsky18 小时前
Kotlin的各种上下文Receiver,到底怎么个事
kotlin
heeheeai18 小时前
okhttp使用指南
okhttp·kotlin·教程
Monkey-旭20 小时前
Android 注解完全指南:从基础概念到自定义实战
android·java·kotlin·注解·annotation
alexhilton2 天前
如何构建Android应用:深入探讨原则而非规则
android·kotlin·android jetpack
TeleostNaCl2 天前
SMBJ 简单使用指南 实现在 Java/Android 程序中访问 SMB 服务器
android·java·运维·服务器·经验分享·kotlin
小孔龙2 天前
Kotlin 序列化:重复引用是技术问题还是架构缺陷?
android·kotlin·json
Kapaseker2 天前
每个Kotlin开发者应该掌握的最佳实践,第三趴
android·kotlin
低调小一3 天前
双端 FPS 全景解析:Android 与 iOS 的渲染机制、监控与优化
android·ios·kotlin·swift·fps
用户093 天前
Kotlin 将会成为跨平台开发的终极选择么?
android·面试·kotlin