docs(core): explain opcode execution and stack root transfers
This commit is contained in:
+93
-2
@@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user