diff --git a/crates/core/src/README.md b/crates/core/src/README.md index a668eb5..7067432 100644 --- a/crates/core/src/README.md +++ b/crates/core/src/README.md @@ -19,3 +19,5 @@ | 新 VM 公开 API | vm.rs/eval.rs 与 lib.rs 导出 | 所属主题、示例、[tests 导航](../tests/README.md) | core 不访问对象私有字段或 Heap;执行器只通过受校验的对象存储操作维护帧根。返回 [core](../README.md)。 + +独立托管调用帧:frame.rs 定义 CoroutineProgress 和私有可追踪帧载荷;eval.rs 提供 new_coroutine/resume_coroutine/cancel_coroutine,暂停帧以托管边保活,恢复时转回执行根。与跨量子只读续算分开,所有权及边界见 docs/runtime/bytecode.md。 diff --git a/crates/core/tests/README.md b/crates/core/tests/README.md index 10d20be..ed16568 100644 --- a/crates/core/tests/README.md +++ b/crates/core/tests/README.md @@ -15,3 +15,5 @@ 核心对象现由 objects 定义,本目录保留既有 endor_core 导入与 Vm 方法的兼容性检查。独立对象存储检查见 [objects/tests](../../objects/tests/README.md)。堆内部伪造句柄与计数溢出检查在 objects 的 src/heap.rs,公开示例由 src/lib.rs rustdoc 提供。文件职责见 [src](../src/README.md),检查方式和结果边界见 [验证](../../../docs/operations/validation.md)。本地不执行测试;存在测试文件不代表某次提交已通过。返回 [core](../README.md)。 execution.rs 同时覆盖宿主可选 assign_from 的全局/局部赋值、别名身份、拒绝替换与执行根清理;不在本地运行。 + +execution.rs 的 managed_coroutine 用例覆盖交替恢复不重放写入、累计预算错误释放、取消幂等和暂停帧引用环回收;本地只经宿主Clippy编译,不运行测试。 diff --git a/crates/core/tests/execution.rs b/crates/core/tests/execution.rs index a647960..017050d 100644 --- a/crates/core/tests/execution.rs +++ b/crates/core/tests/execution.rs @@ -1503,3 +1503,122 @@ fn suspended_call_cancellation_and_total_budget_restore_read_only_state() { vm.release(shared).unwrap(); assert_eq!(vm.object_count(), 0); } + +#[test] +fn managed_coroutines_interleave_without_replaying_writes() { + use endor_core::CoroutineProgress; + let mut vm = Vm::new().unwrap(); + let globals = vm.new_dict().unwrap(); + let code = make_code( + &mut vm, + &[ + op(Opcode::LoadConst, 0), + op(Opcode::StoreGlobal, 0), + op(Opcode::LoadConst, 1), + op(Opcode::StoreGlobal, 1), + op(Opcode::LoadConst, 2), + simple(Opcode::ReturnValue), + ], + vec![text("first"), text("second"), Value::Nil], + CodeLayout { + names: vec!["x".into(), "y".into()], + ..CodeLayout::default() + }, + ); + let function = vm + .new_function("work", code, globals, vec![], vec![]) + .unwrap(); + let first = vm + .new_coroutine(function, &[], ExecutionLimits::default()) + .unwrap(); + let second = vm + .new_coroutine(function, &[], ExecutionLimits::default()) + .unwrap(); + assert!(matches!( + vm.resume_coroutine(first, 2).unwrap(), + CoroutineProgress::Pending { instructions: 2 } + )); + assert!(matches!( + vm.resume_coroutine(second, 2).unwrap(), + CoroutineProgress::Pending { instructions: 2 } + )); + vm.dict_set(globals, text("x"), text("interleaved")) + .unwrap(); + assert!(matches!( + vm.resume_coroutine(first, 16).unwrap(), + CoroutineProgress::Complete { + value: Value::Nil, + instructions: 6 + } + )); + assert_eq!( + vm.dict_get(globals, &text("x")).unwrap(), + Some(text("interleaved")) + ); + assert_eq!( + vm.dict_get(globals, &text("y")).unwrap(), + Some(text("second")) + ); + assert!(vm.cancel_coroutine(second).unwrap()); + assert!(!vm.cancel_coroutine(second).unwrap()); + assert!(vm.resume_coroutine(first, 1).is_err()); + for object in [first, second, function, code, globals] { + vm.release(object).unwrap(); + } + assert_eq!(vm.object_count(), 0); +} + +#[test] +fn parked_coroutine_cycles_are_collectable_without_cancellation() { + let mut vm = Vm::new().unwrap(); + let globals = vm.new_dict().unwrap(); + let code = return_constant(&mut vm, Value::Nil); + let function = vm + .new_function("cycle", code, globals, vec![], vec![]) + .unwrap(); + let coroutine = vm + .new_coroutine(function, &[], ExecutionLimits::default()) + .unwrap(); + vm.dict_set(globals, text("task"), Value::Object(coroutine)) + .unwrap(); + for object in [coroutine, function, code, globals] { + vm.release(object).unwrap(); + } + assert!(vm.collect_cycles() > 0); + assert_eq!(vm.object_count(), 0); +} + +#[test] +fn managed_coroutine_hard_budget_accumulates_and_error_releases_frames() { + use endor_core::CoroutineProgress; + let mut vm = Vm::new().unwrap(); + let globals = vm.new_dict().unwrap(); + let code = return_constant(&mut vm, Value::Nil); + let function = vm + .new_function("limited", code, globals, vec![], vec![]) + .unwrap(); + let coroutine = vm + .new_coroutine( + function, + &[], + ExecutionLimits { + max_frames: 4, + max_instructions: 1, + }, + ) + .unwrap(); + assert!(matches!( + vm.resume_coroutine(coroutine, 1).unwrap(), + CoroutineProgress::Pending { instructions: 1 } + )); + assert!(matches!( + vm.resume_coroutine(coroutine, 1), + Err(VmError::ExecutionLimit("instructions")) + )); + assert!(!vm.last_traceback().is_empty()); + assert!(!vm.cancel_coroutine(coroutine).unwrap()); + for object in [coroutine, function, code, globals] { + vm.release(object).unwrap(); + } + assert_eq!(vm.object_count(), 0); +} diff --git a/docs/README.md b/docs/README.md index c80a0f6..60eb54d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,7 +12,7 @@ | 词法、语法、裸赋值和诊断 | [Frontend](runtime/frontend.md) | frontend/src/lexer.peg/grammar.peg | | ASDL、AST 与编辑器接口 | [AST](runtime/ast.md) | frontend/Endor.asdl | | 名称分析、编译和装载 | [Compiler](runtime/compiler.md) | compiler/src | -| 字节码、函数、共享引用赋值、可恢复只读分片与限额 | [字节码 VM](runtime/bytecode.md) | objects/src、core/src | +| 字节码、函数、共享引用赋值、可恢复只读分片、独立托管调用帧与限额 | [字节码 VM](runtime/bytecode.md) | objects/src、core/src | | 引用与 VM 生命周期 | [生命周期](runtime/lifecycle.md) | objects/src、core/src | | UTF-8 与地区 | [文本编码](runtime/text-encoding.md) | frontend、objects | | 嵌入及宿主入口 | [使用引擎](operations/README.md) | 独立宿主仓库 | diff --git a/docs/runtime/bytecode.md b/docs/runtime/bytecode.md index 10b3788..3fd2bad 100644 --- a/docs/runtime/bytecode.md +++ b/docs/runtime/bytecode.md @@ -186,3 +186,12 @@ fn main() -> Result<(), endor_core::VmError> { BuildClass(编号 29,无参数)消费四个栈值 `[name, baseType, fieldsDict, methodsDict]`,产生一个受管类对象;验证器按 4 入 1 出检查栈。字段字典按声明顺序保存字段名与注解名,方法字典保存 MakeFunction 创建的函数。VM 先验证基类、字段与方法关系,再创建完整类型;输入根在成功前保持在执行栈上,失败由帧统一清理。 LoadAttr 对实例自有属性返回原值;缺失时沿类型父链找方法并创建绑定方法。绑定方法持有 receiver 和选定的函数,调用时经已有非递归委托路径补入 self。类对象上的方法读取提供未绑定函数;type/name/base 元数据优先。宿主读取继承方法应使用返回拥有式结果的 bind_attr;get_attr 保持借用式读取,类型方法结果为未绑定函数。所有委托消耗同一指令预算。 + + +## 独立托管调用帧 + +`Vm::new_coroutine(function, arguments, limits)` 返回拥有一份外部根的通用 coroutine 对象;`resume_coroutine(handle, slice)` 返回 `CoroutineProgress::Pending` 或带拥有式结果的 `Complete`。源码指令首次在 resume 执行,原生函数/委托可能在 new 的准备期间执行,仍受硬指令限额约束。该接口目前只提供分片执行基础,不等于 Endor async/await 语法或等待协议。 + +与只读续算不同,每个 coroutine 把自己的帧保存在同一 VM 的托管对象载荷中,多个调用可独立暂停;Pending 之间可执行普通调用和堆写入,没有跨量子的只读作用域。resume 不重放先前指令,局部槽、栈、返回类型及原生委托契约保持,所有量子共用原始硬指令/帧预算。原生调用仍是原子的,slice 不保证墙钟延迟。读写共享堆,无快照、事务或自动回滚。不能在只读执行/只读暂停期间创建、恢复或取消 coroutine。 + +暂停时,帧内原本的执行根转为载荷 trace 的托管边(包括重复边);恢复前先保活为执行根,再清空暂停载荷。函数边继续拥有 code/globals/builtins。运行中的 handle 临时保活,不能恢复或取消 Running/已结束帧;完成和错误均消耗帧,错误保留 traceback。`cancel_coroutine(handle)` 对尚未结束帧返回 true 并释放托管边,已结束返回 false;已发生的写入不回滚。调用方最终 release handle 和完成结果。丢弃未完成且不可达的 coroutine 不要求先 cancel,局部/全局指回 coroutine 的环可由正常循环回收处理,不把暂停帧永久登记为 VM 根。调度、任务归属、等待条件及取消策略由宿主实现,网络/GUI不进入内核。