fix(TPC/includes): 泛型特化 linkonce_odr + None 全局 common + includes 前缀
- TPC HandlesFunctions/HandlesClassDef: 泛型特化类方法(类名含 '[')设 linkonce_odr,消除 GSList[NameEntry].__before_init__ 等 duplicate symbol - TPC HandlesAssign: None 初值模块级全局用 common linkage,合并 _g_gen 等 跨模块同名全局重复定义 - includes: binascii/hashlib 裸调用加 stdlib./string. 前缀(malloc/free/memset/memcpy) - llvmlite: FunctionPrint 泛型特化方法输出 define linkonce_odr;__module 调整 - 删除 wiki/01-overview.md
This commit is contained in:
1 parent
b4567ffb9a
commit
8ccf5d2c18
10 files changed
+70
-384
No files matched your search
+11
-10
@@ -1,4 +1,5 @@
|
||||
import t, c
|
||||
import t, c
|
||||
import stdlib
|
||||
|
||||
|
||||
HEX_CHARS: t.CArray[t.CChar, None] = "0123456789abcdef"
|
||||
@@ -122,7 +123,7 @@ def crc_hqx(data: str, length: t.CInt, crc: t.CUnsignedShort) -> t.CUnsignedShor
|
||||
|
||||
def hexlify(data: str, length: t.CInt) -> str:
|
||||
if not data or length < 0: return None
|
||||
out: str = str(malloc(length * 2 + 1))
|
||||
out: str = str(stdlib.malloc(length * 2 + 1))
|
||||
if not out: return None
|
||||
oi: t.CInt = 0
|
||||
for i in range(length):
|
||||
@@ -140,14 +141,14 @@ def b2a_hex(data: str, length: t.CInt) -> str:
|
||||
def a2b_hex(hexstr: str, length: t.CInt) -> str:
|
||||
if not hexstr or length < 0 or length % 2 != 0: return None
|
||||
out_len: t.CInt = length // 2
|
||||
out: str = str(malloc(out_len + 1))
|
||||
out: str = str(stdlib.malloc(out_len + 1))
|
||||
if not out: return None
|
||||
oi: t.CInt = 0
|
||||
for i in range(0, length, 2):
|
||||
hi: t.CInt = HEX_VALS[t.CUnsignedChar(hexstr[i])]
|
||||
lo: t.CInt = HEX_VALS[t.CUnsignedChar(hexstr[i + 1])]
|
||||
if hi < 0 or lo < 0:
|
||||
free(out)
|
||||
stdlib.free(out)
|
||||
return None
|
||||
out[oi] = t.CChar((hi << 4) | lo)
|
||||
oi += 1
|
||||
@@ -163,7 +164,7 @@ def b2a_base64(data: str, length: t.CInt) -> str:
|
||||
if length % 3 != 0:
|
||||
olen += 4
|
||||
olen += 1
|
||||
out: str = str(malloc(olen + 1))
|
||||
out: str = str(stdlib.malloc(olen + 1))
|
||||
if not out: return None
|
||||
oi: t.CInt = 0
|
||||
for i in range(0, length, 3):
|
||||
@@ -198,7 +199,7 @@ def a2b_base64(data: str, length: t.CInt) -> str:
|
||||
olen -= 1
|
||||
if length >= 4 and data[length - 2] == '=':
|
||||
olen -= 1
|
||||
out: str = str(malloc(olen + 1))
|
||||
out: str = str(stdlib.malloc(olen + 1))
|
||||
if not out: return None
|
||||
oi: t.CInt = 0
|
||||
for i in range(0, length, 4):
|
||||
@@ -233,7 +234,7 @@ def a2b_base64(data: str, length: t.CInt) -> str:
|
||||
|
||||
def b2a_uu(data: str, length: t.CInt) -> str:
|
||||
if not data or length < 1 or length > 45: return None
|
||||
out: str = str(malloc(length * 2 + 2))
|
||||
out: str = str(stdlib.malloc(length * 2 + 2))
|
||||
if not out: return None
|
||||
oi: t.CInt = 0
|
||||
out[oi] = UU_CHARS[length]
|
||||
@@ -264,7 +265,7 @@ def a2b_uu(data: str) -> str:
|
||||
if not data or data[0] == ' ' or data[0] == '`': return None
|
||||
length: t.CInt = t.CInt(data[0]) - 32
|
||||
if length < 0 or length > 45: return None
|
||||
out: str = str(malloc(length + 1))
|
||||
out: str = str(stdlib.malloc(length + 1))
|
||||
if not out: return None
|
||||
oi: t.CInt = 0
|
||||
i: t.CInt = 1
|
||||
@@ -294,7 +295,7 @@ def a2b_uu(data: str) -> str:
|
||||
def b2a_hqx(data: str, length: t.CInt, crc: t.CUnsignedShort = 0) -> tuple[str, t.CUnsignedShort]:
|
||||
if not data or length != 3:
|
||||
return None, crc
|
||||
out: str = str(malloc(5))
|
||||
out: str = str(stdlib.malloc(5))
|
||||
if not out:
|
||||
return None, crc
|
||||
# 3字节 → 4字节 HQX
|
||||
@@ -324,7 +325,7 @@ def a2b_hqx(data: str, crc: t.CUnsignedShort = 0) -> tuple[str, t.CUnsignedShort
|
||||
m: t.CUnsignedInt = 0
|
||||
for i in range(4):
|
||||
m = (m << 6) | t.CUnsignedInt(HQX_VALS[t.CUnsignedChar(data[i])])
|
||||
out: str = str(malloc(4))
|
||||
out: str = str(stdlib.malloc(4))
|
||||
if not out:
|
||||
return None, crc
|
||||
out[0] = t.CChar((m >> 16) & 0xFF)
|
||||
|
||||
@@ -87,7 +87,7 @@ class sha1:
|
||||
if idx > 0:
|
||||
left: t.CSizeT = SHA1_BLOCK_LEN - idx
|
||||
if length >= left:
|
||||
memcpy(self.buf + idx, data, left)
|
||||
string.memcpy(self.buf + idx, data, left)
|
||||
self.transform(self.buf)
|
||||
data += left
|
||||
length -= left
|
||||
@@ -97,7 +97,7 @@ class sha1:
|
||||
data += SHA1_BLOCK_LEN
|
||||
length -= SHA1_BLOCK_LEN
|
||||
if length > 0:
|
||||
memcpy(self.buf + idx, data, length)
|
||||
string.memcpy(self.buf + idx, data, length)
|
||||
def final(self, out: t.CArray[t.CUInt8T, SHA1_DIGEST_LEN]):
|
||||
idx: t.CSizeT = self.count % SHA1_BLOCK_LEN
|
||||
self.buf[idx] = 0x80
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import t, c
|
||||
import t, c
|
||||
import string
|
||||
|
||||
|
||||
# ==============================================
|
||||
@@ -34,7 +35,7 @@ class sha256:
|
||||
count: t.CUInt64T
|
||||
buf: t.CArray[t.CUInt8T, SHA256_BLOCK_LEN]
|
||||
def __init__(self):
|
||||
memset(self, 0, sha256.__sizeof__())
|
||||
string.memset(self, 0, sha256.__sizeof__())
|
||||
self.state[0] = 0x6a09e667
|
||||
self.state[1] = 0xbb67ae85
|
||||
self.state[2] = 0x3c6ef372
|
||||
@@ -44,7 +45,7 @@ class sha256:
|
||||
self.state[6] = 0x1f83d9ab
|
||||
self.state[7] = 0x5be0cd19
|
||||
self.count = 0
|
||||
# memset(self.buf, 0, SHA256_BLOCK_LEN)
|
||||
# string.memset(self.buf, 0, SHA256_BLOCK_LEN)
|
||||
# SHA256 single-group compression transform
|
||||
def transform(self, block: t.CUInt8T | t.CPtr):
|
||||
w: t.CArray[t.CUInt32T, 64]
|
||||
@@ -91,7 +92,7 @@ class sha256:
|
||||
if idx > 0:
|
||||
left: t.CSizeT = SHA256_BLOCK_LEN - idx
|
||||
if length >= left:
|
||||
memcpy(self.buf + idx, data, left)
|
||||
string.memcpy(self.buf + idx, data, left)
|
||||
self.transform(self.buf)
|
||||
data += left
|
||||
length -= left
|
||||
@@ -101,16 +102,16 @@ class sha256:
|
||||
data += SHA256_BLOCK_LEN
|
||||
length -= SHA256_BLOCK_LEN
|
||||
if length > 0:
|
||||
memcpy(self.buf + idx, data, length)
|
||||
string.memcpy(self.buf + idx, data, length)
|
||||
def final(self, out: t.CArray[t.CUInt8T, SHA256_DIGEST_LEN]):
|
||||
idx: t.CSizeT = self.count % SHA256_BLOCK_LEN
|
||||
self.buf[idx] = 0x80
|
||||
idx += 1
|
||||
if idx > SHA256_BLOCK_LEN - 8:
|
||||
memset(self.buf + idx, 0, SHA256_BLOCK_LEN - idx)
|
||||
string.memset(self.buf + idx, 0, SHA256_BLOCK_LEN - idx)
|
||||
self.transform(self.buf)
|
||||
idx = 0
|
||||
memset(self.buf + idx, 0, SHA256_BLOCK_LEN - 8 - idx)
|
||||
string.memset(self.buf + idx, 0, SHA256_BLOCK_LEN - 8 - idx)
|
||||
bits: t.CUInt64T = self.count << 3
|
||||
for i in range(8):
|
||||
self.buf[SHA256_BLOCK_LEN-8+i] = (bits >> (56 - i*8)) & 0xFF
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import t, c
|
||||
import t, c
|
||||
import string
|
||||
|
||||
|
||||
# ==============================================
|
||||
@@ -45,7 +46,7 @@ class sha512:
|
||||
count: t.CArray[t.CUInt64T, 2]
|
||||
buf: t.CArray[t.CUInt8T, SHA512_BLOCK_LEN]
|
||||
def __init__(self):
|
||||
memset(self, 0, sha512.__sizeof__())
|
||||
string.memset(self, 0, sha512.__sizeof__())
|
||||
self.state[0] = 0x6a09e667f3bcc908
|
||||
self.state[1] = 0xbb67ae8584caa73b
|
||||
self.state[2] = 0x3c6ef372fe94f82b
|
||||
@@ -110,7 +111,7 @@ class sha512:
|
||||
if idx > 0:
|
||||
left: t.CSizeT = SHA512_BLOCK_LEN - idx
|
||||
if length >= left:
|
||||
memcpy(self.buf + idx, data, left)
|
||||
string.memcpy(self.buf + idx, data, left)
|
||||
self.transform(self.buf)
|
||||
data += left
|
||||
length -= left
|
||||
@@ -120,7 +121,7 @@ class sha512:
|
||||
data += SHA512_BLOCK_LEN
|
||||
length -= SHA512_BLOCK_LEN
|
||||
if length > 0:
|
||||
memcpy(self.buf + idx, data, length)
|
||||
string.memcpy(self.buf + idx, data, length)
|
||||
def final(self, out: t.CArray[t.CUInt8T, SHA512_DIGEST_LEN]):
|
||||
bits_lo: t.CUInt64T = self.count[0] << 3
|
||||
bits_hi: t.CUInt64T = (self.count[1] << 3) | (self.count[0] >> 61)
|
||||
@@ -128,10 +129,10 @@ class sha512:
|
||||
self.buf[idx] = 0x80
|
||||
idx += 1
|
||||
if idx > SHA512_BLOCK_LEN - 16:
|
||||
memset(self.buf + idx, 0, SHA512_BLOCK_LEN - idx)
|
||||
string.memset(self.buf + idx, 0, SHA512_BLOCK_LEN - idx)
|
||||
self.transform(self.buf)
|
||||
idx = 0
|
||||
memset(self.buf + idx, 0, SHA512_BLOCK_LEN - 16 - idx)
|
||||
string.memset(self.buf + idx, 0, SHA512_BLOCK_LEN - 16 - idx)
|
||||
for i in range(8):
|
||||
self.buf[SHA512_BLOCK_LEN - 16 + i] = t.CUInt8T(bits_hi >> (56 - i * 8))
|
||||
for i in range(8):
|
||||
|
||||
@@ -618,7 +618,13 @@ def FunctionPrint(buf: t.CChar | t.CPtr, size: t.CSizeT,
|
||||
if func.IsDeclared == 1:
|
||||
_append_cstr(buf, size, "declare ")
|
||||
else:
|
||||
_append_cstr(buf, size, "define ")
|
||||
# 泛型特化类方法(如 GSList[NameEntry].__before_init__):多个模块各自
|
||||
# 特化生成同名定义 → ld.lld duplicate symbol warning。特化方法代码由
|
||||
# 相同模板+类型参数生成,定义相同,输出 linkonce_odr 让链接器合并。
|
||||
if func.Name is not None and string.strchr(func.Name, 91) is not None:
|
||||
_append_cstr(buf, size, "define linkonce_odr ")
|
||||
else:
|
||||
_append_cstr(buf, size, "define ")
|
||||
ty_buf[0] = '\0'
|
||||
TypePrint(ty_buf, 128, func.RetTy, pool)
|
||||
_append_cstr(buf, size, ty_buf)
|
||||
|
||||
@@ -2,6 +2,7 @@ import t, c
|
||||
from stdint import *
|
||||
import string
|
||||
import stdio
|
||||
import stdlib
|
||||
import viperlib
|
||||
import memhub
|
||||
from linkedlist import GSList, GSListNode
|
||||
@@ -114,7 +115,16 @@ class LLVMModule:
|
||||
return
|
||||
# 去重检查(opaque 声明可被完整定义替换)
|
||||
cur: NamedTypeNode | t.CPtr = self.NamedTypeHead
|
||||
cnt_dedup: t.CInt = 0
|
||||
while cur is not None:
|
||||
# 步数守卫:NamedTypeHead 链表成环(内存损坏/插入 bug)会导致
|
||||
# 无限遍历挂起(G3 编译 LinkedNode.prepend 挂起根因)。
|
||||
# 正常链表节点数远小于此上限,超限即链表损坏 → 立即报错终止。
|
||||
cnt_dedup += 1
|
||||
if cnt_dedup > 100000:
|
||||
stdio.printf("[ANT-LOOP] NamedTypeHead 链表疑似成环, name=%s, 遍历节点=%d, 强制终止\n",
|
||||
name if name is not None else "(null)", cnt_dedup)
|
||||
stdlib.exit(1)
|
||||
if cur.Ty is not None:
|
||||
existing_name: t.CChar | t.CPtr = get_struct_name(cur.Ty)
|
||||
if existing_name is not None:
|
||||
@@ -158,7 +168,14 @@ class LLVMModule:
|
||||
if self is None or name is None:
|
||||
return 0
|
||||
cur: NamedTypeNode | t.CPtr = self.NamedTypeHead
|
||||
cnt_tdm: t.CInt = 0
|
||||
while cur is not None:
|
||||
# 步数守卫:同上,防 NamedTypeHead 链表成环无限遍历
|
||||
cnt_tdm += 1
|
||||
if cnt_tdm > 100000:
|
||||
stdio.printf("[TDM-LOOP] NamedTypeHead 链表疑似成环, name=%s, 强制终止\n",
|
||||
name if name is not None else "(null)")
|
||||
stdlib.exit(1)
|
||||
if cur.Ty is not None:
|
||||
existing_name: t.CChar | t.CPtr = get_struct_name(cur.Ty)
|
||||
if existing_name is not None:
|
||||
|
||||
@@ -1520,7 +1520,11 @@ class AssignHandle(BaseHandle):
|
||||
init_val: ir.Constant | None = self._eval_global_const(Node.value, VarType, Gen)
|
||||
if init_val:
|
||||
GlobalVar.initializer = init_val
|
||||
elif isinstance(VarType, ir.BaseStructType):
|
||||
elif isinstance(VarType, ir.BaseStructType) or (isinstance(Node.value, ast.Constant) and Node.value.value is None):
|
||||
# None 初值全局(如各 Handles 模块的 _g_gen: t.CVoid|t.CPtr = None):
|
||||
# 多模块同名定义用 common linkage 让链接器合并(避免
|
||||
# ld.lld duplicate symbol)。common 无 initializer,合并为零值,
|
||||
# 符合 None(=null) 语义。
|
||||
GlobalVar.linkage = 'common'
|
||||
else:
|
||||
GlobalVar.initializer = Gen._zero_value_for_type(VarType)
|
||||
|
||||
@@ -1379,6 +1379,12 @@ class ClassHandle(BaseHandle):
|
||||
func: ir.Function = existing_func
|
||||
else:
|
||||
func = Gen._get_or_declare_function(MangledName, FuncType)
|
||||
# 泛型特化类(ClassName 含 '[',如 GSList[NameEntry])的 __before_init__
|
||||
# 在多模块各自特化生成同名定义(external linkage)→ ld.lld duplicate
|
||||
# symbol warning。__before_init__ 由相同模板+类型参数合成,定义相同,
|
||||
# 设 linkonce_odr 让链接器合并重复定义。
|
||||
if '[' in ClassName:
|
||||
func.linkage = 'linkonce_odr'
|
||||
Gen.functions[NewFuncName] = func
|
||||
EntryBlock: ir.Block = func.append_basic_block(name="entry")
|
||||
saved_builder: ir.IRBuilder = Gen.builder
|
||||
|
||||
@@ -1551,9 +1551,16 @@ class FunctionHandle(BaseHandle):
|
||||
if MangledName == FuncName or FuncName not in Gen._export_extern_funcs:
|
||||
Gen.functions[FuncName] = func
|
||||
func.attributes.add('noredzone')
|
||||
# 泛型特化类方法(如 GSList[NameEntry].__before_init__):多个模块各自
|
||||
# 特化会生成同名定义(external linkage)→ ld.lld duplicate symbol warning。
|
||||
# 特化方法代码由相同模板+类型参数生成,定义相同,设 linkonce_odr 让
|
||||
# 链接器合并重复定义(linkonce_odr 的 alwaysinline 不适用,仅改 linkage)。
|
||||
is_spec_generic_method: bool = '[' in FuncName
|
||||
if IsInlineFunc:
|
||||
func.linkage = 'linkonce_odr'
|
||||
func.attributes.add('alwaysinline')
|
||||
if is_spec_generic_method:
|
||||
func.linkage = 'linkonce_odr'
|
||||
if hasattr(self, '_cdefine_funcs') and FuncName in self._cdefine_funcs:
|
||||
func.linkage = 'linkonce_odr'
|
||||
func.attributes.add('alwaysinline')
|
||||
|
||||
@@ -1,357 +0,0 @@
|
||||
# 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. **两阶段编译**:先从源文件提取声明接口(`.vpi` + `.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 泛型 + 递归泛型继承实现强类型自引用结构
|
||||
- **存根驱动的模块系统**:`.vpi` 存根文件实现跨模块类型解析,无需头文件
|
||||
- **更多内容**:额外更多的不依赖操作系统的函数和语法
|
||||
|
||||
## 两阶段编译流程
|
||||
|
||||
Viper 的编译由 `Projectrans.py` 驱动,分为两个阶段。这是 Viper 编译模型的核心,理解两阶段编译是理解 `t.CDefine`、`t.CExport`、`t.CInline` 等关键概念的前提。
|
||||
|
||||
```
|
||||
源文件 (.vp) ──────────────────────────────────────────────────────
|
||||
│ │
|
||||
│ ┌─────────────── 阶段一:声明提取 ───────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ 1. 计算源文件 SHA1 │ │
|
||||
│ │ 2. 生成 <SHA1>.vpi(签名存根) │ │
|
||||
│ │ 3. 构建结构体注册表 │ │
|
||||
│ │ 4. 生成 <SHA1>.stub.ll(LLVM IR 声明) │ │
|
||||
│ │ │ │
|
||||
│ └────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────── 阶段二:代码翻译 ───────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ 1. 加载所有 .vpi 和 .stub.ll │ │
|
||||
│ │ 2. 构建共享符号表 │ │
|
||||
│ │ 3. 收集内联函数符号 │ │
|
||||
│ │ 4. 翻译源文件 → <SHA1>.ll(含代码) │ │
|
||||
│ │ 5. llc 编译 → <SHA1>.o(目标文件) │ │
|
||||
│ │ 6. ld.lld 链接 → 可执行文件 │ │
|
||||
│ │ │ │
|
||||
│ └────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 阶段一:声明提取(Phase1Generator)
|
||||
|
||||
阶段一的目标是从每个源文件提取**声明接口**,使其他模块在阶段二翻译时能够正确解析跨模块引用。
|
||||
|
||||
#### 步骤 1:可达文件发现与拓扑排序
|
||||
|
||||
从入口文件(`main.vp` 或 `project.json` 指定)出发,通过 `import` 语句递归遍历,找出所有可达的 `.vp` 源文件。然后对文件进行拓扑排序,确保被依赖的模块先被处理。
|
||||
|
||||
```
|
||||
main.vp → import serial → import drivers.serial.uart.serial → ...
|
||||
```
|
||||
|
||||
#### 步骤 2:生成 `.vpi` 签名存根
|
||||
|
||||
对每个源文件,计算其内容的 SHA1 哈希(16位),然后调用 `PythonToStubConverter` 生成签名存根文件 `<SHA1>.vpi`。
|
||||
|
||||
**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:构建结构体注册表
|
||||
|
||||
扫描所有已生成的 `.vpi` 文件,提取:
|
||||
- **结构体名称集合**:所有非枚举、非异常的 `class` 定义
|
||||
- **枚举名称集合**:继承 `t.CEnum` 的类
|
||||
- **异常名称集合**:继承 `Exception` 的类
|
||||
- **结构体→SHA1 映射**:记录每个结构体定义在哪个模块中
|
||||
|
||||
这个注册表用于阶段一的声明生成和阶段二的类型解析,确保跨模块的结构体引用使用正确的 SHA1 命名空间。
|
||||
|
||||
#### 步骤 4:生成 `.stub.ll` LLVM IR 声明
|
||||
|
||||
对每个 `.vpi` 文件,由 `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>.vpi` 或 `<SHA1>.stub.ll` 已存在,则跳过生成(缓存命中)。只有源文件内容变化导致 SHA1 变化时才重新生成。
|
||||
|
||||
### 阶段二:代码翻译(Phase2Translator)
|
||||
|
||||
阶段二使用阶段一生成的声明接口,将源文件翻译为包含完整代码的 LLVM IR。
|
||||
|
||||
#### 步骤 1:加载声明接口
|
||||
|
||||
加载 `temp/` 目录中所有的 `.vpi` 和 `.stub.ll` 文件,构建:
|
||||
- `sig_files`:SHA1 → `.vpi` 文件路径映射
|
||||
- `stub_files`:SHA1 → `.stub.ll` 文件路径映射
|
||||
- `sha1_map`:SHA1 → 源文件相对路径映射
|
||||
|
||||
#### 步骤 2:构建共享符号表
|
||||
|
||||
一次性构建所有文件共享的符号表数据,避免每个文件重复加载。将所有 `.vpi` 存根和 `.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.vp(SHA1 = a1b2c3d4e5f6g7h8)中定义:
|
||||
class Point: x: t.CInt; y: t.CInt
|
||||
def draw(p: Point) -> t.CVoid: ...
|
||||
|
||||
源文件 B.vp(SHA1 = i9j0k1l2m3n4o5p6)中也定义:
|
||||
class Point: x: t.CFloat; y: t.CFloat # 不同的结构体!
|
||||
def draw(p: Point) -> t.CVoid: ...
|
||||
|
||||
编译后:
|
||||
A.vp 的结构体 → %"a1b2c3d4e5f6g7h8.Point" = type { i32, i32 }
|
||||
A.vp 的函数 → declare void @"a1b2c3d4e5f6g7h8.draw"(%"a1b2c3d4e5f6g7h8.Point"*)
|
||||
|
||||
B.vp 的结构体 → %"i9j0k1l2m3n4o5p6.Point" = type { float, float }
|
||||
B.vp 的函数 → declare void @"i9j0k1l2m3n4o5p6.draw"(%"i9j0k1l2m3n4o5p6.Point"*)
|
||||
```
|
||||
|
||||
**没有符号冲突**——即使两个模块定义了同名类型和函数,它们的 LLVM IR 符号也是不同的。即便有两个内容完全相同的文件也不会冲突,他们会被识别为同一个文件,然后进行单次编译。
|
||||
|
||||
### SHA1 前缀的豁免:`t.CExport`
|
||||
|
||||
标记为 `t.CExport` 的函数**不加 SHA1 前缀**,保持原始函数名。这是模块向外部暴露 API/ABI 的机制:
|
||||
|
||||
```python
|
||||
# main.vp — 入口函数,必须全局可见
|
||||
def main() -> t.CInt | t.CExport:
|
||||
return 0
|
||||
|
||||
# 编译后:define i32 @main() — 无 SHA1 前缀
|
||||
```
|
||||
|
||||
```python
|
||||
# serial.vp — 驱动接口,对外暴露
|
||||
def init() -> t.CVoid | t.CExport:
|
||||
serial_puts("init\n")
|
||||
|
||||
# 编译后:define void @init() — 无 SHA1 前缀,其他模块可直接调用
|
||||
```
|
||||
|
||||
### SHA1 与增量编译
|
||||
|
||||
SHA1 同时服务于增量编译:
|
||||
- 源文件内容不变 → SHA1 不变 → `.vpi` 和 `.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` 在存根中的表现
|
||||
|
||||
在 `.vpi` 存根文件中,`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.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` 链接为可执行文件
|
||||
Reference in new issue
Block a user