diff --git a/crates/core/tests/execution.rs b/crates/core/tests/execution.rs index 3533ecb..4a790de 100644 --- a/crates/core/tests/execution.rs +++ b/crates/core/tests/execution.rs @@ -1986,46 +1986,66 @@ fn managed_coroutine_hard_budget_accumulates_and_error_releases_frames() { assert_eq!(vm.object_count(), 0); } +/// await 测试专用宿主载荷,由测试分配到 VM;eval 的 Await 指令通过 ObjectPayload 轮询它。 +/// 只切换宿主就绪标志,不改变托管引用边;结果对象始终由 trace 报告给 Heap。 #[derive(Debug)] struct AwaitGate { + // 测试与载荷共享就绪标志;测试 Release 写入,poll_await Acquire 读取。 ready: Arc, + // 就绪后返回的借用值;若为对象,分配时由 trace 建立内部拥有边。 value: Value, } +// 实现宿主对象协议,接入 Store 的种类检查、Heap 的引用遍历和 eval 的等待操作。 impl ObjectPayload for AwaitGate { + /// Store/宿主载荷检查调用,保留具体 Rust 类型以支持 Any 下转。 fn as_any(&self) -> &dyn Any { self } + /// 对象种类诊断调用,提供稳定的测试载荷名称。 fn kind_name(&self) -> &'static str { "await-gate" } + /// Heap 分配、回收时遍历内部对象边;立即值没有托管引用,无需报告。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { + // 只有对象值携带堆引用;必须报告这一边,使测试释放原根后结果仍存活。 if let Value::Object(reference) = self.value { visit(reference); } } + /// eval 每次进入 Await 时调用;非阻塞返回 Pending 或借用 Ready 值,不自行增加结果根。 fn poll_await(&self) -> Result { + // 与测试的 Release 写入配对;标志可变而 value 的托管引用关系保持不变。 Ok(if self.ready.load(Ordering::Acquire) { + // clone 只复制 Value 表示;eval 在丢弃等待操作数前为结果取得独立根。 endor_core::AwaitPoll::Ready(self.value.clone()) } else { + // 尚未就绪时帧保留原操作数与 Await 位置,下次只重新轮询该指令。 endor_core::AwaitPoll::Pending }) } } #[test] +/// 由 Rust 测试框架调用,验证 Await 停放不重放前缀,等待图保活结果,完成后结果根独立于协程存活。 fn await_keeps_operand_and_owned_object_result_without_replaying_prefix() { use endor_core::CoroutineProgress; + // 本测试独享堆和协程执行状态,最后检查结果归还后没有残留对象。 let mut vm = Vm::new().unwrap(); + // 宿主持一个共享标志,初始未就绪;停放后通过它唤醒下一次轮询。 let ready = Arc::new(std::sync::atomic::AtomicBool::new(false)); + // 分配最终结果并写入 marker,稍后释放原根以排除宿主根掩盖保活错误。 let value = vm.new_object().unwrap(); vm.set_attr(value, "marker", text("kept")).unwrap(); + // 分配等待载荷,Heap 根据 trace 保留 value;宿主标志使用 Arc 共享。 let gate = vm .allocate(AwaitGate { ready: ready.clone(), value: Value::Object(value), }) .unwrap(); + // 环境保存可观察的前缀副作用,等待后宿主将改写它以检测指令重放。 let globals = vm.new_dict().unwrap(); + // 先写 step,再载入 gate 并 Await,最后返回等待结果;两次恢复应只重试 Await。 let code = make_code( &mut vm, &[ @@ -2041,21 +2061,30 @@ fn await_keeps_operand_and_owned_object_result_without_replaying_prefix() { ..CodeLayout::default() }, ); + // 函数拥有代码与环境,代码常量表另持 gate。 let function = vm .new_function("awaited", code, globals, vec![], vec![]) .unwrap(); + // 创建可变托管协程;普通同步入口不支持停放的 Pending 等待。 let coroutine = vm .new_coroutine(function, &[], ExecutionLimits::default()) .unwrap(); + // 首轮应返回 Waiting,携带的借用等待对象必须正是 gate。 assert!( matches!(vm.resume_coroutine(coroutine, 64).unwrap(), CoroutineProgress::Waiting { awaitable, .. } if awaitable == gate) ); + // 归还结果原根,接下来靠 gate 的内部边保活。 vm.release(value).unwrap(); + // 归还等待对象原根,代码常量及停放帧操作数仍持有它。 vm.release(gate).unwrap(); + // 等待期间宿主改写前缀结果;恢复后必须保留该写入。 vm.dict_set(globals, text("step"), text("interleaved")) .unwrap(); + // 强制回收检查等待图仍可达,不能把帧上的 await 操作数误判为垃圾。 vm.collect_cycles(); + // 发布就绪状态;下一次恢复应从保存的 Await 指令重新轮询。 ready.store(true, Ordering::Release); + // 就绪后应取得拥有结果;不应再次返回 Waiting 或重跑整段代码。 let CoroutineProgress::Complete { value: result, .. } = vm.resume_coroutine(coroutine, 64).unwrap() else { @@ -2065,26 +2094,36 @@ fn await_keeps_operand_and_owned_object_result_without_replaying_prefix() { vm.dict_get(globals, &text("step")).unwrap(), Some(text("interleaved")) ); + // 清除协程、函数、代码与环境的测试根,验证结果根本身足以保持对象。 for object in [coroutine, function, code, globals] { vm.release(object).unwrap(); } + // 结果必须仍是有效对象,marker 检查不能依赖已释放的等待图。 let Value::Object(reference) = result else { panic!("owned object result") }; assert_eq!(vm.get_attr(reference, "marker").unwrap(), text("kept")); + // 最后归还调用结果拥有根,此时堆应为空。 vm.release_value(result).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] +/// 由 Rust 测试框架调用,比较切片、同步精确及兼容读取集合;验证无关名称不使名称依赖失效,而遮蔽、命中值变化及对象销毁会失效。 fn named_reads_ignore_unrelated_globals_but_track_missing_shadow_and_payload_kind() { + // 隔离读取作用域与对象生命周期,后续销毁测试不会受其他用例影响。 let mut vm = Vm::new().unwrap(); + // 全局字典起初缺 name;这个缺失也是必须记录的观察。 let globals = vm.new_dict().unwrap(); + // 回退字典提供 name=builtin,全局缺失后才读取它。 let builtins = vm.new_dict().unwrap(); + // 初始化回退值,后续全局缺失时应读取这个内置名称。 vm.dict_set(builtins, text("name"), text("builtin")) .unwrap(); + // 在函数创建前绑定内置字典,函数保存该内置环境引用。 vm.dict_set(globals, text("__builtins__"), Value::Object(builtins)) .unwrap(); + // 只读取名称 name 后返回,避免其他内容读取把依赖扩大为整个字典版本。 let code = make_code( &mut vm, &[op(Opcode::LoadGlobal, 0), simple(Opcode::ReturnValue)], @@ -2094,9 +2133,11 @@ fn named_reads_ignore_unrelated_globals_but_track_missing_shadow_and_payload_kin ..CodeLayout::default() }, ); + // 构造具有独立代码/环境内部边的被测函数。 let function = vm .new_function("read-name", code, globals, vec![], vec![]) .unwrap(); + // 量子足够一次完成;分别取得拥有结果、兼容对象集合和精确依赖快照。 let endor_core::ExecutionProgress::Complete { value, reads, @@ -2109,38 +2150,48 @@ fn named_reads_ignore_unrelated_globals_but_track_missing_shadow_and_payload_kin panic!("complete") }; assert_eq!(value, text("builtin")); + // 全局缺失观察与内置命中观察都必须出现在兼容读取集合里。 assert!(reads.contains(&globals) && reads.contains(&builtins)); + // 归还本次调用结果;读取集合和依赖本身只是借用记录,不新增对象根。 vm.release_value(value).unwrap(); + // 增加无关名称,不能影响此前 name 缺失与内置命中的精确观察。 vm.dict_set(globals, text("_"), text("unrelated")).unwrap(); + // 同步精确入口应产生与切片入口相同的对象集合及依赖。 let (value, synchronous) = vm .call_read_only_precise_with_limits(function, &[], ExecutionLimits::default()) .unwrap(); assert_eq!(value, text("builtin")); assert_eq!(synchronous.dependencies, dependencies); assert_eq!(synchronous.objects, reads); + // 归还本次调用结果;读取集合和依赖本身只是借用记录,不新增对象根。 vm.release_value(value).unwrap(); + // 兼容入口仍返回对象集合,验证旧接口与精确接口的覆盖一致。 let (value, conservative) = vm .call_read_only_with_limits(function, &[], ExecutionLimits::default()) .unwrap(); assert_eq!(conservative, reads); + // 归还本次调用结果;读取集合和依赖本身只是借用记录,不新增对象根。 vm.release_value(value).unwrap(); assert!( dependencies .iter() .all(|dependency| vm.object_store().read_dependency_valid(dependency)) ); + // 新增同名全局遮蔽内置值,必须使原来的全局缺失依赖失效。 vm.dict_set(globals, text("name"), text("shadow")).unwrap(); assert!( dependencies .iter() .any(|dependency| !vm.object_store().read_dependency_valid(dependency)) ); + // 删除遮蔽后,名称观察重新与原快照一致;依赖不是永久失效标记。 vm.dict_delete(globals, &text("name")).unwrap(); assert!( dependencies .iter() .all(|dependency| vm.object_store().read_dependency_valid(dependency)) ); + // 实际命中值变化使内置名称快照失效。 vm.dict_set(builtins, text("name"), text("changed")) .unwrap(); assert!( @@ -2148,6 +2199,7 @@ fn named_reads_ignore_unrelated_globals_but_track_missing_shadow_and_payload_kin .iter() .any(|dependency| !vm.object_store().read_dependency_valid(dependency)) ); + // 归还全部测试根,环境和代码图释放后原依赖中的句柄不再有效。 for object in [function, code, globals, builtins] { vm.release(object).unwrap(); } @@ -2160,13 +2212,20 @@ fn named_reads_ignore_unrelated_globals_but_track_missing_shadow_and_payload_kin } #[test] +/// 由 Rust 测试框架调用,直接在 ObjectStore 检查子区间读取及重验均登记到外层;按名称依赖保持精确,整字典读取仍保守失效。 fn precise_nested_regions_replay_borrowed_dependencies_without_hiding_full_dict_reads() { + // 直接使用 ObjectStore,只验证读取追踪,无需创建可执行函数或运行解释器。 let mut vm = endor_core::ObjectStore::new().unwrap(); + // 准备只含 name 的字典,之后添加其他名称来区分精确与整对象观察。 let globals = vm.new_dict().unwrap(); vm.dict_set(globals, text("name"), text("value")).unwrap(); + // 开启只读外层作用域,收集兼容对象集合及精确观察,退出后才允许写入旧对象。 let scope = vm.begin_read_only(); + // 在现有只读作用域内标记子区间,结束子区间不会清除外层已登记的依赖。 let token = vm.begin_read_tracking().unwrap(); + // 记录字典种类与指定名称命中值,不记录无关名称或整个内容版本。 vm.dict_get_name(globals, "name").unwrap(); + // 结束子区间,取得借用的访问分类与依赖;这些记录不会自动持有对象根。 let tracked = vm.end_read_tracking_precise(token).unwrap(); assert!( tracked @@ -2174,10 +2233,13 @@ fn precise_nested_regions_replay_borrowed_dependencies_without_hiding_full_dict_ .iter() .any(|access| access.object == globals) ); + // 在外层作用域仍开启时重验子区间依赖;正常读取入口会再次登记观察。 for dependency in &tracked.dependencies { assert!(vm.read_dependency_valid(dependency)); } + // 结束外层只读作用域取得累计观察,再进行写入以检验失效范围。 let reads = vm.end_read_only_precise(scope).unwrap(); + // 首次只读仅观察 name,新增 other 后所有原依赖应仍然有效。 vm.dict_set(globals, text("other"), Value::Nil).unwrap(); assert!( reads @@ -2185,9 +2247,13 @@ fn precise_nested_regions_replay_borrowed_dependencies_without_hiding_full_dict_ .iter() .all(|dependency| vm.read_dependency_valid(dependency)) ); + // 开启只读外层作用域,收集兼容对象集合及精确观察,退出后才允许写入旧对象。 let scope = vm.begin_read_only(); + // 第二轮读取全部条目,必须记录整字典版本,不能被之前的精确读取策略掩盖。 vm.dict_items(globals).unwrap(); + // 结束外层只读作用域取得累计观察,再进行写入以检验失效范围。 let reads = vm.end_read_only_precise(scope).unwrap(); + // 整字典内容变化后,应至少有一项整对象依赖失效。 vm.dict_set(globals, text("third"), Value::Nil).unwrap(); assert!( reads @@ -2195,6 +2261,7 @@ fn precise_nested_regions_replay_borrowed_dependencies_without_hiding_full_dict_ .iter() .any(|dependency| !vm.read_dependency_valid(dependency)) ); + // 归还字典唯一外部根;读取记录不拥有根,因此堆应清空。 vm.release(globals).unwrap(); assert_eq!(vm.object_count(), 0); }