use rucc_base::Interner;
use rucc_mir::{Amode, Flags, Func, Inst, Opcode, Operand, Reg};
use rucc_target::MachineInsts;
use crate::changes::{Changes, Plan, Reads};
use crate::fold::Pending;
pub const WINDOW: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Fold {
pub from: &'static str,
pub into: &'static str,
pub load: &'static str,
pub swapped: Option<&'static str>,
}
pub static FOLDS: &[Fold] = &[
Fold { from: "add_rr_8", into: "add_rm_8", load: "mov_rm_8", swapped: Some("add_rm_8") },
Fold { from: "add_rr_16", into: "add_rm_16", load: "mov_rm_16", swapped: Some("add_rm_16") },
Fold { from: "add_rr_32", into: "add_rm_32", load: "mov_rm_32", swapped: Some("add_rm_32") },
Fold { from: "add_rr_64", into: "add_rm_64", load: "mov_rm_64", swapped: Some("add_rm_64") },
Fold { from: "sub_rr_8", into: "sub_rm_8", load: "mov_rm_8", swapped: None },
Fold { from: "sub_rr_16", into: "sub_rm_16", load: "mov_rm_16", swapped: None },
Fold { from: "sub_rr_32", into: "sub_rm_32", load: "mov_rm_32", swapped: None },
Fold { from: "sub_rr_64", into: "sub_rm_64", load: "mov_rm_64", swapped: None },
Fold { from: "and_rr_8", into: "and_rm_8", load: "mov_rm_8", swapped: Some("and_rm_8") },
Fold { from: "and_rr_16", into: "and_rm_16", load: "mov_rm_16", swapped: Some("and_rm_16") },
Fold { from: "and_rr_32", into: "and_rm_32", load: "mov_rm_32", swapped: Some("and_rm_32") },
Fold { from: "and_rr_64", into: "and_rm_64", load: "mov_rm_64", swapped: Some("and_rm_64") },
Fold { from: "or_rr_8", into: "or_rm_8", load: "mov_rm_8", swapped: Some("or_rm_8") },
Fold { from: "or_rr_16", into: "or_rm_16", load: "mov_rm_16", swapped: Some("or_rm_16") },
Fold { from: "or_rr_32", into: "or_rm_32", load: "mov_rm_32", swapped: Some("or_rm_32") },
Fold { from: "or_rr_64", into: "or_rm_64", load: "mov_rm_64", swapped: Some("or_rm_64") },
Fold { from: "xor_rr_8", into: "xor_rm_8", load: "mov_rm_8", swapped: Some("xor_rm_8") },
Fold { from: "xor_rr_16", into: "xor_rm_16", load: "mov_rm_16", swapped: Some("xor_rm_16") },
Fold { from: "xor_rr_32", into: "xor_rm_32", load: "mov_rm_32", swapped: Some("xor_rm_32") },
Fold { from: "xor_rr_64", into: "xor_rm_64", load: "mov_rm_64", swapped: Some("xor_rm_64") },
Fold { from: "imul_rr_16", into: "imul_rm_16", load: "mov_rm_16", swapped: Some("imul_rm_16") },
Fold { from: "imul_rr_32", into: "imul_rm_32", load: "mov_rm_32", swapped: Some("imul_rm_32") },
Fold { from: "imul_rr_64", into: "imul_rm_64", load: "mov_rm_64", swapped: Some("imul_rm_64") },
Fold {
from: "cmp_set_e_8",
into: "cmp_set_e_rm_8",
load: "mov_rm_8",
swapped: Some("cmp_set_e_rm_8"),
},
Fold {
from: "cmp_set_e_16",
into: "cmp_set_e_rm_16",
load: "mov_rm_16",
swapped: Some("cmp_set_e_rm_16"),
},
Fold {
from: "cmp_set_e_32",
into: "cmp_set_e_rm_32",
load: "mov_rm_32",
swapped: Some("cmp_set_e_rm_32"),
},
Fold {
from: "cmp_set_e_64",
into: "cmp_set_e_rm_64",
load: "mov_rm_64",
swapped: Some("cmp_set_e_rm_64"),
},
Fold {
from: "cmp_set_ne_8",
into: "cmp_set_ne_rm_8",
load: "mov_rm_8",
swapped: Some("cmp_set_ne_rm_8"),
},
Fold {
from: "cmp_set_ne_16",
into: "cmp_set_ne_rm_16",
load: "mov_rm_16",
swapped: Some("cmp_set_ne_rm_16"),
},
Fold {
from: "cmp_set_ne_32",
into: "cmp_set_ne_rm_32",
load: "mov_rm_32",
swapped: Some("cmp_set_ne_rm_32"),
},
Fold {
from: "cmp_set_ne_64",
into: "cmp_set_ne_rm_64",
load: "mov_rm_64",
swapped: Some("cmp_set_ne_rm_64"),
},
Fold {
from: "cmp_set_l_8",
into: "cmp_set_l_rm_8",
load: "mov_rm_8",
swapped: Some("cmp_set_g_rm_8"),
},
Fold {
from: "cmp_set_l_16",
into: "cmp_set_l_rm_16",
load: "mov_rm_16",
swapped: Some("cmp_set_g_rm_16"),
},
Fold {
from: "cmp_set_l_32",
into: "cmp_set_l_rm_32",
load: "mov_rm_32",
swapped: Some("cmp_set_g_rm_32"),
},
Fold {
from: "cmp_set_l_64",
into: "cmp_set_l_rm_64",
load: "mov_rm_64",
swapped: Some("cmp_set_g_rm_64"),
},
Fold {
from: "cmp_set_le_8",
into: "cmp_set_le_rm_8",
load: "mov_rm_8",
swapped: Some("cmp_set_ge_rm_8"),
},
Fold {
from: "cmp_set_le_16",
into: "cmp_set_le_rm_16",
load: "mov_rm_16",
swapped: Some("cmp_set_ge_rm_16"),
},
Fold {
from: "cmp_set_le_32",
into: "cmp_set_le_rm_32",
load: "mov_rm_32",
swapped: Some("cmp_set_ge_rm_32"),
},
Fold {
from: "cmp_set_le_64",
into: "cmp_set_le_rm_64",
load: "mov_rm_64",
swapped: Some("cmp_set_ge_rm_64"),
},
Fold {
from: "cmp_set_g_8",
into: "cmp_set_g_rm_8",
load: "mov_rm_8",
swapped: Some("cmp_set_l_rm_8"),
},
Fold {
from: "cmp_set_g_16",
into: "cmp_set_g_rm_16",
load: "mov_rm_16",
swapped: Some("cmp_set_l_rm_16"),
},
Fold {
from: "cmp_set_g_32",
into: "cmp_set_g_rm_32",
load: "mov_rm_32",
swapped: Some("cmp_set_l_rm_32"),
},
Fold {
from: "cmp_set_g_64",
into: "cmp_set_g_rm_64",
load: "mov_rm_64",
swapped: Some("cmp_set_l_rm_64"),
},
Fold {
from: "cmp_set_ge_8",
into: "cmp_set_ge_rm_8",
load: "mov_rm_8",
swapped: Some("cmp_set_le_rm_8"),
},
Fold {
from: "cmp_set_ge_16",
into: "cmp_set_ge_rm_16",
load: "mov_rm_16",
swapped: Some("cmp_set_le_rm_16"),
},
Fold {
from: "cmp_set_ge_32",
into: "cmp_set_ge_rm_32",
load: "mov_rm_32",
swapped: Some("cmp_set_le_rm_32"),
},
Fold {
from: "cmp_set_ge_64",
into: "cmp_set_ge_rm_64",
load: "mov_rm_64",
swapped: Some("cmp_set_le_rm_64"),
},
Fold {
from: "cmp_set_b_8",
into: "cmp_set_b_rm_8",
load: "mov_rm_8",
swapped: Some("cmp_set_a_rm_8"),
},
Fold {
from: "cmp_set_b_16",
into: "cmp_set_b_rm_16",
load: "mov_rm_16",
swapped: Some("cmp_set_a_rm_16"),
},
Fold {
from: "cmp_set_b_32",
into: "cmp_set_b_rm_32",
load: "mov_rm_32",
swapped: Some("cmp_set_a_rm_32"),
},
Fold {
from: "cmp_set_b_64",
into: "cmp_set_b_rm_64",
load: "mov_rm_64",
swapped: Some("cmp_set_a_rm_64"),
},
Fold {
from: "cmp_set_be_8",
into: "cmp_set_be_rm_8",
load: "mov_rm_8",
swapped: Some("cmp_set_ae_rm_8"),
},
Fold {
from: "cmp_set_be_16",
into: "cmp_set_be_rm_16",
load: "mov_rm_16",
swapped: Some("cmp_set_ae_rm_16"),
},
Fold {
from: "cmp_set_be_32",
into: "cmp_set_be_rm_32",
load: "mov_rm_32",
swapped: Some("cmp_set_ae_rm_32"),
},
Fold {
from: "cmp_set_be_64",
into: "cmp_set_be_rm_64",
load: "mov_rm_64",
swapped: Some("cmp_set_ae_rm_64"),
},
Fold {
from: "cmp_set_a_8",
into: "cmp_set_a_rm_8",
load: "mov_rm_8",
swapped: Some("cmp_set_b_rm_8"),
},
Fold {
from: "cmp_set_a_16",
into: "cmp_set_a_rm_16",
load: "mov_rm_16",
swapped: Some("cmp_set_b_rm_16"),
},
Fold {
from: "cmp_set_a_32",
into: "cmp_set_a_rm_32",
load: "mov_rm_32",
swapped: Some("cmp_set_b_rm_32"),
},
Fold {
from: "cmp_set_a_64",
into: "cmp_set_a_rm_64",
load: "mov_rm_64",
swapped: Some("cmp_set_b_rm_64"),
},
Fold {
from: "cmp_set_ae_8",
into: "cmp_set_ae_rm_8",
load: "mov_rm_8",
swapped: Some("cmp_set_be_rm_8"),
},
Fold {
from: "cmp_set_ae_16",
into: "cmp_set_ae_rm_16",
load: "mov_rm_16",
swapped: Some("cmp_set_be_rm_16"),
},
Fold {
from: "cmp_set_ae_32",
into: "cmp_set_ae_rm_32",
load: "mov_rm_32",
swapped: Some("cmp_set_be_rm_32"),
},
Fold {
from: "cmp_set_ae_64",
into: "cmp_set_ae_rm_64",
load: "mov_rm_64",
swapped: Some("cmp_set_be_rm_64"),
},
Fold { from: "cmp_set_e_ri_8", into: "cmp_set_e_mi_8", load: "mov_rm_8", swapped: None },
Fold { from: "cmp_set_e_ri_16", into: "cmp_set_e_mi_16", load: "mov_rm_16", swapped: None },
Fold { from: "cmp_set_e_ri_32", into: "cmp_set_e_mi_32", load: "mov_rm_32", swapped: None },
Fold { from: "cmp_set_e_ri_64", into: "cmp_set_e_mi_64", load: "mov_rm_64", swapped: None },
Fold { from: "cmp_set_ne_ri_8", into: "cmp_set_ne_mi_8", load: "mov_rm_8", swapped: None },
Fold { from: "cmp_set_ne_ri_16", into: "cmp_set_ne_mi_16", load: "mov_rm_16", swapped: None },
Fold { from: "cmp_set_ne_ri_32", into: "cmp_set_ne_mi_32", load: "mov_rm_32", swapped: None },
Fold { from: "cmp_set_ne_ri_64", into: "cmp_set_ne_mi_64", load: "mov_rm_64", swapped: None },
Fold { from: "cmp_set_l_ri_8", into: "cmp_set_l_mi_8", load: "mov_rm_8", swapped: None },
Fold { from: "cmp_set_l_ri_16", into: "cmp_set_l_mi_16", load: "mov_rm_16", swapped: None },
Fold { from: "cmp_set_l_ri_32", into: "cmp_set_l_mi_32", load: "mov_rm_32", swapped: None },
Fold { from: "cmp_set_l_ri_64", into: "cmp_set_l_mi_64", load: "mov_rm_64", swapped: None },
Fold { from: "cmp_set_le_ri_8", into: "cmp_set_le_mi_8", load: "mov_rm_8", swapped: None },
Fold { from: "cmp_set_le_ri_16", into: "cmp_set_le_mi_16", load: "mov_rm_16", swapped: None },
Fold { from: "cmp_set_le_ri_32", into: "cmp_set_le_mi_32", load: "mov_rm_32", swapped: None },
Fold { from: "cmp_set_le_ri_64", into: "cmp_set_le_mi_64", load: "mov_rm_64", swapped: None },
Fold { from: "cmp_set_g_ri_8", into: "cmp_set_g_mi_8", load: "mov_rm_8", swapped: None },
Fold { from: "cmp_set_g_ri_16", into: "cmp_set_g_mi_16", load: "mov_rm_16", swapped: None },
Fold { from: "cmp_set_g_ri_32", into: "cmp_set_g_mi_32", load: "mov_rm_32", swapped: None },
Fold { from: "cmp_set_g_ri_64", into: "cmp_set_g_mi_64", load: "mov_rm_64", swapped: None },
Fold { from: "cmp_set_ge_ri_8", into: "cmp_set_ge_mi_8", load: "mov_rm_8", swapped: None },
Fold { from: "cmp_set_ge_ri_16", into: "cmp_set_ge_mi_16", load: "mov_rm_16", swapped: None },
Fold { from: "cmp_set_ge_ri_32", into: "cmp_set_ge_mi_32", load: "mov_rm_32", swapped: None },
Fold { from: "cmp_set_ge_ri_64", into: "cmp_set_ge_mi_64", load: "mov_rm_64", swapped: None },
Fold { from: "cmp_set_b_ri_8", into: "cmp_set_b_mi_8", load: "mov_rm_8", swapped: None },
Fold { from: "cmp_set_b_ri_16", into: "cmp_set_b_mi_16", load: "mov_rm_16", swapped: None },
Fold { from: "cmp_set_b_ri_32", into: "cmp_set_b_mi_32", load: "mov_rm_32", swapped: None },
Fold { from: "cmp_set_b_ri_64", into: "cmp_set_b_mi_64", load: "mov_rm_64", swapped: None },
Fold { from: "cmp_set_be_ri_8", into: "cmp_set_be_mi_8", load: "mov_rm_8", swapped: None },
Fold { from: "cmp_set_be_ri_16", into: "cmp_set_be_mi_16", load: "mov_rm_16", swapped: None },
Fold { from: "cmp_set_be_ri_32", into: "cmp_set_be_mi_32", load: "mov_rm_32", swapped: None },
Fold { from: "cmp_set_be_ri_64", into: "cmp_set_be_mi_64", load: "mov_rm_64", swapped: None },
Fold { from: "cmp_set_a_ri_8", into: "cmp_set_a_mi_8", load: "mov_rm_8", swapped: None },
Fold { from: "cmp_set_a_ri_16", into: "cmp_set_a_mi_16", load: "mov_rm_16", swapped: None },
Fold { from: "cmp_set_a_ri_32", into: "cmp_set_a_mi_32", load: "mov_rm_32", swapped: None },
Fold { from: "cmp_set_a_ri_64", into: "cmp_set_a_mi_64", load: "mov_rm_64", swapped: None },
Fold { from: "cmp_set_ae_ri_8", into: "cmp_set_ae_mi_8", load: "mov_rm_8", swapped: None },
Fold { from: "cmp_set_ae_ri_16", into: "cmp_set_ae_mi_16", load: "mov_rm_16", swapped: None },
Fold { from: "cmp_set_ae_ri_32", into: "cmp_set_ae_mi_32", load: "mov_rm_32", swapped: None },
Fold { from: "cmp_set_ae_ri_64", into: "cmp_set_ae_mi_64", load: "mov_rm_64", swapped: None },
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Update {
pub from: &'static str,
pub into: &'static str,
pub load: &'static str,
pub store: &'static str,
pub commutes: bool,
}
pub static UPDATES: &[Update] = &[
Update {
from: "add_rr_8",
into: "add_mr_8",
load: "mov_rm_8",
store: "mov_mr_8",
commutes: true,
},
Update {
from: "add_rr_16",
into: "add_mr_16",
load: "mov_rm_16",
store: "mov_mr_16",
commutes: true,
},
Update {
from: "add_rr_32",
into: "add_mr_32",
load: "mov_rm_32",
store: "mov_mr_32",
commutes: true,
},
Update {
from: "add_rr_64",
into: "add_mr_64",
load: "mov_rm_64",
store: "mov_mr_64",
commutes: true,
},
Update {
from: "sub_rr_8",
into: "sub_mr_8",
load: "mov_rm_8",
store: "mov_mr_8",
commutes: false,
},
Update {
from: "sub_rr_16",
into: "sub_mr_16",
load: "mov_rm_16",
store: "mov_mr_16",
commutes: false,
},
Update {
from: "sub_rr_32",
into: "sub_mr_32",
load: "mov_rm_32",
store: "mov_mr_32",
commutes: false,
},
Update {
from: "sub_rr_64",
into: "sub_mr_64",
load: "mov_rm_64",
store: "mov_mr_64",
commutes: false,
},
Update {
from: "and_rr_8",
into: "and_mr_8",
load: "mov_rm_8",
store: "mov_mr_8",
commutes: true,
},
Update {
from: "and_rr_16",
into: "and_mr_16",
load: "mov_rm_16",
store: "mov_mr_16",
commutes: true,
},
Update {
from: "and_rr_32",
into: "and_mr_32",
load: "mov_rm_32",
store: "mov_mr_32",
commutes: true,
},
Update {
from: "and_rr_64",
into: "and_mr_64",
load: "mov_rm_64",
store: "mov_mr_64",
commutes: true,
},
Update {
from: "or_rr_8",
into: "or_mr_8",
load: "mov_rm_8",
store: "mov_mr_8",
commutes: true,
},
Update {
from: "or_rr_16",
into: "or_mr_16",
load: "mov_rm_16",
store: "mov_mr_16",
commutes: true,
},
Update {
from: "or_rr_32",
into: "or_mr_32",
load: "mov_rm_32",
store: "mov_mr_32",
commutes: true,
},
Update {
from: "or_rr_64",
into: "or_mr_64",
load: "mov_rm_64",
store: "mov_mr_64",
commutes: true,
},
Update {
from: "xor_rr_8",
into: "xor_mr_8",
load: "mov_rm_8",
store: "mov_mr_8",
commutes: true,
},
Update {
from: "xor_rr_16",
into: "xor_mr_16",
load: "mov_rm_16",
store: "mov_mr_16",
commutes: true,
},
Update {
from: "xor_rr_32",
into: "xor_mr_32",
load: "mov_rm_32",
store: "mov_mr_32",
commutes: true,
},
Update {
from: "xor_rr_64",
into: "xor_mr_64",
load: "mov_rm_64",
store: "mov_mr_64",
commutes: true,
},
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Bump {
pub from: &'static str,
pub into: &'static str,
pub load: &'static str,
pub store: &'static str,
}
pub static BUMPS: &[Bump] = &[
Bump { from: "add_ri_8", into: "add_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
Bump { from: "add_ri_16", into: "add_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
Bump { from: "add_ri_32", into: "add_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
Bump { from: "add_ri_64", into: "add_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
Bump { from: "sub_ri_8", into: "sub_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
Bump { from: "sub_ri_16", into: "sub_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
Bump { from: "sub_ri_32", into: "sub_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
Bump { from: "sub_ri_64", into: "sub_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
Bump { from: "and_ri_8", into: "and_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
Bump { from: "and_ri_16", into: "and_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
Bump { from: "and_ri_32", into: "and_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
Bump { from: "and_ri_64", into: "and_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
Bump { from: "or_ri_8", into: "or_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
Bump { from: "or_ri_16", into: "or_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
Bump { from: "or_ri_32", into: "or_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
Bump { from: "or_ri_64", into: "or_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
Bump { from: "xor_ri_8", into: "xor_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
Bump { from: "xor_ri_16", into: "xor_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
Bump { from: "xor_ri_32", into: "xor_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
Bump { from: "xor_ri_64", into: "xor_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
];
#[derive(Debug, Clone, Copy)]
struct Waiting {
inst: Inst,
reg: Reg,
load: &'static str,
at: usize,
}
pub fn loads(
func: &mut Func,
machine: &MachineInsts,
names: &mut Interner,
pending: &mut Pending<'_>,
) -> usize {
let mut reads = Reads::of(func);
let mut done = 0;
for block in func.blocks().collect::<Vec<_>>() {
let mut waiting: Option<Waiting> = None;
for (at, inst) in func.insts(block).collect::<Vec<_>>().into_iter().enumerate() {
let name = names.resolve(func[inst].opcode.name()).to_owned();
let bare = machine.bare(&name).to_owned();
let barrier = machine.calls(&name) || !machine.has(&name) || machine.touches_mem(&name);
if let Some(carried) = waiting {
if let Some(plan) = joined(func, &reads, carried, machine, names, inst, &bare) {
let mut set = Changes::new();
set.rewrite(inst, plan);
set.remove(carried.inst);
if set.commit(func, &mut reads, names, machine).is_ok() {
pending.moved(carried.inst, &[inst]);
waiting = None;
done += 1;
}
}
}
if barrier {
waiting = None;
}
if let Some(carried) = waiting {
if at - carried.at >= WINDOW || writes_what_it_reads(func, inst, &carried) {
waiting = None;
}
}
if insisted(func, inst) {
continue;
}
if let Some(load) = FOLDS.iter().find(|fold| fold.load == bare).map(|fold| fold.load) {
let operands = &func[func[inst].operands];
if let Some(first) = operands.first().filter(|operand| operand.role.is_def()) {
waiting = Some(Waiting { inst, reg: first.reg, load, at });
}
}
}
}
done
}
#[derive(Debug, Clone, Copy)]
struct Run {
load: Inst,
alu: Inst,
store: Inst,
update: &'static Update,
kept: Operand,
}
#[derive(Debug, Clone, Copy)]
struct Bumped {
load: Inst,
alu: Inst,
store: Inst,
bump: &'static Bump,
imm: i64,
}
pub fn stores(
func: &mut Func,
machine: &MachineInsts,
names: &mut Interner,
pending: &mut Pending<'_>,
) -> usize {
let mut reads = Reads::of(func);
let mut done = 0;
for block in func.blocks().collect::<Vec<_>>() {
let insts: Vec<Inst> = func.insts(block).collect();
for at in 0..insts.len() {
let found = match run(func, &reads, machine, names, &insts, at) {
Some(found) => Some((
found.load,
found.alu,
found.store,
updated(func, machine, names, &found),
)),
None => constant(func, &reads, machine, names, &insts, at).map(|found| {
(found.load, found.alu, found.store, bumped(func, machine, names, &found))
}),
};
let Some((load, alu, store, plan)) = found else { continue };
if !pending.alike(load, store) {
continue;
}
let mut set = Changes::new();
set.rewrite(store, plan);
set.remove(alu);
set.remove(load);
if set.commit(func, &mut reads, names, machine).is_ok() {
pending.moved(load, &[]);
done += 1;
}
}
}
done
}
fn run(
func: &Func,
reads: &Reads,
machine: &MachineInsts,
names: &Interner,
insts: &[Inst],
at: usize,
) -> Option<Run> {
let store = insts[at];
if insisted(func, store) {
return None;
}
let stored = machine.bare(names.resolve(func[store].opcode.name())).to_owned();
let value = *func[func[store].operands].first()?;
if value.role.is_def() || reads.count(value.reg) != 1 {
return None;
}
let earliest = at.saturating_sub(WINDOW);
let alu = (earliest..at).rev().find(|&k| writes(func, insts[k], value.reg))?;
let bare = machine.bare(names.resolve(func[insts[alu]].opcode.name())).to_owned();
let update = UPDATES.iter().find(|row| row.from == bare && row.store == stored)?;
let operands = func[func[insts[alu]].operands].to_vec();
let [_, first, second] = operands[..] else { return None };
let both = [(first, second), (second, first)];
let tried = if update.commutes { &both[..] } else { &both[..1] };
for &(source, kept) in tried {
if reads.count(source.reg) != 1 {
continue;
}
let Some(from) = (earliest..alu).rev().find(|&k| writes(func, insts[k], source.reg)) else {
continue;
};
let load = insts[from];
if insisted(func, load) {
continue;
}
if machine.bare(names.resolve(func[load].opcode.name())) != update.load {
continue;
}
if !same_place(func, load, store) {
continue;
}
let mut wanted: Vec<Reg> =
func[func[store].operands][1..].iter().map(|operand| operand.reg).collect();
wanted.push(kept.reg);
if !clear(func, machine, names, insts, (from, at), &wanted) {
continue;
}
return Some(Run { load, alu: insts[alu], store, update, kept });
}
None
}
fn constant(
func: &Func,
reads: &Reads,
machine: &MachineInsts,
names: &Interner,
insts: &[Inst],
at: usize,
) -> Option<Bumped> {
let store = insts[at];
if insisted(func, store) {
return None;
}
let stored = machine.bare(names.resolve(func[store].opcode.name())).to_owned();
let value = *func[func[store].operands].first()?;
if value.role.is_def() || reads.count(value.reg) != 1 {
return None;
}
let mem = func[func[store].mem?];
if mem.base == Some(0) || mem.index == Some(0) {
return None;
}
let earliest = at.saturating_sub(WINDOW);
let alu = (earliest..at).rev().find(|&k| writes(func, insts[k], value.reg))?;
let bare = machine.bare(names.resolve(func[insts[alu]].opcode.name())).to_owned();
let bump = BUMPS.iter().find(|row| row.from == bare && row.store == stored)?;
let operands = func[func[insts[alu]].operands].to_vec();
let [_, source] = operands[..] else { return None };
let imm = func[func[insts[alu]].imm?].0;
if reads.count(source.reg) != 1 {
return None;
}
let from = (earliest..alu).rev().find(|&k| writes(func, insts[k], source.reg))?;
let load = insts[from];
if insisted(func, load) {
return None;
}
if machine.bare(names.resolve(func[load].opcode.name())) != bump.load {
return None;
}
if !same_place(func, load, store) {
return None;
}
let wanted: Vec<Reg> =
func[func[store].operands][1..].iter().map(|operand| operand.reg).collect();
if !clear(func, machine, names, insts, (from, at), &wanted) {
return None;
}
Some(Bumped { load, alu: insts[alu], store, bump, imm })
}
fn insisted(func: &Func, inst: Inst) -> bool {
func[inst].flags.contains(Flags::VOLATILE)
}
fn writes(func: &Func, inst: Inst, reg: Reg) -> bool {
func[func[inst].operands].iter().any(|operand| operand.role.is_def() && operand.reg == reg)
}
fn same_place(func: &Func, one: Inst, other: Inst) -> bool {
let (Some(here), Some(there)) = (func[one].mem, func[other].mem) else { return false };
let (here, there) = (func[here], func[there]);
if func[one].symbol != func[other].symbol {
return false;
}
let bare = |amode: Amode| Amode { base: None, index: None, ..amode };
if bare(here) != bare(there) {
return false;
}
let same = |left: Option<u8>, right: Option<u8>| match (left, right) {
(None, None) => true,
(Some(left), Some(right)) => {
func[func[one].operands][usize::from(left)].reg
== func[func[other].operands][usize::from(right)].reg
}
_ => false,
};
same(here.base, there.base) && same(here.index, there.index)
}
fn clear(
func: &Func,
machine: &MachineInsts,
names: &Interner,
insts: &[Inst],
span: (usize, usize),
wanted: &[Reg],
) -> bool {
let (from, to) = span;
insts[from + 1..to].iter().all(|&inst| {
let name = names.resolve(func[inst].opcode.name());
if machine.calls(name) || !machine.has(name) || machine.touches_mem(name) {
return false;
}
!func[func[inst].operands]
.iter()
.any(|operand| operand.role.is_def() && wanted.contains(&operand.reg))
})
}
fn updated(func: &Func, machine: &MachineInsts, names: &mut Interner, run: &Run) -> Plan {
let operands = func[func[run.store].operands].to_vec();
let into = names.intern(&format!("{}{}", machine.prefix, run.update.into));
Plan {
opcode: Opcode::new(into),
operands: [run.kept].into_iter().chain(operands[1..].iter().copied()).collect(),
imm: None,
amode: func[run.store].mem.map(|mem| func[mem]),
symbol: func[run.store].symbol,
}
}
fn bumped(func: &Func, machine: &MachineInsts, names: &mut Interner, run: &Bumped) -> Plan {
let operands = func[func[run.store].operands][1..].to_vec();
let into = names.intern(&format!("{}{}", machine.prefix, run.bump.into));
let back = |at: Option<u8>| at.map(|at| at - 1);
Plan {
opcode: Opcode::new(into),
operands,
imm: Some(run.imm),
amode: func[run.store].mem.map(|mem| {
let mem = func[mem];
Amode { base: back(mem.base), index: back(mem.index), ..mem }
}),
symbol: func[run.store].symbol,
}
}
fn writes_what_it_reads(func: &Func, inst: Inst, carried: &Waiting) -> bool {
let written: Vec<Reg> = func[func[inst].operands]
.iter()
.filter(|operand| operand.role.is_def())
.map(|operand| operand.reg)
.collect();
func[func[carried.inst].operands].iter().any(|operand| written.contains(&operand.reg))
}
fn joined(
func: &Func,
reads: &Reads,
carried: Waiting,
machine: &MachineInsts,
names: &mut Interner,
inst: Inst,
bare: &str,
) -> Option<Plan> {
let fold = FOLDS.iter().find(|fold| fold.from == bare)?;
if carried.load != fold.load || reads.count(carried.reg) != 1 {
return None;
}
let operands = func[func[inst].operands].to_vec();
let (front, into) = match operands[..] {
[answer, first, second] => {
let (kept, into) = if second.reg == carried.reg {
(first, fold.into)
} else if first.reg == carried.reg {
(second, fold.swapped?)
} else {
return None;
};
(vec![answer, kept], into)
}
[answer, only] if only.reg == carried.reg => (vec![answer], fold.into),
_ => return None,
};
let load = carried.inst;
let address = func[func[load].operands][1..].to_vec();
let mut amode = func[func[load].mem?];
let along = u8::try_from(front.len() - 1).expect("a handful of operands");
amode.base = amode.base.map(|at| at + along);
amode.index = amode.index.map(|at| at + along);
let into = names.intern(&format!("{}{}", machine.prefix, into));
Some(Plan {
opcode: Opcode::new(into),
operands: front.into_iter().chain(address).collect(),
imm: func[inst].imm.map(|at| func[at].0),
amode: Some(amode),
symbol: func[load].symbol,
})
}
#[cfg(test)]
mod tests {
use rucc_mir::{self as mir, Constraint, Mem, Operand};
use rucc_target::x86_64::{GPR, MACHINE};
use super::*;
fn empty() -> (Interner, Func, mir::Block) {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let block = func.create_block();
(names, func, block)
}
fn op(names: &mut Interner, name: &str) -> Opcode {
Opcode::new(names.intern(&format!("{}{name}", MACHINE.prefix)))
}
fn load(func: &mut Func, names: &mut Interner, block: mir::Block, base: Reg) -> Reg {
let into = func.new_vreg(GPR);
let mov = op(names, "mov_rm_64");
func.build(block, mov)
.def(into, GPR)
.mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
.finish();
into
}
fn insisted_load(func: &mut Func, names: &mut Interner, block: mir::Block, base: Reg) -> Reg {
let into = func.new_vreg(GPR);
let mov = op(names, "mov_rm_64");
func.build(block, mov)
.def(into, GPR)
.mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
.flags(Flags::VOLATILE)
.finish();
into
}
fn alu(
func: &mut Func,
names: &mut Interner,
block: mir::Block,
name: &str,
first: Reg,
second: Reg,
) -> Reg {
let answer = func.new_vreg(GPR);
let opcode = op(names, name);
func.build(block, opcode)
.operand(Operand::write(answer, GPR).with(Constraint::Reuse(1)))
.uses(first, GPR)
.uses(second, GPR)
.finish();
answer
}
fn compare(
func: &mut Func,
names: &mut Interner,
block: mir::Block,
name: &str,
first: Reg,
second: Reg,
) -> Reg {
let byte = func.new_vreg(GPR);
let opcode = op(names, name);
func.build(block, opcode).def(byte, GPR).uses(first, GPR).uses(second, GPR).finish();
byte
}
fn shape(func: &Func, names: &Interner, block: mir::Block) -> Vec<String> {
func.insts(block).map(|inst| names.resolve(func[inst].opcode.name()).to_owned()).collect()
}
fn combine(func: &mut Func, names: &mut Interner) -> usize {
let mut addresses = Vec::new();
let mut arguments = Vec::new();
let mut dynamic = Vec::new();
let mut pending =
Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
loads(func, &MACHINE, names, &mut pending)
}
fn store(func: &mut Func, names: &mut Interner, block: mir::Block, base: Reg, value: Reg) {
let mov = op(names, "mov_mr_64");
func.build(block, mov)
.uses(value, GPR)
.mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
.finish();
}
fn insisted_store(
func: &mut Func,
names: &mut Interner,
block: mir::Block,
base: Reg,
value: Reg,
) {
let mov = op(names, "mov_mr_64");
func.build(block, mov)
.uses(value, GPR)
.mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
.flags(Flags::VOLATILE)
.finish();
}
fn update(func: &mut Func, names: &mut Interner) -> usize {
let mut addresses = Vec::new();
let mut arguments = Vec::new();
let mut dynamic = Vec::new();
let mut pending =
Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
stores(func, &MACHINE, names, &mut pending)
}
#[test]
fn a_word_read_changed_and_written_back_becomes_one_instruction() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.add_mr_64"]);
let inst = func.insts(block).next().expect("the addition");
let mem = func[inst].mem.expect("it writes memory");
assert_eq!(func[mem].disp, 16, "the address came from the store");
assert_eq!(func[mem].base, Some(1), "and names the operand behind the source");
assert_eq!(func[func[inst].operands].len(), 2, "one source and the base of the address");
assert_eq!(func[func[inst].operands][0].reg, other, "the source it kept");
assert_eq!(func[func[inst].operands][1].reg, base, "the address");
}
#[test]
fn a_word_read_into_the_right_source_of_an_addition_is_still_one_instruction() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu(&mut func, &mut names, block, "add_rr_64", other, word);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.add_mr_64"]);
assert_eq!(func[func[func.insts(block).next().expect("it")].operands][0].reg, other);
}
#[test]
fn a_subtraction_taking_a_register_away_from_memory_becomes_one_instruction() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let left = alu(&mut func, &mut names, block, "sub_rr_64", word, other);
store(&mut func, &mut names, block, base, left);
assert_eq!(update(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.sub_mr_64"]);
}
#[test]
fn a_subtraction_taking_memory_away_from_a_register_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let left = alu(&mut func, &mut names, block, "sub_rr_64", other, word);
store(&mut func, &mut names, block, base, left);
assert_eq!(update(&mut func, &mut names), 0);
assert_eq!(
shape(&func, &names, block),
["x64.mov_rm_64", "x64.sub_rr_64", "x64.mov_mr_64"]
);
}
#[test]
fn a_store_to_another_address_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let elsewhere = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
store(&mut func, &mut names, block, elsewhere, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_store_at_another_displacement_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
let mov = op(&mut names, "mov_mr_64");
func.build(block, mov)
.uses(sum, GPR)
.mem(Mem { disp: 24, ..Mem::at(Operand::read(base, GPR)) })
.finish();
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_word_two_instructions_read_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
alu(&mut func, &mut names, block, "xor_rr_64", word, other);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn an_answer_something_else_reads_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
store(&mut func, &mut names, block, base, sum);
alu(&mut func, &mut names, block, "xor_rr_64", sum, other);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_run_with_another_access_in_the_middle_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
load(&mut func, &mut names, block, other);
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_run_whose_address_register_is_written_in_the_middle_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RSP);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sub = op(&mut names, "sub_ri_64");
func.build(block, sub)
.operand(Operand::write(base, GPR).with(Constraint::Reuse(1)))
.uses(base, GPR)
.imm(32)
.finish();
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn two_locals_the_layout_has_not_placed_yet_are_not_the_same_place() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RSP);
let other = func.new_vreg(GPR);
let mov = op(&mut names, "mov_rm_64");
let word = func.new_vreg(GPR);
func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let read = func.insts(block).next().expect("the load");
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
let put = op(&mut names, "mov_mr_64");
func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let written = func.insts(block).nth(2).expect("the store");
let mut addresses = vec![(read, 3usize), (written, 4usize)];
let mut arguments = Vec::new();
let mut dynamic = Vec::new();
let mut pending =
Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 0);
}
#[test]
fn the_frame_entry_of_a_load_that_goes_comes_off_the_list() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RSP);
let other = func.new_vreg(GPR);
let mov = op(&mut names, "mov_rm_64");
let word = func.new_vreg(GPR);
func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let read = func.insts(block).next().expect("the load");
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
let put = op(&mut names, "mov_mr_64");
func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let written = func.insts(block).nth(2).expect("the store");
let mut addresses = vec![(read, 3usize), (written, 3usize)];
let mut arguments = Vec::new();
let mut dynamic = Vec::new();
let mut pending =
Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 1);
let inst = func.insts(block).next().expect("the addition");
assert_eq!(addresses, [(inst, 3usize)], "one entry, on the instruction that is left");
}
#[test]
fn a_run_whose_widths_disagree_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let into = func.new_vreg(GPR);
let narrow = op(&mut names, "mov_rm_32");
func.build(block, narrow)
.def(into, GPR)
.mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
.finish();
let sum = alu(&mut func, &mut names, block, "add_rr_64", into, other);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn every_row_of_the_update_table_is_four_instructions_this_target_has() {
for update in UPDATES {
for name in [update.from, update.into, update.load, update.store] {
assert!(MACHINE.has(name), "{name} is not an instruction");
}
let width = |name: &str| name.rsplit_once('_').map(|(_, width)| width.to_owned());
assert_eq!(width(update.from), width(update.into), "{} changes width", update.from);
assert_eq!(
width(update.from),
width(update.load),
"{} loads another width",
update.from
);
assert_eq!(
width(update.from),
width(update.store),
"{} stores another width",
update.from
);
assert!((MACHINE.takes_mem)(update.into), "{} reaches no memory", update.into);
assert!(!(MACHINE.takes_mem)(update.from), "{} already reaches memory", update.from);
}
}
#[test]
fn the_update_table_covers_the_arithmetic_this_target_can_do_in_place() {
assert_eq!(UPDATES.len(), 20, "five operations at four widths, and no multiply");
let commuting = UPDATES.iter().filter(|update| update.commutes).count();
assert_eq!(commuting, 16, "everything but the four subtractions");
}
fn alu_imm(
func: &mut Func,
names: &mut Interner,
block: mir::Block,
name: &str,
source: Reg,
value: i64,
) -> Reg {
let answer = func.new_vreg(GPR);
let opcode = op(names, name);
func.build(block, opcode)
.operand(Operand::write(answer, GPR).with(Constraint::Reuse(1)))
.uses(source, GPR)
.imm(value)
.finish();
answer
}
#[test]
fn a_word_read_changed_by_a_constant_and_written_back_becomes_one_instruction() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.add_mi_64"]);
let inst = func.insts(block).next().expect("the addition");
let mem = func[inst].mem.expect("it writes memory");
assert_eq!(func[mem].disp, 16, "the address came from the store");
assert_eq!(func[mem].base, Some(0), "which is now the first operand and not the second");
assert_eq!(func[func[inst].operands].len(), 1, "the base of the address and nothing else");
assert_eq!(func[func[inst].operands][0].reg, base, "the address");
assert_eq!(func[func[inst].imm.expect("the constant")].0, 1);
}
#[test]
fn a_constant_taken_away_from_a_place_becomes_one_instruction() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let left = alu_imm(&mut func, &mut names, block, "sub_ri_64", word, 7);
store(&mut func, &mut names, block, base, left);
assert_eq!(update(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.sub_mi_64"]);
assert_eq!(func[func[func.insts(block).next().expect("it")].imm.expect("it")].0, 7);
}
#[test]
fn a_byte_read_changed_by_a_constant_and_written_back_becomes_one_instruction() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let word = func.new_vreg(GPR);
let mov = op(&mut names, "mov_rm_8");
func.build(block, mov)
.def(word, GPR)
.mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
.finish();
let sum = alu_imm(&mut func, &mut names, block, "or_ri_8", word, 4);
let put = op(&mut names, "mov_mr_8");
func.build(block, put)
.uses(sum, GPR)
.mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
.finish();
assert_eq!(update(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.or_mi_8"]);
}
#[test]
fn a_word_a_constant_changes_and_something_else_reads_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
alu(&mut func, &mut names, block, "xor_rr_64", word, other);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_constant_run_with_another_access_in_the_middle_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let elsewhere = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
load(&mut func, &mut names, block, elsewhere);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_constant_run_whose_address_register_is_written_in_the_middle_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RAX);
let word = load(&mut func, &mut names, block, base);
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
let mov = op(&mut names, "mov_ri_64");
func.build(block, mov).def(base, GPR).imm(0).finish();
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_constant_written_to_another_address_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let elsewhere = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
store(&mut func, &mut names, block, elsewhere, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_constant_run_whose_widths_disagree_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let into = func.new_vreg(GPR);
let narrow = op(&mut names, "mov_rm_32");
func.build(block, narrow)
.def(into, GPR)
.mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
.finish();
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", into, 1);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_place_multiplied_by_a_constant_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let product = alu_imm(&mut func, &mut names, block, "imul_ri_64", word, 3);
store(&mut func, &mut names, block, base, product);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn the_frame_entry_of_a_load_a_constant_run_takes_comes_off_the_list() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RSP);
let mov = op(&mut names, "mov_rm_64");
let word = func.new_vreg(GPR);
func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let read = func.insts(block).next().expect("the load");
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
let put = op(&mut names, "mov_mr_64");
func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let written = func.insts(block).nth(2).expect("the store");
let mut addresses = vec![(read, 3usize), (written, 3usize)];
let mut arguments = Vec::new();
let mut dynamic = Vec::new();
let mut pending =
Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 1);
let inst = func.insts(block).next().expect("the addition");
assert_eq!(addresses, [(inst, 3usize)], "one entry, on the instruction that is left");
}
#[test]
fn two_locals_a_constant_run_would_join_are_not_the_same_place() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RSP);
let mov = op(&mut names, "mov_rm_64");
let word = func.new_vreg(GPR);
func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let read = func.insts(block).next().expect("the load");
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
let put = op(&mut names, "mov_mr_64");
func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
let written = func.insts(block).nth(2).expect("the store");
let mut addresses = vec![(read, 3usize), (written, 4usize)];
let mut arguments = Vec::new();
let mut dynamic = Vec::new();
let mut pending =
Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 0);
}
#[test]
fn every_row_of_the_bump_table_is_four_instructions_this_target_has() {
for bump in BUMPS {
for name in [bump.from, bump.into, bump.load, bump.store] {
assert!(MACHINE.has(name), "{name} is not an instruction");
}
let width = |name: &str| name.rsplit_once('_').map(|(_, width)| width.to_owned());
assert_eq!(width(bump.from), width(bump.into), "{} changes width", bump.from);
assert_eq!(width(bump.from), width(bump.load), "{} loads another width", bump.from);
assert_eq!(width(bump.from), width(bump.store), "{} stores another width", bump.from);
assert!((MACHINE.takes_mem)(bump.into), "{} reaches no memory", bump.into);
assert!(!(MACHINE.takes_mem)(bump.from), "{} already reaches memory", bump.from);
assert!((MACHINE.takes_imm)(bump.into), "{} carries no constant", bump.into);
}
}
#[test]
fn the_bump_table_covers_the_arithmetic_this_target_can_do_in_place_against_a_constant() {
assert_eq!(BUMPS.len(), 20, "five operations at four widths, and no multiply");
let register: Vec<&str> = UPDATES.iter().map(|update| update.from).collect();
for bump in BUMPS {
let same = bump.from.replace("_ri_", "_rr_");
assert!(register.contains(&same.as_str()), "{} has no register row", bump.from);
}
}
#[test]
fn nothing_is_both_a_register_run_and_a_constant_run() {
for bump in BUMPS {
assert!(
!UPDATES.iter().any(|update| update.from == bump.from),
"{} starts both kinds of run",
bump.from
);
}
}
#[test]
fn a_load_read_once_by_an_addition_becomes_its_memory_operand() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
alu(&mut func, &mut names, block, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.add_rm_64"]);
let inst = func.insts(block).next().expect("the addition");
let mem = func[inst].mem.expect("the addition reads memory now");
assert_eq!(func[mem].disp, 16, "the load's displacement came with it");
assert_eq!(func[mem].base, Some(2), "and names the operand behind the source it kept");
assert_eq!(func[func[inst].operands][1].reg, other, "the source it kept");
assert_eq!(func[func[inst].operands][2].reg, base, "the address it took on");
}
#[test]
fn a_load_feeding_the_first_source_of_an_addition_is_swapped_and_folded() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
alu(&mut func, &mut names, block, "add_rr_64", word, other);
assert_eq!(combine(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.add_rm_64"]);
let inst = func.insts(block).next().expect("the addition");
assert_eq!(func[func[inst].operands][1].reg, other);
}
#[test]
fn a_load_feeding_the_left_of_a_subtraction_stays_a_load() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
alu(&mut func, &mut names, block, "sub_rr_64", word, other);
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.sub_rr_64"]);
}
#[test]
fn a_load_feeding_the_right_of_a_subtraction_folds() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
alu(&mut func, &mut names, block, "sub_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.sub_rm_64"]);
}
#[test]
fn a_load_two_instructions_read_stays_a_load() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
alu(&mut func, &mut names, block, "add_rr_64", other, word);
alu(&mut func, &mut names, block, "xor_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(
shape(&func, &names, block),
["x64.mov_rm_64", "x64.add_rr_64", "x64.xor_rr_64"]
);
}
#[test]
fn a_load_with_a_store_between_it_and_its_reader_stays_a_load() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let store = op(&mut names, "mov_mr_64");
func.build(block, store).uses(other, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
alu(&mut func, &mut names, block, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(
shape(&func, &names, block),
["x64.mov_rm_64", "x64.mov_mr_64", "x64.add_rr_64"]
);
}
#[test]
fn a_load_with_another_load_between_it_and_its_reader_stays_a_load() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
load(&mut func, &mut names, block, other);
alu(&mut func, &mut names, block, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(
shape(&func, &names, block),
["x64.mov_rm_64", "x64.mov_rm_64", "x64.add_rr_64"]
);
}
#[test]
fn the_later_of_two_loads_is_the_one_that_folds() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let first = load(&mut func, &mut names, block, base);
let second = load(&mut func, &mut names, block, other);
alu(&mut func, &mut names, block, "add_rr_64", first, second);
assert_eq!(combine(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.add_rm_64"]);
let addition = func.insts(block).nth(1).expect("the addition");
assert_eq!(func[func[addition].operands][1].reg, first, "the earlier load is still read");
assert_eq!(func[func[addition].operands][2].reg, other, "and the later one is the address");
}
#[test]
fn a_load_with_a_call_between_it_and_its_reader_stays_a_load() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let call = op(&mut names, "call");
func.build(block, call).finish();
alu(&mut func, &mut names, block, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.call", "x64.add_rr_64"]);
}
#[test]
fn a_load_whose_address_register_is_written_between_the_two_stays_a_load() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RSP);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sub = op(&mut names, "sub_ri_64");
func.build(block, sub)
.operand(Operand::write(base, GPR).with(Constraint::Reuse(1)))
.uses(base, GPR)
.imm(32)
.finish();
alu(&mut func, &mut names, block, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 0);
}
#[test]
fn a_load_of_the_wrong_width_stays_a_load() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let into = func.new_vreg(GPR);
let narrow = op(&mut names, "mov_rm_32");
func.build(block, narrow).def(into, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
alu(&mut func, &mut names, block, "add_rr_64", other, into);
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["x64.mov_rm_32", "x64.add_rr_64"]);
}
#[test]
fn a_load_whose_value_an_edge_carries_stays_a_load() {
let (mut names, mut func, block) = empty();
let next = func.create_block();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
alu(&mut func, &mut names, block, "add_rr_64", other, word);
let arrived = func.new_vreg(GPR);
func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
*func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![word])];
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.add_rr_64"]);
}
#[test]
fn a_reader_in_another_block_stays_where_it_is() {
let (mut names, mut func, block) = empty();
let next = func.create_block();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
alu(&mut func, &mut names, next, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64"]);
assert_eq!(shape(&func, &names, next), ["x64.add_rr_64"]);
}
#[test]
fn a_reader_past_the_window_stays_where_it_is() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let nop = op(&mut names, "nop");
for _ in 0..WINDOW {
func.build(block, nop).finish();
}
alu(&mut func, &mut names, block, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 0);
}
#[test]
fn a_reader_at_the_edge_of_the_window_folds() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let nop = op(&mut names, "nop");
for _ in 0..WINDOW - 1 {
func.build(block, nop).finish();
}
alu(&mut func, &mut names, block, "add_rr_64", other, word);
assert_eq!(combine(&mut func, &mut names), 1);
}
#[test]
fn the_frame_entry_of_a_load_that_moves_goes_with_it() {
let (mut names, mut func, block) = empty();
let base = Reg::physical(rucc_target::x86_64::RSP);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let reader = func.insts(block).nth(1);
assert!(reader.is_none(), "the block holds the load alone so far");
alu(&mut func, &mut names, block, "add_rr_64", other, word);
let held = func.insts(block).next().expect("the load");
let mut addresses = vec![(held, 3usize)];
let mut arguments = Vec::new();
let mut dynamic = Vec::new();
let mut pending =
Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
assert_eq!(loads(&mut func, &MACHINE, &mut names, &mut pending), 1);
let inst = func.insts(block).next().expect("the addition");
assert_eq!(addresses, [(inst, 3usize)], "the entry names the instruction that took it");
}
#[test]
fn a_comparison_against_a_word_that_was_just_loaded_becomes_one_instruction() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
compare(&mut func, &mut names, block, "cmp_set_l_64", other, word);
assert_eq!(combine(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.cmp_set_l_rm_64"]);
let inst = func.insts(block).next().expect("the comparison");
let mem = func[inst].mem.expect("it reads memory");
assert_eq!(func[mem].disp, 16, "the address came from the load");
assert_eq!(func[mem].base, Some(2), "and names the operand behind the byte and the source");
assert_eq!(func[func[inst].operands][1].reg, other, "the side it kept");
assert_eq!(func[func[inst].operands][2].reg, base, "the address");
}
#[test]
fn a_comparison_whose_left_hand_side_was_just_loaded_turns_the_condition_over() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
compare(&mut func, &mut names, block, "cmp_set_l_64", word, other);
assert_eq!(combine(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.cmp_set_g_rm_64"]);
let inst = func.insts(block).next().expect("the comparison");
assert_eq!(func[func[inst].operands][1].reg, other, "the side it kept");
}
#[test]
fn an_equality_folded_on_either_side_is_the_same_comparison() {
for (first, second) in [(true, false), (false, true)] {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let left = if first { word } else { other };
let right = if second { word } else { other };
compare(&mut func, &mut names, block, "cmp_set_e_64", left, right);
assert_eq!(combine(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.cmp_set_e_rm_64"]);
}
}
#[test]
fn a_comparison_against_a_constant_takes_the_load_on_as_its_memory_operand() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let opcode = op(&mut names, "cmp_set_l_ri_64");
func.build(block, opcode).def(byte, GPR).uses(word, GPR).imm(7).finish();
assert_eq!(combine(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.cmp_set_l_mi_64"]);
let inst = func.insts(block).next().expect("the comparison");
let mem = func[inst].mem.expect("it reads memory now");
assert_eq!(func[mem].disp, 16, "the load's displacement came with it");
assert_eq!(func[mem].base, Some(1), "and names the operand behind the byte");
assert_eq!(func[func[inst].operands][0].reg, byte, "the byte it sets");
assert_eq!(func[func[inst].operands][1].reg, base, "the address it took on");
let imm = func[inst].imm.expect("the constant is still on it");
assert_eq!(func[imm].0, 7, "and is the one that was written");
}
#[test]
fn every_row_of_the_table_is_three_instructions_this_target_has() {
for fold in FOLDS {
assert!(MACHINE.has(fold.from), "{} is not an instruction", fold.from);
assert!(MACHINE.has(fold.into), "{} is not an instruction", fold.into);
assert!(MACHINE.has(fold.load), "{} is not an instruction", fold.load);
let width = |name: &str| name.rsplit_once('_').map(|(_, width)| width.to_owned());
assert_eq!(width(fold.from), width(fold.into), "{} changes width", fold.from);
assert_eq!(width(fold.from), width(fold.load), "{} loads another width", fold.from);
assert!((MACHINE.takes_mem)(fold.into), "{} reads no memory", fold.into);
assert!(!(MACHINE.takes_mem)(fold.from), "{} already reads memory", fold.from);
let Some(swapped) = fold.swapped else { continue };
assert!(MACHINE.has(swapped), "{swapped} is not an instruction");
assert_eq!(width(fold.from), width(swapped), "{} changes width", fold.from);
assert!((MACHINE.takes_mem)(swapped), "{swapped} reads no memory");
}
}
#[test]
fn the_table_covers_the_arithmetic_and_the_comparisons_this_target_has() {
let compares = FOLDS.iter().filter(|fold| fold.from.starts_with("cmp_set_")).count();
assert_eq!(
compares, 80,
"ten conditions at four widths, against a register and a constant"
);
let arithmetic = FOLDS.len() - compares;
assert_eq!(arithmetic, 23, "six operations at four widths, less the eight bit multiply");
let swapped = FOLDS.iter().filter(|fold| fold.swapped.is_some()).count();
assert_eq!(swapped, 59, "everything but the four subtractions and the constant compares");
}
#[test]
fn a_comparison_folded_on_its_left_hand_side_asks_the_same_question_backwards() {
let turned = |condition: &str| match condition {
"e" => "e",
"ne" => "ne",
"l" => "g",
"g" => "l",
"le" => "ge",
"ge" => "le",
"b" => "a",
"a" => "b",
"be" => "ae",
"ae" => "be",
other => panic!("{other} is not a condition this machine has"),
};
let compares = FOLDS
.iter()
.filter(|fold| fold.from.starts_with("cmp_set_") && !fold.from.contains("_ri_"));
for fold in compares {
let (front, width) = fold.from.rsplit_once('_').expect("a name ending in a width");
let condition = front.strip_prefix("cmp_set_").expect("a name with a condition");
assert_eq!(fold.into, format!("cmp_set_{condition}_rm_{width}"));
let wanted = format!("cmp_set_{}_rm_{width}", turned(condition));
assert_eq!(fold.swapped, Some(wanted.as_str()), "{} turns over wrongly", fold.from);
}
}
#[test]
fn a_comparison_against_a_constant_keeps_its_condition_and_has_nothing_to_swap() {
let compares = FOLDS
.iter()
.filter(|fold| fold.from.starts_with("cmp_set_") && fold.from.contains("_ri_"));
let mut rows = 0;
for fold in compares {
let (front, width) = fold.from.rsplit_once('_').expect("a name ending in a width");
let front = front.strip_suffix("_ri").expect("a name against a constant");
let condition = front.strip_prefix("cmp_set_").expect("a name with a condition");
assert_eq!(fold.into, format!("cmp_set_{condition}_mi_{width}"));
assert_eq!(fold.swapped, None, "{} has a side to swap", fold.from);
assert_eq!(fold.load, format!("mov_rm_{width}"), "{} loads wrongly", fold.from);
rows += 1;
}
assert_eq!(rows, 40, "ten conditions at four widths");
}
#[test]
fn a_load_the_program_insisted_on_is_left_where_it_stands() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = insisted_load(&mut func, &mut names, block, base);
alu(&mut func, &mut names, block, "add_rr_64", word, other);
assert_eq!(combine(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.add_rr_64"]);
}
#[test]
fn a_run_whose_load_the_program_insisted_on_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = insisted_load(&mut func, &mut names, block, base);
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_run_whose_store_the_program_insisted_on_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
insisted_store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_constant_run_the_program_insisted_on_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let word = insisted_load(&mut func, &mut names, block, base);
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn a_constant_run_whose_store_the_program_insisted_on_stays_three_instructions() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
insisted_store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 0);
}
#[test]
fn the_same_runs_without_the_flag_are_the_ones_the_pass_takes() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let word = load(&mut func, &mut names, block, base);
let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
store(&mut func, &mut names, block, base, sum);
assert_eq!(update(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["x64.add_mr_64"]);
}
}