8.1 KiB
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 差,实际工程中不推荐使用条件宏,后期将完善宏的设计,引入模板等。
FFI 外部函数声明
Viper 的 FFI(Foreign Function Interface)无需任何特殊语法——仅靠返回类型注解中的 t.State 标记即可声明外部函数。t.State 是声明性标记类型,表示"仅声明不定义"(语义等价于 t.CExport | t.CExtern),编译器只生成 declare 原型,不生成 define 函数体,由链接器在外部库中解析符号。
基本声明
函数体为 pass,返回类型用 RetType | t.State:
def isr0() -> t.CVoid | t.State: pass # extern void isr0(); — 来自汇编或外部
def getchar() -> t.CInt | t.State: pass # extern int getchar(); — 来自 libc
编译为 LLVM IR:
declare void @"isr0"() ; 不加 SHA1 前缀(t.State 含 CExport 语义)
declare i32 @"getchar"()
Win32 API 绑定示例
includes/w32 中的 Win32 绑定全部通过 t.State 声明,零特殊语法:
def CreateFileA(lpFileName: LPCSTR, dwDesiredAccess: ULONG,
dwShareMode: ULONG,
lpSecurityAttributes: SECURITY_ATTRIBUTES | t.CPtr,
dwCreationDisposition: ULONG,
dwFlagsAndAttributes: ULONG,
hTemplateFile: HANDLE) -> HANDLE | t.State:
pass
等价 C 声明:
HANDLE CreateFileA(LPCSTR, ULONG, ULONG, SECURITY_ATTRIBUTES*,
ULONG, ULONG, HANDLE);
链接时通过 project.json 指定外部库(如 kernel32.lib、user32.lib),链接器解析这些符号。
t.State 与 t.CExtern 的区别
| 修饰 | SHA1 前缀 | 生成内容 | 用途 |
|---|---|---|---|
| 无修饰 | ✅ 加前缀 | declare + define |
模块私有函数 |
t.CExtern |
❌ 不加 | declare(引用外部) |
引用外部 C 函数 |
t.CExport |
❌ 不加 | declare + define |
对外暴露 API |
t.State |
❌ 不加 | 仅 declare |
FFI 外部函数声明(= CExport | CExtern) |
声明外部全局变量
外部全局变量通过 t.CExtern 注解声明:
errno: t.CExtern | t.CInt # extern int errno;
详见 02-type-system.md 的 t.State 章节 和 includes/w32 的 Win32 绑定实现。
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 中 运算符重载。