docs(core): explain synchronous and sliced readonly calls
This commit is contained in:
+66
-1
@@ -1,3 +1,7 @@
|
||||
//! VM 的指令执行与调用生命周期;宿主入口在此准备帧,底层对象规则仍通过 Vm/Store 操作。
|
||||
//! 执行结果中的对象拥有一份外部根,参数通常借用;帧/原生上下文负责保留和清理执行期间根。
|
||||
//! 只读续执行跨 Pending 保持 Heap 作用域;托管可变协程另存载荷,不沿用该全局作用域。
|
||||
|
||||
use crate::ExecutionProgress;
|
||||
use crate::frame::{Coroutine, CoroutineState, NativeReturn, StackEntry, SuspendedCall};
|
||||
use crate::{
|
||||
@@ -8,11 +12,15 @@ use crate::{
|
||||
use crate::{CoroutineProgress, ObjectPayload};
|
||||
use crate::{NativeContext, NativeResult};
|
||||
|
||||
/// prepare_call 统一结果,成功的 Value/Frame 都携带执行期间已取得的拥有根。
|
||||
enum PreparedCall {
|
||||
// 原生或委托调用在准备阶段直接完成,结果根移交上层。
|
||||
Value(Value),
|
||||
// 源码帧已绑定参数并持有根,交给循环执行或暂存。
|
||||
Frame(EndorFrame),
|
||||
}
|
||||
|
||||
/// 求值器构造携带代码单元偏移的字节码错误,不将偏移误报为源码行。
|
||||
fn invalid(offset: usize, reason: &'static str) -> VmError {
|
||||
VmError::InvalidBytecode { offset, reason }
|
||||
}
|
||||
@@ -21,24 +29,31 @@ impl Vm {
|
||||
/// Execute a code object in a globals dictionary. Like CPython's EvalCode
|
||||
/// entry path, this constructs a temporary function and enters its frame.
|
||||
/// Object results own one external reference: release_value() when done.
|
||||
/// 宿主执行已验证代码,使用默认预算,最终对象结果归调用方拥有。
|
||||
pub fn execute(&mut self, code: ObjectRef, globals: ObjectRef) -> Result<Value, VmError> {
|
||||
self.execute_with_limits(code, globals, ExecutionLimits::default())
|
||||
}
|
||||
|
||||
/// 宿主以指定预算执行代码,临时源码函数包装全局/代码引用,调用结束后释放包装根。
|
||||
pub fn execute_with_limits(
|
||||
&mut self,
|
||||
code: ObjectRef,
|
||||
globals: ObjectRef,
|
||||
limits: ExecutionLimits,
|
||||
) -> Result<Value, VmError> {
|
||||
// 新调用从空诊断开始,后续失败才写入该次剩余帧回溯。
|
||||
self.last_traceback.clear();
|
||||
// 借用代码载荷的诊断名后转为拥有 String,避免跨可变分配保留载荷借用。
|
||||
let name = self
|
||||
.payload::<EndorCodeObject>(code, "code")?
|
||||
.code()
|
||||
.name()
|
||||
.to_owned();
|
||||
// 创建无参数默认值/无捕获的临时执行函数;代码所需布局仍由函数校验。
|
||||
let function = self.new_function(name, code, globals, Vec::new(), Vec::new())?;
|
||||
// 先保存调用结果,使成功和错误都能在返回前释放临时函数根。
|
||||
let result = self.call_with_limits(function, &[], limits);
|
||||
// 结果对象已由调用路径保留,释放包装函数不应使返回结果失效。
|
||||
self.release(function)?;
|
||||
result
|
||||
}
|
||||
@@ -46,6 +61,7 @@ impl Vm {
|
||||
/// Dispatch both language and host functions. Arguments are borrowed;
|
||||
/// every Object result owns one external reference, including native
|
||||
/// callbacks that return one of their borrowed arguments.
|
||||
/// 宿主同步调用源码/原生函数,参数借用,返回对象根需 release_value。
|
||||
pub fn call(&mut self, function: ObjectRef, arguments: &[Value]) -> Result<Value, VmError> {
|
||||
self.call_with_limits(function, arguments, ExecutionLimits::default())
|
||||
}
|
||||
@@ -53,12 +69,14 @@ impl Vm {
|
||||
/// Allow result allocation while rejecting mutations of objects that were
|
||||
/// already alive on entry. All reads share the call's normal frame budget.
|
||||
/// Returned read handles are borrowed dependency identities, not owned roots.
|
||||
/// 宿主同步只读兼容入口,只保留对象读取集合,丢弃精确依赖键。
|
||||
pub fn call_read_only_with_limits(
|
||||
&mut self,
|
||||
function: ObjectRef,
|
||||
arguments: &[Value],
|
||||
limits: ExecutionLimits,
|
||||
) -> Result<(Value, Vec<ObjectRef>), VmError> {
|
||||
// 精确入口统一维护只读作用域与结果所有权,这里只改变返回观察的形状。
|
||||
let (value, reads) =
|
||||
self.call_read_only_precise_with_limits(function, arguments, limits)?;
|
||||
Ok((value, reads.objects))
|
||||
@@ -67,43 +85,56 @@ impl Vm {
|
||||
/// Synchronous read-only call with precise observations as well as the
|
||||
/// conservative object list. Result ownership and hard limits match call().
|
||||
/// ReadSet and all identities/value snapshots inside it are borrowed only.
|
||||
/// 宿主同步只读入口,建立保护快照,成功返回拥有结果与借用观察集合。
|
||||
pub fn call_read_only_precise_with_limits(
|
||||
&mut self,
|
||||
function: ObjectRef,
|
||||
arguments: &[Value],
|
||||
limits: ExecutionLimits,
|
||||
) -> Result<(Value, crate::ReadSet), VmError> {
|
||||
// 快照进入前对象身份;之后新建结果允许初始化,但已有对象受写屏障保护。
|
||||
let scope = self.objects.begin_read_only();
|
||||
// 先保存调用结果,使成功和错误都能在返回前释放临时函数根。
|
||||
let result = self.call_with_limits(function, arguments, limits);
|
||||
// 结束域先取回借用观察,之后才将调用成功值与观察组合返回。
|
||||
let reads = self.objects.end_read_only_precise(scope)?;
|
||||
result.map(|value| (value, reads))
|
||||
}
|
||||
|
||||
/// 同步调用核心:准备可立即完成的原生结果或源码帧,执行后清理帧及保存错误回溯。
|
||||
pub fn call_with_limits(
|
||||
&mut self,
|
||||
function: ObjectRef,
|
||||
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)? {
|
||||
PreparedCall::Value(value) => return Ok(value),
|
||||
PreparedCall::Frame(frame) => frame,
|
||||
};
|
||||
// 源码帧已创建但预算不允许执行,必须先清理所持根再报错。
|
||||
if limits.max_frames == 0 {
|
||||
self.clear_frame(frame);
|
||||
return Err(VmError::ExecutionLimit("frames"));
|
||||
}
|
||||
// 显式帧向量代替 Rust 递归调用,调用深度由硬预算控制。
|
||||
let mut frames = vec![frame];
|
||||
// 同步入口没有软切片限制且不允许 Pending await;只读恢复另按 slice_end 执行。
|
||||
let result = self
|
||||
.eval_frames(&mut frames, limits, &mut executed, usize::MAX, false)
|
||||
.map(|value| value.expect("unbounded slice completes or fails"));
|
||||
// 错误时先保存诊断,再销毁帧;使用 current_offset 而不是下一指令偏移。
|
||||
if result.is_err() {
|
||||
self.last_traceback = frames
|
||||
.iter()
|
||||
@@ -115,6 +146,7 @@ impl Vm {
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
// 逐帧回收栈、局部、函数和返回约束根,不把 Rust drop 误当托管 release。
|
||||
while let Some(frame) = frames.pop() {
|
||||
self.clear_frame(frame);
|
||||
}
|
||||
@@ -124,6 +156,7 @@ impl Vm {
|
||||
/// 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.
|
||||
/// 宿主开始单个可分片只读调用,原生准备可立即完成,源码帧停放在 VM 内。
|
||||
pub fn start_read_only_call(
|
||||
&mut self,
|
||||
function: ObjectRef,
|
||||
@@ -131,22 +164,32 @@ impl Vm {
|
||||
limits: ExecutionLimits,
|
||||
slice: usize,
|
||||
) -> Result<ExecutionProgress, VmError> {
|
||||
// 拒绝零量子,避免调度者永远不推进且仍报告 Pending。
|
||||
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_precise(scope) {
|
||||
Ok(reads) => reads,
|
||||
// 出错分支在返回前处理本分支拥有的结果/作用域,不能保留半完成状态。
|
||||
Err(error) => {
|
||||
self.drop_root(value);
|
||||
return Err(error);
|
||||
@@ -159,21 +202,25 @@ impl Vm {
|
||||
instructions: executed,
|
||||
})
|
||||
}
|
||||
// 源帧尚未执行,先通过帧预算再交给 VM 的单个续执行槽。
|
||||
Ok(PreparedCall::Frame(frame)) => {
|
||||
// 源码帧已创建但预算不允许执行,必须先清理所持根再报错。
|
||||
if limits.max_frames == 0 {
|
||||
self.clear_frame(frame);
|
||||
self.objects.end_read_only(scope)?;
|
||||
return Err(VmError::ExecutionLimit("frames"));
|
||||
}
|
||||
// 保存根仍由帧持有,Heap 只读作用域跨后续 Pending 不关闭。
|
||||
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)
|
||||
@@ -181,16 +228,20 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
|
||||
/// 调度者判断是否有只读续执行,不表示托管协程数量或等待状态。
|
||||
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> {
|
||||
// 拒绝零量子,避免调度者永远不推进且仍报告 Pending。
|
||||
if slice == 0 {
|
||||
return Err(VmError::InvalidArgument("execution slice must be positive"));
|
||||
}
|
||||
// 读取此前累计计数,新增软边界以 saturating_add 防整数绕回。
|
||||
let executed = self
|
||||
.suspended_call
|
||||
.as_ref()
|
||||
@@ -199,11 +250,14 @@ impl Vm {
|
||||
self.resume_read_only_to(executed.saturating_add(slice))
|
||||
}
|
||||
|
||||
/// start/resume 共用的累计软边界执行器;Pending 原样放回状态,终态关闭只读域。
|
||||
fn resume_read_only_to(&mut self, slice_end: usize) -> Result<ExecutionProgress, VmError> {
|
||||
// 暂时取走续执行状态独占推进;只有 Pending 才重新放回,终态不残留槽。
|
||||
let mut call = self
|
||||
.suspended_call
|
||||
.take()
|
||||
.ok_or(VmError::InvalidArgument("no suspended call"))?;
|
||||
// 按累计 slice_end 推进现有帧;false 禁止 await 停放,None 表示软切片结束。
|
||||
let result = self.eval_frames(
|
||||
&mut call.frames,
|
||||
call.limits,
|
||||
@@ -211,11 +265,15 @@ impl Vm {
|
||||
slice_end,
|
||||
false,
|
||||
);
|
||||
// 软切片耗尽:保留原帧/作用域/硬预算计数,不清理根或重新准备函数。
|
||||
if let Ok(None) = result {
|
||||
// 报告从调用起点累计的工作量,不是当前片局部数量。
|
||||
let instructions = call.executed;
|
||||
// 将完整续执行状态归还 VM,原有只读屏障继续生效。
|
||||
self.suspended_call = Some(call);
|
||||
return Ok(ExecutionProgress::Pending { instructions });
|
||||
}
|
||||
// 错误时先保存诊断,再销毁帧;使用 current_offset 而不是下一指令偏移。
|
||||
if result.is_err() {
|
||||
self.last_traceback = call
|
||||
.frames
|
||||
@@ -228,10 +286,13 @@ impl Vm {
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
// 逐帧回收栈、局部、函数和返回约束根,不把 Rust drop 误当托管 release。
|
||||
while let Some(frame) = call.frames.pop() {
|
||||
self.clear_frame(frame);
|
||||
}
|
||||
// 完成/失败统一关闭作用域,保留收尾错误供下方与执行结果一起处理。
|
||||
let reads = self.objects.end_read_only_precise(call.scope);
|
||||
// 仅执行与收尾均成功才交付结果;收尾失败释放成功结果,执行失败优先传播执行错误。
|
||||
match (result, reads) {
|
||||
(Ok(Some(value)), Ok(reads)) => Ok(ExecutionProgress::Complete {
|
||||
value,
|
||||
@@ -239,6 +300,7 @@ impl Vm {
|
||||
reads: reads.objects,
|
||||
instructions: call.executed,
|
||||
}),
|
||||
// 已产生的成功结果此时不能交付,归还其对象根避免泄漏。
|
||||
(Ok(Some(value)), Err(error)) => {
|
||||
self.drop_root(value);
|
||||
Err(error)
|
||||
@@ -249,10 +311,13 @@ impl Vm {
|
||||
}
|
||||
|
||||
/// Release all retained execution roots and close the read-only scope.
|
||||
/// 宿主放弃当前只读续执行,逐帧释放根并关闭作用域,无任务时返回 false。
|
||||
pub fn cancel_read_only_call(&mut self) -> Result<bool, VmError> {
|
||||
// 没有续执行是正常无操作;有任务则一次取走,后续取消不会重复释放。
|
||||
let Some(mut call) = self.suspended_call.take() else {
|
||||
return Ok(false);
|
||||
};
|
||||
// 逐帧回收栈、局部、函数和返回约束根,不把 Rust drop 误当托管 release。
|
||||
while let Some(frame) = call.frames.pop() {
|
||||
self.clear_frame(frame);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user