Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

3.3 AstGen:AST 如何平铺降级为线性 ZIR

在语法树(AST)构建完成之后,编译器需要将树状嵌套结构转换为便于线性分析的指令流。

在 Zig 0.17 中,负责这一转换的模块为 lib/std/zig/AstGen.zig。


树状表达式的线性化转换

我们以一个简单的算术表达式为例:

const result = (a + b) * (c - d);

在 AST 中,该表达式表现为一棵二叉树:

graph TD
    %% Global styles and definitions
    classDef core stroke:#0066cc,stroke-width:2px;
    classDef middle stroke:#ff9900,stroke-width:2px;
    classDef edge stroke:#ccc,stroke-width:2px;

    subgraph SG_AST_Tree["AST 树状表示 (嵌套结构)"]
        direction TB
        Mul["Node: Mul(*)"]
        Add["Node: Add(+)"]
        Sub["Node: Sub(-)"]
        A["Node: a"]
        B["Node: b"]
        C["Node: c"]
        D["Node: d"]

        Mul --> Add
        Mul --> Sub
        Add --> A
        Add --> B
        Sub --> C
        Sub --> D
    end

    subgraph SG_ZIR_Stream["ZIR 线性指令流"]
        direction TB
        I0["%0 = resolve_ident('a')"]
        I1["%1 = resolve_ident('b')"]
        I2["%2 = add(%0, %1)"]
        I3["%3 = resolve_ident('c')"]
        I4["%4 = resolve_ident('d')"]
        I5["%5 = sub(%3, %4)"]
        I6["%6 = mul(%2, %5)"]
        I7["declaration('result', %6)"]

        I0 --> I1 --> I2 --> I3 --> I4 --> I5 --> I6 --> I7
    end

    SG_AST_Tree -- "AstGen 后序遍历发射" --> SG_ZIR_Stream

    style SG_AST_Tree stroke:#ff9900,stroke-width:2px;
    style SG_ZIR_Stream stroke:#0066cc,stroke-width:2px;

    class Mul,Add,Sub middle;
    class A,B,C,D edge;
    class I0,I1,I2,I3,I4,I5,I6,I7 core;

AstGen 通过后序遍历(Post-order Traversal)递归访问子节点,将基础计算操作依次追加至 instructions 数组中。每条新指令通过其数组索引(Inst.Ref)引用先前指令的生成结果。


AstGen 的关键机制

查看 lib/std/zig/AstGen.zig#L19-L73,可以观察到 AstGen 内部包含几项关键优化机制:

1. 结果位置标注(Result Location Annotation, AstRlAnnotate)

nodes_need_rl: *const AstRlAnnotate.RlNeededSet,

在系统编程中,大体积结构体的拷贝会带来性能负担。Zig 支持原地构造语义(In-place Construction)。在 AstGen 生成 ZIR 前,AstRlAnnotate 预先扫描语法树,标记出可以直接接受“结果指针(Result Pointer)”的表达式。例如当函数返回复杂结构体时,可直接向调用方提供的栈帧缓冲区写入,避免多余的中间拷贝。

2. 线性单向行列号追踪(Linear Source Location Tracking)

source_offset: u32 = 0,
source_line: u32 = 0,
source_column: u32 = 0,

为避免每次记录错误位置时重新从文件开头扫描统计换行符(这容易导致 $O(N^2)$ 的时间开销),AstGen 维护了一个单向递增的源码游标,在整个遍历过程中线性向前推进,以 $O(N)$ 复杂度为各条指令记录精确的源码位置。

3. 声明级源码哈希计算(src_hasher)

src_hasher: std.zig.SrcHasher,

AstGen 在生成 ZIR 时,会针对文件内的每个独立顶层声明(函数、结构体、全局变量)单独计算其源码内容的哈希值。 当文件内容发生微调时,若前序声明的哈希未发生改变,语义分析阶段即可快速复用先前的分析结果,跳过未修改声明的重复推导。

4. 局部字符串去重驻留(String Deduplication)

string_table: std.HashMapUnmanaged(u32, void, StringIndexContext, ...),
string_bytes: ArrayList(u8),

若源文件中频繁出现相同的变量名或标识符,AstGen 通过局部哈希索引确保相同的字符串在当前文件的 string_bytes 字节池中仅存储一份物理拷贝,指令只需记录偏移量。


阶段小结

AstGen 将语法树结构平铺转换为易于线性处理的无类型指令流。由于生成的 ZIR 独立于具体的目标平台架构与类型系统,它具备良好的可持久化特征,可直接写入磁盘缓存。

下一节我们将探讨 .zig-cache 中的 ZIR 磁盘缓存管理机制。