Files
TransPyC/includes/llvmlite
2026-07-18 19:25:40 +08:00
..
2026-07-18 19:25:40 +08:00
2026-07-18 19:25:40 +08:00
2026-07-18 19:25:40 +08:00
2026-07-18 19:25:40 +08:00
2026-07-18 19:25:40 +08:00
2026-07-18 19:25:40 +08:00
2026-07-18 19:25:40 +08:00
2026-07-18 19:25:40 +08:00

llvmlite 设计方案

用 TransPyC 自身实现的轻量级 LLVM IR 文本生成库,替代 Python llvmlite 依赖,为自举(见 wiki/13-bootstrapping.md 路径 B铺路。

1. 目标与定位

  • 输入:类型/值/指令的语义对象
  • 输出:合法的 LLVM IR 文本(.ll),可直接喂给 llc
  • 不做什么:不解析 IR、不做优化、不绑定 LLVM C API纯文本生成
  • 自举约束:本库必须能用当前 TransPyC 编译器完整编译,不得依赖任何 Python 运行时

2. 核心设计决策

2.1 类型系统用 t.REnumtagged union

LLVM 类型变体少、无通用字段、字段都是指针/小整数、操作主要通过 match 分派 —— 完全契合 REnum 的 tag+union 模型。

2.2 指令 opcode 用 t.CEnum

指令类别Binary/Unary/Cast/Memory/Terminator/Compare与具体 opcodeAdd/Sub/Load/Store/Br/Ret/...)用 t.CEnum 组织,类型安全且零开销。

2.3 IR 文本生成用 viperlib.snprintf

所有 IR 文本通过 viperlib.snprintf(buf, size, fmt, *args) 格式化写入缓冲区,避免手写逐字符拼接。snprintf 已支持宽度/精度/符号/%s/%d/%x 等,足够覆盖 IR 语法。

2.4 对象分配用普通类 + mpool

mbuddy 一样用普通类(无 __new__),由调用方注入 mpool.MPool 做批量分配。库内部不持有全局 mbuddy,遵循项目约定(_mbuddy 指针由 os.py 或调用方设置)。


3. 为什么 AST 不用 REnum而 llvmlite 用?

用户质疑REnum 是 tag+unionRust enum配合 match 模式匹配,按理很适合 AST。下面解释两者的适配性差异。

