Skip to main content

cinrs_core/
cfg.rs

1//! The control-flow-graph lowering: the fallback for the jumps Rust cannot
2//! make.
3//!
4//! Nearly every C function's control flow maps onto Rust's own — that is what
5//! [`codegen`](crate::codegen) does by default, and it is what makes an
6//! expansion readable. An outward `goto` maps too, onto the labelled block or
7//! the labelled loop [`regions`](crate::regions) builds for it. What is left
8//! does not:
9//!
10//! * a `goto` **into** a block — a label inside a loop body, an `if` branch or
11//!   a `switch` group, named from outside it — or one whose regions would have
12//!   to overlap another label's without nesting, or one with a declaration
13//!   between it and the label it names; [`regions`](crate::regions) is where
14//!   the line is drawn;
15//! * a `case` (or `default`) label that is not a direct child of its `switch`
16//!   body — Duff's device, where the labels sit inside a loop the `switch`
17//!   wraps; and
18//! * GNU's `&&label` and the computed `goto *e` it feeds.
19//!
20//! A function containing any of them is lowered here instead: its whole body becomes
21//! a list of [basic blocks](BasicBlock) — straight-line statements ending in a
22//! [`Terminator`].
23//!
24//! What codegen does with that graph is **not** a state machine any more.
25//! [`reloop`](crate::reloop) reads it back into loops, `if`s and `match`es —
26//! [`Cfg::shape`] — so that the loop a C program wrote is a Rust loop even when
27//! its jumps are ones no label can express, and LLVM sees the graph GCC sees.
28//! The state machine below is the last resort, for a function whose shapes
29//! would nest deeper than `rustc`'s own parser will go — this `match` is flat
30//! however many arms it has:
31//!
32//! ```text
33//! let mut __cinrs_state: u32 = 0;
34//! 'cfg: loop {
35//!     match __cinrs_state {
36//!         0 => { …; __cinrs_state = 3; continue 'cfg; }
37//!         1 => { if c { __cinrs_state = 2; } else { __cinrs_state = 4; } continue 'cfg; }
38//!         2 => { return x; }
39//!         _ => ::core::unreachable!(),
40//!     }
41//! }
42//! ```
43//!
44//! Every C construct becomes an edge: a loop is a block that branches to its
45//! body or past it, `break` and `continue` are jumps to blocks the loop
46//! registered, a `switch` is one [`Terminator::Switch`] whose table the `case`
47//! labels fill in wherever they are found, and `goto` is a jump like any other.
48//! Expressions are untouched — codegen emits them exactly as it does in the
49//! structured mode.
50//!
51//! # Labels as values
52//!
53//! A computed `goto *e` is lowered the way GCC lowers it: as a `switch` over
54//! the labels whose address the function takes. Those labels are numbered
55//! `1..=n` in the order [`lower`] is given them, and `&&label` *is* that
56//! number, converted to `void *` — [`Cfg::labels`]. Every `goto *e` of the
57//! function stores `e`, converted to an integer, in one hidden local and jumps
58//! to one shared **dispatch** block, whose terminator is an ordinary
59//! [`Terminator::Switch`] with a `case k` for the `k`th label and a `default`
60//! that is unreachable: a value no label has is the undefined behaviour C
61//! already had — [`Terminator::InvalidTarget`], a panic in a debug build.
62//! Sharing the block (GCC's *factored* computed goto)
63//! is what keeps the graph small — an interpreter has a `goto *` at the end of
64//! every handler — and it makes the dispatch loop of such an interpreter an
65//! ordinary [Loop](crate::reloop::Shape::Loop) around a `match`, which is the
66//! shape its `switch`-based twin already had. After that the function has
67//! nothing but ordinary jumps, and the relooper handles it like any other.
68//!
69//! # The table fold
70//!
71//! An interpreter jumps through a table — `goto *dispatch[op]` — and reading
72//! the table on every instruction is a load its `switch` twin does not do. So
73//! when sema finds a table whose contents are known for good, it numbers that
74//! table's labels first and in its order, which makes element `e` label
75//! number `e + 1`, and `goto *table[e]` stores `e + 1` without reading the
76//! table at all. The conditions, checked in `sema`: an array the function's
77//! body defines — a block-scope `static`, or an automatic one with a single
78//! definition — whose initialiser is nothing but the addresses of *distinct* labels of the
79//! function, one per element; whose every use in the body is a read
80//! `table[e]` — never written, never addressed, never passed on, never in a
81//! statement expression; that no other static's initialiser names; and in a
82//! function that defines no nested function, which could name it too. When
83//! more than one table qualifies, the first one does. Any other use keeps the
84//! plain lowering, which is correct for every table. An automatic table is
85//! filled where it is declared, and a `goto` from before the declaration may
86//! reach a read of it first; the fold is right there too, because the number
87//! it stores never depended on what the array holds. The array itself is then
88//! written and never read, which LLVM drops.
89//!
90//! # Hoisting and renaming
91//!
92//! Rust has no way to jump over a `let`, so every local of the function is
93//! defined once at the top, zero-initialised, and the declaration itself
94//! becomes an assignment where it was written (only when the source really
95//! wrote an initialiser — see [`Stmt::Let`]). C allows the same name in
96//! sibling and nested blocks, so the hoisted locals need names that do not
97//! collide: sema has already resolved each declaration to a distinct
98//! [`ObjectId`], and this pass gives each one a unique Rust name derived from
99//! the C one (`x`, `x_1`, …), which is the single place the renaming happens.
100//! Objects with static storage duration are not hoisted at all; they are
101//! separate items already.
102//!
103//! # Cleanups
104//!
105//! Hoisting is also why `__attribute__((cleanup(f)))` cannot be a drop guard
106//! here: the local it would hang on lives to the end of the function, so the
107//! guard would run once, and far too late. The registration
108//! ([`ir::Stmt::Cleanup`]) is read as "from here to the end of this scope,
109//! this call is owed", and every edge that *leaves* a scope emits what it owes
110//! — innermost first — before it jumps: the bottom of a block, `break`,
111//! `continue`, `return` and a `goto` whose label is outside. That is also the
112//! only way to run a cleanup once per pass through a loop body. A forward
113//! `goto` names a label this pass has not reached yet, so how deep each label
114//! stands is collected up front, by the same walk over the same tree.
115//!
116//! [`ir::Stmt::Cleanup`]: crate::ir::Stmt::Cleanup
117//!
118//! # Readability
119//!
120//! A naive lowering produces a state per statement, which is unreadable. Three
121//! cheap cleanups run before codegen sees the graph, in this order:
122//!
123//! 1. **Jump threading.** A block with no statements that only jumps somewhere
124//!    else is removed and every edge into it re-pointed at its target. This is
125//!    what keeps `case 0: case 1:` from costing a state of its own.
126//! 2. **Chain merging.** A block whose only predecessor ends in an
127//!    unconditional jump to it is appended to that predecessor, which is what
128//!    keeps straight-line code in one arm.
129//! 3. **Reverse-postorder numbering**, so the states read top to bottom, with
130//!    unreachable blocks dropped on the way.
131//!
132//! A label whose address is taken needs no special treatment in any of them:
133//! its number is fixed before the graph is built, and the dispatch block's
134//! `switch` is an edge into its block like any other.
135//!
136//! They also make the graph the relooper reads a compact one, which is most of
137//! what keeps its output readable: a `case 0: case 1:` costs no shape, and a
138//! run of statements stays in one block rather than one shape per statement.
139
140use std::collections::{HashMap, HashSet};
141
142use crate::capture::SourceRange;
143use crate::ir::{
144    BinOp, BreakTarget, CaseRange, Expr, ExprKind, LabelId, LoopId, Object, ObjectId, Place,
145    PlaceKind, Stmt, Storage, SwitchId, is_always_true,
146};
147
148/// Identifies a basic block inside a [`Cfg`].
149#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
150pub struct BlockId(pub u32);
151
152impl BlockId {
153    pub(crate) fn index(self) -> usize {
154        self.0 as usize
155    }
156}
157
158/// A local variable hoisted to the top of the function.
159#[derive(Clone, Debug)]
160pub struct Local {
161    /// The object it defines.
162    pub object: ObjectId,
163    /// The Rust name it is given, unique within the function.
164    pub rust_name: String,
165}
166
167/// A straight-line run of statements ending in a [`Terminator`].
168#[derive(Clone, Debug)]
169pub struct BasicBlock {
170    /// Statements with no control flow of their own: [`ir::Stmt::Expr`],
171    /// [`ir::Stmt::Asm`], [`ir::Stmt::Nop`] and [`ir::Stmt::Vla`], whose three
172    /// bindings are hoisted like every other local and whose allocation stays
173    /// here.
174    ///
175    /// [`ir::Stmt::Expr`]: crate::ir::Stmt::Expr
176    /// [`ir::Stmt::Asm`]: crate::ir::Stmt::Asm
177    /// [`ir::Stmt::Nop`]: crate::ir::Stmt::Nop
178    /// [`ir::Stmt::Vla`]: crate::ir::Stmt::Vla
179    pub stmts: Vec<Stmt>,
180    /// How the block ends.
181    pub term: Terminator,
182}
183
184/// How a [`BasicBlock`] ends.
185#[derive(Clone, Debug)]
186pub enum Terminator {
187    /// Control moves to another block.
188    Jump {
189        /// The block entered.
190        target: BlockId,
191        /// The construct the edge came from.
192        range: SourceRange,
193    },
194    /// Control moves to one of two blocks depending on a condition.
195    Branch {
196        /// The controlling expression.
197        cond: Expr,
198        /// Entered when `cond` is non-zero.
199        then_blk: BlockId,
200        /// Entered otherwise.
201        else_blk: BlockId,
202    },
203    /// A `switch` dispatch — also what a computed `goto` becomes; see [Labels
204    /// as values](self#labels-as-values).
205    Switch {
206        /// The controlling expression, after the integer promotions.
207        value: Expr,
208        /// The `case` labels and the blocks they enter, in source order.
209        cases: Vec<(CaseRange, BlockId)>,
210        /// Where `default:` goes — past the statement when there is none.
211        default: BlockId,
212        /// Where the statement was written.
213        range: SourceRange,
214    },
215    /// The function returns.
216    Return {
217        /// The returned value.
218        value: Option<Expr>,
219        /// Where the statement was written.
220        range: SourceRange,
221    },
222    /// Control never gets here.
223    ///
224    /// Only reachable through a bug in this module, and emitted as
225    /// `unreachable!()` so that such a bug is loud rather than silent.
226    Unreachable,
227    /// The `default` of a computed `goto`'s dispatch: a value that is no
228    /// label's number, which is undefined behaviour in C.
229    ///
230    /// Emitted as `unreachable!()` in a build with debug assertions, which
231    /// catches the bug, and as `unreachable_unchecked()` otherwise — what GCC
232    /// assumes too, and what lets the `match` be a bare jump table.
233    InvalidTarget,
234}
235
236impl Terminator {
237    /// The blocks control can move to, in the order they should be read in.
238    pub(crate) fn successors(&self) -> Vec<BlockId> {
239        match self {
240            Terminator::Jump { target, .. } => vec![*target],
241            Terminator::Branch {
242                then_blk, else_blk, ..
243            } => vec![*then_blk, *else_blk],
244            Terminator::Switch { cases, default, .. } => {
245                let mut out: Vec<BlockId> = cases.iter().map(|(_, blk)| *blk).collect();
246                out.push(*default);
247                out
248            }
249            Terminator::Return { .. } | Terminator::Unreachable | Terminator::InvalidTarget => {
250                Vec::new()
251            }
252        }
253    }
254
255    /// Applies `f` to every block this terminator names.
256    fn map_targets(&mut self, mut f: impl FnMut(BlockId) -> BlockId) {
257        match self {
258            Terminator::Jump { target, .. } => *target = f(*target),
259            Terminator::Branch {
260                then_blk, else_blk, ..
261            } => {
262                *then_blk = f(*then_blk);
263                *else_blk = f(*else_blk);
264            }
265            Terminator::Switch { cases, default, .. } => {
266                for (_, blk) in cases.iter_mut() {
267                    *blk = f(*blk);
268                }
269                *default = f(*default);
270            }
271            Terminator::Return { .. } | Terminator::Unreachable | Terminator::InvalidTarget => {}
272        }
273    }
274}
275
276/// A function body lowered into basic blocks.
277#[derive(Clone, Debug)]
278pub struct Cfg {
279    /// Every local of the function, defined once at the top.
280    pub locals: Vec<Local>,
281    /// The blocks, in the order they should be emitted; block 0 is the entry.
282    pub blocks: Vec<BasicBlock>,
283    /// The number each label whose address was taken is given, from 1 up —
284    /// which is the value GNU's `&&label` has, and the `case` of the computed
285    /// `goto`'s dispatch that enters it. See [Labels as
286    /// values](self#labels-as-values) and [`ir::ExprKind::LabelAddr`].
287    ///
288    /// [`ir::ExprKind::LabelAddr`]: crate::ir::ExprKind::LabelAddr
289    pub labels: HashMap<LabelId, u32>,
290    /// The structured form of the same graph, when there is one; see
291    /// [`reloop`](crate::reloop).
292    ///
293    /// This is what codegen emits: loops, `if`s and `match`es, with the state
294    /// machine below kept only for a graph the relooper gives up on.
295    pub shape: Option<crate::reloop::Plan>,
296}
297
298/// Lowers a checked function body into a control-flow graph.
299///
300/// `params` are the function's parameters, whose names the hoisted locals must
301/// not collide with, `objects` is the program's object table, and `taken` are
302/// the labels a `&&label` took the address of — in a fixed order, since it
303/// decides the number each one's address is.
304///
305/// `goto_value` is the hidden automatic object, of an integer type, every
306/// computed `goto` stores its target in on the way to the shared dispatch; sema
307/// makes one for a function with a label in `taken`. A function without one
308/// has no label a `goto *` could reach, so each of them is unreachable.
309///
310/// `table` is a dispatch table sema found [foldable](self#the-table-fold):
311/// its elements are the first labels of `taken`, in order, so reading element
312/// `e` gives label number `e + 1`.
313///
314/// `names` is what each label was called in C, which the loops
315/// [`reloop`](crate::reloop) recovers are named after.
316///
317/// The body must end in a statement that always terminates — sema appends a
318/// `return` when the source does not — so that no block falls off the end.
319pub fn lower(
320    body: Vec<Stmt>,
321    params: &[ObjectId],
322    objects: &[Object],
323    taken: &[LabelId],
324    goto_value: Option<ObjectId>,
325    table: Option<ObjectId>,
326    names: &HashMap<LabelId, String>,
327) -> Cfg {
328    let mut used: HashSet<String> = params
329        .iter()
330        .map(|id| objects[id.0 as usize].name.clone())
331        .collect();
332    used.insert("__cinrs_state".to_owned());
333
334    let mut lowerer = Lowerer {
335        objects,
336        blocks: Vec::new(),
337        current: None,
338        labels: HashMap::new(),
339        loops: HashMap::new(),
340        switches: HashMap::new(),
341        locals: Vec::new(),
342        used,
343        cleanups: Vec::new(),
344        label_cleanups: HashMap::new(),
345        taken: Vec::new(),
346        goto_value,
347        table,
348        dispatch: None,
349    };
350    // A `goto` has to know how many cleanups its *target* is inside, and a
351    // forward one names a label the walk below has not reached yet.
352    lowerer.collect_label_cleanups(&body, 0);
353    // The blocks of the address-taken labels are made first, so that every
354    // computed `goto` knows all of them — each one is a possible target.
355    for id in taken {
356        let block = lowerer.label_block(*id);
357        lowerer.taken.push((*id, block));
358    }
359    if let Some(object) = goto_value {
360        let name = objects[object.0 as usize].name.clone();
361        let rust_name = lowerer.unique_name(&name);
362        lowerer.locals.push(Local { object, rust_name });
363    }
364    let entry = lowerer.new_block();
365    lowerer.current = Some(entry);
366    lowerer.stmts(body);
367    // Sema guarantees a trailing `return`, so anything still open here can
368    // never be entered.
369    if let Some(open) = lowerer.current.take() {
370        lowerer.blocks[open.index()].term = Terminator::Unreachable;
371    }
372    let labels = taken
373        .iter()
374        .enumerate()
375        .map(|(index, id)| (*id, index as u32 + 1))
376        .collect();
377    lowerer.finish(entry, labels, names)
378}
379
380/// What a loop's `break` and `continue` jump to, and how many cleanups each of
381/// the two edges leaves behind.
382#[derive(Clone, Copy)]
383struct LoopBlocks {
384    brk: BlockId,
385    brk_cleanups: usize,
386    cont: BlockId,
387    cont_cleanups: usize,
388}
389
390/// A `switch` whose body is being walked.
391struct SwitchFrame {
392    brk: BlockId,
393    brk_cleanups: usize,
394    cases: Vec<(CaseRange, BlockId)>,
395    default: Option<BlockId>,
396}
397
398struct Lowerer<'a> {
399    objects: &'a [Object],
400    blocks: Vec<BasicBlock>,
401    /// The block statements are being appended to, if control can reach one.
402    current: Option<BlockId>,
403    labels: HashMap<LabelId, BlockId>,
404    loops: HashMap<LoopId, LoopBlocks>,
405    switches: HashMap<SwitchId, SwitchFrame>,
406    locals: Vec<Local>,
407    used: HashSet<String>,
408    /// The `cleanup` calls owed by the scopes enclosing the statement being
409    /// lowered, innermost last.
410    ///
411    /// A hoisted local lives to the end of the function, so there is no scope
412    /// left for a drop guard to hang on; every edge that leaves a scope emits
413    /// the calls it owes instead — which is also the only way to run a
414    /// cleanup once per pass through a loop body.
415    cleanups: Vec<Expr>,
416    /// How many cleanups are owed where each label stands, from the pre-pass
417    /// over the same tree; see [`Lowerer::collect_label_cleanups`].
418    label_cleanups: HashMap<LabelId, usize>,
419    /// The labels a `&&label` took the address of, and the blocks they stand
420    /// for, in the order they were given — which is the order they are
421    /// numbered in, from 1.
422    taken: Vec<(LabelId, BlockId)>,
423    /// Where every computed `goto` stores its target; see [`lower`].
424    goto_value: Option<ObjectId>,
425    /// The dispatch table whose reads are folded; see [`lower`].
426    table: Option<ObjectId>,
427    /// The block every computed `goto` jumps to, made by the first one.
428    dispatch: Option<BlockId>,
429}
430
431impl Lowerer<'_> {
432    // -- building blocks ----------------------------------------------------
433
434    fn new_block(&mut self) -> BlockId {
435        let id = BlockId(self.blocks.len() as u32);
436        self.blocks.push(BasicBlock {
437            stmts: Vec::new(),
438            term: Terminator::Unreachable,
439        });
440        id
441    }
442
443    /// The block statements go into, starting a fresh one if the last one has
444    /// already been terminated.
445    fn current(&mut self) -> BlockId {
446        match self.current {
447            Some(id) => id,
448            None => {
449                let id = self.new_block();
450                self.current = Some(id);
451                id
452            }
453        }
454    }
455
456    fn push(&mut self, stmt: Stmt) {
457        let id = self.current();
458        self.blocks[id.index()].stmts.push(stmt);
459    }
460
461    /// Ends the current block, leaving control nowhere until a caller says
462    /// where it continues.
463    fn terminate(&mut self, term: Terminator) {
464        let id = self.current();
465        self.blocks[id.index()].term = term;
466        self.current = None;
467    }
468
469    fn jump(&mut self, target: BlockId, range: SourceRange) {
470        self.terminate(Terminator::Jump { target, range });
471    }
472
473    fn continue_at(&mut self, block: BlockId) {
474        self.current = Some(block);
475    }
476
477    /// The block a label stands for, created the first time it is mentioned so
478    /// that a forward `goto` works.
479    fn label_block(&mut self, id: LabelId) -> BlockId {
480        if let Some(block) = self.labels.get(&id) {
481            return *block;
482        }
483        let block = self.new_block();
484        self.labels.insert(id, block);
485        block
486    }
487
488    // -- cleanups -----------------------------------------------------------
489
490    /// Records how many `cleanup` calls are owed where each label stands.
491    ///
492    /// It is the same walk [`Lowerer::stmt`] makes, over the same tree, so the
493    /// two agree by construction; doing it ahead of time is what lets a
494    /// forward `goto` know how many scopes it leaves. A jump *into* the scope
495    /// of a cleanup is not refused — GCC allows it, and runs the cleanup at
496    /// the end of the scope all the same, which is what a lexical count does
497    /// here too.
498    fn collect_label_cleanups(&mut self, stmts: &[Stmt], depth: usize) {
499        let mut depth = depth;
500        for stmt in stmts {
501            match stmt {
502                Stmt::Cleanup(_) => depth += 1,
503                Stmt::Block(items) => self.collect_label_cleanups(items, depth),
504                Stmt::Label { id, body, .. } => {
505                    self.label_cleanups.insert(*id, depth);
506                    self.collect_label_cleanups(std::slice::from_ref(body), depth);
507                }
508                Stmt::If {
509                    then_branch,
510                    else_branch,
511                    ..
512                } => {
513                    self.collect_label_cleanups(std::slice::from_ref(then_branch), depth);
514                    if let Some(branch) = else_branch {
515                        self.collect_label_cleanups(std::slice::from_ref(branch), depth);
516                    }
517                }
518                Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => {
519                    self.collect_label_cleanups(std::slice::from_ref(body), depth);
520                }
521                Stmt::For { init, body, .. } => {
522                    // A declaration in the init clause is scoped to the loop,
523                    // so its cleanup is owed by everything inside it.
524                    let inner = depth + init.iter().filter(|s| s.is_cleanup()).count();
525                    self.collect_label_cleanups(std::slice::from_ref(body), inner);
526                }
527                Stmt::SwitchTree(switch) => {
528                    self.collect_label_cleanups(std::slice::from_ref(&switch.body), depth);
529                }
530                Stmt::Case { body, .. } => {
531                    self.collect_label_cleanups(std::slice::from_ref(body), depth);
532                }
533                _ => {}
534            }
535        }
536    }
537
538    /// Emits the cleanup calls owed by the scopes between here and `depth`,
539    /// innermost first, on the edge about to be taken.
540    fn leave_cleanups(&mut self, depth: usize) {
541        if self.cleanups.len() <= depth || self.current.is_none() {
542            return;
543        }
544        let owed: Vec<Expr> = self.cleanups[depth..].iter().rev().cloned().collect();
545        for call in owed {
546            self.push(Stmt::Expr(call));
547        }
548    }
549
550    // -- statements ---------------------------------------------------------
551
552    fn stmts(&mut self, stmts: Vec<Stmt>) {
553        for stmt in stmts {
554            self.stmt(stmt);
555        }
556    }
557
558    /// Lowers a scope: its statements, then the cleanups it owes on the way
559    /// out of it.
560    fn scope(&mut self, stmts: Vec<Stmt>) {
561        let depth = self.cleanups.len();
562        self.stmts(stmts);
563        self.leave_cleanups(depth);
564        self.cleanups.truncate(depth);
565    }
566
567    fn stmt(&mut self, stmt: Stmt) {
568        match stmt {
569            Stmt::Nop => {}
570            Stmt::Expr(expr) => self.push(Stmt::Expr(expr)),
571            // No control flow of its own: `asm goto` is refused.
572            asm @ Stmt::Asm(_) => self.push(asm),
573            Stmt::Let {
574                object,
575                init,
576                explicit,
577            } => self.local(object, init, explicit),
578            Stmt::Vla(def) => self.vla(*def),
579            // The registration itself generates nothing: what it means is the
580            // calls the edges out of this scope now owe.
581            Stmt::Cleanup(def) => self.cleanups.push(def.call),
582            Stmt::Block(items) => self.scope(items),
583            Stmt::If {
584                cond,
585                then_branch,
586                else_branch,
587            } => self.if_stmt(cond, *then_branch, else_branch.map(|b| *b)),
588            Stmt::While {
589                id,
590                cond,
591                body,
592                range,
593            } => self.while_stmt(id, cond, *body, range),
594            Stmt::DoWhile {
595                id,
596                body,
597                cond,
598                range,
599            } => self.do_while(id, *body, cond, range),
600            Stmt::For {
601                id,
602                init,
603                cond,
604                step,
605                body,
606                range,
607            } => self.for_stmt(id, init, cond, step, *body, range),
608            Stmt::SwitchTree(switch) => self.switch(*switch),
609            Stmt::Case {
610                switch,
611                value,
612                body,
613                range,
614            } => self.case(switch, value, *body, range),
615            Stmt::Label { id, body, range } => {
616                let block = self.label_block(id);
617                self.jump(block, range);
618                self.continue_at(block);
619                self.stmt(*body);
620            }
621            Stmt::Goto { id, range } => {
622                let block = self.label_block(id);
623                // Only the scopes the jump leaves are cleaned up; the label's
624                // own scopes stay, and are cleaned up where they end.
625                let depth = self.label_cleanups.get(&id).copied().unwrap_or(0);
626                self.leave_cleanups(depth.min(self.cleanups.len()));
627                self.jump(block, range);
628            }
629            Stmt::GotoPtr { target, range } => self.goto_ptr(target, range),
630            Stmt::Break { target, range } => {
631                let block = match target {
632                    BreakTarget::Loop(id) => self.loops.get(&id).map(|l| (l.brk, l.brk_cleanups)),
633                    BreakTarget::Switch(id) => {
634                        self.switches.get(&id).map(|s| (s.brk, s.brk_cleanups))
635                    }
636                };
637                match block {
638                    Some((block, depth)) => {
639                        self.leave_cleanups(depth);
640                        self.jump(block, range);
641                    }
642                    // Sema rejects a `break` with nothing to leave.
643                    None => self.terminate(Terminator::Unreachable),
644                }
645            }
646            Stmt::Continue { id, range } => {
647                match self.loops.get(&id).map(|l| (l.cont, l.cont_cleanups)) {
648                    Some((block, depth)) => {
649                        self.leave_cleanups(depth);
650                        self.jump(block, range);
651                    }
652                    None => self.terminate(Terminator::Unreachable),
653                }
654            }
655            // The value is already in a temporary where a cleanup could see
656            // it: sema puts it there, because GCC computes the result before
657            // the cleanups run.
658            Stmt::Return { value, range } => {
659                self.leave_cleanups(0);
660                self.terminate(Terminator::Return { value, range });
661            }
662            Stmt::Switch(_) => {
663                unreachable!("sema lowers every switch into a SwitchTree in CFG mode")
664            }
665            Stmt::Region(_) => {
666                unreachable!("a region is only built for a body that stays structured")
667            }
668        }
669    }
670
671    /// GNU's computed `goto *e`.
672    ///
673    /// Which label it enters is a run-time value, so which scopes it leaves is
674    /// one too. What is certain is that every possible target is a label whose
675    /// address was taken, so the scopes *no* target is inside are left on the
676    /// way — that is the shallowest of their depths, and it is exactly right
677    /// in the ordinary case where every such label stands at the top of the
678    /// function. Anything deeper is owed by a scope some target may still be
679    /// in, and is left to the end of that scope, as a `goto` into one is.
680    ///
681    /// The jump itself is a store of the target, converted to an integer, and
682    /// a jump to the function's one dispatch block; see [Labels as
683    /// values](self#labels-as-values).
684    fn goto_ptr(&mut self, target: Expr, range: SourceRange) {
685        let Some(object) = self.goto_value else {
686            // No label has its address taken, so no value can be a target.
687            self.terminate(Terminator::Unreachable);
688            return;
689        };
690        let depth = self
691            .taken
692            .iter()
693            .map(|(id, _)| self.label_cleanups.get(id).copied().unwrap_or(0))
694            .min()
695            .unwrap_or(0);
696        self.leave_cleanups(depth.min(self.cleanups.len()));
697        let place = self.goto_value_place(object, range);
698        let ty = place.ty;
699        // `goto *table[e]` through the folded table: element `e` is label
700        // number `e + 1`, so the table is never read at all.
701        let folded = self
702            .table
703            .and_then(|table| crate::ir::table_read(&target, table))
704            .cloned();
705        let value = match folded {
706            Some(index) => Expr::new(
707                ExprKind::Binary {
708                    op: BinOp::Add,
709                    lhs: Box::new(Expr::new(ExprKind::Cast(Box::new(index)), ty, range)),
710                    rhs: Box::new(Expr::new(ExprKind::Int(1), ty, range)),
711                },
712                ty,
713                range,
714            ),
715            None => Expr::new(ExprKind::Cast(Box::new(target)), ty, range),
716        };
717        self.push(Stmt::Expr(Expr::new(
718            ExprKind::Assign {
719                place,
720                value: Box::new(value),
721            },
722            ty,
723            range,
724        )));
725        let dispatch = self.dispatch(object, range);
726        self.jump(dispatch, range);
727    }
728
729    /// The hidden local a computed `goto` stores its target in.
730    fn goto_value_place(&self, object: ObjectId, range: SourceRange) -> Place {
731        Place {
732            kind: PlaceKind::Object(object),
733            ty: self.objects[object.0 as usize].ty,
734            is_const: false,
735            range,
736        }
737    }
738
739    /// The block every computed `goto` of the function jumps to: a `switch`
740    /// on the stored target with one `case` per address-taken label, whose
741    /// `default` — a value that is no label's — is unreachable.
742    fn dispatch(&mut self, object: ObjectId, range: SourceRange) -> BlockId {
743        if let Some(block) = self.dispatch {
744            return block;
745        }
746        let block = self.new_block();
747        let invalid = self.new_block();
748        self.blocks[invalid.index()].term = Terminator::InvalidTarget;
749        let place = self.goto_value_place(object, range);
750        let ty = place.ty;
751        let cases = self
752            .taken
753            .iter()
754            .enumerate()
755            .map(|(index, (_, target))| (CaseRange::single(index as i128 + 1), *target))
756            .collect();
757        self.blocks[block.index()].term = Terminator::Switch {
758            value: Expr::new(ExprKind::Load(place), ty, range),
759            cases,
760            default: invalid,
761            range,
762        };
763        self.dispatch = Some(block);
764        block
765    }
766
767    /// Hoists a local's definition and leaves its initialiser behind.
768    fn local(&mut self, object: ObjectId, init: Expr, explicit: bool) {
769        let info = &self.objects[object.0 as usize];
770        if !matches!(info.storage, Storage::Automatic) {
771            // A function-local `static` is an item of its own and keeps its
772            // value across calls; it is not a local at all.
773            return;
774        }
775        let (ty, is_const, range, name) = (info.ty, info.is_const, info.range, info.name.clone());
776        let rust_name = self.unique_name(&name);
777        self.locals.push(Local { object, rust_name });
778        if !explicit {
779            return;
780        }
781        let place = Place {
782            kind: PlaceKind::Object(object),
783            ty,
784            is_const,
785            range,
786        };
787        self.push(Stmt::Expr(Expr::new(
788            ExprKind::Assign {
789                place,
790                value: Box::new(init),
791            },
792            ty,
793            range,
794        )));
795    }
796
797    /// Hoists the two bindings a variably modified object needs — its frame,
798    /// which is a slot of the function's array of arena marks, and the
799    /// pointer — leaving the allocation itself where the declaration was
800    /// written.
801    ///
802    /// The storage therefore lives until the declaration is reached again or
803    /// the function returns rather than to the end of the block — the price
804    /// of having no way to jump over a `let` — which a C program can only
805    /// observe as memory it expected to have been given back. Re-reaching the
806    /// declaration gives back the previous pass's space, and everything
807    /// allocated after it, before allocating the fresh object C99 6.2.4p7
808    /// asks for.
809    fn vla(&mut self, def: crate::ir::VlaDef) {
810        for object in [def.storage, def.object] {
811            let name = self.objects[object.0 as usize].name.clone();
812            let rust_name = self.unique_name(&name);
813            self.locals.push(Local { object, rust_name });
814        }
815        self.push(Stmt::Vla(Box::new(def)));
816    }
817
818    /// A Rust name for a hoisted local, derived from the C one.
819    fn unique_name(&mut self, name: &str) -> String {
820        if self.used.insert(name.to_owned()) {
821            return name.to_owned();
822        }
823        for n in 1u32.. {
824            let candidate = format!("{name}_{n}");
825            if self.used.insert(candidate.clone()) {
826                return candidate;
827            }
828        }
829        unreachable!("the loop above always terminates")
830    }
831
832    fn if_stmt(&mut self, cond: Expr, then_branch: Stmt, else_branch: Option<Stmt>) {
833        let range = cond.range;
834        let then_blk = self.new_block();
835        let else_blk = self.new_block();
836        let join = if else_branch.is_some() {
837            self.new_block()
838        } else {
839            else_blk
840        };
841        self.terminate(Terminator::Branch {
842            cond,
843            then_blk,
844            else_blk,
845        });
846        self.continue_at(then_blk);
847        self.stmt(then_branch);
848        self.jump(join, range);
849        if let Some(else_branch) = else_branch {
850            self.continue_at(else_blk);
851            self.stmt(else_branch);
852            self.jump(join, range);
853        }
854        self.continue_at(join);
855    }
856
857    fn while_stmt(&mut self, id: LoopId, cond: Expr, body: Stmt, range: SourceRange) {
858        let head = self.new_block();
859        let body_blk = self.new_block();
860        let exit = self.new_block();
861        self.jump(head, range);
862        self.continue_at(head);
863        self.test(cond, body_blk, exit, range);
864        let depth = self.cleanups.len();
865        self.loops.insert(
866            id,
867            LoopBlocks {
868                brk: exit,
869                brk_cleanups: depth,
870                cont: head,
871                cont_cleanups: depth,
872            },
873        );
874        self.continue_at(body_blk);
875        self.stmt(body);
876        self.jump(head, range);
877        self.continue_at(exit);
878    }
879
880    fn do_while(&mut self, id: LoopId, body: Stmt, cond: Expr, range: SourceRange) {
881        let body_blk = self.new_block();
882        let test = self.new_block();
883        let exit = self.new_block();
884        self.jump(body_blk, range);
885        let depth = self.cleanups.len();
886        self.loops.insert(
887            id,
888            LoopBlocks {
889                brk: exit,
890                brk_cleanups: depth,
891                cont: test,
892                cont_cleanups: depth,
893            },
894        );
895        self.continue_at(body_blk);
896        self.stmt(body);
897        self.jump(test, range);
898        self.continue_at(test);
899        self.test(cond, body_blk, exit, range);
900        self.continue_at(exit);
901    }
902
903    fn for_stmt(
904        &mut self,
905        id: LoopId,
906        init: Vec<Stmt>,
907        cond: Option<Expr>,
908        step: Option<Expr>,
909        body: Stmt,
910        range: SourceRange,
911    ) {
912        // C99 scopes a declaration in the init clause to the whole loop, so a
913        // `cleanup` on one is owed by everything that leaves the loop — the
914        // `break` and the falling-out edge, but not the `continue`, which
915        // stays inside.
916        let outer = self.cleanups.len();
917        self.stmts(init);
918        let inner = self.cleanups.len();
919        let head = self.new_block();
920        let body_blk = self.new_block();
921        let step_blk = self.new_block();
922        let exit = self.new_block();
923        self.jump(head, range);
924        self.continue_at(head);
925        match cond {
926            Some(cond) => self.test(cond, body_blk, exit, range),
927            None => self.jump(body_blk, range),
928        }
929        self.loops.insert(
930            id,
931            LoopBlocks {
932                // The exit block runs what the init clause owes, whichever
933                // way the loop was left, so a `break` only has the body's.
934                brk: exit,
935                brk_cleanups: inner,
936                cont: step_blk,
937                cont_cleanups: inner,
938            },
939        );
940        self.continue_at(body_blk);
941        self.stmt(body);
942        self.jump(step_blk, range);
943        self.continue_at(step_blk);
944        if let Some(step) = step {
945            self.push(Stmt::Expr(step));
946        }
947        self.jump(head, range);
948        self.continue_at(exit);
949        // Both ways out of the loop arrive here; the cleanups the init clause
950        // owes run once, on the way past.
951        self.leave_cleanups(outer);
952        self.cleanups.truncate(outer);
953    }
954
955    /// Ends the current block on a loop's controlling expression, which a
956    /// constantly true one turns into an unconditional edge.
957    fn test(&mut self, cond: Expr, then_blk: BlockId, else_blk: BlockId, range: SourceRange) {
958        if is_always_true(&cond) {
959            self.jump(then_blk, range);
960            return;
961        }
962        self.terminate(Terminator::Branch {
963            cond,
964            then_blk,
965            else_blk,
966        });
967    }
968
969    fn switch(&mut self, switch: crate::ir::SwitchTree) {
970        let crate::ir::SwitchTree {
971            id,
972            scrutinee,
973            body,
974            range,
975        } = switch;
976        let dispatch = self.current();
977        let exit = self.new_block();
978        self.switches.insert(
979            id,
980            SwitchFrame {
981                brk: exit,
982                brk_cleanups: self.cleanups.len(),
983                cases: Vec::new(),
984                default: None,
985            },
986        );
987        // Control enters the body at a label, never at its first statement, so
988        // the body starts a block of its own — one that is unreachable unless
989        // something jumps into it.
990        self.current = None;
991        self.stmt(*body);
992        self.jump(exit, range);
993        let frame = self
994            .switches
995            .remove(&id)
996            .expect("the frame was just inserted");
997        self.blocks[dispatch.index()].term = Terminator::Switch {
998            value: scrutinee,
999            cases: frame.cases,
1000            default: frame.default.unwrap_or(exit),
1001            range,
1002        };
1003        self.continue_at(exit);
1004    }
1005
1006    fn case(&mut self, switch: SwitchId, value: Option<CaseRange>, body: Stmt, range: SourceRange) {
1007        let block = self.new_block();
1008        // A group falls through into the next one, so the statements before
1009        // the label flow here too.
1010        self.jump(block, range);
1011        self.continue_at(block);
1012        if let Some(frame) = self.switches.get_mut(&switch) {
1013            match value {
1014                Some(value) => frame.cases.push((value, block)),
1015                None => frame.default = Some(block),
1016            }
1017        }
1018        self.stmt(body);
1019    }
1020
1021    // -- cleanup ------------------------------------------------------------
1022
1023    fn finish(
1024        mut self,
1025        entry: BlockId,
1026        labels: HashMap<LabelId, u32>,
1027        names: &HashMap<LabelId, String>,
1028    ) -> Cfg {
1029        let entry = self.thread_jumps(entry);
1030        self.merge_chains(entry);
1031        self.renumber(entry, labels, names)
1032    }
1033
1034    /// Removes blocks that only jump somewhere else, re-pointing every edge.
1035    fn thread_jumps(&mut self, entry: BlockId) -> BlockId {
1036        let resolved: Vec<BlockId> = (0..self.blocks.len())
1037            .map(|index| self.resolve(BlockId(index as u32)))
1038            .collect();
1039        for block in &mut self.blocks {
1040            block.term.map_targets(|target| resolved[target.index()]);
1041        }
1042        // A label now stands at whatever the forwarding chain ended on, which
1043        // is where a loop named after it begins.
1044        for block in self.labels.values_mut() {
1045            *block = resolved[block.index()];
1046        }
1047        resolved[entry.index()]
1048    }
1049
1050    /// Follows a chain of statement-free jumps to the block that really runs.
1051    fn resolve(&self, mut block: BlockId) -> BlockId {
1052        let mut seen = HashSet::new();
1053        while seen.insert(block) {
1054            let candidate = &self.blocks[block.index()];
1055            if !candidate.stmts.is_empty() {
1056                break;
1057            }
1058            match candidate.term {
1059                Terminator::Jump { target, .. } if target != block => block = target,
1060                _ => break,
1061            }
1062        }
1063        block
1064    }
1065
1066    /// Appends a block to its predecessor when that is the only way in.
1067    fn merge_chains(&mut self, entry: BlockId) {
1068        let reachable = self.reachable(entry);
1069        let mut predecessors = vec![0usize; self.blocks.len()];
1070        for id in &reachable {
1071            for successor in self.blocks[id.index()].term.successors() {
1072                predecessors[successor.index()] += 1;
1073            }
1074        }
1075        for id in &reachable {
1076            while let Terminator::Jump { target, .. } = self.blocks[id.index()].term {
1077                if target == *id || target == entry || predecessors[target.index()] != 1 {
1078                    break;
1079                }
1080                let mut stmts = std::mem::take(&mut self.blocks[target.index()].stmts);
1081                let term = std::mem::replace(
1082                    &mut self.blocks[target.index()].term,
1083                    Terminator::Unreachable,
1084                );
1085                self.blocks[id.index()].stmts.append(&mut stmts);
1086                self.blocks[id.index()].term = term;
1087                predecessors[target.index()] = 0;
1088            }
1089        }
1090    }
1091
1092    /// The blocks control can reach from `entry`.
1093    fn reachable(&self, entry: BlockId) -> Vec<BlockId> {
1094        let mut seen = HashSet::new();
1095        let mut stack = vec![entry];
1096        let mut out = Vec::new();
1097        while let Some(id) = stack.pop() {
1098            if !seen.insert(id) {
1099                continue;
1100            }
1101            out.push(id);
1102            stack.extend(self.blocks[id.index()].term.successors());
1103        }
1104        out.sort_unstable();
1105        out
1106    }
1107
1108    /// Numbers the reachable blocks in reverse postorder and drops the rest.
1109    ///
1110    /// Reverse postorder is what makes the emitted states read in the order
1111    /// the C did: a block comes before everything only reachable through it,
1112    /// and the `then` branch of a condition comes before the `else`.
1113    fn renumber(
1114        mut self,
1115        entry: BlockId,
1116        labels: HashMap<LabelId, u32>,
1117        names: &HashMap<LabelId, String>,
1118    ) -> Cfg {
1119        let mut order = Vec::with_capacity(self.blocks.len());
1120        let mut visited = vec![false; self.blocks.len()];
1121        let mut stack = vec![(entry, 0usize)];
1122        visited[entry.index()] = true;
1123        while let Some((id, next)) = stack.pop() {
1124            let successors = self.blocks[id.index()].term.successors();
1125            // Successors are pushed in reverse so that the first one is
1126            // explored last and therefore ends up first once the postorder is
1127            // reversed.
1128            if next < successors.len() {
1129                stack.push((id, next + 1));
1130                let successor = successors[successors.len() - 1 - next];
1131                if !visited[successor.index()] {
1132                    visited[successor.index()] = true;
1133                    stack.push((successor, 0));
1134                }
1135                continue;
1136            }
1137            order.push(id);
1138        }
1139        order.reverse();
1140
1141        let mut index_of = vec![None; self.blocks.len()];
1142        for (index, id) in order.iter().enumerate() {
1143            index_of[id.index()] = Some(BlockId(index as u32));
1144        }
1145        let mut blocks = Vec::with_capacity(order.len());
1146        for id in order {
1147            let mut block = std::mem::replace(
1148                &mut self.blocks[id.index()],
1149                BasicBlock {
1150                    stmts: Vec::new(),
1151                    term: Terminator::Unreachable,
1152                },
1153            );
1154            block.term.map_targets(|target| {
1155                index_of[target.index()].expect("a reachable block only names reachable blocks")
1156            });
1157            blocks.push(block);
1158        }
1159        // The C label each block now stands at, for the loops the relooper
1160        // names. A label the clean-up passes merged into a predecessor no
1161        // longer heads a block and leaves nothing behind; the order is by
1162        // label so that the first one written wins whatever the hash order.
1163        let mut named: Vec<(LabelId, BlockId)> =
1164            self.labels.iter().map(|(a, b)| (*a, *b)).collect();
1165        named.sort_unstable();
1166        let mut block_labels: HashMap<BlockId, String> = HashMap::new();
1167        for (id, block) in named {
1168            let (Some(name), Some(numbered)) = (names.get(&id), index_of[block.index()]) else {
1169                continue;
1170            };
1171            block_labels.entry(numbered).or_insert_with(|| name.clone());
1172        }
1173        // The loop an interpreter's handlers go round is headed by the
1174        // computed `goto`'s dispatch, which no C label names.
1175        if let Some(numbered) = self.dispatch.and_then(|block| index_of[block.index()]) {
1176            block_labels
1177                .entry(numbered)
1178                .or_insert_with(|| "dispatch".to_owned());
1179        }
1180        let shape = crate::reloop::plan(&blocks, &block_labels);
1181        Cfg {
1182            locals: self.locals,
1183            blocks,
1184            labels,
1185            shape,
1186        }
1187    }
1188}