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);
|
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" => {
|
"JMP" => {
|
||||||
if ops.is_empty() {
|
if ops.is_empty() {
|
||||||
return Err(AsmError::MissingOperand(line.into()));
|
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_u16(CoreOpCode::Sleep as u16, &mut out);
|
||||||
emit_u32(parse_u32_any(ops)?, &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" => {
|
"SYSCALL" => {
|
||||||
if ops.is_empty() {
|
if ops.is_empty() {
|
||||||
return Err(AsmError::MissingOperand(line.into()));
|
return Err(AsmError::MissingOperand(line.into()));
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
//! - `MAKE_CLOSURE fn=<u32>, captures=<u32>`
|
//! - `MAKE_CLOSURE fn=<u32>, captures=<u32>`
|
||||||
//! - `SPAWN fn=<u32>, argc=<u32>`
|
//! - `SPAWN fn=<u32>, argc=<u32>`
|
||||||
//! - `CALL_CLOSURE 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.
|
//! - `SYSCALL` is printed as `SYSCALL 0xhhhh` (numeric id in hex) to avoid cross-crate deps.
|
||||||
//!
|
//!
|
||||||
//! Notes:
|
//! Notes:
|
||||||
@ -58,7 +59,8 @@ fn format_operand(op: CoreOpCode, imm: &[u8]) -> String {
|
|||||||
| CoreOpCode::GetLocal
|
| CoreOpCode::GetLocal
|
||||||
| CoreOpCode::SetLocal
|
| CoreOpCode::SetLocal
|
||||||
| CoreOpCode::Call
|
| CoreOpCode::Call
|
||||||
| CoreOpCode::Sleep => {
|
| CoreOpCode::Sleep
|
||||||
|
| CoreOpCode::Hostcall => {
|
||||||
let v = u32::from_le_bytes(imm.try_into().unwrap());
|
let v = u32::from_le_bytes(imm.try_into().unwrap());
|
||||||
format!("{}", v)
|
format!("{}", v)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -679,6 +679,7 @@ fn validate_module(module: &BytecodeModule) -> Result<(), LoadError> {
|
|||||||
| OpCode::GetLocal
|
| OpCode::GetLocal
|
||||||
| OpCode::SetLocal
|
| OpCode::SetLocal
|
||||||
| OpCode::PopN
|
| OpCode::PopN
|
||||||
|
| OpCode::Hostcall
|
||||||
| OpCode::Syscall => {
|
| OpCode::Syscall => {
|
||||||
pos += 4;
|
pos += 4;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -185,6 +185,11 @@ pub enum OpCode {
|
|||||||
Sleep = 0x56,
|
Sleep = 0x56,
|
||||||
|
|
||||||
// --- 6.8 Peripherals and System ---
|
// --- 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).
|
/// Invokes a system function (Firmware/OS).
|
||||||
/// Operand: syscall_id (u32)
|
/// Operand: syscall_id (u32)
|
||||||
/// Stack: [args...] -> [results...] (depends on syscall)
|
/// Stack: [args...] -> [results...] (depends on syscall)
|
||||||
@ -246,6 +251,7 @@ impl TryFrom<u16> for OpCode {
|
|||||||
0x55 => Ok(OpCode::Yield),
|
0x55 => Ok(OpCode::Yield),
|
||||||
0x56 => Ok(OpCode::Sleep),
|
0x56 => Ok(OpCode::Sleep),
|
||||||
0x70 => Ok(OpCode::Syscall),
|
0x70 => Ok(OpCode::Syscall),
|
||||||
|
0x71 => Ok(OpCode::Hostcall),
|
||||||
0x80 => Ok(OpCode::FrameSync),
|
0x80 => Ok(OpCode::FrameSync),
|
||||||
_ => Err(format!("Invalid OpCode: 0x{:04X}", value)),
|
_ => Err(format!("Invalid OpCode: 0x{:04X}", value)),
|
||||||
}
|
}
|
||||||
@ -304,6 +310,7 @@ impl OpCode {
|
|||||||
OpCode::Yield => 1,
|
OpCode::Yield => 1,
|
||||||
OpCode::Sleep => 1,
|
OpCode::Sleep => 1,
|
||||||
OpCode::Syscall => 1,
|
OpCode::Syscall => 1,
|
||||||
|
OpCode::Hostcall => 1,
|
||||||
OpCode::FrameSync => 1,
|
OpCode::FrameSync => 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -493,6 +493,16 @@ impl OpCodeSpecExt for OpCode {
|
|||||||
// Considered a safepoint since it forces a frame boundary
|
// Considered a safepoint since it forces a frame boundary
|
||||||
is_safepoint: true,
|
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 {
|
OpCode::Syscall => OpcodeSpec {
|
||||||
name: "SYSCALL",
|
name: "SYSCALL",
|
||||||
imm_bytes: 4,
|
imm_bytes: 4,
|
||||||
|
|||||||
@ -11,7 +11,7 @@ fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec<u8>) {
|
|||||||
#[test]
|
#[test]
|
||||||
fn roundtrip_disasm_assemble_byte_equal_with_closures_and_coroutines() {
|
fn roundtrip_disasm_assemble_byte_equal_with_closures_and_coroutines() {
|
||||||
// Program: PUSH_I32 7; MAKE_CLOSURE fn=1,captures=0; CALL_CLOSURE argc=1;
|
// 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();
|
let mut prog = Vec::new();
|
||||||
|
|
||||||
emit(CoreOpCode::PushI32, Some(&7i32.to_le_bytes()), &mut prog);
|
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);
|
emit(CoreOpCode::Yield, None, &mut prog);
|
||||||
// SLEEP 3
|
// SLEEP 3
|
||||||
emit(CoreOpCode::Sleep, Some(&3u32.to_le_bytes()), &mut prog);
|
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)
|
// SYSCALL gfx.draw_line (0x1003)
|
||||||
emit(CoreOpCode::Syscall, Some(&0x1003u32.to_le_bytes()), &mut prog);
|
emit(CoreOpCode::Syscall, Some(&0x1003u32.to_le_bytes()), &mut prog);
|
||||||
// FRAME_SYNC
|
// FRAME_SYNC
|
||||||
|
|||||||
@ -28,6 +28,8 @@ fn snapshot_representative_program_is_stable() {
|
|||||||
emit(CoreOpCode::Yield, None, &mut prog);
|
emit(CoreOpCode::Yield, None, &mut prog);
|
||||||
// SLEEP 3
|
// SLEEP 3
|
||||||
emit(CoreOpCode::Sleep, Some(&3u32.to_le_bytes()), &mut prog);
|
emit(CoreOpCode::Sleep, Some(&3u32.to_le_bytes()), &mut prog);
|
||||||
|
// HOSTCALL 2
|
||||||
|
emit(CoreOpCode::Hostcall, Some(&2u32.to_le_bytes()), &mut prog);
|
||||||
// SYSCALL 0x1003
|
// SYSCALL 0x1003
|
||||||
emit(CoreOpCode::Syscall, Some(&0x1003u32.to_le_bytes()), &mut prog);
|
emit(CoreOpCode::Syscall, Some(&0x1003u32.to_le_bytes()), &mut prog);
|
||||||
// FRAME_SYNC
|
// FRAME_SYNC
|
||||||
@ -36,6 +38,6 @@ fn snapshot_representative_program_is_stable() {
|
|||||||
emit(CoreOpCode::Halt, None, &mut prog);
|
emit(CoreOpCode::Halt, None, &mut prog);
|
||||||
|
|
||||||
let text = disassemble(&prog).expect("disasm ok");
|
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);
|
assert_eq!(text, expected);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,7 @@ fn disasm(bytes: &[u8]) -> String {
|
|||||||
CoreOpCode::PushF64 => format!("{}", instr.imm_f64().unwrap()),
|
CoreOpCode::PushF64 => format!("{}", instr.imm_f64().unwrap()),
|
||||||
CoreOpCode::PushBool => format!("{}", instr.imm_u8().unwrap()),
|
CoreOpCode::PushBool => format!("{}", instr.imm_u8().unwrap()),
|
||||||
CoreOpCode::PushI32 => format!("{}", instr.imm_i32().unwrap()),
|
CoreOpCode::PushI32 => format!("{}", instr.imm_i32().unwrap()),
|
||||||
CoreOpCode::PopN | CoreOpCode::PushConst => {
|
CoreOpCode::PopN | CoreOpCode::PushConst | CoreOpCode::Hostcall => {
|
||||||
format!("{}", instr.imm_u32().unwrap())
|
format!("{}", instr.imm_u32().unwrap())
|
||||||
}
|
}
|
||||||
_ => format!("0x{}", hex::encode(instr.imm)),
|
_ => format!("0x{}", hex::encode(instr.imm)),
|
||||||
@ -115,6 +115,20 @@ fn disasm_contains_expected_mnemonics_and_operands() {
|
|||||||
assert!(text.contains("HALT"));
|
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
|
// Minimal hex helper to avoid extra deps in tests
|
||||||
mod hex {
|
mod hex {
|
||||||
pub fn encode(bytes: &[u8]) -> String {
|
pub fn encode(bytes: &[u8]) -> String {
|
||||||
|
|||||||
@ -67,6 +67,10 @@ pub enum VerifierError {
|
|||||||
pc: usize,
|
pc: usize,
|
||||||
id: u32,
|
id: u32,
|
||||||
},
|
},
|
||||||
|
HostcallNotPatched {
|
||||||
|
pc: usize,
|
||||||
|
sysc_index: u32,
|
||||||
|
},
|
||||||
/// Execution can fall through past the end of the function without a valid terminator
|
/// 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
|
/// (e.g., RET, JMP to end, HALT/TRAP). Verifier requires every reachable path to end
|
||||||
/// in a terminator.
|
/// in a terminator.
|
||||||
@ -254,6 +258,12 @@ impl Verifier {
|
|||||||
(arg_count, 0)
|
(arg_count, 0)
|
||||||
}
|
}
|
||||||
OpCode::Ret => (func.return_slots, 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 => {
|
OpCode::Syscall => {
|
||||||
let id = instr.imm_u32().unwrap();
|
let id = instr.imm_u32().unwrap();
|
||||||
let syscall = Syscall::from_u32(id).ok_or_else(|| {
|
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 }));
|
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]
|
#[test]
|
||||||
fn test_function_without_terminator_is_rejected() {
|
fn test_function_without_terminator_is_rejected() {
|
||||||
// Single NOP with no RET/JMP/TRAP/HALT at the end → fallthrough to end
|
// Single NOP with no RET/JMP/TRAP/HALT at the end → fallthrough to end
|
||||||
|
|||||||
@ -699,6 +699,15 @@ impl VirtualMachine {
|
|||||||
self.handle_safepoint();
|
self.handle_safepoint();
|
||||||
return Err(LogicalFrameEndingReason::FrameSync);
|
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 => {
|
OpCode::MakeClosure => {
|
||||||
// Immediate carries (fn_id, capture_count)
|
// Immediate carries (fn_id, capture_count)
|
||||||
let (fn_id, cap_count) = instr
|
let (fn_id, cap_count) = instr
|
||||||
@ -2639,9 +2648,16 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_loader_hardening_entrypoint_not_found() {
|
fn test_loader_hardening_entrypoint_not_found() {
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
// Valid empty PBS v0 module
|
let header = prometeu_bytecode::model::BytecodeModule {
|
||||||
let mut header = vec![0u8; 32];
|
version: 0,
|
||||||
header[0..4].copy_from_slice(b"PBS\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)
|
// Try to initialize with numeric entrypoint 10 (out of bounds for empty ROM)
|
||||||
let res = vm.initialize(header, "10");
|
let res = vm.initialize(header, "10");
|
||||||
@ -2657,8 +2673,16 @@ mod tests {
|
|||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
vm.pc = 123; // Pollution
|
vm.pc = 123; // Pollution
|
||||||
|
|
||||||
let mut header = vec![0u8; 32];
|
let header = prometeu_bytecode::model::BytecodeModule {
|
||||||
header[0..4].copy_from_slice(b"PBS\0");
|
version: 0,
|
||||||
|
const_pool: vec![],
|
||||||
|
functions: vec![],
|
||||||
|
code: vec![],
|
||||||
|
debug_info: None,
|
||||||
|
exports: vec![],
|
||||||
|
syscalls: vec![],
|
||||||
|
}
|
||||||
|
.serialize();
|
||||||
|
|
||||||
let res = vm.initialize(header, "");
|
let res = vm.initialize(header, "");
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user