1use rucc_ir::{Func, Inst, Opcode, Value};
60
61use crate::loops::{LoopId, Loops};
62use crate::scev::{Evolution, Invariant, Scev};
63use crate::{Analyses, Fuel, Pass, Preserved, Stats};
64
65const POPULATION: &str =
66 "loop nest two or more deep, perfectly nested, every address in it a straight line";
67const NOT_AFFINE: &str =
68 "loop nest two or more deep, perfectly nested, an address in it is not a straight line";
69const NOT_PERFECT: &str = "loop nest, but not perfectly nested, something sits between the loops";
70const ALONE: &str = "loop with no loop inside it";
71const REFERENCE: &str = "read or write in the innermost loop of a perfect nest";
72
73#[derive(Debug)]
75pub struct Nests;
76
77impl Pass for Nests {
78 fn name(&self) -> &'static str {
79 "nests"
80 }
81
82 fn describe(&self) -> &'static str {
83 "counts the loop nests, and changes nothing"
84 }
85
86 fn preserves(&self) -> Preserved {
87 Preserved::ALL
89 }
90
91 fn run(&self, func: &mut Func, an: &mut Analyses, _fuel: &mut Fuel) -> Stats {
92 let mut stats = Stats::new();
93 if func.entry().is_none() {
94 return stats;
95 }
96 let cfg = an.cfg(func).clone();
97 let loops = an.loops(func).clone();
98 let mut scev = Scev::new(func, &cfg, &loops);
99 for id in loops.all() {
100 if loops.parent(id).is_some() {
101 continue;
102 }
103 match chain(func, &loops, id) {
104 Chain::Broken => stats.note(NOT_PERFECT),
105 Chain::Perfect(nest) => report(func, &loops, &mut scev, &nest, &mut stats),
106 }
107 }
108 stats
109 }
110}
111
112enum Chain {
114 Perfect(Vec<LoopId>),
116 Broken,
118}
119
120fn chain(func: &Func, loops: &Loops, outer: LoopId) -> Chain {
122 let mut nest = vec![outer];
123 let mut at = outer;
124 loop {
125 let inside = loops.children(at);
126 let [only] = inside else {
127 return match inside.is_empty() {
128 true => Chain::Perfect(nest),
129 false => Chain::Broken,
130 };
131 };
132 if between(func, loops, at, *only) {
133 return Chain::Broken;
134 }
135 nest.push(*only);
136 at = *only;
137 }
138}
139
140fn between(func: &Func, loops: &Loops, outer: LoopId, inner: LoopId) -> bool {
142 loops
143 .blocks(outer)
144 .iter()
145 .filter(|&&block| !loops.contains(inner, block))
146 .flat_map(|&block| func.insts(block))
147 .any(|inst| func[inst].opcode.touches_memory())
148}
149
150fn report(func: &Func, loops: &Loops, scev: &mut Scev<'_>, nest: &[LoopId], stats: &mut Stats) {
152 let Some(&innermost) = nest.last() else { return };
153 if nest.len() < 2 {
154 stats.note(ALONE);
155 return;
156 }
157 let mut affine = true;
158 let touching: Vec<Inst> = loops
159 .blocks(innermost)
160 .iter()
161 .flat_map(|&block| func.insts(block))
162 .filter(|&inst| func[inst].opcode.touches_memory())
163 .collect();
164 for inst in touching {
165 stats.note(REFERENCE);
166 affine &= match address(func, inst) {
167 None => false,
170 Some(addr) => straight(scev, nest, addr),
171 };
172 }
173 stats.note(if affine { POPULATION } else { NOT_AFFINE });
174}
175
176fn address(func: &Func, inst: Inst) -> Option<Value> {
178 let data = func[inst];
179 let args = &func[data.args];
180 match data.opcode {
181 Opcode::Load => args.first().copied(),
182 Opcode::Store => args.get(1).copied(),
183 _ => None,
184 }
185}
186
187fn straight(scev: &mut Scev<'_>, nest: &[LoopId], value: Value) -> bool {
194 let Some((&innermost, outer)) = nest.split_last() else { return true };
195 match scev.evolution(innermost, value) {
196 Evolution::Unknown => false,
197 Evolution::Invariant(inv) => part(scev, outer, inv),
198 Evolution::Affine(chrec) => part(scev, outer, chrec.base) && part(scev, outer, chrec.step),
199 }
200}
201
202fn part(scev: &mut Scev<'_>, outer: &[LoopId], inv: Invariant) -> bool {
205 let rest = match inv.on() {
206 Some((on, rest)) => {
211 if !on.value().is_none_or(|on| straight(scev, outer, on)) {
212 return false;
213 }
214 rest
215 }
216 None => inv.plain().expect("an invariant not measured from a value is a plain one"),
217 };
218 match rest.value {
219 None => true,
220 Some(value) => straight(scev, outer, value),
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use rucc_base::Interner;
227 use rucc_ir::{
228 Block, Builder, Flags, Func, IntPred, MemInfo, MemOrder, Opcode, Restrict, Signature, Type,
229 Value,
230 };
231
232 use super::{ALONE, NOT_AFFINE, NOT_PERFECT, Nests, POPULATION, REFERENCE};
233 use crate::stats::Kind;
234 use crate::{Fuel, Pass, Stats};
235
236 fn survey(func: &mut Func) -> Stats {
238 Nests.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
239 }
240
241 fn plain() -> MemInfo {
243 MemInfo {
244 size: 0,
245 align: 4,
246 order: MemOrder::NotAtomic,
247 tbaa: None,
248 restrict: Restrict::NONE,
249 }
250 }
251
252 struct Counted {
254 head: Block,
255 body: Block,
256 out: Block,
257 counter: Value,
258 }
259
260 fn counted(func: &mut Func, into: Block, limit: i128) -> Counted {
271 let head = func.create_block();
272 let body = func.create_block();
273 let out = func.create_block();
274 let i = func.append_param(head, Type::int(32));
275 let carried = func.append_param(body, Type::int(32));
276
277 let mut build = Builder::new(func, into);
278 let zero = build.iconst(Type::int(32), 0);
279 build.jump(head, &[zero]);
280
281 let mut build = Builder::new(func, head);
282 let stop = build.iconst(Type::int(32), limit);
283 let test = build.icmp(IntPred::Slt, i, stop);
284 build.br_if(test, body, &[i], out, &[]);
285
286 Counted { head, body, out, counter: carried }
287 }
288
289 fn close(func: &mut Func, it: &Counted, at: Block) {
294 let mut build = Builder::new(func, at);
295 let one = build.iconst(Type::int(32), 1);
296 let next = build.binary(Opcode::Add, it.counter, one, Flags::NSW);
297 build.jump(it.head, &[next]);
298 }
299
300 fn shell(names: &mut Interner) -> (Func, Block, Value) {
302 let signature = Signature::new().with_params(&[Type::PTR]);
303 let mut func = Func::new(names.intern("f"), signature);
304 let entry = func.create_block();
305 let base = func.append_param(entry, Type::PTR);
306 (func, entry, base)
307 }
308
309 #[test]
310 fn a_loop_with_nothing_inside_it_is_not_a_nest() {
311 let mut names = Interner::new();
312 let (mut func, entry, _) = shell(&mut names);
313 let it = counted(&mut func, entry, 8);
314 close(&mut func, &it, it.body);
315 Builder::new(&mut func, it.out).ret(&[]);
316
317 let stats = survey(&mut func);
318 assert_eq!(stats.count(Kind::Note, ALONE), 1);
319 assert_eq!(stats.count(Kind::Note, POPULATION), 0);
320 assert!(!stats.changed(), "the survey rewrites nothing");
321 }
322
323 #[test]
324 fn two_loops_walking_a_row_at_a_time_are_the_population() {
325 let mut names = Interner::new();
326 let (mut func, entry, base) = shell(&mut names);
327 let outer = counted(&mut func, entry, 4);
328
329 let mut build = Builder::new(&mut func, outer.body);
332 let wide = build.unary(Opcode::SExt, outer.counter, Type::int(64));
333 let stride = build.iconst(Type::int(64), 256);
334 let along = build.binary(Opcode::Mul, wide, stride, Flags::NSW);
335 let row = build.binary(Opcode::PtrAdd, base, along, Flags::NONE);
336
337 let inner = counted(&mut func, outer.body, 3);
338 let mut build = Builder::new(&mut func, inner.body);
340 let step = build.unary(Opcode::SExt, inner.counter, Type::int(64));
341 let four = build.iconst(Type::int(64), 4);
342 let by = build.binary(Opcode::Mul, step, four, Flags::NSW);
343 let addr = build.binary(Opcode::PtrAdd, row, by, Flags::NONE);
344 build.store(inner.counter, addr, plain(), Flags::NONE);
345 close(&mut func, &inner, inner.body);
346 close(&mut func, &outer, inner.out);
347 Builder::new(&mut func, outer.out).ret(&[]);
348
349 let stats = survey(&mut func);
350 assert_eq!(stats.count(Kind::Note, POPULATION), 1);
351 assert_eq!(stats.count(Kind::Note, NOT_AFFINE), 0);
352 assert_eq!(stats.count(Kind::Note, REFERENCE), 1);
353 }
354
355 #[test]
366 fn an_address_added_from_both_counters_is_one_this_compiler_can_describe() {
367 let mut names = Interner::new();
368 let (mut func, entry, base) = shell(&mut names);
369 let outer = counted(&mut func, entry, 4);
370 let inner = counted(&mut func, outer.body, 3);
371
372 let mut build = Builder::new(&mut func, inner.body);
373 let sum = build.binary(Opcode::Add, outer.counter, inner.counter, Flags::NSW);
374 let wide = build.unary(Opcode::SExt, sum, Type::int(64));
375 let addr = build.binary(Opcode::PtrAdd, base, wide, Flags::NONE);
376 build.store(outer.counter, addr, plain(), Flags::NONE);
377 close(&mut func, &inner, inner.body);
378 close(&mut func, &outer, inner.out);
379 Builder::new(&mut func, outer.out).ret(&[]);
380
381 let stats = survey(&mut func);
382 assert_eq!(stats.count(Kind::Note, NOT_AFFINE), 0);
383 assert_eq!(stats.count(Kind::Note, POPULATION), 1);
384 }
385
386 #[test]
387 fn a_write_between_the_two_loops_stops_it_being_a_nest() {
388 let mut names = Interner::new();
389 let (mut func, entry, base) = shell(&mut names);
390 let outer = counted(&mut func, entry, 4);
391 let inner = counted(&mut func, outer.body, 3);
392
393 Builder::new(&mut func, inner.body).store(inner.counter, base, plain(), Flags::NONE);
394 close(&mut func, &inner, inner.body);
395 Builder::new(&mut func, inner.out).store(outer.counter, base, plain(), Flags::NONE);
397 close(&mut func, &outer, inner.out);
398 Builder::new(&mut func, outer.out).ret(&[]);
399
400 let stats = survey(&mut func);
401 assert_eq!(stats.count(Kind::Note, NOT_PERFECT), 1);
402 assert_eq!(stats.count(Kind::Note, POPULATION), 0);
403 }
404
405 #[test]
406 fn an_address_that_came_out_of_memory_is_not_a_straight_line() {
407 let mut names = Interner::new();
408 let (mut func, entry, base) = shell(&mut names);
409 let outer = counted(&mut func, entry, 4);
410 let inner = counted(&mut func, outer.body, 3);
411
412 let mut build = Builder::new(&mut func, inner.body);
414 let addr = build.load(Type::PTR, base, plain(), Flags::NONE);
415 build.store(inner.counter, addr, plain(), Flags::NONE);
416 close(&mut func, &inner, inner.body);
417 close(&mut func, &outer, inner.out);
418 Builder::new(&mut func, outer.out).ret(&[]);
419
420 let stats = survey(&mut func);
421 assert_eq!(stats.count(Kind::Note, NOT_AFFINE), 1);
422 assert_eq!(stats.count(Kind::Note, POPULATION), 0);
423 assert_eq!(stats.count(Kind::Note, REFERENCE), 2, "the load and the write both count");
424 }
425}