//! 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, name: &'static str, desktop: ObjectRef, ) -> Result { 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)); } } 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) } /// 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> { 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); } } }