# 04 - 函数定义与调用 Viper 的函数定义使用 Python 的 `def` 语法,通过类型注解指定参数类型和返回类型。 > 类型书写规范:优先用短名别名(`int`/`uchar`/`uint64`/`bool`…)与 `T*` 指针,尽量少用 `|`。详见 [02-type-system.md 类型书写规范](02-type-system.md#类型书写规范新语法推荐)。 ## 基本函数定义 ```python def add(a: int, b: int) -> int: return a + b ``` 等价 C 代码: ```c int add(int a, int b) { return a + b; } ``` ## 无返回值函数 ```python def print_msg(msg: const char*) -> void: serial.puts(msg) ``` 等价 C 代码: ```c void print_msg(const char* msg) { serial_puts(msg); } ``` ## 函数修饰符 以下均为**语言层软关键字**(解析器识别,内部改写为既有标记)。 ### 导出函数 `export` `export` 控制符号可见性——不加 SHA1 前缀,裸名导出、全局可见。详见 [01-overview.md 中 t.CExport 深度解析](01-overview.md#tcexport-深度解析)。 ```python export def _start() -> int: return 0 ``` ### 外部函数声明 `extern` `extern` 表示仅声明不定义(实现来自外部): ```python extern def isr0() -> void: pass extern def isr1() -> void: pass ``` 等价 C 代码: ```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`): ```python inline def fast_add(a: int, b: int) -> int: return a + b ``` 旧的 `t.CInline` 只是存储类标记,**不产生调用点展开**: ```python def fast_add2(a: int, b: int) -> int | t.CInline: return a + b ``` ### 宏函数 `define` 宏函数有两种等价写法:软关键字 `define f(...)`(自动追加 `t.CDefine` 返回注解),或 `def f(...) -> t.CDefine`。存根保留完整函数体;调用点按编译期内联宏展开。 ```python 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 常量定义](03-variables.md#常量定义))。两者名字都叫 `define`,但一个带 `(`、一个带 `=`,解析器据此区分。 ### 静态函数 `static` ```python static def helper() -> void: # 前置软关键字(推荐) pass def helper2() -> void | t.CStatic: # 等价链式标记写法 pass ``` `static` 函数**仅限定义模块内可见**,跨模块调用 / 取值 / `from ... import` 一律编译期 `NameError` fail-fast。 ### 类型别名 `typedef` ```python 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....())`: ```python @__attribute__(section(".text.startup"), aligned(16)) def _start() -> int: return 0 @c.Attribute(t.attr.always_inline()) # 等价旧写法 def hot_path() -> int: return 0 ``` 等价 C 代码: ```c __attribute__((section(".text.startup"), aligned(16))) int _start() { return 0; } ``` ## LLVM 函数属性 在返回类型注解中通过 `t.attr.llvm` 指定 LLVM 属性: ```python 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 返回,调用处用元组解包接收: ```python 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) ``` 规则: 1. 返回注解中 `tuple[...]` 的类型数量决定返回值个数; 2. 必须使用左右值(元组解包)赋值,返回值不能直接传入函数; 3. `@c.CReturn` 装饰器**已移除**:源码中任何地方出现 `@c.CReturn(...)` 都会编译期报错(fail-fast),请改用 `-> tuple[...]`。 ## 指针参数 ```python def read_data(buf: char*, size: size_t) -> int: pass ``` 等价 C 代码: ```c int read_data(char* buf, size_t size); ``` ### 输出参数模式 用 `&` 传入变量地址作为输出参数(取代 `c.Addr`): ```python res: int = fat32.opendir("/", &dp) ``` 等价 C 代码: ```c int res = fat32_opendir("/", &dp); ``` ## 函数指针类型 用 `callable` / `t.Callable` 定义函数指针类型(统一为 `i8*`): ```python typedef Callback = callable[[int, int], void] # 带签名 irq_handler_t: t.Callable[[], int] # 旧写法等价 ``` ## 默认参数 Viper 支持函数默认参数值: ```python def create_window(x: int, y: int, w: int, h: int, title: const char* = "Window") -> int: pass ``` ## return 语句 ```python return 0 # return 0; return # return;(void 函数) return a, b # 多返回值(需配合 -> tuple[...] 返回注解) ``` ## 全局变量声明 函数外的变量声明为全局变量,函数内通过 `global` 关键字访问: ```python BootInfo: bootinfo* def init() -> void: global BootInfo BootInfo = saved_rdi ``` 支持 func-in-func 嵌套函数。注意:模块级已有同名变量时,函数内**不得再用类型注解声明**同名变量(详见 [03-variables.md 重复声明规则](03-variables.md#重复声明规则硬约定))。 ## 函数调用 函数调用最好通过顺序调用来实现: ```python 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 中的标准实现方法;此外也可以乱序传参: ```python export def main() -> int: print(add(z=3, x=1, y=2)) # (1 + 2) * 3 = 9 ``` 乱序传参对结构体初始化同样有效。默认值也可用于结构体成员: ```python 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_(i8* %env, args...)`,`env` 是一个指针数组;编译器隐式扫描并捕获外层局部变量(`env` 建在堆上)。函数体内用 `nonlocal` 声明要修改的外层变量。 ## 变参函数 使用 Python 的 `*args` 声明变参: ```python 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 的数组指针与函数指针](02-type-system.md#数组指针与函数指针)。