use prometeu_bytecode::isa::core::{CoreOpCode, CoreOpCodeSpecExt}; use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId}; use prometeu_vm::{BudgetReport, LogicalFrameEndingReason, VirtualMachine}; use prometeu_bytecode::Value; fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec) { out.extend_from_slice(&(op as u16).to_le_bytes()); let need = op.spec().imm_bytes as usize; match (need, imm) { (0, None) => {} (n, Some(bytes)) if bytes.len() == n => out.extend_from_slice(bytes), (n, Some(bytes)) => panic!("imm size mismatch for {:?}: need {}, got {}", op, n, bytes.len()), (n, None) => panic!("missing imm for {:?}: need {} bytes", op, n), } } struct NoopNative; impl NativeInterface for NoopNative { fn syscall( &mut self, _id: SyscallId, _args: &[Value], _ret: &mut HostReturn, _ctx: &mut HostContext, ) -> Result<(), prometeu_hal::vm_fault::VmFault> { Ok(()) } } #[test] fn vm_executes_valid_program_in_slices() { // Program: PUSH_I32 2; PUSH_I32 3; ADD; FRAME_SYNC; HALT let mut rom = Vec::new(); emit(CoreOpCode::PushI32, Some(&2i32.to_le_bytes()), &mut rom); emit(CoreOpCode::PushI32, Some(&3i32.to_le_bytes()), &mut rom); emit(CoreOpCode::Add, None, &mut rom); emit(CoreOpCode::FrameSync, None, &mut rom); emit(CoreOpCode::Halt, None, &mut rom); // Construct VM directly; assume input is already verified. let mut vm = VirtualMachine::new(rom, vec![]); vm.prepare_call("0"); let mut native = NoopNative; let mut ctx = HostContext::new(None); // First slice should stop at FRAME_SYNC deterministically. let report: BudgetReport = vm.run_budget(100, &mut native, &mut ctx).expect("run ok"); assert_eq!(report.reason, LogicalFrameEndingReason::FrameSync); assert!(!vm.is_halted()); // Second slice proceeds to HALT. let report2: BudgetReport = vm.run_budget(100, &mut native, &mut ctx).expect("run ok"); assert_eq!(report2.reason, LogicalFrameEndingReason::Halted); assert!(vm.is_halted()); }