main
md 145 lines 4.21 KB
Rendered Raw
1 # pruneUnusedLabels
2
3 ## File
4 `src/ReactiveScopes/PruneUnusedLabels.ts`
5
6 ## Purpose
7 The `pruneUnusedLabels` pass optimizes control flow by:
8
9 1. **Flattening labeled terminals** where the label is not reachable via a `break` or `continue` statement
10 2. **Marking labels as implicit** for terminals where the label exists but is never targeted
11
12 This pass removes unnecessary labeled blocks that were introduced during compilation but serve no control flow purpose in the final output. JavaScript labeled statements are only needed when there is a corresponding `break label` or `continue label` that targets them.
13
14 ## Input Invariants
15 - The input is a `ReactiveFunction` (after conversion from HIR)
16 - All `break` and `continue` terminals have:
17 - A `target` (BlockId) indicating which label they jump to
18 - A `targetKind` that is one of: `'implicit'`, `'labeled'`, or `'unlabeled'`
19 - Each `ReactiveTerminalStatement` has an optional `label` field containing `id` and `implicit`
20 - The pass runs after `assertWellFormedBreakTargets` which validates break/continue targets
21
22 ## Output Guarantees
23 - Labeled terminals where the label is unreachable are flattened into their parent block
24 - When flattening, trailing unlabeled `break` statements (that would just fall through) are removed
25 - Labels that exist but are never targeted have their `implicit` flag set to `true`
26 - Control flow semantics are preserved - only structurally unnecessary labels are removed
27
28 ## Algorithm
29
30 The pass uses a two-phase approach with a single traversal:
31
32 **Phase 1: Collect reachable labels**
33 ```typescript
34 if ((terminal.kind === 'break' || terminal.kind === 'continue') &&
35 terminal.targetKind === 'labeled') {
36 state.add(terminal.target); // Mark this label as reachable
37 }
38 ```
39
40 **Phase 2: Transform terminals**
41 ```typescript
42 const isReachableLabel = stmt.label !== null && state.has(stmt.label.id);
43
44 if (stmt.terminal.kind === 'label' && !isReachableLabel) {
45 // Flatten: extract block contents, removing trailing unlabeled break
46 const block = [...stmt.terminal.block];
47 const last = block.at(-1);
48 if (last?.kind === 'terminal' && last.terminal.kind === 'break' &&
49 last.terminal.target === null) {
50 block.pop(); // Remove trailing break
51 }
52 return {kind: 'replace-many', value: block};
53 } else {
54 if (!isReachableLabel && stmt.label != null) {
55 stmt.label.implicit = true; // Mark as implicit
56 }
57 return {kind: 'keep'};
58 }
59 ```
60
61 ## Edge Cases
62
63 ### Trailing Break Removal
64 When flattening a labeled block, if the last statement is an unlabeled break (`target === null`), it is removed since it would just fall through anyway.
65
66 ### Implicit vs Labeled Breaks
67 Only breaks with `targetKind === 'labeled'` count toward label reachability. Implicit breaks (fallthrough) and unlabeled breaks don't make a label "used".
68
69 ### Continue Statements
70 Both `break` and `continue` with labeled targets mark the label as reachable.
71
72 ### Non-Label Terminals with Labels
73 Other terminal types (like `if`, `while`, `for`) can also have labels. If unreachable, these labels are marked implicit but the terminal is not flattened.
74
75 ## TODOs
76 None in the source file.
77
78 ## Example
79
80 ### Fixture: `unconditional-break-label.js`
81
82 **Input:**
83 ```javascript
84 function foo(a) {
85 let x = 0;
86 bar: {
87 x = 1;
88 break bar;
89 }
90 return a + x;
91 }
92 ```
93
94 **Output (after full compilation):**
95 ```javascript
96 function foo(a) {
97 return a + 1;
98 }
99 ```
100
101 The labeled block `bar: { ... }` is removed because after the pass runs, constant propagation and dead code elimination further simplify the code.
102
103 ### Fixture: `conditional-break-labeled.js`
104
105 **Input:**
106 ```javascript
107 function Component(props) {
108 const a = [];
109 a.push(props.a);
110 label: {
111 if (props.b) {
112 break label;
113 }
114 a.push(props.c);
115 }
116 a.push(props.d);
117 return a;
118 }
119 ```
120
121 **Output:**
122 ```javascript
123 function Component(props) {
124 const $ = _c(5);
125 let a;
126 if ($[0] !== props.a || $[1] !== props.b ||
127 $[2] !== props.c || $[3] !== props.d) {
128 a = [];
129 a.push(props.a);
130 bb0: {
131 if (props.b) {
132 break bb0;
133 }
134 a.push(props.c);
135 }
136 a.push(props.d);
137 // ... cache updates
138 } else {
139 a = $[4];
140 }
141 return a;
142 }
143 ```
144
145 The labeled block `bb0: { ... }` is preserved because the `break bb0` inside the conditional targets this label.