use rucc_base::Interner;
use rucc_cost::Goal;
use rucc_mir::{self as mir, Role};
use rucc_target::{FlagInsts, MachineInsts, Reads, ShortInsts};
use crate::changes::{self, Changes, Plan};
pub fn shorter(
func: &mut mir::Func,
short: &ShortInsts,
flags: &FlagInsts,
machine: &MachineInsts,
names: &mut Interner,
goal: Goal,
) -> usize {
let wanted = short.zeroing.iter().map(|entry| entry.into);
let wanted = wanted.chain(short.narrowing.iter().map(|entry| entry.into));
let wanted = wanted.chain(short.testing.iter().map(|entry| entry.into));
let wanted = wanted.chain(short.stepping.iter().map(|entry| entry.into));
let wanted = wanted.chain(short.copying.iter().map(|entry| entry.into));
let opcodes: Vec<(&'static str, mir::Opcode)> = wanted
.map(|into| (into, mir::Opcode::new(names.intern(&format!("{}{into}", short.prefix)))))
.collect();
let names = &*names;
let mut counts = changes::Reads::of(func);
let mut took = 0;
let free = !carried(func, short, flags, names);
let small = free && goal == Goal::Size;
for block in func.blocks().collect::<Vec<_>>() {
let mut live = false;
let mut carry = false;
for inst in func.insts(block).collect::<Vec<_>>().into_iter().rev() {
if free && !live {
let into = shorter_form(func, short, names, &opcodes, inst);
if into.is_some_and(|op| zeroed(func, &mut counts, machine, names, inst, op)) {
took += 1;
continue;
}
}
let into = narrower_form(func, short, names, &opcodes, inst);
if into.is_some_and(|op| narrowed(func, &mut counts, machine, names, inst, op)) {
took += 1;
}
let into = tested_form(func, short, names, &opcodes, inst);
if into.is_some_and(|op| tested(func, &mut counts, machine, names, inst, op)) {
took += 1;
}
let into = copied_form(func, short, names, &opcodes, inst);
if into.is_some_and(|op| copied(func, &mut counts, machine, names, inst, op)) {
took += 1;
}
if small && !carry {
let into = stepped_form(func, short, names, &opcodes, inst);
if into.is_some_and(|op| stepped(func, &mut counts, machine, names, inst, op)) {
took += 1;
}
}
let Some(name) = opcode(func, flags, names, inst) else {
live = true;
carry = true;
continue;
};
if flags.asks_what_it_reads(name) {
live = false;
carry = false;
} else if let Some(reads) = flags.reads(name) {
live = true;
if matches!(reads, Reads::Unsigned | Reads::Carry) {
carry = true;
}
} else if (flags.writes)(name) && !short.steps(name) {
live = false;
carry = false;
}
}
}
took
}
fn carried(func: &mir::Func, short: &ShortInsts, flags: &FlagInsts, names: &Interner) -> bool {
func.blocks().any(|block| {
for inst in func.insts(block) {
let Some(name) = opcode(func, flags, names, inst) else { return true };
if flags.reads(name).is_some() && !flags.asks_what_it_reads(name) {
return true;
}
if (flags.writes)(name) && !short.steps(name) {
return false;
}
}
false
})
}
fn shorter_form(
func: &mir::Func,
short: &ShortInsts,
names: &Interner,
opcodes: &[(&'static str, mir::Opcode)],
inst: mir::Inst,
) -> Option<mir::Opcode> {
let name = names.resolve(func[inst].opcode.name()).strip_prefix(short.prefix)?;
let into = short.zeroed(name)?;
if func[inst].imm.map(|at| func[at].0) != Some(0) {
return None;
}
opcodes.iter().find(|&&(at, _)| at == into).map(|&(_, opcode)| opcode)
}
fn narrower_form(
func: &mir::Func,
short: &ShortInsts,
names: &Interner,
opcodes: &[(&'static str, mir::Opcode)],
inst: mir::Inst,
) -> Option<mir::Opcode> {
let name = names.resolve(func[inst].opcode.name()).strip_prefix(short.prefix)?;
let narrow = short.narrowed(name)?;
let held = u64::try_from(func[func[inst].imm?].0).ok()?;
if narrow.writes >= u64::BITS || held >= 1u64 << narrow.writes {
return None;
}
opcodes.iter().find(|&&(at, _)| at == narrow.into).map(|&(_, opcode)| opcode)
}
fn narrowed(
func: &mut mir::Func,
counts: &mut changes::Reads,
machine: &MachineInsts,
names: &Interner,
inst: mir::Inst,
opcode: mir::Opcode,
) -> bool {
let mut set = Changes::new();
set.rewrite(inst, Plan { opcode, ..Plan::of(func, inst) });
set.commit(func, counts, names, machine).is_ok()
}
fn tested_form(
func: &mir::Func,
short: &ShortInsts,
names: &Interner,
opcodes: &[(&'static str, mir::Opcode)],
inst: mir::Inst,
) -> Option<mir::Opcode> {
let name = names.resolve(func[inst].opcode.name()).strip_prefix(short.prefix)?;
let into = short.tested(name)?;
if func[inst].imm.map(|at| func[at].0) != Some(0) {
return None;
}
opcodes.iter().find(|&&(at, _)| at == into).map(|&(_, opcode)| opcode)
}
fn tested(
func: &mut mir::Func,
counts: &mut changes::Reads,
machine: &MachineInsts,
names: &Interner,
inst: mir::Inst,
opcode: mir::Opcode,
) -> bool {
let mut set = Changes::new();
set.rewrite(inst, Plan { opcode, imm: None, ..Plan::of(func, inst) });
set.commit(func, counts, names, machine).is_ok()
}
fn copied_form(
func: &mir::Func,
short: &ShortInsts,
names: &Interner,
opcodes: &[(&'static str, mir::Opcode)],
inst: mir::Inst,
) -> Option<mir::Opcode> {
let name = names.resolve(func[inst].opcode.name()).strip_prefix(short.prefix)?;
let into = short.copied(name)?;
let amode = func[inst].mem.map(|at| func[at])?;
if amode.base.is_none() || amode.index.is_some() || amode.disp != 0 {
return None;
}
if amode.symbol.is_some() || amode.block.is_some() || amode.segment.is_some() {
return None;
}
opcodes.iter().find(|&&(at, _)| at == into).map(|&(_, opcode)| opcode)
}
fn copied(
func: &mut mir::Func,
counts: &mut changes::Reads,
machine: &MachineInsts,
names: &Interner,
inst: mir::Inst,
opcode: mir::Opcode,
) -> bool {
let mut set = Changes::new();
set.rewrite(inst, Plan { opcode, amode: None, ..Plan::of(func, inst) });
set.commit(func, counts, names, machine).is_ok()
}
fn zeroed(
func: &mut mir::Func,
counts: &mut changes::Reads,
machine: &MachineInsts,
names: &Interner,
inst: mir::Inst,
opcode: mir::Opcode,
) -> bool {
let written: Vec<mir::Operand> = func[func[inst].operands]
.iter()
.filter(|operand| operand.role != Role::Use)
.copied()
.collect();
let [def] = written[..] else { return false };
let bare = machine.bare(names.resolve(opcode.name()));
let Some(desc) = (machine.operands)(bare) else { return false };
if desc.iter().any(|want| want.class != def.class) {
return false;
}
if desc.iter().filter(|want| want.role != Role::Use).count() != 1 {
return false;
}
let operands = desc
.iter()
.map(|want| mir::Operand {
reg: def.reg,
class: want.class,
role: want.role,
constraint: want.constraint,
})
.collect();
let mut set = Changes::new();
set.rewrite(inst, Plan { opcode, operands, imm: None, ..Plan::of(func, inst) });
set.commit(func, counts, names, machine).is_ok()
}
fn stepped_form(
func: &mir::Func,
short: &ShortInsts,
names: &Interner,
opcodes: &[(&'static str, mir::Opcode)],
inst: mir::Inst,
) -> Option<mir::Opcode> {
let name = names.resolve(func[inst].opcode.name()).strip_prefix(short.prefix)?;
let into = short.stepped(name, func[func[inst].imm?].0)?;
opcodes.iter().find(|&&(at, _)| at == into).map(|&(_, opcode)| opcode)
}
fn stepped(
func: &mut mir::Func,
counts: &mut changes::Reads,
machine: &MachineInsts,
names: &Interner,
inst: mir::Inst,
opcode: mir::Opcode,
) -> bool {
let mut set = Changes::new();
set.rewrite(inst, Plan { opcode, imm: None, ..Plan::of(func, inst) });
set.commit(func, counts, names, machine).is_ok()
}
fn opcode<'a>(
func: &mir::Func,
flags: &FlagInsts,
names: &'a Interner,
inst: mir::Inst,
) -> Option<&'a str> {
names.resolve(func[inst].opcode.name()).strip_prefix(flags.prefix)
}
#[cfg(test)]
mod tests {
use rucc_target::x86_64::{FLAGS, GPR, MACHINE, SHORT};
use super::*;
fn empty() -> (Interner, mir::Func, mir::Block) {
let mut names = Interner::new();
let mut func = mir::Func::new(names.intern("f"));
let block = func.create_block();
(names, func, block)
}
fn op(names: &mut Interner, name: &str) -> mir::Opcode {
mir::Opcode::new(names.intern(&format!("{}{name}", SHORT.prefix)))
}
fn takes(func: &mut mir::Func, names: &mut Interner) -> usize {
shorter(func, &SHORT, &FLAGS, &MACHINE, names, Goal::Speed)
}
fn small(func: &mut mir::Func, names: &mut Interner) -> usize {
shorter(func, &SHORT, &FLAGS, &MACHINE, names, Goal::Size)
}
fn shape(func: &mir::Func, names: &Interner, block: mir::Block) -> Vec<String> {
func.insts(block)
.map(|inst| {
names
.resolve(func[inst].opcode.name())
.strip_prefix(SHORT.prefix)
.unwrap_or("")
.to_owned()
})
.collect()
}
fn reuse(reg: mir::Reg) -> mir::Operand {
mir::Operand {
reg,
class: GPR,
role: Role::Def,
constraint: rucc_mir::Constraint::Reuse(1),
}
}
fn regs(func: &mir::Func, inst: mir::Inst) -> Vec<mir::Reg> {
func[func[inst].operands].iter().map(|operand| operand.reg).collect()
}
fn imm(func: &mir::Func, inst: mir::Inst) -> Option<i64> {
func[inst].imm.map(|at| func[at].0)
}
#[test]
fn a_move_of_zero_becomes_an_exclusive_or() {
let (mut names, mut func, block) = empty();
let into = func.new_vreg(GPR);
let zero = op(&mut names, "mov_ri_32");
let inst = func.build(block, zero).def(into, GPR).imm(0).finish();
assert_eq!(takes(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["xor_rr_32"]);
assert_eq!(regs(&func, inst), [into, into, into]);
assert!(func[inst].imm.is_none());
}
#[test]
fn sixty_four_bits_is_the_same_rewrite_at_half_the_width() {
let (mut names, mut func, block) = empty();
let into = func.new_vreg(GPR);
let zero = op(&mut names, "mov_ri_64");
func.build(block, zero).def(into, GPR).imm(0).finish();
assert_eq!(takes(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["xor_rr_32"]);
}
#[test]
fn a_number_a_narrower_move_holds_is_written_by_the_narrower_move() {
for value in [1, 7, 0x7fff_ffff, 0x8000_0000, 0xffff_ffff] {
let (mut names, mut func, block) = empty();
let into = func.new_vreg(GPR);
let wide = op(&mut names, "mov_ri_64");
let inst = func.build(block, wide).def(into, GPR).imm(value).finish();
assert_eq!(takes(&mut func, &mut names), 1, "{value}");
assert_eq!(shape(&func, &names, block), ["mov_ri_32"], "{value}");
assert_eq!(imm(&func, inst), Some(value), "{value}");
assert_eq!(regs(&func, inst), [into], "{value}");
}
}
#[test]
fn a_number_the_narrower_move_does_not_hold_stays_wide() {
for value in [-1, -7, 0x1_0000_0000, i64::MIN, i64::MAX] {
let (mut names, mut func, block) = empty();
let into = func.new_vreg(GPR);
let wide = op(&mut names, "mov_ri_64");
func.build(block, wide).def(into, GPR).imm(value).finish();
assert_eq!(takes(&mut func, &mut names), 0, "{value}");
assert_eq!(shape(&func, &names, block), ["mov_ri_64"], "{value}");
}
}
#[test]
fn a_zero_the_state_is_not_free_for_is_narrowed_instead() {
let (mut names, mut func, block) = empty();
let left = func.new_vreg(GPR);
let right = func.new_vreg(GPR);
let into = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let cmp = op(&mut names, "cmp_rr_32");
let zero = op(&mut names, "mov_ri_64");
let set = op(&mut names, "set_e");
func.build(block, cmp).uses(left, GPR).uses(right, GPR).finish();
let inst = func.build(block, zero).def(into, GPR).imm(0).finish();
func.build(block, set).def(byte, GPR).finish();
assert_eq!(takes(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["cmp_rr_32", "mov_ri_32", "set_e"]);
assert_eq!(imm(&func, inst), Some(0));
}
#[test]
fn a_function_the_carried_state_turns_down_is_still_narrowed() {
let (mut names, mut func, first) = empty();
let second = func.create_block();
let into = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let wide = op(&mut names, "mov_ri_64");
let set = op(&mut names, "set_e");
func.build(first, wide).def(into, GPR).imm(7).finish();
func.build(second, set).def(byte, GPR).finish();
assert_eq!(takes(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, first), ["mov_ri_32"]);
}
#[test]
fn a_move_of_a_number_that_is_not_zero_stays() {
let (mut names, mut func, block) = empty();
let into = func.new_vreg(GPR);
let one = op(&mut names, "mov_ri_32");
func.build(block, one).def(into, GPR).imm(1).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["mov_ri_32"]);
}
#[test]
fn eight_bits_buys_nothing_and_is_left_alone() {
let (mut names, mut func, block) = empty();
let into = func.new_vreg(GPR);
let zero = op(&mut names, "mov_ri_8");
func.build(block, zero).def(into, GPR).imm(0).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["mov_ri_8"]);
}
#[test]
fn a_move_a_condition_reads_the_state_after_stays() {
let (mut names, mut func, block) = empty();
let left = func.new_vreg(GPR);
let right = func.new_vreg(GPR);
let into = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let cmp = op(&mut names, "cmp_rr_32");
let zero = op(&mut names, "mov_ri_32");
let set = op(&mut names, "set_e");
func.build(block, cmp).uses(left, GPR).uses(right, GPR).finish();
func.build(block, zero).def(into, GPR).imm(0).finish();
func.build(block, set).def(byte, GPR).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["cmp_rr_32", "mov_ri_32", "set_e"]);
}
#[test]
fn a_state_something_else_writes_first_lets_the_rewrite_back_in() {
let (mut names, mut func, block) = empty();
let left = func.new_vreg(GPR);
let right = func.new_vreg(GPR);
let sum = func.new_vreg(GPR);
let into = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let zero = op(&mut names, "mov_ri_32");
let add = op(&mut names, "add_rr_32");
let set = op(&mut names, "set_e");
func.build(block, zero).def(into, GPR).imm(0).finish();
func.build(block, add).def(sum, GPR).uses(left, GPR).uses(right, GPR).finish();
func.build(block, set).def(byte, GPR).finish();
assert_eq!(takes(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["xor_rr_32", "add_rr_32", "set_e"]);
}
#[test]
fn a_state_carried_into_a_block_turns_the_whole_function_down() {
let (mut names, mut func, first) = empty();
let second = func.create_block();
let into = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let zero = op(&mut names, "mov_ri_32");
let set = op(&mut names, "set_e");
func.build(first, zero).def(into, GPR).imm(0).finish();
func.build(second, set).def(byte, GPR).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, first), ["mov_ri_32"]);
}
#[test]
fn a_comparison_against_zero_becomes_a_test_of_the_register_against_itself() {
for (wide, narrow) in [
("cmp_ri_8", "test_rr_8"),
("cmp_ri_16", "test_rr_16"),
("cmp_ri_32", "test_rr_32"),
("cmp_ri_64", "test_rr_64"),
] {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let cmp = op(&mut names, wide);
let set = op(&mut names, "set_e");
let inst = func.build(block, cmp).uses(value, GPR).imm(0).finish();
func.build(block, set).def(byte, GPR).finish();
assert_eq!(takes(&mut func, &mut names), 1, "{wide}");
assert_eq!(shape(&func, &names, block), [narrow, "set_e"], "{wide}");
assert_eq!(regs(&func, inst), [value], "{wide}");
assert_eq!(imm(&func, inst), None, "{wide}");
}
}
#[test]
fn a_comparison_against_a_number_that_is_not_zero_is_left_alone() {
for value in [1, -1, 7, 255, i64::from(i32::MIN)] {
let (mut names, mut func, block) = empty();
let held = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let cmp = op(&mut names, "cmp_ri_32");
let set = op(&mut names, "set_e");
let inst = func.build(block, cmp).uses(held, GPR).imm(value).finish();
func.build(block, set).def(byte, GPR).finish();
assert_eq!(takes(&mut func, &mut names), 0, "{value}");
assert_eq!(shape(&func, &names, block), ["cmp_ri_32", "set_e"], "{value}");
assert_eq!(imm(&func, inst), Some(value), "{value}");
}
}
#[test]
fn a_comparison_is_tested_whatever_the_condition_state_is_doing() {
let (mut names, mut func, first) = empty();
let second = func.create_block();
let value = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let cmp = op(&mut names, "cmp_ri_32");
let set = op(&mut names, "set_e");
func.build(first, cmp).uses(value, GPR).imm(0).finish();
func.build(second, set).def(byte, GPR).finish();
assert_eq!(takes(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, first), ["test_rr_32"]);
assert_eq!(shape(&func, &names, second), ["set_e"]);
}
#[test]
fn adding_or_taking_away_one_becomes_the_instruction_that_says_so_in_its_opcode() {
for (name, by, into) in [
("add_ri_8", 1, "inc_r_8"),
("add_ri_16", 1, "inc_r_16"),
("add_ri_32", 1, "inc_r_32"),
("add_ri_64", 1, "inc_r_64"),
("add_ri_8", -1, "dec_r_8"),
("add_ri_16", -1, "dec_r_16"),
("add_ri_32", -1, "dec_r_32"),
("add_ri_64", -1, "dec_r_64"),
("sub_ri_8", 1, "dec_r_8"),
("sub_ri_16", 1, "dec_r_16"),
("sub_ri_32", 1, "dec_r_32"),
("sub_ri_64", 1, "dec_r_64"),
("sub_ri_8", -1, "inc_r_8"),
("sub_ri_16", -1, "inc_r_16"),
("sub_ri_32", -1, "inc_r_32"),
("sub_ri_64", -1, "inc_r_64"),
] {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let add = op(&mut names, name);
let inst =
func.build(block, add).operand(reuse(value)).uses(value, GPR).imm(by).finish();
assert_eq!(small(&mut func, &mut names), 1, "{name} {by}");
assert_eq!(shape(&func, &names, block), [into], "{name} {by}");
assert_eq!(regs(&func, inst), [value, value], "{name} {by}");
assert_eq!(imm(&func, inst), None, "{name} {by}");
}
}
#[test]
fn a_level_that_wanted_fast_code_keeps_the_addition() {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let add = op(&mut names, "add_ri_32");
let inst = func.build(block, add).operand(reuse(value)).uses(value, GPR).imm(1).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["add_ri_32"]);
assert_eq!(imm(&func, inst), Some(1));
}
#[test]
fn adding_anything_but_one_stays_an_addition() {
for by in [0, 2, -2, 7, 255, i64::from(i32::MIN)] {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let add = op(&mut names, "add_ri_32");
func.build(block, add).operand(reuse(value)).uses(value, GPR).imm(by).finish();
assert_eq!(small(&mut func, &mut names), 0, "{by}");
assert_eq!(shape(&func, &names, block), ["add_ri_32"], "{by}");
}
}
#[test]
fn an_addition_whose_carry_something_reads_stays_an_addition() {
for reader in ["set_b", "set_be", "set_a", "set_ae", "adc_ri_32"] {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let add = op(&mut names, "add_ri_32");
let reads = op(&mut names, reader);
func.build(block, add).operand(reuse(value)).uses(value, GPR).imm(1).finish();
func.build(block, reads).def(byte, GPR).finish();
assert_eq!(small(&mut func, &mut names), 0, "{reader}");
assert_eq!(shape(&func, &names, block), ["add_ri_32", reader], "{reader}");
}
}
#[test]
fn an_addition_whose_zero_or_sign_something_reads_still_steps() {
for reader in ["set_e", "set_ne", "set_l", "set_le", "set_g", "set_ge"] {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let add = op(&mut names, "add_ri_32");
let reads = op(&mut names, reader);
func.build(block, add).operand(reuse(value)).uses(value, GPR).imm(1).finish();
func.build(block, reads).def(byte, GPR).finish();
assert_eq!(small(&mut func, &mut names), 1, "{reader}");
assert_eq!(shape(&func, &names, block), ["inc_r_32", reader], "{reader}");
}
}
#[test]
fn a_write_of_the_state_ends_the_life_of_the_carry_and_a_step_does_not() {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let add = op(&mut names, "add_ri_32");
let cmp = op(&mut names, "cmp_rr_32");
let below = op(&mut names, "set_b");
let first = func.build(block, add).operand(reuse(value)).uses(value, GPR).imm(1).finish();
func.build(block, cmp).uses(value, GPR).uses(value, GPR).finish();
func.build(block, below).def(byte, GPR).finish();
assert_eq!(small(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["inc_r_32", "cmp_rr_32", "set_b"]);
assert_eq!(imm(&func, first), None);
}
#[test]
fn a_step_does_not_hide_the_carry_read_behind_it() {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let add = op(&mut names, "add_ri_32");
let step = op(&mut names, "inc_r_32");
let below = op(&mut names, "set_b");
let first = func.build(block, add).operand(reuse(value)).uses(value, GPR).imm(1).finish();
func.build(block, step).operand(reuse(value)).uses(value, GPR).finish();
func.build(block, below).def(byte, GPR).finish();
assert_eq!(small(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["add_ri_32", "inc_r_32", "set_b"]);
assert_eq!(imm(&func, first), Some(1));
}
#[test]
fn an_addition_the_next_addition_writes_over_still_steps() {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let add = op(&mut names, "add_ri_32");
let below = op(&mut names, "set_b");
func.build(block, add).operand(reuse(value)).uses(value, GPR).imm(1).finish();
let second = func.build(block, add).operand(reuse(value)).uses(value, GPR).imm(1).finish();
func.build(block, below).def(byte, GPR).finish();
assert_eq!(small(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["inc_r_32", "add_ri_32", "set_b"]);
assert_eq!(imm(&func, second), Some(1));
}
#[test]
fn a_block_opening_with_a_comparison_that_keeps_a_byte_is_not_a_carried_state() {
let (mut names, mut func, first) = empty();
let second = func.create_block();
let into = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let zero = op(&mut names, "mov_ri_32");
let fused = op(&mut names, "cmp_set_e_32");
func.build(first, zero).def(into, GPR).imm(0).finish();
func.build(second, fused).def(byte, GPR).uses(value, GPR).uses(value, GPR).finish();
assert_eq!(takes(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, first), ["xor_rr_32"]);
}
#[test]
fn a_comparison_that_keeps_a_byte_ends_the_life_of_the_state() {
let (mut names, mut func, block) = empty();
let into = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let zero = op(&mut names, "mov_ri_32");
let fused = op(&mut names, "cmp_set_e_32");
func.build(block, zero).def(into, GPR).imm(0).finish();
func.build(block, fused).def(byte, GPR).uses(value, GPR).uses(value, GPR).finish();
assert_eq!(takes(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["xor_rr_32", "cmp_set_e_32"]);
}
#[test]
fn a_comparison_that_keeps_a_byte_does_not_keep_the_carry_alive() {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let add = op(&mut names, "add_ri_32");
let fused = op(&mut names, "cmp_set_b_32");
func.build(block, add).operand(reuse(value)).uses(value, GPR).imm(1).finish();
func.build(block, fused).def(byte, GPR).uses(value, GPR).uses(value, GPR).finish();
assert_eq!(small(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["inc_r_32", "cmp_set_b_32"]);
}
#[test]
fn an_add_with_carry_opening_a_block_is_still_a_carried_state() {
let (mut names, mut func, first) = empty();
let second = func.create_block();
let into = func.new_vreg(GPR);
let value = func.new_vreg(GPR);
let zero = op(&mut names, "mov_ri_32");
let adc = op(&mut names, "adc_ri_32");
func.build(first, zero).def(into, GPR).imm(0).finish();
func.build(second, adc).operand(reuse(value)).uses(value, GPR).imm(1).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, first), ["mov_ri_32"]);
}
#[test]
fn a_name_this_target_does_not_know_stops_the_walk() {
let (mut names, mut func, block) = empty();
let left = func.new_vreg(GPR);
let right = func.new_vreg(GPR);
let into = func.new_vreg(GPR);
let cmp = op(&mut names, "cmp_rr_32");
let zero = op(&mut names, "mov_ri_32");
let strange = mir::Opcode::new(names.intern("nowhere.thing"));
func.build(block, cmp).uses(left, GPR).uses(right, GPR).finish();
func.build(block, zero).def(into, GPR).imm(0).finish();
func.build(block, strange).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["cmp_rr_32", "mov_ri_32", ""]);
}
#[test]
fn an_address_that_is_a_register_becomes_a_move() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let into = func.new_vreg(GPR);
let lea = op(&mut names, "lea_64");
let mem = mir::Mem::at(mir::Operand::read(base, GPR));
let inst = func.build(block, lea).def(into, GPR).mem(mem).finish();
assert_eq!(takes(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["mov_rr_64"]);
assert_eq!(regs(&func, inst), [into, base]);
assert!(func[inst].mem.is_none());
}
#[test]
fn an_address_with_a_constant_added_stays() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let into = func.new_vreg(GPR);
let lea = op(&mut names, "lea_64");
let mem = mir::Mem { disp: 8, ..mir::Mem::at(mir::Operand::read(base, GPR)) };
func.build(block, lea).def(into, GPR).mem(mem).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["lea_64"]);
}
#[test]
fn an_address_with_an_index_stays() {
let (mut names, mut func, block) = empty();
let base = func.new_vreg(GPR);
let index = func.new_vreg(GPR);
let into = func.new_vreg(GPR);
let lea = op(&mut names, "lea_64");
let mem = mir::Mem {
index: Some(mir::Operand::read(index, GPR)),
scale: 4,
..mir::Mem::at(mir::Operand::read(base, GPR))
};
func.build(block, lea).def(into, GPR).mem(mem).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["lea_64"]);
}
#[test]
fn an_address_of_a_symbol_stays() {
let (mut names, mut func, block) = empty();
let into = func.new_vreg(GPR);
let lea = op(&mut names, "lea_64");
let mem = mir::Mem::of(names.intern("table"));
func.build(block, lea).def(into, GPR).mem(mem).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block), ["lea_64"]);
}
#[test]
fn an_address_is_copied_whatever_the_state_behind_it_is() {
let (mut names, mut func, block) = empty();
let left = func.new_vreg(GPR);
let right = func.new_vreg(GPR);
let base = func.new_vreg(GPR);
let into = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let cmp = op(&mut names, "cmp_rr_32");
let lea = op(&mut names, "lea_64");
let set = op(&mut names, "set_e");
let mem = mir::Mem::at(mir::Operand::read(base, GPR));
func.build(block, cmp).uses(left, GPR).uses(right, GPR).finish();
func.build(block, lea).def(into, GPR).mem(mem).finish();
func.build(block, set).def(byte, GPR).finish();
assert_eq!(takes(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["cmp_rr_32", "mov_rr_64", "set_e"]);
}
}