137 lines
5.4 KiB
Rust
137 lines
5.4 KiB
Rust
// diff_fast corpus program: `#[test] run` calls work() in rounds (the O3 tier installs between rounds); every
|
|
// result line is `D ...`. rustc --test -O and `makepad test` (baseline and O3) must print identical D lines.
|
|
// Calls in tail position become jumps (sibling calls), as LLVM makes them: millions of chained calls run in
|
|
// constant stack -- a threaded interpreter of `extern "C"` handlers (makepad_stitch's shape: a Rust dispatch fn
|
|
// called from each handler, behind its cannot-unwind guard), mutual recursion, fn-pointer chains. Calls that
|
|
// are not in tail position (drops after them, a local's address passed) keep their frames and drop order,
|
|
// also when a panic unwinds through the chain.
|
|
macro_rules! d { ($($a:tt)*) => { println!("D {}", format!($($a)*)) } }
|
|
fn rounds(f: fn()) {
|
|
let ms: u64 = std::env::var("DIFF_SLEEP_MS").ok().and_then(|v| v.parse().ok()).unwrap_or(0);
|
|
for r in 0..4 { f(); if r < 3 && ms > 0 { std::thread::sleep(std::time::Duration::from_millis(ms)); } }
|
|
}
|
|
#[test]
|
|
fn run() { rounds(work); }
|
|
|
|
// ---- a threaded interpreter (the stitch shape): code is a list of handler pointers and immediates
|
|
type Handler = unsafe extern "C" fn(ip: *const usize, sp: *mut u64, n: u32, acc: u64, x: f32, y: f64, cx: *mut u64) -> u64;
|
|
|
|
#[inline(never)]
|
|
unsafe fn next(ip: *const usize, sp: *mut u64, n: u32, acc: u64, x: f32, y: f64, cx: *mut u64) -> u64 {
|
|
let h: Handler = std::mem::transmute(*ip);
|
|
h(ip.add(1), sp, n, acc, x, y, cx)
|
|
}
|
|
|
|
unsafe extern "C" fn op_add(ip: *const usize, sp: *mut u64, n: u32, acc: u64, x: f32, y: f64, cx: *mut u64) -> u64 {
|
|
let k = *ip as u64;
|
|
next(ip.add(1), sp, n, acc.wrapping_add(k), x + 0.5, y, cx)
|
|
}
|
|
unsafe extern "C" fn op_mul(ip: *const usize, sp: *mut u64, n: u32, acc: u64, x: f32, y: f64, cx: *mut u64) -> u64 {
|
|
next(ip, sp, n, acc.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407), x, y * 1.0000001, cx)
|
|
}
|
|
unsafe extern "C" fn op_store(ip: *const usize, sp: *mut u64, n: u32, acc: u64, x: f32, y: f64, cx: *mut u64) -> u64 {
|
|
*sp.add((acc & 7) as usize) ^= acc;
|
|
*cx += 1;
|
|
next(ip, sp, n, acc, x, y, cx)
|
|
}
|
|
// the loop: back to the start until the counter runs out
|
|
unsafe extern "C" fn op_loop(ip: *const usize, sp: *mut u64, n: u32, acc: u64, x: f32, y: f64, cx: *mut u64) -> u64 {
|
|
let start = *ip as *const usize;
|
|
if n == 0 {
|
|
return acc ^ (x as u64) ^ (y as u64);
|
|
}
|
|
next(start, sp, n - 1, acc, x, y, cx)
|
|
}
|
|
|
|
fn interp(rounds: u32) -> (u64, u64, u64) {
|
|
let mut code: Vec<usize> = Vec::with_capacity(16);
|
|
code.push(op_add as *const () as usize);
|
|
code.push(3);
|
|
code.push(op_mul as *const () as usize);
|
|
code.push(op_store as *const () as usize);
|
|
code.push(op_add as *const () as usize);
|
|
code.push(11);
|
|
code.push(op_mul as *const () as usize);
|
|
code.push(op_loop as *const () as usize);
|
|
let start = code.as_ptr() as usize;
|
|
code.push(start);
|
|
let mut stack = [0u64; 8];
|
|
let mut count = 0u64;
|
|
let r = unsafe { next(code.as_ptr(), stack.as_mut_ptr(), rounds, 1, 0.0, 1.0, &mut count) };
|
|
(r, stack.iter().fold(0, |a, &b| a ^ b.rotate_left(7)), count)
|
|
}
|
|
|
|
// ---- mutual recursion (Relax ABI direct calls)
|
|
#[inline(never)]
|
|
fn even(n: u64, acc: u64) -> u64 {
|
|
if n == 0 { acc } else { odd(n - 1, acc.wrapping_mul(31).wrapping_add(n)) }
|
|
}
|
|
#[inline(never)]
|
|
fn odd(n: u64, acc: u64) -> u64 {
|
|
if n == 0 { !acc } else { even(n - 1, acc ^ (n << 3)) }
|
|
}
|
|
|
|
// ---- a chain through fn pointers (Relax ABI indirect calls) with float arguments
|
|
type Step2 = fn(u64, f64) -> f64;
|
|
#[inline(never)]
|
|
fn ping(n: u64, f: f64) -> f64 {
|
|
if n == 0 { return f; }
|
|
let t = PONG[(n & 1) as usize];
|
|
t(n - 1, f * 0.999 + 1.0)
|
|
}
|
|
#[inline(never)]
|
|
fn pong(n: u64, f: f64) -> f64 {
|
|
if n == 0 { return -f; }
|
|
let t = PING[(n & 1) as usize];
|
|
t(n - 1, f + 0.25)
|
|
}
|
|
static PING: [Step2; 2] = [ping, ping];
|
|
static PONG: [Step2; 2] = [pong, pong];
|
|
|
|
// ---- not in tail position: a guard drops after the call; a local's address is passed down
|
|
struct Guard(u32, &'static std::sync::Mutex<Vec<u32>>);
|
|
impl Drop for Guard {
|
|
fn drop(&mut self) { self.1.lock().unwrap().push(self.0); }
|
|
}
|
|
static ORDER: std::sync::Mutex<Vec<u32>> = std::sync::Mutex::new(Vec::new());
|
|
#[inline(never)]
|
|
fn guarded(n: u32) -> u32 {
|
|
let _g = Guard(n, &ORDER);
|
|
if n == 0 { panic!("bottom"); }
|
|
deeper(n - 1)
|
|
}
|
|
#[inline(never)]
|
|
fn deeper(n: u32) -> u32 {
|
|
if n % 3 == 0 { guarded(n) } else { inner_local(n) }
|
|
}
|
|
#[inline(never)]
|
|
fn inner_local(n: u32) -> u32 {
|
|
let x = n * 2;
|
|
with_ref(&x, n)
|
|
}
|
|
#[inline(never)]
|
|
fn with_ref(x: &u32, n: u32) -> u32 {
|
|
let v = *x;
|
|
if n == 0 { v } else { guarded(n - 1) + v }
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn sum_to(n: u64, acc: u64) -> u64 {
|
|
if n == 0 { return acc; }
|
|
sum_to(n - 1, acc + n)
|
|
}
|
|
|
|
fn work() {
|
|
let (r, s, c) = interp(1_000_000);
|
|
d!("interp r={:x} stack={:x} count={}", r, s, c);
|
|
d!("even/odd {:x} {:x}", even(5_000_000, 7), odd(3_000_001, 9));
|
|
d!("ping {:.6}", ping(4_000_000, 1.5));
|
|
d!("sum_to {}", sum_to(6_000_000, 0));
|
|
ORDER.lock().unwrap().clear();
|
|
let caught = std::panic::catch_unwind(|| guarded(40));
|
|
d!("guarded panicked={} order={:?}", caught.is_err(), ORDER.lock().unwrap());
|
|
// a thread with a small stack: the chains still fit
|
|
let h = std::thread::Builder::new().stack_size(256 << 10).spawn(|| (interp(200_000), even(1_000_000, 1))).unwrap();
|
|
let ((r, s, c), e) = h.join().unwrap();
|
|
d!("small-stack thread r={:x} stack={:x} count={} even={:x}", r, s, c, e);
|
|
}
|