7.9 KiB
04 - 函数定义与调用
Viper 的函数定义使用 Python 的 def 语法,通过类型注解指定参数类型和返回类型。
类型书写规范:优先用短名别名(
int/uchar/uint64/bool…)与T*指针,尽量少用|。详见 02-type-system.md 类型书写规范。
基本函数定义
def add(a: int, b: int) -> int:
return a + b
等价 C 代码:
int add(int a, int b) {
return a + b;
}
无返回值函数
def print_msg(msg: const char*) -> void:
serial.puts(msg)
等价 C 代码:
void print_msg(const char* msg) {
serial_puts(msg);
}
函数修饰符
以下均为语言层软关键字(解析器识别,内部改写为既有标记)。
导出函数 export
export 控制符号可见性——不加 SHA1 前缀,裸名导出、全局可见。详见 01-overview.md 中 t.CExport 深度解析。
export def _start() -> int:
return 0
外部函数声明 extern
extern 表示仅声明不定义(实现来自外部):
extern def isr0() -> void: pass
extern def isr1() -> void: pass
等价 C 代码:
extern void isr0();
extern void isr1();
说明:
extern/export会把返回注解改写成存储类标记(extern→T | t.State,export→T | t.CExport)。因此t.State/t.CExtern/t.CExport标记并未废弃——它们是这些关键字、extern全局变量与CEnum成员的内部承载,手写标记仍然可用。
内联函数 inline
内联推荐用软关键字 inline(合成为 @t.attr.always_inline,即 LLVM alwaysinline):
inline def fast_add(a: int, b: int) -> int:
return a + b
旧的 t.CInline 只是存储类标记,不产生调用点展开:
def fast_add2(a: int, b: int) -> int | t.CInline:
return a + b
宏函数 define
宏函数有两种等价写法:软关键字 define f(...)(自动追加 t.CDefine 返回注解),或 def f(...) -> t.CDefine。存根保留完整函数体;调用点按编译期内联宏展开。
define MAKE_FLAG(bit) -> t.CDefine:
return (1 << bit)
def MAKE_FLAG2(bit) -> t.CDefine: # 等价旧写法
return (1 << bit)
注意区分:
define F(...)是宏函数(函数体在调用点展开);define X: T = Y/define X = Y是编译期常量(见 03-variables.md 常量定义)。两者名字都叫define,但一个带(、一个带=,解析器据此区分。
静态函数 static
static def helper() -> void: # 前置软关键字(推荐)
pass
def helper2() -> void | t.CStatic: # 等价链式标记写法
pass
static 函数仅限定义模块内可见,跨模块调用 / 取值 / from ... import 一律编译期 NameError fail-fast。
类型别名 typedef
typedef Callback = callable[[int, int], int] # 推荐
irq_handler_t: t.CTypedef | t.Callable[[], int] # 等价旧写法
typedef T1 = T2 与 T1: t.CTypedef = T2 完全等价。
函数属性装饰器
推荐用 @__attribute__(...)(比 GCC 少一层括号,实参脱糖为 t.attr.x);也可用 @c.Attribute(t.attr....()):
@__attribute__(section(".text.startup"), aligned(16))
def _start() -> int:
return 0
@c.Attribute(t.attr.always_inline()) # 等价旧写法
def hot_path() -> int:
return 0
等价 C 代码:
__attribute__((section(".text.startup"), aligned(16)))
int _start() {
return 0;
}
LLVM 函数属性
在返回类型注解中通过 t.attr.llvm 指定 LLVM 属性:
def foo() -> int | t.attr.llvm.nobuiltin | t.attr.llvm.nounwind:
return 0
已支持(名称映射到 LLVM 属性):always_inline→alwaysinline、noinline、noreturn、pure→readonly、const→readnone、nounwind、noredzone、stackrealign、willreturn、mustprogress。
t.attr.llvm.xxx 也可写成直接形态 @t.attr.xxx(不加括号,如 @t.attr.nounwind);@__attribute__(...) 会被解析层 desugar 为等价的 @c.Attribute(...)。section("...") 走属性组而非普通函数属性;packed / aligned / visibility / weak 不作为函数属性支持。示例中的 nobuiltin 尚未映射(会被静默丢弃)。
多返回值(-> tuple[...])
使用返回注解 -> tuple[...] 实现多返回值,返回多个值打包为 LLVM struct 返回,调用处用元组解包接收:
def divmod(a: int, b: int) -> tuple[int, int]:
q: int = a // b
r: int = a % b
return q, r
q, r = divmod(a, b)
规则:
- 返回注解中
tuple[...]的类型数量决定返回值个数; - 必须使用左右值(元组解包)赋值,返回值不能直接传入函数;
@c.CReturn装饰器已移除:源码中任何地方出现@c.CReturn(...)都会编译期报错(fail-fast),请改用-> tuple[...]。
指针参数
def read_data(buf: char*, size: size_t) -> int:
pass
等价 C 代码:
int read_data(char* buf, size_t size);
输出参数模式
用 & 传入变量地址作为输出参数(取代 c.Addr):
res: int = fat32.opendir("/", &dp)
等价 C 代码:
int res = fat32_opendir("/", &dp);
函数指针类型
用 callable / t.Callable 定义函数指针类型(统一为 i8*):
typedef Callback = callable[[int, int], void] # 带签名
irq_handler_t: t.Callable[[], int] # 旧写法等价
默认参数
Viper 支持函数默认参数值:
def create_window(x: int, y: int, w: int, h: int, title: const char* = "Window") -> int:
pass
return 语句
return 0 # return 0;
return # return;(void 函数)
return a, b # 多返回值(需配合 -> tuple[...] 返回注解)
全局变量声明
函数外的变量声明为全局变量,函数内通过 global 关键字访问:
BootInfo: bootinfo*
def init() -> void:
global BootInfo
BootInfo = saved_rdi
支持 func-in-func 嵌套函数。注意:模块级已有同名变量时,函数内不得再用类型注解声明同名变量(详见 03-variables.md 重复声明规则)。
函数调用
函数调用最好通过顺序调用来实现:
def add(x: int, y: int, z: int):
return (x + y) * z
export def main() -> int:
print(add(1, 2, 3)) # (1 + 2) * 3 = 9
这也是 C 中的标准实现方法;此外也可以乱序传参:
export def main() -> int:
print(add(z=3, x=1, y=2)) # (1 + 2) * 3 = 9
乱序传参对结构体初始化同样有效。默认值也可用于结构体成员:
def add2(x: int, y: int, z: int = 1):
return (x + y) * z
export def main2() -> int:
print(add2(1, 2)) # (1 + 2) * 1 = 3
闭包(嵌套函数)
函数内定义函数会被提升为顶层 __closure_<name>(i8* %env, args...),env 是一个指针数组;编译器隐式扫描并捕获外层局部变量(env 建在堆上)。函数体内用 nonlocal 声明要修改的外层变量。
变参函数
使用 Python 的 *args 声明变参:
def log(fmt: const char*, *args) -> void:
...
编译器为 *args 建立 24 字节 va_list 缓冲并发射 llvm.va_start;读取用 va_arg / arg(type) 糖函数。Viper 没有 t.CVararg 之类标记,变参只有 *args 一种形态。
函数指针
函数指针类型用 t.Callable(或裸名 callable / Callable)表示,统一为 i8*;详见 02-type-system.md 的数组指针与函数指针。