33 KiB
02 - 类型系统
Viper 的类型系统通过 t 模块提供,所有类型注解继承 t.CType 。大都以 t.C{TypeName:PascalCase}为类型,类型注解是 Viper 的核心机制,决定了变量的内存布局、大小和对齐方式。
书写规范(重要):
t.C*/c.*正在被逐步淘汰。新代码推荐使用短名别名(int/uchar/char/uint64/bool…)、T*指针、&/*指针操作、前缀描述符const/volatile/__attribute__(...)、前缀数组[N]T,以及enum/union/renum/define/typedef/export/extern/virtual等软关键字。旧写法在过渡期仍可用,但不再推荐。规范原文见仓库根目录新规范。
类型书写规范(新语法,推荐)
类型别名(裸名,等价 t.C*)
| 新写法(推荐) | 等价旧写法 | LLVM 类型 |
|---|---|---|
int |
t.CInt |
i32 |
uint |
t.CUnsignedInt |
i32 |
short / ushort |
t.CShort / t.CUnsignedShort |
i16 |
long / ulong |
t.CLong / t.CUnsignedLong |
i32(Windows LLP64)或 i64 |
longlong / ulonglong |
t.CLongLong / t.CUnsignedLongLong |
i64 |
char |
t.CChar |
i8 |
uchar |
t.CUnsignedChar |
i8 |
bool |
t.CBool |
i1 |
float / double |
t.CFloat / t.CDouble |
f32 / f64 |
str / bytes |
(等价 t.CPtr) |
i8*(str* = i8**) |
void |
t.CVoid |
void |
sizet / size_t |
t.CSizeT |
i64 |
int8 / int16 / int32 / int64 |
t.CInt8T … t.CInt64T |
i8 / i16 / i32 / i64 |
uint8 / uint16 / uint32 / uint64 |
t.CUInt8T … t.CUInt64T |
i8 / i16 / i32 / i64 |
i8…i64 / u8…u64 |
同上(stdint.vp 声明) |
同上 |
int8_t…uint64_t、ssize_t、intptr_t、uintptr_t、ptrdiff_t |
同上 | 对应宽度(多为 i64) |
rawptr[T] ≡ T* |
T | t.CPtr |
T* |
callable / Callable |
t.Callable |
i8* |
别名来源:includes/builtins.vp(C 基础类型裸名)、includes/stdint.vp(定长与 *_t);int/char/bool/float/double/str/bytes/void 为编译器内置直通。
尚未实现 / 不可用:
schar;wchar_t、注解位的f32/f64、char8_t/char16_t/char32_t、intfast*/intleast*(声明存在但 TPV 类型映射缺失)。
指针:统一写 T*
T | t.CPtr 一律改写为 T*(层级 = 星号个数),四种写法在类型域恒等价且恒 +1 层:
p: int* # int* ≡ t.CInt | t.CPtr
pp: int** # int** ≡ t.CInt | t.CPtr | t.CPtr
s: char* # char* ≡ t.CChar | t.CPtr
v: void* # void* = i8*(通配指针)
&a的结果类型明确是T*,不需要再写t.CPtr强转。
指针操作:& / *(取代 c.*)
| 旧写法 | 新写法 |
|---|---|
c.Addr(x) |
&x |
c.Deref(p) |
*p |
c.DerefAs(p, v) |
*p = v |
c.Load(p, q) |
*p = *q |
描述符:const / volatile / __attribute__(...)
类型描述符写在类型前面,顺序自由、可混写:
a: const int # ≡ t.CConst | int(只读)
b: const int* # const int*
c: volatile uint32 # 访问落 LLVM volatile
@__attribute__(packed) # 类 / 结构体
d: __attribute__(aligned(16), section(".bss")) int
__attribute__(...)比 GCC 少一层括号:写__attribute__(x)而非__attribute__((x));实参脱糖为t.attr.x。可作为注解前缀,也可作为@__attribute__(...)装饰器。
数组:前缀 [N]T
buf: [64]char # char buf[64] ≡ t.CArray[t.CChar, 64]
grid: [8][4]float # float grid[8][4]
s: []char = "hi" # 长度推导 ≡ t.CArray[char, None]
三种写法(前缀 [N]T、软关键字 array[E,N]、t.CArray[E,N])最终都生成 LLVM [N x E]。
关于 |
注解中的 |(类型组合 / 存储类标记)正在逐步取消:
T | t.CPtr→T*;t.CInt | t.CExport→export def f(...) -> t.CInt;t.CStatic | ...→ 前置static ...;t.CConst | T/t.CVolatile | T→ 前置描述符const T/volatile T。
暂未对齐、仍需
|的仅剩少数字面描述符,如t.Bit[n]、大小端标记t.BigEndian/t.LittleEndian。
基本类型
整数类型
| 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.CUnsignedInt 等。
固定宽度整数类型
| 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 | 是 |
浮点类型
| Viper 类型 | C 等价 | 大小(位) |
|---|---|---|
t.CFloat |
float |
32 |
t.CDouble |
double |
64 |
其他基本类型
| Viper 类型 | C 等价 | 说明 |
|---|---|---|
t.CVoid |
void |
空类型 |
t.CBool |
bool |
布尔类型(LLVM i1,1 位;C _Bool 存储 1 字节) |
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位,无符号) |
void关键字别名:裸名void是t.CVoid的别名(无需import t),二者都解析为 LLVMvoid;void*(void | t.CPtr)归并为i8*。void仅允许出现在返回类型、void*(指针的 pointee)、以及typedef 别名 = void三处;出现在形参、字段、变量声明或被当作值使用时一律编译期 fail-fast(TypeError)。
Python 便捷映射类型
| Python 类型 | Viper 类型 | Viper 值 |
|---|---|---|
int |
t.CInt |
- |
float |
t.CFloat |
- |
str |
i8*(裸 str;str | t.CPtr 才是 i8**) |
- |
bytes |
i8*(与裸 str 同义) |
- |
bool |
t.CBool |
- |
True |
t.CBool |
1 |
False |
t.CBool |
0 |
None |
NULL |
i8* NULL 常量(语义 NULL/零值) |
注:
True/False裸字面量在 LLVM 层为 i32 的 1/0;赋给bool/t.CBool(i1)注解变量时,经比较归一化为 i1。
对于 t.py 和 c.py 的引用,支持 from ... import ... 与 as 别名(编译器自身大量依赖)。
类型组合
Viper 使用 Python 的 | 来组合类型修饰符(TypeUnion):| 在类型/注解上下文(参数、返回、字段、typedef、强转表达式)中表示组合,在值表达式上下文中为位或。这是 Viper 最重要的语法特性之一。
注意:
|作为类型组合正在被逐步取消,新代码请改用短名别名、T*、前缀描述符与软关键字(见上方「类型书写规范」)。本节保留|写法作为过渡期参考。
规划演进(新语法方向):
t.CConst | t.CInt | t.CPtr这类链式组合过于冗长,方向是改为贴近 C 的写法(过渡期链式写法仍可用):
a: const rawptr[int] = xxx(rawptr是编译器内置保留字,裸指针;规范名即 rawptr)- 或
a: const int* = xxx(C 风格指针 / const 前缀类型注解)- 指针写法已统一(2026-09-30 定):
T*≡rawptr[T]≡t.CPtr[T]≡T | t.CPtr,四种写法在类型域恒等价,且恒追加恰好 1 层间接。详见下文「指针类型」。T1 | T2的计划是在基本类型间完全禁用,|转回其本意 TypeUnion(类型联合)。- 计划引入
T1 & T2= TypeIntersection(类型交集),主要面向object,尤其是带 vtable 的object。- 基本类型联合(如
a: int | str)是否支持仍需考虑,目前只期望未来先针对object落实联合/交集。
指针类型
指针有四种等价写法(2026-09-30 起统一口径),在类型域恒等价,且恒追加恰好 1 层间接:
| 写法 | 形态 | 含义 |
|---|---|---|
T | t.CPtr |
链式组合(经典写法) | T* |
T* |
C 风格后缀(T* 文本反解析为 rawptr[...]) |
T* |
rawptr[T] |
内建裸指针下标形 | T* |
t.CPtr[T] |
t 模块指针下标形 |
T* |
关键规则:四种写法一律只加 1 层。
rawptr[T]/t.CPtr[T]里的T是被指类型(pointee)本身,不是「指向 T 的指针」。所以t.CPtr[t.CPtr]=i8**、t.CPtr[int]=i32*;要更高层就显式嵌套(t.CPtr[t.CPtr[int]]=i32**)。
x: int* # 指向 int 的指针
p: void* # 通用指针(归并为 i8*)
s: char* # 字符串指针
pp: int** # 指向指针的指针
# 以下四行彼此等价(都是 int*)
a: int*
b: rawptr[int]
c: t.CPtr[int]
d: int | t.CPtr # 旧链式写法
等价 C 代码:
int* x;
void* p;
char* s;
int** pp;
补充约定:
- 裸
rawptr/ 裸t.CPtr(无下标)本身 =void*=i8*。 void*(t.CVoid | t.CPtr或void | t.CPtr)=i8*,是通配指针:与任意T*双向隐式可转(单级)。rawptr是编译器内置语法保留字(TPC/TPV 双端),用户对它做typedef会被忽略;t.CPtr是t模块提供的类型名,二者类型语义等价。- 复用 Python 高亮插件时,
T | t.CPtr通常比t.CPtr[T]更易被识别为属性访问。 - 裸类名恒为原型 struct(值/布局语义);堆对象引用必须显式写指针(
T*/T | t.CPtr/rawptr[T])。
指针判空写法(硬约定)
指针(T | t.CPtr / T*)判空一律用身份比较:
if p is None: ... # ✅ → icmp eq <ptr>, null
if p is not None: ... # ✅ → icmp ne <ptr>, null
if p == 0: ... # ❌ 不用:虽同样编译为 icmp eq <ptr>, null,但语义要绕"指针 vs 整型"特例分支
以整型承载地址/句柄的字段判空,相反:必须与整型字面量比较(!= 0 / == 0)。
例:llvmlite.LLVMType.Pointee 是 i64(地址值),写 x.Pointee is not None 判不出空——
两侧编译器对"值类型 vs None"的处理是:TPC 直接折成常量(is not None → i1 1、is None → i1 0,
TransPyC/lib/core/Handles/HandlesExprOps.py:383-395);TPV 把 None 物化为数值 0 后按整数比较
(TransPyV/App/lib/core/Gen/Expr/ExprGenOps.vp:684-713)。⇒ 这类判空写 != 0 才两侧同效。
反例现场(守卫写出去了、但运行期没触发)与逐字 IR 证据:Debugger/rawptr-migration-and-fastfail.md §36。
双侧 IR 证据(判据写法 → 生成码)
| 写法 | 静态类型 | 生成 IR | 结论 |
|---|---|---|---|
p is None / p == 0 |
指针 | icmp eq <ptr>, null |
有效(命名 is_cmp / cmpresult) |
x is not None |
整型 i64 | 无 icmp,折成常量 i1 1 |
失效(守卫恒真) |
x is None |
整型 i64 | 无 icmp,折成常量 i1 0 |
失效(守卫恒假 → 死代码) |
x != 0 / x == 0 |
整型 i64 | icmp ne/eq i64 %x, 0 |
有效(两侧同效) |
逐字实证(x = elem_ptr.Ty.Pointee,LLVMType* 的 REnum 载荷槽,偏移 8、load i64):
TransPyV/output/818a0677d1e7fe22.ll:17979-17991 中指针侧真的有 icmp,整型侧却只有
%"and.result.4" = phi i1 [0, %"endif.19"], [1, %"and.rhs0.4"](and.rhs0.4 块内 load i64 后直接 br)。
读"对象首字"(vptr)的唯一可用写法
virtual 类对象的 vptr 在偏移 0(IR:getelementptr %T, %T* %p, i32 0, i32 0 + load i8*),
kind() 等是虚调用(vptr 为 0 ⇒ load [0 + slot] ⇒ ASan READ at 0x0)。
因此"对象有效性校验"必须能读到首字:
v: uint64 = *(uint64*)p # ✅ bitcast p → i64* + load i64(旧写法 c.Deref((uint64*)(p)))
if v == 0: ... # 地址值比较,有效
v2: rawptr = rawptr(*(uint64*)p) # ❌ 被折成裸 0(inttoptr i32 0 to i8*),读不到 vptr
*ptr = <rawptr 值> # ❌ 目标 LHS 是结构体类型的解引用时**可能一条指令都不发射**(静默丢失)
(IR 实证:TransPyV/output/b5883dc9c114a604.ll:10382-10384 对比 :10611-10615。)
类型限定符组合
规划:本节
t.CConst | T/t.CConst | T | t.CPtr链式写法将随上述演进被const rawptr[int]/const int*取代(当前仍可用)。
x: const int # const int
p: const char* # const char*(旧写法:t.CConst | t.CChar | t.CPtr)
存储类组合
存储类修饰可用前置软关键字(推荐)或链式标记:
static x: t.CInt = 0 # 等价于 x: t.CStatic | t.CInt → static int x = 0;
extern errno: t.CInt # 等价于 errno: t.CExtern | t.CInt → extern int errno;
static:符号仅限定义模块内可见,跨模块取值 / 调用 /from ... import一律编译期NameErrorfail-fast(定义侧登记 + 引用侧三处拦截)。extern:模块级变量生成@裸名 = external global T(external linkage、无初始值、无 storage),符号由外部(C 库 / 汇编 / 其它翻译单元)提供。- 旧写法
名字: static 类型(把static写在注解位置)已不再支持,会明确报错,请改为前置形式static 名字: 类型。
存储类标记(
t.CStatic/t.CExtern/t.CExport/t.State/t.CInline等)在联合类型提取类名时会被跳过,它们不是真实类型。
函数返回类型组合
新写法用软关键字;旧写法链式标记仍可用:
export def foo() -> int: # 导出函数,返回 int
return 0
extern def bar() -> void: # 外部函数声明
pass
t.State 声明性标记
t.State 是存储类标记(不是真实类型),承载 extern 软关键字的语义:extern def f(...) -> T 会被解析器改写为返回注解 T | t.State;它同样是 extern NAME: T 全局变量的 external 标记,以及 t.CEnum 成员的默认类型(i32)。函数体为 pass 时,编译器只生成函数原型(declare),不生成定义(define),由链接器在外部库中解析符号。
extern def CreateFileA(lpFileName: LPCSTR, dwDesiredAccess: ULONG,
dwShareMode: ULONG,
lpSecurityAttributes: SECURITY_ATTRIBUTES*,
dwCreationDisposition: ULONG,
dwFlagsAndAttributes: ULONG,
hTemplateFile: HANDLE) -> HANDLE:
pass
等价 C 声明:
HANDLE CreateFileA(LPCSTR, ULONG, ULONG, SECURITY_ATTRIBUTES*, ULONG, ULONG, HANDLE);
日常书写推荐用 extern def 软关键字(内部即改写为 t.State 标记),无需手写标记即可声明外部函数。详见 08-c-operations.md 的 FFI 章节 和 includes/w32 的 Win32 绑定。
数组类型
固定大小数组推荐用前缀写法 [N]T(等价 t.CArray[T, N] = rawarray,软关键字形态 array[T, N])(通常用于栈上和 BSS 段上)。N 可为模块级 define 常量或常量表达式(如 2*MAX);省略 N([]T)表示静态推导长度数组(退化为 T*);也支持多维嵌套。
- 裸用
array(无下标)、缺参 / 多参、长度非正整数常量都会 fail-fast。 list[T, N]的双参数形态是同义的定长数组写法(第 2 实参是元素个数,编译期正整数常量);list[T]单参才是泛型容器特化。因此list[Elem, N]仍是合法的定长数组,并非“旧写法”。
buf: [64]char # char buf[64]
ids: [10]int # int ids[10]
matrix: [16]float # float matrix[16]
entries: [256]idt_entry # struct idt_entry entries[256];
s: []char = [0x68, 0x69] # char s[] = {0x68, 0x69}; 由列表字面量推断长度 2
grid: [8][4]float # float grid[8][4]; 多维嵌套
seg: [BUF_SIZE]char # char seg[BUF_SIZE]; N 可为模块级 define 常量
旧写法
t.CArray[t.CChar, 64]/array[char, 64]与[64]char完全等价,手写可用但不推荐。
等价 C 代码:
char buf[64];
int ids[10];
float matrix[16];
struct idt_entry entries[256];
数组指针与函数指针
lp: [12]int* # int (*lp)[12] — 数组指针
vp: callable[[int, int], int] # 函数指针(带签名)
函数指针(callable):以下写法在类型域全部等价,统一生成 i8*(函数值按 TPV 惯例 bitcast 为 i8* 存储 / 传递):
| 写法 | 说明 |
|---|---|
t.Callable / c.Callable / Callable |
无签名(裸名) |
callable / Callable(裸名下标) |
无签名下标形:callable[] / callable[[]] |
t.Callable[[A, B], R] / callable[[A, B], R] |
带签名;仅额外登记返回类型 R,LLVM 类型仍是 i8* |
带签名形态会登记返回类型供间接调用使用;声明了函数指针变量却缺 t.Callable 注解时,间接调用会 fail-fast。
结构体类型
使用 class 定义结构体,成员通过类型注解声明(纯数据推荐用软关键字 struct,见 05-classes.md 的 struct 章节):
class Point:
x: int
y: int
等价 C 代码:
struct point {
int x;
int y;
};
带指针成员的结构体
class Node:
value: int
next: Node* # struct Node* next
匿名结构体成员(❌)
WARNING: 警告: 由于 Viper 在声明结构体,枚举等处不需要关键字,因此
t.Anonymouse是不需要使用的。此关键字不再维护,强行使用可能导致未定义行为。
继承 t.Anonymous 的类作为匿名成员嵌入:
class Header(t.Anonymous):
magic: uint32
version: uint32
class Packet:
Header # 匿名嵌入,可直接访问 Packet.magic
length: uint32
字节序标记
WARNING: 警告: 此关键字是实验性的。未经充分测试,不当使用可能导致未定义行为。
使用 t.BigEndian 或 t.LittleEndian 标记成员的字节序(仍需 |):
class NetworkPacket:
length: uint16 | t.BigEndian # 大端序存储
type: uint8 # 默认小端序
联合体类型
用软关键字 union(等价 class X(t.CUnion):)定义联合体:
union Value:
ival: int
fval: float
pval: void*
等价 C 代码:
union value {
int ival;
float fval;
void* pval;
};
枚举类型
用软关键字 enum(等价 class X(t.CEnum):)定义枚举:
enum Color:
RED, GREEN, BLUE # 裸名,自动编号 0,1,2
等价 C 代码:
enum Color {
RED = 0,
GREEN = 1,
BLUE = 2
};
枚举成员可直接使用名称:
c: int = Color.RED
REnum(标签联合体)
t.CEnum 是普通枚举(整数常量集合),用软关键字 renum 定义的是标签联合体(tagged union / Rust enum 风格)。每个变体可携带不同的 payload,通过 match 模式匹配分派。适用于少变体、无通用字段、match 分派的场景(如类型系统、IR 表示)。
renum LLVMType:
class Int:
Bits: int
class Void:
pass
match ty:
case LLVMType.Int:
snprintf(buf, size, "i%d", ty.Bits)
case LLVMType.Void:
string.strcpy(buf, "void")
REnum 的详细用法、与 CEnum/胖节点 AST 的对比见 05-classes.md 的 REnum 章节。
Typedef
用软关键字 typedef(等价 Name: t.CTypedef = ...)创建类型别名:
typedef IrqHandler = callable[[], int] # typedef int (*IrqHandler)();
typedef MyStruct = OriginalStruct
等价旧写法:
irq_handler_t: t.CTypedef | t.Callable[[], int]
MyStruct2: t.CTypedef = original_struct
typedef T1 = T2与T1: t.CTypedef = T2完全等价。
泛型类型(PEP 695 语法)
Viper 支持 PEP 695 泛型语法 class Name[T]:,定义类型参数化的类。类型参数 T 在类体内可作为占位类型使用,实例化时用 Name[ConcreteType] 特化。
novirtual class GSList[T]:
Head: T*
Tail: T*
Count: size_t
lst: GSList[GNode]* = &GSList[GNode]()
泛型类还支持递归泛型继承(如 class GNode(GSListNode[GNode])),实现强类型自引用结构,无需 void* + 转型。这是 C/C++/Rust 均无法如此简洁表达的能力。
泛型类的完整用法、递归继承、实例化示例见 05-classes.md 的泛型类章节。
隐式类型转换
右值赋给左值、传参、返回、二元运算对齐时,编译器按以下规则做隐式转换(统一入口 coerce_to_type);不满足则编译期 fail-fast(TypeError / CastError)。
| 源 → 目标 | 规则 |
|---|---|
None → 任意类型 |
全放行:指针目标 bitcast NULL;非指针目标物化零值 |
void → void |
放行(无值传递);void → 非 void 一律 fail-fast |
| 函数值 → 指针 | bitcast(传函数指针) |
| 整数 → 指针 | 禁止;唯一豁免编译期常量 0(空指针) |
| 指针 → 整数 | 禁止(须显式 uint64(p) 等) |
| 整数 ↔ 整数 | 同宽原样;拓宽 zext/sext;目标 i1 真值归一化 x != 0(不是截断取最低位);窄化 trunc(常量溢出 fail-fast) |
float ↔ float |
fpext / fptrunc |
| 整数 ↔ 浮点 | si2fp / fp2si(整数→浮点需浮点宽度 ≥ 整数宽度) |
| 浮点 ↔ 指针 | 禁止 |
| 聚合 ↔ 指针 | 禁止;唯一豁免构造物化(右值来自该类型构造调用,如 p: Point = Point()) |
| 指针 ↔ 指针 | 见下(级数 + pointee 兼容) |
指针 → 指针:
- 通用指针(
t.CPtr/i8*)与任意T*双向互转都放行; T* → U*(两侧都非通用指针)允许 ⇔ 级数相同且 pointeeT → U无损;非基本类型(结构体)只允许上转(源类派生自目标类),不相关结构体、向下转型、结构体 ↔ 基本类型一律禁止;- 非基本类型值与其指针之间一律禁止(构造物化除外);
- 完全相同的类型不算转换。
数组退让:源被指类型是定长数组 [N]T、目标被指类型恰为元素类型 T 时,允许单向隐式转换(数组指针 → 元素指针,[N]T* → T*,C/C++ 同口径)。
显式强转可以绕过上述限制(见下方「强制类型转换」)。
强制类型转换
Viper 的强制类型转换基于 t.CType 的用法,符合 Python 本意。继承自 t.CType 的类型可以直接用 call 转换,写法为 T(x, ...):
x: char* = "Hello"
p: void* = t.CVoid(x, t.CPtr) # (void*)x(旧链式写法,仍可用)
新写法:指针类型直接写 T*,因此多数强转可以省略;确需强转时用 T(x):
typedef VoidPtr = void*
y: VoidPtr = VoidPtr(x)
q: void* = x # void* ↔ 任意 T* 双向隐式可转,无需强转
对于比较、四则运算,可以不使用强制类型转换而依靠隐式类型转换;但严谨数学计算推荐显式强转,避免判断出错。此外,若将一个不同类型的右值赋值到左值,则会隐式将右值转换为左值的类型。
T*(x) 指针强转
C 风格强转可直接写 T*(x)——等价 (T*)(x)、(T*)x、(T | t.CPtr)(x)(四者产出同一 AST、同一 IR 路径):
node: Node* = Node*(child)
p: int* = int*(raw)
data: uint8* = uint8*(s)
歧义与约束(a * (b) 是合法乘法,所以判定必须是纯语法的):
T必须是类型原子(Name / Attribute / Subscript);T与*串、*串与(之间必须紧贴(无空白 / 无换行)。
⇒ T*(x) 中的 T 必须是类型名(class 原型、typedef、uint8 等类型原子);T 若解析不出类型,会在类型解析阶段 fail-fast。而 a * (b)、n * (m - 1)、(i + 1) * (j)(有空白,或前驱不是类型原子)仍按乘法解析。
生成的 IR 与其它强转同路径:指针↔指针 bitcast、整数→指针 inttoptr、指针→整数 ptrtoint、整数↔整数 zext/sext/trunc。
宏定义常量
使用 t.CDefine 定义编译时常量(新写法用软关键字 define X = v / define X: T = v)。t.CDefine 不是数据类型,而是编译时元指令——标记为 t.CDefine 的常量在 LLVM IR 中不生成任何全局变量,而是在引用处直接内联展开。详见 01-overview.md 中 t.CDefine 深度解析。
define MAX_SIZE = 1024 # 推荐:等价 MAX_SIZE: t.CDefine = 1024
define PAGE_SIZE: uint32 = 4096 # 带显式底层类型
FA_READ: t.CDefine = 0x01 # 旧写法等价
语义上等价于 C 的 #define:
#define MAX_SIZE 1024
#define PAGE_SIZE 4096
#define FA_READ 0x01
宏定义支持表达式:
define CPU_FEATURE_FPU = (1 << 0)
define CPU_FEATURE_APIC = (1 << 6)
宏定义也可与类型组合使用(新写法用 define X: T = v):
define CODE_SEG: uint16 = 0x08 # 等价 CODE_SEG: t.CDefine | t.CUInt16T = 0x08
位域
使用 t.Bit(n) 定义位域成员(仍需 |):
class Flags:
a: uint32 | t.Bit[1] # 此处,如果存入 2,二进制是 10,会被截断为 0
b: uint32 | t.Bit[3]
c: uint32 | 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