//! 构造共享 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, name: &'static str, desktop: ObjectRef, ) -> Result { // 每个函数放入常量池。LoadConst/PushNull/Call 组成零参数调用, // 中间结果由 PopTop 丢弃;最后结果留在栈顶交给 ReturnValue。 let mut instructions = Vec::with_capacity(calls.len() * 4 + 2); let mut constants: Vec = 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, "", 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 { // 回调返回 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, name: &'static str, desktop: ObjectRef, ) -> Result { // 常量布局固定为 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, constants: Vec, name: &'static str, desktop: ObjectRef, ) -> Result { let code = EndorCode::from_instructions(&instructions, constants, CodeLayout::default())? .with_location(name, "", 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) -> 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); } } }