Files
endorphin-aster-vm/crates/objects/src/code.rs
T

560 lines
24 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 编译器/宿主提交代码后在此构造不可变执行数据;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)
}