#![no_std] #[cfg(feature = "std")] extern crate std; pub use crate::instrs::*; use core::convert::TryFrom; mod instrs; type OpR = u8; type OpA = u64; type OpO = i32; type OpP = i16; type OpB = u8; type OpH = u16; type OpW = u32; type OpD = u64; /// # Safety /// Has to be valid to be decoded from bytecode. pub unsafe trait BytecodeItem {} unsafe impl BytecodeItem for u8 {} impl TryFrom for Instr { type Error = u8; #[inline] fn try_from(value: u8) -> Result { #[cold] fn failed(value: u8) -> Result { Err(value) } if value < COUNT { unsafe { Ok(core::mem::transmute::(value)) } } else { failed(value) } } } #[inline] unsafe fn encode(instr: T) -> (usize, [u8; instrs::MAX_SIZE]) { let mut buf = [0; instrs::MAX_SIZE]; core::ptr::write(buf.as_mut_ptr() as *mut T, instr); (core::mem::size_of::(), buf) } #[inline] #[cfg(feature = "disasm")] fn decode(binary: &mut &[u8]) -> Option { let (front, rest) = core::mem::take(binary).split_at_checked(core::mem::size_of::())?; *binary = rest; unsafe { Some(core::ptr::read(front.as_ptr() as *const T)) } } /// Rounding mode #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(u8)] pub enum RoundingMode { NearestEven = 0, Truncate = 1, Up = 2, Down = 3, } impl TryFrom for RoundingMode { type Error = (); fn try_from(value: u8) -> Result { (value <= 3).then(|| unsafe { core::mem::transmute(value) }).ok_or(()) } } #[cfg(feature = "disasm")] #[derive(Clone, Copy)] pub enum DisasmItem { Func, Global, } #[cfg(feature = "disasm")] pub fn disasm( binary: &mut &[u8], functions: &std::collections::BTreeMap, out: &mut impl std::io::Write, mut eca_handler: impl FnMut(&mut &[u8]), ) -> std::io::Result<()> { use { self::instrs::Instr, std::{ collections::{hash_map::Entry, HashMap}, convert::TryInto, vec::Vec, }, }; fn instr_from_byte(b: u8) -> std::io::Result { b.try_into().map_err(|_| std::io::ErrorKind::InvalidData.into()) } let mut labels = HashMap::::default(); let mut buf = Vec::::new(); let mut has_cycle = false; let mut has_oob = false; '_offset_pass: for (&off, &(_name, len, kind)) in functions.iter() { if matches!(kind, DisasmItem::Global) { continue; } let prev = *binary; *binary = &binary[off as usize..]; let mut label_count = 0; while let Some(&byte) = binary.first() { let offset: i32 = (prev.len() - binary.len()).try_into().unwrap(); if offset as u32 == off + len { break; } let Ok(inst) = instr_from_byte(byte) else { break }; instrs::parse_args(binary, inst, &mut buf).ok_or(std::io::ErrorKind::OutOfMemory)?; for op in buf.drain(..) { let rel = match op { instrs::Oper::O(rel) => rel, instrs::Oper::P(rel) => rel.into(), _ => continue, }; has_cycle |= rel == 0; let global_offset: u32 = (offset + rel).try_into().unwrap(); if functions.get(&global_offset).is_some() { continue; } label_count += match labels.entry(global_offset) { Entry::Occupied(_) => 0, Entry::Vacant(entry) => { entry.insert(label_count); 1 } } } if matches!(inst, Instr::ECA) { eca_handler(binary); } } *binary = prev; } let mut ordered = functions.iter().collect::>(); ordered.sort_unstable_by_key(|(_, (name, _, _))| name); '_dump: for (&off, &(name, len, kind)) in ordered { if matches!(kind, DisasmItem::Global) { continue; } let prev = *binary; writeln!(out, "{name}:")?; *binary = &binary[off as usize..]; while let Some(&byte) = binary.first() { let offset: i32 = (prev.len() - binary.len()).try_into().unwrap(); if offset as u32 == off + len { break; } let Ok(inst) = instr_from_byte(byte) else { writeln!(out, "invalid instr {byte}")?; break; }; instrs::parse_args(binary, inst, &mut buf).unwrap(); if let Some(label) = labels.get(&offset.try_into().unwrap()) { write!(out, "{:>2}: ", label)?; } else { write!(out, " ")?; } write!(out, "{inst:<8?} ")?; 'a: for (i, op) in buf.drain(..).enumerate() { if i != 0 { write!(out, ", ")?; } let rel = 'b: { match op { instrs::Oper::O(rel) => break 'b rel, instrs::Oper::P(rel) => break 'b rel.into(), instrs::Oper::R(r) => write!(out, "r{r}")?, instrs::Oper::B(b) => write!(out, "{b}b")?, instrs::Oper::H(h) => write!(out, "{h}h")?, instrs::Oper::W(w) => write!(out, "{w}w")?, instrs::Oper::D(d) if (d as i64) < 0 => write!(out, "{}d", d as i64)?, instrs::Oper::D(d) => write!(out, "{d}d")?, instrs::Oper::A(a) => write!(out, "{a}a")?, } continue 'a; }; let global_offset: u32 = (offset + rel).try_into().unwrap(); if let Some(&(name, ..)) = functions.get(&global_offset) { if name.contains('\0') { write!(out, ":{name:?}")?; } else { write!(out, ":{name}")?; } } else { let local_has_oob = global_offset < off || global_offset > off + len || prev .get(global_offset as usize) .map_or(true, |&b| instr_from_byte(b).is_err()) || prev[global_offset as usize] == 0; has_oob |= local_has_oob; let label = labels.get(&global_offset).unwrap(); if local_has_oob { write!(out, "!!!!!!!!!{rel}")?; } else { write!(out, ":{label}")?; } } } writeln!(out)?; if matches!(inst, Instr::ECA) { eca_handler(binary); } } *binary = prev; } if has_oob { return Err(std::io::ErrorKind::InvalidInput.into()); } if has_cycle { return Err(std::io::ErrorKind::TimedOut.into()); } Ok(()) }