pr 46
This commit is contained in:
parent
9fa337687f
commit
9d484a9636
@ -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 },
|
||||
|
||||
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 <i32 rel>`
|
||||
* `JMP_IF_TRUE <i32 rel>`
|
||||
* `JMP_IF_FALSE <i32 rel>`
|
||||
* 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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user