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

8.1 KiB
Raw Blame History

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 的 FFIForeign 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.libuser32.lib),链接器解析这些符号。

t.Statet.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 中 运算符重载