1use rucc_ir::{Block, BlockCall, Def, Extra, Func, Inst, IntPred, Opcode, Value};
51
52use crate::fold::constant;
53use crate::{Analyses, Fuel, Pass, Preserved, Stats};
54
55const FOLDED: &str = "branch on a condition that is always the same way replaced by a jump";
57
58const REMOVED: &str = "block nothing reaches removed";
60
61const NO_FUEL: &str = "branch on a known condition left alone, the pass ran out of fuel";
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub struct SimplifyCfg;
67
68impl Pass for SimplifyCfg {
69 fn name(&self) -> &'static str {
70 "simplify-cfg"
71 }
72
73 fn describe(&self) -> &'static str {
74 "a branch whose condition is known becomes a jump, and unreachable blocks are removed"
75 }
76
77 fn preserves(&self) -> Preserved {
78 Preserved::NONE
81 }
82
83 fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
84 let mut stats = Stats::new();
85 for block in func.blocks().collect::<Vec<Block>>() {
86 let Some(term) = func.terminator(block) else { continue };
87 let Some(taken) = taken(func, term) else { continue };
88 if !fuel.take() {
89 stats.missed(NO_FUEL);
92 continue;
93 }
94 jump_to(func, term, taken);
95 stats.optimized(FOLDED);
96 }
97 if !stats.changed() {
98 return stats;
102 }
103 an.clear();
106 for block in stranded(func, an) {
107 func.remove_block(block);
108 stats.optimized(REMOVED);
109 }
110 stats
111 }
112}
113
114fn taken(func: &Func, term: Inst) -> Option<BlockCall> {
119 let data = &func[term];
120 let arg = *func[data.args].first()?;
121 match data.opcode {
122 Opcode::BrIf => {
123 let Extra::Targets(targets) = data.extra else { return None };
124 let arm = usize::from(!known(func, arg)?);
127 func[targets].get(arm).copied()
128 }
129 Opcode::Switch => {
130 let Extra::Switch(at) = data.extra else { return None };
131 let (value, _) = constant(func, arg)?;
132 let info = func[at];
133 let case = func[info.cases].iter().position(|it| *it == value);
136 func[info.targets].get(case.map_or(0, |case| case + 1)).copied()
137 }
138 _ => None,
139 }
140}
141
142fn jump_to(func: &mut Func, term: Inst, call: BlockCall) {
147 let targets = func.push_block_calls(&[call]);
148 let args = func.push_values(&[]);
149 let data = &mut func[term];
150 data.opcode = Opcode::Jump;
151 data.args = args;
152 data.extra = Extra::Targets(targets);
153}
154
155fn stranded(func: &Func, an: &mut Analyses) -> Vec<Block> {
164 let cfg = an.cfg(func);
165 let Some(entry) = cfg.entry() else { return Vec::new() };
166 let mut seen = vec![false; cfg.capacity()];
167 seen[entry.index()] = true;
168 let mut stack = vec![entry];
169 let mut reached = Vec::new();
170 while let Some(block) = stack.pop() {
171 for &succ in cfg.successors(block) {
172 if !seen[succ.index()] {
173 seen[succ.index()] = true;
174 stack.push(succ);
175 }
176 }
177 reached.push(block);
178 }
179 let mut next = reached;
182 while !next.is_empty() {
183 let mut found = Vec::new();
184 for block in next {
185 for inst in func.insts(block) {
186 if func[inst].opcode != Opcode::BlockAddr {
187 continue;
188 }
189 for call in func.successors(inst) {
190 if !seen[call.block.index()] {
191 seen[call.block.index()] = true;
192 found.push(call.block);
193 }
194 }
195 }
196 }
197 let mut stack = found.clone();
200 while let Some(block) = stack.pop() {
201 for &succ in cfg.successors(block) {
202 if !seen[succ.index()] {
203 seen[succ.index()] = true;
204 stack.push(succ);
205 found.push(succ);
206 }
207 }
208 }
209 next = found;
210 }
211 func.blocks().filter(|block| !seen[block.index()]).collect()
212}
213
214fn known(func: &Func, value: Value) -> Option<bool> {
216 if let Some((imm, _)) = constant(func, value) {
217 return Some(imm.unsigned() != 0);
218 }
219 compared(func, value)
220}
221
222fn compared(func: &Func, value: Value) -> Option<bool> {
224 let Def::Result { inst, .. } = func[value].def else { return None };
225 let data = &func[inst];
226 if data.opcode != Opcode::ICmp {
227 return None;
228 }
229 let Extra::IntPred(pred) = data.extra else { return None };
230 let args = &func[data.args];
231 let (lhs, ty) = constant(func, *args.first()?)?;
232 let (rhs, _) = constant(func, *args.get(1)?)?;
233 Some(match pred {
234 IntPred::Eq => lhs == rhs,
235 IntPred::Ne => lhs != rhs,
236 IntPred::Slt => lhs.signed(ty) < rhs.signed(ty),
237 IntPred::Sle => lhs.signed(ty) <= rhs.signed(ty),
238 IntPred::Sgt => lhs.signed(ty) > rhs.signed(ty),
239 IntPred::Sge => lhs.signed(ty) >= rhs.signed(ty),
240 IntPred::Ult => lhs.unsigned() < rhs.unsigned(),
241 IntPred::Ule => lhs.unsigned() <= rhs.unsigned(),
242 IntPred::Ugt => lhs.unsigned() > rhs.unsigned(),
243 IntPred::Uge => lhs.unsigned() >= rhs.unsigned(),
244 })
245}
246
247#[cfg(test)]
248mod tests {
249 use rucc_base::Interner;
250 use rucc_ir::{Block, Builder, Func, IntPred, Module, Opcode, Signature, Type, Value};
251 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
252
253 use super::SimplifyCfg;
254 use crate::stats::Kind;
255 use crate::testing::graph;
256 use crate::{Analyses, Fuel, Pass, Preserved, Stats};
257
258 fn simplify(func: &mut Func) -> Stats {
260 SimplifyCfg.run(func, &mut Analyses::new(), &mut Fuel::unlimited())
261 }
262
263 fn blocks(func: &Func) -> Vec<usize> {
265 func.blocks().map(Block::index).collect()
266 }
267
268 fn terminator(func: &Func, block: usize) -> Opcode {
270 let block = Block::from_usize(block);
271 func[func.terminator(block).expect("every block here has one")].opcode
272 }
273
274 fn goes_to(func: &Func, block: usize) -> Vec<usize> {
276 let block = Block::from_usize(block);
277 let term = func.terminator(block).expect("every block here has one");
278 func.successors(term).map(|call| call.block.index()).collect()
279 }
280
281 fn diamond(cond: impl FnOnce(&mut Builder<'_>) -> Value) -> Func {
286 let mut names = Interner::new();
287 let mut func = Func::new(names.intern("f"), Signature::new());
288 let entry = func.create_block();
289 let then_block = func.create_block();
290 let else_block = func.create_block();
291 let join = func.create_block();
292 let mut build = Builder::new(&mut func, entry);
293 let cond = cond(&mut build);
294 build.br_if(cond, then_block, &[], else_block, &[]);
295 for arm in [then_block, else_block] {
296 let mut build = Builder::new(&mut func, arm);
297 build.jump(join, &[]);
298 }
299 let mut build = Builder::new(&mut func, join);
300 build.ret(&[]);
301 func
302 }
303
304 #[test]
305 fn a_branch_on_a_true_constant_becomes_a_jump_to_the_first_arm() {
306 let mut func = diamond(|build| build.iconst(Type::int(1), 1));
307 let stats = simplify(&mut func);
308 assert!(stats.changed());
309 assert_eq!(terminator(&func, 0), Opcode::Jump);
310 assert_eq!(goes_to(&func, 0), [1]);
311 assert_eq!(blocks(&func), [0, 1, 3]);
313 assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
314 }
315
316 #[test]
317 fn a_branch_on_a_false_constant_becomes_a_jump_to_the_second_arm() {
318 let mut func = diamond(|build| build.iconst(Type::int(1), 0));
319 assert!(simplify(&mut func).changed());
320 assert_eq!(goes_to(&func, 0), [2]);
321 assert_eq!(blocks(&func), [0, 2, 3]);
322 }
323
324 #[test]
325 fn a_branch_on_a_comparison_of_two_constants_is_read_without_folding_it() {
326 let cases: &[(IntPred, i128, i128, bool)] = &[
330 (IntPred::Eq, 7, 7, true),
331 (IntPred::Eq, 7, 8, false),
332 (IntPred::Ne, 7, 8, true),
333 (IntPred::Ne, 7, 7, false),
334 (IntPred::Slt, -1, 1, true),
335 (IntPred::Slt, 1, -1, false),
336 (IntPred::Sle, -1, -1, true),
337 (IntPred::Sle, 1, -1, false),
338 (IntPred::Sgt, 1, -1, true),
339 (IntPred::Sgt, -1, 1, false),
340 (IntPred::Sge, -1, -1, true),
341 (IntPred::Sge, -1, 1, false),
342 (IntPred::Ult, 1, -1, true),
343 (IntPred::Ult, -1, 1, false),
344 (IntPred::Ule, -1, -1, true),
345 (IntPred::Ule, -1, 1, false),
346 (IntPred::Ugt, -1, 1, true),
347 (IntPred::Ugt, 1, -1, false),
348 (IntPred::Uge, -1, -1, true),
349 (IntPred::Uge, 1, -1, false),
350 ];
351 for &(pred, lhs, rhs, taken) in cases {
352 let mut func = diamond(|build| {
353 let lhs = build.iconst(Type::int(32), lhs);
354 let rhs = build.iconst(Type::int(32), rhs);
355 build.icmp(pred, lhs, rhs)
356 });
357 assert!(simplify(&mut func).changed(), "{pred:?} {lhs} {rhs}");
358 let arm = if taken { 1 } else { 2 };
359 assert_eq!(goes_to(&func, 0), [arm], "{pred:?} {lhs} {rhs}");
360 let kept = func.insts(Block::from_usize(0)).any(|it| func[it].opcode == Opcode::ICmp);
361 assert!(kept, "the comparison was folded away and issue 352 says it must not be");
362 }
363 }
364
365 #[test]
366 fn a_branch_on_something_nobody_knows_is_left_alone() {
367 let mut names = Interner::new();
368 let mut func = Func::new(names.intern("f"), Signature::new().with_params(&[Type::int(1)]));
369 let entry = func.create_block();
370 let then_block = func.create_block();
371 let else_block = func.create_block();
372 let cond = func.append_param(entry, Type::int(1));
373 let mut build = Builder::new(&mut func, entry);
374 build.br_if(cond, then_block, &[], else_block, &[]);
375 for arm in [then_block, else_block] {
376 let mut build = Builder::new(&mut func, arm);
377 build.ret(&[]);
378 }
379 let stats = simplify(&mut func);
380 assert!(!stats.changed());
381 assert!(stats.is_empty(), "a pass with nothing to say should say nothing");
382 assert_eq!(terminator(&func, 0), Opcode::BrIf);
383 assert_eq!(blocks(&func), [0, 1, 2]);
384 }
385
386 #[test]
387 fn a_switch_on_a_constant_takes_the_case_that_matches() {
388 let mut names = Interner::new();
389 let mut func = Func::new(names.intern("f"), Signature::new());
390 let entry = func.create_block();
391 let default = func.create_block();
392 let first = func.create_block();
393 let second = func.create_block();
394 let mut build = Builder::new(&mut func, entry);
395 let value = build.iconst(Type::int(32), 5);
396 build.switch(value, default, &[(4, first), (5, second)]);
397 for arm in [default, first, second] {
398 let mut build = Builder::new(&mut func, arm);
399 build.ret(&[]);
400 }
401 assert!(simplify(&mut func).changed());
402 assert_eq!(terminator(&func, 0), Opcode::Jump);
403 assert_eq!(goes_to(&func, 0), [3]);
404 assert_eq!(blocks(&func), [0, 3]);
405 }
406
407 #[test]
408 fn a_switch_on_a_constant_no_case_names_takes_the_default() {
409 let mut names = Interner::new();
410 let mut func = Func::new(names.intern("f"), Signature::new());
411 let entry = func.create_block();
412 let default = func.create_block();
413 let case = func.create_block();
414 let mut build = Builder::new(&mut func, entry);
415 let value = build.iconst(Type::int(32), 9);
416 build.switch(value, default, &[(4, case)]);
417 for arm in [default, case] {
418 let mut build = Builder::new(&mut func, arm);
419 build.ret(&[]);
420 }
421 assert!(simplify(&mut func).changed());
422 assert_eq!(goes_to(&func, 0), [1]);
423 assert_eq!(blocks(&func), [0, 1]);
424 }
425
426 #[test]
427 fn the_arguments_travel_with_the_edge_that_survives() {
428 let mut names = Interner::new();
432 let mut func = Func::new(names.intern("f"), Signature::new());
433 let entry = func.create_block();
434 let join = func.create_block();
435 let param = func.append_param(join, Type::int(32));
436 let mut build = Builder::new(&mut func, entry);
437 let cond = build.iconst(Type::int(1), 0);
438 let taken = build.iconst(Type::int(32), 11);
439 let other = build.iconst(Type::int(32), 22);
440 build.br_if(cond, join, &[other], join, &[taken]);
441 let mut build = Builder::new(&mut func, join);
442 build.ret(&[]);
443 assert!(simplify(&mut func).changed());
444 let term = func.terminator(entry).expect("the entry has one");
445 let call = func.successors(term).next().expect("a jump goes somewhere");
446 assert_eq!(func[call.args], [taken]);
447 assert_eq!(func[join].params, [param]);
448 }
449
450 #[test]
451 fn a_block_the_dead_arm_shared_with_a_live_one_stays() {
452 let mut names = Interner::new();
455 let mut func = Func::new(names.intern("f"), Signature::new().with_params(&[Type::int(32)]));
456 let entry = func.create_block();
457 let dead = func.create_block();
458 let shared = func.create_block();
459 let exit = func.create_block();
460 let x = func.append_param(entry, Type::int(32));
461 let mut build = Builder::new(&mut func, entry);
462 let never = build.iconst(Type::int(1), 0);
463 build.switch(x, exit, &[(0, dead), (1, shared)]);
464 let mut build = Builder::new(&mut func, dead);
466 build.br_if(never, shared, &[], exit, &[]);
467 for arm in [shared, exit] {
468 let mut build = Builder::new(&mut func, arm);
469 build.ret(&[]);
470 }
471 let stats = simplify(&mut func);
472 assert!(stats.changed());
473 assert_eq!(terminator(&func, 0), Opcode::Switch);
476 assert_eq!(goes_to(&func, 1), [3]);
477 assert_eq!(blocks(&func), [0, 1, 2, 3]);
478 assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 0);
479 }
480
481 #[test]
482 fn a_block_whose_address_is_taken_is_not_removed() {
483 let mut names = Interner::new();
487 let mut func = Func::new(names.intern("f"), Signature::new());
488 let entry = func.create_block();
489 let labelled = func.create_block();
490 let arm = func.create_block();
491 let mut build = Builder::new(&mut func, entry);
492 let cond = build.iconst(Type::int(1), 1);
493 let addr = build.block_addr(labelled);
494 build.br_if(cond, arm, &[], labelled, &[]);
495 let mut build = Builder::new(&mut func, arm);
496 build.indirect_br(addr, &[labelled]);
497 let mut build = Builder::new(&mut func, labelled);
498 build.ret(&[]);
499 assert!(simplify(&mut func).changed());
500 assert_eq!(goes_to(&func, 0), [2]);
501 assert!(blocks(&func).contains(&1), "the labelled block went with the arm");
502 }
503
504 #[test]
505 fn a_block_only_an_unreachable_block_takes_the_address_of_goes_too() {
506 let mut names = Interner::new();
509 let mut func = Func::new(names.intern("f"), Signature::new());
510 let entry = func.create_block();
511 let dead = func.create_block();
512 let labelled = func.create_block();
513 let mut build = Builder::new(&mut func, entry);
514 let cond = build.iconst(Type::int(1), 1);
515 build.br_if(cond, entry, &[], dead, &[]);
516 let mut build = Builder::new(&mut func, dead);
517 let addr = build.block_addr(labelled);
518 build.indirect_br(addr, &[labelled]);
519 let mut build = Builder::new(&mut func, labelled);
520 build.ret(&[]);
521 assert!(simplify(&mut func).changed());
522 assert_eq!(blocks(&func), [0]);
523 }
524
525 #[test]
526 fn out_of_fuel_leaves_the_function_exactly_as_it_was() {
527 let mut func = diamond(|build| build.iconst(Type::int(1), 1));
528 let before = blocks(&func);
529 let stats = SimplifyCfg.run(&mut func, &mut Analyses::new(), &mut Fuel::of(0));
530 assert!(!stats.changed());
531 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
532 assert_eq!(terminator(&func, 0), Opcode::BrIf);
533 assert_eq!(blocks(&func), before);
534 }
535
536 #[test]
537 fn what_fuel_buys_is_one_whole_change_and_never_half_of_one() {
538 let mut func = graph(&[&[1, 2], &[3, 4], &[5], &[5], &[5], &[]]);
542 let stats = SimplifyCfg.run(&mut func, &mut Analyses::new(), &mut Fuel::of(1));
543 assert_eq!(stats.count(Kind::Optimized, super::FOLDED), 1);
544 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
545 assert_eq!(blocks(&func), [0, 1, 3, 4, 5]);
548 }
549
550 #[test]
551 fn the_pass_leaves_the_verifier_nothing_to_complain_about() {
552 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
553 let mut names = Interner::new();
554 let mut module = Module::new(names.intern("test.c"), &target);
555 let mut func = graph(&[&[1, 2], &[3], &[3], &[4, 1], &[]]);
556 simplify(&mut func);
557 module.add_func(func);
558 rucc_ir::verify(&module, &names).expect("the pass left the function verifiable");
559 }
560
561 #[test]
562 fn the_pass_says_it_preserves_nothing() {
563 assert_eq!(SimplifyCfg.preserves(), Preserved::NONE);
564 }
565}