//! Deterministic disassembler for Prometeu Bytecode (PBC). //! //! Goals: //! - Stable formatting across platforms (snapshot-friendly). //! - Complete coverage of the Core ISA, including closures/coroutines. //! - Roundtrip-safe with the paired `assembler` module. //! //! Format (one instruction per line): //! - `MNEMONIC` for zero-operand instructions. //! - `MNEMONIC ` for 1-operand instructions (decimal unless stated). //! - Special operand formats: //! - `PUSH_F64 f64:0xhhhhhhhhhhhhhhhh` — exact IEEE-754 bits in hex (little-endian to_bits()). //! - `MAKE_CLOSURE fn=, captures=` //! - `SPAWN fn=, argc=` //! - `CALL_CLOSURE argc=` //! - `HOSTCALL ` 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: //! - All integers are printed in base-10 except where explicitly noted. //! - Floats use exact bit-pattern format to prevent locale/rounding differences. //! - Ordering is the canonical decode order; no address prefixes are emitted. use crate::decode_next; use crate::isa::core::{CoreOpCode, CoreOpCodeSpecExt}; use crate::DecodeError; fn fmt_f64_bits(bits: u64) -> String { // Fixed-width 16 hex digits, lowercase. format!("f64:0x{bits:016x}") } fn format_operand(op: CoreOpCode, imm: &[u8]) -> String { match op { CoreOpCode::Jmp | CoreOpCode::JmpIfFalse | CoreOpCode::JmpIfTrue => { let v = u32::from_le_bytes(imm.try_into().unwrap()); format!("{}", v) } CoreOpCode::PushI64 => { let v = i64::from_le_bytes(imm.try_into().unwrap()); format!("{}", v) } CoreOpCode::PushF64 => { let v = u64::from_le_bytes(imm.try_into().unwrap()); fmt_f64_bits(v) } CoreOpCode::PushBool => { let v = imm[0]; format!("{}", v) } CoreOpCode::PushI32 => { let v = i32::from_le_bytes(imm.try_into().unwrap()); format!("{}", v) } CoreOpCode::PopN | CoreOpCode::PushConst | CoreOpCode::GetGlobal | CoreOpCode::SetGlobal | CoreOpCode::GetLocal | CoreOpCode::SetLocal | CoreOpCode::Call | CoreOpCode::Sleep | CoreOpCode::Hostcall => { let v = u32::from_le_bytes(imm.try_into().unwrap()); format!("{}", v) } CoreOpCode::MakeClosure => { let fn_id = u32::from_le_bytes(imm[0..4].try_into().unwrap()); let cap = u32::from_le_bytes(imm[4..8].try_into().unwrap()); format!("fn={}, captures={}", fn_id, cap) } CoreOpCode::CallClosure => { let argc = u32::from_le_bytes(imm.try_into().unwrap()); format!("argc={}", argc) } CoreOpCode::Spawn => { let fn_id = u32::from_le_bytes(imm[0..4].try_into().unwrap()); let argc = u32::from_le_bytes(imm[4..8].try_into().unwrap()); format!("fn={}, argc={}", fn_id, argc) } CoreOpCode::Syscall => { let id = u32::from_le_bytes(imm.try_into().unwrap()); // Hex id stable, avoids dependency on HAL metadata. format!("0x{:04x}", id) } CoreOpCode::Intrinsic => { let id = u32::from_le_bytes(imm.try_into().unwrap()); format!("0x{:04x}", id) } _ => { // Fallback: raw immediate hex (little-endian, as encoded) let mut s = String::with_capacity(2 + imm.len() * 2); s.push_str("0x"); for b in imm { use core::fmt::Write as _; let _ = write!(&mut s, "{:02x}", b); } s } } } /// Disassembles a contiguous byte slice (single function body) into deterministic text. pub fn disassemble(bytes: &[u8]) -> Result { let mut pc = 0usize; let mut out = Vec::new(); while pc < bytes.len() { let instr = decode_next(pc, bytes)?; let name = instr.opcode.spec().name; let imm_len = instr.opcode.spec().imm_bytes as usize; if imm_len == 0 { out.push(name.to_string()); } else { let ops = format_operand(instr.opcode, instr.imm); out.push(format!("{} {}", name, ops)); } pc = instr.next_pc; } Ok(out.join("\n")) }