Files
TransPyC/wiki/all.md
2026-07-18 19:25:40 +08:00

98 KiB
Raw Permalink Blame History

01 - 语言概述与编译流程

语言定位

Viper 是一种 系统级编程语言,其语法基于 Python,但编译目标为 LLVM IR,最终生成原生机器码。Viper 不是 Python 的超集或子集,也不是"C 的语法糖"——它是一门拥有独立类型系统、独立编译模型、独立模块体系的语言。Viper 选择 Python 语法作为表达形式,是因为 PythonAST 可被标准库直接解析,从而将编译器的精力集中在语义翻译而非词法/语法分析上。同时也是因为 Python 是人类公认的可读性最高的语言。

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

核心设计决策

  1. Python 语法LLVM 语义:源文件是完全合法的 Python 语法,但通过 t 模块的类型注解系统赋予完全不同的语义 —— t.CInt 不是 Pythonint,而是 LLVMi32
  2. 类型注解即编译指令Viper 的类型注解不是可选的提示,而是编译器生成 LLVM IR 的决定性依据。x: t.CInt 生成 i32x: t.CInt | t.CPtr 生成 i32*,但无注解时会自动推导,详见下文。
  3. 两阶段编译:先从源文件提取声明接口(.pyi + .stub.ll),再使用声明接口翻译源文件为含代码的 .ll。这是 Viper 区别于简单"PythonC"工具的关键架构
  4. SHA1 命名空间:每个源文件按内容 SHA1 哈希命名,非导出函数和结构体自动加上 SHA1 前缀,从根本上消除跨模块的符号冲突
  5. 万物皆数据:为便于底层开发,实际上所有的变量和类型一般并不适用于鸭子类型,但在语义层面我们会尽可能贴近鸭子类型。

Python 的关系

特性 Python Viper
类型系统 动态类型 静态类型(通过 t 模块注解,编译时确定)
内存管理 GC 自动回收 手动管理,无 GC,无运行时
运行时 解释执行 + 字节码 编译为原生机器码(通过 LLVM
对象模型 万物皆对象 @t.Object 类有对象语义,@t.CVTable 为多态,普通 class 仅为结构体布局
标准库 Python 标准库 Viper 标准库(includes/+ ViperOS SDK
空值 None None(编译为 NULL/零值)
模块系统 运行时 import 编译时解析,插入 .stub.llSHA1 命名空间隔离

与 C 的关系

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

  • SHA1 命名空间:自动消除符号冲突,无需手动管理 static/命名前缀
  • 类型组合语法t.CConst | t.CInt | t.CPtrconst 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.CDefinet.CExportt.CInline 等关键概念的前提。

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

阶段一声明提取Phase1Generator

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

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

从入口文件(main.pyproject.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_filesSHA1 → .pyi 文件路径映射
  • stub_filesSHA1 → .stub.ll 文件路径映射
  • sha1_mapSHA1 → 源文件相对路径映射

步骤 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.pySHA1 = a1b2c3d4e5f6g7h8中定义
    class Point: x: t.CInt; y: t.CInt
    def draw(p: Point) -> t.CVoid: ...

源文件 B.pySHA1 = 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)

编译行为

  1. 阶段一:存根生成器保留 t.CDefine 常量的完整定义(包括赋值值),因为其他模块可能引用此常量
  2. 阶段一.stub.ll 声明生成器跳过 t.CDefine 常量,不生成 external global 声明
  3. 阶段二:翻译器遇到 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

编译行为

  1. 阶段一:存根生成器保留 t.CDefine 函数的完整函数体(不像普通函数那样只保留签名)
  2. 阶段二:翻译器遇到 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.CUInt16Tuint16_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

编译行为

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

t.CInlinet.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

编译流程:

  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 链接为可执行文件

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 int0/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 char8位
BYTEPTR BYTE | t.CPtr unsigned char*
WORD t.CUInt16T uint16_t16位
DWORD t.CUInt32T uint32_t32位
QWORD t.CUInt64T uint64_t64位
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_tUTF-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.BigEndiant.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) 的方法,认为这将 voidptr 分开,不便于理解,那么还有一个方法,即将二者打包为一个 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

规则:

  1. CReturn 中的类型数量决定返回值个数
  2. 自动为每个返回类型添加指针参数(t.CIntt.CInt | t.CPtr
  3. 参数名自动生成为 __ReturnValue0____ReturnValue1__
  4. 调用处自动传参 &xxx
  5. 必须使用左右值赋值,返回值不能直接传入函数。

指针参数

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.BigEndiant.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避免修改影响所有实例。

虚方法调度机制

虚方法调用的核心流程:

  1. 从对象指针 GEP 获取第 0 个 slotvtable 指针)
  2. 加载 vtable 指针
  3. bitcast 为 [N x i8*]* 类型
  4. GEP 获取方法索引处的函数指针
  5. 加载函数指针
  6. bitcast 为正确的函数指针类型
  7. 间接调用

继承

结构体成员继承

当子类继承父类(父类在 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 + ba.__add__(b)
- __sub__ a - ba.__sub__(b)
* __mul__ a * ba.__mul__(b)
/ __truediv__ a / ba.__truediv__(b)
// __floordiv__ a // ba.__floordiv__(b)
% __mod__ a % ba.__mod__(b)
** __pow__ a ** ba.__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 是编译时操作,不涉及运行时模块加载。完整的模块解析流程与两阶段编译紧密耦合:

  1. 阶段一:从入口文件出发,通过 import 语句递归发现所有可达的源文件
  2. 阶段一:对每个源文件生成 <SHA1>.pyi 签名存根和 <SHA1>.stub.ll LLVM IR 声明
  3. 阶段二:加载所有 .pyi.stub.ll,构建共享符号表
  4. 阶段二:翻译源文件时,通过符号表解析跨模块的类型和函数引用
  5. 阶段二:将被引用模块的 .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

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 在算术运算中自动进行类型提升:

  1. 整数宽度提升:较窄的整数类型自动扩展为较宽的类型

    • 有符号类型使用符号扩展(sext
    • 无符号类型使用零扩展(zext
  2. 整数到浮点:整数与浮点数运算时,整数自动转换为浮点

  3. 浮点精度提升floatdouble 运算时,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_64ELF 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 变化则重新生成

详见 01-overview.md 中 SHA1 命名空间机制