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}