Files
Viper_Wiki/04-functions.md
T

7.9 KiB
Raw Blame History

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)

规则:

  1. 返回注解中 tuple[...] 的类型数量决定返回值个数;
  2. 必须使用左右值(元组解包)赋值,返回值不能直接传入函数;
  3. @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 的数组指针与函数指针。