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

302 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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与具体 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` 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: ...
```
### 指令 opcodeCEnum
```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 而非胖节点)