docs: 补全 Aster 中文调用链、控制流与生命周期注释 #3

Merged
Zayden-Jung merged 62 commits from codex/detailed-chinese-comments into main 2026-11-15 12:13:30 +00:00
Showing only changes of commit bf3313f58d - Show all commits
+93
View File
@@ -391,9 +391,13 @@ fn relative_branches_and_back_edges_execute_a_loop() {
}
#[test]
/// 验证直接默认参数和嵌套显式参数的对象结果均新增一份拥有根,归还后恢复原计数。
fn nested_language_calls_bind_defaults_and_return_owned_argument_objects() {
// 独立 VM 隔离调用帧、全局与根计数断言。
let mut vm = Vm::new().unwrap();
// 函数执行环境的拥有根,函数也经载荷强边持有该字典。
let globals = vm.new_dict().unwrap();
// 单参数源码函数只读参数再返回,默认值与显式实参应走相同拥有结果协议。
let identity = make_code(
&mut vm,
&[op(Opcode::LoadFast, 0), simple(Opcode::ReturnValue)],
@@ -404,7 +408,9 @@ fn nested_language_calls_bind_defaults_and_return_owned_argument_objects() {
..CodeLayout::default()
},
);
// 默认字符串先有测试根,函数默认值表另持内部边。
let default = vm.new_string("default").unwrap();
// 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。
let function = vm
.new_function(
"identity",
@@ -414,15 +420,20 @@ fn nested_language_calls_bind_defaults_and_return_owned_argument_objects() {
vec![],
)
.unwrap();
// 保存调用前计数,结果交付多一根,归还结果后必须恢复。
let before = vm.ref_count(default).unwrap();
// 函数把已有参数/默认值作为结果,身份不变但结果仍取得独立拥有根。
let returned = vm.call(function, &[]).unwrap();
assert_eq!(returned, Value::Object(default));
assert_eq!(vm.ref_count(default).unwrap(), before + 1);
// 只归还本次调用结果根,不触碰默认表/常量/测试输入的所有权。
vm.release_value(returned).unwrap();
assert_eq!(vm.ref_count(default).unwrap(), before);
vm.dict_set(globals, text("identity"), Value::Object(function))
.unwrap();
// 显式实参放进包装代码常量表,也有独立测试根。
let argument = vm.new_string("argument").unwrap();
// 包装指令按函数、Null、参数布局调用 identity,检验跨两个源码帧的引用转交。
let wrapper = make_code(
&mut vm,
&[
@@ -438,18 +449,25 @@ fn nested_language_calls_bind_defaults_and_return_owned_argument_objects() {
..CodeLayout::default()
},
);
// 保存调用前计数,结果交付多一根,归还结果后必须恢复。
let before = vm.ref_count(argument).unwrap();
// 临时执行函数与嵌套帧清理后,结果对象仍须保持拥有。
let returned = vm.execute(wrapper, globals).unwrap();
assert_eq!(returned, Value::Object(argument));
assert_eq!(vm.ref_count(argument).unwrap(), before + 1);
// 只归还本次调用结果根,不触碰默认表/常量/测试输入的所有权。
vm.release_value(returned).unwrap();
assert_eq!(vm.ref_count(argument).unwrap(), before);
}
#[test]
/// 参数过少/过多及异堆参数均在帧提交前拒绝,函数根计数不得因失败增加。
fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() {
// 独立 VM 隔离调用帧、全局与根计数断言。
let mut vm = Vm::new().unwrap();
// 函数执行环境的拥有根,函数也经载荷强边持有该字典。
let globals = vm.new_dict().unwrap();
// 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。
let code = make_code(
&mut vm,
&[op(Opcode::LoadFast, 0), simple(Opcode::ReturnValue)],
@@ -460,6 +478,7 @@ fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() {
..CodeLayout::default()
},
);
// 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。
let function = vm
.new_function("identity", code, globals, vec![], vec![])
.unwrap();
@@ -479,8 +498,11 @@ fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() {
..
})
));
// 另一堆产生真正有效但归属不同的参数,不能当本堆对象保留。
let mut other = Vm::new().unwrap();
// 异堆参数在执行源码之前拒绝,不改变当前函数计数。
let foreign = other.new_object().unwrap();
// 保存调用前计数,结果交付多一根,归还结果后必须恢复。
let before = vm.ref_count(function).unwrap();
assert_eq!(
vm.call(function, &[Value::Object(foreign)]),
@@ -490,10 +512,15 @@ fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() {
}
#[test]
/// BuildMap 返回根在代码/全局释放后独立保活结果字典和子对象,释放结果才清空图。
fn freshly_built_maps_and_their_children_survive_frame_cleanup() {
// 独立 VM 隔离调用帧、全局与根计数断言。
let mut vm = Vm::new().unwrap();
// 函数执行环境的拥有根,函数也经载荷强边持有该字典。
let globals = vm.new_dict().unwrap();
// 子字符串先由测试根和代码常量持有,最终转由结果字典边保活。
let child = vm.new_string("owned by result").unwrap();
// 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。
let code = make_code(
&mut vm,
&[
@@ -505,10 +532,15 @@ fn freshly_built_maps_and_their_children_survive_frame_cleanup() {
vec![text("child"), Value::Object(child)],
CodeLayout::default(),
);
// 归还测试根,代码常量在实际执行前仍维持子对象存活。
vm.release(child).unwrap();
// 执行结果拥有根可跨越代码与全局生命周期。
let result = vm.execute(code, globals).unwrap();
// 移除代码常量边后,返回图必须自己保留所需对象;不是只靠旧帧保活。
vm.release(code).unwrap();
// 归还环境测试根,临时执行函数已清理,结果不能依赖其持续存在。
vm.release(globals).unwrap();
// 结果字典只有一份外部根,其 child 值是独立内部强边。
let Value::Object(dictionary) = result else {
panic!("expected a map");
};
@@ -518,10 +550,12 @@ fn freshly_built_maps_and_their_children_survive_frame_cleanup() {
Some(Value::Object(child))
);
assert_eq!(vm.str(child).unwrap(), "owned by result");
// 结果最后根归还,子对象的最后字典边随之释放,堆应归零。
vm.release(dictionary).unwrap();
assert_eq!(vm.object_count(), 0);
}
/// 原生辅助只借用返回首参数,空参数错误;对象结果拥有根由 VM 补充。
fn echo(arguments: &[Value]) -> Result<Value, VmError> {
arguments
.first()
@@ -530,15 +564,21 @@ fn echo(arguments: &[Value]) -> Result<Value, VmError> {
}
#[test]
/// 原生 echo 返回临时 BuildMap 实参,输入弹栈前必须先保留结果根。
fn native_return_of_a_temporary_argument_is_retained_before_inputs_are_popped() {
// 独立 VM 隔离调用帧、全局与根计数断言。
let mut vm = Vm::new().unwrap();
// 函数执行环境的拥有根,函数也经载荷强边持有该字典。
let globals = vm.new_dict().unwrap();
// 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。
let function = vm
.allocate(EndorNativeFunctionObject::new("echo", echo))
.unwrap();
vm.dict_set(globals, text("echo"), Value::Object(function))
.unwrap();
// 调用前/后按场景归还测试函数根;全局字典可能仍持有函数内部边。
vm.release(function).unwrap();
// 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。
let code = make_code(
&mut vm,
&[
@@ -554,27 +594,36 @@ fn native_return_of_a_temporary_argument_is_retained_before_inputs_are_popped()
..CodeLayout::default()
},
);
// 执行结果拥有根可跨越代码与全局生命周期。
let result = vm.execute(code, globals).unwrap();
// 移除代码常量边后,返回图必须自己保留所需对象;不是只靠旧帧保活。
vm.release(code).unwrap();
// 归还环境测试根,临时执行函数已清理,结果不能依赖其持续存在。
vm.release(globals).unwrap();
assert_eq!(vm.object_count(), 1);
vm.release_value(result).unwrap();
assert_eq!(vm.object_count(), 0);
}
/// 原生辅助恒定报错,使执行器在 Call 处回溯与清理临时参数。
fn fail(_arguments: &[Value]) -> Result<Value, VmError> {
Err(VmError::InvalidArgument("host failure"))
}
#[test]
/// 错误后对象数恢复调用前,回溯保留故障 Call 偏移及显式代码位置元数据。
fn native_failure_unwinds_temporary_roots_and_keeps_execution_locations() {
// 独立 VM 隔离调用帧、全局与根计数断言。
let mut vm = Vm::new().unwrap();
// 函数执行环境的拥有根,函数也经载荷强边持有该字典。
let globals = vm.new_dict().unwrap();
// 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。
let function = vm
.allocate(EndorNativeFunctionObject::new("fail", fail))
.unwrap();
vm.dict_set(globals, text("fail"), Value::Object(function))
.unwrap();
// 附显式名称、文件和起始行,用故障回溯断言实际保存的元数据。
let code = EndorCode::from_instructions(
&[
op(Opcode::LoadGlobal, 0),
@@ -591,7 +640,9 @@ fn native_failure_unwinds_temporary_roots_and_keeps_execution_locations() {
)
.unwrap()
.with_location("outer", "demo.endor", 12);
// 将已验证代码分配为拥有根,临时执行函数之后另外持代码边。
let code = vm.new_code(code).unwrap();
// 记录代码/全局等场景根数,失败后不得残留临时字典、函数或帧根。
let before = vm.object_count();
assert_eq!(
vm.execute(code, globals),
@@ -605,9 +656,13 @@ fn native_failure_unwinds_temporary_roots_and_keeps_execution_locations() {
}
#[test]
/// 递归源码调用由显式帧向量推进,触及深度硬上限后全部执行根恢复,环境环可显式回收。
fn recursion_uses_explicit_frames_and_unwinds_at_the_frame_limit() {
// 独立 VM 隔离调用帧、全局与根计数断言。
let mut vm = Vm::new().unwrap();
// 函数执行环境的拥有根,函数也经载荷强边持有该字典。
let globals = vm.new_dict().unwrap();
// 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。
let code = make_code(
&mut vm,
&[
@@ -622,11 +677,13 @@ fn recursion_uses_explicit_frames_and_unwinds_at_the_frame_limit() {
..CodeLayout::default()
},
);
// 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。
let function = vm
.new_function("recurse", code, globals, vec![], vec![])
.unwrap();
vm.dict_set(globals, text("recurse"), Value::Object(function))
.unwrap();
// 递归深度会临时保留多份函数根,退出后应恢复精确初始计数。
let roots = vm.ref_count(function).unwrap();
assert_eq!(
vm.call_with_limits(
@@ -641,22 +698,30 @@ fn recursion_uses_explicit_frames_and_unwinds_at_the_frame_limit() {
);
assert_eq!(vm.last_traceback().len(), 64);
assert_eq!(vm.ref_count(function).unwrap(), roots);
// 调用前/后按场景归还测试函数根;全局字典可能仍持有函数内部边。
vm.release(function).unwrap();
// 移除代码常量边后,返回图必须自己保留所需对象;不是只靠旧帧保活。
vm.release(code).unwrap();
// 归还环境测试根,临时执行函数已清理,结果不能依赖其持续存在。
vm.release(globals).unwrap();
assert_eq!(vm.collect_cycles(), 3);
}
#[test]
/// 栈高合法的无限回边仍须由硬指令预算终止,不能泄漏临时函数或帧。
fn instruction_limit_stops_a_valid_infinite_loop_without_leaking_frames() {
// 独立 VM 隔离调用帧、全局与根计数断言。
let mut vm = Vm::new().unwrap();
// 函数执行环境的拥有根,函数也经载荷强边持有该字典。
let globals = vm.new_dict().unwrap();
// 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。
let code = make_code(
&mut vm,
&[op(Opcode::JumpBackward, 1)],
vec![],
CodeLayout::default(),
);
// 记录代码/全局等场景根数,失败后不得残留临时字典、函数或帧根。
let before = vm.object_count();
assert_eq!(
vm.execute_with_limits(
@@ -674,14 +739,19 @@ fn instruction_limit_stops_a_valid_infinite_loop_without_leaking_frames() {
}
#[test]
/// 函数捕获创建时内置字典身份,全局同名键仍优先,改写 __builtins__ 不重绑定旧函数。
fn function_globals_shadow_builtins_and_capture_the_original_builtins_dictionary() {
// 独立 VM 隔离调用帧、全局与根计数断言。
let mut vm = Vm::new().unwrap();
// 函数执行环境的拥有根,函数也经载荷强边持有该字典。
let globals = vm.new_dict().unwrap();
// 原内置字典由函数创建时捕获,不因稍后 globals.__builtins__ 改变而替换身份。
let builtins = vm.new_dict().unwrap();
vm.dict_set(builtins, text("answer"), text("builtin"))
.unwrap();
vm.dict_set(globals, text("__builtins__"), Value::Object(builtins))
.unwrap();
// 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。
let code = make_code(
&mut vm,
&[op(Opcode::LoadGlobal, 0), simple(Opcode::ReturnValue)],
@@ -691,12 +761,15 @@ fn function_globals_shadow_builtins_and_capture_the_original_builtins_dictionary
..CodeLayout::default()
},
);
// 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。
let function = vm
.new_function("read", code, globals, vec![], vec![])
.unwrap();
// 新内置字典仅更新 globals 字段,用于证明旧函数仍引用原内置空间。
let replacement = vm.new_dict().unwrap();
vm.dict_set(globals, text("__builtins__"), Value::Object(replacement))
.unwrap();
// 归还原内置测试根后仍应由函数 captured builtins 强边保活。
vm.release(builtins).unwrap();
assert_eq!(vm.call(function, &[]).unwrap(), text("builtin"));
vm.dict_set(globals, text("answer"), text("global"))
@@ -705,10 +778,15 @@ fn function_globals_shadow_builtins_and_capture_the_original_builtins_dictionary
}
#[test]
/// 多个函数共享同一 Cell,Load/Store/DeleteDeref 对内容变化立即可见,clear 后读取报未绑定。
fn shared_free_variable_cells_are_read_written_and_deleted_by_bytecode() {
// 独立 VM 隔离调用帧、全局与根计数断言。
let mut vm = Vm::new().unwrap();
// 函数执行环境的拥有根,函数也经载荷强边持有该字典。
let globals = vm.new_dict().unwrap();
// 共享槽初值 first,read/write/clear 函数各自持有同一 Cell 强边。
let cell = vm.new_cell(Some(text("first"))).unwrap();
// 没有局部槽,唯一自由变量 Cell 位于 localsplus 索引零。
let read_code = make_code(
&mut vm,
&[op(Opcode::LoadDeref, 0), simple(Opcode::ReturnValue)],
@@ -718,6 +796,7 @@ fn shared_free_variable_cells_are_read_written_and_deleted_by_bytecode() {
..CodeLayout::default()
},
);
// 有一个普通参数槽,外部 Cell 追加在索引一,StoreDeref 不能误写局部槽。
let write_code = make_code(
&mut vm,
&[
@@ -734,6 +813,7 @@ fn shared_free_variable_cells_are_read_written_and_deleted_by_bytecode() {
..CodeLayout::default()
},
);
// DeleteDeref 清空 Cell 而非写 Nil,随后返回直接 Nil。
let clear_code = make_code(
&mut vm,
&[
@@ -747,12 +827,15 @@ fn shared_free_variable_cells_are_read_written_and_deleted_by_bytecode() {
..CodeLayout::default()
},
);
// 只读捕获值的函数,共享槽未复制成独立快照。
let read = vm
.new_function("read", read_code, globals, vec![], vec![cell])
.unwrap();
// 把显式参数写到同一捕获 Cell,其他闭包应立即看到新值。
let write = vm
.new_function("write", write_code, globals, vec![], vec![cell])
.unwrap();
// 删除同一捕获变量后 read 映射 EmptyCell 为具名 UnboundLocal。
let clear = vm
.new_function("clear", clear_code, globals, vec![], vec![cell])
.unwrap();
@@ -764,9 +847,13 @@ fn shared_free_variable_cells_are_read_written_and_deleted_by_bytecode() {
}
#[test]
/// 参数提升为 Cell 后通过 LoadClosure 返回,原帧与参数测试根释放仍由 Cell 边保活内容。
fn captured_argument_cells_keep_their_contents_after_the_frame_returns() {
// 独立 VM 隔离调用帧、全局与根计数断言。
let mut vm = Vm::new().unwrap();
// 函数执行环境的拥有根,函数也经载荷强边持有该字典。
let globals = vm.new_dict().unwrap();
// 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。
let code = make_code(
&mut vm,
&[op(Opcode::LoadClosure, 0), simple(Opcode::ReturnValue)],
@@ -778,17 +865,23 @@ fn captured_argument_cells_keep_their_contents_after_the_frame_returns() {
..CodeLayout::default()
},
);
// 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。
let function = vm
.new_function("capture", code, globals, vec![], vec![])
.unwrap();
// 参数对象有测试根,帧初始化提升 Cell 时另建立内容强边。
let value = vm.new_string("captured").unwrap();
// 函数把已有参数/默认值作为结果,身份不变但结果仍取得独立拥有根。
let returned = vm.call(function, &[Value::Object(value)]).unwrap();
// 归还原参数测试根,返回的 Cell 内容边仍须保活它。
vm.release(value).unwrap();
// LoadClosure 返回的是 Cell 本身,不是 Cell 当前内容。
let Value::Object(cell) = returned else {
panic!("expected cell");
};
assert_eq!(vm.cell_get(cell).unwrap(), Value::Object(value));
assert_eq!(vm.str(value).unwrap(), "captured");
// 最后 Cell 结果根归还后其内容边释放,原字符串身份应立即失效。
vm.release(cell).unwrap();
assert!(matches!(vm.object(value), Err(VmError::InvalidHandle)));
}