302 lines
12 KiB
Markdown
302 lines
12 KiB
Markdown
# 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.REnum`(tagged union)
|
||
|
||
LLVM 类型变体少、无通用字段、字段都是指针/小整数、操作主要通过 `match` 分派 —— 完全契合 REnum 的 tag+union 模型。
|
||
|
||
### 2.2 指令 opcode 用 `t.CEnum`
|
||
|
||
指令类别(Binary/Unary/Cast/Memory/Terminator/Compare)与具体 opcode(Add/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+union(Rust 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` struct,用 `vtype` 区分):一次 `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.py`:`LLVMType` REnum 定义 + `TypePrint(buf, ty)` 将类型序列化为 IR 文本(如 `i32`/`i32*`/`{i32, i8*}`)
|
||
- `__values.py`:`Value` 表示一个 SSA 值(`%0`/`%result`/常量),含类型指针 + 名字 + 是否常量
|
||
- `__module.py`:`Module` 持有函数链表 + 目标三元组 + 数据布局,`ModulePrint` 输出完整 `.ll`
|
||
- `__function.py`:`Function` 持有基本块链表 + 参数 + 返回类型;`BasicBlock` 持有指令文本缓冲
|
||
- `__builder.py`:`IRBuilder` 游标式 API,`build_add`/`build_load`/`build_br`/... 发射指令到当前块
|
||
|
||
---
|
||
|
||
## 5. 类型系统设计(REnum)
|
||
|
||
```python
|
||
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` 导出)
|
||
|
||
```python
|
||
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 分派)
|
||
|
||
```python
|
||
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 设计
|
||
|
||
```python
|
||
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`,发射指令文本到块的缓冲区。
|
||
|
||
```python
|
||
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: ...
|
||
```
|
||
|
||
### 指令 opcode(CEnum)
|
||
|
||
```python
|
||
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` 格式化到块缓冲区。示例:
|
||
|
||
```python
|
||
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 输出
|
||
|
||
```python
|
||
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. **类型打印测试**:构造各 `LLVMType`,`TypePrint` 输出,对比预期字符串
|
||
2. **小函数生成**:用 `IRBuilder` 生成 `i32 add(i32 a, i32 b) { ret i32 %r }`,`ModulePrint` 写 `.ll`,用 `llc` 编译验证语法合法
|
||
3. **match 分派测试**:构造各变体 `LLVMType`,`match` 正确绑定 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 而非胖节点)
|