98 KiB
01 - 语言概述与编译流程
语言定位
Viper 是一种 系统级编程语言,其语法基于 Python,但编译目标为 LLVM IR,最终生成原生机器码。Viper 不是 Python 的超集或子集,也不是"C 的语法糖"——它是一门拥有独立类型系统、独立编译模型、独立模块体系的语言。Viper 选择 Python 语法作为表达形式,是因为 Python 的 AST 可被标准库直接解析,从而将编译器的精力集中在语义翻译而非词法/语法分析上。同时也是因为 Python 是人类公认的可读性最高的语言。
而实际上,下文中所有提到的 C 语言都是为了便于使用 C 的底层开发者理解。由于历史原因,在旧版本中我们使用 C 来作为中间语言,如同旧版本的 C++ 一样,但现在 target="c" 的路径已经完全删除,C 路径永久不再受维护。
核心设计决策
- Python 语法,LLVM 语义:源文件是完全合法的
Python语法,但通过t模块的类型注解系统赋予完全不同的语义 ——t.CInt不是Python的int,而是LLVM的i32 - 类型注解即编译指令:Viper 的类型注解不是可选的提示,而是编译器生成
LLVM IR的决定性依据。x: t.CInt生成i32,x: t.CInt | t.CPtr生成i32*,但无注解时会自动推导,详见下文。 - 两阶段编译:先从源文件提取声明接口(
.pyi+.stub.ll),再使用声明接口翻译源文件为含代码的.ll。这是Viper区别于简单"Python转C"工具的关键架构 SHA1命名空间:每个源文件按内容SHA1哈希命名,非导出函数和结构体自动加上SHA1前缀,从根本上消除跨模块的符号冲突- 万物皆数据:为便于底层开发,实际上所有的变量和类型一般并不适用于鸭子类型,但在语义层面我们会尽可能贴近鸭子类型。
与 Python 的关系
| 特性 | Python |
Viper |
|---|---|---|
| 类型系统 | 动态类型 | 静态类型(通过 t 模块注解,编译时确定) |
| 内存管理 | GC 自动回收 |
手动管理,无 GC,无运行时 |
| 运行时 | 解释执行 + 字节码 | 编译为原生机器码(通过 LLVM) |
| 对象模型 | 万物皆对象 | 仅 @t.Object 类有对象语义,@t.CVTable 为多态,普通 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 - 存根驱动的模块系统:
.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 计算方式:
sha1 = hashlib.sha1(content.encode('utf-8')).hexdigest()[:16]
存根文件的内容规则:
| 源文件元素 | 存根文件中的表示 |
|---|---|
t.CDefine 常量 |
保留原样(含赋值):MAX_SIZE: t.CDefine = 1024 |
t.CDefine 函数(返回 t.CDefine 的函数) |
保留完整函数体(宏展开需要) |
| 普通函数 | 仅保留签名,添加 ` |
| 类定义 | 保留成员类型注解和方法签名 |
| 全局变量 | 添加 t.CExtern:`x: t.CExtern |
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:
- 将对应模块的
.stub.ll声明嵌入到生成的.ll文件头部 - 所有被引用模块的
.stub.ll声明也被嵌入 - 翻译 AST 节点为 LLVM IR 指令
- 输出
<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 的机制:
# main.py — 入口函数,必须全局可见
def main() -> t.CInt | t.CExport:
return 0
# 编译后:define i32 @main() — 无 SHA1 前缀
# 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 常量
MAX_SIZE: t.CDefine = 1024
PAGE_SIZE: t.CDefine = 4096
FA_READ: t.CDefine = 0x01
CPU_FEATURE_FPU: t.CDefine = (1 << 0)
编译行为:
- 阶段一:存根生成器保留
t.CDefine常量的完整定义(包括赋值值),因为其他模块可能引用此常量 - 阶段一:
.stub.ll声明生成器跳过t.CDefine常量,不生成external global声明 - 阶段二:翻译器遇到
t.CDefine常量的引用时,直接内联其值,不生成任何加载指令
这意味着 t.CDefine 常量在 LLVM IR 层面不存在——它们在编译时被完全展开,等价于 C 的 #define 宏。
t.CDefine 函数
当函数的返回类型注解包含 t.CDefine 时,该函数被编译器视为宏函数:
def MAKE_FLAG(bit) -> t.CDefine:
return (1 << bit)
def GET_PAGE_ORDER(size) -> t.CDefine:
return size // PAGE_SIZE
编译行为:
- 阶段一:存根生成器保留
t.CDefine函数的完整函数体(不像普通函数那样只保留签名) - 阶段二:翻译器遇到
t.CDefine函数的调用时,将调用内联展开为函数体的计算结果
t.CDefine 函数在 LLVM IR 中不生成函数定义——它们是纯粹的编译时宏。
t.CDefine 与类型组合
t.CDefine 可以与具体类型组合使用,为常量指定底层类型:
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(仅声明) |
外部定义 | 引用外部函数 |
使用场景
# 入口函数 — 必须全局可见,链接器需要找到它
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 标记函数为内联函数,编译器在调用点直接展开函数体。
语义
def fast_add(a: t.CInt, b: t.CInt) -> t.CInt | t.CInline:
return a + b
编译行为:
- 阶段二预收集:
_collect_inline_symbols扫描includes/目录中被引用的模块,收集所有t.CInline函数的 AST body - 翻译时展开:遇到内联函数调用时,将函数体的 AST 直接嵌入调用点,替换参数为实际值
- 不生成独立函数:内联函数不生成 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 示例
import t, c
def main() -> t.CInt | t.CExport:
print("Hello, ViperOS!\n")
return 0
编译流程:
- 阶段一:计算
main.py的 SHA1,生成.pyi存根(def main() -> t.CInt | t.CExport | c.State: pass),生成.stub.ll声明(declare i32 @main(),因为是 CExport 不加前缀) - 阶段二:翻译
main.py为<SHA1>.ll,嵌入print对应的puts/printf声明,生成define i32 @main()函数体 - 编译:
llc将.ll编译为.o - 链接:
ld.lld链接为可执行文件
02 - 类型系统
Viper 的类型系统通过 t 模块提供,所有类型注解继承 t.CType 。大都以 t.C{TypeName:PascalCase}为类型,类型注解是 Viper 的核心机制,决定了变量的内存布局、大小和对齐方式。
基本类型
整数类型
| Viper 类型 | C 等价 | 大小(位) | 有符号 |
|---|---|---|---|
t.CChar |
char |
8 | 是 |
t.CShort |
short |
16 | 是 |
t.CInt |
int |
32 | 是 |
t.CLong |
long |
64 | 是 |
t.CUnsignedChar |
unsigned char |
8 | 否 |
t.CUnsignedShort |
unsigned short |
16 | 否 |
t.CUnsigned / t.CUnsignedInt |
unsigned int |
32 | 否 |
t.CUnsignedLong |
unsigned long |
64 | 否 |
t.CSignedChar |
signed char |
8 | 是 |
值得注意的是,t.CUnsigned 一般不单独使用,而是配合其它有符号类型,替换表示无符号,比如 t.CUnsigned | t.CInt,由于为了便于使用一些常用类型,则将其组合为常用的 t.CUnginedInt 等。
固定宽度整数类型
| Viper 类型 | C 等价 | 大小(位) | 有符号 |
|---|---|---|---|
t.CInt8T |
int8_t |
8 | 是 |
t.CInt16T |
int16_t |
16 | 是 |
t.CInt32T |
int32_t |
32 | 是 |
t.CInt64T |
int64_t |
64 | 是 |
t.CUInt8T |
uint8_t |
8 | 否 |
t.CUInt16T |
uint16_t |
16 | 否 |
t.CUInt32T |
uint32_t |
32 | 否 |
t.CUInt64T |
uint64_t |
64 | 否 |
最小宽度整数类型
| Viper 类型 | C 等价 | 大小(位) | 有符号 |
|---|---|---|---|
t.CIntLeast8T |
int_least8_t |
≥8 | 是 |
t.CIntLeast16T |
int_least16_t |
≥16 | 是 |
t.CIntLeast32T |
int_least32_t |
≥32 | 是 |
t.CIntLeast64T |
int_least64_t |
≥64 | 是 |
t.CUIntLeast8T |
uint_least8_t |
≥8 | 否 |
t.CUIntLeast16T |
uint_least16_t |
≥16 | 否 |
t.CUIntLeast32T |
uint_least32_t |
≥32 | 否 |
t.CUIntLeast64T |
uint_least64_t |
≥64 | 否 |
最快最小宽度整数类型
| Viper 类型 | C 等价 | 大小(位) | 有符号 |
|---|---|---|---|
t.CIntFast8T |
int_fast8_t |
≥8 | 是 |
t.CIntFast16T |
int_fast16_t |
≥16 | 是 |
t.CIntFast32T |
int_fast32_t |
≥32 | 是 |
t.CIntFast64T |
int_fast64_t |
≥64 | 是 |
t.CUIntFast8T |
uint_fast8_t |
≥8 | 否 |
t.CUIntFast16T |
uint_fast16_t |
≥16 | 否 |
t.CUIntFast32T |
uint_fast32_t |
≥32 | 否 |
t.CUIntFast64T |
uint_fast64_t |
≥64 | 否 |
最大宽度整数类型
| Viper 类型 | C 等价 | 大小(位) | 有符号 |
|---|---|---|---|
t.CIntMaxT |
intmax_t |
≥64 | 是 |
t.CUIntMaxT |
uintmax_t |
≥64 | 否 |
stdint 短名别名
通过 import stdint 引入,这些短名是上述类型的 t.CTypedef 别名,便于在系统编程场景中快速使用。允许直接使用 from stdint import * 来直接使用。
基本类型短名
| 短名 | 等价展开 | 说明 |
|---|---|---|
stdint.INT |
t.CInt |
- |
stdint.UINT |
t.CUnsignedInt |
- |
stdint.BOOL |
t.CInt |
1 为真,0 为假 |
stdint.SHORT |
t.CShort |
- |
stdint.USHORT |
t.CUnsignedShort |
- |
stdint.LONG |
t.CLong |
- |
stdint.ULONG |
t.CUnsignedLong |
- |
stdint.LONGLONG |
t.CLong | t.CLong |
- |
stdint.ULONGLONG |
t.CUnsignedLong | t.CLong |
- |
stdint.FLOAT |
t.CFloat |
- |
stdint.DOUBLE |
t.CDouble |
- |
固定宽度短名
| 短名 | 等价展开 |
|---|---|
stdint.INT8 |
t.CInt8T |
stdint.INT16 |
t.CInt16T |
stdint.INT32 |
t.CInt32T |
stdint.INT64 |
t.CInt64T |
stdint.UINT8 |
t.CUInt8T |
stdint.UINT16 |
t.CUInt16T |
stdint.UINT32 |
t.CUInt32T |
stdint.UINT64 |
t.CUInt64T |
字节与字短名
| 短名 | 等价展开 | 说明 |
|---|---|---|
stdint.BYTE |
t.CUnsignedChar |
8 位无符号 |
stdint.WORD |
t.CUInt16T |
16 位无符号 |
stdint.DWORD |
t.CUInt32T |
32 位无符号 |
stdint.QWORD |
t.CUInt64T |
64 位无符号 |
字符类型短名
| 短名 | 等价展开 | 说明 |
|---|---|---|
stdint.TCHAR |
t.CChar |
- |
stdint.CHARLIST |
str | t.CPtr / list[str, None] |
- |
stdint.WCHAR |
stdint.WORD |
UTF-16 代码单元 |
stdint.CHAR8 |
t.CChar8T |
- |
stdint.CHAR16 |
t.CChar16T |
- |
stdint.CHAR32 |
t.CChar32T |
- |
指针短名
| 短名 | 等价展开 |
|---|---|
stdint.INTPTR |
t.CInt | t.CPtr |
stdint.UINTPTR |
t.CUnsignedInt | t.CPtr |
stdint.BYTEPTR |
stdint.BYTE | t.CPtr |
stdint.SHORTPTR |
t.CShort | t.CPtr |
stdint.USHORTPTR |
t.CUnsignedShort | t.CPtr |
stdint.WCHARPTR |
stdint.WORD | t.CPtr |
stdint.VOIDPTR |
t.CVoid | t.CPtr |
stdint.INT8PTR |
t.CInt8T | t.CPtr |
stdint.INT16PTR |
t.CInt16T | t.CPtr |
stdint.INT32PTR |
t.CInt32T | t.CPtr |
stdint.INT64PTR |
t.CInt64T | t.CPtr |
stdint.UINT8PTR |
t.CUInt8T | t.CPtr |
stdint.UINT16PTR |
t.CUInt16T | t.CPtr |
stdint.UINT32PTR |
t.CUInt32T | t.CPtr |
stdint.UINT64PTR |
t.CUInt64T | t.CPtr |
stdint.CHAR8PTR |
t.CChar8T | t.CPtr |
stdint.CHAR16PTR |
t.CChar16T | t.CPtr |
stdint.CHAR32PTR |
t.CChar32T | t.CPtr |
平台特定短名
| 短名 | 等价展开 | 说明 |
|---|---|---|
stdint.FSIZE_t |
stdint.DWORD |
文件大小 |
stdint.LBA_t |
stdint.DWORD |
逻辑块地址 |
平台相关类型
| Viper 类型 | C 等价 | 大小(位) | 有符号 |
|---|---|---|---|
t.CSizeT |
size_t |
64 | 否 |
t.CIntPtrT |
intptr_t |
64 | 是 |
t.CUIntPtrT |
uintptr_t |
64 | 否 |
t.CPtrDiffT |
ptrdiff_t |
64 | 是 |
stdint 短名别名
Viper 在 stdint 模块中提供了更简短的类型别名,通过 t.CTypedef 映射到对应的完整类型。使用时需 import stdint。
固定宽度整数短名
| 短名 | 等价 Viper 类型 | C 等价 |
|---|---|---|
INT8 |
t.CInt8T |
int8_t |
INT16 |
t.CInt16T |
int16_t |
INT32 |
t.CInt32T |
int32_t |
INT64 |
t.CInt64T |
int64_t |
UINT8 |
t.CUInt8T |
uint8_t |
UINT16 |
t.CUInt16T |
uint16_t |
UINT32 |
t.CUInt32T |
uint32_t |
UINT64 |
t.CUInt64T |
uint64_t |
固定宽度指针短名
| 短名 | 等价 Viper 类型 | C 等价 |
|---|---|---|
INT8PTR |
t.CInt8T | t.CPtr |
int8_t* |
INT16PTR |
t.CInt16T | t.CPtr |
int16_t* |
INT32PTR |
t.CInt32T | t.CPtr |
int32_t* |
INT64PTR |
t.CInt64T | t.CPtr |
int64_t* |
UINT8PTR |
t.CUInt8T | t.CPtr |
uint8_t* |
UINT16PTR |
t.CUInt16T | t.CPtr |
uint16_t* |
UINT32PTR |
t.CUInt32T | t.CPtr |
uint32_t* |
UINT64PTR |
t.CUInt64T | t.CPtr |
uint64_t* |
字符类型短名
| 短名 | 等价 Viper 类型 | C 等价 |
|---|---|---|
CHAR8 |
t.CChar8T |
char8_t |
CHAR16 |
t.CChar16T |
char16_t |
CHAR32 |
t.CChar32T |
char32_t |
CHAR8PTR |
t.CChar8T | t.CPtr |
char8_t* |
CHAR16PTR |
t.CChar16T | t.CPtr |
char16_t* |
CHAR32PTR |
t.CChar32T | t.CPtr |
char32_t* |
基本类型短名
| 短名 | 等价 Viper 类型 | C 等价 |
|---|---|---|
INT |
t.CInt |
int |
UINT |
t.CUnsignedInt |
unsigned int |
BOOL |
t.CInt |
int(0/1) |
SHORT |
t.CShort |
short |
USHORT |
t.CUnsignedShort |
unsigned short |
LONG |
t.CLong |
long |
ULONG |
t.CUnsignedLong |
unsigned long |
LONGLONG |
t.CLong | t.CLong |
long long |
ULONGLONG |
t.CUnsignedLong | t.CLong |
unsigned long long |
FLOAT |
t.CFloat |
float |
DOUBLE |
t.CDouble |
double |
平台 / Windows 风格短名
| 短名 | 等价 Viper 类型 | C 等价 |
|---|---|---|
BYTE |
t.CUnsignedChar |
unsigned char(8位) |
BYTEPTR |
BYTE | t.CPtr |
unsigned char* |
WORD |
t.CUInt16T |
uint16_t(16位) |
DWORD |
t.CUInt32T |
uint32_t(32位) |
QWORD |
t.CUInt64T |
uint64_t(64位) |
INTPTR |
t.CInt | t.CPtr |
int* |
UINTPTR |
t.CUnsignedInt | t.CPtr |
unsigned int* |
SHORTPTR |
t.CShort | t.CPtr |
short* |
USHORTPTR |
t.CUnsignedShort | t.CPtr |
unsigned short* |
VOIDPTR |
t.CVoid | t.CPtr |
void* |
TCHAR |
t.CChar |
char |
WCHAR |
WORD |
uint16_t(UTF-16) |
WCHARPTR |
WORD | t.CPtr |
uint16_t*(UTF-16) |
CHARLIST |
str | t.CPtr |
char* |
FSIZE_t |
DWORD |
uint32_t |
LBA_t |
DWORD |
uint32_t |
浮点类型
| Viper 类型 | C 等价 | 大小(位) |
|---|---|---|
t.CFloat |
float |
32 |
t.CDouble |
double |
64 |
其他基本类型
| Viper 类型 | C 等价 | 说明 |
|---|---|---|
t.CVoid |
void |
空类型 |
t.CBool |
bool |
布尔类型(8位) |
t.CWCharT |
wchar_t |
宽字符(32位) |
t.CChar8T |
char8_t |
UTF-8 字符(8位,无符号) |
t.CChar16T |
char16_t |
UTF-16 字符(16位,无符号) |
t.CChar32T |
char32_t |
UTF-32 字符(32位,无符号) |
Python 便捷映射类型
| Python 类型 | Viper 类型 | Viper 值 |
|---|---|---|
int |
t.CInt |
- |
float |
t.CFloat |
- |
str |
t.CChar | t.CPtr |
- |
bool |
t.CBool |
- |
True |
t.CBool |
1 |
False |
t.CBool |
0 |
None |
NULL |
t.CIntPtr(0) |
对于 t.py 和 c.py 的引用,必须使用 import,不能使用 from ... import ...,也不能使用 as,因为这两者都是直接以字符串识别的。
类型组合
Viper 使用 Python 的 | 来组合类型修饰符(TypeUnion),其仅在左值注解中有效,其它位置均为位或语义(至少重载前是这样的),这是 Viper 最重要的语法特性之一。
指针类型
使用 t.CPtr 表示指针,通过 | 与基本类型组合:
x: t.CInt | t.CPtr # int* — 指向 int 的指针
p: t.CVoid | t.CPtr # void* — 通用指针
s: t.CChar | t.CPtr # char* — 字符串指针
pp: t.CInt | t.CPtr | t.CPtr # int** — 指向指针的指针
等价 C 代码:
int* x;
void* p;
char* s;
int** pp;
同时,指针也可以用 Ptr[T] 的方法表示,但在 Viper 中不太推荐,虽然说这样做一直是类似工程的常见用法,但是如果此处 T 是一个多态,同时我们想要复用 Python 的高亮插件,插件无法识别 Ptr[T] 的属性取值,但是对于 T | Ptr 可以。
x: t.CPtr[t.CInt]
p: t.CPtr[t.CVoid]
s: t.CPtr[t.CChar]
pp: t.CPtr[t.CPtr[t.CInt]]
pp2: t.CInt | t.CPtr[t.CPtr] # 同时也可以这么表示
等价 C 代码:
int* x;
void* p;
char* s:
int** pp;
int** pp2;
类型限定符组合
x: t.CConst | t.CInt # const int
p: t.CConst | t.CChar | t.CPtr # const char*
存储类组合
x: t.CStatic | t.CInt # static int
f: t.CExtern | t.CInt # extern int(声明其存在于外部)
函数返回类型组合
def foo() -> t.CInt | t.CExport: # 导出函数,返回 int
return 0
def bar() -> t.CVoid | t.CExtern: # 外部函数声明
pass
数组类型
使用 Python 的 list[ElementType, Size] 语法表示固定大小数组(通常的,这用于栈上和 BSS 段上):
buf: list[t.CChar, 64] # char buf[64]
ids: list[t.CInt, 10] # int ids[10]
matrix: list[t.CFloat, 16] # float matrix[16]
entries: list[idt_entry, 256] # struct idt_entry entries[256]
s: list[t.CChar, None] # 自推长度 char s[];
等价 C 代码:
char buf[64];
int ids[10];
float matrix[16];
struct idt_entry entries[256];
数组指针和函数指针
lp: list[t.CInt, 12] | t.CPtr
vp: t.Callable[[t.CInt, t.CInt], t.CInt] = x # 其中 t.Callable = typing.Callable,可以直接使用。
结构体类型
使用 class 定义结构体,成员通过类型注解声明:
class point:
x: t.CInt
y: t.CInt
等价 C 代码:
struct point {
int x;
int y;
};
带指针成员的结构体
class node:
value: t.CInt
next: node | t.CPtr # struct node* next
匿名结构体成员(❌)
WARNING: 警告: 由于 Viper 在声明结构体,枚举等处不需要关键字,因此
t.Anonymouse是不需要使用的。此关键字不再维护,强行使用可能导致未定义行为。
继承 t.Anonymous 的类作为匿名成员嵌入:
class header(t.Anonymous):
magic: t.CUInt32T
version: t.CUInt32T
class packet:
header # 匿名嵌入,可直接访问 packet.magic
length: t.CUInt32T
字节序标记
WARNING: 警告: 此关键字是实验性的。未经充分测试,不当使用可能导致未定义行为。
使用 t.BigEndian 或 t.LittleEndian 标记成员的字节序:
class network_packet:
length: t.CUInt16T | t.BigEndian # 大端序存储
type: t.CUInt8T # 默认小端序
联合体类型
继承 t.CUnion 定义联合体:
class value(t.CUnion):
ival: t.CInt
fval: t.CFloat
pval: t.CVoid | t.CPtr
等价 C 代码:
union value {
int ival;
float fval;
void* pval;
};
枚举类型
继承 t.CEnum 定义枚举:
class color(t.CEnum):
RED: t.CEnum = 0
GREEN: t.CEnum = 1
BLUE: t.CEnum = 2
等价 C 代码:
enum color {
RED = 0,
GREEN = 1,
BLUE = 2
};
枚举成员可直接使用名称:
c: t.CInt = color.RED
Typedef
使用 t.CTypedef 创建类型别名:
irq_handler_t: t.CTypedef | t.Callable[[], t.CInt] # typedef int (*irq_handler_t)();
或通过注解赋值:
MyStruct: t.CTypedef = original_struct # typedef struct original_struct MyStruct;
更推荐通过后者的形式,前者编译器不一定能够识别到注解是对 t.CTypedef 的,还是后面的注解的,而且容易触发未定义行为。
强制类型转换
在 Viper 中的强制类型转换有些不同,强制类型转换有两种方案,都是基于 t.CType 的用法,符合 Python 本意。
对于原先就继承自 t.CType 的类型来说,可以直接使用 call 的方法进行类型转换,比如
x: t.CChar | t.CPtr = "Hello"
t.CVoid(x, t.CPtr)
其中用法为 t.CType(x, ...),此处...可以是其它的 t.CType 或将要提到的 Typedef,将其视为组合类型对 x 进行强制转换。
额外补充一句,对于比较,四则运算,可以不使用强制类型转换,而是依靠隐式类型转换,但如果是严谨数学计算,推荐还是强制类型转换避免判断出错。
此外,若将一个不同类型的右值赋值到左值,则会隐式将右值转换为左值的类型。
如果你不喜欢 t.CVoid(x, t.CPtr) 的方法,认为这将 void 和 ptr 分开,不便于理解,那么还有一个方法,即将二者打包为一个 Typedef,即:
VOIDPTR: t.CTypedef = t.CVoid | t.CPtr
则如果你想,你可以直接通过 call 的方式进行强转,比如:
y: VOIDPTR = VOIDPTR(x)
宏定义常量
使用 t.CDefine 定义编译时常量。t.CDefine 不是数据类型,而是编译时元指令——标记为 t.CDefine 的常量在 LLVM IR 中不生成任何全局变量,而是在引用处直接内联展开。详见 01-overview.md 中 t.CDefine 深度解析。
MAX_SIZE: t.CDefine = 1024
PAGE_SIZE: t.CDefine = 4096
FA_READ: t.CDefine = 0x01
语义上等价于 C 的 #define:
#define MAX_SIZE 1024
#define PAGE_SIZE 4096
#define FA_READ 0x01
宏定义支持表达式:
CPU_FEATURE_FPU: t.CDefine = (1 << 0)
CPU_FEATURE_APIC: t.CDefine = (1 << 6)
宏定义也可与类型组合使用:
CODE_SEG: t.CDefine | t.CUInt16T = 0x08
位域
使用 t.Bit(n) 定义位域成员:
class flags:
a: t.CUInt32T | t.Bit[1] # 此处,如果存入 2,二进制是 10,会被截断为 0
b: t.CUInt32T | t.Bit[3]
c: t.CUInt32T | t.Bit[4]
LLVM IR 类型简写
Viper 提供了 LLVM IR 级别的类型简写:
i1 # 1位整数
i8 # 8位有符号整数
i16 # 16位有符号整数
i32 # 32位有符号整数
i64 # 64位有符号整数
u1 # 1位无符号整数
u8 # 8位无符号整数
u16 # 16位无符号整数
u32 # 32位无符号整数
u64 # 64位无符号整数
仍然通过 t.x 的方式调用,比如 t.i1 等。
__attribute__ 属性
通过 t.attr 命名空间访问 GCC __attribute__ 属性:
t.attr.packed() # __attribute__((packed))
t.attr.aligned(16) # __attribute__((aligned(16)))
t.attr.section(".text.startup") # __attribute__((section(".text.startup")))
t.attr.always_inline() # __attribute__((always_inline))
t.attr.noinline() # __attribute__((noinline))
t.attr.noreturn() # __attribute__((noreturn))
t.attr.weak() # __attribute__((weak))
t.attr.unused() # __attribute__((unused))
t.attr.constructor() # __attribute__((constructor))
t.attr.destructor() # __attribute__((destructor))
t.attr.deprecated("use X") # __attribute__((deprecated("use X")))
LLVM 函数属性
通过 t.attr.llvm 命名空间指定 LLVM 函数属性:
t.attr.llvm.nobuiltin
t.attr.llvm.nounwind
t.attr.llvm.noredzone
t.attr.llvm.willreturn
t.attr.llvm.mustprogress
t.attr.llvm.optnone
t.attr.llvm.noinline
t.attr.llvm.alwaysinline
t.attr.llvm.readnone
t.attr.llvm.readonly
t.attr.llvm.writeonly
t.attr.llvm.inaccessiblememonly
t.attr.llvm.inaccessiblemem_or_argmemonly
在函数返回类型注解中使用:
def foo() -> t.CInt | t.attr.llvm.nobuiltin | t.attr.llvm.nounwind:
return 0
03 - 变量声明与赋值
Viper 的变量声明使用 Python 的类型注解语法,通过 t 模块指定 C 级别的类型信息。
带注解的变量声明
使用 变量名: 类型 语法声明变量:
x: t.CInt = 0 # int x = 0;
y: t.CUInt32T = 42 # uint32_t y = 42;
name: list[t.CChar, 64] # char name[64];
ptr: t.CVoid | t.CPtr # void* ptr;
msg: t.CConst | str = "Hello" # const char* msg = "Hello";
无初始值的声明
未赋值的变量声明会生成零初始化的变量,此处是未定义行为,即在不同环境位置可能会覆盖0,也可能不会,最好预先复制或用 memset 清除:
count: t.CInt # int count = 0;
buffer: list[t.CChar, 256] # char buffer[256] = {0};
延迟赋值
变量可以先声明后赋值:
x: t.CInt
x = 10
指针变量
p: t.CInt | t.CPtr # int* p;
fb: t.CVoid | t.CPtr # void* fb;
str_ptr: t.CConst | t.CChar | t.CPtr # const char* str_ptr;
指针变量的特殊初始化
使用 t.CVoid(0, t.CPtr) 初始化指针为 NULL:
p: t.CVoid | t.CPtr = t.CVoid(0, t.CPtr) # void* p = NULL;
或使用 None:
p: t.CVoid | t.CPtr = None # void* p = NULL;
普通赋值
x: t.CInt = 0
x = 42 # x = 42;
增强赋值
支持所有 Python 增强赋值运算符:
x: t.CInt = 0
x += 1 # x += 1;
x -= 1 # x -= 1;
x *= 2 # x *= 2;
x //= 3 # x /= 3; (整数除法)
x %= 5 # x %= 5;
x <<= 1 # x <<= 1;
x >>= 1 # x >>= 1;
x |= 0xFF # x |= 0xFF;
x &= 0x0F # x &= 0x0F;
x ^= 0xAA # x ^= 0xAA;
全局变量
模块顶层声明的变量即为全局变量:
kbd_tid: t.CInt = -1 # 全局 int kbd_tid = -1;
BootInfo: bootinfo | t.CPtr # 全局 struct bootinfo* BootInfo;
静态全局变量
x: t.CStatic | t.CInt = 0 # static int x = 0;
外部声明
x: t.CExtern | t.CInt # extern int x;
常量定义
使用 t.CDefine 定义编译时常量。t.CDefine 不是数据类型,而是编译时元指令——常量在 LLVM IR 中不生成全局变量,而是在引用处直接内联展开。详见 01-overview.md 中 t.CDefine 深度解析。
MAX_SIZE: t.CDefine = 1024
PAGE_SIZE: t.CDefine = 4096
FA_READ: t.CDefine = 0x01
FA_WRITE: t.CDefine = 0x02
字符串常量
Viper 中的字符串字面量编译为 C 的字符串常量(const char*):
msg: t.CConst | str = "Hello, World!"
serial.puts(msg)
也可以直接传递字符串字面量:
serial.puts("Hello, World!")
类型转换
使用类型构造函数进行显式类型转换:
x: t.CInt = 42
y: t.CUInt32T = t.CUInt32T(x) # (uint32_t)x
f: t.CFloat = float(x) # (float)x
i: t.CInt = int(f) # (int)f
指针类型转换
ptr: t.CVoid | t.CPtr = some_ptr
int_val: t.CUInt64T = t.CUInt64T(ptr) # (uint64_t)ptr — 指针转整数
结构体实例化
此处不理解参见 (05-classes.md 中 结构体实例化与初始化)
直接使用类名作为构造函数:
info: fat32_types.fat32_fileinfo
dp: fat32_types.fat32_dirobj
带初始化的实例化:
obj: MyClass = MyClass(arg1, arg2)
使用 c.Addr 获取实例指针:
obj_ptr: MyClass | t.CPtr = c.Addr(MyClass(arg1, arg2))
delete 语句
del 语句可用于调用对象的 __del__ 或 __delete__ 方法:
del obj # 调用 obj.__del__() 或 obj.__delete__()
del arr[idx] # 调用 arr.__delitem__(idx)
04 - 函数定义与调用
Viper 的函数定义使用 Python 的 def 语法,通过类型注解指定参数类型和返回类型。
基本函数定义
def add(a: t.CInt, b: t.CInt) -> t.CInt:
return a + b
等价 C 代码:
int add(int a, int b) {
return a + b;
}
无返回值函数
def print_msg(msg: t.CConst | t.CChar | t.CPtr) -> t.CVoid:
serial.puts(msg)
等价 C 代码:
void print_msg(const char* msg) {
serial_puts(msg);
}
函数修饰符
通过返回类型的 | 组合添加函数修饰符:
导出函数
t.CExport 控制函数的符号可见性——标记为 t.CExport 的函数不加 SHA1 前缀,全局可见。详见 01-overview.md 中 t.CExport 深度解析。
def _start() -> t.CInt | t.CExport:
return 0
外部函数声明
使用 c.State 表示仅声明不定义:
def isr0() -> t.CExtern | t.CVoid | c.State: pass
def isr1() -> t.CExtern | t.CVoid | c.State: pass
等价 C 代码:
extern void isr0();
extern void isr1();
内联函数
t.CInline 标记函数为内联函数,编译器在调用点直接展开函数体。详见 01-overview.md 中 t.CInline 深度解析。
def fast_add(a: t.CInt, b: t.CInt) -> t.CInt | t.CInline:
return a + b
宏函数
当函数返回类型注解包含 t.CDefine 时,该函数被视为宏函数,不生成 LLVM IR 函数定义,在调用处内联展开。详见 01-overview.md 中 t.CDefine 函数。
def MAKE_FLAG(bit) -> t.CDefine:
return (1 << bit)
静态函数
def helper() -> t.CVoid | t.CStatic:
pass
函数属性装饰器
使用 @c.Attribute 添加 GCC __attribute__ 属性:
@c.Attribute(t.attr.section(".text.startup"), t.attr.aligned(16))
def _start() -> t.CInt:
return 0
等价 C 代码:
__attribute__((section(".text.startup"), aligned(16)))
int _start() {
return 0;
}
常用属性组合
@c.Attribute(t.attr.always_inline())
def hot_path() -> t.CInt:
return 0
@c.Attribute(t.attr.noreturn())
def panic(msg: t.CConst | t.CChar | t.CPtr) -> t.CVoid:
serial.puts(msg)
while True: pass
@c.Attribute(t.attr.packed)
LLVM 函数属性
在返回类型注解中通过 t.attr.llvm 指定 LLVM 属性:
def foo() -> t.CInt | t.attr.llvm.nobuiltin | t.attr.llvm.nounwind:
return 0
多返回值(CReturn)
使用 @c.CReturn 装饰器实现多返回值,通过指针参数实现:
@c.CReturn(t.CInt, t.CInt)
def divmod(a: t.CInt, b: t.CInt) -> t.CVoid:
q: t.CInt = a // b
r: t.CInt = a % b
return q, r
规则:
CReturn中的类型数量决定返回值个数- 自动为每个返回类型添加指针参数(
t.CInt→t.CInt | t.CPtr) - 参数名自动生成为
__ReturnValue0__、__ReturnValue1__等 - 调用处自动传参
&xxx - 必须使用左右值赋值,返回值不能直接传入函数。
指针参数
def read_data(buf: t.CChar | t.CPtr, size: t.CSizeT) -> t.CInt:
pass
等价 C 代码:
int read_data(char* buf, size_t size);
输出参数模式
使用 c.Addr 传递变量地址作为输出参数:
res: t.CInt = fat32.opendir("/", c.Addr(dp))
等价 C 代码:
int res = fat32_opendir("/", &dp);
后续会使用隐式左值获取来减少 c.Addr 的直接使用。
函数指针类型
使用 t.Callable 定义函数指针类型:
irq_handler_t: t.CTypedef | t.Callable[[], t.CInt]
等价 C 代码:
typedef int (*irq_handler_t)();
带参数的函数指针:
callback_t: t.Callable[[t.CInt, t.CInt], t.CVoid]
默认参数
Viper 支持函数默认参数值:
def create_window(x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt, title: t.CConst | str = "Window") -> t.CInt:
pass
return 语句
return 0 # return 0;
return # return; (void 函数)
return a, b # 多返回值(需配合 @c.CReturn)
全局变量声明
函数外的变量声明为全局变量,函数内通过 global 关键字访问:
BootInfo: bootinfo | t.CPtr
def init() -> t.CVoid:
global BootInfo
BootInfo = saved_rdi
支持 func-in-func 嵌套函数。
Call Func 函数调用
函数调用最好通过顺序调用来实现
def add(x: int, y: int, z: int):
return (x + y) * z
def main() -> int | t.CExport:
print(add(1, 2, 3)) # (1 + 2) * 3 = 9
这也是 C 中的标准实现方法,但实际上,你也可以乱序传参。
# 仍然用刚才的 add 举例
def main() -> int | t.CExport:
print(add(z=3, x=1, y=2)) # (1 + 2) * 3 = 9
乱序传参或带参传参对于结构体的初始化同样有效。同时,你也能够给函数指定默认值。
def add(x: int, y: int, z: int = 1):
return (x + y) * z
def main() -> int | t.CExport:
print(add(1, 2)) # (1 + 2) * 1 = 3
在结构体中也可以指定默认值。
05 - 类与数据布局
Viper 中的 class 有两种用途:定义数据布局(结构体/联合体/枚举,本章内容),以及定义面向对象的对象类型(见 06-oop.md)。
结构体定义
普通 class 定义编译为 LLVM 结构体类型:
class point:
x: t.CInt
y: t.CInt
在 C 中:
struct point {
int x;
int y;
};
在 LLVM IR 中:
%"<SHA1>.point" = type { i32, i32 }
带属性的结构体
@c.Attribute(t.attr.packed)
class idt_entry:
base_low: t.CUInt16T
selector: t.CUInt16T
ist_attr: t.CUInt8T
type_attr: t.CUInt8T
base_middle: t.CUInt16T
base_high: t.CUInt32T
reserved1: t.CUInt32T
结构体数组
idt: list[idt_entry, 256] = []
结构体指针成员
class node:
value: t.CInt
next: node | t.CPtr # 指向自身的指针(有向图链表)
结构体实例化与初始化
entry: idt_entry = idt_entry()
或带构造函数:
mousepoint = Point(x=1, y=2)
乱序传参或带参传参对于结构体的初始化同样有效:
entry: idt_entry = idt_entry(selector=0x08, base_low=0x0000)
也可以给结构体成员指定默认值:
class config:
width: t.CInt = 640
height: t.CInt = 480
depth: t.CInt = 32
使用 c.Addr 获取实例指针:
obj_ptr: MyClass | t.CPtr = c.Addr(MyClass(arg1, arg2))
匿名结构体成员(❌)
WARNING: 警告: 由于 Viper 在声明结构体,枚举等处不需要关键字,因此
t.Anonymouse是不需要使用的。此关键字不再维护,强行使用可能导致未定义行为。
继承 t.Anonymous 的类作为匿名成员嵌入:
class header(t.Anonymous):
magic: t.CUInt32T
version: t.CUInt32T
class packet:
header # 匿名嵌入,可直接访问 packet.magic
length: t.CUInt32T
字节序标记
WARNING: 警告: 此关键字是实验性的。未经充分测试,不当使用可能导致未定义行为。
使用 t.BigEndian 或 t.LittleEndian 标记成员的字节序:
class network_packet:
length: t.CUInt16T | t.BigEndian # 大端序存储
type: t.CUInt8T # 默认小端序
联合体
继承 t.CUnion 定义联合体:
class value(t.CUnion):
ival: t.CInt
fval: t.CFloat
pval: t.CVoid | t.CPtr
等价 C 代码:
union value {
int ival;
float fval;
void* pval;
};
枚举
继承 t.CEnum 定义枚举:
class color(t.CEnum):
RED: t.CEnum = 0
GREEN: t.CEnum = 1
BLUE: t.CEnum = 2
等价 C 代码:
enum color {
RED = 0,
GREEN = 1,
BLUE = 2
};
枚举成员可直接使用名称:
c: t.CInt = color.RED
Typedef
使用 t.CTypedef 创建类型别名:
irq_handler_t: t.CTypedef | t.Callable[[], t.CInt] # typedef int (*irq_handler_t)();
或通过注解赋值:
MyStruct: t.CTypedef = original_struct # typedef struct original_struct MyStruct;
更推荐通过后者的形式,前者编译器不一定能够识别到注解是对 t.CTypedef 的,还是后面的注解的,而且容易触发未定义行为。
位域
使用 t.Bit(n) 定义位域成员:
class flags:
a: t.CUInt32T | t.Bit[1]
b: t.CUInt32T | t.Bit[3]
c: t.CUInt32T | t.Bit[4]
注意:如果存入超出位域宽度的值,会被截断。例如 a 为 1 位,存入 2(二进制 10)会被截断为 0。
06 - 面向对象与运算符重载
Viper 中的 class 有两种语义:数据布局(结构体/联合体/枚举,见 05-classes.md)和面向对象。面向对象特性通过 @t.Object 装饰器启用,多态通过 @t.CVTable 装饰器启用。
@t.Object 面向对象
使用 @t.Object 装饰器启用面向对象特性,类将拥有成员方法、运算符重载、属性装饰器等 OOP 支持。编译器也会自动识别:如果结构体内有函数定义,会自动使用 @t.Object 行为,但在实际工程中更推荐明确标记,避免未定义行为。
基本定义
@t.Object
class ViperKernel:
BootInfo: bootinfo | t.CPtr
VGA: vga._VGAScreenDriver | t.CPtr
VGA_drv: vga._VGAScreenDriver
def __init__(self):
self.BootInfo = BootInfo
serial.init()
serial.puts("ViperOS VKernel Test\n")
避免在 __init__ 中使用 return,虽然不会报错,但是你永远无法获得 return 的结果,如果需要提前结束代码流,return 也不失为一种办法。
成员方法
@t.Object
class Sheet:
id: t.CInt
x: t.CInt
y: t.CInt
w: t.CInt
h: t.CInt
def __init__(self, x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt):
self.x = x
self.y = y
self.w = w
self.h = h
def MoveTo(self, nx: t.CInt, ny: t.CInt):
self.x = nx
self.y = ny
def Resize(self, nw: t.CInt, nh: t.CInt):
self.w = nw
self.h = nh
方法调用
sheet: Sheet = Sheet(0, 0, 640, 480)
sheet.MoveTo(100, 100)
sheet.Resize(800, 600)
属性装饰器
支持 Python 的 @property 和 @xxx.setter 装饰器:
@xxx.setter和自定义装饰器正在试验阶段,尽可能不要使用
@t.Object
class MyObj:
_value: t.CInt
@property
def value(self) -> t.CInt:
return self._value
@value.setter
def value(self, v: t.CInt):
self._value = v
静态方法
@staticmethod
def _yield():
pass
对象指针与解引用
@t.Object 的实例通常通过指针操作:
obj_ptr: MyClass | t.CPtr = c.Addr(MyClass())
obj: MyClass = c.Deref(obj_ptr)
成员访问
通过 self 访问成员变量和方法:
def method(self) -> t.CVoid:
self.x = 10
y: t.CInt = self.y
self.DoSomething()
内嵌对象
@t.Object 可以包含其他对象实例(非指针):
@t.Object
class Sheet:
_surf_obj: gfx.Surface
_renderer_obj: gfx.Renderer
surf: gfx.Surface | t.CPtr
renderer: gfx.Renderer | t.CPtr
def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, w: t.CInt, h: t.CInt):
self._surf_obj = gfx.Surface(fb, zb, w, h)
self.surf = c.Addr(self._surf_obj)
self._renderer_obj = gfx.Renderer(self.surf)
self.renderer = c.Addr(self._renderer_obj)
__before_init__ 方法
@t.Object 或 @t.CVTable 类自动生成 __before_init__ 方法声明,用于在 __init__ 之前初始化 vtable:
declare void @"<SHA1>.ClassName.__before_init__"(%"<SHA1>.ClassName"*)
值得注意的是,__before_init__ 并不帮忙初始化内存,而只是初始化虚表等准备工作,实际上,我们更希望用户能自己管理内存。实验性阶段内容中,我们通过内存池机制来帮助用户管理碎片化内容,包括部分小的结构体,但较大内容最好还是由用户手动处理。
@t.CVTable 虚函数表与多态
尽可能避免使用
self[0]来获取结构体成员,其不稳定,也不要手动操作虚表。
使用 @t.CVTable 装饰器可以启用虚函数表支持。启用后,结构体第一个成员自动插入 i8* 类型的 vtable 指针。@t.CVTable 的操作必须配合 @t.Object,但有时编译器会自动识别是否存在结构体内函数,自动使用 @t.Object。
基本定义
@t.Object
@t.CVTable
class Base:
def __init__(self):
pass
def method(self) -> t.CInt:
return 0
在 LLVM IR 中,@t.CVTable 类的结构体类型自动插入 vtable 指针:
%"<SHA1>.Base" = type { i8*, i32 }
同时,编译器会生成全局 vtable 变量:
@"<SHA1>.Base_Vtable" = internal global [1 x i8*] zeroinitializer
继承与多态
虚表可以帮你使用继承语法实现多态:
@t.Object
@t.CVTable
class Base:
def __init__(self):
pass
def method(self) -> t.CInt:
return 0
@t.Object
@t.CVTable
class M1(Base):
def method(self) -> t.CInt:
return 1
当 M1 被初始化时,VTable 会自动修改,这点和 C++ 相同,尽可能避免直接操作 VTable。
继承时,子类会自动继承父类的成员变量和方法。子类重写父类方法时,vtable 中对应位置的函数指针被子类方法替换,实现多态。
VTable 运行时修改
如果需要临时构造一个额外的 Base 结构体,还可以使用:
@t.Object
@t.CVTable
class Base:
def __init__(self):
pass
def method(self) -> t.CInt:
return 0
def __method1(self) -> t.CInt:
return 1
def main() -> t.CInt | t.CExport:
b: Base = Base()
b.method = __method1
启用 VTable 的表中,允许直接修改内部函数为函数指针,在底层,此操作将修改 VTable 来达成目的。编译器会检测当前 vtable 是否与类 vtable 相同,如果相同则创建 vtable 的副本(memcpy),避免修改影响所有实例。
虚方法调度机制
虚方法调用的核心流程:
- 从对象指针 GEP 获取第 0 个 slot(vtable 指针)
- 加载 vtable 指针
- bitcast 为
[N x i8*]*类型 - GEP 获取方法索引处的函数指针
- 加载函数指针
- bitcast 为正确的函数指针类型
- 间接调用
继承
结构体成员继承
当子类继承父类(父类在 Gen.class_members 中存在)时:
- 父类的成员变量被继承到子类(排在子类成员之前)
- 父类的默认值也被继承
- 父类的方法被继承(重命名为
子类名.方法名)
@t.Object
class Animal:
name: list[t.CChar, 32]
age: t.CInt
def __init__(self, age: t.CInt):
self.age = age
def Speak(self) -> t.CVoid:
serial.puts("...\n")
@t.Object
class Dog(Animal):
breed: t.CInt
def Speak(self) -> t.CVoid:
serial.puts("Woof!\n")
异常类继承
Viper 支持异常类的继承,用于 try/except 的异常匹配:
class MyError(Exception):
pass
class SpecificError(MyError):
pass
详见 09-exceptions.md。
运算符重载
@t.Object 类支持运算符重载。编译器在遇到运算符时,首先检查左操作数是否为结构体类型,如果是则尝试调用对应的 dunder 方法。
算术运算符重载
| 运算符 | Dunder 方法 | 示例 |
|---|---|---|
+ |
__add__ |
a + b → a.__add__(b) |
- |
__sub__ |
a - b → a.__sub__(b) |
* |
__mul__ |
a * b → a.__mul__(b) |
/ |
__truediv__ |
a / b → a.__truediv__(b) |
// |
__floordiv__ |
a // b → a.__floordiv__(b) |
% |
__mod__ |
a % b → a.__mod__(b) |
** |
__pow__ |
a ** b → a.__pow__(b) |
示例:
@t.Object
class Vec2:
x: t.CFloat
y: t.CFloat
def __init__(self, x: t.CFloat, y: t.CFloat):
self.x = x
self.y = y
def __add__(self, other: Vec2) -> Vec2:
result: Vec2 = Vec2(0.0, 0.0)
result.x = self.x + other.x
result.y = self.y + other.y
return result
def __mul__(self, scalar: t.CFloat) -> Vec2:
result: Vec2 = Vec2(0.0, 0.0)
result.x = self.x * scalar
result.y = self.y * scalar
return result
增量赋值运算符重载
| 增量运算符 | 优先 Dunder | 回退 Dunder |
|---|---|---|
+= |
__iadd__ |
__add__ |
-= |
__isub__ |
__sub__ |
*= |
__imul__ |
__mul__ |
/= |
__itruediv__ |
__truediv__ |
//= |
__ifloordiv__ |
__floordiv__ |
%= |
__imod__ |
__mod__ |
**= |
__ipow__ |
__pow__ |
如果未定义增量版本(如 __iadd__),编译器自动回退到普通版本(如 __add__)。
比较运算符重载
实验性:以下比较运算符 dunder 方法在编译器中被识别,但尚未实现自动调度。定义它们不会报错,但比较运算不会自动调用。
| 运算符 | Dunder 方法 | 状态 |
|---|---|---|
== |
__eq__ |
仅类型推断识别 |
!= |
__ne__ |
仅类型推断识别 |
< |
__lt__ |
仅类型推断识别 |
<= |
__le__ |
仅类型推断识别 |
> |
__gt__ |
仅类型推断识别 |
>= |
__ge__ |
仅类型推断识别 |
反向运算符重载
实验性:以下反向运算符 dunder 方法在编译器中被识别,但尚未实现自动调度。
| Dunder 方法 | 预期语义 | 状态 |
|---|---|---|
__radd__ |
右操作数加法 | 仅类型推断识别 |
__rsub__ |
右操作数减法 | 仅类型推断识别 |
__rmul__ |
右操作数乘法 | 仅类型推断识别 |
__rtruediv__ |
右操作数真除法 | 仅类型推断识别 |
__rfloordiv__ |
右操作数整除 | 仅类型推断识别 |
__rmod__ |
右操作数取模 | 仅类型推断识别 |
__rpow__ |
右操作数幂运算 | 仅类型推断识别 |
一元运算符重载
实验性:以下一元运算符 dunder 方法在编译器中被识别,但尚未实现自动调度。
| 运算符 | Dunder 方法 | 状态 |
|---|---|---|
-a |
__neg__ |
仅类型推断识别 |
+a |
__pos__ |
仅类型推断识别 |
可调用对象 __call__
@t.Object
class Counter:
value: t.CInt
def __init__(self):
self.value = 0
def __call__(self) -> t.CInt:
self.value += 1
return self.value
c: Counter = Counter()
x: t.CInt = c() # x = 1
y: t.CInt = c() # y = 2
下标访问重载
__getitem__ — 读取
def __getitem__(self, key: t.CInt) -> t.CInt:
return self.data[key]
__setitem__ — 赋值
def __setitem__(self, key: t.CInt, value: t.CInt):
self.data[key] = value
__delitem__ — 删除
def __delitem__(self, key: t.CInt):
pass
布尔转换 __bool__
@t.Object
class Buffer:
size: t.CInt
data: list[t.CChar, 1024]
def __bool__(self) -> t.CBool:
return self.size > 0
buf: Buffer = Buffer()
if buf:
serial.puts("buffer is not empty\n")
长度 __len__
@t.Object
class Buffer:
size: t.CInt
def __len__(self) -> t.CInt:
return self.size
buf: Buffer = Buffer()
n: t.CInt = len(buf)
字符串转换 __str__
@t.Object
class Point:
x: t.CInt
y: t.CInt
def __str__(self) -> t.CConst | t.CChar | t.CPtr:
return "Point"
p: Point = Point(1, 2)
print(p) # 调用 p.__str__()
绝对值 __abs__
@t.Object
class SignedValue:
val: t.CInt
def __abs__(self) -> t.CInt:
if self.val < 0:
return -self.val
return self.val
sv: SignedValue = SignedValue()
sv.val = -42
a: t.CInt = abs(sv) # a = 42
迭代器协议
Viper 的迭代器协议与 Python 相同,但 __next__ 的实现方式通过 StopIteration 异常来结束。
__iter__ 和 __next__
@t.Object
class IntRange:
start: t.CInt
end: t.CInt
current: t.CInt
def __init__(self, start: t.CInt, end: t.CInt):
self.start = start
self.end = end
self.current = start
def __iter__(self):
return self
def __next__(self, stop_flag) -> t.CInt:
if self.current >= self.end:
raise StopIteration
val: t.CInt = self.current
self.current += 1
return val
for 循环使用
r: IntRange = IntRange(0, 10)
for i in r:
print(i)
编译器生成的等价逻辑:
IntRange r = IntRange_init(0, 10);
IntRange iter = IntRange___iter__(&r);
while (1) {
i1 stop_flag = 0;
int i = IntRange___next__(&iter, &stop_flag);
if (stop_flag == 1) break;
print_int(i);
}
上下文管理器
with 语句要求对象实现 __enter__ 和 __exit__ 方法:
@t.Object
class File:
fd: t.CInt
path: t.CConst | t.CChar | t.CPtr
def __init__(self, path: t.CConst | t.CChar | t.CPtr, flags: t.CInt):
self.path = path
self.fd = -1
def __enter__(self):
self.fd = fat32.open(self.path, self.flags)
return self
def __exit__(self):
fat32.close(self.fd)
def read(self) -> t.CInt:
return fat32.read(self.fd)
使用方式:
with File("/test.txt", FA_READ) as f:
data: t.CInt = f.read()
编译器会自动保证 __exit__ 在 with 块结束时被调用。
析构 __del__ / __delete__
del obj # 调用 obj.__del__() 或 obj.__delete__()
del arr[idx] # 调用 arr.__delitem__(idx)
Dunder 方法完整参考
已实现自动调度的 Dunder 方法
| Dunder 方法 | 触发语法 | 说明 |
|---|---|---|
__init__ |
ClassName(args) |
构造函数 |
__before_init__ |
自动生成 | vtable 初始化,在 __init__ 之前执行 |
__add__ |
a + b |
加法 |
__sub__ |
a - b |
减法 |
__mul__ |
a * b |
乘法 |
__truediv__ |
a / b |
真除法 |
__floordiv__ |
a // b |
整除 |
__mod__ |
a % b |
取模 |
__pow__ |
a ** b |
幂运算 |
__iadd__ |
a += b |
增量加法(回退到 __add__) |
__isub__ |
a -= b |
增量减法(回退到 __sub__) |
__imul__ |
a *= b |
增量乘法(回退到 __mul__) |
__itruediv__ |
a /= b |
增量真除法(回退到 __truediv__) |
__ifloordiv__ |
a //= b |
增量整除(回退到 __floordiv__) |
__imod__ |
a %= b |
增量取模(回退到 __mod__) |
__ipow__ |
a **= b |
增量幂运算(回退到 __pow__) |
__call__ |
obj(args) |
可调用对象 |
__getitem__ |
obj[key] |
下标读取 |
__setitem__ |
obj[key] = value |
下标赋值 |
__delitem__ |
del obj[key] |
下标删除 |
__bool__ |
if obj: |
布尔转换 |
__len__ |
len(obj) |
长度 |
__str__ |
str(obj) / print(obj) |
字符串转换 |
__abs__ |
abs(obj) |
绝对值 |
__iter__ |
for x in obj: |
获取迭代器 |
__next__ |
for x in obj: |
获取下一个值(标志位方式) |
__enter__ |
with obj as x: |
上下文管理器进入 |
__exit__ |
with obj as x: |
上下文管理器退出 |
__del__ |
del obj |
析构 |
__delete__ |
del obj |
删除描述符 |
声明支持但未实现自动调度的 Dunder 方法
| Dunder 方法 | 预期触发语法 | 当前状态 |
|---|---|---|
__eq__ |
a == b |
仅类型推断识别,比较运算不自动调度 |
__ne__ |
a != b |
同上 |
__lt__ |
a < b |
同上 |
__le__ |
a <= b |
同上 |
__gt__ |
a > b |
同上 |
__ge__ |
a >= b |
同上 |
__neg__ |
-a |
仅类型推断识别,一元负号不自动调度 |
__pos__ |
+a |
同上 |
__radd__ |
右操作数加法 | 仅类型推断识别 |
__rsub__ |
右操作数减法 | 同上 |
__rmul__ |
右操作数乘法 | 同上 |
__rtruediv__ |
右操作数真除法 | 同上 |
__rfloordiv__ |
右操作数整除 | 同上 |
__rmod__ |
右操作数取模 | 同上 |
__rpow__ |
右操作数幂运算 | 同上 |
07 - 控制流
Viper 支持 Python 风格的控制流语句,编译为 LLVM IR 的基本块和分支指令。
条件语句
if / elif / else
if x > 0:
y = 1
elif x == 0:
y = 0
else:
y = -1
等价 C 代码:
if (x > 0) {
y = 1;
} else if (x == 0) {
y = 0;
} else {
y = -1;
}
条件表达式
is_dir: t.CInt = 1 if (info.attr & AM_DIR) else 0
等价 C 代码:
int is_dir = (info.attr & AM_DIR) ? 1 : 0;
None 检查
if BootInfo != None:
paging.init(BootInfo.MemmapAddr, BootInfo.MemmapSize)
else:
paging.init(0, 0)
等价 C 代码:
if (BootInfo != NULL) {
paging_init(BootInfo->MemmapAddr, BootInfo->MemmapSize);
} else {
paging_init(0, 0);
}
循环语句
while 循环
i: t.CInt = 0
while i < 10:
buf[i] = 0
i += 1
等价 C 代码:
int i = 0;
while (i < 10) {
buf[i] = 0;
i += 1;
}
while-else
while i < count:
if found:
break
else:
serial.puts("not found\n")
else 块在循环正常结束(非 break 退出)时执行。
无限循环
while True:
sched.Scheduler._yield()
等价 C 代码:
while (1) {
scheduler_yield();
}
for 循环(range)(关于 for 循环的更多用法,参见 06-oop.md 中 迭代器协议)
for i in range(10):
buf[i] = 0
for i in range(5, 10):
buf[i] = 0
for i in range(0, 100, 2):
buf[i] = 0
等价 C 代码:
for (int i = 0; i < 10; i++) {
buf[i] = 0;
}
for (int i = 5; i < 10; i++) {
buf[i] = 0;
}
for (int i = 0; i < 100; i += 2) {
buf[i] = 0;
}
for 循环(迭代器)
如果类实现了 __iter__ 和 __next__ 方法,可以使用 for ... in 迭代:
container: Iter = Iter()
for item in container:
process(item)
for-else
for i in range(count):
if items[i] == target:
break
else:
serial.puts("not found\n")
break 和 continue
while True:
if done:
break
if skip:
continue
process()
match 语句
Viper 支持 Python 3.10+ 的 match 语句,编译为 C 的 switch 语句:
match self.ctype:
case UI_LABEL:
self.RenderLabel(sh)
case UI_BUTTON:
self.RenderButton(sh)
case UI_PANEL:
self.RenderPanel(sh)
case _:
pass
等价 C 代码:
switch (self->ctype) {
case UI_LABEL:
self_RenderLabel(self, sh);
break;
case UI_BUTTON:
self_RenderButton(self, sh);
break;
case UI_PANEL:
self_RenderPanel(self, sh);
break;
default:
break;
}
match 值匹配
match code:
case 0:
serial.puts("OK\n")
case 1:
serial.puts("Error\n")
case _:
serial.puts("Unknown\n")
match 或模式
match value:
case 1 | 2 | 3:
serial.puts("small\n")
case _:
serial.puts("other\n")
fallthrough(无 break)
默认每个 case 自动添加 break。如需 fallthrough,使用 c.NoBreak:
match value:
case 1:
do_one()
c.NoBreak
case 2:
do_two()
如果需要提前 break,需要使用 c.Break(),因为在 Python 中,match 分支不支持直接使用 break。
assert 语句
避免使用此语句,此语句未经充分测试,在多数情况下,编译器不知道如何展示结果。
assert ptr != None, "null pointer"
编译为条件检查和错误报告。
with 语句
with 语句用于资源管理,要求对象实现 __enter__ 和 __exit__ 方法:
with File("/test.txt", FA_READ) as f:
data: t.CInt = f.read()
等价 C 代码:
File* f = File_enter(File_new("/test.txt", FA_READ));
int data = File_read(f);
File_exit(f);
08 - C 语言操作
Viper 通过 c 模块提供对 C 语言底层特性的访问,包括指针操作、内联汇编、预处理指令等。
指针操作
取地址 c.Addr
x: t.CInt = 42
p: t.CInt | t.CPtr = c.Addr(x) # int* p = &x;
对结构体成员取地址:
res: t.CInt = fat32.opendir("/", c.Addr(dp)) # res = fat32_opendir("/", &dp);
对数组取地址:
viperlib.snprintf(c.Addr(buf), 64, "hello %d", 42) # snprintf(&buf, 64, "hello %d", 42);
解引用 c.Deref
ptr: Sheet | t.CPtr = c.Addr(sheet_obj)
obj: Sheet = c.Deref(ptr) # struct Sheet obj = *ptr;
内存拷贝 c.Load
c.Load(ptr, value) # *ptr = *value;
解引用赋值 c.DerefAs
c.DerefAs(ptr, value) # *ptr = value;
赋值 c.Set
c.Set(target, value) # target = value;
内联汇编 c.Asm
Viper 提供了声明式的内联汇编语法,编译为 GCC 风格的 __asm__ __volatile__ 语句。
基本用法
c.Asm("nop") # __asm__ __volatile__("nop");
带操作数的汇编
使用 f-string 和 c.AsmInp / c.AsmOut 标记操作数:
saved_rdi: t.CUnsignedLong
c.Asm(f"mov {c.AsmOut(saved_rdi, t.ASM_DESCR.OUTPUT_REG)}, rdi",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RDI])
等价 C 代码:
__asm__ __volatile__(
"mov %0, rdi"
: "=r"(saved_rdi)
:
: "memory", "rdi"
);
输入操作数 c.AsmInp
msg: t.CConst | t.CChar | t.CPtr = "Hello"
c.Asm(f"mov rdi, {c.AsmInp(msg, t.ASM_DESCR.REG_ANY)}",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX])
完整示例
c.Asm(f"""mov rdi, {c.AsmInp(msg, t.ASM_DESCR.REG_ANY)}
call {c.AsmInp(log_info_fn, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_RDI, t.ASM_DESCR.CLOBBER_RSI,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
ASM_DESCR 约束字符
操作数修饰符
| 常量 | 值 | 说明 |
|---|---|---|
MODIFIER_OUTPUT |
= |
输出操作数 |
MODIFIER_READWRITE |
+ |
读写操作数 |
MODIFIER_INPUT |
`` | 输入操作数(默认) |
MODIFIER_GLOBAL |
& |
全局操作数 |
寄存器约束
| 常量 | 值 | 说明 |
|---|---|---|
REG_ANY |
r |
任何通用寄存器 |
REG_EAX / REG_RAX |
a |
EAX/RAX |
REG_EBX / REG_RBX |
b |
EBX/RBX |
REG_ECX / REG_RCX |
c |
ECX/RCX |
REG_EDX / REG_RDX |
d |
EDX/RDX |
REG_ESI / REG_RSI |
S |
ESI/RSI |
REG_EDI / REG_RDI |
D |
EDI/RDI |
REG_XMM |
x |
XMM 寄存器 |
内存与立即数
| 常量 | 值 | 说明 |
|---|---|---|
MEMORY |
m |
内存操作数 |
IMMEDIATE |
i |
立即数 |
ANY |
g |
通用寄存器/内存/立即数 |
预定义组合约束
| 常量 | 值 | 说明 |
|---|---|---|
OUTPUT_REG |
=r |
输出,通用寄存器 |
OUTPUT_MEM |
=m |
输出,内存 |
OUTPUT_EAX |
=a |
输出,EAX/RAX |
INPUT_REG |
r |
输入,通用寄存器 |
INPUT_MEM |
m |
输入,内存 |
INPUT_IMM |
i |
输入,立即数 |
破坏描述符
| 常量 | 说明 |
|---|---|
CLOBBER_EAX / CLOBBER_RAX |
破坏 EAX/RAX |
CLOBBER_EBX / CLOBBER_RBX |
破坏 EBX/RBX |
CLOBBER_ECX / CLOBBER_RCX |
破坏 ECX/RCX |
CLOBBER_EDX / CLOBBER_RDX |
破坏 EDX/RDX |
CLOBBER_ESI / CLOBBER_RSI |
破坏 ESI/RSI |
CLOBBER_EDI / CLOBBER_RDI |
破坏 EDI/RDI |
CLOBBER_CC |
破坏条件码(标志寄存器) |
CLOBBER_MEMORY |
破坏内存 |
CLOBBER_R8 ~ CLOBBER_R15 |
破坏 R8~R15 |
预处理指令
Viper 通过 c 模块的函数调用实现 C 预处理指令。
#define
c.CDefine("MAX_SIZE", 1024) # #define MAX_SIZE 1024
上述方法容易引起未定义行为,至少是不便于理解,更常用的方式是使用类型注解:
MAX_SIZE: t.CDefine = 1024 # #define MAX_SIZE 1024
条件编译
c.CIfndef(HEADER_H) # #ifndef HEADER_H
c.CDefine(HEADER_H) # #define HEADER_H
c.CEndif() # #endif
c.CIfdef(DEBUG) # #ifdef DEBUG
c.CEndif() # #endif
c.CIf(VERSION > 2) # #if VERSION > 2
c.CElif(VERSION > 1) # #elif VERSION > 1
c.CElse() # #else
c.CEndif() # #endif
#undef
c.Undef(MACRO_NAME) # #undef MACRO_NAME
#error
c.CError("Platform not supported") # #error "Platform not supported"
#pragma
此关键字已不再支持
c.CPragma("GCC diagnostic push") # #pragma GCC diagnostic push
## 连接符
此方法可能已经不再支持
c.TokenPast("PREFIX_", "NAME") # PREFIX_ ## NAME
由于编译器设计,以及不需要像 C 一样做大量字符替换,对于上述所有的条件宏都远比 C 差,实际工程中不推荐使用条件宏,后期将完善宏的设计,引入模板等。
仅声明 c.State
c.State 用于声明但不定义函数或变量:
def isr0() -> t.CExtern | t.CVoid | c.State: pass # extern void isr0();
LLVM IR 内联
c.LLVMIR
直接嵌入 LLVM IR 指令来实现高效且跨平台的汇编操作:
c.LLVMIR(f"add i32 {c.LInp(a)}, {c.LInp(b)}", t.CInt)
c.LInp / c.LOut
标记 LLVM IR 的输入/输出操作数:
c.LInp(expr) # 输入操作数
c.LOut(expr) # 输出操作数
运算符重载
@t.Object 类支持运算符重载,详见 06-oop.md 中 运算符重载。
09 - 异常处理
Viper 支持 Python 风格的 try/except/finally 异常处理,编译为基于 jmp_buf 和错误码的 C 级别异常处理机制。
异常处理机制
Viper 的异常处理不使用 C++ 风格的零开销异常,而是通过函数参数传递错误码和错误消息实现。编译器自动为可能抛出异常的函数添加 __eh_msg_out__ 和 __eh_code_out__ 参数。
try / except
try:
result = risky_operation()
except ValueError:
serial.puts("ValueError occurred\n")
except OSError:
serial.puts("OSError occurred\n")
except Exception:
serial.puts("Unknown error\n")
内置异常类型
Viper 预定义了以下异常类型及其错误码:
| 异常类型 | 错误码 |
|---|---|
ValueError |
1 |
TypeError |
2 |
RuntimeError |
3 |
ZeroDivisionError |
4 |
IndexError |
5 |
KeyError |
6 |
IOError |
7 |
OSError |
8 |
AssertionError |
9 |
Exception |
99 |
自定义异常
class DiskFullError(Exception):
pass
class ReadOnlyError(DiskFullError):
pass
自定义异常会自动分配递增的错误码(从 100 开始),并支持继承匹配。
多异常捕获
try:
result = operation()
except (ValueError, TypeError):
serial.puts("Value or Type error\n")
获取异常信息
try:
result = operation()
except Exception as e:
serial.puts("Error occurred\n")
try / finally
try:
result = operation()
finally:
cleanup()
finally 块无论是否发生异常都会执行。
try / except / finally
try:
result = operation()
except ValueError:
handle_value_error()
finally:
cleanup()
raise 语句
抛出异常
raise ValueError("invalid value")
抛出自定义异常
raise MyCustomError("something went wrong")
重新抛出异常
raise
StopIteration
StopIteration 有特殊处理,用于迭代器协议:
raise StopIteration
异常处理的编译实现
函数签名变换
可能抛出异常的函数会自动添加错误输出参数:
def risky_operation() -> t.CInt:
if error:
raise ValueError("bad value")
return 0
编译后等价于:
int risky_operation(char** __eh_msg_out__, int* __eh_code_out__) {
if (error) {
*__eh_code_out__ = 1; // ValueError code
*__eh_msg_out__ = "bad value";
return 0;
}
return 0;
}
调用点变换
try:
result = risky_operation()
except ValueError:
handle_error()
编译后等价于:
char* __eh_msg = NULL;
int __eh_code = 0;
int result = risky_operation(&__eh_msg, &__eh_code);
if (__eh_msg != NULL) {
if (__eh_code == 1) { // ValueError
handle_error();
}
}
异常继承匹配
异常匹配支持继承体系。捕获父类异常时,子类异常也会被匹配:
class FileSystemError(Exception):
pass
class FileNotFoundError(FileSystemError):
pass
class PermissionError(FileSystemError):
pass
try:
operation()
except FileSystemError:
# 也会捕获 FileNotFoundError 和 PermissionError
handle_fs_error()
10 - 模块与导入系统
Viper 的模块系统基于 Python 的 import 语法,但编译时通过 TransPyC 的符号表和类型系统实现跨模块的类型解析。
导入语法
标准导入
import t, c # Viper 内置类型和操作模块
import asm # 汇编辅助模块
import string # 字符串操作模块
模块导入
import bootinfo
import intr
import platform.pch as pch
import drivers.serial.uart.serial as serial
from 导入
from stdint import * # 导入所有 stdint 类型别名
from drivers.video.vesafb.vga import _VGAScreenDriver
相对导入
from . import submodule
from .. import parent_module
特殊模块
t 模块
类型定义模块,提供所有 C 类型对应的 Viper 类型。每个 Viper 文件通常都需要导入:
import t
c 模块
C 语言操作模块,提供指针操作、内联汇编、预处理指令等:
import c
asm 模块
汇编辅助模块,提供常用汇编指令的封装:
import asm
asm.sti() # __asm__ __volatile__("sti");
asm.hlt() # __asm__ __volatile__("hlt");
asm.cli() # __asm__ __volatile__("cli");
asm.nop() # __asm__ __volatile__("nop");
asm.BSSClean() # 清零 BSS 段
string 模块
字符串/内存操作模块,对应 C 标准库的 string.h:
string 部分使用 x86_64 专有汇编进行加速,因此某些函数在其它架构可能需要手动重新编写。
import string
string.memset(c.Addr(buf), 0, 64) # memset(&buf, 0, 64);
string.memcpy(dst, src, size) # memcpy(dst, src, size);
string.memmove(dst, src, size) # memmove(dst, src, size);
string.memcmp(a, b, size) # memcmp(a, b, size);
string.strcmp(a, b) # strcmp(a, b);
string.strcpy(dst, src) # strcpy(dst, src);
stdint 模块
提供 C stdint.h 中的类型别名:
from stdint import *
# 提供以下类型别名:
# UINT8PTR, UINT16PTR, UINT32PTR, UINT64PTR
# INT8PTR, INT16PTR, INT32PTR, INT64PTR
# 等等
viperlib 模块
ViperOS 标准库,提供常用 C 库函数:
import viperlib
viperlib.snprintf(c.Addr(buf), 64, "value=%d", 42) # snprintf(&buf, 64, "value=%d", 42);
vpsdk 模块
vpsdk 模块 专供于基于 Viper 开发的示例操作系统 ViperOS,不可用于其它平台,且需要特殊配置。
ViperOS 应用 SDK,提供系统调用和窗口管理:
import vpsdk.window as window
import vpsdk.process as process
import vpsdk.dynlib as dynlib
import vpsdk.syscall as syscall
模块解析流程
编译时模块加载
Viper 的 import 是编译时操作,不涉及运行时模块加载。完整的模块解析流程与两阶段编译紧密耦合:
- 阶段一:从入口文件出发,通过
import语句递归发现所有可达的源文件 - 阶段一:对每个源文件生成
<SHA1>.pyi签名存根和<SHA1>.stub.llLLVM IR 声明 - 阶段二:加载所有
.pyi和.stub.ll,构建共享符号表 - 阶段二:翻译源文件时,通过符号表解析跨模块的类型和函数引用
- 阶段二:将被引用模块的
.stub.ll声明嵌入到生成的.ll文件头部
类型存根文件(.pyi)
.pyi 文件是模块的声明接口,由 PythonToStubConverter 在阶段一生成。存根文件的内容规则:
| 元素 | 存根表示 |
|---|---|
t.CDefine 常量 |
保留完整定义(含赋值) |
t.CDefine 函数 |
保留完整函数体 |
| 普通函数 | 仅签名 + c.State |
| 全局变量 | 添加 t.CExtern |
| 类定义 | 保留成员类型注解和方法签名 |
存根文件还自动添加宏守卫(c.CIfndef/c.CEndif()),防止重复包含。
SHA1 命名空间与导入
每个模块的函数和结构体在 LLVM IR 中自动加上 SHA1 前缀,消除跨模块符号冲突。只有标记为 t.CExport 的函数保持原始名称。详见 01-overview.md 中 SHA1 命名空间机制。
符号表
所有模块的类型信息统一存储在 SymbolTable 中,包含:
- 结构体/联合体/枚举定义
- 函数签名
- 全局变量
- typedef 别名
t.CDefine常量
跨模块类型引用
# 在 fat32_types.py 中定义
class fat32_fileinfo:
fname: list[t.CChar, 13]
attr: t.CUInt8T
file_size: t.CUInt32T
# 在其他模块中引用
import fat32_types
info: fat32_types.fat32_fileinfo
跨模块引用的结构体在 LLVM IR 中使用 SHA1 前缀的类型名:
%"<SHA1_of_fat32_types>.fat32_fileinfo" = type { [13 x i8], i8, i32 }
项目 includes 配置
在 project.json 中配置模块搜索路径:
{
"includes": ["../.."],
"source_dir": "./Kernel"
}
编译器会在 includes 指定的目录中搜索导入的模块。
11 - 内置函数与运算符
Viper 支持 Python 内置函数和运算符,编译为对应的 C/LLVM 操作。
内置函数
print("Hello, World!") # 调用 puts 或 printf
print(x, y) # 多参数输出
len
n: t.CInt = len(array) # 获取数组长度(编译时常量)
sizeof
s: t.CSizeT = sizeof(t.CInt) # sizeof(int)
s: t.CSizeT = obj.__sizeof__() # sizeof(obj)
abs
a: t.CInt = abs(x) # abs(x)
int / float / bool
i: t.CInt = int(3.14) # (int)3.14
f: t.CFloat = float(42) # (float)42
b: t.CBool = bool(x) # x != 0
min / max
m: t.CInt = min(a, b) # a < b ? a : b
m: t.CInt = max(a, b) # a > b ? a : b
chr / ord
c: t.CChar = chr(65) # 'A' (整数转字符)
n: t.CInt = ord('A') # 65 (字符转整数)
range
用于 for 循环:
for i in range(10): pass # for (int i = 0; i < 10; i++)
for i in range(5, 10): pass # for (int i = 5; i < 10; i++)
for i in range(0, 10, 2): pass # for (int i = 0; i < 10; i += 2)
运算符
算术运算符
| Viper | C 等价 | 说明 |
|---|---|---|
a + b |
a + b |
加法 |
a - b |
a - b |
减法 |
a * b |
a * b |
乘法 |
a / b |
a / b |
浮点除法 |
a // b |
a / b |
整数除法 |
a % b |
a % b |
取模 |
a ** b |
pow(a, b) |
幂运算 |
-a |
-a |
取负 |
+a |
+a |
正号 |
位运算符
| Viper | C 等价 | 说明 |
|---|---|---|
a & b |
a & b |
按位与 |
a | b |
a | b |
按位或 |
a ^ b |
a ^ b |
按位异或 |
~a |
~a |
按位取反 |
a << n |
a << n |
左移 |
a >> n |
a >> n |
右移 |
注意:
|运算符在类型注解中表示类型组合,在表达式中表示按位或。编译器根据上下文区分。
比较运算符
| Viper | C 等价 | 说明 |
|---|---|---|
a == b |
a == b |
等于 |
a != b |
a != b |
不等于 |
a < b |
a < b |
小于 |
a > b |
a > b |
大于 |
a <= b |
a <= b |
小于等于 |
a >= b |
a >= b |
大于等于 |
逻辑运算符
| Viper | C 等价 | 说明 |
|---|---|---|
a and b |
a && b |
逻辑与 |
a or b |
a || b |
逻辑或 |
not a |
!a |
逻辑非 |
增强赋值运算符
| Viper | C 等价 |
|---|---|
a += b |
a += b |
a -= b |
a -= b |
a *= b |
a *= b |
a //= b |
a /= b |
a %= b |
a %= b |
a <<= b |
a <<= b |
a >>= b |
a >>= b |
a &= b |
a &= b |
a |= b |
a |= b |
a ^= b |
a ^= b |
类型自动转换
Viper 在算术运算中自动进行类型提升:
-
整数宽度提升:较窄的整数类型自动扩展为较宽的类型
- 有符号类型使用符号扩展(
sext) - 无符号类型使用零扩展(
zext)
- 有符号类型使用符号扩展(
-
整数到浮点:整数与浮点数运算时,整数自动转换为浮点
-
浮点精度提升:
float与double运算时,float提升为double
指针算术
指针与整数的加法支持自动计算偏移:
buf: t.CUInt32T | t.CPtr
buf[i] = value # buf[i] = value; (自动计算偏移)
数组下标
buf: list[t.CChar, 64]
buf[0] = 'H' # buf[0] = 'H';
buf[i] = value # buf[i] = value;
info.fname[0] # info.fname[0] (结构体数组成员访问)
成员访问
obj.x # obj.x (直接成员)
obj.method() # obj.method() (方法调用)
ptr.x # ptr->x (指针自动解引用)
字符串字面量
字符串字面量编译为 C 字符串常量(const char*),其为只读:
serial.puts("Hello") # 传递 const char*
字符串索引
msg: t.CConst | str = "Hello"
serial.puts(c.Addr(msg[4])) # 访问 msg[4] 的地址,输出 'o'
C 标准库函数
Viper 内置支持以下 C 标准库函数的直接调用:
| 函数 | 签名 |
|---|---|
strlen(s) |
size_t strlen(const char* s) |
memset(s, c, n) |
void* memset(void* s, int c, size_t n) |
memcpy(d, s, n) |
void* memcpy(void* d, const void* s, size_t n) |
memmove(d, s, n) |
void* memmove(void* d, const void* s, size_t n) |
memcmp(a, b, n) |
int memcmp(const void* a, const void* b, size_t n) |
strcmp(a, b) |
int strcmp(const char* a, const char* b) |
strncmp(a, b, n) |
int strncmp(const char* a, const char* b, size_t n) |
strcpy(d, s) |
char* strcpy(char* d, const char* s) |
strncpy(d, s, n) |
char* strncpy(char* d, const char* s, size_t n) |
strcat(d, s) |
char* strcat(char* d, const char* s) |
puts(s) |
int puts(const char* s) |
atoi(s) |
int atoi(const char* s) |
atol(s) |
long atol(const char* s) |
abs(x) |
int abs(int x) |
labs(x) |
long labs(long x) |
部分模式比如裸机没有 libc,则不能使用这些函数,即使环境满足,Viper 标准库也可能和 C 标准库冲突。
12 - 项目配置与构建
Viper 项目使用 project.json 配置文件管理编译和链接参数。
project.json 结构
{
"name": "Kernel",
"version": "1.0.0",
"source_dir": "./Kernel",
"temp_dir": "./temp",
"output_dir": "./output",
"compiler": {
"cmd": "llc",
"flags": ["-filetype=obj", "-mtriple=x86_64-none-elf", "-relocation-model=static", "-O2"]
},
"linker": {
"cmd": "ld.lld.exe",
"flags": [
"-m", "elf_x86_64",
"-T", "linker.ld",
"--oformat", "elf64-x86-64",
"--strip-all"
],
"output": "kernel.bin"
},
"includes": ["../../includes"],
"target": {
"triple": "x86_64-none-elf",
"datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
},
"options": {
"slice_level": 3,
"target": "llvm",
"strict_mode": true
}
}
配置项说明
基本信息字段
| 字段 | 类型 | 说明 |
|---|---|---|
name |
string | 项目名称 |
version |
string | 项目版本 |
source_dir |
string | 源代码目录(相对于 project.json 所在目录) |
temp_dir |
string | 临时文件目录(存放 .pyi 存根等) |
output_dir |
string | 输出文件目录 |
编译器配置
| 字段 | 类型 | 说明 |
|---|---|---|
compiler.cmd |
string | 编译器命令(通常为 llc) |
compiler.flags |
string[] | 编译器标志 |
常用 llc 标志:
-filetype=obj:输出目标文件-mtriple=x86_64-none-elf:目标三元组-relocation-model=static:静态重定位-O2:优化级别
链接器配置
| 字段 | 类型 | 说明 |
|---|---|---|
linker.cmd |
string | 链接器命令(通常为 ld.lld.exe) |
linker.flags |
string[] | 链接器标志 |
linker.output |
string | 输出文件名 |
常用 ld.lld 标志:
-m elf_x86_64:ELF x86_64 格式-T linker.ld:链接脚本--oformat elf64-x86-64:输出 ELF 格式--oformat binary:输出原始二进制(用于内核)--strip-all:去除所有符号信息
目标平台配置
| 字段 | 类型 | 说明 |
|---|---|---|
target.triple |
string | LLVM 目标三元组 |
target.datalayout |
string | LLVM 数据布局字符串 |
默认值:
- triple:
x86_64-none-elf - datalayout:
e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
包含路径
| 字段 | 类型 | 说明 |
|---|---|---|
includes |
string[] | 模块搜索路径列表 |
编译选项
| 字段 | 类型 | 说明 |
|---|---|---|
options.slice_level |
int | 切片优化级别(0-3) |
options.target |
string | 编译目标(目前仅支持 llvm) |
options.strict_mode |
bool | 严格模式 |
切片优化级别
| 级别 | 名称 | 说明 |
|---|---|---|
0 |
no_optimize |
无优化 |
1 |
conservative |
保守优化 |
2 |
moderate |
适度优化 |
3 |
aggressive |
激进优化(默认) |
s |
size |
体积优化 |
项目结构示例
内核项目
VKernel/
├── project.json # 项目配置
├── linker.ld # 链接脚本
├── Kernel/ # 源代码目录
│ ├── main.py # 内核入口
│ ├── bootinfo.py
│ ├── intr/
│ │ ├── gdt.py
│ │ ├── idt.py
│ │ └── syscall.py
│ ├── drivers/
│ │ ├── serial/
│ │ ├── video/
│ │ ├── storage/
│ │ └── ...
│ └── ...
├── temp/ # 临时文件(自动生成)
│ ├── <SHA1>.pyi # 签名存根(阶段一生成)
│ ├── <SHA1>.stub.ll # LLVM IR 声明(阶段一生成)
│ └── _sha1_map.txt # SHA1→源文件路径映射
└── output/ # 输出文件(自动生成)
├── kernel.bin # 内核二进制
├── *.deps.json # 依赖信息
└── linker.ld # 生成的链接脚本
应用项目
HelloWorld/
├── project.json # 项目配置
├── linker.ld # 链接脚本
├── main.py # 应用入口
├── temp/ # 临时文件
└── output/ # 输出文件
└── helloworld.elf # ELF 可执行文件
库项目
SerialLogger/
├── project.json # 项目配置
├── linker.ld # 链接脚本
├── serial_logger.py # 库源码
├── temp/ # 临时文件
└── output/ # 输出文件
链接脚本
ViperOS 使用自定义链接脚本控制内存布局:
ENTRY(_start)
SECTIONS
{
. = 0x100000; /* 加载地址 */
.text : {
*(.text.startup)
*(.text*)
}
.rodata : {
*(.rodata*)
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
*(COMMON)
}
}
编译命令
TransPyC 编译器的基本用法(注:适用于单个文件,而不适用于工程):
python TransPyC.py -f InputFile -o OutputFile [options]
命令行参数
| 参数 | 说明 |
|---|---|
-f |
输入文件路径 |
-o |
输出文件路径 |
-wh |
头文件列表 |
-debug |
调试输出文件 |
-cc |
编译命令 |
-cflags |
编译标志 |
-run |
编译后运行 |
-args |
运行参数 |
-h |
辅助文件(C 或 Python) |
工程编译使用:
python Projectrans.py xxx.json
即可
增量编译
TransPyC 支持基于 SHA1 的增量编译,与两阶段编译模型紧密耦合:
阶段一增量
- 每个源文件按内容计算 SHA1 哈希(16位)
- 如果
<SHA1>.pyi和<SHA1>.stub.ll已存在,则跳过生成(缓存命中) - 只有源文件内容变化导致 SHA1 变化时才重新生成声明接口
temp/_sha1_map.txt记录 SHA1 → 源文件路径的映射,格式为<sha1>:<relative_path>
阶段二增量
- 阶段二加载
temp/_sha1_map.txt重建 SHA1 映射 - 过滤掉不在当前 SHA1 映射中的旧
.pyi和.stub.ll文件 - 共享符号表一次性构建,避免每个文件重复加载
SHA1 命名空间与增量编译的协同
SHA1 同时服务于命名空间隔离和增量编译:
- 命名空间:非
t.CExport函数和结构体使用 SHA1 前缀,消除符号冲突 - 增量编译:SHA1 不变则缓存命中,SHA1 变化则重新生成