rucc_opt/loops.rs
1//! The loop forest: what loops there are, how they nest, and what is in a cycle that is not one.
2//!
3//! Design: `spec/optimizer/07-loops-and-scev.md` sections 7.1 through 7.3 and 7.6.
4//!
5//! A back edge is an edge whose head dominates its tail, the natural loop of a back edge is the
6//! set of blocks that reach the tail without passing through the head, and natural loops with
7//! different headers are either disjoint or one contains the other. That is the textbook
8//! construction and it needs the dominator tree and nothing else.
9//!
10//! What is built here is the same set of loops by a different route, because the textbook route
11//! answers three questions with three walks and this one answers them with one. Take the
12//! strongly connected components of the graph. A component with a cycle in it is either a
13//! natural loop or an irreducible region, and which one it is comes down to a single question:
14//! whether the nearest block dominating all of it is one of its own blocks. If it is, that block
15//! is the header, every way into the component goes through it, and the component is exactly the
16//! natural loop of the back edges arriving at it. If it is not, the component has two ways in
17//! and is what `goto` into a loop body produces. Taking the header out and doing it again finds
18//! what is nested inside, so the nesting falls out of the recursion rather than being worked out
19//! afterwards by comparing block sets.
20//!
21//! Section 7.1's other decision is here by omission: two back edges to one header are two
22//! latches of one loop and this does not try to guess whether one of them is really an inner
23//! loop's. GCC guesses, from the profile if it has one and from induction variables if it does
24//! not. rucc requires a single latch as a canonical form instead, and the canonicalizer in
25//! document 26 creates one, which is an edit rather than a guess and is always right.
26
27use rucc_ir::{Block, Def, Func, Value};
28
29use crate::cfg::Cfg;
30use crate::dom::Dominators;
31
32/// One loop, by number.
33///
34/// A handle rather than a reference, because a loop's parent and children are loops and a tree
35/// of references to each other is not a thing to hand a pass.
36#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub struct LoopId(u32);
38
39impl LoopId {
40 /// Its number, for indexing something the caller keeps alongside.
41 #[must_use]
42 pub fn index(self) -> usize {
43 self.0 as usize
44 }
45}
46
47/// An edge that leaves a loop.
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub struct Exit {
50 /// The block inside the loop the edge leaves from.
51 pub from: Block,
52 /// The block outside the loop it arrives at.
53 pub to: Block,
54}
55
56/// What is known about one loop.
57#[derive(Clone, Debug, PartialEq, Eq)]
58struct LoopData {
59 header: Block,
60 /// Every block in the loop, including the blocks of the loops nested in it.
61 blocks: Vec<Block>,
62 /// The blocks with a back edge to the header. Canonical form wants exactly one.
63 latches: Vec<Block>,
64 /// Every edge out of the loop, cached because every loop pass asks and the alternative is
65 /// a walk of the body each time.
66 exits: Vec<Exit>,
67 parent: Option<LoopId>,
68 children: Vec<LoopId>,
69 depth: u32,
70}
71
72/// The loops of a function, nested.
73#[derive(Clone, Debug, PartialEq, Eq)]
74pub struct Loops {
75 loops: Vec<LoopData>,
76 /// The innermost loop holding each block, indexed by block number.
77 innermost: Vec<Option<LoopId>>,
78 /// The loops nothing encloses.
79 roots: Vec<LoopId>,
80 /// Blocks in a cycle that is not a natural loop, in block order.
81 irreducible: Vec<Block>,
82}
83
84impl Loops {
85 /// Finds the loops.
86 ///
87 /// Linear in the graph per level of nesting, so linear with a small constant on the code
88 /// people write. Section 7.8 says this is not the part of loop analysis to worry about.
89 #[must_use]
90 pub fn new(cfg: &Cfg, doms: &Dominators) -> Self {
91 let mut build = Build::new(cfg, doms);
92 let region: Vec<Block> = cfg.postorder().to_vec();
93 build.region(®ion, None);
94 build.finish()
95 }
96
97 /// How many loops there are, counting nested ones.
98 #[must_use]
99 pub fn count(&self) -> usize {
100 self.loops.len()
101 }
102
103 /// Every loop, outer before inner.
104 pub fn all(&self) -> impl Iterator<Item = LoopId> + use<> {
105 (0..self.loops.len()).map(|index| LoopId(index as u32))
106 }
107
108 /// The loops nothing encloses.
109 #[must_use]
110 pub fn roots(&self) -> &[LoopId] {
111 &self.roots
112 }
113
114 /// The one block every path into the loop arrives at.
115 #[must_use]
116 pub fn header(&self, id: LoopId) -> Block {
117 self.loops[id.index()].header
118 }
119
120 /// Every block in the loop, including the blocks of the loops nested in it.
121 #[must_use]
122 pub fn blocks(&self, id: LoopId) -> &[Block] {
123 &self.loops[id.index()].blocks
124 }
125
126 /// The blocks with an edge back to the header.
127 ///
128 /// Canonical form wants exactly one of these, and section 7.3 says why: it is what makes
129 /// "the last thing that happens in an iteration" a place rather than a question.
130 #[must_use]
131 pub fn latches(&self, id: LoopId) -> &[Block] {
132 &self.loops[id.index()].latches
133 }
134
135 /// Every edge that leaves the loop.
136 #[must_use]
137 pub fn exits(&self, id: LoopId) -> &[Exit] {
138 &self.loops[id.index()].exits
139 }
140
141 /// The loop this one is nested in.
142 #[must_use]
143 pub fn parent(&self, id: LoopId) -> Option<LoopId> {
144 self.loops[id.index()].parent
145 }
146
147 /// The loops nested directly in this one.
148 #[must_use]
149 pub fn children(&self, id: LoopId) -> &[LoopId] {
150 &self.loops[id.index()].children
151 }
152
153 /// How many loops enclose this one, counting from zero for one nothing encloses.
154 #[must_use]
155 pub fn depth(&self, id: LoopId) -> u32 {
156 self.loops[id.index()].depth
157 }
158
159 /// The innermost loop holding this block, if any holds it.
160 #[must_use]
161 pub fn innermost(&self, block: Block) -> Option<LoopId> {
162 *self.innermost.get(block.index()).unwrap_or(&None)
163 }
164
165 /// Whether the block is in this loop or in a loop nested in it.
166 #[must_use]
167 pub fn contains(&self, id: LoopId, block: Block) -> bool {
168 let mut walk = self.innermost(block);
169 while let Some(inner) = walk {
170 if inner == id {
171 return true;
172 }
173 walk = self.parent(inner);
174 }
175 false
176 }
177
178 /// The block outside the loop that every path in comes through, when there is exactly one
179 /// such block and its only successor is the header.
180 ///
181 /// This is the preheader of section 7.3, which is where loop invariant code motion puts
182 /// what it hoists. `None` means the loop is not in canonical form yet, and the answer is to
183 /// run the canonicalizer rather than to split an edge here.
184 #[must_use]
185 pub fn preheader(&self, cfg: &Cfg, id: LoopId) -> Option<Block> {
186 let header = self.header(id);
187 let mut outside = cfg.predecessors(header).iter().filter(|&&pred| !self.contains(id, pred));
188 let candidate = *outside.next()?;
189 if outside.next().is_some() || cfg.successors(candidate).len() != 1 {
190 return None;
191 }
192 Some(candidate)
193 }
194
195 /// Blocks that are in a cycle and in no natural loop.
196 ///
197 /// These are the irreducible regions of section 7.1, which is what `goto` into a loop body
198 /// and some state machines written as a `switch` inside a `for` produce. rucc does not turn
199 /// them into reducible ones: node splitting can blow up code size exponentially and the
200 /// payoff is a handful of loop optimizations applying to code that is rare and usually
201 /// cold. GCC does not do it either. The analysis reports them, the loop passes decline
202 /// them, and the value level passes are unaffected because they only need dominance.
203 ///
204 /// The search stops at an irreducible region rather than looking inside it, so a back edge
205 /// buried in one does not become a loop even when its head dominates its tail. That loses a
206 /// self loop inside a two entry region and nothing else anyone has produced, and it loses
207 /// nothing in practice because a loop pass skips those blocks either way. What it buys is
208 /// that "in a loop" and "irreducible" are decided by one walk, so they cannot disagree.
209 #[must_use]
210 pub fn irreducible(&self) -> &[Block] {
211 &self.irreducible
212 }
213
214 /// Whether this block is in a cycle that is not a natural loop.
215 #[must_use]
216 pub fn is_irreducible(&self, block: Block) -> bool {
217 self.irreducible.binary_search_by_key(&block.index(), |b| b.index()).is_ok()
218 }
219
220 /// Whether the value is the same on every iteration of this loop.
221 ///
222 /// The second of the four questions section 7.6 says this analysis answers. A value is
223 /// invariant when what defines it is outside the loop, which covers the function's
224 /// arguments and everything computed before the loop was entered. A value defined inside
225 /// can still be invariant, when everything it reads is, and answering that is a walk this
226 /// deliberately does not do: the caller that wants it is loop invariant code motion, which
227 /// has to walk the body in order anyway and gets the transitive answer for free as it goes.
228 #[must_use]
229 pub fn is_invariant(&self, func: &Func, id: LoopId, value: Value) -> bool {
230 let block = match func[value].def {
231 Def::Result { inst, .. } => func.block_of(inst),
232 Def::Param { block, .. } => Some(block),
233 };
234 // A value whose defining instruction is in no block was removed, and nothing should be
235 // asking about it. Saying it is not invariant is the answer that stops a caller acting
236 // on it.
237 block.is_some_and(|block| !self.contains(id, block))
238 }
239
240 /// What is wrong with the forest, which on a forest this built is nothing.
241 ///
242 /// Section 7.2 asks for the equivalent of GCC's `verify_loop_structure`, and it is a
243 /// separate thing from document 04.3's check that a pass did not lie about what it
244 /// preserved. That one catches a pass claiming to have kept the forest when it changed the
245 /// graph under it. This one catches a pass that rebuilt the forest into something malformed,
246 /// which is a different mistake and is the one that follows from an edit near a header.
247 ///
248 /// This does not check canonical form. A preheader, a single latch, loop-closed SSA and a
249 /// dedicated exit are what document 26's canonicalizer establishes before the loop pipeline
250 /// runs, and a forest read off an arbitrary function has none of them.
251 #[must_use]
252 pub fn problems(&self, cfg: &Cfg, doms: &Dominators) -> Vec<String> {
253 let mut found = Vec::new();
254 for id in self.all() {
255 let header = self.header(id);
256 for &block in self.blocks(id) {
257 if !doms.dominates(header, block) {
258 found.push(format!("loop {} holds a block its header does not dominate", id.0));
259 break;
260 }
261 }
262 if self.latches(id).is_empty() {
263 found.push(format!("loop {} has no way back to its header", id.0));
264 }
265 for &latch in self.latches(id) {
266 if !cfg.successors(latch).contains(&header) {
267 found.push(format!("loop {} has a latch that does not reach its header", id.0));
268 break;
269 }
270 }
271 for exit in self.exits(id) {
272 if !self.contains(id, exit.from) || self.contains(id, exit.to) {
273 found.push(format!("loop {} has an exit that does not leave it", id.0));
274 break;
275 }
276 }
277 if let Some(parent) = self.parent(id) {
278 if !self.blocks(id).iter().all(|&block| self.contains(parent, block)) {
279 found.push(format!("loop {} is not inside the loop it says it is in", id.0));
280 }
281 if self.depth(id) != self.depth(parent) + 1 {
282 found.push(format!("loop {} is not one deeper than its parent", id.0));
283 }
284 } else if self.depth(id) != 0 {
285 found.push(format!("loop {} has a depth and nothing to be deep inside", id.0));
286 }
287 }
288 found
289 }
290}
291
292/// The state of one construction, which recurses into what it finds.
293struct Build<'a> {
294 cfg: &'a Cfg,
295 doms: &'a Dominators,
296 loops: Vec<LoopData>,
297 innermost: Vec<Option<LoopId>>,
298 roots: Vec<LoopId>,
299 irreducible: Vec<Block>,
300 /// Whether each block is in the region being looked at, so an edge out of it is skipped in
301 /// O(1) rather than by searching the region.
302 inside: Vec<bool>,
303 /// Tarjan's numbering, and the lowest one reachable from each block.
304 index: Vec<u32>,
305 low: Vec<u32>,
306 stacked: Vec<bool>,
307}
308
309/// A block Tarjan's walk has not numbered yet.
310const UNVISITED: u32 = u32::MAX;
311
312impl<'a> Build<'a> {
313 fn new(cfg: &'a Cfg, doms: &'a Dominators) -> Self {
314 let blocks = cfg.capacity();
315 Self {
316 cfg,
317 doms,
318 loops: Vec::new(),
319 innermost: vec![None; blocks],
320 roots: Vec::new(),
321 irreducible: Vec::new(),
322 inside: vec![false; blocks],
323 index: vec![UNVISITED; blocks],
324 low: vec![0; blocks],
325 stacked: vec![false; blocks],
326 }
327 }
328
329 /// Finds the loops of one region and then of what is left of each of them.
330 ///
331 /// The region of the first call is every block control reaches. The region of a later one is
332 /// a loop with its header taken out, which is the graph the loops nested in it live in.
333 fn region(&mut self, region: &[Block], parent: Option<LoopId>) {
334 for &block in region {
335 self.inside[block.index()] = true;
336 self.index[block.index()] = UNVISITED;
337 self.stacked[block.index()] = false;
338 }
339 let components = self.components(region);
340 for &block in region {
341 self.inside[block.index()] = false;
342 }
343
344 for component in components {
345 // The nearest block dominating all of it. If it is one of the component's own
346 // blocks then every path in arrives there, because a block outside the component
347 // that the header dominates is a block the header reaches and that reaches back
348 // into the component, which would put it in the component. So a header inside means
349 // one way in, which is what reducible means.
350 let Some(header) = component
351 .iter()
352 .copied()
353 .try_fold(component[0], |a, b| self.doms.nearest_common_dominator(a, b))
354 else {
355 continue;
356 };
357 if !component.contains(&header) {
358 self.irreducible.extend_from_slice(&component);
359 continue;
360 }
361 let id = self.record(header, component, parent);
362 let inner: Vec<Block> =
363 self.loops[id.index()].blocks.iter().copied().filter(|&b| b != header).collect();
364 if !inner.is_empty() {
365 self.region(&inner, Some(id));
366 }
367 }
368 }
369
370 /// Adds a loop, and says which blocks are in it.
371 fn record(&mut self, header: Block, blocks: Vec<Block>, parent: Option<LoopId>) -> LoopId {
372 let id = LoopId(self.loops.len() as u32);
373 let mut latches = Vec::new();
374 let mut exits = Vec::new();
375 for &block in &blocks {
376 // Every block of the loop belongs to it until an inner call says otherwise, and an
377 // inner call runs after this, so the last writer is the innermost loop.
378 self.innermost[block.index()] = Some(id);
379 if self.cfg.successors(block).contains(&header) {
380 latches.push(block);
381 }
382 for &next in self.cfg.successors(block) {
383 if !blocks.contains(&next) {
384 exits.push(Exit { from: block, to: next });
385 }
386 }
387 }
388 let depth = parent.map_or(0, |parent| self.loops[parent.index()].depth + 1);
389 self.loops.push(LoopData {
390 header,
391 blocks,
392 latches,
393 exits,
394 parent,
395 children: Vec::new(),
396 depth,
397 });
398 match parent {
399 Some(parent) => self.loops[parent.index()].children.push(id),
400 None => self.roots.push(id),
401 }
402 id
403 }
404
405 /// The strongly connected components of the region that have a cycle in them.
406 ///
407 /// Tarjan's algorithm, with the recursion written out, because the depth of the walk is the
408 /// length of the longest path in the function and a long C function has one.
409 fn components(&mut self, region: &[Block]) -> Vec<Vec<Block>> {
410 let mut found = Vec::new();
411 let mut next = 0;
412 let mut component: Vec<Block> = Vec::new();
413 let mut walk: Vec<(Block, usize)> = Vec::new();
414 for &start in region {
415 if self.index[start.index()] != UNVISITED {
416 continue;
417 }
418 self.enter(start, &mut next, &mut component);
419 walk.push((start, 0));
420 while let Some((block, step)) = walk.pop() {
421 let successors = self.cfg.successors(block);
422 if step < successors.len() {
423 let next_block = successors[step];
424 walk.push((block, step + 1));
425 if !self.inside[next_block.index()] {
426 continue;
427 }
428 if self.index[next_block.index()] == UNVISITED {
429 self.enter(next_block, &mut next, &mut component);
430 walk.push((next_block, 0));
431 } else if self.stacked[next_block.index()] {
432 let seen = self.index[next_block.index()];
433 self.low[block.index()] = self.low[block.index()].min(seen);
434 }
435 continue;
436 }
437 if self.low[block.index()] == self.index[block.index()] {
438 let start = component.iter().rposition(|&b| b == block).expect("on the stack");
439 let members: Vec<Block> = component.split_off(start);
440 for &member in &members {
441 self.stacked[member.index()] = false;
442 }
443 if members.len() > 1 || self.cfg.successors(block).contains(&block) {
444 found.push(members);
445 }
446 }
447 if let Some(&(above, _)) = walk.last() {
448 let reached = self.low[block.index()];
449 self.low[above.index()] = self.low[above.index()].min(reached);
450 }
451 }
452 }
453 found
454 }
455
456 /// Numbers a block and puts it on the component stack.
457 fn enter(&mut self, block: Block, next: &mut u32, component: &mut Vec<Block>) {
458 self.index[block.index()] = *next;
459 self.low[block.index()] = *next;
460 *next += 1;
461 component.push(block);
462 self.stacked[block.index()] = true;
463 }
464
465 fn finish(mut self) -> Loops {
466 self.irreducible.sort_unstable_by_key(|block| block.index());
467 self.irreducible.dedup();
468 Loops {
469 loops: self.loops,
470 innermost: self.innermost,
471 roots: self.roots,
472 irreducible: self.irreducible,
473 }
474 }
475}
476
477#[cfg(test)]
478mod tests {
479 use rucc_ir::Block;
480
481 use crate::cfg::Cfg;
482 use crate::dom::Dominators;
483 use crate::loops::Loops;
484 use crate::testing::graph;
485
486 /// Block number `n`, spelled the way the tests read.
487 fn b(n: usize) -> Block {
488 Block::from_usize(n)
489 }
490
491 /// The forest of a graph, along with the graph and the tree it was read from.
492 fn forest(edges: &[&[usize]]) -> (Cfg, Loops) {
493 let func = graph(edges);
494 let cfg = Cfg::new(&func);
495 let doms = Dominators::new(&cfg);
496 let loops = Loops::new(&cfg, &doms);
497 assert_eq!(loops.problems(&cfg, &doms), Vec::<String>::new());
498 (cfg, loops)
499 }
500
501 /// The blocks of a loop, as sorted block numbers.
502 fn blocks(loops: &Loops, id: crate::loops::LoopId) -> Vec<usize> {
503 let mut list: Vec<usize> = loops.blocks(id).iter().map(|b| b.index()).collect();
504 list.sort_unstable();
505 list
506 }
507
508 #[test]
509 fn a_function_with_no_cycle_has_no_loops() {
510 let (_, loops) = forest(&[&[1, 2], &[3], &[3], &[]]);
511 assert_eq!(loops.count(), 0);
512 assert!(loops.innermost(b(1)).is_none());
513 assert!(loops.irreducible().is_empty());
514 }
515
516 #[test]
517 fn a_block_that_branches_to_itself_is_a_loop() {
518 let (_, loops) = forest(&[&[1], &[1, 2], &[]]);
519 assert_eq!(loops.count(), 1);
520 let id = loops.roots()[0];
521 assert_eq!(loops.header(id), b(1));
522 assert_eq!(blocks(&loops, id), [1]);
523 assert_eq!(loops.latches(id), [b(1)]);
524 assert_eq!(loops.exits(id), [crate::loops::Exit { from: b(1), to: b(2) }]);
525 }
526
527 #[test]
528 fn a_loop_holds_its_body_and_names_its_latch() {
529 // 0 -> 1, 1 branches to the body 2 and the exit 3, 2 goes back to 1.
530 let (cfg, loops) = forest(&[&[1], &[2, 3], &[1], &[]]);
531 let id = loops.roots()[0];
532 assert_eq!(loops.header(id), b(1));
533 assert_eq!(blocks(&loops, id), [1, 2]);
534 assert_eq!(loops.latches(id), [b(2)]);
535 assert_eq!(loops.depth(id), 0);
536 assert_eq!(loops.preheader(&cfg, id), Some(b(0)));
537 }
538
539 #[test]
540 fn a_loop_inside_a_loop_is_a_child_of_it() {
541 // 0 -> 1; the outer header 1 goes to the inner header 2 and to the exit 4; 2 goes to 3
542 // and back to 2; 3 goes back to 1.
543 let (_, loops) = forest(&[&[1], &[2, 4], &[2, 3], &[1], &[]]);
544 assert_eq!(loops.count(), 2);
545 let outer = loops.roots()[0];
546 assert_eq!(loops.header(outer), b(1));
547 assert_eq!(blocks(&loops, outer), [1, 2, 3]);
548 assert_eq!(loops.children(outer).len(), 1);
549 let inner = loops.children(outer)[0];
550 assert_eq!(loops.header(inner), b(2));
551 assert_eq!(blocks(&loops, inner), [2]);
552 assert_eq!(loops.depth(inner), 1);
553 assert_eq!(loops.parent(inner), Some(outer));
554 // The innermost loop of a block is the one it is deepest inside, and the block set of
555 // the outer loop still holds it.
556 assert_eq!(loops.innermost(b(2)), Some(inner));
557 assert!(loops.contains(outer, b(2)));
558 assert!(!loops.contains(inner, b(3)));
559 }
560
561 #[test]
562 fn two_back_edges_to_one_header_are_two_latches_of_one_loop() {
563 // 1 is the header, 2 and 3 both go back to it. GCC would try to work out whether one of
564 // them is really an inner loop's latch. This does not guess: they are two latches, the
565 // canonical form wants one, and the canonicalizer is what makes one.
566 let (_, loops) = forest(&[&[1], &[2, 3], &[1], &[1, 4], &[]]);
567 assert_eq!(loops.count(), 1);
568 let id = loops.roots()[0];
569 let mut latches: Vec<usize> = loops.latches(id).iter().map(|b| b.index()).collect();
570 latches.sort_unstable();
571 assert_eq!(latches, [2, 3]);
572 }
573
574 #[test]
575 fn a_two_entry_loop_is_irreducible_and_is_not_a_loop() {
576 // The classic one. Block 0 branches into the cycle at 1 or at 2, so neither of them
577 // dominates the other and neither is a header.
578 let (_, loops) = forest(&[&[1, 2], &[2], &[1, 3], &[]]);
579 assert_eq!(loops.count(), 0);
580 assert_eq!(loops.irreducible(), [b(1), b(2)]);
581 assert!(loops.is_irreducible(b(1)));
582 assert!(!loops.is_irreducible(b(0)));
583 }
584
585 #[test]
586 fn an_irreducible_region_inside_a_loop_is_found_and_the_loop_is_still_a_loop() {
587 // The outer loop 1 -> {2, 3} -> {2, 3} -> 4 -> 1 is a natural loop, and the cycle
588 // between 2 and 3 has two ways in. Taking the header out and looking again is what
589 // finds the second one, which is the reason the search recurses rather than reporting
590 // whatever is left over at the end.
591 let (_, loops) = forest(&[&[1], &[2, 3], &[3, 4], &[2, 4], &[1, 5], &[]]);
592 assert_eq!(loops.count(), 1);
593 let id = loops.roots()[0];
594 assert_eq!(loops.header(id), b(1));
595 assert_eq!(blocks(&loops, id), [1, 2, 3, 4]);
596 assert_eq!(loops.irreducible(), [b(2), b(3)]);
597 }
598
599 #[test]
600 fn a_self_loop_inside_an_irreducible_region_is_declined_along_with_it() {
601 // 0 branches to 1 and 2, 1 goes back to 0 and on to 2, and 2 branches to 1 and to
602 // itself. The whole thing is one natural loop headed at 0. Inside it, the cycle between
603 // 1 and 2 has two ways in and is irreducible, and block 2's branch to itself is a back
604 // edge sitting in the middle of it.
605 //
606 // The textbook construction would report that back edge as a second loop. This does not,
607 // because it stops at the irreducible region rather than looking inside, and the answer
608 // it gives instead is that block 2 is irreducible. A loop pass reads that and leaves the
609 // block alone, which is what it would have done with a one block loop it was told not to
610 // touch. The property test in `tests/loops.rs` is where the difference is pinned down.
611 let (_, loops) = forest(&[&[1, 2], &[0, 2], &[1, 2]]);
612 assert_eq!(loops.count(), 1);
613 let id = loops.roots()[0];
614 assert_eq!(loops.header(id), b(0));
615 assert_eq!(blocks(&loops, id), [0, 1, 2]);
616 assert_eq!(loops.irreducible(), [b(1), b(2)]);
617 }
618
619 #[test]
620 fn a_loop_with_more_than_one_way_in_has_no_preheader() {
621 // Both 0 and 1 branch to the header, so there is no single block to hoist into.
622 let (cfg, loops) = forest(&[&[1, 2], &[2], &[2, 3], &[]]);
623 let id = loops.roots()[0];
624 assert_eq!(loops.header(id), b(2));
625 assert!(loops.preheader(&cfg, id).is_none());
626 }
627
628 #[test]
629 fn a_predecessor_that_goes_two_ways_is_not_a_preheader() {
630 // Block 0 branches to the header and to somewhere else, so hoisting into it would run
631 // the hoisted code on a path that never enters the loop.
632 let (cfg, loops) = forest(&[&[1, 3], &[1, 2], &[], &[]]);
633 let id = loops.roots()[0];
634 assert_eq!(loops.header(id), b(1));
635 assert!(loops.preheader(&cfg, id).is_none());
636 }
637
638 #[test]
639 fn an_unreachable_cycle_is_not_a_loop() {
640 // Blocks 2 and 3 are a cycle nothing reaches. Section 6.5 says an unreachable block is
641 // invisible to every analysis, and a loop nothing can enter is not a loop to unroll.
642 let (_, loops) = forest(&[&[1], &[], &[3], &[2]]);
643 assert_eq!(loops.count(), 0);
644 assert!(loops.irreducible().is_empty());
645 }
646
647 #[test]
648 fn a_value_defined_outside_the_loop_is_invariant_in_it() {
649 use rucc_base::Interner;
650 use rucc_ir::{Builder, Func, Signature, Type};
651
652 let mut names = Interner::new();
653 let mut func = Func::new(names.intern("f"), Signature::new());
654 let entry = func.create_block();
655 let header = func.create_block();
656 let after = func.create_block();
657
658 let mut build = Builder::new(&mut func, entry);
659 let outside = build.iconst(Type::int(32), 7);
660 build.jump(header, &[]);
661 let mut build = Builder::new(&mut func, header);
662 let inside = build.iconst(Type::int(32), 9);
663 let cond = build.iconst(Type::int(1), 1);
664 build.br_if(cond, header, &[], after, &[]);
665 let mut build = Builder::new(&mut func, after);
666 build.ret(&[]);
667
668 let cfg = Cfg::new(&func);
669 let loops = Loops::new(&cfg, &Dominators::new(&cfg));
670 let id = loops.roots()[0];
671 assert!(loops.is_invariant(&func, id, outside));
672 assert!(!loops.is_invariant(&func, id, inside));
673 }
674}