rucc_opt/loop_delete.rs
1//! Takes out a loop that comes back, working out what it left behind.
2//!
3//! Design: `spec/optimizer/17-dce.md` for what makes a thing removable and section 28.9 for the
4//! closed form, the off by one in it, and why the two questions this pass asks want different
5//! things from the trip count. This is tamnd/rucc#1631.
6//!
7//! [`crate::dce`] cannot do this and the reason is worth stating, because it looks at first like a
8//! gap in that pass. An empty counted loop has a counter, an add, a compare and a branch, and
9//! every one of them is used: the add feeds the compare, the compare feeds the branch, and the
10//! branch feeds the block parameter the add reads. Nothing in it has a use count of zero, so a
11//! pass driven by use counts correctly leaves all of it alone. The question that gets the loop out
12//! is not asked of an instruction, it is asked of the loop: does anything outside read what it
13//! computes, does it do anything to memory, and does it come back. Three yeses and the loop is a
14//! way of spending time.
15//!
16//! # Which loops
17//!
18//! A preheader, one exit, and a bound rather than an estimate. The bound is what says the loop
19//! terminates, which is the third question and the one a person is most likely to forget: a loop
20//! that computes nothing and never comes back still cannot be taken out, because not coming back
21//! is what it does. Section 7.5's distinction between a bound and an estimate is exactly this: an
22//! estimate decides whether a transformation pays and a bound decides what the program does.
23//!
24//! The bound is read through [`crate::scev::Bound::comes_back`] rather than through the accessor
25//! [`crate::unroll`] uses, and the difference is worth a sentence. Unrolling multiplies by the
26//! count, so it needs the count to be the right number. Deleting only needs there to be a last
27//! iteration, and `for (i = 0; i < n; i++)` has one whatever `n` turns out to be, so a count
28//! worked out from a value the loop does not change is as good as a number here. It stops being
29//! as good the moment anything reads what the loop left behind, which is why the two questions
30//! are asked in that order and with different accessors.
31//!
32//! Every instruction inside has to be one whose not happening nothing can tell. That is the
33//! predicate [`crate::dce`] already has, so it is read from there rather than written again, and
34//! it means a plain load may be inside the loop and a `volatile` one may not. A call is allowed
35//! when the purity analysis says it reads memory at most and comes back, which is the same rule
36//! that lets a call whose result nothing reads go.
37//!
38//! # What the loop leaves behind
39//!
40//! A loop whose total somebody reads afterwards is a loop that hands something over, and most
41//! loops worth writing are that kind. Sometimes what it hands over can be worked out without
42//! running it. A value that goes up by the same amount every time round is `{base, +, step}` in
43//! [`crate::scev`]'s terms, the loop is left on the iteration the exit test first fails, and that
44//! iteration's number is the count, so what the loop leaves behind is `base + step * count`. The
45//! preheader works that out in one go and hands it over instead, and then nothing outside reads
46//! anything the loop computed and the loop goes.
47//!
48//! Handing it over is two different edits, because a value defined in the loop reaches the code
49//! after it by two different roads. It may be an argument on the edge out, landing in a parameter
50//! of the block the loop leaves to, which is the shape [`crate::canon`] puts things in. Or the
51//! block after the loop may simply name it, which is legal wherever the definition dominates the
52//! use and is what is actually there by the time this runs, since the block loop closed form put in
53//! the way is one [`crate::simplify_cfg`] has every reason to fold away again. So both are looked
54//! for, and a use of the second kind is rewritten where it stands.
55//!
56//! No overflow argument is needed for this and it is worth saying why, because the neighbouring
57//! transformation in section 28.4 does need one. A value that steps by a fixed amount evolves in
58//! its own type, which is to say modulo two to the width, and addition modulo two to the width is
59//! associative, so adding `step` to `base` `count` times and working out `base + step * count` the
60//! same way are the same number whatever either of them does to the top bit. Section 28.4's rewrite
61//! is a different claim, that one comparison holds exactly where another does, and that one does
62//! turn on whether the limit overflows. So the arithmetic written here carries neither `nsw` nor
63//! `nuw`, and the promise the loop's own increment carried is not copied onto it, because that
64//! promise is about the sequence and says nothing about this.
65//!
66//! What is written down is the whole expression rather than three instructions to be folded later,
67//! because this pass is the last one in the pipeline and there is no later. `base` and `step` are
68//! both [`crate::scev::Invariant`], which is `value * scale + offset` with the arithmetic on it
69//! already, so `base + step * count` is worked out in that form first and only what is left of it
70//! reaches the function. A loop adding one a million times leaves a constant behind and a loop
71//! adding an invariant `n` a million times leaves one multiply.
72//!
73//! A count that is an expression rather than a number is written as `base + step * max(count, 0)`,
74//! and the two things bolted onto it there are two assumptions paid for rather than believed. The
75//! clamp is [`crate::scev::Assumption::Entered`]. A count that comes out negative is a loop whose
76//! test failed the first time it ran, which is a loop that took its back edge no times and handed
77//! over what one pass through its body left, and zero is the count that says exactly that. The
78//! widening is the reading the exit test took, a sign extension for a signed test and a zero
79//! extension for an unsigned one. Section 7.7 is the warning about getting that one wrong: a limit
80//! past the middle of a thirty two bit type is a large number to an unsigned test and a negative
81//! one to a signed test, so sign extending what an unsigned test compared would clamp to zero and
82//! turn a loop over three billion elements into one that ran no times.
83//!
84//! The clamp is done in sixty four bits and the arithmetic in the value's own type, and the cut
85//! between the two is exact rather than close enough. Multiplying modulo two to the width and then
86//! cutting to a narrower width is the same number as cutting first and then multiplying, so a count
87//! worked out wide and truncated is the count. Widening it instead is a zero extension, because the
88//! clamp has already made it a number that is not negative.
89//!
90//! It is only done when it lets the loop go, which is a cost rule rather than a correctness one.
91//! Writing the final value down where the loop stays behind costs a multiply in the preheader and
92//! saves nothing, because the loop still carries the value round its own back edge and nothing in
93//! rucc yet takes out a block parameter whose only reader is the argument it passes to itself.
94//! [`crate::dce`]'s own notes call that out as a transformation worth having and a different one
95//! from what it does. When there is one, this gate is the thing to reconsider.
96//!
97//! # What it does
98//!
99//! Works out in the preheader whatever the loop was going to leave behind, puts those values where
100//! the loop's own were read, points the preheader at the block the loop left to with whatever the
101//! edge out was already carrying from outside, and lets the sweep in [`crate::simplify_cfg`] take
102//! the blocks nothing reaches. Every value named in any of it is asserted to dominate the preheader
103//! rather than assumed to: a value defined outside the loop that reaches the exit test has to
104//! dominate the preheader, and an assertion is cheaper than being wrong about why.
105
106use std::collections::{HashMap, HashSet};
107
108use rucc_ir::{Block, Builder, Def, Extra, Func, Inst, InstData, IntPred, Opcode, Type, Value};
109
110use crate::cfg::Cfg;
111use crate::dom::Dominators;
112use crate::loops::{LoopId, Loops};
113use crate::purity::Facts;
114use crate::scev::{Assumption, Count, Invariant, Plain, Reading, Scev};
115use crate::{Analyses, Fuel, Pass, Preserved, Stats};
116
117const DELETED: &str = "loop taken out, it comes back and leaves nothing behind";
118const WRITTEN: &str = "what the loop was going to leave behind worked out in front of it instead";
119const NO_COUNT: &str = "loop left as it was, nothing here says it comes back";
120const SHAPE: &str =
121 "loop left as it was, it has no preheader or it leaves from more than one place";
122const EFFECTS: &str = "loop left as it was, something in it does more than work out a value";
123const NO_FORM: &str =
124 "loop left as it was, what it leaves behind is not a thing this can work out in front of it";
125const ENTRIES: &str = "loop left as it was, it is reached somewhere other than at its header";
126const NO_FUEL: &str = "loop left as it was, the pass ran out of fuel";
127
128/// Section 17's dead code elimination, asked about a loop rather than about an instruction.
129#[derive(Debug)]
130pub struct LoopDelete;
131
132impl Pass for LoopDelete {
133 fn name(&self) -> &'static str {
134 "loop-delete"
135 }
136
137 fn describe(&self) -> &'static str {
138 "a loop that comes back and leaves nothing behind is taken out"
139 }
140
141 fn preserves(&self) -> Preserved {
142 // The loop goes, and its blocks with it.
143 Preserved::NONE
144 }
145
146 fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
147 let mut stats = Stats::new();
148 if func.entry().is_none() {
149 return stats;
150 }
151 let mut done: HashSet<Block> = HashSet::new();
152 let mut say = true;
153 while let Some(job) = plan(func, an, &done, &mut stats, say) {
154 say = false;
155 if !fuel.take() {
156 stats.missed(NO_FUEL);
157 break;
158 }
159 done.insert(job.header);
160 for _ in 0..apply(func, &job) {
161 stats.optimized(WRITTEN);
162 }
163 stats.optimized(DELETED);
164 an.clear();
165 crate::simplify_cfg::sweep(func, an, &mut stats);
166 }
167 // What only the loop read goes with it. This is the last pass in every pipeline, so there
168 // is no dead code elimination after it to take out what a deleted loop leaves behind, and
169 // what it leaves is not always nothing: a countdown from ivopts starts from a clamp worked
170 // out in front of the loop, and that clamp is read by the loop and by nothing else. What
171 // it says about the instructions is dead code elimination's to say, not this pass's.
172 if !done.is_empty() {
173 crate::dce::dce_in(func, an.purity(), &mut Fuel::unlimited());
174 }
175 an.clear();
176 stats
177 }
178}
179
180/// One loop to take out, worked out against the function as it stands.
181#[derive(Debug)]
182struct Job {
183 /// The block the loop is entered at, which is what says which loop this was.
184 header: Block,
185 /// The one block outside the loop with an edge to the header.
186 preheader: Block,
187 /// The block outside the loop the one exit edge arrives at.
188 exit: Block,
189 /// The blocks the loop is made of, which is what says which uses are the ones outside it.
190 inside: HashSet<Block>,
191 /// What the edge out was going to carry, which the preheader carries instead.
192 args: Vec<Value>,
193 /// Every value the loop defines that anything outside it reads, and what it ends up holding.
194 ends: Vec<(Value, Leaves)>,
195 /// Whether the count is only right for a loop that was entered, which is what the clamp is for.
196 entered: bool,
197}
198
199/// What a value the loop defines holds by the time anything outside it looks.
200///
201/// Two shapes for the two shapes a count comes in, and what separates them is how much of the
202/// answer is settled before anything reaches the function. A count that is a number settles all of
203/// it, so what is left is one expression and often one constant. A count that is an expression
204/// settles none of it and the preheader does the work.
205#[derive(Clone, Copy, Debug)]
206enum Leaves {
207 /// `base + step * count`, worked out in full because the count is a number.
208 Worked {
209 /// The type it evolved in, which is the type the arithmetic is done in.
210 ty: Type,
211 /// The whole of it, as far as it goes without writing anything down.
212 end: Invariant,
213 },
214 /// `base + step * max(count, 0)`, built in the preheader because the count is an expression.
215 Built {
216 /// The type it evolved in, which is the type the arithmetic is done in.
217 ty: Type,
218 /// What the value holds the first time anything outside could have looked.
219 base: Invariant,
220 /// How much it goes up by each time round.
221 step: Invariant,
222 /// How many times the back edge is taken, before the clamp the module notes describe.
223 count: Plain,
224 /// How the exit test read what the count is built on, which is the widening owed.
225 reading: Reading,
226 },
227}
228
229/// The innermost loop that can go, and what it would take.
230///
231/// One at a time, for the reason [`crate::unroll::plan`] takes one at a time: taking a loop out
232/// invalidates the forest the next answer would be read out of. `say` is false after the first
233/// round so that a loop this declines is declined once rather than once per round.
234fn plan(
235 func: &Func,
236 an: &mut Analyses,
237 done: &HashSet<Block>,
238 stats: &mut Stats,
239 say: bool,
240) -> Option<Job> {
241 let facts = an.purity();
242 let cfg = an.cfg(func);
243 let doms = an.dominators(func);
244 let loops = an.loops(func);
245 let mut scev = Scev::new(func, cfg, loops);
246 let mut found: Option<(u32, Job)> = None;
247 for id in loops.all() {
248 if done.contains(&loops.header(id)) {
249 continue;
250 }
251 match consider(func, cfg, doms, loops, facts, &mut scev, id) {
252 Ok(job) => {
253 let depth = loops.depth(id);
254 if found.as_ref().is_none_or(|(had, _)| depth > *had) {
255 found = Some((depth, job));
256 }
257 }
258 Err(why) if say => stats.missed(why),
259 Err(_) => (),
260 }
261 }
262 found.map(|(_, job)| job)
263}
264
265/// Whether this loop can go, and why not when it cannot.
266fn consider(
267 func: &Func,
268 cfg: &Cfg,
269 doms: &Dominators,
270 loops: &Loops,
271 facts: &Facts,
272 scev: &mut Scev<'_>,
273 id: LoopId,
274) -> Result<Job, &'static str> {
275 let header = loops.header(id);
276 let preheader = loops.preheader(cfg, id).ok_or(SHAPE)?;
277 let [only] = loops.exits(id) else {
278 return Err(SHAPE);
279 };
280
281 let blocks = loops.blocks(id).to_vec();
282 let inside: HashSet<Block> = blocks.iter().copied().collect();
283 for &block in &blocks {
284 // The same reducibility check unrolling makes. A block of the loop reached from outside
285 // the loop is a region this has no right to reason about as one piece.
286 if block != header && cfg.predecessors(block).iter().any(|at| !inside.contains(at)) {
287 return Err(ENTRIES);
288 }
289 for inst in func.insts(block) {
290 if func.is_terminator(inst) {
291 // A terminator that leaves the function or goes somewhere worked out at run time
292 // is not an edge the forest accounted for, so the one exit counted above is not
293 // the only way out.
294 if !matches!(func[inst].opcode, Opcode::Jump | Opcode::BrIf) {
295 return Err(EFFECTS);
296 }
297 continue;
298 }
299 if !crate::dce::removable(func, inst, facts) {
300 return Err(EFFECTS);
301 }
302 }
303 }
304 // The bound is what says the loop comes back. A loop that computes nothing and runs forever
305 // still does something, which is run forever. A count worked out from a value the loop does
306 // not change says that as well as a number does: whatever that value is, the loop gets to it,
307 // and nothing in here needs to know how many steps that took. What is not allowed is a loop
308 // ending on `!=` whose counter may step past its limit, and that is the one assumption
309 // [`crate::scev::Bound::comes_back`] holds back.
310 let bound = scev.bound(id).ok_or(NO_COUNT)?;
311 // Taken here rather than inside [`ending`] because it belongs to the exit test rather than to
312 // any one value the loop hands over, so every one of them owes the same widening.
313 let reading = bound.reading();
314 let entered = bound.assumptions().contains(&Assumption::Entered);
315 let count = bound.comes_back().ok_or(NO_COUNT)?;
316
317 let term = func.terminator(only.from).ok_or(SHAPE)?;
318 let leaving = func.successors(term).find(|call| call.block == only.to).ok_or(SHAPE)?;
319 let args = func[leaving.args].to_vec();
320
321 let mut wanted = read_outside(func, &blocks, &inside);
322 for &arg in &args {
323 if loops.is_invariant(func, id, arg) {
324 debug_assert!(
325 doms.dominates(defined_in(func, arg), preheader),
326 "a value outside the loop that reaches the exit test dominates the preheader"
327 );
328 continue;
329 }
330 if !wanted.contains(&arg) {
331 wanted.push(arg);
332 }
333 }
334
335 let mut ends = Vec::with_capacity(wanted.len());
336 for value in wanted {
337 let end = ending(func, scev, id, value, count, reading).ok_or(NO_FORM)?;
338 debug_assert!(
339 names(end).iter().all(|&on| doms.dominates(defined_in(func, on), preheader)),
340 "a value the loop does not change is defined outside it and so dominates the preheader"
341 );
342 ends.push((value, end));
343 }
344 Ok(Job { header, preheader, exit: only.to, inside, args, ends, entered })
345}
346
347/// Every value the loop defines that a block outside it names, in the order they turn up.
348///
349/// [`crate::unroll::escapes`] asks whether there is one of these and stops there, because a loop
350/// with one is a loop it will not copy. Here they are the work rather than the reason to stop, so
351/// the answer has to be which ones.
352fn read_outside(func: &Func, blocks: &[Block], inside: &HashSet<Block>) -> Vec<Value> {
353 let mut defined: HashSet<Value> = HashSet::new();
354 for &block in blocks {
355 defined.extend(func[block].params.iter().copied());
356 for inst in func.insts(block) {
357 defined.extend(func[inst].results());
358 }
359 }
360 let mut found = Vec::new();
361 for block in func.blocks() {
362 if inside.contains(&block) {
363 continue;
364 }
365 for inst in func.insts(block) {
366 let reads = func[func[inst].args].iter().copied();
367 let passes = func.successors(inst).flat_map(|call| func[call.args].to_vec());
368 for value in reads.chain(passes) {
369 if defined.contains(&value) && !found.contains(&value) {
370 found.push(value);
371 }
372 }
373 }
374 }
375 found
376}
377
378/// What the loop leaves in a value it hands over, or `None` when that is not a thing to write down.
379///
380/// Every refusal here is asked before anything is written, so that a refusal is a refusal rather
381/// than a preheader with half an expression in it. There is no undo and there should not need to
382/// be.
383fn ending(
384 func: &Func,
385 scev: &mut Scev<'_>,
386 id: LoopId,
387 value: Value,
388 count: Count,
389 reading: Reading,
390) -> Option<Leaves> {
391 let chrec = scev.evolution(id, value).chrec()?;
392 // The arithmetic below is integer arithmetic in one lane. A chrec over anything else is not a
393 // thing this knows how to write down, whatever the count turned out to be.
394 if !chrec.ty.is_int() || chrec.ty.is_vector() {
395 return None;
396 }
397 match count {
398 Count::Exact(trips) => {
399 let trips = i128::try_from(trips).ok()?;
400 let all = chrec.step.times(Invariant::number(trips))?;
401 let end = chrec.base.plus(all)?;
402 writable(func, end, chrec.ty)?;
403 Some(Leaves::Worked { ty: chrec.ty, end })
404 }
405 Count::Symbolic(count) => {
406 writable(func, chrec.base, chrec.ty)?;
407 writable(func, chrec.step, chrec.ty)?;
408 let count = count.plain()?;
409 // A count built on a value that is itself read through an extension carries a widening
410 // of its own, and which of that one and the exit test's should be spent is not a
411 // question with an answer here. Refused rather than guessed at, the same way
412 // [`crate::trip::counted`] refuses it.
413 if count.read.is_some() {
414 return None;
415 }
416 // Room for the clamp to happen in. A count built on something already as wide as the
417 // arithmetic that carries it has nowhere to be negative.
418 if count.value.filter(|_| count.scale != 0).is_none_or(|on| func[on].ty.bits() > 64) {
419 return None;
420 }
421 Some(Leaves::Built { ty: chrec.ty, base: chrec.base, step: chrec.step, count, reading })
422 }
423 }
424}
425
426/// Whether [`write`] can put an expression in front of the loop in the type given.
427fn writable(func: &Func, part: Invariant, ty: Type) -> Option<Plain> {
428 let plain = part.plain()?;
429 if plain.read.is_some() {
430 return None;
431 }
432 if plain.value.is_some_and(|named| func[named].ty != ty) {
433 return None;
434 }
435 Some(plain)
436}
437
438/// Every value an expression is built on, which is what the dominance assertion is asked of.
439fn names(leaves: Leaves) -> Vec<Value> {
440 let on =
441 |part: Invariant| part.plain().and_then(|plain| plain.value.filter(|_| plain.scale != 0));
442 match leaves {
443 Leaves::Worked { end, .. } => on(end).into_iter().collect(),
444 Leaves::Built { base, step, count, .. } => {
445 [on(base), on(step), count.value].into_iter().flatten().collect()
446 }
447 }
448}
449
450/// The block a value is defined in.
451fn defined_in(func: &Func, value: Value) -> Block {
452 match func[value].def {
453 Def::Result { inst, .. } => {
454 func.block_of(inst).expect("a value in use is defined in a block")
455 }
456 Def::Param { block, .. } => block,
457 }
458}
459
460/// Points the preheader past the loop, working out on the way what the loop was going to leave.
461///
462/// Answers how many of those there were, which is what the report counts.
463fn apply(func: &mut Func, job: &Job) -> usize {
464 let term = func.terminator(job.preheader).expect("a preheader ends in a jump to the header");
465 let mut instead: HashMap<Value, Value> = HashMap::new();
466 // One clamp for the whole loop rather than one per value, since the count belongs to the loop
467 // and the values differ only in what they do with it.
468 let mut times: Option<Value> = None;
469 for &(value, leaves) in &job.ends {
470 let worked = match leaves {
471 Leaves::Worked { ty, end } => write(func, term, ty, end),
472 Leaves::Built { ty, base, step, count, reading } => {
473 let all = match times {
474 Some(had) => had,
475 None if job.entered => *times.insert(clamped(func, term, count, reading)),
476 None => *times.insert(widened(func, term, count, reading)),
477 };
478 built(func, term, ty, base, step, all)
479 }
480 };
481 instead.insert(value, worked);
482 }
483 swap_in(func, job, &instead);
484 let args: Vec<Value> =
485 job.args.iter().map(|arg| instead.get(arg).copied().unwrap_or(*arg)).collect();
486 func.remove_inst(term);
487 Builder::new(func, job.preheader).jump(job.exit, &args);
488 instead.len()
489}
490
491/// Puts the worked out values where the loop's own were read.
492///
493/// Only outside the loop, because inside it the loop's own values are still the right answer right
494/// up until the blocks go. The preheader is outside and gets walked with the rest, which is
495/// harmless and better than a special case: what was just written into it names nothing the loop
496/// defines.
497fn swap_in(func: &mut Func, job: &Job, instead: &HashMap<Value, Value>) {
498 if instead.is_empty() {
499 return;
500 }
501 let outside: Vec<Block> = func.blocks().filter(|at| !job.inside.contains(at)).collect();
502 for block in outside {
503 for inst in func.insts(block).collect::<Vec<_>>() {
504 let mut lists = vec![func[inst].args];
505 lists.extend(func.successors(inst).map(|call| call.args));
506 for list in lists {
507 func.rewrite(list, |value| instead.get(&value).copied().unwrap_or(value));
508 }
509 }
510 }
511}
512
513/// Works an expression out in front of an instruction.
514///
515/// `value * scale + offset`, with the parts that are nothing left out, so a scale of one is no
516/// multiply and an offset of zero is no add and an expression built on no value at all is one
517/// constant. That is what makes a loop adding one a million times leave a number behind rather than
518/// three instructions nothing is going to fold, this being the last pass there is.
519fn write(func: &mut Func, before: Inst, ty: Type, end: Invariant) -> Value {
520 let plain = end.plain().expect("consider refused anything this cannot write");
521 let Some(value) = plain.value.filter(|_| plain.scale != 0) else {
522 return crate::ivopts::number(func, before, ty, plain.offset);
523 };
524 let mut so_far = value;
525 if plain.scale != 1 {
526 let by = crate::ivopts::number(func, before, ty, plain.scale);
527 so_far = arith(func, before, Opcode::Mul, so_far, by, ty);
528 }
529 if plain.offset != 0 {
530 let by = crate::ivopts::number(func, before, ty, plain.offset);
531 so_far = arith(func, before, Opcode::Add, so_far, by, ty);
532 }
533 so_far
534}
535
536/// How many times the back edge is taken, worked out in front of the loop and clamped at zero.
537///
538/// `max(read(value) * scale + offset, 0)`, in sixty four bits whatever the count's own type is, and
539/// the module notes say what each of those two is paying for. The clamp is a `select` rather than a
540/// branch because the whole of this has to be straight line code in a preheader, and nothing on any
541/// of it promises anything about overflow, since the count came out of a subtraction the analysis
542/// already reasoned about rather than out of anything written here.
543///
544/// [`crate::ivopts`] writes the same clamp in front of the same kind of loop, because it is
545/// discharging the same assumption in the same place, and two of these would be two things to keep
546/// in step. It lives here because this is where it was written and where the argument for it is.
547pub(crate) fn clamped(func: &mut Func, before: Inst, count: Plain, reading: Reading) -> Value {
548 let word = Type::int(64);
549 let wide = widened(func, before, count, reading);
550 let none = crate::ivopts::number(func, before, word, 0);
551 let args = func.push_values(&[wide, none]);
552 let test =
553 InstData { args, extra: Extra::IntPred(IntPred::Sgt), ..InstData::new(Opcode::ICmp) };
554 let entered = made(func, before, test, word.with_lane(Type::I1));
555 let args = func.push_values(&[entered, wide, none]);
556 made(func, before, InstData { args, ..InstData::new(Opcode::Select) }, word)
557}
558
559/// How many times the back edge is taken, worked out in front of the loop, for a count with nothing
560/// to clamp.
561///
562/// The same `read(value) * scale + offset` in sixty four bits as [`clamped`], without the `max`. A
563/// count that does not rest on [`Assumption::Entered`] is never negative, and neither is one the
564/// loop guard shows is not, so the clamp would be a `select` choosing the same thing every time.
565///
566/// A count built on a sum of something and a number is built on the something, with the number
567/// folded into the offset. Nothing after this pass would fold the two, and the countdown ivopts
568/// writes starts one above its count, so the count read back off it is exactly that shape. The fold
569/// is exact because all of this is sixty four bit arithmetic that wraps, and so is the sum.
570pub(crate) fn widened(func: &mut Func, before: Inst, count: Plain, reading: Reading) -> Value {
571 let word = Type::int(64);
572 let mut on = count.value.expect("a count that is an expression is built on a value");
573 let mut offset = count.offset;
574 if count.scale == 1 && func[on].ty == word {
575 if let Some((inner, more)) = plus_a_number(func, on) {
576 if let Some(sum) = offset.checked_add(more) {
577 (on, offset) = (inner, sum);
578 }
579 }
580 }
581 let mut wide = on;
582 if func[on].ty.bits() < 64 {
583 let widen = match reading {
584 Reading::Signed => Opcode::SExt,
585 Reading::Unsigned => Opcode::ZExt,
586 };
587 wide = cast(func, before, widen, wide, word);
588 }
589 if count.scale != 1 {
590 let by = crate::ivopts::number(func, before, word, count.scale);
591 wide = arith(func, before, Opcode::Mul, wide, by, word);
592 }
593 if offset != 0 {
594 let by = crate::ivopts::number(func, before, word, offset);
595 wide = arith(func, before, Opcode::Add, wide, by, word);
596 }
597 wide
598}
599
600/// The value this one adds a number to, and the number, when it is that.
601fn plus_a_number(func: &Func, value: Value) -> Option<(Value, i128)> {
602 let Def::Result { inst, .. } = func[value].def else { return None };
603 if func[inst].opcode != Opcode::Add {
604 return None;
605 }
606 let [inner, by] = func[func[inst].args] else { return None };
607 let (imm, ty) = crate::fold::constant(func, by)?;
608 Some((inner, imm.signed(ty)))
609}
610
611/// `base + step * times`, worked out in front of the loop in the type the value evolved in.
612///
613/// The trivial parts are left out where the numbers make them trivial, for the reason [`write`]
614/// leaves them out: nothing after this pass folds a multiply by one, so a loop counting by ones
615/// would otherwise leave one in every preheader.
616fn built(
617 func: &mut Func,
618 before: Inst,
619 ty: Type,
620 base: Invariant,
621 step: Invariant,
622 times: Value,
623) -> Value {
624 if step.as_number() == Some(0) {
625 return write(func, before, ty, base);
626 }
627 let narrow = resize(func, before, times, ty);
628 let mut so_far = narrow;
629 if step.as_number() != Some(1) {
630 let by = write(func, before, ty, step);
631 so_far = arith(func, before, Opcode::Mul, by, narrow, ty);
632 }
633 if base.as_number() != Some(0) {
634 let from = write(func, before, ty, base);
635 so_far = arith(func, before, Opcode::Add, from, so_far, ty);
636 }
637 so_far
638}
639
640/// The clamped count in the type the arithmetic is done in.
641///
642/// A truncation where that type is narrower, which loses nothing that matters: cutting a product
643/// modulo two to the width and multiplying a cut are the same number. A zero extension where it is
644/// wider, which is exact because the clamp has already made the count a number that is not
645/// negative. Neither where the widths agree.
646fn resize(func: &mut Func, before: Inst, times: Value, ty: Type) -> Value {
647 let had = func[times].ty.bits();
648 if had == ty.bits() {
649 return times;
650 }
651 let either = if ty.bits() < had { Opcode::Trunc } else { Opcode::ZExt };
652 cast(func, before, either, times, ty)
653}
654
655/// One widening or narrowing, worked out in front of another instruction.
656fn cast(func: &mut Func, before: Inst, opcode: Opcode, arg: Value, ty: Type) -> Value {
657 let args = func.push_values(&[arg]);
658 made(func, before, InstData { args, ..InstData::new(opcode) }, ty)
659}
660
661/// One arithmetic instruction, worked out in front of another one and promising nothing.
662///
663/// Neither `nsw` nor `nuw`, which is the point rather than an omission. The module notes say why:
664/// what the loop did is the same arithmetic modulo two to the width as many times as it ran, and
665/// `base + step * count` worked out the same way is the same number. A flag the loop's own
666/// increment carried is a fact about that sequence, and putting it here would be inventing one.
667pub(crate) fn arith(
668 func: &mut Func,
669 before: Inst,
670 opcode: Opcode,
671 left: Value,
672 right: Value,
673 ty: Type,
674) -> Value {
675 let args = func.push_values(&[left, right]);
676 made(func, before, InstData { args, ..InstData::new(opcode) }, ty)
677}
678
679/// One instruction with one result, put in front of another one and given its source location.
680fn made(func: &mut Func, before: Inst, data: InstData, ty: Type) -> Value {
681 let span = func.span(before);
682 let inst = func.create_inst(data, &[ty], span);
683 func.insert_before(inst, before);
684 func[inst].first_result.expect("one result was asked for")
685}
686
687#[cfg(test)]
688mod tests {
689 use rucc_base::Interner;
690 use rucc_ir::{
691 Block, Builder, Def, Flags, Func, IntPred, MemInfo, MemOrder, Module, Opcode, Restrict,
692 Signature, Type, Value, verify_func,
693 };
694 use rucc_target::{TargetInfo, Triple};
695
696 use super::{DELETED, EFFECTS, LoopDelete, NO_COUNT, NO_FORM, NO_FUEL, WRITTEN};
697 use crate::stats::Kind;
698 use crate::{Fuel, Pass, Stats};
699
700 /// Runs the pass over the function as it stands.
701 fn delete(func: &mut Func, fuel: &mut Fuel) -> Stats {
702 LoopDelete.run(func, &mut crate::machine::fixtures::analyses(), fuel)
703 }
704
705 /// Insists the function is one the rest of the compiler may believe.
706 ///
707 /// Pointing a block at a different successor is the edit that hands a block the wrong number
708 /// of arguments and strands a definition its uses still name, so this is where most of the
709 /// strength of these tests is.
710 fn sound(func: &Func, names: &mut Interner) {
711 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
712 let module = Module::new(names.intern("t.c"), &target);
713 if let Err(errors) = verify_func(&module, func, names) {
714 panic!("{errors:#?}");
715 }
716 }
717
718 /// How many instructions of that opcode the whole function holds.
719 fn tally(func: &Func, opcode: Opcode) -> usize {
720 func.blocks()
721 .flat_map(|block| func.insts(block))
722 .filter(|&inst| func[inst].opcode == opcode)
723 .count()
724 }
725
726 /// The one value now handed to the block the loop used to leave to.
727 fn handed_value(func: &Func, done: &Block) -> Option<Value> {
728 let cfg = crate::cfg::Cfg::new(func);
729 let [only] = cfg.predecessors(*done) else {
730 return None;
731 };
732 let term = func.terminator(*only)?;
733 let call = func.successors(term).find(|call| call.block == *done)?;
734 let args = func[call.args].to_vec();
735 let [arg] = args[..] else {
736 return None;
737 };
738 Some(arg)
739 }
740
741 /// What the value handed over is a multiple of, when it is a multiple of something.
742 fn handed(func: &Func, done: &Block) -> Option<i128> {
743 let value = handed_value(func, done)?;
744 let Def::Result { inst, .. } = func[value].def else {
745 return None;
746 };
747 if func[inst].opcode != Opcode::Mul {
748 return None;
749 }
750 let args = func[func[inst].args].to_vec();
751 let (imm, ty) = crate::fold::constant(func, args[1])?;
752 Some(imm.signed(ty))
753 }
754
755 /// How many loops are left.
756 fn loops(func: &Func) -> usize {
757 let cfg = crate::cfg::Cfg::new(func);
758 let doms = crate::dom::Dominators::new(&cfg);
759 crate::loops::Loops::new(&cfg, &doms).count()
760 }
761
762 /// A four byte write with nothing said about what it aliases.
763 fn plain() -> MemInfo {
764 MemInfo {
765 size: 4,
766 align: 4,
767 order: MemOrder::NotAtomic,
768 tbaa: None,
769 owns: 0,
770 restrict: Restrict::NONE,
771 }
772 }
773
774 /// What the limit of the exit test is.
775 #[derive(Clone, Copy)]
776 enum Limit {
777 /// A number written in the program.
778 Number(i128),
779 /// A value the function was handed, which the loop does not change.
780 Given,
781 /// The same value, waited for with `!=` rather than counted up to with an ordering.
782 Landing,
783 }
784
785 /// What the loop does, which is the whole of what decides whether it can go.
786 #[derive(Clone, Copy, PartialEq)]
787 enum What {
788 /// Adds up a number nothing ever reads.
789 Nothing,
790 /// Writes each running total to the pointer it was handed.
791 Writes,
792 /// Hands the block it leaves to a total that went up by the same amount every time.
793 HandsOut,
794 /// Hands out a total that went up by one every time, so there is no multiply to write.
795 HandsOne,
796 /// Hands out a total that went up by a different amount every time round.
797 HandsSquare,
798 /// Leaves its total to be read after the loop by a road other than the edge out.
799 ReadAfter,
800 }
801
802 impl What {
803 /// Whether the total goes out on the edge the loop leaves by.
804 fn hands_out(self) -> bool {
805 matches!(self, What::HandsOut | What::HandsOne | What::HandsSquare)
806 }
807 }
808
809 struct Shape {
810 names: Interner,
811 func: Func,
812 entry: Block,
813 done: Block,
814 }
815
816 /// A counted loop in the shape `crate::canon` and `crate::header_copy` leave a `for` in.
817 ///
818 /// ```text
819 /// entry(p, n): jump head(0, 0)
820 /// head(i, sum): jump body(i, sum)
821 /// body(c, r): total = r + n; next = c + 1; test = next < limit
822 /// br test -> head(next, total), done()
823 /// done: ret
824 /// ```
825 ///
826 /// Two blocks in the loop rather than one, so that taking it out has more than one block to get
827 /// rid of, and a running total carried round, so that there is something inside worth asking
828 /// whether anybody reads. The total goes up by `n` each time round, which is the shape of
829 /// `total += seed` in the issue: a value that goes up by the same amount every time, where the
830 /// amount is not a number anything here knows.
831 fn shaped(limit: Limit, what: What) -> Shape {
832 let mut names = Interner::new();
833 let signature = Signature::new().with_params(&[Type::PTR, Type::int(32)]);
834 let mut func = Func::new(names.intern("f"), signature);
835 let entry = func.create_block();
836 let head = func.create_block();
837 let body = func.create_block();
838 let done = func.create_block();
839 let place = func.append_param(entry, Type::PTR);
840 let given = func.append_param(entry, Type::int(32));
841 let i = func.append_param(head, Type::int(32));
842 let sum = func.append_param(head, Type::int(32));
843 let carried = func.append_param(body, Type::int(32));
844 let running = func.append_param(body, Type::int(32));
845 if what.hands_out() {
846 func.append_param(done, Type::int(32));
847 }
848
849 let mut build = Builder::new(&mut func, entry);
850 let zero = build.iconst(Type::int(32), 0);
851 build.jump(head, &[zero, zero]);
852 Builder::new(&mut func, head).jump(body, &[i, sum]);
853
854 let mut build = Builder::new(&mut func, body);
855 let one = build.iconst(Type::int(32), 1);
856 let by = match what {
857 What::HandsOne => one,
858 What::HandsSquare => carried,
859 _ => given,
860 };
861 let total = build.binary(Opcode::Add, running, by, Flags::NSW);
862 if what == What::Writes {
863 build.store(total, place, plain(), Flags::NONE);
864 }
865 let next = build.binary(Opcode::Add, carried, one, Flags::NSW);
866 let stop = match limit {
867 Limit::Number(n) => build.iconst(Type::int(32), n),
868 Limit::Given | Limit::Landing => given,
869 };
870 let pred = match limit {
871 Limit::Landing => IntPred::Ne,
872 _ => IntPred::Slt,
873 };
874 let test = build.icmp(pred, next, stop);
875 let out: Vec<Value> = if what.hands_out() { vec![total] } else { Vec::new() };
876 build.br_if(test, head, &[next, total], done, &out);
877 let mut build = Builder::new(&mut func, done);
878 if what == What::ReadAfter {
879 build.store(total, place, plain(), Flags::NONE);
880 }
881 build.ret(&[]);
882 Shape { names, func, entry, done }
883 }
884
885 #[test]
886 fn a_loop_that_leaves_nothing_behind_is_taken_out() {
887 let mut it = shaped(Limit::Number(1000), What::Nothing);
888 let stats = delete(&mut it.func, &mut Fuel::unlimited());
889 assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
890 assert_eq!(loops(&it.func), 0);
891 assert_eq!(tally(&it.func, Opcode::Add), 0, "the counter and the total go with it");
892 assert_eq!(tally(&it.func, Opcode::BrIf), 0);
893 sound(&it.func, &mut it.names);
894 }
895
896 #[test]
897 fn the_blocks_it_took_out_are_swept_rather_than_left_unreachable() {
898 let mut it = shaped(Limit::Number(1000), What::Nothing);
899 delete(&mut it.func, &mut Fuel::unlimited());
900 let left: Vec<Block> = it.func.blocks().collect();
901 assert_eq!(left, vec![it.entry, it.done], "the header and the body are gone");
902 sound(&it.func, &mut it.names);
903 }
904
905 /// A loop counting up to a value nothing here knows still has a last iteration.
906 ///
907 /// The bound comes back [`crate::scev::Count::Symbolic`] with two assumptions on it rather
908 /// than one. The overflow one the front end already promised.
909 /// [`crate::scev::Assumption::Entered`] it did not, and what that one says is whether the
910 /// count is the distance to the limit or zero. Both of those are numbers of times a loop goes
911 /// round, so the question this pass asks, which is whether there is a last time, has been
912 /// answered whichever of them it turns out to be. Nothing outside reads what this loop
913 /// computes, so the count is never multiplied by and the assumption is never spent.
914 ///
915 /// This is the `for (i = 0; i < n; i++)` of tamnd/rucc#1631 and of the corpus rows the report
916 /// had rucc losing on. Reading the bound through [`crate::scev::Bound::comes_back`] is what
917 /// makes it the answer.
918 #[test]
919 fn a_loop_counting_up_to_a_value_handed_in_is_taken_out() {
920 let mut it = shaped(Limit::Given, What::Nothing);
921 let stats = delete(&mut it.func, &mut Fuel::unlimited());
922 assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
923 assert_eq!(loops(&it.func), 0);
924 assert_eq!(tally(&it.func, Opcode::Add), 0, "the counter and the total go with it");
925 sound(&it.func, &mut it.names);
926 }
927
928 /// A loop that may step over the value it is waiting for is a loop that may not come back.
929 ///
930 /// `!=` ends a loop on the one iteration where the counter is the limit, so a counter that
931 /// starts past the limit, or that steps over it, goes round until it wraps. That is
932 /// [`crate::scev::Assumption::Approaching`], the one
933 /// [`crate::scev::Bound::comes_back`] holds back, and it is held back because document 17.2
934 /// says rucc does not take out a loop that might not end. The step here is one and the loop
935 /// would in fact arrive, which is the point: the pass refuses on what it has been shown rather
936 /// than on what happens to be true.
937 #[test]
938 fn a_loop_that_may_step_over_the_value_it_waits_for_is_left_alone() {
939 let mut it = shaped(Limit::Landing, What::Nothing);
940 let stats = delete(&mut it.func, &mut Fuel::unlimited());
941 assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
942 assert_eq!(stats.count(Kind::Missed, NO_COUNT), 1);
943 assert_eq!(loops(&it.func), 1);
944 sound(&it.func, &mut it.names);
945 }
946
947 #[test]
948 fn a_loop_that_writes_to_memory_is_left_alone() {
949 let mut it = shaped(Limit::Number(1000), What::Writes);
950 let stats = delete(&mut it.func, &mut Fuel::unlimited());
951 assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
952 assert_eq!(stats.count(Kind::Missed, EFFECTS), 1);
953 assert_eq!(loops(&it.func), 1);
954 assert_eq!(tally(&it.func, Opcode::Store), 1);
955 sound(&it.func, &mut it.names);
956 }
957
958 /// A total read after the loop without going through a parameter of the block that reads it.
959 ///
960 /// This is the shape that is actually there by the time the pass runs, rather than the loop
961 /// closed form one, because the block loop closed form put in the way is one `simplify-cfg`
962 /// folds back out. The value is defined in the loop and named in a block the loop dominates,
963 /// which is legal and is what a `for` loop adding to a total and printing it afterwards comes
964 /// out as. The worked out total goes where the loop's own was read.
965 #[test]
966 fn a_total_read_after_the_loop_by_another_road_is_worked_out_too() {
967 let mut it = shaped(Limit::Number(1000), What::ReadAfter);
968 let stats = delete(&mut it.func, &mut Fuel::unlimited());
969 assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
970 assert_eq!(stats.count(Kind::Optimized, WRITTEN), 1);
971 assert_eq!(loops(&it.func), 0);
972 assert_eq!(tally(&it.func, Opcode::Mul), 1, "one multiply, by the trip count");
973 assert_eq!(tally(&it.func, Opcode::Store), 1, "and the store that read it is still there");
974 sound(&it.func, &mut it.names);
975 }
976
977 /// The total the loop was going to hand over, worked out without running the loop.
978 ///
979 /// The loop adds `n` to a running total a thousand times, so the total it hands over is
980 /// `n * 1000`, and what is left of the function is that multiply. The count is 999 rather than
981 /// 1000 and the base is `n` rather than zero, because the exit edge is taken on the iteration
982 /// the test first fails and the total has already been added to by then: `n + n * 999`. Doing
983 /// the arithmetic on [`crate::scev::Invariant`] before writing anything down is what turns that
984 /// into one instruction rather than three nothing would fold, this being the last pass run.
985 #[test]
986 fn a_total_the_loop_hands_over_is_worked_out_in_front_of_it() {
987 let mut it = shaped(Limit::Number(1000), What::HandsOut);
988 let stats = delete(&mut it.func, &mut Fuel::unlimited());
989 assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
990 assert_eq!(stats.count(Kind::Optimized, WRITTEN), 1);
991 assert_eq!(loops(&it.func), 0);
992 assert_eq!(tally(&it.func, Opcode::Mul), 1, "one multiply, by the trip count");
993 assert_eq!(tally(&it.func, Opcode::Add), 0, "and nothing to add to it");
994 assert_eq!(handed(&it.func, &it.done), Some(1000), "n times a thousand");
995 sound(&it.func, &mut it.names);
996 }
997
998 /// The same thing where the amount is one, which leaves a number rather than a multiply.
999 #[test]
1000 fn a_total_that_went_up_by_one_is_left_as_a_number() {
1001 let mut it = shaped(Limit::Number(1000), What::HandsOne);
1002 let stats = delete(&mut it.func, &mut Fuel::unlimited());
1003 assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
1004 assert_eq!(stats.count(Kind::Optimized, WRITTEN), 1);
1005 assert_eq!(tally(&it.func, Opcode::Mul), 0);
1006 assert_eq!(tally(&it.func, Opcode::Add), 0);
1007 let handed = handed_value(&it.func, &it.done).expect("the total is handed over");
1008 let (imm, ty) = crate::fold::constant(&it.func, handed).expect("and it is a number");
1009 assert_eq!(imm.signed(ty), 1000);
1010 sound(&it.func, &mut it.names);
1011 }
1012
1013 /// The total handed over by a loop counting up to a value nothing here knows.
1014 ///
1015 /// The loop adds `n` to a running total until the counter arrives at `n`, so what it hands over
1016 /// is `n + n * max(n - 1, 0)` and every part of that is written down in front of where the loop
1017 /// was. The `select` is the clamp. [`crate::scev::Assumption::Entered`] says the count is
1018 /// either the distance to the limit or zero, and taking the larger of the two is that
1019 /// assumption paid for rather than leaned on. The sign extension in front of it is the exit
1020 /// test's own reading of the value, which is `<` on signed values here.
1021 #[test]
1022 fn a_total_from_a_loop_counting_up_to_a_value_handed_in_is_worked_out() {
1023 let mut it = shaped(Limit::Given, What::HandsOut);
1024 let stats = delete(&mut it.func, &mut Fuel::unlimited());
1025 assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
1026 assert_eq!(stats.count(Kind::Optimized, WRITTEN), 1);
1027 assert_eq!(loops(&it.func), 0);
1028 assert_eq!(tally(&it.func, Opcode::Select), 1, "the clamp at zero");
1029 assert_eq!(tally(&it.func, Opcode::ICmp), 1, "and the test it picks on");
1030 let read = "the count read the way the exit test read it";
1031 assert_eq!(tally(&it.func, Opcode::SExt), 1, "{read}");
1032 assert_eq!(tally(&it.func, Opcode::Trunc), 1, "and cut back to what the total is added in");
1033 assert_eq!(tally(&it.func, Opcode::Mul), 1, "one multiply, by the count");
1034 assert_eq!(tally(&it.func, Opcode::Add), 2, "the off by one on the count and the base");
1035 sound(&it.func, &mut it.names);
1036 }
1037
1038 /// The same total read after the loop rather than handed over, which is the corpus row.
1039 ///
1040 /// `loop-deletion.u32.1000000.unknown.read-back` is this shape, a bound nothing can see and a
1041 /// total read once the loop is done. It is the row rucc ran a million times and gcc 16 ran no
1042 /// times at all. The store stays and what it stores is worked out where the loop used to be.
1043 #[test]
1044 fn a_total_read_after_a_loop_counting_up_to_a_value_handed_in_is_worked_out() {
1045 let mut it = shaped(Limit::Given, What::ReadAfter);
1046 let stats = delete(&mut it.func, &mut Fuel::unlimited());
1047 assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
1048 assert_eq!(stats.count(Kind::Optimized, WRITTEN), 1);
1049 assert_eq!(loops(&it.func), 0);
1050 assert_eq!(tally(&it.func, Opcode::Select), 1, "the clamp at zero");
1051 assert_eq!(tally(&it.func, Opcode::Store), 1, "and the store that read it is still there");
1052 sound(&it.func, &mut it.names);
1053 }
1054
1055 /// A total that went up by a different amount every time is not a thing to write down.
1056 ///
1057 /// Here the total goes up by the counter rather than by a fixed amount, so what it holds after
1058 /// `k` times round is a square number and [`crate::scev`] rightly has no affine form for it.
1059 /// The loop ends and does nothing to memory, so the only thing keeping it is the total, and the
1060 /// pass says so rather than guessing at it.
1061 #[test]
1062 fn a_total_that_went_up_by_a_different_amount_each_time_leaves_the_loop_alone() {
1063 let mut it = shaped(Limit::Number(1000), What::HandsSquare);
1064 let stats = delete(&mut it.func, &mut Fuel::unlimited());
1065 assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
1066 assert_eq!(stats.count(Kind::Missed, NO_FORM), 1);
1067 assert_eq!(loops(&it.func), 1);
1068 sound(&it.func, &mut it.names);
1069 }
1070
1071 /// A loop that comes back but not after a number of steps anything here can work out.
1072 ///
1073 /// ```text
1074 /// entry(p, n): jump head(1)
1075 /// head(c): next = c + c; test = next < 1000; br test -> head(next), done()
1076 /// done: ret
1077 /// ```
1078 ///
1079 /// The counter doubles, so it is not a value that goes up by the same amount every time and
1080 /// there is no count to be had. It does terminate, which is the point: the pass is not allowed
1081 /// to lean on a loop looking harmless, only on the count that says it ends.
1082 fn doubling() -> Shape {
1083 let mut names = Interner::new();
1084 let signature = Signature::new().with_params(&[Type::PTR, Type::int(32)]);
1085 let mut func = Func::new(names.intern("f"), signature);
1086 let entry = func.create_block();
1087 let head = func.create_block();
1088 let done = func.create_block();
1089 func.append_param(entry, Type::PTR);
1090 func.append_param(entry, Type::int(32));
1091 let carried = func.append_param(head, Type::int(32));
1092
1093 let mut build = Builder::new(&mut func, entry);
1094 let one = build.iconst(Type::int(32), 1);
1095 build.jump(head, &[one]);
1096
1097 let mut build = Builder::new(&mut func, head);
1098 let next = build.binary(Opcode::Add, carried, carried, Flags::NSW);
1099 let stop = build.iconst(Type::int(32), 1000);
1100 let test = build.icmp(IntPred::Slt, next, stop);
1101 build.br_if(test, head, &[next], done, &[]);
1102 Builder::new(&mut func, done).ret(&[]);
1103 Shape { names, func, entry, done }
1104 }
1105
1106 #[test]
1107 fn a_loop_whose_count_is_not_known_is_left_alone() {
1108 let mut it = doubling();
1109 let stats = delete(&mut it.func, &mut Fuel::unlimited());
1110 assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
1111 assert_eq!(stats.count(Kind::Missed, NO_COUNT), 1);
1112 assert_eq!(loops(&it.func), 1);
1113 sound(&it.func, &mut it.names);
1114 }
1115
1116 #[test]
1117 fn the_pass_stops_when_the_fuel_runs_out() {
1118 let mut it = shaped(Limit::Number(1000), What::Nothing);
1119 let stats = delete(&mut it.func, &mut Fuel::of(0));
1120 assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
1121 assert_eq!(stats.count(Kind::Missed, NO_FUEL), 1);
1122 assert_eq!(loops(&it.func), 1);
1123 sound(&it.func, &mut it.names);
1124 }
1125
1126 #[test]
1127 fn a_function_with_no_body_is_not_a_problem() {
1128 let mut names = Interner::new();
1129 let mut func = Func::new(names.intern("f"), Signature::new());
1130 let stats = delete(&mut func, &mut Fuel::unlimited());
1131 assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
1132 }
1133}