Files
Viper_Wiki/08-c-operations.md
T

12 KiB
Raw Blame History

08 - C 语言操作

Viper 通过 c 模块提供对 C 语言底层特性的访问,包括指针操作、内联汇编、预处理指令等。

书写规范:c.* 正在被逐步淘汰——指针操作用 & / *,内联汇编用 __asm__,内联 LLVM IR 用 __llvmir__,操作数标记用 asminp/asmout/irinp/irout。旧的 c.Addr/c.Asm/c.LLVMIR 等在过渡期仍可用。

指针操作

指针操作推荐使用 & / * 算子(c.Addr / c.Deref / c.DerefAs / c.Load 仍可用,属过渡期兼容写法)。

旧写法 新写法 说明
c.Addr(x) &x 取地址,结果类型为 T*
c.Deref(p) *p 解引用(按 pointee 类型 load)
c.DerefAs(p, v) *p = v 解引用写入
c.Load(p, q) *p = *q 内存拷贝(两侧解引用)

取地址 &

x: int = 42
p: int* = &x                     # int* p = &x;(&x 已是 T*,无需再写 t.CPtr 强转)

对结构体成员取地址:

res: int = fat32.opendir("/", &dp)           # res = fat32_opendir("/", &dp);

对数组取地址:

viperlib.snprintf(&buf, 64, "hello %d", 42)  # snprintf(&buf, 64, "hello %d", 42);

解引用 *

ptr: Sheet* = &sheet_obj
obj: Sheet = *ptr                # struct Sheet obj = *ptr;

解引用写入 *p = v 与内存拷贝 *p = *q

*p = value                       # c.DerefAs 的等价新写法
*p = *q                          # c.Load 的等价新写法

c.Set(target, value)(target = value)没有独立新语法,直接写普通赋值即可。

内联汇编 __asm__ / c.Asm

Viper 提供了声明式的内联汇编语法,编译为 GCC 风格的 __asm__ __volatile__ 语句。

__asm__(...) 是 c.Asm(...) 的语法糖(裸名调用,无需 import c),两者完全等价:

  • 操作数标记 asminp / asmout 等价于 c.AsmInp / c.AsmOut(裸名,无需 import c)
  • 约束常量可直接写 ASM_DESCR.XXX,等价于 t.ASM_DESCR.XXX(无需 import t)

旧写法 c.Asm / c.AsmInp / c.AsmOut / t.ASM_DESCR.XXX 全部保留可用。

基本用法

__asm__("nop")                          # __asm__ __volatile__("nop");

带操作数的汇编

使用 f-string 和 asmout / asminp 标记操作数:

saved_rdi: t.CUnsignedLong
__asm__(f"mov {asmout(saved_rdi, ASM_DESCR.OUTPUT_REG)}, rdi",
    op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RDI])

等价 C 代码:

__asm__ __volatile__(
    "mov %0, rdi"
    : "=r"(saved_rdi)
    : 
    : "memory", "rdi"
);

输入操作数 asminp

msg: const char* = "Hello"
__asm__(f"mov rdi, {asminp(msg, ASM_DESCR.REG_ANY)}",
    op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX])

完整示例

__asm__(f"""mov rdi, {asminp(msg, ASM_DESCR.REG_ANY)}
        call {asminp(log_info_fn, ASM_DESCR.REG_ANY)}""",
    op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX,
        ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX,
        ASM_DESCR.CLOBBER_RDI, ASM_DESCR.CLOBBER_RSI,
        ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9,
        ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11])

端口 I/O 示例

def outb(port: uint16, value: uint8):
    __asm__(f"""mov edx, {asminp(uint32(port), ASM_DESCR.REG_ANY)}
              mov eax, {asminp(uint32(value), ASM_DESCR.REG_ANY)}
              out dx, al""",
    op=[ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RAX])

ASM_DESCR 约束字符

下表为 ASM_DESCR(即 t.ASM_DESCR)的完整期望常量集;TPV 实际已映射子集见本节末。 常量既可写 ASM_DESCR.XXX,也可写 t.ASM_DESCR.XXX。

操作数修饰符

常量 值 说明
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

TPV 实际已映射子集:REG_ANY / REG_EAX..RDI / RAX..R15、OUTPUT_REG / OUTPUT_MEM / OUTPUT_EAX、INPUT_REG / INPUT_MEM / INPUT_EAX…、CLOBBER_MEMORY / CLOBBER_CC / CLOBBER_RAX..R15。REG_XMM / MEMORY / IMMEDIATE / ANY / MODIFIER_OUTPUT / READWRITE / INPUT / GLOBAL 等期望实现。

预处理指令

Viper 通过 c 模块的函数调用实现 C 预处理指令。

#define

define MAX_SIZE = 1024                  # #define MAX_SIZE 1024(推荐)

define X = Y / define X: T = Y 是推荐的宏定义写法(等价旧写法 X: t.CDefine = Y)。

调用式 c.CDefine("X", v) 不存在(期望)。

define 宏函数

define 软关键字与 def f(...) -> t.CDefine 声明的函数均为宏函数:存根保留完整函数体,调用点在编译期内联展开。

