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 owns: 0,
249 restrict: Restrict::NONE,
250 }
251 }
252
253 struct Counted {
255 head: Block,
256 body: Block,
257 out: Block,
258 counter: Value,
259 }
260
261 fn counted(func: &mut Func, into: Block, limit: i128) -> Counted {
272 let head = func.create_block();
273 let body = func.create_block();
274 let out = func.create_block();
275 let i = func.append_param(head, Type::int(32));
276 let carried = func.append_param(body, Type::int(32));
277
278 let mut build = Builder::new(func, into);
279 let zero = build.iconst(Type::int(32), 0);
280 build.jump(head, &[zero]);
281
282 let mut build = Builder::new(func, head);
283 let stop = build.iconst(Type::int(32), limit);
284 let test = build.icmp(IntPred::Slt, i, stop);
285 build.br_if(test, body, &[i], out, &[]);
286
287 Counted { head, body, out, counter: carried }
288 }
289
290 fn close(func: &mut Func, it: &Counted, at: Block) {
295 let mut build = Builder::new(func, at);
296 let one = build.iconst(Type::int(32), 1);
297 let next = build.binary(Opcode::Add, it.counter, one, Flags::NSW);
298 build.jump(it.head, &[next]);
299 }
300
301 fn shell(names: &mut Interner) -> (Func, Block, Value) {
303 let signature = Signature::new().with_params(&[Type::PTR]);
304 let mut func = Func::new(names.intern("f"), signature);
305 let entry = func.create_block();
306 let base = func.append_param(entry, Type::PTR);
307 (func, entry, base)
308 }
309
310 #[test]
311 fn a_loop_with_nothing_inside_it_is_not_a_nest() {
312 let mut names = Interner::new();
313 let (mut func, entry, _) = shell(&mut names);
314 let it = counted(&mut func, entry, 8);
315 close(&mut func, &it, it.body);
316 Builder::new(&mut func, it.out).ret(&[]);
317
318 let stats = survey(&mut func);
319 assert_eq!(stats.count(Kind::Note, ALONE), 1);
320 assert_eq!(stats.count(Kind::Note, POPULATION), 0);
321 assert!(!stats.changed(), "the survey rewrites nothing");
322 }
323
324 #[test]
325 fn two_loops_walking_a_row_at_a_time_are_the_population() {
326 let mut names = Interner::new();
327 let (mut func, entry, base) = shell(&mut names);
328 let outer = counted(&mut func, entry, 4);
329
330 let mut build = Builder::new(&mut func, outer.body);
333 let wide = build.unary(Opcode::SExt, outer.counter, Type::int(64));
334 let stride = build.iconst(Type::int(64), 256);
335 let along = build.binary(Opcode::Mul, wide, stride, Flags::NSW);
336 let row = build.binary(Opcode::PtrAdd, base, along, Flags::NONE);
337
338 let inner = counted(&mut func, outer.body, 3);
339 let mut build = Builder::new(&mut func, inner.body);
341 let step = build.unary(Opcode::SExt, inner.counter, Type::int(64));
342 let four = build.iconst(Type::int(64), 4);
343 let by = build.binary(Opcode::Mul, step, four, Flags::NSW);
344 let addr = build.binary(Opcode::PtrAdd, row, by, Flags::NONE);
345 build.store(inner.counter, addr, plain(), Flags::NONE);
346 close(&mut func, &inner, inner.body);
347 close(&mut func, &outer, inner.out);
348 Builder::new(&mut func, outer.out).ret(&[]);
349
350 let stats = survey(&mut func);
351 assert_eq!(stats.count(Kind::Note, POPULATION), 1);
352 assert_eq!(stats.count(Kind::Note, NOT_AFFINE), 0);
353 assert_eq!(stats.count(Kind::Note, REFERENCE), 1);
354 }
355
356 #[test]
367 fn an_address_added_from_both_counters_is_one_this_compiler_can_describe() {
368 let mut names = Interner::new();
369 let (mut func, entry, base) = shell(&mut names);
370 let outer = counted(&mut func, entry, 4);
371 let inner = counted(&mut func, outer.body, 3);
372
373 let mut build = Builder::new(&mut func, inner.body);
374 let sum = build.binary(Opcode::Add, outer.counter, inner.counter, Flags::NSW);
375 let wide = build.unary(Opcode::SExt, sum, Type::int(64));
376 let addr = build.binary(Opcode::PtrAdd, base, wide, Flags::NONE);
377 build.store(outer.counter, addr, plain(), Flags::NONE);
378 close(&mut func, &inner, inner.body);
379 close(&mut func, &outer, inner.out);
380 Builder::new(&mut func, outer.out).ret(&[]);
381
382 let stats = survey(&mut func);
383 assert_eq!(stats.count(Kind::Note, NOT_AFFINE), 0);
384 assert_eq!(stats.count(Kind::Note, POPULATION), 1);
385 }
386
387 #[test]
388 fn a_write_between_the_two_loops_stops_it_being_a_nest() {
389 let mut names = Interner::new();
390 let (mut func, entry, base) = shell(&mut names);
391 let outer = counted(&mut func, entry, 4);
392 let inner = counted(&mut func, outer.body, 3);
393
394 Builder::new(&mut func, inner.body).store(inner.counter, base, plain(), Flags::NONE);
395 close(&mut func, &inner, inner.body);
396 Builder::new(&mut func, inner.out).store(outer.counter, base, plain(), Flags::NONE);
398 close(&mut func, &outer, inner.out);
399 Builder::new(&mut func, outer.out).ret(&[]);
400
401 let stats = survey(&mut func);
402 assert_eq!(stats.count(Kind::Note, NOT_PERFECT), 1);
403 assert_eq!(stats.count(Kind::Note, POPULATION), 0);
404 }
405
406 #[test]
407 fn an_address_that_came_out_of_memory_is_not_a_straight_line() {
408 let mut names = Interner::new();
409 let (mut func, entry, base) = shell(&mut names);
410 let outer = counted(&mut func, entry, 4);
411 let inner = counted(&mut func, outer.body, 3);
412
413 let mut build = Builder::new(&mut func, inner.body);
415 let addr = build.load(Type::PTR, base, plain(), Flags::NONE);
416 build.store(inner.counter, addr, plain(), Flags::NONE);
417 close(&mut func, &inner, inner.body);
418 close(&mut func, &outer, inner.out);
419 Builder::new(&mut func, outer.out).ret(&[]);
420
421 let stats = survey(&mut func);
422 assert_eq!(stats.count(Kind::Note, NOT_AFFINE), 1);
423 assert_eq!(stats.count(Kind::Note, POPULATION), 0);
424 assert_eq!(stats.count(Kind::Note, REFERENCE), 2, "the load and the write both count");
425 }
426}