560 lines
24 KiB
Rust
560 lines
24 KiB
Rust
//! 编译器/宿主提交代码后在此构造不可变执行数据;core::frame 共享代码,eval 按偏移读取指令。
|
||
//! 构造先验证名字布局、解码和控制流栈高度;这不是执行器,也不证明运行时值类型或调用一定成功。
|
||
//! 常量中的对象句柄在 EndorCode 内只是表示,由堆上 EndorCodeObject 的 trace 建立托管强边。
|
||
|
||
use std::any::Any;
|
||
use std::collections::{BTreeSet, VecDeque};
|
||
use std::sync::Arc;
|
||
|
||
use crate::builtins::trace_value;
|
||
use crate::instruction::{compare_operation, decode, function_counts, invalid};
|
||
use crate::{DecodedInstruction, Instruction, ObjectPayload, ObjectRef, Opcode, Value, VmError};
|
||
|
||
/// 编译器生成、执行帧消费的槽布局:参数占局部槽前缀,捕获局部仍占原槽但存 Cell。
|
||
/// 外部自由变量 Cell 追加在所有局部槽之后,不能与局部变量重名。
|
||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||
pub struct CodeLayout {
|
||
/// 位置参数名称顺序,必须与 locals 前缀相同。
|
||
pub parameters: Vec<String>,
|
||
/// 全部局部名称,参数必须是其完整前缀。
|
||
pub locals: Vec<String>,
|
||
/// 需要提升为 Cell 的局部名称子集,不额外新增局部槽。
|
||
pub cell_variables: Vec<String>,
|
||
/// 从外层捕获的 Cell 名称,顺序决定闭包传入槽顺序。
|
||
pub free_variables: Vec<String>,
|
||
/// 全局与属性操作使用的文本名称索引表。
|
||
pub names: Vec<String>,
|
||
}
|
||
|
||
/// 编译器传入的源码类型声明,与执行槽布局分开;类型名称在调用/契约检查时解析。
|
||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
pub struct SourceSignature {
|
||
/// 位置参数的类型注解顺序,数量必须与代码参数槽数相同。
|
||
pub parameters: Vec<String>,
|
||
/// 非空的返回类型注解名称,尚不保证该名称可成功解析。
|
||
pub return_type: String,
|
||
/// 可选方法所属类型名,用于源码方法契约;Some 空串非法。
|
||
pub method_owner: Option<String>,
|
||
}
|
||
|
||
/// 已通过构造校验的 Rust 执行数据,尚未分配为托管代码对象;clone 不为常量对象增加根。
|
||
#[derive(Debug, Clone)]
|
||
pub struct EndorCode {
|
||
/// 保留的物理双字节单元编码,含扩展前缀。
|
||
bytecode: Vec<u8>,
|
||
/// 解码后按偏移排序的逻辑指令列表,执行时无需重新组合前缀。
|
||
instructions: Vec<DecodedInstruction>,
|
||
/// 常量表示;Rust clone 不为其中的对象句柄 retain。
|
||
constants: Vec<Value>,
|
||
/// 经 validate_layout 检查的槽和名称布局。
|
||
layout: CodeLayout,
|
||
/// 从可达控制流计算的最大栈高度,供帧预留容量。
|
||
stack_size: usize,
|
||
/// 诊断显示的代码名称。
|
||
name: String,
|
||
/// 诊断显示的源码文件名,不读取该文件。
|
||
filename: String,
|
||
/// 调用方给出的起始行,供回溯显示,不是逐指令行号表。
|
||
first_line: usize,
|
||
/// 可选源码声明;不改变已校验的指令布局。
|
||
signature: Option<SourceSignature>,
|
||
}
|
||
|
||
impl EndorCode {
|
||
/// 宿主从原始字节码建立只有参数局部槽的代码;需要额外局部或本地 Cell 时用 with_layout。
|
||
pub fn new(
|
||
bytecode: Vec<u8>,
|
||
constants: Vec<Value>,
|
||
parameters: Vec<String>,
|
||
free_variables: Vec<String>,
|
||
) -> Result<Self, VmError> {
|
||
Self::with_layout(
|
||
bytecode,
|
||
constants,
|
||
CodeLayout {
|
||
locals: parameters.clone(),
|
||
parameters,
|
||
free_variables,
|
||
..CodeLayout::default()
|
||
},
|
||
)
|
||
}
|
||
|
||
/// 编译器与宿主把逻辑指令编码后走统一构造校验;此入口不替调用者修补跳转距离。
|
||
pub fn from_instructions(
|
||
instructions: &[Instruction],
|
||
constants: Vec<Value>,
|
||
layout: CodeLayout,
|
||
) -> Result<Self, VmError> {
|
||
Self::with_layout(Instruction::encode_all(instructions)?, constants, layout)
|
||
}
|
||
|
||
/// 全部构造入口汇合处:先检查布局,再解码,最后验证索引、跳转和可达路径栈高度。
|
||
pub fn with_layout(
|
||
bytecode: Vec<u8>,
|
||
constants: Vec<Value>,
|
||
layout: CodeLayout,
|
||
) -> Result<Self, VmError> {
|
||
// 布局错误必须先拒绝,后续槽索引校验依赖这些不变量。
|
||
validate_layout(&layout)?;
|
||
// 合并 EXTENDED_ARG 并保留物理单元偏移,未知/截断编码在这里失败。
|
||
let instructions = decode(&bytecode)?;
|
||
// 校验成功后才建立可供执行器使用的代码,栈高只来自可达指令。
|
||
let stack_size = verify(&instructions, constants.len(), &layout)?;
|
||
Ok(Self {
|
||
bytecode,
|
||
instructions,
|
||
constants,
|
||
layout,
|
||
stack_size,
|
||
name: "<code>".into(),
|
||
filename: "<endor>".into(),
|
||
first_line: 1,
|
||
signature: None,
|
||
})
|
||
}
|
||
|
||
/// 编译器在代码构造后附加声明;这里只检查数量和非空字符串,不解析类型名。
|
||
pub fn with_source_signature(mut self, signature: SourceSignature) -> Result<Self, VmError> {
|
||
// 参数注解数量必须对应位置参数;每项与可选 owner 都不允许空串。
|
||
if signature.parameters.len() != self.parameters().len()
|
||
|| signature.return_type.is_empty()
|
||
|| signature.parameters.iter().any(String::is_empty)
|
||
|| signature
|
||
.method_owner
|
||
.as_ref()
|
||
.is_some_and(String::is_empty)
|
||
{
|
||
return Err(VmError::InvalidArgument(
|
||
"source signature does not match code parameters",
|
||
));
|
||
}
|
||
self.signature = Some(signature);
|
||
Ok(self)
|
||
}
|
||
/// Store 的源码调用/效果/契约检查借用声明;None 表示没有附加源码类型元数据。
|
||
pub fn source_signature(&self) -> Option<&SourceSignature> {
|
||
self.signature.as_ref()
|
||
}
|
||
|
||
/// Store/effects 的保守只读证明:所有指令(包括不可达部分)都须在白名单内。
|
||
/// 局部赋值可能触发共享引用更新,不自动视为纯;未知调用/写入也不在这里证明安全。
|
||
pub fn is_statically_read_only(&self) -> bool {
|
||
self.instructions
|
||
.iter()
|
||
.all(|decoded| read_only_opcode(decoded.instruction.opcode))
|
||
}
|
||
|
||
/// 编译器/宿主附加诊断位置;不校验文件存在或起始行范围,也不改变执行布局。
|
||
pub fn with_location(
|
||
mut self,
|
||
name: impl Into<String>,
|
||
filename: impl Into<String>,
|
||
first_line: usize,
|
||
) -> Self {
|
||
self.name = name.into();
|
||
self.filename = filename.into();
|
||
self.first_line = first_line;
|
||
self
|
||
}
|
||
|
||
/// 宿主诊断/序列查看借用原编码;不允许绕过验证修改字节。
|
||
pub fn bytecode(&self) -> &[u8] {
|
||
&self.bytecode
|
||
}
|
||
/// eval、效果分析与诊断借用已解码逻辑指令。
|
||
pub fn instructions(&self) -> &[DecodedInstruction] {
|
||
&self.instructions
|
||
}
|
||
/// 执行器读取常量,代码载荷 trace 枚举其对象边;返回切片不新增托管根。
|
||
pub fn constants(&self) -> &[Value] {
|
||
&self.constants
|
||
}
|
||
/// 帧参数绑定与函数默认值校验读取参数顺序。
|
||
pub fn parameters(&self) -> &[String] {
|
||
&self.layout.parameters
|
||
}
|
||
/// 函数闭包校验和帧初始化读取捕获槽顺序。
|
||
pub fn free_variables(&self) -> &[String] {
|
||
&self.layout.free_variables
|
||
}
|
||
/// 编译器/帧与 Store 分析借用完整布局。
|
||
pub fn layout(&self) -> &CodeLayout {
|
||
&self.layout
|
||
}
|
||
/// 执行帧用可达路径最大栈高预留容量,不是执行步数上限。
|
||
pub fn stack_size(&self) -> usize {
|
||
self.stack_size
|
||
}
|
||
/// 错误和代码 repr 读取诊断名称。
|
||
pub fn name(&self) -> &str {
|
||
&self.name
|
||
}
|
||
/// 回溯与 repr 读取文件标识,不访问文件系统。
|
||
pub fn filename(&self) -> &str {
|
||
&self.filename
|
||
}
|
||
/// 回溯/诊断读取调用方设置的起始行。
|
||
pub fn first_line(&self) -> usize {
|
||
self.first_line
|
||
}
|
||
/// 帧创建 localsplus 时计算局部槽加外部自由变量槽的总数。
|
||
pub fn slot_count(&self) -> usize {
|
||
self.layout.locals.len() + self.layout.free_variables.len()
|
||
}
|
||
/// 帧初始化/闭包访问查询槽是否存 Cell,越界槽返回 false。
|
||
pub fn is_cell_slot(&self, slot: usize) -> bool {
|
||
is_cell_slot(&self.layout, slot)
|
||
}
|
||
/// 执行器为已验证槽生成未绑定变量等诊断时查询名称。
|
||
///
|
||
/// # Panics
|
||
/// slot 超出局部/自由变量布局时 panic;调用者不能传入未经边界检查的槽。
|
||
pub fn slot_name(&self, slot: usize) -> &str {
|
||
// 局部槽保留原顺序,自由变量索引从局部槽末尾偏移。
|
||
if slot < self.layout.locals.len() {
|
||
&self.layout.locals[slot]
|
||
} else {
|
||
&self.layout.free_variables[slot - self.layout.locals.len()]
|
||
}
|
||
}
|
||
/// eval 按指令指针查询完整逻辑起点;非法偏移返回带位置的字节码错误。
|
||
pub fn instruction_at(&self, offset: usize) -> Result<DecodedInstruction, VmError> {
|
||
instruction_index(&self.instructions, offset)
|
||
.map(|index| self.instructions[index])
|
||
.ok_or_else(|| {
|
||
invalid(
|
||
offset,
|
||
"instruction pointer is not on an instruction boundary",
|
||
)
|
||
})
|
||
}
|
||
}
|
||
|
||
/// Store::new_code 分配的代码载荷;函数持有其托管引用,帧可共享内部不可变执行数据。
|
||
#[derive(Debug, Clone)]
|
||
pub struct EndorCodeObject {
|
||
/// 共享不可变 Rust 执行数据;Arc 本身不是常量对象的托管根。
|
||
code: Arc<EndorCode>,
|
||
}
|
||
|
||
impl EndorCodeObject {
|
||
/// Store::new_code 将已校验执行数据包装成待分配载荷,共享数据由 Arc 持有。
|
||
pub fn new(code: EndorCode) -> Self {
|
||
Self {
|
||
code: Arc::new(code),
|
||
}
|
||
}
|
||
/// Store 校验函数引用的代码布局时借用共享执行数据。
|
||
pub fn code(&self) -> &EndorCode {
|
||
&self.code
|
||
}
|
||
/// core::frame 共享执行数据,不深拷贝常量和编码;Arc 不是常量托管对象的拥有根。
|
||
/// 调用方使用常量时仍须保持托管代码对象有根,不能只靠此 Arc 延长常量对象寿命。
|
||
pub fn shared_code(&self) -> Arc<EndorCode> {
|
||
Arc::clone(&self.code)
|
||
}
|
||
}
|
||
|
||
impl ObjectPayload for EndorCodeObject {
|
||
/// 不可变代码载荷不实现原地赋值,允许只读局部引用重绑定。
|
||
fn reference_assignment_only(&self) -> bool {
|
||
true
|
||
}
|
||
/// Store::payload 的 Rust 下转型入口。
|
||
fn as_any(&self) -> &dyn Any {
|
||
self
|
||
}
|
||
/// 代码载荷的诊断类别,不是名义类型判断。
|
||
fn kind_name(&self) -> &'static str {
|
||
"code"
|
||
}
|
||
/// Heap 分配/替换代码载荷时登记常量对象边;不递归扫描其指令或源码文本。
|
||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||
// 仅对象变体贡献边,重复出现的同一对象仍逐次计数。
|
||
for value in self.code.constants() {
|
||
trace_value(value, visit);
|
||
}
|
||
}
|
||
/// Store::repr 用代码名/文件/起始行生成诊断,不运行代码。
|
||
fn repr(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
||
Ok(format!(
|
||
"<code {} at {}:{}>",
|
||
self.code.name(),
|
||
self.code.filename(),
|
||
self.code.first_line()
|
||
))
|
||
}
|
||
}
|
||
|
||
// 白名单之外一律不在此处证明只读;动态调用图分析可以另行判断已知调用。
|
||
/// 本地只读证明和 effects 调用图分析共享白名单;新增指令需显式审查其副作用。
|
||
pub(crate) fn read_only_opcode(opcode: Opcode) -> bool {
|
||
matches!(
|
||
opcode,
|
||
Opcode::Nop
|
||
| Opcode::LoadConst
|
||
| Opcode::LoadFast
|
||
| Opcode::DeleteFast
|
||
| Opcode::LoadGlobal
|
||
| Opcode::PopTop
|
||
| Opcode::Copy
|
||
| Opcode::Swap
|
||
| Opcode::PushNull
|
||
| Opcode::ReturnValue
|
||
| Opcode::JumpForward
|
||
| Opcode::JumpBackward
|
||
| Opcode::PopJumpIfFalse
|
||
| Opcode::PopJumpIfTrue
|
||
| Opcode::ToBool
|
||
| Opcode::UnaryNot
|
||
| Opcode::BinaryArithmetic
|
||
| Opcode::UnaryNegate
|
||
| Opcode::CompareOp
|
||
| Opcode::LoadDeref
|
||
| Opcode::LoadClosure
|
||
| Opcode::LoadAttr
|
||
| Opcode::BuildMap
|
||
| Opcode::MakeFunction
|
||
)
|
||
}
|
||
|
||
/// with_layout 在解码前调用,验证各名称表内部唯一性以及局部/捕获槽的交叉关系。
|
||
fn validate_layout(layout: &CodeLayout) -> Result<(), VmError> {
|
||
// 每类名称表都检查内部唯一性,不能把全部表混在一个 seen 中。
|
||
for names in [
|
||
&layout.parameters,
|
||
&layout.locals,
|
||
&layout.cell_variables,
|
||
&layout.free_variables,
|
||
&layout.names,
|
||
] {
|
||
// 每张表单独去重;不同表间允许的重名关系在后面单独检查。
|
||
let mut seen = BTreeSet::new();
|
||
// 空名称无法可靠绑定,表内重复会使槽或名称索引产生歧义。
|
||
for name in names {
|
||
// 发现首个违规就退出,构造不产生可执行半成品。
|
||
if name.is_empty() || !seen.insert(name) {
|
||
return Err(VmError::InvalidArgument(
|
||
"empty or duplicate code variable name",
|
||
));
|
||
}
|
||
}
|
||
}
|
||
// 帧按局部槽前缀绑定参数,不能仅检查包含关系。
|
||
if !layout.locals.starts_with(&layout.parameters) {
|
||
return Err(VmError::InvalidArgument(
|
||
"parameter names must lead local slots",
|
||
));
|
||
}
|
||
// Cell 局部必须来自 locals;外部自由变量不得同时作为本地变量。
|
||
if layout
|
||
.cell_variables
|
||
.iter()
|
||
.any(|name| !layout.locals.contains(name))
|
||
|| layout
|
||
.free_variables
|
||
.iter()
|
||
.any(|name| layout.locals.contains(name))
|
||
{
|
||
return Err(VmError::InvalidArgument(
|
||
"invalid local/cell/free-variable layout",
|
||
));
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// 代码公共查询与操作数校验共用:外部自由变量槽全是 Cell,局部槽按名称判断。
|
||
fn is_cell_slot(layout: &CodeLayout, slot: usize) -> bool {
|
||
// 自由变量段范围内都为 Cell;段外返回 false,避免越界索引。
|
||
if slot >= layout.locals.len() {
|
||
slot < layout.locals.len() + layout.free_variables.len()
|
||
} else {
|
||
// 局部槽按名字判断是否提升为 Cell,不改变其原有位置。
|
||
layout.cell_variables.contains(&layout.locals[slot])
|
||
}
|
||
}
|
||
|
||
/// instruction_at、跳转验证与栈分析用二分查找逻辑起点;前缀中段不是合法目标。
|
||
fn instruction_index(instructions: &[DecodedInstruction], offset: usize) -> Option<usize> {
|
||
instructions
|
||
.binary_search_by_key(&offset, |instruction| instruction.offset)
|
||
.ok()
|
||
}
|
||
|
||
/// DecodedInstruction 与 verify 共用相对偏移计算;此助手只算数,不验证目标边界。
|
||
pub(crate) fn jump_target(instruction: DecodedInstruction) -> Result<usize, VmError> {
|
||
// 操作数是代码单元距离,不是字节数或逻辑指令数量。
|
||
let argument = instruction.instruction.argument as usize;
|
||
// 仅 JumpBackward 做减法,条件跳转与 JumpForward 做加法;checked 防止绕回。
|
||
let target = if instruction.instruction.opcode == Opcode::JumpBackward {
|
||
instruction.next_offset.checked_sub(argument)
|
||
} else {
|
||
instruction.next_offset.checked_add(argument)
|
||
};
|
||
target.ok_or_else(|| invalid(instruction.offset, "jump offset overflow"))
|
||
}
|
||
|
||
/// 拒绝非法索引/跳转、可达栈下溢、路径汇合高度不符和落出代码;循环回边必须保持同高。
|
||
/// with_layout 解码后调用;先检查全部指令操作数,再分析入口可达控制流并返回最大栈高。
|
||
fn verify(
|
||
instructions: &[DecodedInstruction],
|
||
constants: usize,
|
||
layout: &CodeLayout,
|
||
) -> Result<usize, VmError> {
|
||
// 操作数检查覆盖全部指令,包括入口不可达部分。
|
||
for instruction in instructions {
|
||
// 逻辑操作码与完整 u32 参数;前缀已由解码器合并。
|
||
let Instruction { opcode, argument } = instruction.instruction;
|
||
// 表索引以 usize 使用;其上界由对应布局或常量数量限定。
|
||
let index = argument as usize;
|
||
// Fast 不能指向 Cell,Deref 必须指向 Cell;跳转必须落在完整逻辑起点。
|
||
let valid = match opcode {
|
||
Opcode::LoadConst => index < constants,
|
||
Opcode::LoadFast | Opcode::StoreFast | Opcode::DeleteFast => {
|
||
index < layout.locals.len() && !is_cell_slot(layout, index)
|
||
}
|
||
Opcode::LoadDeref | Opcode::StoreDeref | Opcode::DeleteDeref | Opcode::LoadClosure => {
|
||
is_cell_slot(layout, index)
|
||
}
|
||
Opcode::LoadGlobal
|
||
| Opcode::StoreGlobal
|
||
| Opcode::DeleteGlobal
|
||
| Opcode::LoadAttr
|
||
| Opcode::StoreAttr => index < layout.names.len(),
|
||
Opcode::CompareOp => compare_operation(argument).is_some(),
|
||
Opcode::BinaryArithmetic => argument <= 3,
|
||
Opcode::Copy => argument >= 1,
|
||
Opcode::Swap => argument >= 2,
|
||
Opcode::JumpForward
|
||
| Opcode::JumpBackward
|
||
| Opcode::PopJumpIfFalse
|
||
| Opcode::PopJumpIfTrue => {
|
||
instruction_index(instructions, jump_target(*instruction)?).is_some()
|
||
}
|
||
Opcode::ExtendedArg => false,
|
||
_ => true,
|
||
};
|
||
// 将错误定位到指令起点,尚未开始传播栈状态。
|
||
if !valid {
|
||
return Err(invalid(
|
||
instruction.offset,
|
||
"invalid operand or jump target",
|
||
));
|
||
}
|
||
}
|
||
// None 表示尚未从入口到达,不表示其操作数未检查。
|
||
let mut heights = vec![None; instructions.len()];
|
||
// decode 保证列表非空;代码入口求值栈为空。
|
||
heights[0] = Some(0_usize);
|
||
// 工作队列保存待展开的指令索引,每个新到达位置只加入一次。
|
||
let mut pending = VecDeque::from([0]);
|
||
// 全部可达指令执行前后的最大栈高度。
|
||
let mut maximum = 0;
|
||
// 前向/回向边共用工作队列,汇合高度一致时不重复展开。
|
||
while let Some(index) = pending.pop_front() {
|
||
// 队列只保存已赋高度的位置,因此下方 unwrap 由入队不变量保证。
|
||
let instruction = instructions[index];
|
||
// 当前指令执行前高度,不含任何类型或值内容信息。
|
||
let height = heights[index].unwrap();
|
||
// 逻辑操作码与完整 u32 参数;前缀已由解码器合并。
|
||
let Instruction { opcode, argument } = instruction.instruction;
|
||
let argument = argument as usize;
|
||
// required 是最深访问需求,popped/pushed 是净变化来源;Copy/Swap 不弹出其深层项。
|
||
let (required, popped, pushed) = match opcode {
|
||
Opcode::LoadConst
|
||
| Opcode::LoadFast
|
||
| Opcode::LoadGlobal
|
||
| Opcode::LoadDeref
|
||
| Opcode::LoadClosure
|
||
| Opcode::PushNull => (0, 0, 1),
|
||
Opcode::StoreFast
|
||
| Opcode::StoreGlobal
|
||
| Opcode::PopTop
|
||
| Opcode::StoreDeref
|
||
| Opcode::PopJumpIfFalse
|
||
| Opcode::PopJumpIfTrue => (1, 1, 0),
|
||
Opcode::ToBool
|
||
| Opcode::UnaryNot
|
||
| Opcode::LoadAttr
|
||
| Opcode::Await
|
||
| Opcode::UnaryNegate => (1, 1, 1),
|
||
Opcode::CompareOp | Opcode::BinaryArithmetic => (2, 2, 1),
|
||
Opcode::StoreAttr => (2, 2, 0),
|
||
Opcode::Copy => (argument, 0, 1),
|
||
Opcode::Swap => (argument, 0, 0),
|
||
Opcode::Call => {
|
||
// 显式参数之外还消费函数与接收者/Null 两项,先检查加法溢出。
|
||
let consumed = argument
|
||
.checked_add(2)
|
||
.ok_or_else(|| invalid(instruction.offset, "call operand overflow"))?;
|
||
(consumed, consumed, 1)
|
||
}
|
||
Opcode::BuildMap => {
|
||
// 每对键值占两项,先检查乘法溢出。
|
||
let consumed = argument
|
||
.checked_mul(2)
|
||
.ok_or_else(|| invalid(instruction.offset, "map operand overflow"))?;
|
||
(consumed, consumed, 1)
|
||
}
|
||
Opcode::MakeFunction => {
|
||
// 函数创建消费默认值、捕获 Cell 和一个代码对象,压回一个函数。
|
||
let (defaults, closure) = function_counts(instruction.instruction.argument);
|
||
// 两个计数来自 16 位段,总和加代码对象项有界。
|
||
let consumed = defaults + closure + 1;
|
||
(consumed, consumed, 1)
|
||
}
|
||
Opcode::BuildClass => (4, 4, 1),
|
||
Opcode::ReturnValue => (1, 1, 0),
|
||
_ => (0, 0, 0),
|
||
};
|
||
// 在减 popped 前检查足够高度,避免栈下溢。
|
||
if height < required {
|
||
return Err(invalid(instruction.offset, "evaluation stack underflow"));
|
||
}
|
||
// 返回必须恰好留下一个结果供移出,禁止帧内遗留其他栈项。
|
||
if opcode == Opcode::ReturnValue && height != 1 {
|
||
return Err(invalid(
|
||
instruction.offset,
|
||
"RETURN_VALUE must leave an empty frame stack",
|
||
));
|
||
}
|
||
// 校验保证减法安全;随后统计当前和后继高度。
|
||
let next_height = height - popped + pushed;
|
||
maximum = maximum.max(height).max(next_height);
|
||
// 返回无后继;条件跳转同时传播跳转/顺序两条路径,不猜测条件值。
|
||
let successors: Vec<usize> = match opcode {
|
||
Opcode::ReturnValue => Vec::new(),
|
||
Opcode::JumpForward | Opcode::JumpBackward => vec![jump_target(instruction)?],
|
||
Opcode::PopJumpIfFalse | Opcode::PopJumpIfTrue => {
|
||
vec![jump_target(instruction)?, instruction.next_offset]
|
||
}
|
||
_ => vec![instruction.next_offset],
|
||
};
|
||
// 每条后继都必须指向现存逻辑指令,顺序落到代码末尾也属于错误。
|
||
for successor in successors {
|
||
// 将物理单元偏移转换为逻辑指令索引,拒绝扩展前缀中部。
|
||
let target = instruction_index(instructions, successor).ok_or_else(|| {
|
||
invalid(instruction.offset, "execution falls off the code object")
|
||
})?;
|
||
// 已到达位置只能接受同高;新位置设置高度并排队。
|
||
match heights[target] {
|
||
// 回边或分支汇合必须与第一次到达时的高度完全一致。
|
||
Some(existing) if existing != next_height => {
|
||
return Err(invalid(
|
||
successor,
|
||
"control-flow paths have different stack heights",
|
||
));
|
||
}
|
||
Some(_) => {}
|
||
// 首次到达的后继写入高度,之后排队检查它的栈需求。
|
||
None => {
|
||
heights[target] = Some(next_height);
|
||
pending.push_back(target);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Ok(maximum)
|
||
}
|