59 lines
2.9 KiB
Rust
59 lines
2.9 KiB
Rust
//! A branch on `x == 0` at the start of a block reuses the flags of the ALU op that just wrote
|
|
//! x only when that op is in the same block: a block start is a jump target, and a predecessor
|
|
//! jumping in brings other flags. (shader_ir::ty's O3 code: the merge block of
|
|
//! HashMap::get's two results branched on the `add` of the found path while its None path
|
|
//! jumped in with ZF=0, and loaded through the null.)
|
|
#![cfg(target_arch = "x86_64")]
|
|
use relax_backend::rir::*;
|
|
use relax_backend::x64;
|
|
|
|
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(0);
|
|
}
|
|
|
|
/// f(x, take_add) = { v = take_add ? x + 12 : 0; if v == 0 { 111 } else { v } }, laid out so the
|
|
/// `add` block falls through into the branch-only merge block and the other path jumps in after
|
|
/// an `add` that leaves ZF clear.
|
|
fn merge_fn() -> Func {
|
|
let mut f = Func::new("flags_merge".to_string(), 0);
|
|
let (x, flag) = (f.vreg(Cls::I), f.vreg(Cls::I));
|
|
f.params = vec![x, flag];
|
|
f.rets = vec![Cls::I];
|
|
let (b0, b1, b2, b3, b4, b5) = (f.block(), f.block(), f.block(), f.block(), f.block(), f.block());
|
|
let (z, c0, w, v, c, k) = (f.vreg(Cls::I), f.vreg(Cls::I), f.vreg(Cls::I), f.vreg(Cls::I), f.vreg(Cls::I), f.vreg(Cls::I));
|
|
push(&mut f, b0, Inst::Iconst(z, 0));
|
|
push(&mut f, b0, Inst::ICmpI(Cond::Ne, false, c0, flag, 0));
|
|
f.blocks[b0 as usize].term = Term::Branch(c0, b2, b1);
|
|
// the None path: an add with a nonzero result (ZF clear), then v = 0 by a move
|
|
push(&mut f, b1, Inst::IBinI(IOp::Add, U64, w, x, 1));
|
|
push(&mut f, b1, Inst::Mov(v, z));
|
|
f.blocks[b1 as usize].term = Term::Jump(b3);
|
|
// the found path: v = x + 12, falling through into the merge block
|
|
push(&mut f, b2, Inst::IBinI(IOp::Add, U64, v, x, 12));
|
|
f.blocks[b2 as usize].term = Term::Jump(b3);
|
|
push(&mut f, b3, Inst::ICmpI(Cond::Eq, false, c, v, 0));
|
|
f.blocks[b3 as usize].term = Term::Branch(c, b4, b5);
|
|
push(&mut f, b4, Inst::Iconst(k, 111));
|
|
f.blocks[b4 as usize].term = Term::Ret(vec![k]);
|
|
f.blocks[b5 as usize].term = Term::Ret(vec![v]);
|
|
let _ = w;
|
|
f.verify().unwrap();
|
|
f
|
|
}
|
|
|
|
#[test]
|
|
fn branch_at_block_start_compares_again() {
|
|
let f = merge_fn();
|
|
let env = x64::Env { slots: relax_backend::rir::SlotsPtr::NONE, tls_key: 0, tls_slow: 0, resume: 0, opt_regs: false, syms: 0, sym_class: 0, near: false };
|
|
let c = x64::compile(&relax_session::Options::default(), &f, &env).unwrap();
|
|
assert!(c.relocs.is_empty());
|
|
let code = relax_backend::exec::Code::new(&c.code).unwrap();
|
|
let run: extern "sysv64" fn(u64, u64) -> u64 = unsafe { std::mem::transmute(code.ptr()) };
|
|
// the None path: v == 0 must take the branch whatever the flags it jumped in with
|
|
assert_eq!(run(5, 0), 111);
|
|
assert_eq!(run(5, 1), 17);
|
|
assert_eq!(run(u64::MAX - 11, 1), 111);
|
|
}
|