use rucc_ir::{BlockCall, Builder, Extra, Func, Imm, Inst, IntPred, Opcode, Value};
pub fn switches(func: &mut Func) {
let found: Vec<Inst> = func
.blocks()
.filter_map(|block| func.terminator(block))
.filter(|&inst| func[inst].opcode == Opcode::Switch)
.collect();
for inst in found {
chain(func, inst);
}
}
fn chain(func: &mut Func, inst: Inst) {
let block = func.block_of(inst).expect("a terminator is in a block");
let span = func.span(inst);
let Extra::Switch(info) = func[inst].extra else { return };
let info = func[info];
let value = func[func[inst].args][0];
let ty = func[value].ty.lane();
let calls: Vec<BlockCall> = func[info.targets].to_vec();
let cases: Vec<Imm> = func[info.cases].to_vec();
let Some((default, arms)) = calls.split_first() else { return };
func.remove_inst(inst);
let Some((first, rest)) = arms.split_first() else {
let args: Vec<Value> = func[default.args].to_vec();
Builder::new(func, block).at(span).jump(default.block, &args);
return;
};
let mut at = block;
for (index, arm) in std::iter::once(first).chain(rest).enumerate() {
let last = index + 1 == arms.len();
let next = if last { default.block } else { func.create_block() };
let onward: Vec<Value> = if last { func[default.args].to_vec() } else { Vec::new() };
let taken: Vec<Value> = func[arm.args].to_vec();
let case = cases[index].signed(ty);
let mut build = Builder::new(func, at).at(span);
let want = build.iconst(ty, case);
let same = build.icmp(IntPred::Eq, value, want);
build.br_if(same, arm.block, &taken, next, &onward);
at = next;
}
}
#[must_use]
pub fn blocks_for(cases: usize) -> usize {
cases.saturating_sub(1)
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{Builder, Func, Module, Opcode, Signature, Type};
use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
use super::{blocks_for, switches};
fn target() -> TargetInfo {
TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
}
fn built(cases: &[i128]) -> (Interner, Func) {
let mut names = Interner::new();
let int = Type::int(32);
let mut func = Func::new(
names.intern("sw"),
Signature::new().with_params(&[int]).with_returns(&[int]),
);
let entry = func.create_block();
let x = func.append_param(entry, int);
let default = func.create_block();
let arms: Vec<_> = cases.iter().map(|_| func.create_block()).collect();
let table: Vec<(i128, rucc_ir::Block)> =
cases.iter().copied().zip(arms.iter().copied()).collect();
Builder::new(&mut func, entry).switch(x, default, &table);
for (index, &arm) in arms.iter().enumerate() {
let mut build = Builder::new(&mut func, arm);
let what = i128::try_from(index).expect("a small number of cases");
let v = build.iconst(int, (what + 1) * 10);
build.ret(&[v]);
}
let mut build = Builder::new(&mut func, default);
let v = build.iconst(int, 30);
build.ret(&[v]);
(names, func)
}
fn count(func: &Func) -> usize {
func.blocks().count()
}
fn printed(func: &Func, names: &mut Interner) -> String {
let module = Module::new(names.intern("sw.c"), &target());
rucc_ir::print_func(&module, func, names)
}
#[test]
fn a_switch_becomes_a_compare_and_a_branch_for_each_case() {
let (mut names, mut func) = built(&[1, 2]);
let before = count(&func);
switches(&mut func);
assert_eq!(count(&func), before + blocks_for(2));
let text = printed(&func, &mut names);
assert!(!text.contains("switch"), "the switch is gone: {text}");
assert_eq!(text.matches("icmp eq").count(), 2, "one compare per case: {text}");
assert_eq!(text.matches("br_if").count(), 2, "one branch per case: {text}");
}
#[test]
fn the_last_case_falls_to_the_default_rather_than_to_a_block_of_its_own() {
let (_, mut func) = built(&[7]);
let before = count(&func);
switches(&mut func);
assert_eq!(count(&func), before);
assert_eq!(blocks_for(1), 0);
}
#[test]
fn a_switch_with_only_a_default_is_a_jump() {
let (_, mut func) = built(&[]);
switches(&mut func);
let entry = func.entry().expect("an entry block");
let term = func.terminator(entry).expect("a terminator");
assert_eq!(func[term].opcode, Opcode::Jump);
}
#[test]
fn what_comes_out_is_valid_ir() {
let (mut names, mut func) = built(&[1, 2, 3, 4]);
switches(&mut func);
let module = Module::new(names.intern("sw.c"), &target());
rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
}
#[test]
fn a_function_with_no_switch_is_left_exactly_as_it_was() {
let mut names = Interner::new();
let int = Type::int(32);
let mut func =
Func::new(names.intern("f"), Signature::new().with_params(&[int]).with_returns(&[int]));
let entry = func.create_block();
let x = func.append_param(entry, int);
Builder::new(&mut func, entry).ret(&[x]);
let before = printed(&func, &mut names);
switches(&mut func);
assert_eq!(printed(&func, &mut names), before);
}
}