99 lines
4.8 KiB
Rust
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());
|
|
}
|
|
}
|