pr 2.2
This commit is contained in:
parent
2a79f641dd
commit
96062bf1d0
@ -184,7 +184,7 @@ pub fn assemble(src: &str) -> Result<Vec<u8>, AsmError> {
|
||||
emit_u16(CoreOpCode::FrameSync as u16, &mut out);
|
||||
}
|
||||
|
||||
// One u32 immediate (decimal or hex accepted for SYSCALL only; others decimal ok)
|
||||
// One u32 immediate (decimal or hex accepted; SYSCALL/HOSTCALL commonly use hex/idx)
|
||||
"JMP" => {
|
||||
if ops.is_empty() {
|
||||
return Err(AsmError::MissingOperand(line.into()));
|
||||
@ -338,6 +338,13 @@ pub fn assemble(src: &str) -> Result<Vec<u8>, AsmError> {
|
||||
emit_u16(CoreOpCode::Sleep as u16, &mut out);
|
||||
emit_u32(parse_u32_any(ops)?, &mut out);
|
||||
}
|
||||
"HOSTCALL" => {
|
||||
if ops.is_empty() {
|
||||
return Err(AsmError::MissingOperand(line.into()));
|
||||
}
|
||||
emit_u16(CoreOpCode::Hostcall as u16, &mut out);
|
||||
emit_u32(parse_u32_any(ops)?, &mut out);
|
||||
}
|
||||
"SYSCALL" => {
|
||||
if ops.is_empty() {
|
||||
return Err(AsmError::MissingOperand(line.into()));
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
//! - `MAKE_CLOSURE fn=<u32>, captures=<u32>`
|
||||
//! - `SPAWN fn=<u32>, argc=<u32>`
|
||||
//! - `CALL_CLOSURE argc=<u32>`
|
||||
//! - `HOSTCALL <index>` is printed in decimal because it is a `SYSC` table index.
|
||||
//! - `SYSCALL` is printed as `SYSCALL 0xhhhh` (numeric id in hex) to avoid cross-crate deps.
|
||||
//!
|
||||
//! Notes:
|
||||
@ -58,7 +59,8 @@ fn format_operand(op: CoreOpCode, imm: &[u8]) -> String {
|
||||
| CoreOpCode::GetLocal
|
||||
| CoreOpCode::SetLocal
|
||||
| CoreOpCode::Call
|
||||
| CoreOpCode::Sleep => {
|
||||
| CoreOpCode::Sleep
|
||||
| CoreOpCode::Hostcall => {
|
||||
let v = u32::from_le_bytes(imm.try_into().unwrap());
|
||||
format!("{}", v)
|
||||
}
|
||||
|
||||
@ -679,6 +679,7 @@ fn validate_module(module: &BytecodeModule) -> Result<(), LoadError> {
|
||||
| OpCode::GetLocal
|
||||
| OpCode::SetLocal
|
||||
| OpCode::PopN
|
||||
| OpCode::Hostcall
|
||||
| OpCode::Syscall => {
|
||||
pos += 4;
|
||||
}
|
||||
|
||||
@ -185,6 +185,11 @@ pub enum OpCode {
|
||||
Sleep = 0x56,
|
||||
|
||||
// --- 6.8 Peripherals and System ---
|
||||
/// Pre-load host binding call by `SYSC` table index.
|
||||
/// Operand: sysc_index (u32)
|
||||
/// This opcode is valid only in PBX artifact form and must be patched by the loader
|
||||
/// into a final numeric `SYSCALL <id>` before verification or execution.
|
||||
Hostcall = 0x71,
|
||||
/// Invokes a system function (Firmware/OS).
|
||||
/// Operand: syscall_id (u32)
|
||||
/// Stack: [args...] -> [results...] (depends on syscall)
|
||||
@ -246,6 +251,7 @@ impl TryFrom<u16> for OpCode {
|
||||
0x55 => Ok(OpCode::Yield),
|
||||
0x56 => Ok(OpCode::Sleep),
|
||||
0x70 => Ok(OpCode::Syscall),
|
||||
0x71 => Ok(OpCode::Hostcall),
|
||||
0x80 => Ok(OpCode::FrameSync),
|
||||
_ => Err(format!("Invalid OpCode: 0x{:04X}", value)),
|
||||
}
|
||||
@ -304,6 +310,7 @@ impl OpCode {
|
||||
OpCode::Yield => 1,
|
||||
OpCode::Sleep => 1,
|
||||
OpCode::Syscall => 1,
|
||||
OpCode::Hostcall => 1,
|
||||
OpCode::FrameSync => 1,
|
||||
}
|
||||
}
|
||||
|
||||
@ -493,6 +493,16 @@ impl OpCodeSpecExt for OpCode {
|
||||
// Considered a safepoint since it forces a frame boundary
|
||||
is_safepoint: true,
|
||||
},
|
||||
OpCode::Hostcall => OpcodeSpec {
|
||||
name: "HOSTCALL",
|
||||
imm_bytes: 4,
|
||||
pops: 0,
|
||||
pushes: 0,
|
||||
is_branch: false,
|
||||
is_terminator: false,
|
||||
may_trap: false,
|
||||
is_safepoint: false,
|
||||
},
|
||||
OpCode::Syscall => OpcodeSpec {
|
||||
name: "SYSCALL",
|
||||
imm_bytes: 4,
|
||||
|
||||
@ -11,7 +11,7 @@ fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec<u8>) {
|
||||
#[test]
|
||||
fn roundtrip_disasm_assemble_byte_equal_with_closures_and_coroutines() {
|
||||
// Program: PUSH_I32 7; MAKE_CLOSURE fn=1,captures=0; CALL_CLOSURE argc=1;
|
||||
// SPAWN fn=2,argc=1; YIELD; SLEEP 3; SYSCALL 0x1003; FRAME_SYNC; HALT
|
||||
// SPAWN fn=2,argc=1; YIELD; SLEEP 3; HOSTCALL 2; SYSCALL 0x1003; FRAME_SYNC; HALT
|
||||
let mut prog = Vec::new();
|
||||
|
||||
emit(CoreOpCode::PushI32, Some(&7i32.to_le_bytes()), &mut prog);
|
||||
@ -31,6 +31,8 @@ fn roundtrip_disasm_assemble_byte_equal_with_closures_and_coroutines() {
|
||||
emit(CoreOpCode::Yield, None, &mut prog);
|
||||
// SLEEP 3
|
||||
emit(CoreOpCode::Sleep, Some(&3u32.to_le_bytes()), &mut prog);
|
||||
// HOSTCALL sysc[2]
|
||||
emit(CoreOpCode::Hostcall, Some(&2u32.to_le_bytes()), &mut prog);
|
||||
// SYSCALL gfx.draw_line (0x1003)
|
||||
emit(CoreOpCode::Syscall, Some(&0x1003u32.to_le_bytes()), &mut prog);
|
||||
// FRAME_SYNC
|
||||
|
||||
@ -28,6 +28,8 @@ fn snapshot_representative_program_is_stable() {
|
||||
emit(CoreOpCode::Yield, None, &mut prog);
|
||||
// SLEEP 3
|
||||
emit(CoreOpCode::Sleep, Some(&3u32.to_le_bytes()), &mut prog);
|
||||
// HOSTCALL 2
|
||||
emit(CoreOpCode::Hostcall, Some(&2u32.to_le_bytes()), &mut prog);
|
||||
// SYSCALL 0x1003
|
||||
emit(CoreOpCode::Syscall, Some(&0x1003u32.to_le_bytes()), &mut prog);
|
||||
// FRAME_SYNC
|
||||
@ -36,6 +38,6 @@ fn snapshot_representative_program_is_stable() {
|
||||
emit(CoreOpCode::Halt, None, &mut prog);
|
||||
|
||||
let text = disassemble(&prog).expect("disasm ok");
|
||||
let expected = "PUSH_I32 7\nMAKE_CLOSURE fn=1, captures=0\nCALL_CLOSURE argc=1\nSPAWN fn=2, argc=1\nYIELD\nSLEEP 3\nSYSCALL 0x1003\nFRAME_SYNC\nHALT";
|
||||
let expected = "PUSH_I32 7\nMAKE_CLOSURE fn=1, captures=0\nCALL_CLOSURE argc=1\nSPAWN fn=2, argc=1\nYIELD\nSLEEP 3\nHOSTCALL 2\nSYSCALL 0x1003\nFRAME_SYNC\nHALT";
|
||||
assert_eq!(text, expected);
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ fn disasm(bytes: &[u8]) -> String {
|
||||
CoreOpCode::PushF64 => format!("{}", instr.imm_f64().unwrap()),
|
||||
CoreOpCode::PushBool => format!("{}", instr.imm_u8().unwrap()),
|
||||
CoreOpCode::PushI32 => format!("{}", instr.imm_i32().unwrap()),
|
||||
CoreOpCode::PopN | CoreOpCode::PushConst => {
|
||||
CoreOpCode::PopN | CoreOpCode::PushConst | CoreOpCode::Hostcall => {
|
||||
format!("{}", instr.imm_u32().unwrap())
|
||||
}
|
||||
_ => format!("0x{}", hex::encode(instr.imm)),
|
||||
@ -115,6 +115,20 @@ fn disasm_contains_expected_mnemonics_and_operands() {
|
||||
assert!(text.contains("HALT"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostcall_roundtrips_with_decimal_index() {
|
||||
let mut prog = Vec::new();
|
||||
prog.extend(encode_instr(CoreOpCode::Hostcall, Some(&7u32.to_le_bytes())));
|
||||
prog.extend(encode_instr(CoreOpCode::Halt, None));
|
||||
|
||||
let text = disasm(&prog);
|
||||
|
||||
assert!(text.contains("HOSTCALL 7"));
|
||||
|
||||
let rebuilt = prometeu_bytecode::assemble(&text).expect("assemble hostcall");
|
||||
assert_eq!(rebuilt, prog);
|
||||
}
|
||||
|
||||
// Minimal hex helper to avoid extra deps in tests
|
||||
mod hex {
|
||||
pub fn encode(bytes: &[u8]) -> String {
|
||||
|
||||
@ -67,6 +67,10 @@ pub enum VerifierError {
|
||||
pc: usize,
|
||||
id: u32,
|
||||
},
|
||||
HostcallNotPatched {
|
||||
pc: usize,
|
||||
sysc_index: u32,
|
||||
},
|
||||
/// Execution can fall through past the end of the function without a valid terminator
|
||||
/// (e.g., RET, JMP to end, HALT/TRAP). Verifier requires every reachable path to end
|
||||
/// in a terminator.
|
||||
@ -254,6 +258,12 @@ impl Verifier {
|
||||
(arg_count, 0)
|
||||
}
|
||||
OpCode::Ret => (func.return_slots, 0),
|
||||
OpCode::Hostcall => {
|
||||
return Err(VerifierError::HostcallNotPatched {
|
||||
pc: func_start + pc,
|
||||
sysc_index: instr.imm_u32().unwrap(),
|
||||
});
|
||||
}
|
||||
OpCode::Syscall => {
|
||||
let id = instr.imm_u32().unwrap();
|
||||
let syscall = Syscall::from_u32(id).ok_or_else(|| {
|
||||
@ -1145,6 +1155,18 @@ mod tests {
|
||||
assert_eq!(res, Err(VerifierError::InvalidSyscallId { pc: 0, id: 0xDEADBEEF }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verifier_rejects_unpatched_hostcall() {
|
||||
let mut code = Vec::new();
|
||||
code.push(OpCode::Hostcall as u8);
|
||||
code.push(0x00);
|
||||
code.extend_from_slice(&3u32.to_le_bytes());
|
||||
|
||||
let functions = vec![FunctionMeta { code_offset: 0, code_len: 6, ..Default::default() }];
|
||||
let res = Verifier::verify(&code, &functions);
|
||||
assert_eq!(res, Err(VerifierError::HostcallNotPatched { pc: 0, sysc_index: 3 }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_without_terminator_is_rejected() {
|
||||
// Single NOP with no RET/JMP/TRAP/HALT at the end → fallthrough to end
|
||||
|
||||
@ -699,6 +699,15 @@ impl VirtualMachine {
|
||||
self.handle_safepoint();
|
||||
return Err(LogicalFrameEndingReason::FrameSync);
|
||||
}
|
||||
OpCode::Hostcall => {
|
||||
let sysc_index = instr
|
||||
.imm_u32()
|
||||
.map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?;
|
||||
return Err(LogicalFrameEndingReason::Panic(format!(
|
||||
"HOSTCALL {} reached execution without loader patching",
|
||||
sysc_index
|
||||
)));
|
||||
}
|
||||
OpCode::MakeClosure => {
|
||||
// Immediate carries (fn_id, capture_count)
|
||||
let (fn_id, cap_count) = instr
|
||||
@ -2639,9 +2648,16 @@ mod tests {
|
||||
#[test]
|
||||
fn test_loader_hardening_entrypoint_not_found() {
|
||||
let mut vm = VirtualMachine::default();
|
||||
// Valid empty PBS v0 module
|
||||
let mut header = vec![0u8; 32];
|
||||
header[0..4].copy_from_slice(b"PBS\0");
|
||||
let header = prometeu_bytecode::model::BytecodeModule {
|
||||
version: 0,
|
||||
const_pool: vec![],
|
||||
functions: vec![],
|
||||
code: vec![],
|
||||
debug_info: None,
|
||||
exports: vec![],
|
||||
syscalls: vec![],
|
||||
}
|
||||
.serialize();
|
||||
|
||||
// Try to initialize with numeric entrypoint 10 (out of bounds for empty ROM)
|
||||
let res = vm.initialize(header, "10");
|
||||
@ -2657,8 +2673,16 @@ mod tests {
|
||||
let mut vm = VirtualMachine::default();
|
||||
vm.pc = 123; // Pollution
|
||||
|
||||
let mut header = vec![0u8; 32];
|
||||
header[0..4].copy_from_slice(b"PBS\0");
|
||||
let header = prometeu_bytecode::model::BytecodeModule {
|
||||
version: 0,
|
||||
const_pool: vec![],
|
||||
functions: vec![],
|
||||
code: vec![],
|
||||
debug_info: None,
|
||||
exports: vec![],
|
||||
syscalls: vec![],
|
||||
}
|
||||
.serialize();
|
||||
|
||||
let res = vm.initialize(header, "");
|
||||
assert!(res.is_ok());
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user