makepad/libs/relax/codegen/tests/copy_runs.rs
Admin 83a2eee339 work: O3 tier reports panicking builds; multi-latch unroll rule narrowed (race -1%)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 02:31:39 +02:00

99 lines
4.8 KiB
Rust

//! merge_copy_runs: a struct copied field by field (loads from one base, each value stored at the
//! same distance from another) becomes one Copy; an intervening write that may touch either range,
//! a read of the destination in between, or a call keeps the fields. Results compiled before and
//! after the pass must agree.
#![cfg(any(all(target_arch = "x86_64", unix), all(target_arch = "aarch64", unix)))]
use relax_backend::{exec::Code, rir::*};
use relax_codegen::opt;
const U64: IntTy = IntTy { bits: 64, signed: false };
fn push(f: &mut Func, b: u32, i: Inst) {
f.blocks[b as usize].insts.push(i);
f.blocks[b as usize].pos.push(1);
}
#[cfg(target_arch = "x86_64")]
fn run(f: &Func, p: u64) -> u64 {
let env = relax_backend::x64::Env { slots: SlotsPtr::NONE, tls_key: 0, tls_slow: 0, resume: 0, opt_regs: true, syms: 0, sym_class: 0, near: false };
let c = relax_backend::x64::compile(&relax_session::Options::default(), f, &env).unwrap();
let code = Code::new(&c.code[..c.text_len as usize]).unwrap();
let g: extern "C" fn(u64) -> u64 = unsafe { std::mem::transmute(code.ptr()) };
g(p)
}
#[cfg(target_arch = "aarch64")]
fn run(f: &Func, p: u64) -> u64 {
let env = relax_backend::arm64::Env { slots: SlotsPtr::NONE, tls_key: 0, tls_slow: 0, resume: 0, opt_regs: true, syms: 0, sym_class: 0, near: false };
let c = relax_backend::arm64::compile(&relax_session::Options::default(), f, &env).unwrap();
let code = Code::new(&c.code[..c.text_len as usize]).unwrap();
let g: extern "C" fn(u64) -> u64 = unsafe { std::mem::transmute(code.ptr()) };
g(p)
}
/// f(p): S = { p[0..8], p[8..12] as f32, p[12..16] as i32, p[16..24] } field by field (a derived
/// Clone of Copy fields), `between`: an instruction after the first field's store; returns a
/// checksum of S's four words read back.
#[derive(Clone, Copy, PartialEq, Debug)]
enum Between { Nothing, StoreSrc, LoadDst, Call }
fn build(between: Between) -> Func {
let mut f = Func::new("copy_runs".into(), 0);
let p = f.vreg(Cls::I);
f.params.push(p);
f.rets.push(Cls::I);
let s = f.slot(24, 8);
f.block();
let v = |f: &mut Func, c: Cls| f.vreg(c);
let ps = v(&mut f, Cls::I);
let (a, b, c, d) = (v(&mut f, Cls::I), v(&mut f, Cls::F32), v(&mut f, Cls::I), v(&mut f, Cls::I));
push(&mut f, 0, Inst::SlotAddr(ps, s));
push(&mut f, 0, Inst::Load(Mem::Int(8, false), a, p, 0));
push(&mut f, 0, Inst::Load(Mem::F32, b, p, 8));
push(&mut f, 0, Inst::Store(Mem::Int(8, false), ps, 0, a));
match between {
Between::Nothing => {}
Between::StoreSrc => { let k = v(&mut f, Cls::I); push(&mut f, 0, Inst::Iconst(k, 5)); push(&mut f, 0, Inst::Store(Mem::Int(4, false), p, 12, k)); }
Between::LoadDst => { let k = v(&mut f, Cls::I); push(&mut f, 0, Inst::Load(Mem::Int(8, false), k, ps, 0)); push(&mut f, 0, Inst::Store(Mem::Int(8, false), p, 32, k)); }
Between::Call => push(&mut f, 0, Inst::Call(Callee::Fn(9), vec![], vec![])),
}
push(&mut f, 0, Inst::Store(Mem::F32, ps, 8, b));
push(&mut f, 0, Inst::Load(Mem::Int(4, true), c, p, 12));
push(&mut f, 0, Inst::Store(Mem::Int(4, true), ps, 12, c));
push(&mut f, 0, Inst::Load(Mem::Int(8, false), d, p, 16));
push(&mut f, 0, Inst::Store(Mem::Int(8, false), ps, 16, d));
let (w0, w1, w2, x, y) = (v(&mut f, Cls::I), v(&mut f, Cls::I), v(&mut f, Cls::I), v(&mut f, Cls::I), v(&mut f, Cls::I));
push(&mut f, 0, Inst::Load(Mem::Int(8, false), w0, ps, 0));
push(&mut f, 0, Inst::Load(Mem::Int(8, false), w1, ps, 8));
push(&mut f, 0, Inst::Load(Mem::Int(8, false), w2, ps, 16));
push(&mut f, 0, Inst::IBinI(IOp::Mul, U64, x, w0, 3));
push(&mut f, 0, Inst::IBin(IOp::Add, U64, x, x, w1));
push(&mut f, 0, Inst::IBinI(IOp::Mul, U64, y, x, 5));
push(&mut f, 0, Inst::IBin(IOp::Add, U64, y, y, w2));
f.blocks[0].term = Term::Ret(vec![y]);
f
}
fn copies(f: &Func) -> usize {
f.blocks.iter().flat_map(|b| b.insts.iter()).filter(|i| matches!(i, Inst::Copy(..))).count()
}
#[test]
fn fields_copied_one_by_one_become_one_copy() {
let f = build(Between::Nothing);
let mut g = f.clone();
assert!(opt::merge_copy_runs(&mut g));
assert_eq!(copies(&g), 1, "{}", g.dump());
g.verify().unwrap();
let mut mem = [0x1122_3344_5566_7788u64, 0x4048_f5c3_0000_0007, 0x0102_0304_0506_0708, 0, 0];
let p = mem.as_mut_ptr() as u64;
assert_eq!(run(&f, p), run(&g, p));
}
#[test]
fn a_write_of_the_source_or_a_read_of_the_destination_in_between_keeps_the_fields() {
for b in [Between::StoreSrc, Between::LoadDst, Between::Call] {
let mut g = build(b);
// the fields after the intervening instruction may still form a run of their own (< 16 bytes here)
opt::merge_copy_runs(&mut g);
assert_eq!(copies(&g), 0, "{:?}:\n{}", b, g.dump());
}
}