TPC 已实现;TransPyV 的展开尚未对齐。

条件编译

编译期条件编译的真实形态是 if 缩进块(已实现),块结束即 #endif:

if c.CIfdef(DEBUG):
    ...                                  # #ifdef DEBUG
if c.CIfndef(HEADER_H):
    ...                                  # #ifndef HEADER_H
if c.CIf(VERSION > 2):
    ...                                  # #if VERSION > 2

语句式 c.CEndif() / c.CElif(...) / c.CElse() 不存在,且暂不实现。

#undef

期望实现——当前不存在 c.Undef。

c.Undef(MACRO_NAME)                   # #undef MACRO_NAME

#error

期望实现——当前不存在 c.CError。

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)通过 extern / export 关键字声明,无需旧版注解:

  • extern def foo() -> T(函数体为 pass):仅声明。编译器只生成 declare 原型,不生成 define 函数体,由链接器在外部库中解析符号;
  • export def foo() -> T:以裸名导出函数定义。

extern / export 是语言层软关键字,解析器会把它们改写为返回注解中的存储类标记(extern → T | t.State,export → T | t.CExport)。t.State / t.CExtern / t.CExport 标记并未废弃,仍是这些关键字与 extern 全局变量的内部承载。

基本声明

extern def 仅声明(函数体为 pass);export def 以裸名导出:

extern def isr0() -> t.CVoid: pass        # extern void isr0();  — 来自汇编或外部
extern def getchar() -> t.CInt: pass      # extern int getchar(); — 来自 libc
export def foo() -> t.CInt: pass          # export int foo();    — 裸名导出(define)

编译为 LLVM IR:

declare void @"isr0"()        ; extern:不加 SHA1 前缀,仅 declare
declare i32  @"getchar"()
define i32  @"foo"() { ... } ; export:不加 SHA1 前缀,裸名 define

Win32 API 绑定示例

includes/w32 中的 Win32 绑定以 extern 关键字声明,零特殊语法:

extern def CreateFileA(lpFileName: LPCSTR, dwDesiredAccess: ULONG,
                dwShareMode: ULONG,
                lpSecurityAttributes: SECURITY_ATTRIBUTES | t.CPtr,
                dwCreationDisposition: ULONG,
                dwFlagsAndAttributes: ULONG,
                hTemplateFile: HANDLE) -> HANDLE:
    pass

等价 C 声明:

HANDLE CreateFileA(LPCSTR, ULONG, ULONG, SECURITY_ATTRIBUTES*,
                   ULONG, ULONG, HANDLE);

链接时通过 project.json 指定外部库(如 kernel32.lib、user32.lib),链接器解析这些符号。

修饰方式

写法 SHA1 前缀 生成内容 用途
无修饰(普通函数) ✅ 加前缀 declare + define 模块私有函数
extern def f() ❌ 不加 仅 declare FFI 外部函数声明(引用外部)
export def f() ❌ 不加 裸名 define 对外导出 API

新关键字等价写法:无修饰 = 普通函数加 SHA1 前缀;extern = 仅 declare;export = 裸名 define。旧的 t.CExtern / t.CExport / t.State 标记在内部仍承载这些关键字,手写可用但不推荐。

extern 重名(临时规则,待修正):多个模块声明同名 extern 符号时,调用点按依赖 / 导入顺序取「最近」的那个实体;请避免多模块重复声明同名 extern,该行为日后会修正。详见 10-imports.md 的跨模块约束。

声明外部全局变量

外部全局变量用 extern 软关键字声明(推荐),或等价的 t.CExtern 标记:

extern errno: t.CInt             # extern int errno;(推荐)
fd: t.CExtern | t.CInt           # 等价写法

模块级 extern 全局生成 @裸名 = external global T(external linkage、无初始值、无 storage),符号由外部单元提供。

详见 02-type-system.md 的 t.State 章节 和 includes/w32 的 Win32 绑定实现。

LLVM IR 内联

__llvmir__ / c.LLVMIR

__llvmir__(...) 是 c.LLVMIR(...) 的语法糖(裸名调用,无需 import c),两者完全等价。

设计期望无返回值(void),直接嵌入原始 LLVM IR 文本(不做指令解析);当前过渡期调用可带第二类型参数取得返回值(llvmir_test 用法),后续以无返回值为准并修订代码:

__llvmir__(f"{irout(r)} = add i32 {irinp(a)}, {irinp(b)}")

irinp / irout(c.LInp / c.LOut)

标记 LLVM IR 的输入/输出操作数,裸名写法与 c. 前缀写法等价:

irinp(expr)     # 输入操作数:替换为 expr 的 LLVM 值引用文本(%x 或常量文本)
irout(expr)     # 输出操作数:为 expr 变量分配唯一 SSA 寄存器 %llvmirN,
                # 模板嵌入后自动追加 store 写回变量 alloca

旧写法 c.LLVMIR / c.LInp / c.LOut 全部保留可用。

运算符重载

对象类支持运算符重载,详见 06-oop.md 中 运算符重载。