# 01 - 语言概述与编译流程

## 语言定位

`Viper` 是一种 **系统级编程语言**，其语法基于 `Python`，但编译目标为 `LLVM IR`，最终生成原生机器码。`Viper` 不是 `Python` 的超集或子集，也不是"`C` 的语法糖"——它是一门拥有独立类型系统、独立编译模型、独立模块体系的语言。`Viper` 选择 `Python` 语法作为表达形式，是因为 `Python` 是人类公认的可读性最高的语言。

当前编译器 `TransPyC`（用 Python 实现）使用 Python 标准库 `ast` 解析源文件，将精力集中在语义翻译而非词法/语法分析上。但项目已在 [includes/ast](../includes/ast) 中实现**完全自研的 Python 解析器**（词法器 + 语法器 + AST 构造，约 4176 LOC，`AstTest` 综合测试通过），覆盖 CPython 几乎全部语法。这是向 `TransPyV`（用 Viper 重写编译器自身）完全自举的关键准备——自举后编译器将脱离 Python 运行时，解析、类型检查、IR 生成都由原生机器码完成。详见 [13-bootstrapping.md](13-bootstrapping.md)。

而实际上，下文中所有提到的 `C` 语言都是为了便于使用 `C` 的底层开发者理解。由于历史原因，在旧版本中我们使用 `C` 来作为中间语言，如同旧版本的 `C++` 一样，但现在 `target="c"` 的路径已经完全删除，`C` 路径永久不再受维护。

### 核心设计决策

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`"工具的关键架构
4. **`SHA1` 命名空间**：每个源文件按内容 `SHA1` 哈希命名，非导出函数和结构体自动加上 `SHA1` 前缀，从根本上消除跨模块的符号冲突
5. **万物皆数据**：为便于底层开发，实际上所有的变量和类型一般并不适用于鸭子类型，但在语义层面我们会尽可能贴近鸭子类型。

### 与 `Python` 的关系

| 特性 | `Python` | `Viper` |
|------|--------|-------|
| 类型系统 | 动态类型 | 静态类型（通过 `t` 模块注解，编译时确定） |
| 内存管理 | `GC` 自动回收 | 手动管理，无 `GC`，无运行时 |
| 运行时 | 解释执行 + 字节码 | 编译为原生机器码（通过 `LLVM`） |
| 对象模型 | 万物皆对象 | `@t.Object` 启用 OOP，`@t.CVTable` 为多态（vtable），`@t.NoVTable` 为非多态继承（C++ 风格，零运行时开销），普通 `class` 仅为结构体布局 |
| 标准库 | `Python` 标准库 | `Viper` 标准库（`includes/`）+ `ViperOS SDK` |
| 空值 | `None` | `None`（编译为 `NULL`/零值） |
| 模块系统 | 运行时 `import` | 编译时解析，插入 `.stub.ll`，`SHA1` 命名空间隔离 |

### 与 C 的关系

Viper 编译后的代码在底层等价于 `C` 编译后的机器码（都经过 LLVM），但 Viper 在语言层面提供了 C 所没有的能力：

- **SHA1 命名空间**：自动消除符号冲突，无需手动管理 `static`/命名前缀
- **类型组合语法**：`t.CConst | t.CInt | t.CPtr` 比 `const int*` 更具组合性
- **声明式内联汇编**：`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` 存根文件实现跨模块类型解析，无需头文件
- **更多内容**：额外更多的不依赖操作系统的函数和语法

## 两阶段编译流程

Viper 的编译由 `Projectrans.py` 驱动，分为两个阶段。这是 Viper 编译模型的核心，理解两阶段编译是理解 `t.CDefine`、`t.CExport`、`t.CInline` 等关键概念的前提。

```
源文件 (.py) ──────────────────────────────────────────────────────
    │                                                              │
    │  ┌─────────────── 阶段一：声明提取 ───────────────┐          │
    │  │                                               │          │
    │  │  1. 计算源文件 SHA1                            │          │
    │  │  2. 生成 <SHA1>.pyi（签名存根）                │          │
    │  │  3. 构建结构体注册表                           │          │
    │  │  4. 生成 <SHA1>.stub.ll（LLVM IR 声明）        │          │
    │  │                                                │          │
    │  └────────────────────────────────────────────────┘          │
    │                                                              │
    │  ┌─────────────── 阶段二：代码翻译 ───────────────┐          │
    │  │                                                │          │
    │  │  1. 加载所有 .pyi 和 .stub.ll                  │          │
    │  │  2. 构建共享符号表                             │          │
    │  │  3. 收集内联函数符号                           │          │
    │  │  4. 翻译源文件 → <SHA1>.ll（含代码）           │          │
    │  │  5. llc 编译 → <SHA1>.o（目标文件）            │          │
    │  │  6. ld.lld 链接 → 可执行文件                   │          │
    │  │                                                │          │
    │  └────────────────────────────────────────────────┘          │
    │                                                              │
    └──────────────────────────────────────────────────────────────┘
