1use rucc_ir::{BlockCall, Builder, Extra, Func, Imm, Inst, IntPred, Opcode, Value};
28
29pub fn switches(func: &mut Func) {
35 let found: Vec<Inst> = func
36 .blocks()
37 .filter_map(|block| func.terminator(block))
38 .filter(|&inst| func[inst].opcode == Opcode::Switch)
39 .collect();
40 for inst in found {
41 chain(func, inst);
42 }
43}
44
45fn chain(func: &mut Func, inst: Inst) {
57 let block = func.block_of(inst).expect("a terminator is in a block");
58 let span = func.span(inst);
59 let Extra::Switch(info) = func[inst].extra else { return };
60 let info = func[info];
61 let value = func[func[inst].args][0];
62 let ty = func[value].ty.lane();
65 let calls: Vec<BlockCall> = func[info.targets].to_vec();
66 let cases: Vec<Imm> = func[info.cases].to_vec();
67 let Some((default, arms)) = calls.split_first() else { return };
68
69 func.remove_inst(inst);
72
73 let Some((first, rest)) = arms.split_first() else {
77 let args: Vec<Value> = func[default.args].to_vec();
78 Builder::new(func, block).at(span).jump(default.block, &args);
79 return;
80 };
81
82 let mut at = block;
83 for (index, arm) in std::iter::once(first).chain(rest).enumerate() {
84 let last = index + 1 == arms.len();
85 let next = if last { default.block } else { func.create_block() };
86 let onward: Vec<Value> = if last { func[default.args].to_vec() } else { Vec::new() };
87 let taken: Vec<Value> = func[arm.args].to_vec();
88 let case = cases[index].signed(ty);
89
90 let mut build = Builder::new(func, at).at(span);
91 let want = build.iconst(ty, case);
92 let same = build.icmp(IntPred::Eq, value, want);
93 build.br_if(same, arm.block, &taken, next, &onward);
94 at = next;
95 }
96}
97
98#[must_use]
103pub fn blocks_for(cases: usize) -> usize {
104 cases.saturating_sub(1)
105}
106
107#[cfg(test)]
108mod tests {
109 use rucc_base::Interner;
110 use rucc_ir::{Builder, Func, Module, Opcode, Signature, Type};
111 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
112
113 use super::{blocks_for, switches};
114
115 fn target() -> TargetInfo {
116 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
117 }
118
119 fn built(cases: &[i128]) -> (Interner, Func) {
122 let mut names = Interner::new();
123 let int = Type::int(32);
124 let mut func = Func::new(
125 names.intern("sw"),
126 Signature::new().with_params(&[int]).with_returns(&[int]),
127 );
128 let entry = func.create_block();
129 let x = func.append_param(entry, int);
130
131 let default = func.create_block();
132 let arms: Vec<_> = cases.iter().map(|_| func.create_block()).collect();
133 let table: Vec<(i128, rucc_ir::Block)> =
134 cases.iter().copied().zip(arms.iter().copied()).collect();
135 Builder::new(&mut func, entry).switch(x, default, &table);
136
137 for (index, &arm) in arms.iter().enumerate() {
138 let mut build = Builder::new(&mut func, arm);
139 let what = i128::try_from(index).expect("a small number of cases");
140 let v = build.iconst(int, (what + 1) * 10);
141 build.ret(&[v]);
142 }
143 let mut build = Builder::new(&mut func, default);
144 let v = build.iconst(int, 30);
145 build.ret(&[v]);
146 (names, func)
147 }
148
149 fn count(func: &Func) -> usize {
150 func.blocks().count()
151 }
152
153 fn printed(func: &Func, names: &mut Interner) -> String {
154 let module = Module::new(names.intern("sw.c"), &target());
155 rucc_ir::print_func(&module, func, names)
156 }
157
158 #[test]
159 fn a_switch_becomes_a_compare_and_a_branch_for_each_case() {
160 let (mut names, mut func) = built(&[1, 2]);
161 let before = count(&func);
162 switches(&mut func);
163 assert_eq!(count(&func), before + blocks_for(2));
164
165 let text = printed(&func, &mut names);
166 assert!(!text.contains("switch"), "the switch is gone: {text}");
167 assert_eq!(text.matches("icmp eq").count(), 2, "one compare per case: {text}");
168 assert_eq!(text.matches("br_if").count(), 2, "one branch per case: {text}");
169 }
170
171 #[test]
172 fn the_last_case_falls_to_the_default_rather_than_to_a_block_of_its_own() {
173 let (_, mut func) = built(&[7]);
174 let before = count(&func);
175 switches(&mut func);
176 assert_eq!(count(&func), before);
178 assert_eq!(blocks_for(1), 0);
179 }
180
181 #[test]
182 fn a_switch_with_only_a_default_is_a_jump() {
183 let (_, mut func) = built(&[]);
184 switches(&mut func);
185 let entry = func.entry().expect("an entry block");
186 let term = func.terminator(entry).expect("a terminator");
187 assert_eq!(func[term].opcode, Opcode::Jump);
188 }
189
190 #[test]
193 fn what_comes_out_is_valid_ir() {
194 let (mut names, mut func) = built(&[1, 2, 3, 4]);
195 switches(&mut func);
196 let module = Module::new(names.intern("sw.c"), &target());
197 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
198 }
199
200 #[test]
203 fn a_function_with_no_switch_is_left_exactly_as_it_was() {
204 let mut names = Interner::new();
205 let int = Type::int(32);
206 let mut func =
207 Func::new(names.intern("f"), Signature::new().with_params(&[int]).with_returns(&[int]));
208 let entry = func.create_block();
209 let x = func.append_param(entry, int);
210 Builder::new(&mut func, entry).ret(&[x]);
211
212 let before = printed(&func, &mut names);
213 switches(&mut func);
214 assert_eq!(printed(&func, &mut names), before);
215 }
216}