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