```

### 阶段一：声明提取（Phase1Generator）

阶段一的目标是从每个源文件提取**声明接口**，使其他模块在阶段二翻译时能够正确解析跨模块引用。

#### 步骤 1：可达文件发现与拓扑排序

从入口文件（`main.py` 或 `project.json` 指定）出发，通过 `import` 语句递归遍历，找出所有可达的 `.py` 源文件。然后对文件进行拓扑排序，确保被依赖的模块先被处理。

```
main.py → import serial → import drivers.serial.uart.serial → ...
```

#### 步骤 2：生成 `.pyi` 签名存根

对每个源文件，计算其内容的 SHA1 哈希（16位），然后调用 `PythonToStubConverter` 生成签名存根文件 `<SHA1>.pyi`。

**SHA1 计算方式**：
```python
sha1 = hashlib.sha1(content.encode('utf-8')).hexdigest()[:16]
```

**存根文件的内容规则**：

| 源文件元素 | 存根文件中的表示 |
|-----------|----------------|
| `t.CDefine` 常量 | 保留原样（含赋值）：`MAX_SIZE: t.CDefine = 1024` |
| `t.CDefine` 函数（返回 `t.CDefine` 的函数） | 保留完整函数体（宏展开需要） |
| 普通函数 | 仅保留签名，添加 `| c.State`：`def foo(x: t.CInt) -> t.CVoid | c.State: pass` |
| 类定义 | 保留成员类型注解和方法签名 |
| 全局变量 | 添加 `t.CExtern`：`x: t.CExtern | t.CInt` |
| `c.CIfdef`/`c.CEndif()` 等预处理指令 | 保留原样 |
| `import` 语句 | 保留原样 |

**关键设计**：`t.CDefine` 常量和 `t.CDefine` 函数在存根中**保留完整定义**（包括赋值和函数体），因为它们是编译时常量/宏，其他模块引用时需要完整展开。普通函数只保留签名并标记 `c.State`（仅声明），因为函数体在阶段二生成。


#### 步骤 3：构建结构体注册表

扫描所有已生成的 `.pyi` 文件，提取：
- **结构体名称集合**：所有非枚举、非异常的 `class` 定义
- **枚举名称集合**：继承 `t.CEnum` 的类
- **异常名称集合**：继承 `Exception` 的类
- **结构体→SHA1 映射**：记录每个结构体定义在哪个模块中

这个注册表用于阶段一的声明生成和阶段二的类型解析，确保跨模块的结构体引用使用正确的 SHA1 命名空间。

#### 步骤 4：生成 `.stub.ll` LLVM IR 声明

对每个 `.pyi` 文件，由 `DeclarationGenerator` 生成对应的 LLVM IR 声明文件 `<SHA1>.stub.ll`。

**声明生成规则**：

| 元素 | 生成的 LLVM IR |
|------|---------------|
| 普通函数 | `declare void @"<SHA1>.foo"(i32)` — 非 CExport 函数加 SHA1 前缀 |
| CExport 函数 | `declare i32 @main()` — CExport 函数不加前缀，全局可见 |
| 结构体 | `%"<SHA1>.ClassName" = type { i32, i32* }` — 类型名加 SHA1 前缀 |
| 全局变量 | `@varname = external global i32` |
| `t.CDefine` 常量 | **不生成声明**（编译时常量，直接内联展开） |
| `t.CTypedef` 别名 | **不生成声明**（类型别名，在类型解析时展开） |
| 枚举 | 为每个枚举成员生成 `@__config_EnumName_member = external global i32` |

**增量编译**：如果 `<SHA1>.pyi` 或 `<SHA1>.stub.ll` 已存在，则跳过生成（缓存命中）。只有源文件内容变化导致 SHA1 变化时才重新生成。

### 阶段二：代码翻译（Phase2Translator）

阶段二使用阶段一生成的声明接口，将源文件翻译为包含完整代码的 LLVM IR。

#### 步骤 1：加载声明接口

加载 `temp/` 目录中所有的 `.pyi` 和 `.stub.ll` 文件，构建：
- `sig_files`：SHA1 → `.pyi` 文件路径映射
- `stub_files`：SHA1 → `.stub.ll` 文件路径映射
- `sha1_map`：SHA1 → 源文件相对路径映射

#### 步骤 2：构建共享符号表

一次性构建所有文件共享的符号表数据，避免每个文件重复加载。将所有 `.pyi` 存根和 `.stub.ll` 声明中的类型信息注册到统一的 `SymbolTable` 中。

#### 步骤 3：收集内联函数符号

扫描 `includes/` 目录中被引用的模块，收集标记为 `t.CInline` 的函数。内联函数的完整 AST body 被保存，在翻译调用点时直接展开。值得注意的是，和 C 语言不同，`t.CInline` 不是优化建议，而是强制性的。

#### 步骤 4：翻译源文件

对每个源文件，使用 `TransPyC` 翻译器将 Python AST 翻译为 LLVM IR：
1. 将对应模块的 `.stub.ll` 声明嵌入到生成的 `.ll` 文件头部
2. 所有被引用模块的 `.stub.ll` 声明也被嵌入
3. 翻译 AST 节点为 LLVM IR 指令
4. 输出 `<SHA1>.ll` 文件

#### 步骤 5：编译与链接

- `llc`：将每个 `.ll` 编译为 `.o` 目标文件
- `ld.lld`：将所有 `.o` 链接为最终可执行文件

## SHA1 命名空间机制

SHA1 命名空间是 Viper 解决跨模块符号冲突的核心机制。

### 问题

在 C 语言中，多个模块可能定义同名函数或结构体，导致链接时符号冲突。C 的解决方案是 `static`（限制可见性）和命名前缀（手动避免冲突）。

### Viper 的解决方案

Viper 使用源文件内容的 SHA1 哈希作为命名空间前缀：

```
源文件 A.py（SHA1 = a1b2c3d4e5f6g7h8）中定义：
    class Point: x: t.CInt; y: t.CInt
    def draw(p: Point) -> t.CVoid: ...

