@samitouri / QOS-React-1 / commits / f8cee28f1d

Retain minimal variable declarations in DCE

Currently DCE can remove variable declarations that are unused, ie where all control-flow paths to usage of the variable are overwritten by a reassignment. We then have to reconstruct the original variable declaration at the appropriate block scope during LeaveSSA, which is complex and can actually be incorrect in some cases. This PR updates to ensure that DCE will not remove the original variable declaration for any variable that is used (even in the case of always being reassigned before use). The main changes are: * DCE retains variable declarations, but if a variable declaration is always shadowed by reassignments then DCE will rewrite StoreLocal -> DeclareLocal so that it can DCE the unused initial value. * BuildHIR now has to change its handling for reassignment destructure instructions with nesting. Nesting uses a temporary which would appear as a declaration of a new variable, which is incompatible with other reassignments. See comments in the file. * LeaveSSA is quite a bit simpler now, since we never need to reconstruct a declaration.

Joe Savona committed Oct 19, 2023 at 22:08 UTC f8cee28f1d7c8da8522974dd3e61b7fc0815c85d
54 files changed +484 -279
compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts
+50 -16
@@ -3204,11 +3204,19 @@ function lowerAssignment(
3204 }
3205 }
3206 case "ArrayPattern": {
3207 - // TODO
3207 const lvalue = lvaluePath as NodePath<t.ArrayPattern>;
3208 const elements = lvalue.get("elements");
3209 const items: ArrayPattern["items"] = [];
3210 const followups: Array<{ place: Place; path: NodePath<t.LVal> }> = [];
3211 + // A given destructuring statement must contain all declarations or all
3212 + // reassignments. This is enforced by the parser, but we rewrite nested
3213 + // destructuring into assignment to a temporary. Therefore, if we see
3214 + // any reassignments that are nested destructuring we fall back to
3215 + // using temporaries for all variables, and emitting the actual reassignments
3216 + // in follow-up statements
3217 + const forceTemporaries =
3218 + kind === InstructionKind.Reassign &&
3219 + elements.some((element) => !element.isIdentifier());
3220 for (let i = 0; i < elements.length; i++) {
3221 const element = elements[i];
3222 if (element.node == null) {
@@ -3228,7 +3236,7 @@ function lowerAssignment(
3236 }
3237 if (element.isRestElement()) {
3238 const argument = element.get("argument");
3231 - if (argument.isIdentifier()) {
3239 + if (argument.isIdentifier() && !forceTemporaries) {
3240 const identifier = lowerIdentifierForAssignment(
3241 builder,
3242 element.node.loc ?? GeneratedSource,
@@ -3253,7 +3261,7 @@ function lowerAssignment(
3261 });
3262 followups.push({ place: temp, path: argument as NodePath<t.LVal> }); // TODO remove type cast
3263 }
3256 - } else if (element.isIdentifier()) {
3264 + } else if (element.isIdentifier() && !forceTemporaries) {
3265 const identifier = lowerIdentifierForAssignment(
3266 builder,
3267 element.node.loc ?? GeneratedSource,
@@ -3295,6 +3303,20 @@ function lowerAssignment(
3303 const propertiesPaths = lvalue.get("properties");
3304 const properties: ObjectPattern["properties"] = [];
3305 const followups: Array<{ place: Place; path: NodePath<t.LVal> }> = [];
3306 + // A given destructuring statement must contain all declarations or all
3307 + // reassignments. This is enforced by the parser, but we rewrite nested
3308 + // destructuring into assignment to a temporary. Therefore, if we see
3309 + // any reassignments that are nested destructuring we fall back to
3310 + // using temporaries for all variables, and emitting the actual reassignments
3311 + // in follow-up statements
3312 + const forceTemporaries =
3313 + kind === InstructionKind.Reassign &&
3314 + propertiesPaths.some(
3315 + (property) =>
3316 + property.isRestElement() ||
3317 + (property.isObjectProperty() &&
3318 + !property.get("value").isIdentifier())
3319 + );
3320 for (let i = 0; i < propertiesPaths.length; i++) {
3321 const property = propertiesPaths[i];
3322 if (property.isRestElement()) {
@@ -3308,19 +3330,31 @@ function lowerAssignment(
3330 });
3331 continue;
3332 }
3311 - const identifier = lowerIdentifierForAssignment(
3312 - builder,
3313 - property.node.loc ?? GeneratedSource,
3314 - kind,
3315 - argument
3316 - );
3317 - if (identifier === null) {
3318 - continue;
3333 + if (forceTemporaries) {
3334 + const temp = buildTemporaryPlace(
3335 + builder,
3336 + property.node.loc ?? GeneratedSource
3337 + );
3338 + properties.push({
3339 + kind: "Spread",
3340 + place: { ...temp },
3341 + });
3342 + followups.push({ place: temp, path: argument as NodePath<t.LVal> }); // TODO remove type cast
3343 + } else {
3344 + const identifier = lowerIdentifierForAssignment(
3345 + builder,
3346 + property.node.loc ?? GeneratedSource,
3347 + kind,
3348 + argument
3349 + );
3350 + if (identifier === null) {
3351 + continue;
3352 + }
3353 + properties.push({
3354 + kind: "Spread",
3355 + place: identifier,
3356 + });
3357 }
3320 - properties.push({
3321 - kind: "Spread",
3322 - place: identifier,
3323 - });
3358 } else {
3359 // TODO: this should always be true given the if/else
3360 if (!property.isObjectProperty()) {
@@ -3355,7 +3389,7 @@ function lowerAssignment(
3389 });
3390 continue;
3391 }
3358 - if (element.isIdentifier()) {
3392 + if (element.isIdentifier() && !forceTemporaries) {
3393 const identifier = lowerIdentifierForAssignment(
3394 builder,
3395 element.node.loc ?? GeneratedSource,
compiler/packages/babel-plugin-react-forget/src/Optimization/DeadCodeElimination.ts
+84 -22
@@ -10,7 +10,9 @@ import {
10 BlockId,
11 HIRFunction,
12 Identifier,
13 + IdentifierId,
14 Instruction,
15 + InstructionKind,
16 InstructionValue,
17 ObjectPattern,
18 } from "../HIR";
@@ -48,7 +50,7 @@ export function deadCodeElimination(fn: HIRFunction): void {
50 for (let i = block.instructions.length - 1; i >= 0; i--) {
51 const instr = block.instructions[i]!;
52 if (
51 - !state.used(instr.lvalue.identifier) &&
53 + !state.isIdOrNameUsed(instr.lvalue.identifier) &&
54 pruneableValue(instr.value, state) &&
55 // Can't prune the last value of a value block, that's its value!
56 !(block.kind !== "block" && i === block.instructions.length - 1)
@@ -56,10 +58,20 @@ export function deadCodeElimination(fn: HIRFunction): void {
58 continue;
59 }
60 state.reference(instr.lvalue.identifier);
61 +
62 + // For the last value of a value block, if it's not pruneable we can't
63 + // rewrite it. This is necessary to preserve unused value blocks
64 + if (block.kind !== "block" && i === block.instructions.length - 1) {
65 + for (const place of eachInstructionValueOperand(instr.value)) {
66 + state.reference(place.identifier);
67 + }
68 + continue;
69 + }
70 + // Otherwise rewrite instructions to remove unused parts of them
71 visitInstruction(instr, state);
72 }
73 for (const phi of block.phis) {
62 - if (state.used(phi.id)) {
74 + if (state.isIdOrNameUsed(phi.id)) {
75 for (const [_pred, operand] of phi.operands) {
76 state.reference(operand);
77 }
@@ -69,25 +81,42 @@ export function deadCodeElimination(fn: HIRFunction): void {
81 } while (state.count > size && hasLoop);
82 for (const [, block] of fn.body.blocks) {
83 for (const phi of block.phis) {
72 - if (!state.used(phi.id)) {
84 + if (!state.isIdOrNameUsed(phi.id)) {
85 block.phis.delete(phi);
86 }
87 }
88 retainWhere(block.instructions, (instr) =>
77 - state.used(instr.lvalue.identifier)
89 + state.isIdOrNameUsed(instr.lvalue.identifier)
90 );
91 }
92 }
93
94 class State {
83 - identifiers: Set<Identifier> = new Set();
95 + named: Set<string> = new Set();
96 + identifiers: Set<IdentifierId> = new Set();
97
98 + // Mark the identifier as being referenced (not dead code)
99 reference(identifier: Identifier): void {
86 - this.identifiers.add(identifier);
100 + this.identifiers.add(identifier.id);
101 + if (identifier.name !== null) {
102 + this.named.add(identifier.name);
103 + }
104 + }
105 +
106 + // Check if any version of the given identifier is used somewhere.
107 + // This checks both for usage of this specific identifer id (ssa id)
108 + // and (for named identifiers) for any usages of that identifier name.
109 + isIdOrNameUsed(identifier: Identifier): boolean {
110 + return (
111 + this.identifiers.has(identifier.id) ||
112 + (identifier.name !== null && this.named.has(identifier.name))
113 + );
114 }
115
89 - used(identifier: Identifier): boolean {
90 - return this.identifiers.has(identifier);
116 + // Like `used()`, but only checks for usages of this specific identifier id
117 + // (ssa id).
118 + isIdUsed(identifier: Identifier): boolean {
119 + return this.identifiers.has(identifier.id);
120 }
121
122 get count(): number {
@@ -110,12 +139,12 @@ function visitInstruction(instr: Instruction, state: State): void {
139 for (let i = originalItems.length - 1; i >= 0; i--) {
140 const item = originalItems[i];
141 if (item.kind === "Identifier") {
113 - if (state.used(item.identifier)) {
142 + if (state.isIdOrNameUsed(item.identifier)) {
143 nextItems = originalItems.slice(0, i + 1);
144 break;
145 }
146 } else if (item.kind === "Spread") {
118 - if (state.used(item.place.identifier)) {
147 + if (state.isIdOrNameUsed(item.place.identifier)) {
148 nextItems = originalItems.slice(0, i + 1);
149 break;
150 }
@@ -135,12 +164,12 @@ function visitInstruction(instr: Instruction, state: State): void {
164 let nextProperties: ObjectPattern["properties"] | null = null;
165 for (const property of instr.value.lvalue.pattern.properties) {
166 if (property.kind === "ObjectProperty") {
138 - if (state.used(property.place.identifier)) {
167 + if (state.isIdOrNameUsed(property.place.identifier)) {
168 nextProperties ??= [];
169 nextProperties.push(property);
170 }
171 } else {
143 - if (state.used(property.place.identifier)) {
172 + if (state.isIdOrNameUsed(property.place.identifier)) {
173 nextProperties = null;
174 break;
175 }
@@ -160,6 +189,25 @@ function visitInstruction(instr: Instruction, state: State): void {
189 );
190 }
191 }
192 + } else if (instr.value.kind === "StoreLocal") {
193 + if (
194 + instr.value.lvalue.kind !== InstructionKind.Reassign &&
195 + !state.isIdUsed(instr.value.lvalue.place.identifier)
196 + ) {
197 + // This is a const/let declaration where the variable is accessed later,
198 + // but where the value is always overwritten before being read. Ie the
199 + // initializer value is never read. We rewrite to a DeclareLocal so
200 + // that the initializer value can be DCE'd
201 + instr.value = {
202 + kind: "DeclareLocal",
203 + lvalue: instr.value.lvalue,
204 + loc: instr.value.loc,
205 + };
206 + } else {
207 + // Else we mark the initializer as referenced, since the variable itself is
208 + // referenced
209 + state.reference(instr.value.value.identifier);
210 + }
211 } else {
212 for (const operand of eachInstructionValueOperand(instr.value)) {
213 state.reference(operand.identifier);
@@ -174,26 +222,40 @@ function visitInstruction(instr: Instruction, state: State): void {
222 function pruneableValue(value: InstructionValue, state: State): boolean {
223 switch (value.kind) {
224 case "DeclareLocal": {
177 - return !state.used(value.lvalue.place.identifier);
225 + // Declarations are pruneable only if the named variable is never read later
226 + return !state.isIdOrNameUsed(value.lvalue.place.identifier);
227 }
228 case "StoreLocal": {
180 - // Stores are pruneable only if the identifier being stored to is never read later
181 - return !state.used(value.lvalue.place.identifier);
229 + if (value.lvalue.kind === InstructionKind.Reassign) {
230 + // Reassignments can be pruned if the specific instance being assigned is never read
231 + return !state.isIdUsed(value.lvalue.place.identifier);
232 + }
233 + // Declarations are pruneable only if the named variable is never read later
234 + return !state.isIdOrNameUsed(value.lvalue.place.identifier);
235 }
236 case "Destructure": {
184 - // Destructure is pruneable only if none of the identifiers are read from later
185 - // TODO: as an optimization, prune unused properties where safe
237 + let isIdOrNameUsed = false;
238 + let isIdUsed = false;
239 for (const place of eachPatternOperand(value.lvalue.pattern)) {
187 - if (state.used(place.identifier)) {
188 - return false;
240 + if (state.isIdUsed(place.identifier)) {
241 + isIdOrNameUsed = true;
242 + isIdUsed = true;
243 + } else if (state.isIdOrNameUsed(place.identifier)) {
244 + isIdOrNameUsed = true;
245 }
246 }
191 - return true;
247 + if (value.lvalue.kind === InstructionKind.Reassign) {
248 + // Reassignments can be pruned if the specific instance being assigned is never read
249 + return !isIdUsed;
250 + } else {
251 + // Otherwise pruneable only if none of the identifiers are read from later
252 + return !isIdOrNameUsed;
253 + }
254 }
255 case "PostfixUpdate":
256 case "PrefixUpdate": {
195 - // Updates are pruneable only if the identifier being stored to is never read later
196 - return !state.used(value.lvalue.identifier);
257 + // Updates are pruneable if the specific instance instance being assigned is never read
258 + return !state.isIdUsed(value.lvalue.identifier);
259 }
260 case "Debugger": {
261 // explicitly retain debugger statements to not break debugging workflows
compiler/packages/babel-plugin-react-forget/src/SSA/LeaveSSA.ts
+8 -85
@@ -9,11 +9,8 @@ import { CompilerError } from "../CompilerError";
9 import {
10 BasicBlock,
11 BlockId,
12 - Effect,
13 - GeneratedSource,
12 HIRFunction,
13 Identifier,
16 - Instruction,
14 InstructionKind,
15 LValue,
16 LValuePattern,
@@ -420,88 +417,13 @@ export function leaveSSA(fn: HIRFunction): void {
417 suggestions: null,
418 });
419 const declaration = declarations.get(phi.id.name);
423 - if (declaration === undefined) {
424 - let initValue: Place;
425 - if (initOperand === null) {
426 - initValue = {
427 - effect: Effect.Read,
428 - kind: "Identifier",
429 - loc: GeneratedSource,
430 - identifier: {
431 - id: fn.env.nextIdentifierId,
432 - name: null,
433 - mutableRange: {
434 - // TODO: this is technically the wrong start range; we do this because the instruction to create the
435 - // undefined and the instruction to store it to the identifier share an InstructionId, which makes
436 - // this value otherwise appear mutable when stored. All that matters is that the range end prior
437 - // to the StoreLocal's instruction id, so we decrement by one.
438 - start: makeInstructionId(block.terminal.id - 1),
439 - end: makeInstructionId(block.terminal.id),
440 - },
441 - scope: null,
442 - type: { kind: "Primitive" },
443 - },
444 - };
445 - block.instructions.push({
446 - id: block.terminal.id,
447 - lvalue: { ...initValue, effect: Effect.ConditionallyMutate },
448 - value: {
449 - kind: "Primitive",
450 - // TODO: consider leaving the variable uninitialized rather than explicitly undefined.
451 - value: undefined,
452 - loc: GeneratedSource,
453 - },
454 - loc: GeneratedSource,
455 - });
456 - } else {
457 - initValue = {
458 - kind: "Identifier",
459 - identifier: initOperand,
460 - effect: Effect.Capture,
461 - loc: GeneratedSource,
462 - };
463 - }
464 - const lvalue: LValue = {
465 - place: {
466 - kind: "Identifier",
467 - identifier: phi.id,
468 - effect: Effect.ConditionallyMutate,
469 - loc: GeneratedSource,
470 - },
471 - kind: InstructionKind.Let,
472 - };
473 - const instr: Instruction = {
474 - // NOTE: reuse the terminal id since these lets must be scoped with the terminal anyway.
475 - // the mutable range of this canonical id must by definition span from the binding (before
476 - // the if) to the phi, so it's safe to reuse the terminal's id.
477 - id: block.terminal.id,
478 - lvalue: {
479 - kind: "Identifier",
480 - identifier: {
481 - id: fn.env.nextIdentifierId,
482 - mutableRange: {
483 - start: block.terminal.id,
484 - end: makeInstructionId(block.terminal.id + 1),
485 - },
486 - name: null,
487 - scope: null,
488 - type: phi.id.type,
489 - },
490 - effect: Effect.ConditionallyMutate,
491 - loc: GeneratedSource,
492 - },
493 - value: {
494 - kind: "StoreLocal",
495 - lvalue,
496 - value: initValue,
497 - loc: GeneratedSource,
498 - },
499 - loc: GeneratedSource,
500 - };
501 - block.instructions.push(instr);
502 - declarations.set(phi.id.name, { lvalue, place: lvalue.place });
503 - phi.id.mutableRange.start = terminal.id;
504 - } else if (isPhiMutatedAfterCreation) {
420 + CompilerError.invariant(declaration != null, {
421 + loc: null,
422 + reason: "Expected a declaration for all variables",
423 + description: null,
424 + suggestions: null,
425 + });
426 + if (isPhiMutatedAfterCreation) {
427 // The declaration is not guaranteed to flow into the phi, for example in the case of a variable
428 // that is reassigned in all control flow paths to a given phi. The original declaration's range
429 // has to be extended in this case (if the phi is later mutated) since we are reusing the original
@@ -511,6 +433,7 @@ export function leaveSSA(fn: HIRFunction): void {
433 // not prune. Otherwise, the declaration would have been pruned and we'd synthesize a new one.
434 declaration.place.identifier.mutableRange.end = phi.id.mutableRange.end;
435 }
436 + rewrites.set(phi.id, declaration.place.identifier);
437 }
438
439 // Similar logic for rewrite phis that occur in loops, except that instead of a new let binding
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/assignment-variations.expect.md
+3 -1
@@ -22,7 +22,9 @@ export const FIXTURE_ENTRYPOINT = {
22
23 ```javascript
24 function f() {
25 - const x = 3 >>> 1;
25 + let x;
26 +
27 + x = 3 >>> 1;
28 return x;
29 }
30
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-do-while-test.expect.md
+1 -2
@@ -33,10 +33,9 @@ export const FIXTURE_ENTRYPOINT = {
33 import { unstable_useMemoCache as useMemoCache } from "react";
34 function Component(props) {
35 const $ = useMemoCache(1);
36 -
36 + let x;
37 let i = 0;
38 do {
39 - let x = undefined;
39 if (i > 10) {
40 x = 10;
41 } else {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-if.expect.md
+1 -1
@@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = {
28 import { unstable_useMemoCache as useMemoCache } from "react";
29 function Component(props) {
30 const $ = useMemoCache(1);
31 - let x = undefined;
31 + let x;
32 if (props.cond) {
33 x = 1;
34 } else {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-case-test.expect.md
+1 -1
@@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = {
36 import { unstable_useMemoCache as useMemoCache } from "react";
37 function Component(props) {
38 const $ = useMemoCache(1);
39 - let x = undefined;
39 + let x;
40 bb1: switch (props.cond) {
41 case true: {
42 x = 1;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug.reactive-control-dependency-switch-condition.expect.md
+1 -1
@@ -38,7 +38,7 @@ const GLOBAL = 42;
38 function Component(t14) {
39 const $ = useMemoCache(1);
40 const { value } = t14;
41 - let x = undefined;
41 + let x;
42 bb1: switch (GLOBAL) {
43 case value: {
44 x = 1;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md
+1 -1
@@ -51,7 +51,7 @@ function getNativeLogFunction(level) {
51 let t0;
52 if ($[0] !== level) {
53 t0 = function () {
54 - let str = undefined;
54 + let str;
55 if (arguments.length === 1 && typeof arguments[0] === "string") {
56 str = arguments[0];
57 } else {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/capturing-function-within-block.expect.md
+2 -1
@@ -36,6 +36,7 @@ function component(a) {
36 t0 = $[1];
37 }
38 const z = t0;
39 + let x;
40 let t1;
41 if ($[2] !== z) {
42 t1 = function () {
@@ -46,7 +47,7 @@ function component(a) {
47 } else {
48 t1 = $[3];
49 }
49 - const x = t1;
50 + x = t1;
51 return x;
52 }
53
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/const-propagation-phi-nodes.expect.md new
+58
@@ -0,0 +1,58 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function useFoo(setOne: boolean) {
6 + let x;
7 + let y;
8 + let z;
9 + if (setOne) {
10 + x = y = z = 1;
11 + } else {
12 + x = 2;
13 + y = 3;
14 + z = 5;
15 + }
16 + return { x, y, z };
17 +}
18 +
19 +export const FIXTURE_ENTRYPOINT = {
20 + fn: useFoo,
21 + params: [true],
22 +};
23 +
24 +```
25 +
26 +## Code
27 +
28 +```javascript
29 +import { unstable_useMemoCache as useMemoCache } from "react";
30 +function useFoo(setOne) {
31 + const $ = useMemoCache(1);
32 + let x;
33 + let y;
34 + let z;
35 + if (setOne) {
36 + x = y = z = 1;
37 + } else {
38 + x = 2;
39 + y = 3;
40 + z = 5;
41 + }
42 + let t0;
43 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
44 + t0 = { x, y, z };
45 + $[0] = t0;
46 + } else {
47 + t0 = $[0];
48 + }
49 + return t0;
50 +}
51 +
52 +export const FIXTURE_ENTRYPOINT = {
53 + fn: useFoo,
54 + params: [true],
55 +};
56 +
57 +```
58 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/const-propagation-phi-nodes.ts renamed
+1 -1
@@ -12,7 +12,7 @@ function useFoo(setOne: boolean) {
12 return { x, y, z };
13 }
14
15 -export const FIXTURE_ENTRYPONT = {
15 +export const FIXTURE_ENTRYPOINT = {
16 fn: useFoo,
17 params: [true],
18 };
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/dce-unused-postfix-update.expect.md new
+35
@@ -0,0 +1,35 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + let i = 0;
7 + i++;
8 + i = props.i;
9 + return i;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: Component,
14 + params: [{ i: 42 }],
15 +};
16 +
17 +```
18 +
19 +## Code
20 +
21 +```javascript
22 +function Component(props) {
23 + let i;
24 +
25 + i = props.i;
26 + return i;
27 +}
28 +
29 +export const FIXTURE_ENTRYPOINT = {
30 + fn: Component,
31 + params: [{ i: 42 }],
32 +};
33 +
34 +```
35 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/dce-unused-postfix-update.js new
+11
@@ -0,0 +1,11 @@
1 +function Component(props) {
2 + let i = 0;
3 + i++;
4 + i = props.i;
5 + return i;
6 +}
7 +
8 +export const FIXTURE_ENTRYPOINT = {
9 + fn: Component,
10 + params: [{ i: 42 }],
11 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/dce-unused-prefix-update.expect.md new
+35
@@ -0,0 +1,35 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + let i = 0;
7 + --i;
8 + i = props.i;
9 + return i;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: Component,
14 + params: [{ i: 42 }],
15 +};
16 +
17 +```
18 +
19 +## Code
20 +
21 +```javascript
22 +function Component(props) {
23 + let i;
24 +
25 + i = props.i;
26 + return i;
27 +}
28 +
29 +export const FIXTURE_ENTRYPOINT = {
30 + fn: Component,
31 + params: [{ i: 42 }],
32 +};
33 +
34 +```
35 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/dce-unused-prefix-update.js new
+11
@@ -0,0 +1,11 @@
1 +function Component(props) {
2 + let i = 0;
3 + --i;
4 + i = props.i;
5 + return i;
6 +}
7 +
8 +export const FIXTURE_ENTRYPOINT = {
9 + fn: Component,
10 + params: [{ i: 42 }],
11 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-direct-reassignment.expect.md
+3 -1
@@ -22,7 +22,9 @@ export const FIXTURE_ENTRYPOINT = {
22
23 ```javascript
24 function foo(props) {
25 - let { x, y } = { x: props.a, y: props.b };
25 + let x;
26 + let y;
27 + ({ x, y } = { x: props.a, y: props.b });
28 console.log(x);
29 x = props.c;
30 return x + y;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md new
+82
@@ -0,0 +1,82 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function useFoo(props: {
6 + x?: string;
7 + y?: string;
8 + z?: string;
9 + doDestructure: boolean;
10 +}) {
11 + let x = null;
12 + let y = null;
13 + let z = null;
14 + const myList = [];
15 + if (props.doDestructure) {
16 + ({ x, y, z } = props);
17 +
18 + myList.push(z);
19 + }
20 + return {
21 + x,
22 + y,
23 + myList,
24 + };
25 +}
26 +
27 +export const FIXTURE_ENTRYPOINT = {
28 + fn: useFoo,
29 + params: [{ x: "hello", y: "world", doDestructure: true }],
30 +};
31 +
32 +```
33 +
34 +## Code
35 +
36 +```javascript
37 +import { unstable_useMemoCache as useMemoCache } from "react";
38 +function useFoo(props) {
39 + const $ = useMemoCache(9);
40 +
41 + let x = null;
42 + let y = null;
43 + let z;
44 + let myList;
45 + if ($[0] !== props) {
46 + myList = [];
47 + if (props.doDestructure) {
48 + ({ x, y, z } = props);
49 +
50 + myList.push(z);
51 + }
52 + $[0] = props;
53 + $[1] = myList;
54 + $[2] = x;
55 + $[3] = y;
56 + $[4] = z;
57 + } else {
58 + myList = $[1];
59 + x = $[2];
60 + y = $[3];
61 + z = $[4];
62 + }
63 + let t0;
64 + if ($[5] !== x || $[6] !== y || $[7] !== myList) {
65 + t0 = { x, y, myList };
66 + $[5] = x;
67 + $[6] = y;
68 + $[7] = myList;
69 + $[8] = t0;
70 + } else {
71 + t0 = $[8];
72 + }
73 + return t0;
74 +}
75 +
76 +export const FIXTURE_ENTRYPOINT = {
77 + fn: useFoo,
78 + params: [{ x: "hello", y: "world", doDestructure: true }],
79 +};
80 +
81 +```
82 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.ts renamed
+1 -1
@@ -20,7 +20,7 @@ function useFoo(props: {
20 };
21 }
22
23 -export const FIXTURE_ENTRYPONT = {
23 +export const FIXTURE_ENTRYPOINT = {
24 fn: useFoo,
25 params: [{ x: "hello", y: "world", doDestructure: true }],
26 };
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructuring-assignment-array-default.expect.md
+1 -1
@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
26 import { unstable_useMemoCache as useMemoCache } from "react";
27 function Component(props) {
28 const $ = useMemoCache(2);
29 - let x = undefined;
29 + let x;
30 if (props.cond) {
31 const [t0] = props.y;
32 let t1;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructuring-assignment.expect.md
+14 -9
@@ -35,15 +35,20 @@ export const FIXTURE_ENTRYPOINT = {
35 import { unstable_useMemoCache as useMemoCache } from "react";
36 function foo(a, b, c) {
37 const $ = useMemoCache(5);
38 -
39 - const [d, t46] = a;
40 - const [t48] = t46;
41 - const { e: t50 } = t48;
42 - const { f: g } = t50;
43 - const { l: t55, o } = b;
44 - const { m: t58 } = t55;
45 - const [t60] = t58;
46 - const [n] = t60;
38 + let d;
39 + let g;
40 + let n;
41 + let o;
42 + const [t49, t50] = a;
43 + d = t49;
44 + const [t54] = t50;
45 + const { e: t56 } = t54;
46 + ({ f: g } = t56);
47 + const { l: t61, o: t62 } = b;
48 + const { m: t64 } = t61;
49 + const [t66] = t64;
50 + [n] = t66;
51 + o = t62;
52 let t0;
53 if ($[0] !== d || $[1] !== g || $[2] !== n || $[3] !== o) {
54 t0 = { d, g, n, o };
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.const-propagation-phi-nodes.expect.md deleted
-33
@@ -1,33 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -function useFoo(setOne: boolean) {
6 - let x;
7 - let y;
8 - let z;
9 - if (setOne) {
10 - x = y = z = 1;
11 - } else {
12 - x = 2;
13 - y = 3;
14 - z = 5;
15 - }
16 - return { x, y, z };
17 -}
18 -
19 -export const FIXTURE_ENTRYPONT = {
20 - fn: useFoo,
21 - params: [true],
22 -};
23 -
24 -```
25 -
26 -
27 -## Error
28 -
29 -```
30 -[ReactForget] Invariant: Const declaration cannot be referenced as an expression (6:6)
31 -```
32 -
33 -
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.destructure-in-branch-ssa.expect.md deleted
-41
@@ -1,41 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -function useFoo(props: {
6 - x?: string;
7 - y?: string;
8 - z?: string;
9 - doDestructure: boolean;
10 -}) {
11 - let x = null;
12 - let y = null;
13 - let z = null;
14 - const myList = [];
15 - if (props.doDestructure) {
16 - ({ x, y, z } = props);
17 -
18 - myList.push(z);
19 - }
20 - return {
21 - x,
22 - y,
23 - myList,
24 - };
25 -}
26 -
27 -export const FIXTURE_ENTRYPONT = {
28 - fn: useFoo,
29 - params: [{ x: "hello", y: "world", doDestructure: true }],
30 -};
31 -
32 -```
33 -
34 -
35 -## Error
36 -
37 -```
38 -[ReactForget] Invariant: Expected consistent kind for destructuring. Other places were 'Reassign' but 'store z$44' is const (12:12)
39 -```
40 -
41 -
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.while-with-assignment-in-test.expect.md deleted
-25
@@ -1,25 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -function f(reader) {
6 - const queue = [1, 2, 3];
7 - let value = 0;
8 - let sum = 0;
9 - // BUG: we need to codegen the complex test expression
10 - while ((value = queue.pop()) != null) {
11 - sum += value;
12 - }
13 - return sum;
14 -}
15 -
16 -```
17 -
18 -
19 -## Error
20 -
21 -```
22 -[ReactForget] Invariant: TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE) (6:6)
23 -```
24 -
25 -
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/escape-analysis-not-if-test.expect.md
+1 -1
@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
26 ```javascript
27 function Component(props) {
28 const x = [props.a];
29 - let y = undefined;
29 + let y;
30 if (x) {
31 y = props.b;
32 } else {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-loop-let-undefined-decl.expect.md
+1 -1
@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
26 ```javascript
27 function useFoo() {
28 for (let i = 0; i <= 5; i++) {
29 - let color = undefined;
29 + let color;
30 if (isSelected) {
31 color = isCurrent ? "#FFCC22" : "#FF5050";
32 } else {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/function-declaration-redeclare.expect.md
+2 -1
@@ -24,6 +24,7 @@ export const FIXTURE_ENTRYPOINT = {
24 import { unstable_useMemoCache as useMemoCache } from "react";
25 function component() {
26 const $ = useMemoCache(1);
27 + let x;
28 let t0;
29 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
30 t0 = function x() {};
@@ -31,7 +32,7 @@ function component() {
32 } else {
33 t0 = $[0];
34 }
34 - const x = t0;
35 + x = t0;
36 return x;
37 }
38
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hoisting-nested-const-declaration.expect.md
+3 -1
@@ -35,7 +35,9 @@ function hoisting() {
35 let t0;
36 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
37 const qux = () => {
38 - const result = foo();
38 + let result;
39 +
40 + result = foo();
41 return result;
42 };
43
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hooks-freeze-possibly-mutable-arguments.expect.md
+1 -1
@@ -30,7 +30,7 @@ function Component(props) {
30 const $ = useMemoCache(1);
31 const cond = props.cond;
32 const x = props.x;
33 - let a = undefined;
33 + let a;
34 if (cond) {
35 a = x;
36 } else {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/infer-phi-primitive.expect.md
+1 -1
@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
26
27 ```javascript
28 function foo(a, b) {
29 - let x = undefined;
29 + let x;
30 if (a) {
31 x = 1;
32 } else {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/inverted-if-else.expect.md
+1 -1
@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
26
27 ```javascript
28 function foo(a, b, c) {
29 - let x = undefined;
29 + let x;
30 bb1: {
31 if (a) {
32 x = b;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/obj-literal-cached-in-if-else.expect.md
+1 -1
@@ -21,7 +21,7 @@ function foo(a, b, c, d) {
21 import { unstable_useMemoCache as useMemoCache } from "react";
22 function foo(a, b, c, d) {
23 const $ = useMemoCache(4);
24 - let x = undefined;
24 + let x;
25 if (someVal) {
26 let t0;
27 if ($[0] !== b) {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/obj-literal-mutated-after-if-else.expect.md
-1
@@ -24,7 +24,6 @@ function foo(a, b, c, d) {
24 const $ = useMemoCache(3);
25 let x;
26 if ($[0] !== b || $[1] !== c) {
27 - x = undefined;
27 if (someVal) {
28 x = { b };
29 } else {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/obj-mutated-after-if-else-with-alias.expect.md
-1
@@ -27,7 +27,6 @@ function foo(a, b, c, d) {
27 someObj();
28 let x;
29 if ($[0] !== a) {
30 - x = undefined;
30 if (a) {
31 const y = someObj();
32 const z = y;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/obj-mutated-after-if-else.expect.md
-1
@@ -25,7 +25,6 @@ function foo(a, b, c, d) {
25 someObj();
26 let x;
27 if ($[0] !== a) {
28 - x = undefined;
28 if (a) {
29 x = someObj();
30 } else {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/obj-mutated-after-nested-if-else-with-alias.expect.md
+1 -2
@@ -33,9 +33,8 @@ function foo(a, b, c, d) {
33 someObj();
34 let x;
35 if ($[0] !== a || $[1] !== b) {
36 - x = undefined;
36 if (a) {
38 - let z = undefined;
37 + let z;
38 if (b) {
39 const w = someObj();
40 z = w;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md
+1 -3
@@ -2,7 +2,6 @@
2 ## Input
3
4 ```javascript
5 -// @debug
5 function Component(props) {
6 const x = {};
7 let y;
@@ -30,14 +29,13 @@ export const FIXTURE_ENTRYPOINT = {
29 ## Code
30
31 ```javascript
33 -import { unstable_useMemoCache as useMemoCache } from "react"; // @debug
32 +import { unstable_useMemoCache as useMemoCache } from "react";
33 function Component(props) {
34 const $ = useMemoCache(6);
35 let x;
36 let y;
37 if ($[0] !== props) {
38 x = {};
40 - y = undefined;
39 if (props.cond) {
40 y = [props.value];
41 } else {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/phi-type-inference-array-push.js
-1
@@ -1,4 +1,3 @@
1 -// @debug
1 function Component(props) {
2 const x = {};
3 let y;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md
-1
@@ -43,7 +43,6 @@ function Component(props) {
43 const x = t0;
44 let y;
45 if ($[1] !== props || $[2] !== x) {
46 - y = undefined;
46 if (props.cond) {
47 y = {};
48 } else {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/primitive-alias-mutate.expect.md
+1 -1
@@ -20,7 +20,7 @@ function component(a) {
20
21 ```javascript
22 function component(a) {
23 - let x = undefined;
23 + let x;
24 if (a) {
25 x = "bar";
26 } else {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md
+1 -1
@@ -25,7 +25,7 @@ function Component(props) {
25 let t0;
26 if ($[0] !== props.str) {
27 t0 = () => {
28 - let str = undefined;
28 + let str;
29 if (arguments.length) {
30 str = arguments[0];
31 } else {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/simple-alias.expect.md
+5 -2
@@ -22,18 +22,21 @@ function foo() {
22 import { unstable_useMemoCache as useMemoCache } from "react";
23 function mutate() {}
24 function foo() {
25 - const $ = useMemoCache(1);
25 + const $ = useMemoCache(2);
26 + let a;
27 let c;
28 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
29 let b = {};
30 c = {};
30 - const a = b;
31 + a = b;
32 b = c;
33 c = a;
34 mutate(a, b);
35 $[0] = c;
36 + $[1] = a;
37 } else {
38 c = $[0];
39 + a = $[1];
40 }
41 return c;
42 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ssa-multiple-phis.expect.md
+3 -1
@@ -34,7 +34,9 @@ export const FIXTURE_ENTRYPOINT = {
34
35 ```javascript
36 function foo(a, b, c, d) {
37 - const x = a;
37 + let x;
38 +
39 + x = a;
40 return x;
41 }
42
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ssa-reassign.expect.md
+3 -1
@@ -22,7 +22,9 @@ export const FIXTURE_ENTRYPOINT = {
22
23 ```javascript
24 function foo(a, b, c) {
25 - const x = c;
25 + let x;
26 +
27 + x = c;
28 return x;
29 }
30
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ternary-assignment-expression.expect.md
+1 -2
@@ -20,8 +20,7 @@ export const FIXTURE_ENTRYPOINT = {
20
21 ```javascript
22 function ternary(props) {
23 - let x = undefined;
24 -
23 + let x;
24 const y = props.a ? (x = 1) : (x = 2);
25 return x + y;
26 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-immediately-returns.expect.md
+1
@@ -26,6 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
26
27 ```javascript
28 function Component(props) {
29 + let x;
30 return 42;
31 }
32
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-immediately-throws-after-constant-propagation.expect.md
+1
@@ -26,6 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
26
27 ```javascript
28 function Component(props) {
29 + let x;
30 return 42;
31 }
32
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch.expect.md
+1 -1
@@ -29,7 +29,7 @@ const { throwErrorWithMessage } = require("shared-runtime");
29
30 function Component(props) {
31 const $ = useMemoCache(1);
32 - let x = undefined;
32 + let x;
33 try {
34 let t0;
35 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/unused-conditional.expect.md
+1 -2
@@ -20,8 +20,7 @@ export const FIXTURE_ENTRYPOINT = {
20
21 ```javascript
22 function Component(props) {
23 - let x = undefined;
24 -
23 + let x;
24 ((x = 1), 1) && (x = 2);
25 return x;
26 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/unused-logical.expect.md
+1 -2
@@ -20,8 +20,7 @@ export const FIXTURE_ENTRYPOINT = {
20
21 ```javascript
22 function Component(props) {
23 - let x = undefined;
24 -
23 + let x;
24 props.cond ? (x = 1) : (x = 2);
25 return x;
26 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/useMemo-switch-return.expect.md
+1 -1
@@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = {
36 function Component(props) {
37 let t49;
38 bb11: {
39 - let y = undefined;
39 + let y;
40 bb2: switch (props.switch) {
41 case "foo": {
42 t49 = "foo";
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/while-with-assignment-in-test.expect.md new
+41
@@ -0,0 +1,41 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component() {
6 + const queue = [1, 2, 3];
7 + let value = 0;
8 + let sum = 0;
9 + while ((value = queue.pop()) != null) {
10 + sum += value;
11 + }
12 + return sum;
13 +}
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: Component,
17 + params: [],
18 +};
19 +
20 +```
21 +
22 +## Code
23 +
24 +```javascript
25 +function Component() {
26 + const queue = [1, 2, 3];
27 + let value;
28 + let sum = 0;
29 + while ((value = queue.pop()) != null) {
30 + sum = sum + value;
31 + }
32 + return sum;
33 +}
34 +
35 +export const FIXTURE_ENTRYPOINT = {
36 + fn: Component,
37 + params: [],
38 +};
39 +
40 +```
41 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/while-with-assignment-in-test.js renamed
+6 -2
@@ -1,10 +1,14 @@
1 -function f(reader) {
1 +function Component() {
2 const queue = [1, 2, 3];
3 let value = 0;
4 let sum = 0;
5 - // BUG: we need to codegen the complex test expression
5 while ((value = queue.pop()) != null) {
6 sum += value;
7 }
8 return sum;
9 }
10 +
11 +export const FIXTURE_ENTRYPOINT = {
12 + fn: Component,
13 + params: [],
14 +};
compiler/packages/sprout/src/SproutTodoFilter.ts
-2
@@ -416,8 +416,6 @@ const skipFilter = new Set([
416 "readonly-object-method-calls",
417 "readonly-object-method-calls-mutable-lambda",
418
419 - "bug.reactive-control-dependency-do-while-test",
420 -
419 // TODO: 🌲
420 "forest-basic",
421