Skip to main content

rucc_lower/
ssa.rs

1//! SSA construction, by the algorithm of Braun and others.
2//!
3//! Design: `spec/08-ir.md` section 8.5.
4//!
5//! The classical way to get SSA out of a C front end is to give every local variable a stack
6//! slot, emit a load for every read and a store for every write, and then run a pass that
7//! builds dominance frontiers and deletes almost all of it again. That pass is the only one
8//! `-O0` runs, and everything it deletes was allocated first. So we do not build it: a local
9//! whose address is never taken never gets a slot, and the value it holds is worked out here
10//! while the tree is being walked.
11//!
12//! The algorithm is Braun, Buchwald, Hack, Leissa, Mallon and Zwinkau, "Simple and Efficient
13//! Construction of Static Single Assignment Form" (CC 2013). Writing a variable records the
14//! value it now holds in the block doing the writing. Reading one in a block that wrote it is
15//! a lookup. Reading one in a block that did not is a question for the predecessors, and the
16//! answer is either the one value they all agree on or a new block parameter that collects
17//! what each of them has.
18//!
19//! # Sealing
20//!
21//! The one thing the caller has to get right. A block is sealed when it will get no further
22//! predecessors, and reading a variable in an unsealed block cannot ask the predecessors
23//! because they are not all there yet. It gets a block parameter instead, which is filled in
24//! when the block is sealed. That is what a loop header needs and is the whole reason the
25//! algorithm handles loops without a dominance computation: the header is created, left
26//! unsealed while its body is walked, and sealed when the back edge has been emitted.
27//!
28//! A block with no back edge into it can be sealed as soon as it is created, and the walk
29//! seals almost everything immediately.
30//!
31//! # Block parameters, not phi nodes
32//!
33//! A phi in the paper is a block parameter here, and adding an operand to one is appending an
34//! argument to the branch in each predecessor. The paper's removal of trivial phis is done in
35//! two halves: a parameter found to stand for one value is recorded as standing for it, and
36//! nothing is deleted until [`Ssa::finish`], which resolves every operand in the function once
37//! and then drops the parameters and the arguments that went with them. One pass over the
38//! function rather than one walk per removal, and no use lists to keep in step.
39
40use std::collections::HashMap;
41
42use rucc_base::Idx;
43use rucc_diag::Span;
44use rucc_ir::{Block, BlockCall, Extra, Func, Imm, Inst, InstData, Opcode, Type, Value};
45
46/// A variable, which is somewhere in the source that can be written more than once.
47///
48/// What it names is the caller's business. The walk over the typed tree makes one of these per
49/// local whose address is never taken, and nothing here looks inside it.
50#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
51pub struct Var(u32);
52
53impl Var {
54    /// The variable with that number.
55    #[must_use]
56    pub const fn new(raw: u32) -> Var {
57        Var(raw)
58    }
59
60    /// Its number.
61    #[must_use]
62    pub const fn raw(self) -> u32 {
63        self.0
64    }
65}
66
67/// One edge into a block: where it comes from, and the branch target that carries its
68/// arguments.
69///
70/// The target is named by its place in the function's table rather than by the block it goes
71/// to, because an edge that has to grow an argument later needs to be found again, and two
72/// edges to the same block are the same block.
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74struct Edge {
75    from: Block,
76    call: Idx<BlockCall>,
77}
78
79/// A block parameter that stands for a variable, which is what the paper calls a phi.
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81struct Phi {
82    block: Block,
83    var: Var,
84}
85
86/// The state of an SSA construction over one function.
87#[derive(Debug)]
88pub struct Ssa {
89    /// The integer type a pointer has the width of, for the one value this has to invent.
90    address: Type,
91    /// What each variable holds at the end of each block.
92    defs: HashMap<(Var, Block), Value>,
93    /// Whether each block will get more predecessors.
94    sealed: Vec<bool>,
95    /// The parameters of each unsealed block that are waiting for its predecessors.
96    incomplete: Vec<Vec<(Var, Value)>>,
97    /// The edges into each block.
98    preds: Vec<Vec<Edge>>,
99    /// Which block parameters are ours, and what they stand for.
100    phis: HashMap<Value, Phi>,
101    /// For each value, the parameters of ours that read it. The use list the paper needs,
102    /// restricted to the uses it actually walks.
103    users: HashMap<Value, Vec<Value>>,
104    /// What each parameter that turned out to be redundant stands for instead.
105    subst: HashMap<Value, Value>,
106    /// The value a read of something never written gives back, one per type.
107    zero: Vec<(Type, Value)>,
108}
109
110impl Ssa {
111    /// A construction over a function whose pointers are as wide as that integer type.
112    ///
113    /// The width is here because of one case: reading a variable that nothing has written, in
114    /// a block nothing branches to. C says the value is indeterminate and
115    /// `spec/08-ir.md` section 8.4 says it is unspecified but stable, so this hands back a
116    /// zero, and a zero of pointer type is an integer zero cast to one.
117    #[must_use]
118    pub fn new(address: Type) -> Ssa {
119        Ssa {
120            address,
121            defs: HashMap::new(),
122            sealed: Vec::new(),
123            incomplete: Vec::new(),
124            preds: Vec::new(),
125            phis: HashMap::new(),
126            users: HashMap::new(),
127            subst: HashMap::new(),
128            zero: Vec::new(),
129        }
130    }
131
132    /// Records that a variable holds a value from here to the end of the block.
133    pub fn write(&mut self, var: Var, block: Block, value: Value) {
134        self.defs.insert((var, block), value);
135    }
136
137    /// The value a variable holds at this point in a block, which is the whole algorithm.
138    ///
139    /// The type is what a parameter would be given if one has to be made. It is passed in
140    /// rather than remembered per variable because the caller has it in hand and a variable
141    /// whose type this had to store would be a variable this had to be told about first. A
142    /// variable read at two types is a variable read wrong, and what comes back is whatever
143    /// the first read decided.
144    ///
145    /// What comes back may be a parameter that [`Ssa::finish`] later takes out. Putting it
146    /// into the function is safe, because finish rewrites everything the function holds.
147    /// Remembering it on the side and comparing it to something afterwards is not.
148    ///
149    /// # Panics
150    ///
151    /// Panics if the function has no entry block, which can only happen when nothing has been
152    /// built into it yet.
153    pub fn read(&mut self, func: &mut Func, var: Var, block: Block, ty: Type) -> Value {
154        // A run of blocks with one predecessor each is walked rather than recursed through.
155        // It is the shape a sequence of `if (c) return;` leaves behind, there can be thousands
156        // of them in one function, and the recursion the paper is written with would be that
157        // deep.
158        let mut chain = Vec::new();
159        let mut at = block;
160        let value = loop {
161            if let Some(&value) = self.defs.get(&(var, at)) {
162                break self.resolve(value);
163            }
164            self.reserve(at);
165            if !self.sealed[at.index()] {
166                break self.pending(func, var, at, ty);
167            }
168            match self.preds[at.index()].len() {
169                // Nothing reaches here, so nothing wrote it on the way.
170                0 => break self.undefined(func, ty),
171                // One predecessor is not a choice, so it needs no parameter to record one.
172                1 => {
173                    chain.push(at);
174                    at = self.preds[at.index()][0].from;
175                }
176                _ => break self.phi(func, var, at, ty),
177            }
178        };
179        for at in chain {
180            self.write(var, at, value);
181        }
182        self.write(var, block, value);
183        value
184    }
185
186    /// Records the edges a terminator makes, which is what tells this the shape of the CFG.
187    ///
188    /// Every terminator has to be handed over, and before the block it goes to is sealed. A
189    /// branch this was not told about is a predecessor that will be missed, and the parameter
190    /// that should have collected a value from it will be short an argument, which is
191    /// something the verifier says out loud rather than something that goes quiet.
192    ///
193    /// # Panics
194    ///
195    /// Panics if the instruction is not in a block.
196    pub fn branch(&mut self, func: &Func, inst: Inst) {
197        let from = func.block_of(inst).expect("a terminator in a block");
198        for call in func.target_list(inst).iter() {
199            let to = func[call].block;
200            self.reserve(to);
201            self.preds[to.index()].push(Edge { from, call });
202        }
203    }
204
205    /// Says that a block has all the predecessors it is going to have.
206    ///
207    /// # Panics
208    ///
209    /// Panics if the block has already been sealed.
210    pub fn seal(&mut self, func: &mut Func, block: Block) {
211        self.reserve(block);
212        assert!(!self.sealed[block.index()], "a block is sealed once");
213        self.sealed[block.index()] = true;
214        // Taken rather than iterated, because filling one of these in reads variables, which
215        // can leave a parameter waiting in another block but never in this one.
216        let waiting = std::mem::take(&mut self.incomplete[block.index()]);
217        for (var, phi) in waiting {
218            let value = self.operands(func, var, phi);
219            // Only when the parameter is still what the block holds. Between the read that made
220            // it and this, the block may have written the variable again, and that write is what
221            // the block holds now: the parameter is what it held at the top. A `switch` case is
222            // where this happens, since it is read from, written to, and sealed only when the
223            // whole body has been walked.
224            if self.defs.get(&(var, block)) == Some(&phi) {
225                self.write(var, block, value);
226            }
227        }
228    }
229
230    /// Whether a block has been told it has all its predecessors.
231    #[must_use]
232    pub fn is_sealed(&self, block: Block) -> bool {
233        self.sealed.get(block.index()).copied().unwrap_or(false)
234    }
235
236    /// Applies everything that was worked out and drops what turned out to be redundant.
237    ///
238    /// Until this runs the function is correct but wordy: a parameter that stands for one
239    /// value is still a parameter, and the branches still pass it. This resolves every operand
240    /// of every instruction and every argument of every branch once, and then takes the
241    /// parameters out along with the arguments that fed them.
242    pub fn finish(mut self, func: &mut Func) {
243        if self.subst.is_empty() {
244            return;
245        }
246
247        let blocks: Vec<Block> = func.blocks().collect();
248        for &block in &blocks {
249            let insts: Vec<Inst> = func.insts(block).collect();
250            for inst in insts {
251                let args = func[inst].args;
252                func.rewrite(args, |value| self.resolve(value));
253                for call in func.target_list(inst).iter() {
254                    let args = func[call].args;
255                    func.rewrite(args, |value| self.resolve(value));
256                }
257            }
258        }
259
260        // Which positions each block is losing. Read off the function rather than off the
261        // edges this was told about, so that a branch nobody mentioned still comes out with
262        // arguments that match the block it goes to.
263        let mut dropped: Vec<Vec<usize>> = vec![Vec::new(); func.counts().blocks];
264        for &block in &blocks {
265            for (index, &param) in func[block].params.iter().enumerate() {
266                if self.subst.contains_key(&param) {
267                    dropped[block.index()].push(index);
268                }
269            }
270        }
271
272        for &block in &blocks {
273            let insts: Vec<Inst> = func.insts(block).collect();
274            for inst in insts {
275                for at in func.target_list(inst).iter() {
276                    let mut call = func[at];
277                    let going = &dropped[call.block.index()];
278                    if going.is_empty() {
279                        continue;
280                    }
281                    let kept: Vec<Value> = func[call.args]
282                        .iter()
283                        .copied()
284                        .enumerate()
285                        .filter(|(index, _)| !going.contains(index))
286                        .map(|(_, value)| value)
287                        .collect();
288                    call.args = func.push_values(&kept);
289                    func.set_block_call(at, call);
290                }
291            }
292        }
293
294        for &block in &blocks {
295            if !dropped[block.index()].is_empty() {
296                func.retain_params(block, |param| !self.subst.contains_key(&param));
297            }
298        }
299    }
300
301    // The parts of the algorithm.
302
303    /// A parameter for a block that does not know its predecessors yet.
304    fn pending(&mut self, func: &mut Func, var: Var, block: Block, ty: Type) -> Value {
305        let phi = func.append_param(block, ty);
306        self.phis.insert(phi, Phi { block, var });
307        self.incomplete[block.index()].push((var, phi));
308        self.write(var, block, phi);
309        phi
310    }
311
312    /// A parameter for a block that has more than one predecessor, filled in at once.
313    fn phi(&mut self, func: &mut Func, var: Var, block: Block, ty: Type) -> Value {
314        let phi = func.append_param(block, ty);
315        self.phis.insert(phi, Phi { block, var });
316        // Written before the operands are read, because reading them can come back here, and
317        // this is what stops a loop going round for ever.
318        self.write(var, block, phi);
319        self.operands(func, var, phi)
320    }
321
322    /// Gives a parameter one argument in each predecessor, and asks whether it was worth it.
323    fn operands(&mut self, func: &mut Func, var: Var, phi: Value) -> Value {
324        let block = self.phis[&phi].block;
325        let ty = func[phi].ty;
326        // By index, because reading a variable in a predecessor can add edges elsewhere. Not
327        // here: the edges of a sealed block are all in, and an unsealed one is not filling
328        // anything in yet.
329        for index in 0..self.preds[block.index()].len() {
330            let edge = self.preds[block.index()][index];
331            let value = self.read(func, var, edge.from, ty);
332            let mut call = func[edge.call];
333            call.args = func.append_arg(call.args, value);
334            func.set_block_call(edge.call, call);
335            self.users.entry(value).or_default().push(phi);
336        }
337        self.trivial(func, phi)
338    }
339
340    /// Records a parameter as standing for one value, when that is all it ever collected.
341    ///
342    /// A parameter whose arguments are all one value, ignoring the ones that are the parameter
343    /// itself coming round a loop, is that value written at a distance. The paper deletes it
344    /// here. This records it and lets [`Ssa::finish`] do the deleting, which is what turns one
345    /// walk of the function per removal into one walk of the function.
346    fn trivial(&mut self, func: &mut Func, phi: Value) -> Value {
347        let block = self.phis[&phi].block;
348        let Some(at) = func[block].params.iter().position(|&param| param == phi) else {
349            return phi;
350        };
351
352        let mut same: Option<Value> = None;
353        for index in 0..self.preds[block.index()].len() {
354            let edge = self.preds[block.index()][index];
355            let arg = self.resolve(func[func[edge.call].args][at]);
356            if arg == phi || same == Some(arg) {
357                continue;
358            }
359            if same.is_some() {
360                // Two values reach here, so the parameter is what says which.
361                return phi;
362            }
363            same = Some(arg);
364        }
365
366        let same = match same {
367            Some(value) => value,
368            // No arguments at all, so nothing wrote the variable on any path that reaches
369            // here, and this is the same case as reading it in a block with no predecessors.
370            None => self.undefined(func, func[phi].ty),
371        };
372        self.subst.insert(phi, same);
373
374        // Whoever read this parameter now reads what it stands for, and one of them may have
375        // been holding on for this one value.
376        let users = self.users.remove(&phi).unwrap_or_default();
377        let inherited: Vec<Value> = users.iter().copied().filter(|&user| user != phi).collect();
378        self.users.entry(same).or_default().extend(inherited.iter().copied());
379        for user in inherited {
380            if !self.subst.contains_key(&user) {
381                self.trivial(func, user);
382            }
383        }
384        self.resolve(same)
385    }
386
387    /// What a value stands for, after every parameter along the way has been resolved.
388    ///
389    /// The walk terminates because a parameter is recorded as standing for something exactly
390    /// once and what it stands for was already resolved when it was recorded, so the chains
391    /// grow at the far end and never close on themselves.
392    fn resolve(&mut self, value: Value) -> Value {
393        let mut at = value;
394        while let Some(&next) = self.subst.get(&at) {
395            at = next;
396        }
397        if at != value {
398            self.subst.insert(value, at);
399        }
400        at
401    }
402
403    /// The value of a variable nothing wrote, which is a zero at the top of the entry block.
404    ///
405    /// One per type, so that two reads of the same uninitialized variable give the same
406    /// answer, which is what `spec/08-ir.md` means by unspecified but stable.
407    fn undefined(&mut self, func: &mut Func, ty: Type) -> Value {
408        if let Some(&(_, value)) = self.zero.iter().find(|&&(at, _)| at == ty) {
409            return value;
410        }
411
412        let entry = func.entry().expect("a function with a block in it");
413        let first = func.insts(entry).next();
414        let value = if ty.is_ptr() {
415            let int = self.constant(func, entry, first, self.address);
416            let args = func.push_values(&[int]);
417            let cast = func.create_inst(
418                InstData { args, ..InstData::new(Opcode::IntToPtr) },
419                &[ty],
420                Span::DUMMY,
421            );
422            place(func, entry, first, cast);
423            func[cast].first_result.expect("one result")
424        } else {
425            self.constant(func, entry, first, ty)
426        };
427
428        self.zero.push((ty, value));
429        value
430    }
431
432    /// A zero of an arithmetic type, at the top of the entry block.
433    fn constant(&mut self, func: &mut Func, entry: Block, first: Option<Inst>, ty: Type) -> Value {
434        let imm = if ty.lane().is_float() { Imm::from_bits(0) } else { Imm::int(0, ty.lane()) };
435        let imm = func.add_imm(imm);
436        let opcode = if ty.lane().is_float() { Opcode::FConst } else { Opcode::IConst };
437        let inst = func.create_inst(
438            InstData { extra: Extra::Imm(imm), ..InstData::new(opcode) },
439            &[ty],
440            Span::DUMMY,
441        );
442        place(func, entry, first, inst);
443        func[inst].first_result.expect("one result")
444    }
445
446    /// Makes room for a block this has not been told about before.
447    fn reserve(&mut self, block: Block) {
448        let wanted = block.index() + 1;
449        if self.sealed.len() < wanted {
450            self.sealed.resize(wanted, false);
451            self.incomplete.resize_with(wanted, Vec::new);
452            self.preds.resize_with(wanted, Vec::new);
453        }
454    }
455}
456
457/// Puts an instruction at the top of the entry block, before whatever was first.
458fn place(func: &mut Func, entry: Block, first: Option<Inst>, inst: Inst) {
459    match first {
460        Some(first) => func.insert_before(inst, first),
461        None => func.append_inst(entry, inst),
462    }
463}
464
465#[cfg(test)]
466mod tests {
467    use rucc_base::Interner;
468    use rucc_ir::{Builder, Flags, IntPred, Module, Signature, print_func, verify_func};
469    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
470
471    use super::*;
472
473    const I32: Type = Type::int(32);
474    const BOOL: Type = Type::int(1);
475
476    fn target() -> TargetInfo {
477        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
478    }
479
480    /// The function as text, after the verifier has agreed that it is one.
481    ///
482    /// Both halves matter and neither says what the other says. The verifier says the result is
483    /// a function the rest of the compiler may believe, and the text says which values the
484    /// algorithm decided on, which is the part a person has to read to know it did the right
485    /// thing rather than merely a consistent one.
486    fn checked(func: Func, names: &mut Interner) -> String {
487        let mut module = Module::new(names.intern("t.c"), &target());
488        let id = module.add_func(func);
489        if let Err(errors) = verify_func(&module, &module[id], names) {
490            let listed: Vec<String> = errors.iter().map(ToString::to_string).collect();
491            panic!("{}", listed.join("\n"));
492        }
493        print_func(&module, &module[id], names)
494    }
495
496    /// A function taking one condition and returning an `i32`, with its entry block sealed.
497    fn start(names: &mut Interner) -> (Func, Ssa, Block, Value) {
498        let signature = Signature::new().with_params(&[BOOL]).with_returns(&[I32]);
499        let mut func = Func::new(names.intern("f"), signature);
500        let entry = func.create_block();
501        let cond = func.append_param(entry, BOOL);
502        let mut ssa = Ssa::new(Type::int(64));
503        ssa.seal(&mut func, entry);
504        (func, ssa, entry, cond)
505    }
506
507    #[test]
508    fn a_variable_read_where_it_was_written_is_the_value_it_was_written() {
509        let mut names = Interner::new();
510        let (mut func, mut ssa, entry, _) = start(&mut names);
511        let x = Var::new(0);
512
513        let one = Builder::new(&mut func, entry).iconst(I32, 1);
514        ssa.write(x, entry, one);
515        let read = ssa.read(&mut func, x, entry, I32);
516        assert_eq!(read, one);
517
518        Builder::new(&mut func, entry).ret(&[read]);
519        ssa.finish(&mut func);
520        assert!(func[entry].params.len() == 1, "no parameter was needed");
521    }
522
523    #[test]
524    fn a_variable_written_on_both_arms_arrives_as_a_block_parameter() {
525        let mut names = Interner::new();
526        let (mut func, mut ssa, entry, cond) = start(&mut names);
527        let x = Var::new(0);
528
529        let then = func.create_block();
530        let otherwise = func.create_block();
531        let join = func.create_block();
532
533        let branch = Builder::new(&mut func, entry).br_if(cond, then, &[], otherwise, &[]);
534        ssa.branch(&func, branch);
535        ssa.seal(&mut func, then);
536        ssa.seal(&mut func, otherwise);
537
538        let one = Builder::new(&mut func, then).iconst(I32, 1);
539        ssa.write(x, then, one);
540        let jump = Builder::new(&mut func, then).jump(join, &[]);
541        ssa.branch(&func, jump);
542
543        let two = Builder::new(&mut func, otherwise).iconst(I32, 2);
544        ssa.write(x, otherwise, two);
545        let jump = Builder::new(&mut func, otherwise).jump(join, &[]);
546        ssa.branch(&func, jump);
547
548        ssa.seal(&mut func, join);
549        let read = ssa.read(&mut func, x, join, I32);
550        Builder::new(&mut func, join).ret(&[read]);
551        ssa.finish(&mut func);
552
553        assert_eq!(checked(func, &mut names), DIAMOND);
554    }
555
556    #[test]
557    fn a_variable_both_arms_agree_about_needs_no_block_parameter() {
558        let mut names = Interner::new();
559        let (mut func, mut ssa, entry, cond) = start(&mut names);
560        let x = Var::new(0);
561
562        let one = Builder::new(&mut func, entry).iconst(I32, 1);
563        ssa.write(x, entry, one);
564
565        let then = func.create_block();
566        let otherwise = func.create_block();
567        let join = func.create_block();
568
569        let branch = Builder::new(&mut func, entry).br_if(cond, then, &[], otherwise, &[]);
570        ssa.branch(&func, branch);
571        ssa.seal(&mut func, then);
572        ssa.seal(&mut func, otherwise);
573
574        for block in [then, otherwise] {
575            let jump = Builder::new(&mut func, block).jump(join, &[]);
576            ssa.branch(&func, jump);
577        }
578
579        ssa.seal(&mut func, join);
580        let read = ssa.read(&mut func, x, join, I32);
581        assert_eq!(read, one, "the parameter stood for the one value both arms had");
582        Builder::new(&mut func, join).ret(&[read]);
583        ssa.finish(&mut func);
584
585        assert!(func[join].params.is_empty(), "the parameter was taken out again");
586        assert_eq!(checked(func, &mut names), AGREED);
587    }
588
589    #[test]
590    fn a_variable_a_loop_changes_is_carried_by_the_headers_parameter() {
591        let mut names = Interner::new();
592        let (mut func, mut ssa, entry, _) = start(&mut names);
593        let x = Var::new(0);
594
595        let zero = Builder::new(&mut func, entry).iconst(I32, 0);
596        ssa.write(x, entry, zero);
597
598        let header = func.create_block();
599        let body = func.create_block();
600        let exit = func.create_block();
601
602        let jump = Builder::new(&mut func, entry).jump(header, &[]);
603        ssa.branch(&func, jump);
604
605        // The header is left unsealed, which is the whole point: the back edge has not been
606        // emitted yet and reading the variable here cannot ask the predecessors.
607        let counter = ssa.read(&mut func, x, header, I32);
608        let mut build = Builder::new(&mut func, header);
609        let ten = build.iconst(I32, 10);
610        let test = build.icmp(IntPred::Slt, counter, ten);
611        let branch = build.br_if(test, body, &[], exit, &[]);
612        ssa.branch(&func, branch);
613        ssa.seal(&mut func, body);
614        ssa.seal(&mut func, exit);
615
616        let carried = ssa.read(&mut func, x, body, I32);
617        let mut build = Builder::new(&mut func, body);
618        let one = build.iconst(I32, 1);
619        let next = build.binary(Opcode::Add, carried, one, Flags::NONE);
620        let jump = build.jump(header, &[]);
621        ssa.write(x, body, next);
622        ssa.branch(&func, jump);
623        ssa.seal(&mut func, header);
624
625        let result = ssa.read(&mut func, x, exit, I32);
626        Builder::new(&mut func, exit).ret(&[result]);
627        ssa.finish(&mut func);
628
629        assert_eq!(checked(func, &mut names), LOOP);
630    }
631
632    #[test]
633    fn a_variable_a_loop_does_not_change_is_not_carried_at_all() {
634        let mut names = Interner::new();
635        let (mut func, mut ssa, entry, cond) = start(&mut names);
636        let x = Var::new(0);
637
638        let seven = Builder::new(&mut func, entry).iconst(I32, 7);
639        ssa.write(x, entry, seven);
640
641        let header = func.create_block();
642        let body = func.create_block();
643        let exit = func.create_block();
644
645        let jump = Builder::new(&mut func, entry).jump(header, &[]);
646        ssa.branch(&func, jump);
647
648        let branch = Builder::new(&mut func, header).br_if(cond, body, &[], exit, &[]);
649        ssa.branch(&func, branch);
650        ssa.seal(&mut func, body);
651        ssa.seal(&mut func, exit);
652
653        // Read in the body, which is what makes the header need a parameter before the back
654        // edge says the parameter is only ever the one value.
655        let inside = ssa.read(&mut func, x, body, I32);
656        let mut build = Builder::new(&mut func, body);
657        build.binary(Opcode::Add, inside, inside, Flags::NONE);
658        let jump = build.jump(header, &[]);
659        ssa.branch(&func, jump);
660        ssa.seal(&mut func, header);
661
662        let result = ssa.read(&mut func, x, exit, I32);
663        Builder::new(&mut func, exit).ret(&[result]);
664        ssa.finish(&mut func);
665
666        assert!(func[header].params.is_empty(), "the parameter went, and the addition reads %1");
667        assert_eq!(checked(func, &mut names), UNCHANGED);
668    }
669
670    #[test]
671    fn a_variable_two_nested_loops_do_not_change_is_carried_by_neither() {
672        // The case the paper's recursive removal is for. The inner header's parameter looks
673        // like it collects two values until the outer header's parameter turns out to stand
674        // for one, and nothing but redoing the inner one finds that out.
675        let mut names = Interner::new();
676        let (mut func, mut ssa, entry, cond) = start(&mut names);
677        let x = Var::new(0);
678
679        let seven = Builder::new(&mut func, entry).iconst(I32, 7);
680        ssa.write(x, entry, seven);
681
682        let outer = func.create_block();
683        let inner = func.create_block();
684        let latch = func.create_block();
685        let exit = func.create_block();
686
687        let jump = Builder::new(&mut func, entry).jump(outer, &[]);
688        ssa.branch(&func, jump);
689
690        let jump = Builder::new(&mut func, outer).jump(inner, &[]);
691        ssa.branch(&func, jump);
692
693        let read = ssa.read(&mut func, x, inner, I32);
694        let mut build = Builder::new(&mut func, inner);
695        build.binary(Opcode::Add, read, read, Flags::NONE);
696        let branch = build.br_if(cond, inner, &[], latch, &[]);
697        ssa.branch(&func, branch);
698        ssa.seal(&mut func, inner);
699        ssa.seal(&mut func, latch);
700
701        let branch = Builder::new(&mut func, latch).br_if(cond, outer, &[], exit, &[]);
702        ssa.branch(&func, branch);
703        ssa.seal(&mut func, outer);
704        ssa.seal(&mut func, exit);
705
706        let result = ssa.read(&mut func, x, exit, I32);
707        Builder::new(&mut func, exit).ret(&[result]);
708        ssa.finish(&mut func);
709
710        assert!(func[outer].params.is_empty() && func[inner].params.is_empty());
711        assert_eq!(checked(func, &mut names), NESTED);
712    }
713
714    #[test]
715    fn a_write_after_the_read_that_made_a_parameter_is_what_the_block_holds() {
716        // The shape a `switch` case has. The case block is left unsealed while the rest of the
717        // body is walked, so a read in it makes a parameter, and a write after that read is
718        // what the block holds from then on. Sealing must not put the parameter back.
719        let mut names = Interner::new();
720        let (mut func, mut ssa, entry, cond) = start(&mut names);
721        let x = Var::new(0);
722
723        let one = Builder::new(&mut func, entry).iconst(I32, 1);
724        ssa.write(x, entry, one);
725
726        let case = func.create_block();
727        let other = func.create_block();
728        let branch = Builder::new(&mut func, entry).br_if(cond, case, &[], other, &[]);
729        ssa.branch(&func, branch);
730        ssa.seal(&mut func, other);
731
732        // The case, reached before it is known what else reaches it.
733        let read = ssa.read(&mut func, x, case, I32);
734        let sum = Builder::new(&mut func, case).binary(Opcode::Add, read, read, Flags::NONE);
735        ssa.write(x, case, sum);
736
737        // The other edge into it, which is what a `case` falling into the next one is.
738        let mut build = Builder::new(&mut func, other);
739        let two = build.iconst(I32, 2);
740        let jump = build.jump(case, &[]);
741        ssa.write(x, other, two);
742        ssa.branch(&func, jump);
743        ssa.seal(&mut func, case);
744
745        let after = ssa.read(&mut func, x, case, I32);
746        assert_eq!(after, sum, "the block holds what it wrote, not the parameter it started at");
747        Builder::new(&mut func, case).ret(&[after]);
748        ssa.finish(&mut func);
749
750        assert_eq!(checked(func, &mut names), WRITTEN_AFTER);
751    }
752
753    #[test]
754    fn a_variable_nothing_wrote_reads_as_the_same_zero_every_time() {
755        let mut names = Interner::new();
756        let (mut func, mut ssa, entry, _) = start(&mut names);
757        let x = Var::new(0);
758        let y = Var::new(1);
759        let z = Var::new(2);
760
761        let first = ssa.read(&mut func, x, entry, I32);
762        let second = ssa.read(&mut func, y, entry, I32);
763        let pointer = ssa.read(&mut func, z, entry, Type::PTR);
764        assert_eq!(first, second, "unspecified, and the same both times");
765        assert_ne!(first, pointer);
766
767        Builder::new(&mut func, entry).ret(&[first]);
768        ssa.finish(&mut func);
769        assert_eq!(checked(func, &mut names), UNWRITTEN);
770    }
771
772    /// Two arms with different values, so the block below them takes a parameter.
773    const DIAMOND: &str = "\
774func @f(i1) -> i32, linkage(external) {
775block0(%0: i1):
776    br_if %0, block1, block2
777
778block1:
779    %1 = iconst.i32 1
780    jump block3(%1)
781
782block2:
783    %2 = iconst.i32 2
784    jump block3(%2)
785
786block3(%3: i32):
787    return %3
788}
789";
790
791    /// Two arms with one value between them, so it does not.
792    const AGREED: &str = "\
793func @f(i1) -> i32, linkage(external) {
794block0(%0: i1):
795    %1 = iconst.i32 1
796    br_if %0, block1, block2
797
798block1:
799    jump block3
800
801block2:
802    jump block3
803
804block3:
805    return %1
806}
807";
808
809    /// A counter, which the header's parameter carries round and the body's addition adds
810    /// to. The parameter is what a phi node would have been.
811    const LOOP: &str = "\
812func @f(i1) -> i32, linkage(external) {
813block0(%0: i1):
814    %1 = iconst.i32 0
815    jump block1(%1)
816
817block1(%2: i32):
818    %3 = iconst.i32 10
819    %4 = icmp slt %2, %3
820    br_if %4, block2, block3
821
822block2:
823    %5 = iconst.i32 1
824    %6 = add %2, %5
825    jump block1(%6)
826
827block3:
828    return %2
829}
830";
831
832    /// The same loop over a variable nothing in it writes, where the header's parameter is
833    /// made, filled in from both edges, and then found to be the one value it started with.
834    const UNCHANGED: &str = "\
835func @f(i1) -> i32, linkage(external) {
836block0(%0: i1):
837    %1 = iconst.i32 7
838    jump block1
839
840block1:
841    br_if %0, block2, block3
842
843block2:
844    %2 = add %1, %1
845    jump block1
846
847block3:
848    return %1
849}
850";
851
852    /// Two nested loops over a variable neither writes. The inner header's parameter is only
853    /// found to be redundant after the outer one is, which is the recursion in the paper.
854    const NESTED: &str = "\
855func @f(i1) -> i32, linkage(external) {
856block0(%0: i1):
857    %1 = iconst.i32 7
858    jump block1
859
860block1:
861    jump block2
862
863block2:
864    %2 = add %1, %1
865    br_if %0, block2, block3
866
867block3:
868    br_if %0, block1, block4
869
870block4:
871    return %1
872}
873";
874
875    /// A block whose parameter carries what the variable held on the way in, and whose own
876    /// write is what it holds on the way out.
877    const WRITTEN_AFTER: &str = "\
878func @f(i1) -> i32, linkage(external) {
879block0(%0: i1):
880    %1 = iconst.i32 1
881    br_if %0, block1(%1), block2
882
883block1(%2: i32):
884    %3 = add %2, %2
885    return %3
886
887block2:
888    %4 = iconst.i32 2
889    jump block1(%4)
890}
891";
892
893    /// A read of something nothing wrote, twice for one type and once for a pointer, which is
894    /// two constants at the top of the entry block and a cast for the pointer.
895    const UNWRITTEN: &str = "\
896func @f(i1) -> i32, linkage(external) {
897block0(%0: i1):
898    %1 = iconst.i64 0
899    %2 = inttoptr.ptr %1
900    %3 = iconst.i32 0
901    return %3
902}
903";
904}