源文件 B.py（SHA1 = 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"*)

    B.py 的结构体 → %"i9j0k1l2m3n4o5p6.Point" = type { float, float }
    B.py 的函数   → declare void @"i9j0k1l2m3n4o5p6.draw"(%"i9j0k1l2m3n4o5p6.Point"*)
```

**没有符号冲突**——即使两个模块定义了同名类型和函数，它们的 LLVM IR 符号也是不同的。即便有两个内容完全相同的文件也不会冲突，他们会被识别为同一个文件，然后进行单次编译。

### SHA1 前缀的豁免：`t.CExport`

标记为 `t.CExport` 的函数**不加 SHA1 前缀**，保持原始函数名。这是模块向外部暴露 API/ABI 的机制：

```python
# main.py — 入口函数，必须全局可见
def main() -> t.CInt | t.CExport:
    return 0

# 编译后：define i32 @main() — 无 SHA1 前缀
```

```python
# serial.py — 驱动接口，对外暴露
def init() -> t.CVoid | t.CExport:
    serial_puts("init\n")

# 编译后：define void @init() — 无 SHA1 前缀，其他模块可直接调用
```

### SHA1 与增量编译

SHA1 同时服务于增量编译：
- 源文件内容不变 → SHA1 不变 → `.pyi` 和 `.stub.ll` 缓存命中，跳过阶段一
- 源文件内容变化 → SHA1 变化 → 重新生成声明接口
- `temp/_sha1_map.txt` 记录 SHA1 → 源文件路径的映射，供阶段二加载

理论上，如果一个未经变动的模块的依赖路径上存在变动的模块，暂时的方法是将其简单做 SHA1 替换，暨将旧 SHA1 替换为新 SHA1，但不适用于签名改变的情况，不过签名改变这个未经变动的模块必然也需要改变，但是由于宏展开的存在，不能完全保证不会发生错误情况。且依赖路径的 SHA1 替换成功性未经完整测试，可能出现未定义行为。

## `t.CDefine` 深度解析

`t.CDefine` 是 Viper 中最特殊的类型——它不是数据类型，而是**编译时元指令**，控制编译器的代码生成行为。

### `t.CDefine` 常量

```python
MAX_SIZE: t.CDefine = 1024
PAGE_SIZE: t.CDefine = 4096
FA_READ: t.CDefine = 0x01
CPU_FEATURE_FPU: t.CDefine = (1 << 0)
```

**编译行为**：
1. **阶段一**：存根生成器保留 `t.CDefine` 常量的完整定义（包括赋值值），因为其他模块可能引用此常量
2. **阶段一**：`.stub.ll` 声明生成器**跳过** `t.CDefine` 常量，不生成 `external global` 声明
3. **阶段二**：翻译器遇到 `t.CDefine` 常量的引用时，直接内联其值，不生成任何加载指令

这意味着 `t.CDefine` 常量在 LLVM IR 层面**不存在**——它们在编译时被完全展开，等价于 C 的 `#define` 宏。

