//! 编译器/宿主提交代码后在此构造不可变执行数据;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, /// 全部局部名称,参数必须是其完整前缀。 pub locals: Vec, /// 需要提升为 Cell 的局部名称子集,不额外新增局部槽。 pub cell_variables: Vec, /// 从外层捕获的 Cell 名称,顺序决定闭包传入槽顺序。 pub free_variables: Vec, /// 全局与属性操作使用的文本名称索引表。 pub names: Vec, } /// 编译器传入的源码类型声明,与执行槽布局分开;类型名称在调用/契约检查时解析。 #[derive(Debug, Clone, PartialEq, Eq)] pub struct SourceSignature { /// 位置参数的类型注解顺序,数量必须与代码参数槽数相同。 pub parameters: Vec, /// 非空的返回类型注解名称,尚不保证该名称可成功解析。 pub return_type: String, /// 可选方法所属类型名,用于源码方法契约;Some 空串非法。 pub method_owner: Option, } /// 已通过构造校验的 Rust 执行数据,尚未分配为托管代码对象;clone 不为常量对象增加根。 #[derive(Debug, Clone)] pub struct EndorCode { /// 保留的物理双字节单元编码,含扩展前缀。 bytecode: Vec, /// 解码后按偏移排序的逻辑指令列表,执行时无需重新组合前缀。 instructions: Vec, /// 常量表示;Rust clone 不为其中的对象句柄 retain。 constants: Vec, /// 经 validate_layout 检查的槽和名称布局。 layout: CodeLayout, /// 从可达控制流计算的最大栈高度,供帧预留容量。 stack_size: usize, /// 诊断显示的代码名称。 name: String, /// 诊断显示的源码文件名,不读取该文件。 filename: String, /// 调用方给出的起始行,供回溯显示,不是逐指令行号表。 first_line: usize, /// 可选源码声明;不改变已校验的指令布局。 signature: Option, } impl EndorCode { /// 宿主从原始字节码建立只有参数局部槽的代码;需要额外局部或本地 Cell 时用 with_layout。 pub fn new( bytecode: Vec, constants: Vec, parameters: Vec, free_variables: Vec, ) -> Result { Self::with_layout( bytecode, constants, CodeLayout { locals: parameters.clone(), parameters, free_variables, ..CodeLayout::default() }, ) } /// 编译器与宿主把逻辑指令编码后走统一构造校验;此入口不替调用者修补跳转距离。 pub fn from_instructions( instructions: &[Instruction], constants: Vec, layout: CodeLayout, ) -> Result { Self::with_layout(Instruction::encode_all(instructions)?, constants, layout) } /// 全部构造入口汇合处:先检查布局,再解码,最后验证索引、跳转和可达路径栈高度。 pub fn with_layout( bytecode: Vec, constants: Vec, layout: CodeLayout, ) -> Result { // 布局错误必须先拒绝,后续槽索引校验依赖这些不变量。 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: "".into(), filename: "".into(), first_line: 1, signature: None, }) } /// 编译器在代码构造后附加声明;这里只检查数量和非空字符串,不解析类型名。 pub fn with_source_signature(mut self, signature: SourceSignature) -> Result { // 参数注解数量必须对应位置参数;每项与可选 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, filename: impl Into, 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 { 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, } 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 { 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 { Ok(format!( "", 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 { instructions .binary_search_by_key(&offset, |instruction| instruction.offset) .ok() } /// DecodedInstruction 与 verify 共用相对偏移计算;此助手只算数,不验证目标边界。 pub(crate) fn jump_target(instruction: DecodedInstruction) -> Result { // 操作数是代码单元距离,不是字节数或逻辑指令数量。 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 { // 操作数检查覆盖全部指令,包括入口不可达部分。 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 = 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) }