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}