Files
endorphin-tessera-gui/src/runtime/vm_sequence.rs

203 lines
8.8 KiB
Rust
Raw Permalink 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.
//! 构造共享 VM 调用序列与同帧循环驱动的字节码。
//! 用托管函数串联步骤,避免为每个布局节点反复创建独立解释器调用;重复驱动会保留同一个执行帧和预算域。
//!
//! Validated call sequences share the current VM budget and explicit frames.
use endor_core::{
CodeLayout, EndorCode, EndorCodeObject, Instruction, NativeContext, ObjectRef, Opcode, Value,
VmError,
};
/// 将零参数调用列表编成一个托管驱动,按输入顺序执行,返回最后一次调用的值。
/// 空列表返回 Nil。所有调用都在调用者的 VM 预算内运行,避免宿主逐个调用时重置预算。
/// name 用于诊断位置;desktop 用来给生成函数挂接桌面 Function 类型。
pub fn build(
cx: &mut NativeContext<'_>,
calls: Vec<ObjectRef>,
name: &'static str,
desktop: ObjectRef,
) -> Result<ObjectRef, VmError> {
// 每个函数放入常量池。LoadConst/PushNull/Call 组成零参数调用,
// 中间结果由 PopTop 丢弃;最后结果留在栈顶交给 ReturnValue。
let mut instructions = Vec::with_capacity(calls.len() * 4 + 2);
let mut constants: Vec<Value> = calls.into_iter().map(Value::Object).collect();
for index in 0..constants.len() {
instructions.push(Instruction::new(Opcode::LoadConst, index as u32));
instructions.push(Instruction::simple(Opcode::PushNull));
instructions.push(Instruction::new(Opcode::Call, 0));
if index + 1 != constants.len() {
instructions.push(Instruction::simple(Opcode::PopTop));
}
}
// 没有调用时也必须给 ReturnValue 提供合法栈顶,不能返回一个空栈。
if constants.is_empty() {
constants.push(Value::Nil);
instructions.push(Instruction::new(Opcode::LoadConst, 0));
}
instructions.push(Instruction::simple(Opcode::ReturnValue));
let code = EndorCode::from_instructions(&instructions, constants, CodeLayout::default())?
.with_location(name, "<tessera>", 1);
let code = cx.allocate(EndorCodeObject::new(code))?;
let globals = cx.new_dict()?;
let driver = cx.new_function(name, code, globals)?;
crate::ui_bridge::attach_desktop_type(cx, driver, "Function", desktop)?;
Ok(driver)
}
/// A fixed-size driver. Each callback returns whether another occurrence is
/// needed; delegated source calls and all iterations share this VM budget.
/// 构造重复调用同一托管函数的驱动,保持共享参数与引用生命周期。
pub fn repeat_call(
cx: &mut NativeContext<'_>,
callback: ObjectRef,
name: &'static str,
desktop: ObjectRef,
) -> Result<ObjectRef, VmError> {
// 回调返回 Bool;repeat_while 消费这个条件并决定是否跳回本帧开头。
// 退出后显式返回 Nil,回调的继续条件不会泄露为驱动返回值。
let mut instructions = vec![
Instruction::new(Opcode::LoadConst, 0),
Instruction::simple(Opcode::PushNull),
Instruction::new(Opcode::Call, 0),
];
repeat_while(&mut instructions)?;
instructions.extend([
Instruction::new(Opcode::LoadConst, 1),
Instruction::simple(Opcode::ReturnValue),
]);
driver(
cx,
instructions,
vec![Value::Object(callback), Value::Nil],
name,
desktop,
)
}
/// Prepare only the current occurrence, collect its result, then continue the
/// outer driver. The helper frame and its managed captures die before the next.
/// 将一步调用结果收集到驱动状态,再安排下一步调用。
pub fn collect_and_continue(
cx: &mut NativeContext<'_>,
collector: ObjectRef,
function: ObjectRef,
arguments: Vec<Value>,
name: &'static str,
desktop: ObjectRef,
) -> Result<ObjectRef, VmError> {
// 常量布局固定为 collector、function、各实参、true。
// 先把 collector 的调用前缀压栈,再执行 function,使其返回值直接成为 collector 的唯一实参。
let count = arguments.len();
let mut constants = vec![Value::Object(collector), Value::Object(function)];
constants.extend(arguments);
constants.push(Value::Bool(true));
let mut instructions = vec![
Instruction::new(Opcode::LoadConst, 0),
Instruction::simple(Opcode::PushNull),
Instruction::new(Opcode::LoadConst, 1),
Instruction::simple(Opcode::PushNull),
];
for index in 0..count {
instructions.push(Instruction::new(Opcode::LoadConst, index as u32 + 2));
}
// 收集器返回值不参与外层调度;丢弃后返回 true,通知重复驱动继续取下一项。
instructions.extend([
Instruction::new(Opcode::Call, count as u32),
Instruction::new(Opcode::Call, 1),
Instruction::simple(Opcode::PopTop),
Instruction::new(Opcode::LoadConst, count as u32 + 2),
Instruction::simple(Opcode::ReturnValue),
]);
driver(cx, instructions, constants, name, desktop)
}
/// 建立承载整个步骤序列的托管函数,使续体可以在同一个 VM 帧中恢复。
fn driver(
cx: &mut NativeContext<'_>,
instructions: Vec<Instruction>,
constants: Vec<Value>,
name: &'static str,
desktop: ObjectRef,
) -> Result<ObjectRef, VmError> {
let code = EndorCode::from_instructions(&instructions, constants, CodeLayout::default())?
.with_location(name, "<tessera>", 1);
let code = cx.allocate(EndorCodeObject::new(code))?;
let globals = cx.new_dict()?;
let function = cx.new_function(name, code, globals)?;
crate::ui_bridge::attach_desktop_type(cx, function, "Function", desktop)?;
Ok(function)
}
/// Consume the condition on the stack: true repeats this same frame from zero,
/// false falls through. Operand widths include EXTENDED_ARG code units.
/// 构造由继续条件决定是否再执行一轮的同帧循环。
pub fn repeat_while(instructions: &mut Vec<Instruction>) -> Result<(), VmError> {
// 跳转单位是编码后的双字节 code unit,不是 Instruction 数量。
// 大操作数会生成 EXTENDED_ARG,因此必须编码后计算实际长度。
let body = Instruction::encode_all(instructions)?.len() / 2;
let mut back = Instruction::new(Opcode::JumpBackward, 0);
// 回跳距离包含自身宽度;而距离变大又可能扩大自身编码宽度。
// 迭代到宽度/距离一致后,false 跳过回跳指令,true 回到整个驱动的起点。
loop {
let units = Instruction::encode_all(&[back])?.len() / 2;
let distance = u32::try_from(body + 1 + units)
.map_err(|_| VmError::ExecutionLimit("layout bytecode"))?;
if back.argument == distance {
instructions.push(Instruction::new(Opcode::PopJumpIfFalse, units as u32));
instructions.push(back);
return Ok(());
}
back.argument = distance;
}
}
#[cfg(test)]
mod tests {
use super::*;
use endor_core::{ExecutionLimits, Vm};
#[test]
/// 生成超过单字节跳距的循环,false 条件正常返回,true 条件只耗尽指令预算而非一帧预算;释放后对象数归零。
fn repeated_bytecode_handles_extended_operands_and_keeps_one_frame() {
// 300 个 Nop 强制跨越单字节跳转距离:false 应正常退出;true 应耗尽指令预算,
// 而非耗尽帧预算,从而验证循环确实复用同一个执行帧。
for condition in [false, true] {
let mut instructions = vec![Instruction::simple(Opcode::Nop); 300];
instructions.push(Instruction::new(Opcode::LoadConst, 0));
repeat_while(&mut instructions).unwrap();
instructions.extend([
Instruction::new(Opcode::LoadConst, 1),
Instruction::simple(Opcode::ReturnValue),
]);
let code = EndorCode::from_instructions(
&instructions,
vec![Value::Bool(condition), Value::Nil],
CodeLayout::default(),
)
.unwrap();
let mut vm = Vm::new().unwrap();
let code = vm.new_code(code).unwrap();
let globals = vm.new_dict().unwrap();
let result = vm.execute_with_limits(
code,
globals,
ExecutionLimits {
max_frames: 1,
max_instructions: 1000,
},
);
if condition {
assert!(matches!(
result,
Err(VmError::ExecutionLimit("instructions"))
));
} else {
assert_eq!(result.unwrap(), Value::Nil);
}
vm.release(code).unwrap();
vm.release(globals).unwrap();
// 释放代码与全局字典后执行循环回收,确认生成的常量/函数没有遗留托管对象。
vm.collect_cycles();
assert_eq!(vm.object_count(), 0);
}
}
}