V 编译器 v3 ownership 模式:编译与使用指南
上一篇文章梳理了 v3 的内存管理(scoped bump arena)。这一篇接着写 v3 的另一条主线:ownership checker------它怎么打开,怎么编译出来,怎么用。
一、为什么需要单独编译
v3 的标准二进制把 $if ownership ? 块全部编译裁掉了:
The standard v3 executable is built without
-d ownership, so the ownership checker and its analysis stages are compiled out. It rejects both-ownershipand-d ownership; the main V driver builds a separate ownership-enabled v3 executable only for an explicitv -ownershipinvocation.
所以 ./v3 -ownership foo.v 会直接报错:
csharp
ownership support is not compiled into this v3 executable
正确做法是:先用主 v 编译出一个独立的 v3_ownership 二进制,再用它去编用户程序。
二、编译 v3_ownership
在仓库根目录执行:
sh
./v -gc none -d ownership -o v3_ownership vlib/v3/v3.v
参数含义:
| 参数 | 作用 |
|---|---|
-gc none |
v3 拒绝任何 GC,必须显式 |
-d ownership |
编译期 $define ownership,让 v3 源码里的所有 $if ownership ? 块全部编入 |
-o v3_ownership |
产物文件名(任意) |
vlib/v3/v3.v |
v3 编译器入口 |
这条命令正是 cmd/v/v.v 里 launch_v3_ownership_compiler() 调用的同一条:
v
compilation_command := '${os.quoted_path(vexe)} -gc none -d ownership -o ${os.quoted_path(v3_exe)} ${os.quoted_path(v3_main_source)}'
如果走 v -ownership 驱动,它会自动把编译产物缓存到 $VTMP/v/delegated_v3/<vroot-hash>/v3_ownership,无需手动管理。
产物对比
| 二进制 | 大小 | 说明 |
|---|---|---|
v |
~6.3 MB | 主 v 驱动 |
v2 / v3 |
~5.9 MB | 标准 v3(不含 ownership) |
v3_ownership |
~9.1 MB | 带 ownership checker 的 v3 |
因为 -d ownership 把 types/scope.v 的 scope 路径、types/checker_parallel.v 的 fork 处理、ownership drop 等大块代码都拉进来了,所以 v3_ownership 比标准 v3 大 50%+。
三、v3_ownership 怎么用
虽然叫 v3_ownership,但内部命令名仍是 v3,CLI 跟 v 几乎一样:
sh
./v3_ownership [run|test] <file.v|directory> [options]
关键:必须加 -ownership 才会真正启用 ownership 检查。不加就和普通 v3 没区别(checker 已经在二进制里了,但运行时开关没打开)。
基本用法
sh
# 编译一个程序(带 ownership 检查)
./v3_ownership -ownership -o my_prog my_prog.v
# 编译并直接跑
./v3_ownership -ownership run my_prog.v
# 跑测试
./v3_ownership -ownership test my_pkg/
# 只生成 C,不链接(看 codegen)
./v3_ownership -ownership -o my_prog.c my_prog.v
通用选项
| 选项 | 说明 |
|------------------------------|----------------|------|----------|----|
| -o <output> | 输出二进制或 .c 文件 |
| `-b <c | arm64 | wasm | eval>` | 后端 |
| -os <name> -arch <name> | 交叉编译目标 |
| -cc <compiler> | C 编译器 |
| -prod -c99 -shared -strict | C 构建模式 |
| -v | 详细阶段计时 |
| -no-memory-limit | 关闭 10 GiB 内存保护 |
| -d <name> | 编译期 define |
四、几个容易踩的坑
1. -ownership ≠ -d ownership
- 编译期
-d ownership:让 v3 自己的$if ownership ?块参与编译。这是构建 v3_ownership 那一步要传的参数。 - 运行时
-ownership:决定这一次编译 是否走 ownership checker,且只在v3_ownership这类二进制上才有意义。 v3.v里的注释特意强调运行时-ownership不能把ownership暴露给目标程序的$if或_d_ownership.v文件------它只加载 builtin overlays。
vlib/v3/tests/drop_codegen_test.v 里的写法(编目标程序时)就是 -d ownership 裸 define 形式:
v
compile := os.execute('${v3_bin} ${src} -d ownership -b c -o ${out}')
跟 -ownership 一样能激活 checker,只是作用层次不同。
2. -ownership 只支持直接编译
cmd/v/v.v 里的 is_ownership_relevant_command() 会拒绝 run / crun:
go
v: `-ownership` currently supports direct compilation only. Use `v -ownership module_dir`.
如果你想编译完直接跑,得用 ./v3_ownership -ownership run ...(绕过 v 驱动),或者拆成两步。
3. 复杂模块可能触发 codegen bug
我拿 net.http 那个 ip_check.v 试了一下,v3_ownership 编它的时候会在两处 drop 点崩:
rust
src.c:31616:21: error: member reference base type 'int' is not a structure or union
t->cur_size -= (0).size;
这是 ownership pass 在「作用域退出时释放数组」的路径上,把 struct 当成 literal 0 在引用。标准 v3 不会触发,v3_ownership 会------看起来是当前 ownership checker 的一个边界 bug(与 VMemoryBlock / scope drop 相关),不是二进制本身的问题。最小复现一般能稳定出现,建议提 issue。
五、一个跑通的最小例子
/tmp/v3own_demo/main.v:
v
module main
struct Box {
mut:
value int
}
fn make_box(v int) &Box {
return &Box{value: v}
}
fn main() {
b := make_box(42)
println('box: ${b.value}')
}
sh
$ ./v3_ownership -ownership -o /tmp/v3own_demo/box /tmp/v3own_demo/main.v
$ /tmp/v3own_demo/box
box: 42
ownership pass 跑完生成的 C 代码,在 &Box{...} 离开 make_box 时按 checker 规则正确释放,没有 leak。
六、总结
| 步骤 | 命令 |
|---|---|
| 1. 编译 v3_ownership(一次性) | ./v -gc none -d ownership -o v3_ownership vlib/v3/v3.v |
| 2. 用它编你的模块 | ./v3_ownership -ownership -o my_prog my_prog.v |
| 3. (或)走 v 驱动自动维护 | v -ownership my_prog_dir/ |
核心点就两个:
- 标准 v3 不带 ownership checker ------必须先用
./v -gc none -d ownership -o v3_ownership vlib/v3/v3.v编出专用二进制。 - 运行时开关
-ownership必须显式传------否则二进制就当普通 v3 用。
至于 ownership checker 自己的规则、move semantics、borrow check 的细节,下一篇再展开。