@samitouri / QOS-React / commits / 0d0d3a4038

Ensure stable variable names after leaving SSA

With this PR we now no longer emit copy instructions during LeaveSSA, and restore the original identifier name.

Lauren Tan committed Feb 15, 2023 at 15:22 UTC 0d0d3a4038574ba221d88da83b4cadca51757a34
33 files changed +388 -371
compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts
+15 -5
@@ -5,7 +5,6 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import invariant from "invariant";
8 import {
9 HIRFunction,
10 Identifier,
@@ -81,10 +80,21 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
80 // that mutate together.
81 const scopeIdentifiers = new DisjointSet<Identifier>();
82 for (const [_, block] of fn.body.blocks) {
84 - invariant(
85 - block.phis.size === 0,
86 - "Expected phis to be cleared by LeaveSSA pass"
87 - );
83 + // If a phi is mutated after creation, then we need to alias all of its operands such that they
84 + // are assigned to the same scope.
85 + for (const phi of block.phis) {
86 + if (
87 + // The phi was reset because it was not mutated after creation
88 + phi.id.mutableRange.start + 1 !== phi.id.mutableRange.end &&
89 + phi.id.mutableRange.end >
90 + (block.instructions.at(0)?.id ?? block.terminal.id)
91 + ) {
92 + for (const [, phiId] of phi.operands) {
93 + scopeIdentifiers.union([phi.id, phiId]);
94 + }
95 + }
96 + block.phis.clear();
97 + }
98
99 for (const instr of block.instructions) {
100 const operands: Array<Identifier> = [];
compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts
+31 -23
@@ -65,6 +65,7 @@ type Scopes = Array<ReactiveScope>;
65
66 class Context {
67 #declarations: DeclMap = new Map();
68 + #reassignments: Map<Identifier, Decl> = new Map();
69 #dependencies: Set<ReactiveScopeDependency> = new Set();
70 // Produces a de-duplicated mapping of Id -> ReactiveScopeDependency
71 // This helps with.. temporaries that are created only for property loads
@@ -90,7 +91,10 @@ class Context {
91 * on itself.
92 */
93 declare(identifier: Identifier, decl: Decl): void {
93 - this.#declarations.set(identifier.id, decl);
94 + if (!this.#declarations.has(identifier.id)) {
95 + this.#declarations.set(identifier.id, decl);
96 + }
97 + this.#reassignments.set(identifier, decl);
98 }
99
100 declareProperty(lvalue: Place, object: Place, property: string): void {
@@ -150,22 +154,23 @@ class Context {
154 }
155 }
156
153 - const decl = this.#declarations.get(maybeDependency.place.identifier.id);
154 -
155 - // if decl is undefined here, then this is a free var
156 - // (all other decls e.g. `let x;` should be initialized in BuildHIR)
157 -
158 - // Any value used after its defining scope has concluded must be added as an
157 + // Any value used after its originally defining scope has concluded must be added as an
158 // output of its defining scope. Regardless of whether its a const or not,
159 // some later code needs access to the value. If the current
161 - // scope we are visiting is the same scope where the value originates,
162 - // it can't be a dependency on itself.
160 + // scope we are visiting is the same scope where the value originates, it can't be a dependency
161 + // on itself.
162 +
163 + // if originalDeclaration is undefined here, then this is a free var
164 + // (all other decls e.g. `let x;` should be initialized in BuildHIR)
165 + const originalDeclaration = this.#declarations.get(
166 + maybeDependency.place.identifier.id
167 + );
168 if (
164 - decl !== undefined &&
165 - decl.scope !== null &&
166 - !this.#isScopeActive(decl.scope)
169 + originalDeclaration !== undefined &&
170 + originalDeclaration.scope !== null &&
171 + !this.#isScopeActive(originalDeclaration.scope)
172 ) {
168 - decl.scope.declarations.set(
173 + originalDeclaration.scope.declarations.set(
174 maybeDependency.place.identifier.id,
175 maybeDependency.place.identifier
176 );
@@ -173,13 +178,17 @@ class Context {
178
179 // If this operand is used in a scope, has a dynamic value, and was defined
180 // before this scope, then its a dependency of the scope.
181 + const currentDeclaration =
182 + this.#reassignments.get(maybeDependency.place.identifier) ??
183 + this.#declarations.get(maybeDependency.place.identifier.id);
184 const currentScope = this.currentScope;
185 if (
186 currentScope != null &&
179 - decl !== undefined &&
180 - decl.kind !== DeclKind.Const &&
181 - decl.id < currentScope.range.start &&
182 - (decl.scope == null || !this.#isScopeActive(decl.scope))
187 + currentDeclaration !== undefined &&
188 + currentDeclaration.kind !== DeclKind.Const &&
189 + currentDeclaration.id < currentScope.range.start &&
190 + (currentDeclaration.scope == null ||
191 + !this.#isScopeActive(currentDeclaration.scope))
192 ) {
193 // Check if there is an existing dependency that describes this operand
194 for (const dep of this.#dependencies) {
@@ -374,11 +383,10 @@ function visitInstruction(context: Context, instr: ReactiveInstruction): void {
383 }
384 if (lvalue.kind === InstructionKind.Reassign) {
385 context.visitReassignment(lvalue);
377 - } else {
378 - context.declare(lvalue.place.identifier, {
379 - kind: DeclKind.Dynamic,
380 - id: instr.id,
381 - scope: context.currentScope,
382 - });
386 }
387 + context.declare(lvalue.place.identifier, {
388 + kind: DeclKind.Dynamic,
389 + id: instr.id,
390 + scope: context.currentScope,
391 + });
392 }
compiler/forget/src/SSA/LeaveSSA.ts
+98 -109
@@ -15,6 +15,7 @@ import {
15 Instruction,
16 InstructionId,
17 InstructionKind,
18 + LValue,
19 makeInstructionId,
20 Phi,
21 Place,
@@ -87,10 +88,8 @@ import {
88 * ```
89 */
90 export function leaveSSA(fn: HIRFunction): void {
90 - // For "memoizable" phis (see docblock), this maps the original identifiers to the identifier they
91 - // should be reassigned to. The keys are phi operands, and the values are the phi id to which
92 - // they are being explicitly reassigned.
93 - const reassignments: Map<Identifier, Identifier> = new Map();
91 + // Maps identifier names to their original declaration.
92 + const declarations: Map<string, LValue> = new Map();
93
94 // For non-memoizable phis, this maps original identifiers to the identifier they should be
95 // *rewritten* to. The keys are the original identifiers, and the value will be _either_ the
@@ -108,17 +107,48 @@ export function leaveSSA(fn: HIRFunction): void {
107 }
108
109 for (const [, block] of fn.body.blocks) {
111 - invariant(
112 - block.phis.size === 0,
113 - "Expected all phis to be cleared by predecessors"
114 - );
110 + for (const instr of block.instructions) {
111 + // Iterate the instructions and perform any rewrites as well as promoting SSA variables to
112 + // `let` or `reassign` where possible.
113 + const { lvalue } = instr;
114 + if (
115 + lvalue.kind === InstructionKind.Const &&
116 + rewrites.has(lvalue.place.identifier)
117 + ) {
118 + // For rewrites, the declaration of the canonical identifier has to be `let`,
119 + // all other assignments are reassignments (which we annotate for codegen
120 + // purposes).
121 + lvalue.kind =
122 + rewrites.get(lvalue.place.identifier) === lvalue.place.identifier
123 + ? InstructionKind.Let
124 + : InstructionKind.Reassign;
125 + } else if (lvalue.place.identifier.name != null) {
126 + const originalLVal = declarations.get(lvalue.place.identifier.name);
127 + if (originalLVal === undefined) {
128 + declarations.set(lvalue.place.identifier.name, lvalue);
129 + } else {
130 + // This is an instance of the original id, so we need to promote the original declaration
131 + // to a `let` and the current lval to a `reassign`
132 + originalLVal.kind = InstructionKind.Let;
133 + lvalue.kind = InstructionKind.Reassign;
134 + }
135 + }
136 + rewritePlace(lvalue.place, rewrites, declarations);
137 + for (const operand of eachInstructionValueOperand(instr.value)) {
138 + rewritePlace(operand, rewrites, declarations);
139 + }
140 + }
141 +
142 + const terminal = block.terminal;
143 + for (const operand of eachTerminalOperand(terminal)) {
144 + rewritePlace(operand, rewrites, declarations);
145 + }
146
147 // Find any phi nodes which need a variable declaration in the current block
148 // This includes phis in fallthrough nodes, or blocks that form part of control flow
149 // such as for or while (and later if/switch).
150 const reassignmentPhis: Array<PhiState> = [];
151 const rewritePhis: Array<PhiState> = [];
121 - const terminal = block.terminal;
152 if (
153 (terminal.kind === "if" ||
154 terminal.kind === "switch" ||
@@ -127,8 +157,13 @@ export function leaveSSA(fn: HIRFunction): void {
157 terminal.fallthrough !== null
158 ) {
159 const fallthrough = fn.body.blocks.get(terminal.fallthrough)!;
130 - pushPhis(reassignmentPhis, fallthrough);
131 - fallthrough.phis.clear();
160 + for (const phi of fallthrough.phis) {
161 + if (phi.id.name == null) {
162 + rewritePhis.push({ phi, block: fallthrough });
163 + } else {
164 + reassignmentPhis.push({ phi, block: fallthrough });
165 + }
166 + }
167 }
168 if (terminal.kind === "while" || terminal.kind === "for") {
169 const test = fn.body.blocks.get(terminal.test)!;
@@ -160,52 +195,34 @@ export function leaveSSA(fn: HIRFunction): void {
195 // an if but not another. In this case we populate the let binding with this initial
196 // value rather than generate an extra assignment.
197 let initOperand: Identifier | null = null;
163 -
164 - // Determine the canonical id to use for this phi. In general this is the phi id,
165 - // but if one phi flows into another as an operand, this will be the final phi.
166 - let canonicalId = reassignments.get(phi.id);
167 - if (canonicalId === undefined) {
168 - canonicalId = phi.id;
169 - canonicalId.mutableRange.start = Math.min(
170 - canonicalId.mutableRange.start,
171 - terminal.id
172 - ) as InstructionId;
173 - reassignments.set(phi.id, canonicalId);
174 - }
175 -
176 - // all versions of the variable need to be remapped to the canonical id
198 for (const [, operand] of phi.operands) {
178 - reassignments.set(operand, canonicalId);
199 if (operand.mutableRange.start < terminal.id) {
180 - invariant(
181 - initOperand === null,
182 - "A phi cannot have two operands initialized before its declaration"
183 - );
184 - initOperand = operand;
200 + if (initOperand == null) {
201 + initOperand = operand;
202 + }
203 }
204 }
205
188 - // If there are no instructions in the block then there's just a terminal
189 - // node, which has no mutation, so that should be false.
190 - //
191 - // TODO(joe): This above statement is true, right? Could there be a value
192 - // block with instructions in terminals?
206 + // If the phi is mutated after its creation, then any values which flow into the phi
207 + // must also have their ranges extended accordingly.
208 const isPhiMutatedAfterCreation: boolean =
209 phi.id.mutableRange.end >
210 (phiBlock.instructions.at(0)?.id ?? phiBlock.terminal.id);
211
212 // If a phi is never mutated after creation, reset its mutable range to be itself
213 if (!isPhiMutatedAfterCreation) {
199 - canonicalId.mutableRange.start = phiBlock.terminal.id;
200 - canonicalId.mutableRange.end = makeInstructionId(
201 - phiBlock.terminal.id + 1
202 - );
214 + phi.id.mutableRange.start = terminal.id;
215 + phi.id.mutableRange.end = makeInstructionId(terminal.id + 1);
216 }
217
205 - // If this phi id is the canonical id we need to generate a let binding for it
206 - // (otherwise, it means this phi merges into some other phi which already generated
207 - // a binding
208 - if (canonicalId === phi.id) {
218 + // If we never saw a declaration for this phi, it may have been pruned by DCE, so synthesize
219 + // a new Let binding
220 + invariant(
221 + phi.id.name != null,
222 + "Expected reassignment phis to have a name"
223 + );
224 + const declaration = declarations.get(phi.id.name);
225 + if (declaration === undefined) {
226 const instr: Instruction = {
227 // NOTE: reuse the terminal id since these lets must be scoped with the terminal anyway.
228 // the mutable range of this canonical id must by definition span from the binding (before
@@ -214,7 +231,7 @@ export function leaveSSA(fn: HIRFunction): void {
231 lvalue: {
232 place: {
233 kind: "Identifier",
217 - identifier: canonicalId,
234 + identifier: phi.id,
235 effect: Effect.Mutate,
236 loc: GeneratedSource,
237 },
@@ -237,37 +254,19 @@ export function leaveSSA(fn: HIRFunction): void {
254 loc: GeneratedSource,
255 };
256 block.instructions.push(instr);
257 + declarations.set(phi.id.name, instr.lvalue);
258 + phi.id.mutableRange.start = terminal.id;
259 + if (!isPhiMutatedAfterCreation) {
260 + phi.id.mutableRange.end = makeInstructionId(terminal.id + 1);
261 + }
262 + } else if (isPhiMutatedAfterCreation) {
263 + declaration.place.identifier.mutableRange.end = phi.id.mutableRange.end;
264 }
265
242 - // Generate an assignment in each predecessor
243 - for (const [predecessorId, operand] of phi.operands) {
266 + for (const [, operand] of phi.operands) {
267 if (isPhiMutatedAfterCreation) {
268 operand.mutableRange.end = phi.id.mutableRange.end;
269 }
247 - if (operand === initOperand) {
248 - continue;
249 - }
250 - const predecessor = fn.body.blocks.get(predecessorId)!;
251 - const instr: Instruction = {
252 - id: predecessor.terminal.id,
253 - lvalue: {
254 - place: {
255 - kind: "Identifier",
256 - identifier: canonicalId,
257 - effect: Effect.Mutate,
258 - loc: GeneratedSource,
259 - },
260 - kind: InstructionKind.Reassign,
261 - },
262 - value: {
263 - kind: "Identifier",
264 - identifier: operand,
265 - effect: Effect.Read,
266 - loc: GeneratedSource,
267 - },
268 - loc: GeneratedSource,
269 - };
270 - predecessor.instructions.push(instr);
270 }
271 }
272
@@ -289,6 +288,13 @@ export function leaveSSA(fn: HIRFunction): void {
288 terminal.id
289 ) as InstructionId;
290 rewrites.set(phi.id, canonicalId);
291 +
292 + if (canonicalId.name !== null) {
293 + const declaration = declarations.get(canonicalId.name);
294 + if (declaration !== undefined) {
295 + declaration.kind = InstructionKind.Let;
296 + }
297 + }
298 }
299
300 // all versions of the variable need to be remapped to the canonical id
@@ -304,31 +310,6 @@ export function leaveSSA(fn: HIRFunction): void {
310 canonicalId.mutableRange.start = makeInstructionId(start);
311 canonicalId.mutableRange.end = makeInstructionId(end);
312 }
307 -
308 - // Finally, iterate the instructions and perform any rewrites as well as converting
309 - // SSA variables to `const` where possible
310 - for (const instr of block.instructions) {
311 - const { lvalue } = instr;
312 - if (
313 - lvalue.kind === InstructionKind.Const &&
314 - rewrites.has(lvalue.place.identifier)
315 - ) {
316 - // For rewrites, the declaration of the canonical identifier has to be `let`,
317 - // all other assignments are reassignments (which we annotate for codegen
318 - // purposes).
319 - lvalue.kind =
320 - rewrites.get(lvalue.place.identifier) === lvalue.place.identifier
321 - ? InstructionKind.Let
322 - : InstructionKind.Reassign;
323 - }
324 - rewritePlace(lvalue.place, rewrites);
325 - for (const operand of eachInstructionValueOperand(instr.value)) {
326 - rewritePlace(operand, rewrites);
327 - }
328 - }
329 - for (const operand of eachTerminalOperand(terminal)) {
330 - rewritePlace(operand, rewrites);
331 - }
313 }
314 }
315
@@ -337,21 +318,29 @@ export function leaveSSA(fn: HIRFunction): void {
318 // place's range.
319 function rewritePlace(
320 place: Place,
340 - rewrites: Map<Identifier, Identifier>
321 + rewrites: Map<Identifier, Identifier>,
322 + declarations: Map<string, LValue>
323 ): void {
324 const prevIdentifier = place.identifier;
325 const nextIdentifier = rewrites.get(prevIdentifier);
344 - if (nextIdentifier === undefined || nextIdentifier === prevIdentifier) {
345 - return;
326 +
327 + if (nextIdentifier !== undefined) {
328 + if (nextIdentifier === prevIdentifier) return;
329 + nextIdentifier.mutableRange.start = makeInstructionId(
330 + Math.min(
331 + nextIdentifier.mutableRange.start,
332 + prevIdentifier.mutableRange.start
333 + )
334 + );
335 + nextIdentifier.mutableRange.end = makeInstructionId(
336 + Math.max(nextIdentifier.mutableRange.end, prevIdentifier.mutableRange.end)
337 + );
338 + place.identifier = nextIdentifier;
339 + } else if (prevIdentifier.name != null) {
340 + const declaration = declarations.get(prevIdentifier.name);
341 + if (declaration === undefined) return;
342 + // Only rewrite identifiers that were declared within the function
343 + const originalIdentifier = declaration.place.identifier;
344 + prevIdentifier.id = originalIdentifier.id;
345 }
347 - nextIdentifier.mutableRange.start = makeInstructionId(
348 - Math.min(
349 - nextIdentifier.mutableRange.start,
350 - prevIdentifier.mutableRange.start
351 - )
352 - );
353 - nextIdentifier.mutableRange.end = makeInstructionId(
354 - Math.max(nextIdentifier.mutableRange.end, prevIdentifier.mutableRange.end)
355 - );
356 - place.identifier = nextIdentifier;
346 }
compiler/forget/src/__tests__/fixtures/hir/assignment-in-nested-if.expect.md new
+39
@@ -0,0 +1,39 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function useBar(props) {
6 + let z;
7 +
8 + if (props.a) {
9 + if (props.b) {
10 + z = baz();
11 + }
12 + }
13 +
14 + return z;
15 +}
16 +
17 +```
18 +
19 +## Code
20 +
21 +```javascript
22 +function useBar(props) {
23 + const $ = React.unstable_useMemoCache();
24 + let z = undefined;
25 + if (props.a) {
26 + if (props.b) {
27 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
28 + z = baz();
29 + $[0] = z;
30 + } else {
31 + z = $[0];
32 + }
33 + }
34 + }
35 + return z;
36 +}
37 +
38 +```
39 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/assignment-in-nested-if.js renamed
compiler/forget/src/__tests__/fixtures/hir/assignment-variations.expect.md
+3 -3
@@ -16,9 +16,9 @@ function f() {
16
17 ```javascript
18 function f() {
19 - const x = 3;
20 - const x$0 = x >>> 1;
21 - return x$0;
19 + let x = 3;
20 + x = x >>> 1;
21 + return x;
22 }
23
24 ```
compiler/forget/src/__tests__/fixtures/hir/error.assignment-in-nested-if.expect.md deleted
-26
@@ -1,26 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -function useBar(props) {
6 - let z;
7 -
8 - if (props.a) {
9 - if (props.b) {
10 - z = baz();
11 - }
12 - }
13 -
14 - return z;
15 -}
16 -
17 -```
18 -
19 -
20 -## Error
21 -
22 -```
23 -A phi cannot have two operands initialized before its declaration
24 -```
25 -
26 -
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/error.inverted-if-else.expect.md
+1 -1
@@ -20,7 +20,7 @@ function foo(a, b, c) {
20 ## Error
21
22 ```
23 -Expected all phis to be cleared by predecessors
23 +Expected a break target
24 ```
25
26
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.expect.md
+4 -7
@@ -31,17 +31,14 @@ function Component(props) {
31 const x = props.x;
32 let a = undefined;
33 if (cond) {
34 - const a$0 = x;
35 - a = a$0;
34 + a = x;
35 } else {
37 - let a$1;
36 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
39 - a$1 = [];
40 - $[0] = a$1;
37 + a = [];
38 + $[0] = a;
39 } else {
42 - a$1 = $[0];
40 + a = $[0];
41 }
44 - a = a$1;
42 }
43
44 useFreeze(a);
compiler/forget/src/__tests__/fixtures/hir/infer-phi-primitive.expect.md
+2 -4
@@ -22,11 +22,9 @@ function foo(a, b) {
22 function foo(a, b) {
23 let x = undefined;
24 if (a) {
25 - const x$0 = 1;
26 - x = x$0;
25 + x = 1;
26 } else {
28 - const x$1 = 2;
29 - x = x$1;
27 + x = 2;
28 }
29
30 const y = x;
compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-loops.expect.md
+5 -13
@@ -50,10 +50,6 @@ function Component(props) {
50 let b = {};
51 let c = {};
52 let d = {};
53 - let a$0 = a;
54 - let b$1 = b;
55 - let c$2 = c;
56 - let d$3 = d;
53 while (true) {
54 const z = a;
55 a = b;
@@ -61,24 +57,20 @@ function Component(props) {
57 c = d;
58 d = z;
59 mutate(a, b);
64 - a$0 = a;
65 - b$1 = b;
66 - c$2 = c;
67 - d$3 = d;
60 if (cond(a)) {
61 break;
62 }
63 }
72 - if (a$0) {
64 + if (a) {
65 }
74 - if (b$1) {
66 + if (b) {
67 }
76 - if (c$2) {
68 + if (c) {
69 }
78 - if (d$3) {
70 + if (d) {
71 }
72
81 - mutate(d$3, null);
73 + mutate(d, null);
74 }
75
76 ```
compiler/forget/src/__tests__/fixtures/hir/obj-literal-cached-in-if-else.expect.md
+6 -10
@@ -23,26 +23,22 @@ function foo(a, b, c, d) {
23 let x = undefined;
24 if (someVal) {
25 const c_0 = $[0] !== b;
26 - let x$0;
26 if (c_0) {
28 - x$0 = { b: b };
27 + x = { b: b };
28 $[0] = b;
30 - $[1] = x$0;
29 + $[1] = x;
30 } else {
32 - x$0 = $[1];
31 + x = $[1];
32 }
34 - x = x$0;
33 } else {
34 const c_2 = $[2] !== c;
37 - let x$1;
35 if (c_2) {
39 - x$1 = { c: c };
36 + x = { c: c };
37 $[2] = c;
41 - $[3] = x$1;
38 + $[3] = x;
39 } else {
43 - x$1 = $[3];
40 + x = $[3];
41 }
45 - x = x$1;
42 }
43 return x;
44 }
compiler/forget/src/__tests__/fixtures/hir/obj-literal-mutated-after-if-else.expect.md
+2 -4
@@ -27,11 +27,9 @@ function foo(a, b, c, d) {
27 if (c_0 || c_1) {
28 x = undefined;
29 if (someVal) {
30 - const x$0 = { b: b };
31 - x = x$0;
30 + x = { b: b };
31 } else {
33 - const x$1 = { c: c };
34 - x = x$1;
32 + x = { c: c };
33 }
34 x.f = 1;
35 $[0] = b;
compiler/forget/src/__tests__/fixtures/hir/obj-mutated-after-if-else.expect.md
+8 -11
@@ -21,25 +21,22 @@ function foo(a, b, c, d) {
21 ```javascript
22 function foo(a, b, c, d) {
23 const $ = React.unstable_useMemoCache();
24 - const x = someObj();
24 const c_0 = $[0] !== a;
26 - let x$0;
25 + let x;
26 if (c_0) {
28 - x$0 = undefined;
27 + x = someObj();
28 if (a) {
30 - const x$1 = someObj();
31 - x$0 = x$1;
29 + x = someObj();
30 } else {
33 - const x$2 = someObj();
34 - x$0 = x$2;
31 + x = someObj();
32 }
36 - x$0.f = 1;
33 + x.f = 1;
34 $[0] = a;
38 - $[1] = x$0;
35 + $[1] = x;
36 } else {
40 - x$0 = $[1];
37 + x = $[1];
38 }
42 - return x$0;
39 + return x;
40 }
41
42 ```
compiler/forget/src/__tests__/fixtures/hir/primitive-alias-mutate.expect.md
+2 -4
@@ -22,11 +22,9 @@ function component(a) {
22 function component(a) {
23 let x = undefined;
24 if (a) {
25 - const x$0 = "bar";
26 - x = x$0;
25 + x = "bar";
26 } else {
28 - const x$1 = "baz";
29 - x = x$1;
27 + x = "baz";
28 }
29
30 const y = x;
compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md
+6 -9
@@ -35,25 +35,22 @@ function Component(props) {
35 x = $[1];
36 }
37 const y = x;
38 - let x$0 = x;
38 if (props.p1) {
40 - let x$1;
39 if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
42 - x$1 = [];
43 - $[2] = x$1;
40 + x = [];
41 + $[2] = x;
42 } else {
45 - x$1 = $[2];
43 + x = $[2];
44 }
47 - x$0 = x$1;
45 }
46
47 y.push(props.p2);
51 - const c_3 = $[3] !== x$0;
48 + const c_3 = $[3] !== x;
49 const c_4 = $[4] !== y;
50 let t0;
51 if (c_3 || c_4) {
55 - t0 = <Component x={x$0} y={y}></Component>;
56 - $[3] = x$0;
52 + t0 = <Component x={x} y={y}></Component>;
53 + $[3] = x;
54 $[4] = y;
55 $[5] = t0;
56 } else {
compiler/forget/src/__tests__/fixtures/hir/reassignment-separate-scopes.expect.md
+11 -16
@@ -56,48 +56,43 @@ function foo(a, b, c) {
56 } else {
57 y = $[3];
58 }
59 - let x$0 = undefined;
59 bb3: switch (b) {
60 case 0: {
61 const c_4 = $[4] !== b;
63 - let x$1;
62 if (c_4) {
65 - x$1 = [];
66 - x$1.push(b);
63 + x = [];
64 + x.push(b);
65 $[4] = b;
68 - $[5] = x$1;
66 + $[5] = x;
67 } else {
70 - x$1 = $[5];
68 + x = $[5];
69 }
72 - x$0 = x$1;
70 break bb3;
71 }
72 default: {
73 const c_6 = $[6] !== c;
77 - let x$2;
74 if (c_6) {
79 - x$2 = [];
80 - x$2.push(c);
75 + x = [];
76 + x.push(c);
77 $[6] = c;
82 - $[7] = x$2;
78 + $[7] = x;
79 } else {
84 - x$2 = $[7];
80 + x = $[7];
81 }
86 - x$0 = x$2;
82 }
83 }
84 const c_8 = $[8] !== y;
90 - const c_9 = $[9] !== x$0;
85 + const c_9 = $[9] !== x;
86 let t0;
87 if (c_8 || c_9) {
88 t0 = (
89 <div>
90 {y}
96 - {x$0}
91 + {x}
92 </div>
93 );
94 $[8] = y;
100 - $[9] = x$0;
95 + $[9] = x;
96 $[10] = t0;
97 } else {
98 t0 = $[10];
compiler/forget/src/__tests__/fixtures/hir/reassignment.expect.md
+18 -14
@@ -24,34 +24,38 @@ function Component(props) {
24 const $ = React.unstable_useMemoCache();
25 const c_0 = $[0] !== props.p0;
26 const c_1 = $[1] !== props.p1;
27 + let x;
28 let y;
29 if (c_0 || c_1) {
29 - const x = [];
30 + x = [];
31 x.push(props.p0);
32 y = x;
32 - let x$0;
33 - if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
34 - x$0 = [];
35 - $[3] = x$0;
33 + if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
34 + x = [];
35 + $[4] = x;
36 } else {
37 - x$0 = $[3];
37 + x = $[4];
38 }
39
40 y.push(props.p1);
41 $[0] = props.p0;
42 $[1] = props.p1;
43 - $[2] = y;
43 + $[2] = x;
44 + $[3] = y;
45 } else {
45 - y = $[2];
46 + x = $[2];
47 + y = $[3];
48 }
47 - const c_4 = $[4] !== y;
49 + const c_5 = $[5] !== x;
50 + const c_6 = $[6] !== y;
51 let t0;
49 - if (c_4) {
50 - t0 = <Component x={x$0} y={y}></Component>;
51 - $[4] = y;
52 - $[5] = t0;
52 + if (c_5 || c_6) {
53 + t0 = <Component x={x} y={y}></Component>;
54 + $[5] = x;
55 + $[6] = y;
56 + $[7] = t0;
57 } else {
54 - t0 = $[5];
58 + t0 = $[7];
59 }
60 return t0;
61 }
compiler/forget/src/__tests__/fixtures/hir/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md
+9 -10
@@ -72,33 +72,32 @@ function foo(props) {
72 const c_5 = $[5] !== x;
73 const c_6 = $[6] !== props.b;
74 const c_7 = $[7] !== props.c;
75 - let x$0;
75 let y;
76 if (c_5 || c_6 || c_7) {
77 y = [x];
79 - x$0 = [];
78 + x = [];
79 y.push(props.b);
81 - x$0.push(props.c);
80 + x.push(props.c);
81 $[5] = x;
82 $[6] = props.b;
83 $[7] = props.c;
85 - $[8] = x$0;
86 - $[9] = y;
84 + $[8] = y;
85 + $[9] = x;
86 } else {
88 - x$0 = $[8];
89 - y = $[9];
87 + y = $[8];
88 + x = $[9];
89 }
91 - const c_10 = $[10] !== x$0;
90 + const c_10 = $[10] !== x;
91 const c_11 = $[11] !== y;
92 let content;
93 if (c_10 || c_11) {
94 content = (
95 <div>
97 - {x$0}
96 + {x}
97 {y}
98 </div>
99 );
101 - $[10] = x$0;
100 + $[10] = x;
101 $[11] = y;
102 $[12] = content;
103 } else {
compiler/forget/src/__tests__/fixtures/hir/same-variable-as-dep-and-redeclare.expect.md
+9 -10
@@ -69,33 +69,32 @@ function foo(props) {
69 const c_4 = $[4] !== x;
70 const c_5 = $[5] !== props.b;
71 const c_6 = $[6] !== props.c;
72 - let x$0;
72 let y;
73 if (c_4 || c_5 || c_6) {
74 y = [x];
76 - x$0 = [];
75 + x = [];
76 y.push(props.b);
78 - x$0.push(props.c);
77 + x.push(props.c);
78 $[4] = x;
79 $[5] = props.b;
80 $[6] = props.c;
82 - $[7] = x$0;
83 - $[8] = y;
81 + $[7] = y;
82 + $[8] = x;
83 } else {
85 - x$0 = $[7];
86 - y = $[8];
84 + y = $[7];
85 + x = $[8];
86 }
88 - const c_9 = $[9] !== x$0;
87 + const c_9 = $[9] !== x;
88 const c_10 = $[10] !== y;
89 let content;
90 if (c_9 || c_10) {
91 content = (
92 <div>
94 - {x$0}
93 + {x}
94 {y}
95 </div>
96 );
98 - $[9] = x$0;
97 + $[9] = x;
98 $[10] = y;
99 $[11] = content;
100 } else {
compiler/forget/src/__tests__/fixtures/hir/simple-alias.expect.md
+9 -9
@@ -22,19 +22,19 @@ function foo() {
22 function mutate() {}
23 function foo() {
24 const $ = React.unstable_useMemoCache();
25 - let c$1;
25 + let c;
26 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
27 - const b = {};
28 - const c = {};
27 + let b = {};
28 + c = {};
29 const a = b;
30 - const b$0 = c;
31 - c$1 = a;
32 - mutate(a, b$0);
33 - $[0] = c$1;
30 + b = c;
31 + c = a;
32 + mutate(a, b);
33 + $[0] = c;
34 } else {
35 - c$1 = $[0];
35 + c = $[0];
36 }
37 - return c$1;
37 + return c;
38 }
39
40 ```
compiler/forget/src/__tests__/fixtures/hir/ssa-leave-case.expect.md
+8 -10
@@ -27,36 +27,34 @@ function Component(props) {
27 const c_0 = $[0] !== props.p0;
28 const c_1 = $[1] !== props.p1;
29 let x;
30 - let y$0;
30 + let y;
31 if (c_0 || c_1) {
32 x = [];
33 - const y = undefined;
34 - y$0 = y;
33 + y = undefined;
34 if (props.p0) {
35 x.push(props.p1);
37 - const y$1 = x;
38 - y$0 = y$1;
36 + y = x;
37 }
38 $[0] = props.p0;
39 $[1] = props.p1;
40 $[2] = x;
43 - $[3] = y$0;
41 + $[3] = y;
42 } else {
43 x = $[2];
46 - y$0 = $[3];
44 + y = $[3];
45 }
46 const c_4 = $[4] !== x;
49 - const c_5 = $[5] !== y$0;
47 + const c_5 = $[5] !== y;
48 let t0;
49 if (c_4 || c_5) {
50 t0 = (
51 <Component>
52 {x}
55 - {y$0}
53 + {y}
54 </Component>
55 );
56 $[4] = x;
59 - $[5] = y$0;
57 + $[5] = y;
58 $[6] = t0;
59 } else {
60 t0 = $[6];
compiler/forget/src/__tests__/fixtures/hir/ssa-nested-partial-phi.expect.md
+3 -5
@@ -19,14 +19,12 @@ function foo(a, b, c) {
19
20 ```javascript
21 function foo(a, b, c) {
22 - const x = a;
22 + let x = a;
23 if (b) {
24 - let x$0 = x;
24 if (c) {
26 - const x$1 = c;
27 - x$0 = x$1;
25 + x = c;
26 }
29 - return x$0;
27 + return x;
28 }
29 }
30
compiler/forget/src/__tests__/fixtures/hir/ssa-nested-partial-reassignment.expect.md
+4 -7
@@ -20,18 +20,15 @@ function foo(a, b, c, d, e) {
20
21 ```javascript
22 function foo(a, b, c, d, e) {
23 - const x = null;
24 - let x$0 = x;
23 + let x = null;
24 if (a) {
26 - const x$1 = b;
27 - x$0 = x$1;
25 + x = b;
26 } else {
27 if (c) {
30 - const x$2 = d;
31 - x$0 = x$2;
28 + x = d;
29 }
30 }
34 - return x$0;
31 + return x;
32 }
33
34 ```
compiler/forget/src/__tests__/fixtures/hir/ssa-non-empty-initializer.expect.md new
+38
@@ -0,0 +1,38 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function foo(a, b) {
6 + let x = [];
7 + if (a) {
8 + x = 1;
9 + }
10 +
11 + let y = x;
12 + return y;
13 +}
14 +
15 +```
16 +
17 +## Code
18 +
19 +```javascript
20 +function foo(a, b) {
21 + const $ = React.unstable_useMemoCache();
22 + let x;
23 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
24 + x = [];
25 + $[0] = x;
26 + } else {
27 + x = $[0];
28 + }
29 + if (a) {
30 + x = 1;
31 + }
32 +
33 + const y = x;
34 + return y;
35 +}
36 +
37 +```
38 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/ssa-non-empty-initializer.js new
+9
@@ -0,0 +1,9 @@
1 +function foo(a, b) {
2 + let x = [];
3 + if (a) {
4 + x = 1;
5 + }
6 +
7 + let y = x;
8 + return y;
9 +}
compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-with-mutation.expect.md
+8 -10
@@ -24,26 +24,24 @@ function foo(props) {
24 const c_0 = $[0] !== props.bar;
25 const c_1 = $[1] !== props.cond;
26 const c_2 = $[2] !== props.foo;
27 - let x$0;
27 + let x;
28 if (c_0 || c_1 || c_2) {
29 - const x = [];
29 + x = [];
30 x.push(props.bar);
31 - x$0 = x;
31 if (props.cond) {
33 - const x$1 = [];
34 - x$1.push(props.foo);
35 - x$0 = x$1;
32 + x = [];
33 + x.push(props.foo);
34 }
35
38 - mut(x$0);
36 + mut(x);
37 $[0] = props.bar;
38 $[1] = props.cond;
39 $[2] = props.foo;
42 - $[3] = x$0;
40 + $[3] = x;
41 } else {
44 - x$0 = $[3];
42 + x = $[3];
43 }
46 - return x$0;
44 + return x;
45 }
46
47 ```
compiler/forget/src/__tests__/fixtures/hir/ssa-renaming.expect.md
+5 -8
@@ -30,21 +30,18 @@ function foo(props) {
30 } else {
31 x = $[1];
32 }
33 - let x$0 = x;
33 if (props.cond) {
34 const c_2 = $[2] !== props.foo;
36 - let x$1;
35 if (c_2) {
38 - x$1 = [];
39 - x$1.push(props.foo);
36 + x = [];
37 + x.push(props.foo);
38 $[2] = props.foo;
41 - $[3] = x$1;
39 + $[3] = x;
40 } else {
43 - x$1 = $[3];
41 + x = $[3];
42 }
45 - x$0 = x$1;
43 }
47 - return x$0;
44 + return x;
45 }
46
47 ```
compiler/forget/src/__tests__/fixtures/hir/ssa-shadowing.expect.md
+3 -5
@@ -23,17 +23,15 @@ function Foo(cond) {
23 function log() {}
24
25 function Foo(cond) {
26 - const str = "";
27 - let str$0 = str;
26 + let str = "";
27 if (cond) {
28 const str_0 = "other test";
29 log(str_0);
30 } else {
32 - const str$1 = "fallthrough test";
33 - str$0 = str$1;
31 + str = "fallthrough test";
32 }
33
36 - log(str$0);
34 + log(str);
35 }
36
37 ```
compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md
+12 -16
@@ -36,42 +36,38 @@ function Component(props) {
36 const c_0 = $[0] !== props.p0;
37 const c_1 = $[1] !== props.p2;
38 let x;
39 - let y$0;
39 + let y;
40 if (c_0 || c_1) {
41 x = [];
42 - const y = undefined;
43 - y$0 = y;
42 + y = undefined;
43 bb1: switch (props.p0) {
44 case 1: {
45 break bb1;
46 }
47 case true: {
48 x.push(props.p2);
50 - let y$1;
49 if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
52 - y$1 = [];
53 - $[4] = y$1;
50 + y = [];
51 + $[4] = y;
52 } else {
55 - y$1 = $[4];
53 + y = $[4];
54 }
57 - y$0 = y$1;
55 break bb1;
56 }
57 default: {
58 break bb1;
59 }
60 case false: {
64 - const y$2 = x;
65 - y$0 = y$2;
61 + y = x;
62 }
63 }
64 $[0] = props.p0;
65 $[1] = props.p2;
66 $[2] = x;
71 - $[3] = y$0;
67 + $[3] = y;
68 } else {
69 x = $[2];
74 - y$0 = $[3];
70 + y = $[3];
71 }
72 const c_5 = $[5] !== x;
73 let child;
@@ -82,13 +78,13 @@ function Component(props) {
78 } else {
79 child = $[6];
80 }
85 - y$0.push(props.p4);
86 - const c_7 = $[7] !== y$0;
81 + y.push(props.p4);
82 + const c_7 = $[7] !== y;
83 const c_8 = $[8] !== child;
84 let t0;
85 if (c_7 || c_8) {
90 - t0 = <Component data={y$0}>{child}</Component>;
91 - $[7] = y$0;
86 + t0 = <Component data={y}>{child}</Component>;
87 + $[7] = y;
88 $[8] = child;
89 $[9] = t0;
90 } else {
compiler/forget/src/__tests__/fixtures/hir/switch.expect.md
+9 -11
@@ -32,29 +32,27 @@ function Component(props) {
32 const c_1 = $[1] !== props.p2;
33 const c_2 = $[2] !== props.p3;
34 let x;
35 - let y$0;
35 + let y;
36 if (c_0 || c_1 || c_2) {
37 x = [];
38 - const y = undefined;
39 - y$0 = y;
38 + y = undefined;
39 switch (props.p0) {
40 case true: {
41 x.push(props.p2);
42 x.push(props.p3);
43 }
44 case false: {
46 - const y$1 = x;
47 - y$0 = y$1;
45 + y = x;
46 }
47 }
48 $[0] = props.p0;
49 $[1] = props.p2;
50 $[2] = props.p3;
51 $[3] = x;
54 - $[4] = y$0;
52 + $[4] = y;
53 } else {
54 x = $[3];
57 - y$0 = $[4];
55 + y = $[4];
56 }
57 const c_5 = $[5] !== x;
58 let child;
@@ -65,13 +63,13 @@ function Component(props) {
63 } else {
64 child = $[6];
65 }
68 - y$0.push(props.p4);
69 - const c_7 = $[7] !== y$0;
66 + y.push(props.p4);
67 + const c_7 = $[7] !== y;
68 const c_8 = $[8] !== child;
69 let t0;
70 if (c_7 || c_8) {
73 - t0 = <Component data={y$0}>{child}</Component>;
74 - $[7] = y$0;
71 + t0 = <Component data={y}>{child}</Component>;
72 + $[7] = y;
73 $[8] = child;
74 $[9] = t0;
75 } else {
compiler/forget/src/__tests__/fixtures/hir/template-literal.expect.md
+3 -3
@@ -19,9 +19,9 @@ function componentB(props) {
19
20 ```javascript
21 function componentA(props) {
22 - const t = `hello ${props.a}, ${props.b}!`;
23 - const t$0 = t + ``;
24 - return t$0;
22 + let t = `hello ${props.a}, ${props.b}!`;
23 + t = t + ``;
24 + return t;
25 }
26
27 function componentB(props) {
compiler/forget/src/__tests__/fixtures/hir/update-expression.expect.md
+8 -8
@@ -16,18 +16,18 @@ function foo(props) {
16 ```javascript
17 function foo(props) {
18 const $ = React.unstable_useMemoCache();
19 - const x = props.x;
20 - const x$0 = x + 1;
21 - const y = x$0;
22 - const x$1 = x$0 - 1;
23 - const z = x$1;
24 - const c_0 = $[0] !== x$1;
19 + let x = props.x;
20 + x = x + 1;
21 + const y = x;
22 + x = x - 1;
23 + const z = x;
24 + const c_0 = $[0] !== x;
25 const c_1 = $[1] !== y;
26 const c_2 = $[2] !== z;
27 let t0;
28 if (c_0 || c_1 || c_2) {
29 - t0 = { x: x$1, y: y, z: z };
30 - $[0] = x$1;
29 + t0 = { x: x, y: y, z: z };
30 + $[0] = x;
31 $[1] = y;
32 $[2] = z;
33 $[3] = t0;