3.1 REnum 的硬限制(来自 HandlesClassDef._EmitREnumLlvm

  1. 无通用字段共享:每个 variant 独立声明字段,通用字段(如 lineno/child/next)必须在每 variant 重复声明。
  2. 共享最大变体尺寸:布局为 { i32 __tag, <max_variant_payload> },小变体浪费内存。
  3. payload 仅位置绑定match 通过位置绑定 payload不能直接 node.child 属性访问 payload 字段。
  4. 无方法variant 不能挂方法。

3.2 AST 不适合 REnum 的四个原因

维度 AST 需求 REnum 限制 冲突
变体数量 70+ 节点类型 共享最大变体尺寸 内存膨胀(每节点都占最大变体空间)
通用字段 lineno/child/next/parent 等 ~8 个字段全节点共享 无通用字段共享 要么每 variant 重复声明(冗长),要么放弃统一访问
访问模式 频繁直接 node.child/node.next/node.lineno payload 仅位置绑定,需 match 分派 遍历代码爆炸(每次访问都要 match
字段复用 int_val/str_val/op 等字段按 vtype 复用语义 每 variant 独立 payload 无法复用

AST 采用"胖节点"设计(所有节点共用一个 AST structvtype 区分):一次 malloc、统一链表遍历(child/next)、字段复用。这与 REnum"每变体独立 payload"的哲学根本冲突。

3.3 llvmlite 的 LLVMType 适合 REnum 的原因

维度 LLVMType 需求 REnum 特性 契合度
变体数量 ~10 个Int/Ptr/Func/Array/Struct/Void/Float/Label/... 共享最大变体尺寸 浪费可接受(最大变体 ~24 字节)
通用字段 无(每变体字段不同) 无通用字段共享 完美匹配
访问模式 类型操作天然通过 match 分派("是 Int 取 bits是 Ptr 取 pointee" payload 位置绑定 + match 天然契合
字段尺寸 都是指针(8B)或小整数(4B) 共享最大变体 浪费小

结论REnum 是"少变体、无共性、match 分派"场景的最佳工具 —— llvmlite 类型系统正好如此AST 正好相反。


4. 文件结构

includes/llvmlite/
├── __init__.py          # 公共导出 + 便捷工厂函数
├── __types.py           # LLVMType (REnum) + 类型构造/打印
├── __values.py          # Value/Constant/SSA 值表示
├── __module.py          # Module 容器(函数列表 + 目标三元组 + 输出 .ll
├── __function.py        # Function + BasicBlock + 参数管理
├── __builder.py         # IRBuilder指令发射 + SSA 命名 + 块跳转)
└── README.md            # 本文件

职责划分

  • __types.pyLLVMType REnum 定义 + TypePrint(buf, ty) 将类型序列化为 IR 文本(如 i32/i32*/{i32, i8*}
  • __values.pyValue 表示一个 SSA 值(%0/%result/常量),含类型指针 + 名字 + 是否常量
  • __module.pyModule 持有函数链表 + 目标三元组 + 数据布局,ModulePrint 输出完整 .ll
  • __function.pyFunction 持有基本块链表 + 参数 + 返回类型;BasicBlock 持有指令文本缓冲
  • __builder.pyIRBuilder 游标式 APIbuild_add/build_load/build_br/... 发射指令到当前块

5. 类型系统设计REnum

import t
from stdint import *

class LLVMType(t.REnum):
    # 变体 Int整数类型
    IntBits: t.CInt               # 位宽 1/8/16/32/64/128

    # 变体 Ptr指针类型
    PtrPointee: LLVMType | t.CPtr  # 指向的类型(自引用用指针)

    # 变体 Func函数类型
    FuncRet:    LLVMType | t.CPtr  # 返回类型
    FuncParams: LLVMType | t.CPtr  # 参数类型链表头
    FuncPCount: t.CInt             # 参数数量

    # 变体 Array数组类型
    ArrayElem:  LLVMType | t.CPtr  # 元素类型
    ArrayCount: t.CInt             # 元素数量

    # 变体 Struct结构体类型
    StructHead:  LLVMType | t.CPtr # 字段类型链表头
    StructCount: t.CInt            # 字段数量

    # 变体 Float浮点类型
    FloatBits: t.CInt              # 16/32/64/128

    # 变体 Void / Label / Metadata无 payload
    # REnum 允许空变体,仅靠 __tag 区分)

类型工厂(__init__.py 导出)

def Int1()  -> LLVMType | t.CPtr: ...   # i1
def Int8()  -> LLVMType | t.CPtr: ...   # i8
def Int32() -> LLVMType | t.CPtr: ...   # i32
def Int64() -> LLVMType | t.CPtr: ...   # i64
def Ptr(pointee: LLVMType | t.CPtr) -> LLVMType | t.CPtr: ...
def Func(ret: LLVMType | t.CPtr, params: LLVMType | t.CPtr, count: t.CInt) -> LLVMType | t.CPtr: ...
def Void() -> LLVMType | t.CPtr: ...

类型打印match 分派)

def TypePrint(buf: t.CChar | t.CPtr, size: t.CSizeT, ty: LLVMType | t.CPtr):
    match ty:
        case LLVMType.Int:        # 位置绑定 IntBits
            viperlib.snprintf(buf, size, "i%d", ty.IntBits)
        case LLVMType.Ptr:        # 位置绑定 PtrPointee
            TypePrint(inner, size2, ty.PtrPointee)
            # 拼接 "*"
        case LLVMType.Func:
            # 打印 ret (params)
        case LLVMType.Void:
            string.strcpy(buf, "void")
        ...

6. Value / SSA 设计

class Value:
    Ty:    LLVMType | t.CPtr   # 值的类型
    Name:  t.CChar | t.CPtr    # SSA 名("%0"/"%result")或常量文本
    IsConst: t.CInt            # 1=常量字面量0=SSA 临时值
    Next:  Value | t.CPtr      # 链表(函数内的值池)

IRBuilder 维护一个递增的 %N 计数器,每发射一条产生结果的指令就分配一个新名。


7. IRBuilder 设计

游标式 API跟踪当前 BasicBlock,发射指令文本到块的缓冲区。

class IRBuilder:
    Func:   Function | t.CPtr   # 所属函数
    CurBlock: BasicBlock | t.CPtr  # 当前插入点
    Counter: t.CInt             # SSA 名计数器

    def build_add(self, lhs: Value | t.CPtr, rhs: Value | t.CPtr) -> Value | t.CPtr:
        # 1. 分配 SSA 名 %N
        # 2. snprintf(buf, size, "  %%%d = add %s %s, %s\n", N, ty, lhs, rhs)
        # 3. 追加到 CurBlock 缓冲
        # 4. 返回新 Value

    def build_load(self, ty: LLVMType | t.CPtr, ptr: Value | t.CPtr) -> Value | t.CPtr: ...
    def build_store(self, val: Value | t.CPtr, ptr: Value | t.CPtr): ...
    def build_br(self, target: BasicBlock | t.CPtr): ...
    def build_cond_br(self, cond: Value | t.CPtr, then: BasicBlock | t.CPtr, else_: BasicBlock | t.CPtr): ...
    def build_ret(self, val: Value | t.CPtr): ...
    def build_alloca(self, ty: LLVMType | t.CPtr) -> Value | t.CPtr: ...
    def build_gep(self, ptr: Value | t.CPtr, idx: Value | t.CPtr) -> Value | t.CPtr: ...
    def build_call(self, callee: t.CChar | t.CPtr, args: Value | t.CPtr, ret_ty: LLVMType | t.CPtr) -> Value | t.CPtr: ...

指令 opcodeCEnum

class IROp(t.CEnum):
    # Terminator
    Ret:        t.CEnum = 1
    Br:         t.CEnum = 2
    CondBr:     t.CEnum = 3
    Switch:     t.CEnum = 4
    # Binary
    Add:        t.CEnum = 10
    Sub:        t.CEnum = 11
    Mul:        t.CEnum = 12
    SDiv:       t.CEnum = 13
    # Memory
    Load:       t.CEnum = 20
    Store:      t.CEnum = 21
    Alloca:     t.CEnum = 22
    Gep:        t.CEnum = 23
    # Cast
    BitCast:    t.CEnum = 30
    SExt:       t.CEnum = 31
    Trunc:      t.CEnum = 32
    # Compare
    ICmp:       t.CEnum = 40
    # Call
    Call:       t.CEnum = 50

8. IR 文本生成snprintf 用法)

每条指令发射用 viperlib.snprintf 格式化到块缓冲区。示例:

import viperlib

def _emit_add(block: BasicBlock | t.CPtr, name: t.CChar | t.CPtr,
              ty_str: t.CChar | t.CPtr, lhs: t.CChar | t.CPtr, rhs: t.CChar | t.CPtr):
    line: t.CChar | t.CPtr = mpool.alloc(128)   # 行缓冲
    viperlib.snprintf(line, 128, "  %s = add %s %s, %s\n", name, ty_str, lhs, rhs)
    _block_append(block, line)

Module 输出

def ModulePrint(mod: Module | t.CPtr, out_path: t.CChar | t.CPtr):
    # 1. 写头部target triple / datalayout
    # 2. 遍历函数,写 declare/define
    # 3. 每函数:签名 + 基本块 + 指令
    # 用 snprintf 拼接写到文件viperio 或 w32/win32file

9. 内存管理

  • 库内所有对象从 mpool.MPool 分配(像 ast 库一样)
  • IRBuilder/Module/Function 不持有 mbuddy,由调用方传入 mpool
  • 文本缓冲区按行分配每指令一行128 字节起步),避免频繁 realloc

10. 基础构建范围(第一阶段)

最小可用集,能生成一个完整的小程序 IR

  1. 类型IntType(1/8/16/32/64) + PointerType + FunctionType + VoidType + StructType
  2. Value + 常量整数字面量
  3. Module/Function/BasicBlock:容器与链表
  4. IRBuilder 指令子集:
    • alloca / load / store
    • add / sub / mul / sdiv
    • icmp (eq/ne/sgt/sge/slt/sle)
    • br / cond_br / ret
    • call(外部函数)
    • bitcast / sext / trunc
    • getelementptr
  5. ModulePrint:输出完整 .ll 文件

后续扩展路线

  • 第二阶段:浮点指令、phi 节点、switch、字符串常量全局、属性(nounwind/nocapture
  • 第三阶段:内联汇编、atomic 指令、va_arg、精确的 SSA 验证支配边界、phi 完整性)
  • 第四阶段:对接 TransPyC 的 LLVMCG,替换 Python llvmlite,完成路径 B 自举

11. 测试计划

独立测试项目(Test/LLvmLiteTest/

  1. 类型打印测试:构造各 LLVMTypeTypePrint 输出,对比预期字符串
  2. 小函数生成:用 IRBuilder 生成 i32 add(i32 a, i32 b) { ret i32 %r }ModulePrint.ll,用 llc 编译验证语法合法
  3. match 分派测试:构造各变体 LLVMTypematch 正确绑定 payload
  4. snprintf 格式测试:验证 %d/%s/%x 在 IR 文本中的正确性

12. 与现有架构的对接点

  • lib/core/Codegen/LLVMCG.py:当前用 Python llvmlite 生成 IR自举时改用本库
  • includes/viperlib.py:提供 snprintf / sprintf
  • includes/mpool.py:提供 MPool 内存池
  • includes/string.py:提供 strcpy/strlen/memcpy
  • includes/ast/:参考其"胖节点 + mpool + CEnum 常量"的组织方式(但类型系统用 REnum 而非胖节点)