取消了 i8* == i8* 实际使用 strcmp 的规则

This commit is contained in:
2026-07-30 21:56:41 +08:00
parent cfc30d735c
commit 377b60fd67
54 changed files with 129 additions and 3730 deletions

View File

@@ -12,7 +12,7 @@
1. **Python 语法LLVM 语义**:源文件是完全合法的 `Python` 语法,但通过 `t` 模块的类型注解系统赋予完全不同的语义 —— `t.CInt` 不是 `Python``int`,而是 `LLVM``i32`
2. **类型注解即编译指令**Viper 的类型注解不是可选的提示,而是编译器生成 `LLVM IR` 的决定性依据。`x: t.CInt` 生成 `i32``x: t.CInt | t.CPtr` 生成 `i32*`,但无注解时会自动推导,详见下文。
3. **两阶段编译**:先从源文件提取声明接口(`.pyi` + `.stub.ll`),再使用声明接口翻译源文件为含代码的 `.ll`。这是 `Viper` 区别于简单"`Python``C`"工具的关键架构
3. **两阶段编译**:先从源文件提取声明接口(`.vpi` + `.stub.ll`),再使用声明接口翻译源文件为含代码的 `.ll`。这是 `Viper` 区别于简单"`Python``C`"工具的关键架构
4. **`SHA1` 命名空间**:每个源文件按内容 `SHA1` 哈希命名,非导出函数和结构体自动加上 `SHA1` 前缀,从根本上消除跨模块的符号冲突
5. **万物皆数据**:为便于底层开发,实际上所有的变量和类型一般并不适用于鸭子类型,但在语义层面我们会尽可能贴近鸭子类型。
@@ -37,7 +37,7 @@ Viper 编译后的代码在底层等价于 `C` 编译后的机器码(都经过
- **声明式内联汇编**`c.Asm(f"mov {c.AsmOut(x, t.ASM_DESCR.OUTPUT_REG)}, rdi")` 比裸 `__asm__` 更安全,也更具可读性。
- **结构化预处理**`c.CIfdef`/`c.CEndif()` 替代 `#ifdef`/`#endif`
- **面向对象**`@t.Object` + `@t.CVTable` 提供 `vtable` 支持的多态 `OOP``@t.NoVTable` 提供 C++ 风格的非多态继承(零 vtable 开销PEP 695 泛型 + 递归泛型继承实现强类型自引用结构
- **存根驱动的模块系统**`.pyi` 存根文件实现跨模块类型解析,无需头文件
- **存根驱动的模块系统**`.vpi` 存根文件实现跨模块类型解析,无需头文件
- **更多内容**:额外更多的不依赖操作系统的函数和语法
## 两阶段编译流程
@@ -45,12 +45,12 @@ Viper 编译后的代码在底层等价于 `C` 编译后的机器码(都经过
Viper 的编译由 `Projectrans.py` 驱动,分为两个阶段。这是 Viper 编译模型的核心,理解两阶段编译是理解 `t.CDefine``t.CExport``t.CInline` 等关键概念的前提。
```
源文件 (.py) ──────────────────────────────────────────────────────
源文件 (.vp) ──────────────────────────────────────────────────────
│ │
│ ┌─────────────── 阶段一:声明提取 ───────────────┐ │
│ │ │ │
│ │ 1. 计算源文件 SHA1 │ │
│ │ 2. 生成 <SHA1>.pyi签名存根 │ │
│ │ 2. 生成 <SHA1>.vpi签名存根 │ │
│ │ 3. 构建结构体注册表 │ │
│ │ 4. 生成 <SHA1>.stub.llLLVM IR 声明) │ │
│ │ │ │
@@ -58,7 +58,7 @@ Viper 的编译由 `Projectrans.py` 驱动,分为两个阶段。这是 Viper
│ │
│ ┌─────────────── 阶段二:代码翻译 ───────────────┐ │
│ │ │ │
│ │ 1. 加载所有 .pyi 和 .stub.ll │ │
│ │ 1. 加载所有 .vpi 和 .stub.ll │ │
│ │ 2. 构建共享符号表 │ │
│ │ 3. 收集内联函数符号 │ │
│ │ 4. 翻译源文件 → <SHA1>.ll含代码 │ │
@@ -76,15 +76,15 @@ Viper 的编译由 `Projectrans.py` 驱动,分为两个阶段。这是 Viper
#### 步骤 1可达文件发现与拓扑排序
从入口文件(`main.py``project.json` 指定)出发,通过 `import` 语句递归遍历,找出所有可达的 `.py` 源文件。然后对文件进行拓扑排序,确保被依赖的模块先被处理。
从入口文件(`main.vp``project.json` 指定)出发,通过 `import` 语句递归遍历,找出所有可达的 `.vp` 源文件。然后对文件进行拓扑排序,确保被依赖的模块先被处理。
```
main.py → import serial → import drivers.serial.uart.serial → ...
main.vp → import serial → import drivers.serial.uart.serial → ...
```
#### 步骤 2生成 `.pyi` 签名存根
#### 步骤 2生成 `.vpi` 签名存根
对每个源文件,计算其内容的 SHA1 哈希16位然后调用 `PythonToStubConverter` 生成签名存根文件 `<SHA1>.pyi`
对每个源文件,计算其内容的 SHA1 哈希16位然后调用 `PythonToStubConverter` 生成签名存根文件 `<SHA1>.vpi`
**SHA1 计算方式**
```python
@@ -108,7 +108,7 @@ sha1 = hashlib.sha1(content.encode('utf-8')).hexdigest()[:16]
#### 步骤 3构建结构体注册表
扫描所有已生成的 `.pyi` 文件,提取:
扫描所有已生成的 `.vpi` 文件,提取:
- **结构体名称集合**:所有非枚举、非异常的 `class` 定义
- **枚举名称集合**:继承 `t.CEnum` 的类
- **异常名称集合**:继承 `Exception` 的类
@@ -118,7 +118,7 @@ sha1 = hashlib.sha1(content.encode('utf-8')).hexdigest()[:16]
#### 步骤 4生成 `.stub.ll` LLVM IR 声明
对每个 `.pyi` 文件,由 `DeclarationGenerator` 生成对应的 LLVM IR 声明文件 `<SHA1>.stub.ll`
对每个 `.vpi` 文件,由 `DeclarationGenerator` 生成对应的 LLVM IR 声明文件 `<SHA1>.stub.ll`
**声明生成规则**
@@ -132,7 +132,7 @@ sha1 = hashlib.sha1(content.encode('utf-8')).hexdigest()[:16]
| `t.CTypedef` 别名 | **不生成声明**(类型别名,在类型解析时展开) |
| 枚举 | 为每个枚举成员生成 `@__config_EnumName_member = external global i32` |
**增量编译**:如果 `<SHA1>.pyi``<SHA1>.stub.ll` 已存在,则跳过生成(缓存命中)。只有源文件内容变化导致 SHA1 变化时才重新生成。
**增量编译**:如果 `<SHA1>.vpi``<SHA1>.stub.ll` 已存在,则跳过生成(缓存命中)。只有源文件内容变化导致 SHA1 变化时才重新生成。
### 阶段二代码翻译Phase2Translator
@@ -140,14 +140,14 @@ sha1 = hashlib.sha1(content.encode('utf-8')).hexdigest()[:16]
#### 步骤 1加载声明接口
加载 `temp/` 目录中所有的 `.pyi``.stub.ll` 文件,构建:
- `sig_files`SHA1 → `.pyi` 文件路径映射
加载 `temp/` 目录中所有的 `.vpi``.stub.ll` 文件,构建:
- `sig_files`SHA1 → `.vpi` 文件路径映射
- `stub_files`SHA1 → `.stub.ll` 文件路径映射
- `sha1_map`SHA1 → 源文件相对路径映射
#### 步骤 2构建共享符号表
一次性构建所有文件共享的符号表数据,避免每个文件重复加载。将所有 `.pyi` 存根和 `.stub.ll` 声明中的类型信息注册到统一的 `SymbolTable` 中。
一次性构建所有文件共享的符号表数据,避免每个文件重复加载。将所有 `.vpi` 存根和 `.stub.ll` 声明中的类型信息注册到统一的 `SymbolTable` 中。
#### 步骤 3收集内联函数符号
@@ -179,20 +179,20 @@ SHA1 命名空间是 Viper 解决跨模块符号冲突的核心机制。
Viper 使用源文件内容的 SHA1 哈希作为命名空间前缀:
```
源文件 A.pySHA1 = a1b2c3d4e5f6g7h8中定义
源文件 A.vpSHA1 = a1b2c3d4e5f6g7h8中定义
class Point: x: t.CInt; y: t.CInt
def draw(p: Point) -> t.CVoid: ...
源文件 B.pySHA1 = i9j0k1l2m3n4o5p6中也定义
源文件 B.vpSHA1 = i9j0k1l2m3n4o5p6中也定义
class Point: x: t.CFloat; y: t.CFloat # 不同的结构体!
def draw(p: Point) -> t.CVoid: ...
编译后:
A.py 的结构体 → %"a1b2c3d4e5f6g7h8.Point" = type { i32, i32 }
A.py 的函数 → declare void @"a1b2c3d4e5f6g7h8.draw"(%"a1b2c3d4e5f6g7h8.Point"*)
A.vp 的结构体 → %"a1b2c3d4e5f6g7h8.Point" = type { i32, i32 }
A.vp 的函数 → declare void @"a1b2c3d4e5f6g7h8.draw"(%"a1b2c3d4e5f6g7h8.Point"*)
B.py 的结构体 → %"i9j0k1l2m3n4o5p6.Point" = type { float, float }
B.py 的函数 → declare void @"i9j0k1l2m3n4o5p6.draw"(%"i9j0k1l2m3n4o5p6.Point"*)
B.vp 的结构体 → %"i9j0k1l2m3n4o5p6.Point" = type { float, float }
B.vp 的函数 → declare void @"i9j0k1l2m3n4o5p6.draw"(%"i9j0k1l2m3n4o5p6.Point"*)
```
**没有符号冲突**——即使两个模块定义了同名类型和函数,它们的 LLVM IR 符号也是不同的。即便有两个内容完全相同的文件也不会冲突,他们会被识别为同一个文件,然后进行单次编译。
@@ -202,7 +202,7 @@ Viper 使用源文件内容的 SHA1 哈希作为命名空间前缀:
标记为 `t.CExport` 的函数**不加 SHA1 前缀**,保持原始函数名。这是模块向外部暴露 API/ABI 的机制:
```python
# main.py — 入口函数,必须全局可见
# main.vp — 入口函数,必须全局可见
def main() -> t.CInt | t.CExport:
return 0
@@ -210,7 +210,7 @@ def main() -> t.CInt | t.CExport:
```
```python
# serial.py — 驱动接口,对外暴露
# serial.vp — 驱动接口,对外暴露
def init() -> t.CVoid | t.CExport:
serial_puts("init\n")
@@ -220,7 +220,7 @@ def init() -> t.CVoid | t.CExport:
### SHA1 与增量编译
SHA1 同时服务于增量编译:
- 源文件内容不变 → SHA1 不变 → `.pyi``.stub.ll` 缓存命中,跳过阶段一
- 源文件内容不变 → SHA1 不变 → `.vpi``.stub.ll` 缓存命中,跳过阶段一
- 源文件内容变化 → SHA1 变化 → 重新生成声明接口
- `temp/_sha1_map.txt` 记录 SHA1 → 源文件路径的映射,供阶段二加载
@@ -305,7 +305,7 @@ def _helper() -> t.CInt:
### `t.CExport` 在存根中的表现
`.pyi` 存根文件中,`t.CExport` 函数与普通函数一样只保留签名(添加 `c.State`,表示单纯声明),但 `t.CExport` 标记被保留在返回类型注解中。阶段一的 `DeclarationGenerator` 检查 `t.CExport` 标记来决定是否添加 SHA1 前缀。
`.vpi` 存根文件中,`t.CExport` 函数与普通函数一样只保留签名(添加 `c.State`,表示单纯声明),但 `t.CExport` 标记被保留在返回类型注解中。阶段一的 `DeclarationGenerator` 检查 `t.CExport` 标记来决定是否添加 SHA1 前缀。
## `t.CInline` 深度解析
@@ -351,7 +351,7 @@ def main() -> t.CInt | t.CExport:
编译流程:
1. **阶段一**:计算 `main.py` 的 SHA1生成 `.pyi` 存根(`def main() -> t.CInt | t.CExport | c.State: pass`),生成 `.stub.ll` 声明(`declare i32 @main()`,因为是 CExport 不加前缀)
2. **阶段二**:翻译 `main.py``<SHA1>.ll`,嵌入 `print` 对应的 `puts`/`printf` 声明,生成 `define i32 @main()` 函数体
1. **阶段一**:计算 `main.vp` 的 SHA1生成 `.vpi` 存根(`def main() -> t.CInt | t.CExport | c.State: pass`),生成 `.stub.ll` 声明(`declare i32 @main()`,因为是 CExport 不加前缀)
2. **阶段二**:翻译 `main.vp``<SHA1>.ll`,嵌入 `print` 对应的 `puts`/`printf` 声明,生成 `define i32 @main()` 函数体
3. **编译**`llc``.ll` 编译为 `.o`
4. **链接**`ld.lld` 链接为可执行文件