From 9d484a96369564b889e759be41c01263c198c967 Mon Sep 17 00:00:00 2001 From: Nilton Constantino Date: Sat, 31 Jan 2026 17:55:35 +0000 Subject: [PATCH] pr 46 --- .../src/virtual_machine/opcode_spec.rs | 4 +- .../src/virtual_machine/virtual_machine.rs | 176 +++++++++++++++++- docs/specs/pbs/files/PRs para Junie.md | 28 --- 3 files changed, 171 insertions(+), 37 deletions(-) diff --git a/crates/prometeu-core/src/virtual_machine/opcode_spec.rs b/crates/prometeu-core/src/virtual_machine/opcode_spec.rs index a26fd50b..1096a815 100644 --- a/crates/prometeu-core/src/virtual_machine/opcode_spec.rs +++ b/crates/prometeu-core/src/virtual_machine/opcode_spec.rs @@ -23,8 +23,8 @@ impl OpCodeSpecExt for OpCode { OpCode::Nop => OpcodeSpec { name: "NOP", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false }, OpCode::Halt => OpcodeSpec { name: "HALT", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: true, may_trap: false }, OpCode::Jmp => OpcodeSpec { name: "JMP", imm_bytes: 4, pops: 0, pushes: 0, is_branch: true, is_terminator: true, may_trap: false }, - OpCode::JmpIfFalse => OpcodeSpec { name: "JMP_IF_FALSE", imm_bytes: 4, pops: 1, pushes: 0, is_branch: true, is_terminator: false, may_trap: false }, - OpCode::JmpIfTrue => OpcodeSpec { name: "JMP_IF_TRUE", imm_bytes: 4, pops: 1, pushes: 0, is_branch: true, is_terminator: false, may_trap: false }, + OpCode::JmpIfFalse => OpcodeSpec { name: "JMP_IF_FALSE", imm_bytes: 4, pops: 1, pushes: 0, is_branch: true, is_terminator: false, may_trap: true }, + OpCode::JmpIfTrue => OpcodeSpec { name: "JMP_IF_TRUE", imm_bytes: 4, pops: 1, pushes: 0, is_branch: true, is_terminator: false, may_trap: true }, OpCode::Trap => OpcodeSpec { name: "TRAP", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: true, may_trap: true }, OpCode::PushConst => OpcodeSpec { name: "PUSH_CONST", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, OpCode::Pop => OpcodeSpec { name: "POP", imm_bytes: 0, pops: 1, pushes: 0, is_branch: false, is_terminator: false, may_trap: false }, diff --git a/crates/prometeu-core/src/virtual_machine/virtual_machine.rs b/crates/prometeu-core/src/virtual_machine/virtual_machine.rs index 5a4b69c2..7717c3fa 100644 --- a/crates/prometeu-core/src/virtual_machine/virtual_machine.rs +++ b/crates/prometeu-core/src/virtual_machine/virtual_machine.rs @@ -5,7 +5,7 @@ use crate::virtual_machine::value::Value; use crate::virtual_machine::{NativeInterface, Program, VmInitError}; use prometeu_bytecode::opcode::OpCode; use prometeu_bytecode::pbc::{self, ConstantPoolEntry}; -use prometeu_bytecode::abi::{TrapInfo, TRAP_OOB, TRAP_DIV_ZERO}; +use prometeu_bytecode::abi::{TrapInfo, TRAP_OOB, TRAP_DIV_ZERO, TRAP_TYPE}; /// Reason why the Virtual Machine stopped execution during a specific run. /// This allows the system to decide if it should continue execution in the next tick @@ -379,17 +379,39 @@ impl VirtualMachine { OpCode::JmpIfFalse => { let target = u32::from_le_bytes(instr.imm[0..4].try_into().unwrap()) as usize; let val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?; - if let Value::Boolean(false) = val { - let func_start = self.call_stack.last().map(|f| self.program.functions[f.func_idx].code_offset as usize).unwrap_or(0); - self.pc = func_start + target; + match val { + Value::Boolean(false) => { + let func_start = self.call_stack.last().map(|f| self.program.functions[f.func_idx].code_offset as usize).unwrap_or(0); + self.pc = func_start + target; + } + Value::Boolean(true) => {} + _ => { + return Err(LogicalFrameEndingReason::Trap(TrapInfo { + code: TRAP_TYPE, + opcode: opcode as u16, + message: format!("Expected boolean for JMP_IF_FALSE, got {:?}", val), + pc: start_pc as u32, + })); + } } } OpCode::JmpIfTrue => { let target = u32::from_le_bytes(instr.imm[0..4].try_into().unwrap()) as usize; let val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?; - if let Value::Boolean(true) = val { - let func_start = self.call_stack.last().map(|f| self.program.functions[f.func_idx].code_offset as usize).unwrap_or(0); - self.pc = func_start + target; + match val { + Value::Boolean(true) => { + let func_start = self.call_stack.last().map(|f| self.program.functions[f.func_idx].code_offset as usize).unwrap_or(0); + self.pc = func_start + target; + } + Value::Boolean(false) => {} + _ => { + return Err(LogicalFrameEndingReason::Trap(TrapInfo { + code: TRAP_TYPE, + opcode: opcode as u16, + message: format!("Expected boolean for JMP_IF_TRUE, got {:?}", val), + pc: start_pc as u32, + })); + } } } OpCode::Trap => { @@ -2137,4 +2159,144 @@ mod tests { _ => panic!("Expected Trap, got {:?}", report.reason), } } + + #[test] + fn test_nested_if() { + let mut native = MockNative; + let mut hw = MockHardware; + + // if (true) { + // if (false) { + // PUSH 1 + // } else { + // PUSH 2 + // } + // } else { + // PUSH 3 + // } + // HALT + let mut rom = Vec::new(); + // 0: PUSH_BOOL true + rom.extend_from_slice(&(OpCode::PushBool as u16).to_le_bytes()); + rom.push(1); + // 3: JMP_IF_FALSE -> ELSE1 (offset 42) + rom.extend_from_slice(&(OpCode::JmpIfFalse as u16).to_le_bytes()); + rom.extend_from_slice(&42u32.to_le_bytes()); + + // INNER IF: + // 9: PUSH_BOOL false + rom.extend_from_slice(&(OpCode::PushBool as u16).to_le_bytes()); + rom.push(0); + // 12: JMP_IF_FALSE -> ELSE2 (offset 30) + rom.extend_from_slice(&(OpCode::JmpIfFalse as u16).to_le_bytes()); + rom.extend_from_slice(&30u32.to_le_bytes()); + // 18: PUSH_I32 1 + rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); + rom.extend_from_slice(&1i32.to_le_bytes()); + // 24: JMP -> END (offset 48) + rom.extend_from_slice(&(OpCode::Jmp as u16).to_le_bytes()); + rom.extend_from_slice(&48u32.to_le_bytes()); + + // ELSE2: + // 30: PUSH_I32 2 + rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); + rom.extend_from_slice(&2i32.to_le_bytes()); + // 36: JMP -> END (offset 48) + rom.extend_from_slice(&(OpCode::Jmp as u16).to_le_bytes()); + rom.extend_from_slice(&48u32.to_le_bytes()); + + // ELSE1: + // 42: PUSH_I32 3 + rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); + rom.extend_from_slice(&3i32.to_le_bytes()); + + // END: + // 48: HALT + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + + let mut vm = VirtualMachine::new(rom, vec![]); + // We need to set up the function meta for absolute jumps to work correctly + vm.program.functions = std::sync::Arc::from(vec![FunctionMeta { + code_offset: 0, + code_len: 50, + ..Default::default() + }]); + vm.prepare_call("0"); + + vm.run_budget(100, &mut native, &mut hw).unwrap(); + assert_eq!(vm.pop().unwrap(), Value::Int32(2)); + } + + #[test] + fn test_if_with_empty_branches() { + let mut native = MockNative; + let mut hw = MockHardware; + + // PUSH_BOOL true + // JMP_IF_FALSE -> ELSE (offset 15) + // // Empty then + // JMP -> END (offset 15) + // ELSE: + // // Empty else + // END: + // HALT + let mut rom = Vec::new(); + // 0-2: PUSH_BOOL true + rom.extend_from_slice(&(OpCode::PushBool as u16).to_le_bytes()); + rom.push(1); + // 3-8: JMP_IF_FALSE -> 15 + rom.extend_from_slice(&(OpCode::JmpIfFalse as u16).to_le_bytes()); + rom.extend_from_slice(&15u32.to_le_bytes()); + // 9-14: JMP -> 15 + rom.extend_from_slice(&(OpCode::Jmp as u16).to_le_bytes()); + rom.extend_from_slice(&15u32.to_le_bytes()); + // 15-16: HALT + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + + let mut vm = VirtualMachine::new(rom, vec![]); + vm.program.functions = std::sync::Arc::from(vec![FunctionMeta { + code_offset: 0, + code_len: 17, + ..Default::default() + }]); + vm.prepare_call("0"); + + let report = vm.run_budget(100, &mut native, &mut hw).unwrap(); + assert_eq!(report.reason, LogicalFrameEndingReason::Halted); + assert_eq!(vm.operand_stack.len(), 0); + } + + #[test] + fn test_jmp_if_non_boolean_trap() { + let mut native = MockNative; + let mut hw = MockHardware; + + // PUSH_I32 1 + // JMP_IF_TRUE 9 + // HALT + let mut rom = Vec::new(); + rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); + rom.extend_from_slice(&1i32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::JmpIfTrue as u16).to_le_bytes()); + rom.extend_from_slice(&9u32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + + let mut vm = VirtualMachine::new(rom, vec![]); + vm.program.functions = std::sync::Arc::from(vec![FunctionMeta { + code_offset: 0, + code_len: 14, + ..Default::default() + }]); + vm.prepare_call("0"); + + let report = vm.run_budget(100, &mut native, &mut hw).unwrap(); + match report.reason { + LogicalFrameEndingReason::Trap(trap) => { + assert_eq!(trap.code, TRAP_TYPE); + assert_eq!(trap.opcode, OpCode::JmpIfTrue as u16); + assert!(trap.message.contains("Expected boolean")); + } + _ => panic!("Expected Trap, got {:?}", report.reason), + } + } } diff --git a/docs/specs/pbs/files/PRs para Junie.md b/docs/specs/pbs/files/PRs para Junie.md index 0c7c05ca..f03c9726 100644 --- a/docs/specs/pbs/files/PRs para Junie.md +++ b/docs/specs/pbs/files/PRs para Junie.md @@ -1,31 +1,3 @@ -## PR-06 — Control flow opcodes: jumps, conditional branches, structured “if” - -**Why:** `if` must be predictable and verifier-safe. - -### Scope - -* Implement opcodes: - - * `JMP ` - * `JMP_IF_TRUE ` - * `JMP_IF_FALSE ` -* Verifier rules: - - * targets must be valid instruction boundaries - * stack height at join points must match - -### Tests - -* nested if -* if with empty branches -* branch join mismatch rejected - -### Acceptance - -* Control flow is safe; no implicit stack juggling. - ---- - ## PR-07 — Calling convention v0: CALL / RET / multi-slot returns **Why:** Without a correct call model, PBS isn’t executable.