docs(core): explain synchronous and sliced readonly calls

This commit is contained in:
2026-11-15 17:49:09 +08:00
parent 48629ab55b
commit 8e190b25ec
+66 -1
View File
@@ -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);
}