feat: retain read-only execution frames across instruction slices
This commit is contained in:
@@ -5,9 +5,9 @@
|
||||
| 文件 | 职责 |
|
||||
| --- | --- |
|
||||
| [lib.rs](lib.rs) | VM 类型导出、objects 类型兼容重导出(含 ReadAccess/CallSignature/CallType/CallEffect)、宿主调用与字节码 rustdoc 示例 |
|
||||
| [vm.rs](vm.rs) | Vm 拥有 ObjectStore 与错误位置记录;对象操作和宿主 new_class 和显式/类型化Trait注册/关系查询显式委托给 objects;函数/字段检查统一开关、统一参数区间/已知调用效果查询与函数类型配置 |
|
||||
| [frame.rs](frame.rs) | EndorFrame、栈条目、源码与原生委托返回契约执行根、执行位置元数据、ExecutionLimits |
|
||||
| [eval.rs](eval.rs) | execute/call、只读调用与依赖结果、参数绑定、原生实现前数量/类型/效果检查和原生/类型化Trait委托返回检查、可选注解/动态覆写兼容检查、源码类字段环境绑定、帧切换、取指分派、MakeFunction、结果根转移与失败清理 |
|
||||
| [vm.rs](vm.rs) | Vm 拥有 ObjectStore、错误位置记录与挂起的只读执行状态;对象操作和宿主 new_class 和显式/类型化Trait注册/关系查询显式委托给 objects;函数/字段检查统一开关、统一参数区间/已知调用效果查询与函数类型配置 |
|
||||
| [frame.rs](frame.rs) | EndorFrame、栈条目、源码与原生委托返回契约执行根、执行位置元数据、ExecutionLimits、ExecutionProgress 与 VM 内续算帧状态 |
|
||||
| [eval.rs](eval.rs) | execute/call、只读调用/可恢复分片/取消与依赖结果、参数绑定、原生实现前数量/类型/效果检查和原生/类型化Trait委托返回检查、可选注解/动态覆写兼容检查、源码类字段环境绑定、帧切换、取指分派、MakeFunction、结果根转移与失败清理 |
|
||||
|
||||
## 常见修改入口
|
||||
|
||||
|
||||
+147
-4
@@ -1,4 +1,5 @@
|
||||
use crate::frame::{NativeReturn, StackEntry};
|
||||
use crate::ExecutionProgress;
|
||||
use crate::frame::{NativeReturn, StackEntry, SuspendedCall};
|
||||
use crate::{
|
||||
CompareOp, EndorCellObject, EndorCodeObject, EndorFrame, EndorFunctionObject,
|
||||
EndorStringObject, ExecutionLimits, ExecutionLocation, Instruction, ObjectRef, Opcode, Value,
|
||||
@@ -69,6 +70,11 @@ impl Vm {
|
||||
arguments: &[Value],
|
||||
limits: ExecutionLimits,
|
||||
) -> Result<Value, VmError> {
|
||||
if self.suspended_call.is_some() {
|
||||
return Err(VmError::UnsupportedOperation(
|
||||
"call while read-only execution is suspended",
|
||||
));
|
||||
}
|
||||
self.last_traceback.clear();
|
||||
let mut executed = 0;
|
||||
let frame = match self.prepare_call(function, arguments.to_vec(), &mut executed, limits)? {
|
||||
@@ -80,7 +86,9 @@ impl Vm {
|
||||
return Err(VmError::ExecutionLimit("frames"));
|
||||
}
|
||||
let mut frames = vec![frame];
|
||||
let result = self.eval_frames(&mut frames, limits, &mut executed);
|
||||
let result = self
|
||||
.eval_frames(&mut frames, limits, &mut executed, usize::MAX)
|
||||
.map(|value| value.expect("unbounded slice completes or fails"));
|
||||
if result.is_err() {
|
||||
self.last_traceback = frames
|
||||
.iter()
|
||||
@@ -98,6 +106,137 @@ impl Vm {
|
||||
result
|
||||
}
|
||||
|
||||
/// Begin a resumable read-only call. One VM owns at most one continuation.
|
||||
/// A slice is a soft instruction quantum: native callbacks and delegation
|
||||
/// complete atomically and remain subject to the cumulative hard limits.
|
||||
pub fn start_read_only_call(
|
||||
&mut self,
|
||||
function: ObjectRef,
|
||||
arguments: &[Value],
|
||||
limits: ExecutionLimits,
|
||||
slice: usize,
|
||||
) -> Result<ExecutionProgress, VmError> {
|
||||
if slice == 0 {
|
||||
return Err(VmError::InvalidArgument("execution slice must be positive"));
|
||||
}
|
||||
if self.suspended_call.is_some() || self.objects.is_read_only() {
|
||||
return Err(VmError::UnsupportedOperation(
|
||||
"start suspended call inside another execution",
|
||||
));
|
||||
}
|
||||
self.last_traceback.clear();
|
||||
let scope = self.objects.begin_read_only();
|
||||
let mut executed = 0;
|
||||
let prepared = self.prepare_call(function, arguments.to_vec(), &mut executed, limits);
|
||||
match prepared {
|
||||
Ok(PreparedCall::Value(value)) => {
|
||||
let reads = match self.objects.end_read_only(scope) {
|
||||
Ok(reads) => reads,
|
||||
Err(error) => {
|
||||
self.drop_root(value);
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
Ok(ExecutionProgress::Complete {
|
||||
value,
|
||||
reads,
|
||||
instructions: executed,
|
||||
})
|
||||
}
|
||||
Ok(PreparedCall::Frame(frame)) => {
|
||||
if limits.max_frames == 0 {
|
||||
self.clear_frame(frame);
|
||||
self.objects.end_read_only(scope)?;
|
||||
return Err(VmError::ExecutionLimit("frames"));
|
||||
}
|
||||
self.suspended_call = Some(SuspendedCall {
|
||||
frames: vec![frame],
|
||||
scope,
|
||||
limits,
|
||||
executed,
|
||||
});
|
||||
// Preparation counts toward the first quantum as well.
|
||||
self.resume_read_only_to(slice)
|
||||
}
|
||||
Err(error) => {
|
||||
self.objects.end_read_only(scope)?;
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_suspended_call(&self) -> bool {
|
||||
self.suspended_call.is_some()
|
||||
}
|
||||
|
||||
/// Resume existing frames, never replay earlier instructions. The read-only
|
||||
/// scope remains active across Pending; preexisting heap writes stay barred.
|
||||
pub fn resume_read_only_call(&mut self, slice: usize) -> Result<ExecutionProgress, VmError> {
|
||||
if slice == 0 {
|
||||
return Err(VmError::InvalidArgument("execution slice must be positive"));
|
||||
}
|
||||
let executed = self
|
||||
.suspended_call
|
||||
.as_ref()
|
||||
.ok_or(VmError::InvalidArgument("no suspended call"))?
|
||||
.executed;
|
||||
self.resume_read_only_to(executed.saturating_add(slice))
|
||||
}
|
||||
|
||||
fn resume_read_only_to(&mut self, slice_end: usize) -> Result<ExecutionProgress, VmError> {
|
||||
let mut call = self
|
||||
.suspended_call
|
||||
.take()
|
||||
.ok_or(VmError::InvalidArgument("no suspended call"))?;
|
||||
let result = self.eval_frames(&mut call.frames, call.limits, &mut call.executed, slice_end);
|
||||
if let Ok(None) = result {
|
||||
let instructions = call.executed;
|
||||
self.suspended_call = Some(call);
|
||||
return Ok(ExecutionProgress::Pending { instructions });
|
||||
}
|
||||
if result.is_err() {
|
||||
self.last_traceback = call
|
||||
.frames
|
||||
.iter()
|
||||
.map(|frame| ExecutionLocation {
|
||||
function: frame.name.clone(),
|
||||
filename: frame.executable.filename().into(),
|
||||
first_line: frame.executable.first_line(),
|
||||
instruction_offset: frame.current_offset,
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
while let Some(frame) = call.frames.pop() {
|
||||
self.clear_frame(frame);
|
||||
}
|
||||
let reads = self.objects.end_read_only(call.scope);
|
||||
match (result, reads) {
|
||||
(Ok(Some(value)), Ok(reads)) => Ok(ExecutionProgress::Complete {
|
||||
value,
|
||||
reads,
|
||||
instructions: call.executed,
|
||||
}),
|
||||
(Ok(Some(value)), Err(error)) => {
|
||||
self.drop_root(value);
|
||||
Err(error)
|
||||
}
|
||||
(Err(error), _) => Err(error),
|
||||
(Ok(None), _) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Release all retained execution roots and close the read-only scope.
|
||||
pub fn cancel_read_only_call(&mut self) -> Result<bool, VmError> {
|
||||
let Some(mut call) = self.suspended_call.take() else {
|
||||
return Ok(false);
|
||||
};
|
||||
while let Some(frame) = call.frames.pop() {
|
||||
self.clear_frame(frame);
|
||||
}
|
||||
self.objects.end_read_only(call.scope)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Outer-to-inner execution locations from the last failed call/execute.
|
||||
/// These are copied metadata, not heap references or language exceptions.
|
||||
pub fn last_traceback(&self) -> &[ExecutionLocation] {
|
||||
@@ -613,11 +752,15 @@ impl Vm {
|
||||
frames: &mut Vec<EndorFrame>,
|
||||
limits: ExecutionLimits,
|
||||
executed: &mut usize,
|
||||
) -> Result<Value, VmError> {
|
||||
slice_end: usize,
|
||||
) -> Result<Option<Value>, VmError> {
|
||||
loop {
|
||||
if *executed >= limits.max_instructions {
|
||||
return Err(VmError::ExecutionLimit("instructions"));
|
||||
}
|
||||
if *executed >= slice_end {
|
||||
return Ok(None);
|
||||
}
|
||||
*executed += 1;
|
||||
let frame = frames.last_mut().expect("an evaluation frame is active");
|
||||
let decoded = frame.executable.instruction_at(frame.instruction_offset)?;
|
||||
@@ -782,7 +925,7 @@ impl Vm {
|
||||
if let Some(caller) = frames.last_mut() {
|
||||
caller.stack.push(StackEntry::Value(result));
|
||||
} else {
|
||||
return Ok(result);
|
||||
return Ok(Some(result));
|
||||
}
|
||||
}
|
||||
Opcode::JumpForward | Opcode::JumpBackward => {
|
||||
|
||||
@@ -91,3 +91,25 @@ impl Default for ExecutionLimits {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A read-only call either retains its VM-owned continuation or returns an
|
||||
/// owned value plus borrowed read dependencies. Instruction counts accumulate.
|
||||
#[derive(Debug)]
|
||||
pub enum ExecutionProgress {
|
||||
Pending {
|
||||
instructions: usize,
|
||||
},
|
||||
Complete {
|
||||
value: Value,
|
||||
reads: Vec<ObjectRef>,
|
||||
instructions: usize,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SuspendedCall {
|
||||
pub frames: Vec<EndorFrame>,
|
||||
pub scope: usize,
|
||||
pub limits: ExecutionLimits,
|
||||
pub executed: usize,
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ mod eval;
|
||||
mod frame;
|
||||
mod vm;
|
||||
|
||||
pub use frame::{EndorFrame, ExecutionLimits, ExecutionLocation};
|
||||
pub use frame::{EndorFrame, ExecutionLimits, ExecutionLocation, ExecutionProgress};
|
||||
// Preserve existing embedding imports; objects owns these definitions.
|
||||
pub use objects::ObjectStore;
|
||||
pub use objects::{
|
||||
|
||||
@@ -12,6 +12,7 @@ pub struct Vm {
|
||||
pub(crate) objects: ObjectStore,
|
||||
pub(crate) last_traceback: Vec<ExecutionLocation>,
|
||||
pub(crate) source_type_checks: bool,
|
||||
pub(crate) suspended_call: Option<crate::frame::SuspendedCall>,
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
@@ -166,6 +167,7 @@ impl Vm {
|
||||
objects: ObjectStore::new()?,
|
||||
last_traceback: Vec::new(),
|
||||
source_type_checks: false,
|
||||
suspended_call: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
| --- | --- |
|
||||
| [runtime.rs](runtime.rs) | 空堆启动、默认操作、引用/借用、属性原子性、稳定地址、长链/循环、原生回调、VM 销毁 |
|
||||
| [builtins.rs](builtins.rs) | 无损 Number、Unicode 字符串、Dict 顺序与键、Cell、函数环境/共享代码、异常数据 |
|
||||
| [execution.rs](execution.rs) | wordcode 与校验、控制流、参数/默认值、嵌套帧、字节码函数创建/默认值/环境捕获、闭包、返回值存活、失败清理、限额 |
|
||||
| [execution.rs](execution.rs) | wordcode 与校验、控制流、参数/默认值、嵌套帧、字节码函数创建/默认值/环境捕获、闭包、返回值存活、失败清理、限额、只读分片/取消/累计预算/暂停期间GC执行根 |
|
||||
| [types.rs](types.rs) | 名义身份、继承与实例关系、类型边保活/循环回收、只读类型元数据与外来句柄、显式Trait身份/继承/参数约束及后代更新原子性;统一函数分类、原生数量元数据/副作用前拒绝/能力更新/默认参数与返回类型错误根清理;仅远端执行 |
|
||||
| [typed_traits.rs](typed_traits.rs) | 类型化Trait登记、原子拒绝、源码检查关闭时的显式约束、宽参数调用、类型强边GC、体后实际能力结果检查与临时验证集合恢复;仅远端执行 |
|
||||
| [method_contracts.rs](method_contracts.rs) | 源码/原生方法覆写参数逆变/返回协变、联合参数、已证明源码/调用图只读、全局/闭包/接收者重绑定、控制流汇合与原生效果及共享赋值保守分类、祖先替换原子性、动态名称重绑定/前向名称与体前拒绝/恢复及清理;仅远端执行 |
|
||||
|
||||
@@ -1386,3 +1386,120 @@ fn later_execution_failure_releases_a_new_function_and_its_temporary_default() {
|
||||
vm.release(globals).unwrap();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suspended_read_only_call_keeps_roots_and_releases_them_on_completion() {
|
||||
use endor_core::ExecutionProgress;
|
||||
let mut vm = Vm::new().unwrap();
|
||||
let shared = vm.new_object().unwrap();
|
||||
vm.set_attr(shared, "value", Value::Number(7_i64.into()))
|
||||
.unwrap();
|
||||
let globals = vm.new_dict().unwrap();
|
||||
let mut instructions = vec![simple(Opcode::Nop); 16];
|
||||
instructions.extend([op(Opcode::LoadConst, 0), simple(Opcode::ReturnValue)]);
|
||||
let code = make_code(
|
||||
&mut vm,
|
||||
&instructions,
|
||||
vec![Value::Object(shared)],
|
||||
CodeLayout::default(),
|
||||
);
|
||||
let function = vm
|
||||
.new_function("sliced", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
let mut progress = vm
|
||||
.start_read_only_call(
|
||||
function,
|
||||
&[],
|
||||
ExecutionLimits {
|
||||
max_frames: 8,
|
||||
max_instructions: 100,
|
||||
},
|
||||
1,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
progress,
|
||||
ExecutionProgress::Pending { instructions: 1 }
|
||||
));
|
||||
assert!(
|
||||
vm.set_attr(shared, "value", Value::Number(8_i64.into()))
|
||||
.is_err()
|
||||
);
|
||||
assert!(vm.call(function, &[]).is_err());
|
||||
vm.release(function).unwrap();
|
||||
vm.release(code).unwrap();
|
||||
vm.release(globals).unwrap();
|
||||
vm.release(shared).unwrap();
|
||||
vm.collect_cycles();
|
||||
loop {
|
||||
match progress {
|
||||
ExecutionProgress::Pending { .. } => progress = vm.resume_read_only_call(2).unwrap(),
|
||||
ExecutionProgress::Complete {
|
||||
value,
|
||||
instructions,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(instructions, 18);
|
||||
assert_eq!(value, Value::Object(shared));
|
||||
assert_eq!(
|
||||
vm.get_attr(shared, "value").unwrap(),
|
||||
Value::Number(7_i64.into())
|
||||
);
|
||||
vm.release_value(value).unwrap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(!vm.has_suspended_call());
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suspended_call_cancellation_and_total_budget_restore_read_only_state() {
|
||||
let mut vm = Vm::new().unwrap();
|
||||
let shared = vm.new_object().unwrap();
|
||||
let globals = vm.new_dict().unwrap();
|
||||
let mut instructions = vec![simple(Opcode::Nop); 8];
|
||||
instructions.extend([op(Opcode::LoadConst, 0), simple(Opcode::ReturnValue)]);
|
||||
let code = make_code(
|
||||
&mut vm,
|
||||
&instructions,
|
||||
vec![Value::Object(shared)],
|
||||
CodeLayout::default(),
|
||||
);
|
||||
let function = vm
|
||||
.new_function("budget", code, globals, vec![], vec![])
|
||||
.unwrap();
|
||||
let roots = vm.ref_count(function).unwrap();
|
||||
vm.start_read_only_call(
|
||||
function,
|
||||
&[],
|
||||
ExecutionLimits {
|
||||
max_frames: 8,
|
||||
max_instructions: 3,
|
||||
},
|
||||
1,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(vm.resume_read_only_call(0).is_err());
|
||||
assert!(vm.has_suspended_call());
|
||||
vm.resume_read_only_call(1).unwrap();
|
||||
assert_eq!(
|
||||
vm.resume_read_only_call(1).unwrap_err(),
|
||||
VmError::ExecutionLimit("instructions")
|
||||
);
|
||||
assert!(!vm.has_suspended_call());
|
||||
assert_eq!(vm.ref_count(function).unwrap(), roots);
|
||||
vm.set_attr(shared, "after", Value::Bool(true)).unwrap();
|
||||
vm.start_read_only_call(function, &[], ExecutionLimits::default(), 1)
|
||||
.unwrap();
|
||||
assert!(vm.cancel_read_only_call().unwrap());
|
||||
assert!(!vm.cancel_read_only_call().unwrap());
|
||||
assert_eq!(vm.ref_count(function).unwrap(), roots);
|
||||
vm.set_attr(shared, "after", Value::Bool(false)).unwrap();
|
||||
vm.release(function).unwrap();
|
||||
vm.release(code).unwrap();
|
||||
vm.release(globals).unwrap();
|
||||
vm.release(shared).unwrap();
|
||||
assert_eq!(vm.object_count(), 0);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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) | 独立宿主仓库 |
|
||||
|
||||
@@ -158,6 +158,10 @@ fn main() -> Result<(), endor_core::VmError> {
|
||||
|
||||
`ExecutionLimits` 默认限制 1000 个活动帧,指令配额为 `usize::MAX`。`execute_with_limits`/`call_with_limits` 可显式指定限额,嵌套语言调用和原生分派共享计步器;每次原生分派额外计一步,扩展参数不单独计步。原生 Invoke 委托通过同一帧循环推进,不重置预算;详见 [原生调用](native-calls.md)。指令预算不会抢占正在执行的宿主回调。
|
||||
|
||||
`start_read_only_call(function, arguments, limits, slice)` 和 `resume_read_only_call(slice)` 提供可恢复的只读字节码调用。`ExecutionProgress::Pending` 保留同一 VM 中的活动帧、局部槽、栈、原生委托返回契约、读取追踪及所有执行根;继续时不重放已执行指令。`Complete` 返回拥有式 value、借用式 reads 和累计 instructions。slice 必须大于0,暂停只能发生在字节码指令边界;原生回调和原生委托链原子完成,软分片不保证墙钟延迟,整个调用仍共用原始硬指令/帧限额。
|
||||
|
||||
暂停期间只读保护一直有效;已有对象不可写,也不能用普通 call 或第二个 start 在同一 VM 中交错执行。宿主不得更改只读作用域栈或手动释放并非自己拥有的引用。`cancel_read_only_call`、完成或执行错误会清理帧根并退出只读作用域,错误保留 traceback。取消只终止只读执行,不是通用事务回滚。跨线程任务调度、GUI 帧发布及响应新输入由宿主负责;本接口不实现调度器。参见 execution.rs 的分片/取消/累计限额/GC 根回归(未本地执行)。
|
||||
|
||||
运行错误仍返回 `VmError`。`last_traceback()` 另提供从外到内的函数名、文件、代码首行和出错指令偏移;它保存独立元数据,不保活已经清理的帧。首行是代码声明的 first_line,不是由行号表推导出的实际错误源码行。成功的新一次调用会清空旧记录。尚无语言异常对象传播、raise/try/except 或完整源码行号表。
|
||||
|
||||
暂未实现:源码闭包编译、关键字/可变参数、生成器/协程、迭代器专用指令、数值算术、自动方法绑定、指令特化和 JIT。
|
||||
|
||||
Reference in New Issue
Block a user