Skip to main content

qcode_vm/
jit_abi.rs

1//! The calling interface between compiled code and this VM's memory.
2//!
3//! Compiled code reaches guest RAM through an inlined translation and an
4//! inlined permission check (see [`crate::tlb`]). Neither can answer every
5//! access: the page may not be cached yet, the access may straddle a page
6//! boundary, the permissions may refuse it, or a dynamic check may be in force
7//! that the inline test cannot express. All of those land here, on the same
8//! [`Mmu`](crate::mmu::Mmu) the interpreter uses.
9//!
10//! That is the point of the split. There is one implementation of what an
11//! access *means* — mapping, permissions, initializedness, watchpoints, fault
12//! addresses — and the inline path is a cache in front of it, never a second
13//! opinion. A miss costs a call; a wrong answer would cost correctness.
14//!
15//! # Faults
16//!
17//! These return a status rather than taking a fault themselves, because
18//! compiled code has to unwind on its own: it stops at the faulting access,
19//! leaving the guest state its earlier stores produced, and returns the status
20//! to the runtime. The fault itself is left on the [`VmMemory`] for the caller
21//! to take, exactly as an interpreted fault would be.
22
23use crate::VmMemory;
24
25/// The four integer divisions, at a width the host has no instruction for.
26///
27/// x86-64 divides a 128-bit dividend by a 64-bit divisor, so SLEIGH lifts every
28/// `div` and `idiv` as a 128-bit operation — and neither the machine nor
29/// Cranelift can do one. Compiled code calls out to these rather than declining
30/// the block: a call is far cheaper than interpreting every instruction around
31/// the division as well.
32///
33/// Each states QCode's rule directly, rather than the caller guarding the
34/// operands: a zero divisor yields zero, and signed division wraps where the
35/// machine would fault.
36macro_rules! wide_division {
37    ($name:ident, $int:ty, $checked:ident, $wrapping:ident) => {
38        /// # Safety
39        ///
40        /// `out` must point to two writable, consecutive `u64`s, which receive
41        /// the low and high halves of the result. Called only from code this
42        /// crate's JIT backend generated.
43        pub unsafe extern "C" fn $name(
44            a_low: u64,
45            a_high: u64,
46            b_low: u64,
47            b_high: u64,
48            out: *mut u64,
49        ) {
50            let a = (u128::from(a_high) << 64 | u128::from(a_low)) as $int;
51            let b = (u128::from(b_high) << 64 | u128::from(b_low)) as $int;
52            // `checked_*` covers both rules at once: it declines a zero divisor
53            // and the signed overflow, and QCode's answer for each is zero and
54            // the wrapped value respectively.
55            let result = match a.$checked(b) {
56                Some(value) => value,
57                None if b == 0 => 0,
58                // The only other refusal is the most negative value over -1,
59                // where wrapping gives exactly what QCode specifies: that same
60                // value as the quotient, and zero as the remainder.
61                None => a.$wrapping(b),
62            } as u128;
63            // SAFETY: the caller guarantees two writable `u64`s.
64            unsafe {
65                out.write(result as u64);
66                out.add(1).write((result >> 64) as u64);
67            }
68        }
69    };
70}
71
72wide_division!(qcode_jit_udiv128, u128, checked_div, wrapping_div);
73wide_division!(qcode_jit_urem128, u128, checked_rem, wrapping_rem);
74wide_division!(qcode_jit_sdiv128, i128, checked_div, wrapping_div);
75wide_division!(qcode_jit_srem128, i128, checked_rem, wrapping_rem);
76
77/// The access succeeded.
78pub const ACCESS_OK: u32 = 0;
79/// The access faulted; the fault is on the [`VmMemory`].
80pub const ACCESS_FAULT: u32 = 1;
81
82/// Performs a guest RAM read that compiled code could not do inline.
83///
84/// # Safety
85///
86/// `memory` must point to a live [`VmMemory`] that nothing else is borrowing,
87/// `out` to a writable `u64`, and `size` must be at most 8. Called only from
88/// code this crate's JIT backend generated.
89pub unsafe extern "C" fn qcode_jit_load(
90    memory: *mut VmMemory,
91    addr: u64,
92    size: u32,
93    out: *mut u64,
94) -> u32 {
95    // SAFETY: the caller guarantees an exclusive, live pointer.
96    let memory = unsafe { &mut *memory };
97    let size = size as usize;
98    debug_assert!(size <= 8);
99
100    let mut bytes = [0u8; 8];
101    if let Err(fault) = memory.mmu.read(addr, &mut bytes[..size]) {
102        memory.record_read_fault(fault);
103        return ACCESS_FAULT;
104    }
105    // SAFETY: the caller guarantees `out` is a writable `u64`.
106    unsafe { out.write(u64::from_le_bytes(bytes)) };
107
108    // The access worked, so the page is resident and reachable: cache it so
109    // the next execution of this instruction stays inline. An access that
110    // straddled two pages caches the first, which the inline path's same-page
111    // test will decline again — that is a slow instruction, not a wrong one.
112    memory.mmu.cache_translation(addr);
113    ACCESS_OK
114}
115
116/// Performs a guest RAM write that compiled code could not do inline.
117///
118/// # Safety
119///
120/// As [`qcode_jit_load`], and `size` must be at most 8.
121pub unsafe extern "C" fn qcode_jit_store(
122    memory: *mut VmMemory,
123    addr: u64,
124    size: u32,
125    value: u64,
126) -> u32 {
127    // SAFETY: the caller guarantees an exclusive, live pointer.
128    let memory = unsafe { &mut *memory };
129    let size = size as usize;
130    debug_assert!(size <= 8);
131
132    if let Err(fault) = memory.mmu.write(addr, &value.to_le_bytes()[..size]) {
133        memory.record_write_fault(fault);
134        return ACCESS_FAULT;
135    }
136    memory.mmu.cache_translation(addr);
137    ACCESS_OK
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use crate::mmu::{FaultKind, PAGE_SIZE, perm};
144
145    fn memory() -> VmMemory {
146        let mut memory = VmMemory::new();
147        memory.mmu.map(0x1000, PAGE_SIZE, perm::RW_INIT).unwrap();
148        memory
149    }
150
151    #[test]
152    fn a_served_access_leaves_the_page_cached() {
153        let mut memory = memory();
154        let mut out = 0u64;
155        let status = unsafe { qcode_jit_store(&raw mut memory, 0x1008, 4, 0xdead_beef) };
156        assert_eq!(status, ACCESS_OK);
157        let status = unsafe { qcode_jit_load(&raw mut memory, 0x1008, 4, &raw mut out) };
158        assert_eq!(status, ACCESS_OK);
159        assert_eq!(out, 0xdead_beef);
160        // Having served it the slow way once, the next one can be inline.
161        assert!(memory.mmu.cache_translation(0x1008));
162    }
163
164    #[test]
165    fn a_refused_access_reports_a_fault_and_caches_nothing() {
166        let mut memory = memory();
167        let mut out = 0u64;
168        let status = unsafe { qcode_jit_load(&raw mut memory, 0x9000, 1, &raw mut out) };
169        assert_eq!(status, ACCESS_FAULT);
170        assert_eq!(
171            memory.take_fault().map(|fault| fault.kind),
172            Some(FaultKind::ReadUnmapped)
173        );
174    }
175
176    #[test]
177    fn a_write_to_read_only_memory_faults_at_the_byte() {
178        let mut memory = VmMemory::new();
179        memory.mmu.map(0x1000, PAGE_SIZE, perm::RX_INIT).unwrap();
180        let status = unsafe { qcode_jit_store(&raw mut memory, 0x1000, 1, 0xff) };
181        assert_eq!(status, ACCESS_FAULT);
182        assert_eq!(memory.take_fault().map(|fault| fault.addr), Some(0x1000));
183    }
184
185    #[test]
186    fn an_access_straddling_two_pages_is_served_whole() {
187        let mut memory = VmMemory::new();
188        memory
189            .mmu
190            .map(0x1000, 2 * PAGE_SIZE, perm::RW_INIT)
191            .unwrap();
192        let mut out = 0u64;
193        // The inline path declines this shape outright; the slow path is the
194        // only thing that ever sees it, so it has to be right here.
195        unsafe { qcode_jit_store(&raw mut memory, 0x1ffe, 8, 0x0102_0304_0506_0708) };
196        unsafe { qcode_jit_load(&raw mut memory, 0x1ffe, 8, &raw mut out) };
197        assert_eq!(out, 0x0102_0304_0506_0708);
198    }
199}