use crate::codegen::ExtFunc;
use core::fmt::Display;
pub use table::JumpTable;
mod pc;
mod relocate;
mod table;
mod target;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Jump {
Label(u16),
Func(u32),
ExtFunc(ExtFunc),
}
impl Display for Jump {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Jump::Label(offset) => write!(f, "Label(0x{offset:x})"),
Jump::Func(index) => write!(f, "Func({index})"),
Jump::ExtFunc(_) => write!(f, "ExtFunc"),
}
}
}
impl Jump {
pub fn is_label(&self) -> bool {
matches!(self, Jump::Label { .. })
}
pub fn is_call(&self) -> bool {
!self.is_label()
}
}
#[cfg(test)]
mod tests {
use crate::jump::{Jump, JumpTable};
use smallvec::smallvec;
#[allow(unused)]
fn init_tracing() {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.without_time()
.compact()
.try_init()
.ok();
}
fn assert_target_shift_vs_relocation(mut table: JumpTable) -> anyhow::Result<()> {
let mut buffer = smallvec![0; table.max_target() as usize];
table.shift_targets()?;
let max_target = table.max_target();
table.relocate(&mut buffer)?;
assert_eq!(buffer.len(), max_target as usize);
Ok(())
}
#[test]
fn test_target_shift_vs_relocation() -> anyhow::Result<()> {
let mut table = JumpTable::default();
table.register(0x10, Jump::Label(0x20)); table.register(0x30, Jump::Label(0x40)); assert_target_shift_vs_relocation(table)
}
#[test]
fn test_multiple_internal_calls() -> anyhow::Result<()> {
let mut table = JumpTable::default();
table.register(0x10, Jump::Label(0x100)); table.register(0x20, Jump::Label(0x100)); assert_target_shift_vs_relocation(table)
}
#[test]
fn test_nested_function_calls() -> anyhow::Result<()> {
let mut table = JumpTable::default();
table.register(0x100, Jump::Label(0x200)); table.register(0x110, Jump::Label(0x300)); table.register(0x200, Jump::Label(0x400)); let mut buffer = smallvec![0; table.max_target() as usize];
table.relocate(&mut buffer)?;
assert_eq!(buffer[0x100], 0x61); assert_eq!(buffer[0x113], 0x61); assert_eq!(buffer[0x206], 0x61); Ok(())
}
#[test]
fn test_label_call_interaction() -> anyhow::Result<()> {
init_tracing();
let mut table = JumpTable::default();
table.func.insert(1, 0x317);
table.label(0x10, 0x12);
table.call(0x11, 1);
let mut buffer = smallvec![0; table.max_target() as usize];
table.relocate(&mut buffer)?;
assert_eq!(buffer[0x11], 0x17, "{buffer:?}");
assert_eq!(buffer[0x14], 0x03, "{buffer:?}");
assert_eq!(buffer[0x15], 0x1c, "{buffer:?}");
Ok(())
}
#[test]
fn test_large_target_offset_calculation() -> anyhow::Result<()> {
let mut table = JumpTable::default();
table.register(0x10, Jump::Label(0x80));
table.register(0x20, Jump::Label(0x100));
table.register(0x30, Jump::Label(0x1000));
let mut buffer = smallvec![0; table.max_target() as usize];
table.relocate(&mut buffer)?;
assert_eq!(buffer[0x11], 0x88); assert_eq!(buffer[0x23], 0x01); assert_eq!(buffer[0x24], 0x08); assert_eq!(buffer[0x36], 0x10); assert_eq!(buffer[0x37], 0x08); Ok(())
}
#[test]
fn test_sequential_large_jumps() -> anyhow::Result<()> {
let mut table = JumpTable::default();
for i in 0..20 {
let target = 0x100 + (i * 0x20);
table.register(0x10 + i, Jump::Label(target));
}
let mut buffer = smallvec![0; table.max_target() as usize];
table.relocate(&mut buffer)?;
assert_eq!(buffer[0x10], 0x61); assert_eq!(buffer[0x11], 0x01); assert_eq!(buffer[0x12], 0x3c); assert_eq!(0x013c, 0x100 + 20 * 3);
let last_idx = 0x10 + 19 + 19 * 3;
assert_eq!(buffer[last_idx], 0x61); assert_eq!(buffer[last_idx + 1], 0x03); assert_eq!(buffer[last_idx + 2], 0x9c); assert_eq!(0x039c, 0x100 + 0x20 * 19 + 20 * 3);
Ok(())
}
#[test]
fn test_dispatcher_jump_targets() -> anyhow::Result<()> {
let mut table = JumpTable::default();
let selectors = 5;
for i in 0..selectors {
let i = i as u16;
let check_pc = 0x10 + i * 0x20;
let target_pc = 0x100 + i * 0x40;
table.register(check_pc, Jump::Label(check_pc + 0x10));
table.register(check_pc + 0x10, Jump::Label(target_pc));
}
let mut buffer = smallvec![0; table.max_target() as usize];
table.relocate(&mut buffer)?;
let mut total_offset = 0;
for i in 0..selectors {
let check_pc = 0x10 + i * 0x20 + total_offset;
let check_pc_offset = if check_pc + 0x10 > 0xff { 3 } else { 2 };
let func_pc = check_pc + 0x10 + check_pc_offset;
let check_jump = buffer[check_pc];
let func_jump = buffer[func_pc];
assert_eq!(check_jump, if func_pc > 0xff { 0x61 } else { 0x60 });
assert_eq!(func_jump, 0x61);
total_offset += check_pc_offset + 3;
}
Ok(())
}
}