use crate::VmMemory;
macro_rules! wide_division {
($name:ident, $int:ty, $checked:ident, $wrapping:ident) => {
pub unsafe extern "C" fn $name(
a_low: u64,
a_high: u64,
b_low: u64,
b_high: u64,
out: *mut u64,
) {
let a = (u128::from(a_high) << 64 | u128::from(a_low)) as $int;
let b = (u128::from(b_high) << 64 | u128::from(b_low)) as $int;
let result = match a.$checked(b) {
Some(value) => value,
None if b == 0 => 0,
None => a.$wrapping(b),
} as u128;
unsafe {
out.write(result as u64);
out.add(1).write((result >> 64) as u64);
}
}
};
}
wide_division!(qcode_jit_udiv128, u128, checked_div, wrapping_div);
wide_division!(qcode_jit_urem128, u128, checked_rem, wrapping_rem);
wide_division!(qcode_jit_sdiv128, i128, checked_div, wrapping_div);
wide_division!(qcode_jit_srem128, i128, checked_rem, wrapping_rem);
pub const ACCESS_OK: u32 = 0;
pub const ACCESS_FAULT: u32 = 1;
pub unsafe extern "C" fn qcode_jit_load(
memory: *mut VmMemory,
addr: u64,
size: u32,
out: *mut u64,
) -> u32 {
let memory = unsafe { &mut *memory };
let size = size as usize;
debug_assert!(size <= 8);
let mut bytes = [0u8; 8];
if let Err(fault) = memory.mmu.read(addr, &mut bytes[..size]) {
memory.record_read_fault(fault);
return ACCESS_FAULT;
}
unsafe { out.write(u64::from_le_bytes(bytes)) };
memory.mmu.cache_translation(addr);
ACCESS_OK
}
pub unsafe extern "C" fn qcode_jit_store(
memory: *mut VmMemory,
addr: u64,
size: u32,
value: u64,
) -> u32 {
let memory = unsafe { &mut *memory };
let size = size as usize;
debug_assert!(size <= 8);
if let Err(fault) = memory.mmu.write(addr, &value.to_le_bytes()[..size]) {
memory.record_write_fault(fault);
return ACCESS_FAULT;
}
memory.mmu.cache_translation(addr);
ACCESS_OK
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mmu::{FaultKind, PAGE_SIZE, perm};
fn memory() -> VmMemory {
let mut memory = VmMemory::new();
memory.mmu.map(0x1000, PAGE_SIZE, perm::RW_INIT).unwrap();
memory
}
#[test]
fn a_served_access_leaves_the_page_cached() {
let mut memory = memory();
let mut out = 0u64;
let status = unsafe { qcode_jit_store(&raw mut memory, 0x1008, 4, 0xdead_beef) };
assert_eq!(status, ACCESS_OK);
let status = unsafe { qcode_jit_load(&raw mut memory, 0x1008, 4, &raw mut out) };
assert_eq!(status, ACCESS_OK);
assert_eq!(out, 0xdead_beef);
assert!(memory.mmu.cache_translation(0x1008));
}
#[test]
fn a_refused_access_reports_a_fault_and_caches_nothing() {
let mut memory = memory();
let mut out = 0u64;
let status = unsafe { qcode_jit_load(&raw mut memory, 0x9000, 1, &raw mut out) };
assert_eq!(status, ACCESS_FAULT);
assert_eq!(
memory.take_fault().map(|fault| fault.kind),
Some(FaultKind::ReadUnmapped)
);
}
#[test]
fn a_write_to_read_only_memory_faults_at_the_byte() {
let mut memory = VmMemory::new();
memory.mmu.map(0x1000, PAGE_SIZE, perm::RX_INIT).unwrap();
let status = unsafe { qcode_jit_store(&raw mut memory, 0x1000, 1, 0xff) };
assert_eq!(status, ACCESS_FAULT);
assert_eq!(memory.take_fault().map(|fault| fault.addr), Some(0x1000));
}
#[test]
fn an_access_straddling_two_pages_is_served_whole() {
let mut memory = VmMemory::new();
memory
.mmu
.map(0x1000, 2 * PAGE_SIZE, perm::RW_INIT)
.unwrap();
let mut out = 0u64;
unsafe { qcode_jit_store(&raw mut memory, 0x1ffe, 8, 0x0102_0304_0506_0708) };
unsafe { qcode_jit_load(&raw mut memory, 0x1ffe, 8, &raw mut out) };
assert_eq!(out, 0x0102_0304_0506_0708);
}
}