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 {
204 match inv.value {
205 None => true,
206 Some(value) => straight(scev, outer, value),
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use rucc_base::Interner;
213 use rucc_ir::{
214 Block, Builder, Flags, Func, IntPred, MemInfo, MemOrder, Opcode, Restrict, Signature, Type,
215 Value,
216 };
217
218 use super::{ALONE, NOT_AFFINE, NOT_PERFECT, Nests, POPULATION, REFERENCE};
219 use crate::stats::Kind;
220 use crate::{Fuel, Pass, Stats};
221
222 fn survey(func: &mut Func) -> Stats {
224 Nests.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
225 }
226
227 fn plain() -> MemInfo {
229 MemInfo {
230 size: 0,
231 align: 4,
232 order: MemOrder::NotAtomic,
233 tbaa: None,
234 restrict: Restrict::NONE,
235 }
236 }
237
238 struct Counted {
240 head: Block,
241 body: Block,
242 out: Block,
243 counter: Value,
244 }
245
246 fn counted(func: &mut Func, into: Block, limit: i128) -> Counted {
257 let head = func.create_block();
258 let body = func.create_block();
259 let out = func.create_block();
260 let i = func.append_param(head, Type::int(32));
261 let carried = func.append_param(body, Type::int(32));
262
263 let mut build = Builder::new(func, into);
264 let zero = build.iconst(Type::int(32), 0);
265 build.jump(head, &[zero]);
266
267 let mut build = Builder::new(func, head);
268 let stop = build.iconst(Type::int(32), limit);
269 let test = build.icmp(IntPred::Slt, i, stop);
270 build.br_if(test, body, &[i], out, &[]);
271
272 Counted { head, body, out, counter: carried }
273 }
274
275 fn close(func: &mut Func, it: &Counted, at: Block) {
280 let mut build = Builder::new(func, at);
281 let one = build.iconst(Type::int(32), 1);
282 let next = build.binary(Opcode::Add, it.counter, one, Flags::NSW);
283 build.jump(it.head, &[next]);
284 }
285
286 fn shell(names: &mut Interner) -> (Func, Block, Value) {
288 let signature = Signature::new().with_params(&[Type::PTR]);
289 let mut func = Func::new(names.intern("f"), signature);
290 let entry = func.create_block();
291 let base = func.append_param(entry, Type::PTR);
292 (func, entry, base)
293 }
294
295 #[test]
296 fn a_loop_with_nothing_inside_it_is_not_a_nest() {
297 let mut names = Interner::new();
298 let (mut func, entry, _) = shell(&mut names);
299 let it = counted(&mut func, entry, 8);
300 close(&mut func, &it, it.body);
301 Builder::new(&mut func, it.out).ret(&[]);
302
303 let stats = survey(&mut func);
304 assert_eq!(stats.count(Kind::Note, ALONE), 1);
305 assert_eq!(stats.count(Kind::Note, POPULATION), 0);
306 assert!(!stats.changed(), "the survey rewrites nothing");
307 }
308
309 #[test]
310 fn two_loops_walking_a_row_at_a_time_are_the_population() {
311 let mut names = Interner::new();
312 let (mut func, entry, base) = shell(&mut names);
313 let outer = counted(&mut func, entry, 4);
314
315 let mut build = Builder::new(&mut func, outer.body);
318 let wide = build.unary(Opcode::SExt, outer.counter, Type::int(64));
319 let stride = build.iconst(Type::int(64), 256);
320 let along = build.binary(Opcode::Mul, wide, stride, Flags::NSW);
321 let row = build.binary(Opcode::PtrAdd, base, along, Flags::NONE);
322
323 let inner = counted(&mut func, outer.body, 3);
324 let mut build = Builder::new(&mut func, inner.body);
326 let step = build.unary(Opcode::SExt, inner.counter, Type::int(64));
327 let four = build.iconst(Type::int(64), 4);
328 let by = build.binary(Opcode::Mul, step, four, Flags::NSW);
329 let addr = build.binary(Opcode::PtrAdd, row, by, Flags::NONE);
330 build.store(inner.counter, addr, plain(), Flags::NONE);
331 close(&mut func, &inner, inner.body);
332 close(&mut func, &outer, inner.out);
333 Builder::new(&mut func, outer.out).ret(&[]);
334
335 let stats = survey(&mut func);
336 assert_eq!(stats.count(Kind::Note, POPULATION), 1);
337 assert_eq!(stats.count(Kind::Note, NOT_AFFINE), 0);
338 assert_eq!(stats.count(Kind::Note, REFERENCE), 1);
339 }
340
341 #[test]
350 fn an_address_added_from_both_counters_is_not_one_this_compiler_can_describe() {
351 let mut names = Interner::new();
352 let (mut func, entry, base) = shell(&mut names);
353 let outer = counted(&mut func, entry, 4);
354 let inner = counted(&mut func, outer.body, 3);
355
356 let mut build = Builder::new(&mut func, inner.body);
357 let sum = build.binary(Opcode::Add, outer.counter, inner.counter, Flags::NSW);
358 let wide = build.unary(Opcode::SExt, sum, Type::int(64));
359 let addr = build.binary(Opcode::PtrAdd, base, wide, Flags::NONE);
360 build.store(outer.counter, addr, plain(), Flags::NONE);
361 close(&mut func, &inner, inner.body);
362 close(&mut func, &outer, inner.out);
363 Builder::new(&mut func, outer.out).ret(&[]);
364
365 let stats = survey(&mut func);
366 assert_eq!(stats.count(Kind::Note, NOT_AFFINE), 1);
367 assert_eq!(stats.count(Kind::Note, POPULATION), 0);
368 }
369
370 #[test]
371 fn a_write_between_the_two_loops_stops_it_being_a_nest() {
372 let mut names = Interner::new();
373 let (mut func, entry, base) = shell(&mut names);
374 let outer = counted(&mut func, entry, 4);
375 let inner = counted(&mut func, outer.body, 3);
376
377 Builder::new(&mut func, inner.body).store(inner.counter, base, plain(), Flags::NONE);
378 close(&mut func, &inner, inner.body);
379 Builder::new(&mut func, inner.out).store(outer.counter, base, plain(), Flags::NONE);
381 close(&mut func, &outer, inner.out);
382 Builder::new(&mut func, outer.out).ret(&[]);
383
384 let stats = survey(&mut func);
385 assert_eq!(stats.count(Kind::Note, NOT_PERFECT), 1);
386 assert_eq!(stats.count(Kind::Note, POPULATION), 0);
387 }
388
389 #[test]
390 fn an_address_that_came_out_of_memory_is_not_a_straight_line() {
391 let mut names = Interner::new();
392 let (mut func, entry, base) = shell(&mut names);
393 let outer = counted(&mut func, entry, 4);
394 let inner = counted(&mut func, outer.body, 3);
395
396 let mut build = Builder::new(&mut func, inner.body);
398 let addr = build.load(Type::PTR, base, plain(), Flags::NONE);
399 build.store(inner.counter, addr, plain(), Flags::NONE);
400 close(&mut func, &inner, inner.body);
401 close(&mut func, &outer, inner.out);
402 Builder::new(&mut func, outer.out).ret(&[]);
403
404 let stats = survey(&mut func);
405 assert_eq!(stats.count(Kind::Note, NOT_AFFINE), 1);
406 assert_eq!(stats.count(Kind::Note, POPULATION), 0);
407 assert_eq!(stats.count(Kind::Note, REFERENCE), 2, "the load and the write both count");
408 }
409}