Skip to main content

rucc_codegen/
expand.rs

1//! The IR rewrites the machine needs before a rule can be asked anything.
2//!
3//! Design: `spec/10-backend.md` section 10.2, which is where the ordering comes from.
4//!
5//! Everything else in this crate turns an instruction into instructions. This turns a block into
6//! blocks, which is the one thing a rule cannot do: a rule replaces a term with a term, and the
7//! replacement has nowhere to put a block, so a construct whose lowering is a new shape of control
8//! flow has to be rewritten before selection rather than during it.
9//!
10//! There is one such construct today and it is `switch`. Every other terminator leaves a block
11//! with one successor or two, which is what the block layout writes jumps for, and a `switch`
12//! leaves it with as many as the program had cases.
13//!
14//! # Why this is the backend's and not the front end's
15//!
16//! What a `switch` should become is a target decision and not a language one. A chain of compares
17//! is right for three cases and wrong for two hundred, where the answer is a jump table, and wrong
18//! again for twenty spread over a million, where it is a binary search on the value. A front end
19//! that picked one would be picking for every target at once, and the IR would no longer hold what
20//! the program said. So the `switch` survives as far as here, and here is where it is given up.
21//!
22//! What is written today is the chain, which `spec/10-backend.md` calls the version every compiler
23//! starts with. It is correct for any number of cases and it is slow for a large one. A jump table
24//! wants a read only section to put the table in and a relocation to reach it, and neither exists
25//! yet, so the chain is also the only one that could be written today.
26
27use rucc_ir::{BlockCall, Builder, Extra, Func, Imm, Inst, IntPred, Opcode, Value};
28
29/// Rewrites every `switch` in the function into branches, and leaves everything else alone.
30///
31/// The function is changed in place, which is what makes this the last thing that reads the IR as
32/// the front end built it. `--emit=ir` prints before this runs, and nothing after this asks what
33/// the program said, only what the machine has to do.
34pub fn switches(func: &mut Func) {
35    let found: Vec<Inst> = func
36        .blocks()
37        .filter_map(|block| func.terminator(block))
38        .filter(|&inst| func[inst].opcode == Opcode::Switch)
39        .collect();
40    for inst in found {
41        chain(func, inst);
42    }
43}
44
45/// One `switch`, as a compare and a branch for each case in the order they were written.
46///
47/// The block the `switch` was in gets the first compare, and each case after the first gets a
48/// block of its own that the one before it falls to when its compare failed. The last of them
49/// falls to the default, so the default is not a block anything is created for and the chain costs
50/// one block per case less one.
51///
52/// The order is the order the cases are in, which is the order the program wrote them and not a
53/// sorted one. Sorting would be the first half of a binary search and the second half is not here,
54/// so it would cost a reader the ability to look at the assembly and see their own `switch`, and
55/// buy nothing.
56fn chain(func: &mut Func, inst: Inst) {
57    let block = func.block_of(inst).expect("a terminator is in a block");
58    let span = func.span(inst);
59    let Extra::Switch(info) = func[inst].extra else { return };
60    let info = func[info];
61    let value = func[func[inst].args][0];
62    // The lane, because a `switch` on a vector is not a thing C can write and the immediates are
63    // an integer's either way.
64    let ty = func[value].ty.lane();
65    let calls: Vec<BlockCall> = func[info.targets].to_vec();
66    let cases: Vec<Imm> = func[info.cases].to_vec();
67    let Some((default, arms)) = calls.split_first() else { return };
68
69    // Before anything is written, because the builder appends and the `switch` is where the
70    // appending has to happen.
71    func.remove_inst(inst);
72
73    // A `switch` with nothing but a default is a jump, which is worth writing down rather than
74    // refusing: it is what a `switch` whose only label is `default` is, and it is also what one
75    // whose cases were all folded away by a later pass would be.
76    let Some((first, rest)) = arms.split_first() else {
77        let args: Vec<Value> = func[default.args].to_vec();
78        Builder::new(func, block).at(span).jump(default.block, &args);
79        return;
80    };
81
82    let mut at = block;
83    for (index, arm) in std::iter::once(first).chain(rest).enumerate() {
84        let last = index + 1 == arms.len();
85        let next = if last { default.block } else { func.create_block() };
86        let onward: Vec<Value> = if last { func[default.args].to_vec() } else { Vec::new() };
87        let taken: Vec<Value> = func[arm.args].to_vec();
88        let case = cases[index].signed(ty);
89
90        let mut build = Builder::new(func, at).at(span);
91        let want = build.iconst(ty, case);
92        let same = build.icmp(IntPred::Eq, value, want);
93        build.br_if(same, arm.block, &taken, next, &onward);
94        at = next;
95    }
96}
97
98/// The blocks a chain of `n` cases needs beyond the ones the program already had.
99///
100/// Here so that a test can say the number rather than count it, and so that whoever writes the
101/// jump table has one place to compare against.
102#[must_use]
103pub fn blocks_for(cases: usize) -> usize {
104    cases.saturating_sub(1)
105}
106
107#[cfg(test)]
108mod tests {
109    use rucc_base::Interner;
110    use rucc_ir::{Builder, Func, Module, Opcode, Signature, Type};
111    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
112
113    use super::{blocks_for, switches};
114
115    fn target() -> TargetInfo {
116        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
117    }
118
119    /// `int sw(int x) { switch (x) { case 1: return 10; case 2: return 20; default: return 30; } }`
120    /// as the walk builds it, which is the program in issue 275.
121    fn built(cases: &[i128]) -> (Interner, Func) {
122        let mut names = Interner::new();
123        let int = Type::int(32);
124        let mut func = Func::new(
125            names.intern("sw"),
126            Signature::new().with_params(&[int]).with_returns(&[int]),
127        );
128        let entry = func.create_block();
129        let x = func.append_param(entry, int);
130
131        let default = func.create_block();
132        let arms: Vec<_> = cases.iter().map(|_| func.create_block()).collect();
133        let table: Vec<(i128, rucc_ir::Block)> =
134            cases.iter().copied().zip(arms.iter().copied()).collect();
135        Builder::new(&mut func, entry).switch(x, default, &table);
136
137        for (index, &arm) in arms.iter().enumerate() {
138            let mut build = Builder::new(&mut func, arm);
139            let what = i128::try_from(index).expect("a small number of cases");
140            let v = build.iconst(int, (what + 1) * 10);
141            build.ret(&[v]);
142        }
143        let mut build = Builder::new(&mut func, default);
144        let v = build.iconst(int, 30);
145        build.ret(&[v]);
146        (names, func)
147    }
148
149    fn count(func: &Func) -> usize {
150        func.blocks().count()
151    }
152
153    fn printed(func: &Func, names: &mut Interner) -> String {
154        let module = Module::new(names.intern("sw.c"), &target());
155        rucc_ir::print_func(&module, func, names)
156    }
157
158    #[test]
159    fn a_switch_becomes_a_compare_and_a_branch_for_each_case() {
160        let (mut names, mut func) = built(&[1, 2]);
161        let before = count(&func);
162        switches(&mut func);
163        assert_eq!(count(&func), before + blocks_for(2));
164
165        let text = printed(&func, &mut names);
166        assert!(!text.contains("switch"), "the switch is gone: {text}");
167        assert_eq!(text.matches("icmp eq").count(), 2, "one compare per case: {text}");
168        assert_eq!(text.matches("br_if").count(), 2, "one branch per case: {text}");
169    }
170
171    #[test]
172    fn the_last_case_falls_to_the_default_rather_than_to_a_block_of_its_own() {
173        let (_, mut func) = built(&[7]);
174        let before = count(&func);
175        switches(&mut func);
176        // One case needs no chain block at all: the one compare goes to the arm or to the default.
177        assert_eq!(count(&func), before);
178        assert_eq!(blocks_for(1), 0);
179    }
180
181    #[test]
182    fn a_switch_with_only_a_default_is_a_jump() {
183        let (_, mut func) = built(&[]);
184        switches(&mut func);
185        let entry = func.entry().expect("an entry block");
186        let term = func.terminator(entry).expect("a terminator");
187        assert_eq!(func[term].opcode, Opcode::Jump);
188    }
189
190    /// The rewrite has to leave a function the verifier still accepts, since every check it makes
191    /// is one the rest of the back end assumes and none of them is rechecked after this runs.
192    #[test]
193    fn what_comes_out_is_valid_ir() {
194        let (mut names, mut func) = built(&[1, 2, 3, 4]);
195        switches(&mut func);
196        let module = Module::new(names.intern("sw.c"), &target());
197        rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
198    }
199
200    /// Nothing else is touched, which matters because this runs over every function whether or not
201    /// one has a `switch` in it.
202    #[test]
203    fn a_function_with_no_switch_is_left_exactly_as_it_was() {
204        let mut names = Interner::new();
205        let int = Type::int(32);
206        let mut func =
207            Func::new(names.intern("f"), Signature::new().with_params(&[int]).with_returns(&[int]));
208        let entry = func.create_block();
209        let x = func.append_param(entry, int);
210        Builder::new(&mut func, entry).ret(&[x]);
211
212        let before = printed(&func, &mut names);
213        switches(&mut func);
214        assert_eq!(printed(&func, &mut names), before);
215    }
216}