### `t.CDefine` 函数

当函数的返回类型注解包含 `t.CDefine` 时，该函数被编译器视为**宏函数**：

```python
def MAKE_FLAG(bit) -> t.CDefine:
    return (1 << bit)

def GET_PAGE_ORDER(size) -> t.CDefine:
    return size // PAGE_SIZE
```

**编译行为**：
1. **阶段一**：存根生成器保留 `t.CDefine` 函数的**完整函数体**（不像普通函数那样只保留签名）
2. **阶段二**：翻译器遇到 `t.CDefine` 函数的调用时，将调用内联展开为函数体的计算结果

`t.CDefine` 函数在 LLVM IR 中**不生成函数定义**——它们是纯粹的编译时宏。

### `t.CDefine` 与类型组合

`t.CDefine` 可以与具体类型组合使用，为常量指定底层类型：

```python
CODE_SEG: t.CDefine | t.CUInt16T = 0x08
DATA_SEG: t.CDefine | t.CUInt16T = 0x10
```

这表示常量在类型检查时被视为 `t.CUInt16T`（`uint16_t`），但在代码生成时仍然内联展开。

## `t.CExport` 深度解析

`t.CExport` 控制函数的符号可见性，是 SHA1 命名空间的豁免机制。

### 语义

| 修饰 | LLVM IR 函数名 | 链接可见性 | 用途 |
|------|---------------|-----------|------|
| 无修饰 | `@<SHA1>.funcname` | 模块内部 | 模块私有函数 |
| `t.CExport` | `@funcname` | 全局可见 | 对外暴露的 API |
| `t.CExtern` | `@funcname`（仅声明） | 外部定义 | 引用外部函数 |

### 使用场景

```python
# 入口函数 — 必须全局可见，链接器需要找到它
def _start() -> t.CInt | t.CExport:
    return kernel_main()

# 驱动接口 — 其他模块/应用需要调用
def init() -> t.CVoid | t.CExport:
    uart_init()

# 内部辅助函数 — 不需要全局可见，自动加 SHA1 前缀
def _helper() -> t.CInt:
    return 42
```

### `t.CExport` 在存根中的表现

在 `.pyi` 存根文件中，`t.CExport` 函数与普通函数一样只保留签名（添加 `c.State`，表示单纯声明），但 `t.CExport` 标记被保留在返回类型注解中。阶段一的 `DeclarationGenerator` 检查 `t.CExport` 标记来决定是否添加 SHA1 前缀。

## `t.CInline` 深度解析

`t.CInline` 标记函数为内联函数，编译器在调用点直接展开函数体。

### 语义

```python
def fast_add(a: t.CInt, b: t.CInt) -> t.CInt | t.CInline:
    return a + b
```

**编译行为**：
1. **阶段二预收集**：`_collect_inline_symbols` 扫描 `includes/` 目录中被引用的模块，收集所有 `t.CInline` 函数的 AST body
2. **翻译时展开**：遇到内联函数调用时，将函数体的 AST 直接嵌入调用点，替换参数为实际值
3. **不生成独立函数**：内联函数不生成 LLVM IR 函数定义（除非也被非内联调用）

### `t.CInline` 与 `t.CDefine` 函数的区别

| 特性 | `t.CInline` | `t.CDefine` 函数 |
|------|------------|-----------------|
| 展开时机 | 阶段二翻译时 | 阶段二翻译时 |
| 类型检查 | 完整的参数和返回类型检查 | 返回类型为宏，类型检查较弱 |
| 存根表示 | 签名 + `c.State` | 完整函数体 |
| LLVM IR | 可能生成函数定义（如果有非内联调用） | 不生成函数定义 |
| 适用场景 | 性能关键的短函数 | 编译时常量和宏计算 |

## 目标平台

默认目标三元组：`x86_64-none-elf`

数据布局：`e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128`

## Hello World 示例

```python
import t, c

def main() -> t.CInt | t.CExport:
    print("Hello, ViperOS!\n")
    return 0
```

编译流程：

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()` 函数体
3. **编译**：`llc` 将 `.ll` 编译为 `.o`
4. **链接**：`ld.lld` 链接为可执行文件
