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符号系统的具体实现。

相关推荐
Kapaseker16 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
A0微声z3 天前
Kotlin Multiplatform (KMP) 中使用 Protobuf
kotlin
alexhilton3 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
lhDream3 天前
Kotlin 开发者必看!JetBrains 开源 LLM 框架 Koog 快速上手指南(含示例)
kotlin
RdoZam4 天前
Android-封装基类Activity\Fragment,从0到1记录
android·kotlin
Kapaseker4 天前
研究表明,开发者对Kotlin集合的了解不到 20%
android·kotlin
糖猫猫cc4 天前
Kite:两种方式实现动态表名
java·kotlin·orm·kite
如此风景5 天前
kotlin协程学习小计
android·kotlin
Kapaseker5 天前
你搞得懂这 15 个 Android 架构问题吗
android·kotlin
zh_xuan5 天前
kotlin 高阶函数用法
开发语言·kotlin