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

185 lines
7.1 KiB
Rust

//! 构造共享 VM 调用序列与同帧循环驱动的字节码。
//! 用托管函数串联步骤,避免为每个布局节点反复创建独立解释器调用;重复驱动会保留同一个执行帧和预算域。
//!
//! Validated call sequences share the current VM budget and explicit frames.
use endor_core::{
CodeLayout, EndorCode, EndorCodeObject, Instruction, NativeContext, ObjectRef, Opcode, Value,
VmError,
};
/// 根据当前输入构造本模块的派生结果,具体的验证与资源边界见函数内步骤。
pub fn build(
cx: &mut NativeContext<'_>,
calls: Vec<ObjectRef>,
name: &'static str,
desktop: ObjectRef,
) -> Result<ObjectRef, VmError> {
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));
}
}
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> {
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> {
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));
}
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> {
let body = Instruction::encode_all(instructions)?.len() / 2;
let mut back = Instruction::new(Opcode::JumpBackward, 0);
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]
fn repeated_bytecode_handles_extended_operands_and_keeps_one_frame() {
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);
}
}
}