docs(core): explain opcode execution and stack root transfers

This commit is contained in:
2026-11-15 17:56:37 +08:00
parent 8e800dd0b8
commit fb909de7fd
+93 -2
View File
@@ -1308,6 +1308,8 @@ impl Vm {
.new_class_with_scope(name, base, fields, methods, Some(frame.function))
}
/// 所有同步/切片/协程入口共用的显式帧循环:Some 是拥有结果,None 是软预算或允许的 await 停放。
/// 错误不在本函数销毁全部帧,由外层保存回溯再统一清理;指令索引/栈高已在代码构造时验证。
fn eval_frames(
&mut self,
frames: &mut Vec<EndorFrame>,
@@ -1316,43 +1318,58 @@ impl Vm {
slice_end: usize,
allow_wait: bool,
) -> Result<Option<Value>, VmError> {
// 每轮只推进当前最内层帧,源码调用通过帧向量增长而非 Rust 递归。
loop {
// 硬上限先于软边界检查,同点到达硬上限必须报错,不能继续 Pending。
if *executed >= limits.max_instructions {
return Err(VmError::ExecutionLimit("instructions"));
}
// 软量子结束保留帧、栈和指令指针,外层决定如何停放。
if *executed >= slice_end {
return Ok(None);
}
// 实际取指前累计一次,原生委托准备另在 prepare_call 计数。
*executed += 1;
// 当前帧必须存在;源码 Return 最外层时会提前返回结果退出循环。
let frame = frames.last_mut().expect("an evaluation frame is active");
// 按完整逻辑指令起点取指,扩展前缀已合并,非法指针返回位置错误。
let decoded = frame.executable.instruction_at(frame.instruction_offset)?;
// 保存当前起点给错误诊断,同时预置顺序后继;跳转/等待会覆盖下一位置。
frame.current_offset = decoded.offset;
frame.instruction_offset = decoded.next_offset;
// 本轮操作种类,后面的栈和槽更新按此分派。
let opcode = decoded.instruction.opcode;
// 完整 u32 操作数转为索引/数量,范围合法性由代码验证保证。
let argument = decoded.instruction.argument as usize;
// 每条指令成功后保持根计数与栈/槽一致,错误交外层清理当前帧。
match opcode {
Opcode::Await => {
// 只读调用不允许 await,不可用等待期间外部修改破坏只读快照。
if self.objects.is_read_only() {
return Err(VmError::UnsupportedOperation(
"await during read-only execution",
));
}
// 等待协议仅对象可实现,直接 Nil/数字/文本都不能 await。
let Value::Object(awaitable) = Self::top_value(frame)? else {
return Err(VmError::ObjectKindMismatch("awaitable object"));
};
// poll_await 非阻塞观察当前状态,不重跑生成等待对象的表达式。
match self.object(awaitable)?.payload().poll_await()? {
// 操作数根继续留栈,协程状态稍后接管;同步入口不允许这种停放。
crate::AwaitPoll::Pending => {
// 仅可变协程支持 Pending await,同步调用不能悄悄变为暂停。
if !allow_wait {
return Err(VmError::UnsupportedOperation(
"pending await outside a coroutine",
));
}
// Keep the evaluated operand, never replay its call
// or the statements before this await instruction.
// 保留已求值等待操作数,指针回到 Await 本身;不会重放此前调用或语句。
frame.waiting = true;
frame.instruction_offset = decoded.offset;
return Ok(None);
}
// 先取得结果根再释放等待操作数,避免结果仅由等待对象图持有时失效。
crate::AwaitPoll::Ready(value) => {
self.retain_value(&value)?;
self.discard(frame, 1);
@@ -1361,47 +1378,61 @@ impl Vm {
}
}
}
// 无操作,只消耗本轮预算并继续预置后继。
Opcode::Nop => {}
Opcode::LoadConst => {
// clone 仍为借用表示,push_borrowed 为结果栈项取得独立根。
let value = frame.executable.constants()[argument].clone();
self.push_borrowed(frame, value)?;
}
Opcode::LoadFast => {
// 未绑定 None 报具名局部错误,已绑定值压栈时另保留根。
let value = frame.localsplus[argument].clone().ok_or_else(|| {
VmError::UnboundLocal(frame.executable.slot_name(argument).into())
})?;
self.push_borrowed(frame, value)?;
}
// 局部赋值先保留新槽根,再释放原栈输入及被替换旧槽根。
Opcode::StoreFast => {
// 先执行共享引用赋值选择,可能保留旧对象身份,再按目标槽/字典协议提交。
let value = self.assignment_value(
frame.localsplus[argument].clone(),
Self::top_value(frame)?,
)?;
self.retain_value(&value)?;
self.discard(frame, 1);
// 新值先保留/写入,最后归还被替换的旧槽根,即使新旧同身份也安全。
if let Some(old) = frame.localsplus[argument].replace(value) {
self.drop_root(old);
}
}
Opcode::DeleteFast => {
// 先移走槽拥有值,使局部恢复 None,再归还旧根。
let value = frame.localsplus[argument].take().ok_or_else(|| {
VmError::UnboundLocal(frame.executable.slot_name(argument).into())
})?;
self.drop_root(value);
}
Opcode::LoadGlobal => {
// 从代码 names 表借用名称,索引不来自任意运行时文本。
let name = &frame.executable.layout().names[argument];
// 按名记录命中/缺失,只在全局缺失时查询捕获的内置字典。
let mut value = self.objects.dict_get_name(frame.globals, name)?;
// Some(Nil) 也是命中,不会回退内置空间。
if value.is_none()
&& let Some(builtins) = frame.builtins
{
value = self.objects.dict_get_name(builtins, name)?;
}
// 全局和可选内置均未命中才报 NameNotFound。
let value = value.ok_or_else(|| VmError::NameNotFound(name.clone()))?;
self.push_borrowed(frame, value)?;
}
// 全局修改由字典拥有新值内部边,成功后释放输入栈根。
Opcode::StoreGlobal => {
// 全局写入使用直接文本键,与名称读取协议对应。
let key = Value::Text(frame.executable.layout().names[argument].clone());
// 先执行共享引用赋值选择,可能保留旧对象身份,再按目标槽/字典协议提交。
let value = self.assignment_value(
self.dict_get(frame.globals, &key)?,
Self::top_value(frame)?,
@@ -1410,17 +1441,22 @@ impl Vm {
self.discard(frame, 1);
}
Opcode::DeleteGlobal => {
// 从代码 names 表借用名称,索引不来自任意运行时文本。
let name = &frame.executable.layout().names[argument];
// 删除缺键报告名称错误,字典删除协议其他错误正常传播。
if !self.dict_delete(frame.globals, &Value::Text(name.clone()))? {
return Err(VmError::NameNotFound(name.clone()));
}
}
Opcode::LoadDeref | Opcode::LoadClosure => {
// 对应槽必须存活且为 Cell,本帧与闭包函数均可能持有其根/边。
let cell = self.cell_slot(frame, argument)?;
// LoadClosure 压 Cell 本身,LoadDeref 压当前内容,两者都经 push_borrowed 取得栈根。
let value = if opcode == Opcode::LoadClosure {
Value::Object(cell)
} else {
self.cell_get(cell).map_err(|error| {
// 语言层将空捕获槽映射为变量未绑定,其余底层错误保持原样。
if error == VmError::EmptyCell {
VmError::UnboundLocal(frame.executable.slot_name(argument).into())
} else {
@@ -1430,15 +1466,20 @@ impl Vm {
};
self.push_borrowed(frame, value)?;
}
// 空 Cell 读取经 ok() 视作没有旧值,写入成功后才弹出输入。
Opcode::StoreDeref => {
// 对应槽必须存活且为 Cell,本帧与闭包函数均可能持有其根/边。
let cell = self.cell_slot(frame, argument)?;
// 读取/计算下一状态后再更新栈,失败由原帧保留输入清理责任。
let value =
self.assignment_value(self.cell_get(cell).ok(), Self::top_value(frame)?)?;
self.cell_set(cell, value)?;
self.discard(frame, 1);
}
Opcode::DeleteDeref => {
// 对应槽必须存活且为 Cell,本帧与闭包函数均可能持有其根/边。
let cell = self.cell_slot(frame, argument)?;
// 当前实现将读取错误视为未绑定;上方 cell_slot 已检查槽载荷。
if self.cell_get(cell).is_err() {
return Err(VmError::UnboundLocal(
frame.executable.slot_name(argument).into(),
@@ -1446,41 +1487,56 @@ impl Vm {
}
self.cell_clear(cell)?;
}
// 丢弃一个拥有栈项,协议 Null 则不 release。
Opcode::PopTop => self.discard(frame, 1),
Opcode::Copy => {
// Copy 复制表示不自动 retain,对象项下方另取新栈根,Null 可直接复制。
let entry = frame.stack[frame.stack.len() - argument].clone();
// 协议 Null 没有对象根;普通对象项复制时须取得一份新根。
if let StackEntry::Value(value) = &entry {
self.retain_value(value)?;
}
frame.stack.push(entry);
}
Opcode::Swap => {
// Swap 仅交换现有拥有槽,不增加或释放任何根。
let length = frame.stack.len();
frame.stack.swap(length - 1, length - argument);
}
// 压入专用调用标记,不能与 Value::Nil 混淆。
Opcode::PushNull => frame.stack.push(StackEntry::Null),
Opcode::Call => {
// 调用布局是函数、receiver/Null、显式参数,argument 只计显式实参。
let start = frame.stack.len() - argument - 2;
// 调用目标必须为对象,是否可调用由 prepare_call/载荷协议继续判定。
let Value::Object(function) = Self::stack_value(frame, start)? else {
return Err(VmError::UnsupportedOperation("call"));
};
// 先收集借用参数,原栈输入直到调用准备完成前保持根保护。
let mut arguments = Vec::with_capacity(argument + 1);
// 非 Null 的协议接收者先加入实际参数,Null 则不传入语言实参。
if let StackEntry::Value(value) = &frame.stack[start + 1] {
arguments.push(value.clone());
}
// 后续显式参数按源码顺序传入,不在此处归还原栈根。
for index in start + 2..frame.stack.len() {
arguments.push(Self::stack_value(frame, index)?);
}
// 准备成功得到已持根源码帧或拥有结果,之后才能清理当前帧调用输入。
match self.prepare_call(function, arguments, executed, limits)? {
// 新帧还未加入向量,若深度预算不足必须主动清理它。
PreparedCall::Frame(mut callee) => {
// 硬帧限制包括现有所有源码帧,不把原生委托另算源码帧。
if frames.len() >= limits.max_frames {
self.clear_frame(callee);
return Err(VmError::ExecutionLimit("frames"));
}
// 记录调用者索引,无裸指针,不受向量扩容影响。
callee.previous = Some(frames.len() - 1);
self.discard(frames.last_mut().unwrap(), argument + 2);
frames.push(callee);
}
// 原生直接结果已经拥有根,清除调用输入后直接移入栈,不再 retain。
PreparedCall::Value(value) => {
self.discard(frames.last_mut().unwrap(), argument + 2);
frames
@@ -1492,7 +1548,9 @@ impl Vm {
}
}
Opcode::ReturnValue => {
// 返回值先借用读取,实际根在后面 pop 时从帧栈移交,不能提前 release。
let result = Self::top_value(frame)?;
// 源码返回注解已解析并保活,执行中全局重绑定不能替换该约束。
if let Some(expected) = frame.return_type
&& !self.objects.matches_annotation(&result, expected)?
{
@@ -1508,31 +1566,39 @@ impl Vm {
actual: self.objects.value_type_name(&result)?,
});
}
// 所有委托返回约束都要满足,按最内层到外层顺序检查。
for check in frame.native_returns.iter().rev() {
check.check(&self.objects, &result)?;
}
// 仅移出结果项不 release,将它原有的栈根转交调用者/外层结果。
frame.stack.pop();
self.clear_frame(frames.pop().unwrap());
// 还有调用者则接管结果根到其栈;最外层直接移交给宿主。
if let Some(caller) = frames.last_mut() {
caller.stack.push(StackEntry::Value(result));
} else {
return Ok(Some(result));
}
}
// 无条件跳转仅改变下一位置,不改变栈高度或引用计数。
Opcode::JumpForward | Opcode::JumpBackward => {
frame.instruction_offset = decoded.jump_target()?
}
Opcode::PopJumpIfFalse | Opcode::PopJumpIfTrue => {
// 先求真再丢弃输入根,协议错误时输入仍在帧中供外层清理。
let truth = self.value_is_true(&Self::top_value(frame)?)?;
self.discard(frame, 1);
// 条件成立才覆盖预置顺序后继,跳转距离以代码单元计算。
if truth == (opcode == Opcode::PopJumpIfTrue) {
frame.instruction_offset = decoded.jump_target()?;
}
}
Opcode::ToBool | Opcode::UnaryNot => {
// 先求真再丢弃输入根,协议错误时输入仍在帧中供外层清理。
let truth = self.value_is_true(&Self::top_value(frame)?)?;
self.discard(frame, 1);
frame.stack.push(StackEntry::Value(Value::Bool(
// ToBool 保留真值,UnaryNot 取反,两者都输出直接 Bool。
if opcode == Opcode::UnaryNot {
!truth
} else {
@@ -1541,10 +1607,15 @@ impl Vm {
)));
}
Opcode::BinaryArithmetic => {
// 加法额外支持两个直接 Text 拼接,堆字符串不在此快速路径中。
if decoded.instruction.argument == 0 {
// 读取左输入借用表示,原栈根继续维持对象存活。
let left = Self::stack_value(frame, frame.stack.len() - 2)?;
// 读取右输入借用表示,不能先弹栈再访问对象载荷。
let right = Self::top_value(frame)?;
// 仅双直接文本走拼接,其他情况继续数值运算并校验 Number。
if let (Value::Text(a), Value::Text(b)) = (left, right) {
// 限制合并 UTF-8 字节数为 1 MiB,饱和相加防预算绕回。
if a.len().saturating_add(b.len()) > 1_048_576 {
return Err(VmError::InvalidArgument(
"text concatenation exceeds operation budget",
@@ -1555,14 +1626,19 @@ impl Vm {
continue;
}
}
// 读取左输入借用表示,原栈根继续维持对象存活。
let left = Self::stack_value(frame, frame.stack.len() - 2)?;
// 读取右输入借用表示,不能先弹栈再访问对象载荷。
let right = Self::top_value(frame)?;
// 直接/堆数值统一为借用 Number;缺失表示报 Number 种类错误。
let left = self
.number_value(&left)?
.ok_or(VmError::ObjectKindMismatch("Number"))?;
// 右值同样要求数值,不能隐式把文本或 Bool 转数字。
let right = self
.number_value(&right)?
.ok_or(VmError::ObjectKindMismatch("Number"))?;
// 操作数映射已由代码验证,运算仍可因零除/预算/非有限值报错。
let result = left.arithmetic(
right,
crate::ArithmeticOp::from_argument(decoded.instruction.argument).unwrap(),
@@ -1571,17 +1647,23 @@ impl Vm {
frame.stack.push(StackEntry::Value(Value::Number(result)));
}
Opcode::UnaryNegate => {
// 借用栈顶,原拥有根保留至协议运算或修改成功。
let value = Self::top_value(frame)?;
// 数值取负只接受直接/堆 Number,不丢弃其他类型信息做隐式转换。
let value = self
.number_value(&value)?
.ok_or(VmError::ObjectKindMismatch("Number"))?;
// 先成功计算再释放输入,输出直接 Number 自己拥有 Rust 数值数据。
let result = value.negated()?;
self.discard(frame, 1);
frame.stack.push(StackEntry::Value(Value::Number(result)));
}
Opcode::CompareOp => {
// 读取左输入借用表示,原栈根继续维持对象存活。
let left = Self::stack_value(frame, frame.stack.len() - 2)?;
// 读取右输入借用表示,不能先弹栈再访问对象载荷。
let right = Self::top_value(frame)?;
// 数学/文本/对象协议在辅助函数统一选择,结果是直接 Bool。
let result = self.compare_values(
&left,
&right,
@@ -1591,23 +1673,28 @@ impl Vm {
frame.stack.push(StackEntry::Value(Value::Bool(result)));
}
Opcode::LoadAttr => {
// 属性协议需要对象 owner,直接值不能假装有托管动态字段。
let Value::Object(owner) = Self::top_value(frame)? else {
return Err(VmError::ObjectKindMismatch("object"));
};
// 读取/计算下一状态后再更新栈,失败由原帧保留输入清理责任。
let value =
self.bind_attr(owner, &frame.executable.layout().names[argument])?;
self.discard(frame, 1);
frame.stack.push(StackEntry::Value(value));
}
Opcode::BuildClass => {
// 构类成功返回拥有根,之后才丢弃名称/基类/字段/方法输入四项。
let class = self.build_class(frame)?;
self.discard(frame, 4);
frame.stack.push(StackEntry::Value(Value::Object(class)));
}
Opcode::StoreAttr => {
// 属性协议需要对象 owner,直接值不能假装有托管动态字段。
let Value::Object(owner) = Self::top_value(frame)? else {
return Err(VmError::ObjectKindMismatch("object"));
};
// 读取/计算下一状态后再更新栈,失败由原帧保留输入清理责任。
let value = Self::stack_value(frame, frame.stack.len() - 2)?;
self.set_attr(
owner,
@@ -1617,6 +1704,7 @@ impl Vm {
self.discard(frame, 2);
}
Opcode::BuildMap => {
// 字典构造取得根后,释放原键值栈根,由字典内部边接管持有。
let dictionary = self.build_map(frame, argument)?;
self.discard(frame, argument * 2);
frame
@@ -1624,12 +1712,15 @@ impl Vm {
.push(StackEntry::Value(Value::Object(dictionary)));
}
Opcode::MakeFunction => {
// 函数构造校验并保留全部边后才消费原栈输入,避免捕获值提前释放。
let function = self.make_function(frame, decoded.instruction.argument)?;
// 按与构造器相同的位段计数弹出默认值、Cell 与代码项。
let (defaults, closure) =
Instruction::function_counts(decoded.instruction.argument);
self.discard(frame, defaults + closure + 1);
frame.stack.push(StackEntry::Value(Value::Object(function)));
}
// 扩展参数已由解码器合并,验证代码不含可执行的独立前缀。
Opcode::ExtendedArg => unreachable!("EXTENDED_ARG was decoded before evaluation"),
}
}