@samitouri / QOS-React-2 / commits / 1dfaf8a94b

Lower all operands to temporaries

This PR changes BuildHIR to lower all operands to temporaries. Example: ```javascript // Input a + b; // Previous Lowering Const t0 = BinaryOperation Place(a) "+" Place(b) // New Lowering Const t0 = Place(a); Const t1 = Place(b); BinaryOperation Place(t0) "+" Place(t1) ``` This is necessary to ensure we're always referring to the correct version of a variable, even in the case of reassignment mid-expression. For example, we previously evaluated `let x=1; x + (x = 2) + x` incorrectly to 6 because we lowered the `x = 2` prior to the binary operators. We now lowers each instance of x to a temporary, ensuring they refer to the correct SSA version of the variable, and produce the correct result (5). Note that with this change, the _only_ place a variable can appear as an operator is when the InstructionValue is a raw identifier. This was already the case for globals (as of the LoadGlobal instruction). All other instruction value variants will only ever receive temporaries as arguments. This necessitated a few changes to our inference: * The logic to extend the range of phi operands (if the phi is mutated) was previously in LeaveSSA, but that was actually too late. The introduction of lowering to temporaries help discover failing cases, which I fixed earlier in the stack by moving the logic to extend the range of phi operands into the InferMutableRanges fixpoint loop. * PropagateScopeDependencies now has to track variable reassignments in addition to tracking property accesses * AnalyzeFunctions now has to track variable reassignments in addition to tracking property accesses * InferReactiveIdentifiers now needs a fixpoint iteration, because identifiers don't directly appear together in the same instruction anymore (such that we can directly propagate the reactivity between them). Instead, we'll first see that the temporaries are reactive, and have to propagate that back to the identifiers the temporaries were loaded from. Overall while this does introduce a bit more complexity, it also makes the compiler more robust. As with the phi example illustrates, there are legitimate inputs that can create similar indirections to that introduced by lowering identifiers to temporaries. Note that there’s a theme to the changes here: several analysis passes need to map an operand back to its identifier value. Ideally our HIR structure would directly support looking up the value for a temporary. For example, if operands were references to eg the index of the instruction that produced them. Because we don’t have such a representation yet (it would fall out naturally if we were writing in Rust), we have to do some bookkeeping. The key takeaway here is that this bookkeeping is incidental complexity given our current representation, not fundamental complexity of the algorithm.

Joe Savona committed Feb 22, 2023 at 15:53 UTC 1dfaf8a94bf5ab3f7fe2afac6f31ac75ce884fed
38 files changed +329 -244
compiler/forget/src/HIR/BuildHIR.ts
+74 -48
@@ -127,7 +127,7 @@ export function lower(
127 const terminal: ReturnTerminal = {
128 kind: "return",
129 loc: GeneratedSource,
130 - value: lowerExpressionToPlace(builder, body),
130 + value: lowerExpressionToTemporary(builder, body),
131 id: makeInstructionId(0),
132 };
133 builder.terminateWithContinuation(terminal, fallthrough);
@@ -169,7 +169,7 @@ function lowerStatement(
169 switch (stmtNode.type) {
170 case "ThrowStatement": {
171 const stmt = stmtPath as NodePath<t.ThrowStatement>;
172 - const value = lowerExpressionToPlace(builder, stmt.get("argument"));
172 + const value = lowerExpressionToTemporary(builder, stmt.get("argument"));
173 const terminal: ThrowTerminal = {
174 kind: "throw",
175 value,
@@ -183,7 +183,10 @@ function lowerStatement(
183 const argument = stmt.get("argument");
184 const value =
185 argument.node != null
186 - ? lowerExpressionToPlace(builder, argument as NodePath<t.Expression>)
186 + ? lowerExpressionToTemporary(
187 + builder,
188 + argument as NodePath<t.Expression>
189 + )
190 : null;
191 const terminal: ReturnTerminal = {
192 kind: "return",
@@ -225,7 +228,7 @@ function lowerStatement(
228 // If there is no else clause, use the continuation directly
229 alternateBlock = continuationBlock.id;
230 }
228 - const test = lowerExpressionToPlace(builder, stmt.get("test"));
231 + const test = lowerExpressionToTemporary(builder, stmt.get("test"));
232 const terminal: IfTerminal = {
233 kind: "if",
234 test,
@@ -353,7 +356,7 @@ function lowerStatement(
356 builder.terminateWithContinuation(
357 {
358 kind: "branch",
356 - test: lowerExpressionToPlace(
359 + test: lowerExpressionToTemporary(
360 builder,
361 test as NodePath<t.Expression>
362 ),
@@ -409,7 +412,7 @@ function lowerStatement(
412 * The conditional block is empty and exists solely as conditional for
413 * (re)entering or exiting the loop
414 */
412 - const test = lowerExpressionToPlace(builder, stmt.get("test"));
415 + const test = lowerExpressionToTemporary(builder, stmt.get("test"));
416 const terminal: BranchTerminal = {
417 kind: "branch",
418 test,
@@ -525,7 +528,7 @@ function lowerStatement(
528 });
529 }
530 }
528 - test = lowerExpressionToPlace(
531 + test = lowerExpressionToTemporary(
532 builder,
533 testExpr as NodePath<t.Expression>
534 );
@@ -549,7 +552,10 @@ function lowerStatement(
552 cases.push({ test: null, block: continuationBlock.id });
553 }
554
552 - const test = lowerExpressionToPlace(builder, stmt.get("discriminant"));
555 + const test = lowerExpressionToTemporary(
556 + builder,
557 + stmt.get("discriminant")
558 + );
559 builder.terminateWithContinuation(
560 {
561 kind: "switch",
@@ -772,7 +778,7 @@ function lowerExpression(
778 hasError = true;
779 continue;
780 }
775 - const value = lowerExpressionToPlace(builder, valuePath);
781 + const value = lowerExpressionToTemporary(builder, valuePath);
782 properties.set(key.name, value);
783 }
784 return hasError
@@ -798,7 +804,7 @@ function lowerExpression(
804 continue;
805 }
806 elements.push(
801 - lowerExpressionToPlace(builder, element as NodePath<t.Expression>)
807 + lowerExpressionToTemporary(builder, element as NodePath<t.Expression>)
808 );
809 }
810 return hasError
@@ -820,7 +826,7 @@ function lowerExpression(
826 });
827 return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
828 }
823 - const callee = lowerExpressionToPlace(builder, calleePath);
829 + const callee = lowerExpressionToTemporary(builder, calleePath);
830 let args: Place[] = [];
831 let hasError = false;
832 for (const argPath of expr.get("arguments")) {
@@ -833,7 +839,7 @@ function lowerExpression(
839 hasError = true;
840 continue;
841 }
836 - args.push(lowerExpressionToPlace(builder, argPath));
842 + args.push(lowerExpressionToTemporary(builder, argPath));
843 }
844
845 return hasError
@@ -873,7 +879,7 @@ function lowerExpression(
879 hasError = true;
880 continue;
881 }
876 - args.push(lowerExpressionToPlace(builder, argPath));
882 + args.push(lowerExpressionToTemporary(builder, argPath));
883 }
884 if (typeof property === "string") {
885 return {
@@ -893,7 +899,7 @@ function lowerExpression(
899 };
900 }
901 } else {
896 - const callee = lowerExpressionToPlace(builder, calleePath);
902 + const callee = lowerExpressionToTemporary(builder, calleePath);
903 let args: Place[] = [];
904 for (const argPath of expr.get("arguments")) {
905 if (!argPath.isExpression()) {
@@ -905,7 +911,7 @@ function lowerExpression(
911 hasError = true;
912 continue;
913 }
908 - args.push(lowerExpressionToPlace(builder, argPath));
914 + args.push(lowerExpressionToTemporary(builder, argPath));
915 }
916 return hasError
917 ? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }
@@ -928,8 +934,8 @@ function lowerExpression(
934 });
935 return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
936 }
931 - const left = lowerExpressionToPlace(builder, leftPath);
932 - const right = lowerExpressionToPlace(builder, expr.get("right"));
937 + const left = lowerExpressionToTemporary(builder, leftPath);
938 + const right = lowerExpressionToTemporary(builder, expr.get("right"));
939 const operator = expr.node.operator;
940 return {
941 kind: "BinaryExpression",
@@ -945,7 +951,7 @@ function lowerExpression(
951
952 let last: Place | null = null;
953 for (const item of expr.get("expressions")) {
948 - last = lowerExpressionToPlace(builder, item);
954 + last = lowerExpressionToTemporary(builder, item);
955 }
956 if (last === null) {
957 builder.errors.push({
@@ -971,7 +977,7 @@ function lowerExpression(
977 builder.push({
978 id: makeInstructionId(0),
979 lvalue: { kind: InstructionKind.Reassign, place: { ...place } },
974 - value: lowerExpressionToPlace(builder, expr.get("consequent")),
980 + value: lowerExpressionToTemporary(builder, expr.get("consequent")),
981 loc: exprLoc,
982 });
983 return {
@@ -986,7 +992,7 @@ function lowerExpression(
992 builder.push({
993 id: makeInstructionId(0),
994 lvalue: { kind: InstructionKind.Reassign, place: { ...place } },
989 - value: lowerExpressionToPlace(builder, expr.get("alternate")),
995 + value: lowerExpressionToTemporary(builder, expr.get("alternate")),
996 loc: exprLoc,
997 });
998 return {
@@ -1007,7 +1013,7 @@ function lowerExpression(
1013 },
1014 testBlock
1015 );
1010 - const testPlace = lowerExpressionToPlace(builder, expr.get("test"));
1016 + const testPlace = lowerExpressionToTemporary(builder, expr.get("test"));
1017 builder.terminateWithContinuation(
1018 {
1019 kind: "branch",
@@ -1048,7 +1054,7 @@ function lowerExpression(
1054 builder.push({
1055 id: makeInstructionId(0),
1056 lvalue: { kind: InstructionKind.Reassign, place: { ...place } },
1051 - value: lowerExpressionToPlace(builder, expr.get("right")),
1057 + value: lowerExpressionToTemporary(builder, expr.get("right")),
1058 loc: exprLoc,
1059 });
1060 return {
@@ -1072,7 +1078,7 @@ function lowerExpression(
1078 builder.push({
1079 id: makeInstructionId(0),
1080 lvalue: { kind: InstructionKind.Reassign, place: { ...leftPlace } },
1075 - value: lowerExpressionToPlace(builder, expr.get("left")),
1081 + value: lowerExpressionToTemporary(builder, expr.get("left")),
1082 loc: exprLoc,
1083 });
1084 builder.terminateWithContinuation(
@@ -1093,7 +1099,7 @@ function lowerExpression(
1099
1100 if (builder.currentBlockKind() === "value") {
1101 // try lowering the RHS in case it also contains errors
1096 - lowerExpressionToPlace(builder, expr.get("right"));
1102 + lowerExpressionToTemporary(builder, expr.get("right"));
1103 builder.errors.push({
1104 reason: `(BuildHIR::lowerExpression) Handle AssignmentExpression within a LogicalExpression or ConditionalExpression`,
1105 severity: ErrorSeverity.Todo,
@@ -1109,7 +1115,8 @@ function lowerExpression(
1115 left.node.loc ?? GeneratedSource,
1116 InstructionKind.Reassign,
1117 left,
1112 - lowerExpression(builder, expr.get("right"))
1118 + // NOTE: it's okay not to lower to a temporary here because this is the entire RHS value, not a single operand
1119 + lowerExpressionToPlace(builder, expr.get("right"))
1120 );
1121 }
1122
@@ -1130,7 +1137,7 @@ function lowerExpression(
1137 const binaryOperator = operators[operator];
1138 if (binaryOperator == null) {
1139 builder.errors.push({
1133 - reason: `(BuildHIR::lowerExpression) Handle ${operator} operaators in AssignmentExpression`,
1140 + reason: `(BuildHIR::lowerExpression) Handle ${operator} operators in AssignmentExpression`,
1141 severity: ErrorSeverity.Todo,
1142 nodePath: expr.get("operator"),
1143 });
@@ -1141,8 +1148,8 @@ function lowerExpression(
1148 switch (leftNode.type) {
1149 case "Identifier": {
1150 const leftExpr = left as NodePath<t.Identifier>;
1144 - const place = lowerExpressionToPlace(builder, leftExpr);
1145 - const right = lowerExpressionToPlace(builder, expr.get("right"));
1151 + const place = lowerIdentifier(builder, leftExpr);
1152 + const right = lowerExpressionToTemporary(builder, expr.get("right"));
1153 builder.push({
1154 id: makeInstructionId(0),
1155 lvalue: { place: { ...place }, kind: InstructionKind.Reassign },
@@ -1191,7 +1198,7 @@ function lowerExpression(
1198 kind: "BinaryExpression",
1199 operator: binaryOperator,
1200 left: { ...previousValuePlace },
1194 - right: lowerExpressionToPlace(builder, expr.get("right")),
1201 + right: lowerExpressionToTemporary(builder, expr.get("right")),
1202 loc: leftExpr.node.loc ?? GeneratedSource,
1203 },
1204 loc: leftExpr.node.loc ?? GeneratedSource,
@@ -1252,7 +1259,7 @@ function lowerExpression(
1259 let hasError = false;
1260 for (const attribute of opening.get("attributes")) {
1261 if (attribute.isJSXSpreadAttribute()) {
1255 - const argument = lowerExpressionToPlace(
1262 + const argument = lowerExpressionToTemporary(
1263 builder,
1264 attribute.get("argument")
1265 );
@@ -1281,7 +1288,7 @@ function lowerExpression(
1288 const valueExpr = attribute.get("value");
1289 let value;
1290 if (valueExpr.isJSXElement() || valueExpr.isStringLiteral()) {
1284 - value = lowerExpressionToPlace(builder, valueExpr);
1291 + value = lowerExpressionToTemporary(builder, valueExpr);
1292 } else {
1293 if (!valueExpr.isJSXExpressionContainer()) {
1294 builder.errors.push({
@@ -1302,7 +1309,7 @@ function lowerExpression(
1309 hasError = true;
1310 continue;
1311 }
1305 - value = lowerExpressionToPlace(builder, expression);
1312 + value = lowerExpressionToTemporary(builder, expression);
1313 }
1314 const prop: string = name.node.name;
1315 props.push({ kind: "JsxAttribute", name: prop, place: value });
@@ -1402,7 +1409,7 @@ function lowerExpression(
1409
1410 return {
1411 kind: "TaggedTemplateExpression",
1405 - tag: lowerExpressionToPlace(builder, expr.get("tag")),
1412 + tag: lowerExpressionToTemporary(builder, expr.get("tag")),
1413 value,
1414 loc: exprLoc,
1415 };
@@ -1431,7 +1438,7 @@ function lowerExpression(
1438 }
1439
1440 const subexprPlaces = subexprs.map((e) =>
1434 - lowerExpressionToPlace(builder, e as NodePath<t.Expression>)
1441 + lowerExpressionToTemporary(builder, e as NodePath<t.Expression>)
1442 );
1443
1444 return {
@@ -1446,7 +1453,7 @@ function lowerExpression(
1453 return {
1454 kind: "UnaryExpression",
1455 operator: expr.node.operator,
1449 - value: lowerExpressionToPlace(builder, expr.get("argument")),
1456 + value: lowerExpressionToTemporary(builder, expr.get("argument")),
1457 loc: exprLoc,
1458 };
1459 }
@@ -1454,7 +1461,7 @@ function lowerExpression(
1461 let expr = exprPath as NodePath<t.TypeCastExpression>;
1462 return {
1463 kind: "TypeCastExpression",
1457 - value: lowerExpressionToPlace(builder, expr.get("expression")),
1464 + value: lowerExpressionToTemporary(builder, expr.get("expression")),
1465 type: expr.get("typeAnnotation").node,
1466 loc: exprLoc,
1467 };
@@ -1493,7 +1500,7 @@ function lowerExpression(
1500 loc: expr.node.loc ?? GeneratedSource,
1501 });
1502 const identifier = argument as NodePath<t.Identifier>;
1496 - const place = lowerExpressionToPlace(builder, identifier);
1503 + const place = lowerIdentifier(builder, identifier);
1504 builder.push({
1505 id: makeInstructionId(0),
1506 lvalue: { place: { ...place }, kind: InstructionKind.Reassign },
@@ -1525,7 +1532,7 @@ function lowerMemberExpression(
1532 ): { object: Place; property: Place | string; value: InstructionValue } {
1533 const exprNode = expr.node;
1534 const exprLoc = exprNode.loc ?? GeneratedSource;
1528 - const object = lowerExpressionToPlace(builder, expr.get("object"));
1535 + const object = lowerExpressionToTemporary(builder, expr.get("object"));
1536 const property = expr.get("property");
1537 if (!expr.node.computed) {
1538 if (!property.isIdentifier()) {
@@ -1565,7 +1572,7 @@ function lowerMemberExpression(
1572 },
1573 };
1574 }
1568 - const propertyPlace = lowerExpressionToPlace(builder, property);
1575 + const propertyPlace = lowerExpressionToTemporary(builder, property);
1576 const value: InstructionValue = {
1577 kind: "ComputedLoad",
1578 object: { ...object },
@@ -1635,7 +1642,7 @@ function lowerJsxElement(
1642 const exprNode = exprPath.node;
1643 const exprLoc = exprNode.loc ?? GeneratedSource;
1644 if (exprPath.isJSXElement() || exprPath.isJSXFragment()) {
1638 - return lowerExpressionToPlace(builder, exprPath);
1645 + return lowerExpressionToTemporary(builder, exprPath);
1646 } else if (exprPath.isJSXExpressionContainer()) {
1647 const expression = exprPath.get("expression");
1648 if (!expression.isExpression()) {
@@ -1657,7 +1664,7 @@ function lowerJsxElement(
1664 });
1665 return { ...place };
1666 }
1660 - return lowerExpressionToPlace(builder, expression);
1667 + return lowerExpressionToTemporary(builder, expression);
1668 } else if (exprPath.isJSXText()) {
1669 const place: Place = buildTemporaryPlace(builder, exprLoc);
1670 builder.push({
@@ -1694,19 +1701,38 @@ function lowerJsxElement(
1701 }
1702 }
1703
1704 +function lowerExpressionToTemporary(
1705 + builder: HIRBuilder,
1706 + exprPath: NodePath<t.Expression>
1707 +): Place {
1708 + const value = lowerExpression(builder, exprPath);
1709 + if (value.kind === "Identifier" && value.identifier.name === null) {
1710 + return value;
1711 + }
1712 + const exprLoc = exprPath.node.loc ?? GeneratedSource;
1713 + const place: Place = buildTemporaryPlace(builder, exprLoc);
1714 + builder.push({
1715 + id: makeInstructionId(0),
1716 + value: value,
1717 + loc: exprLoc,
1718 + lvalue: { place: { ...place }, kind: InstructionKind.Const },
1719 + });
1720 + return place;
1721 +}
1722 +
1723 function lowerExpressionToPlace(
1724 builder: HIRBuilder,
1725 exprPath: NodePath<t.Expression>
1726 ): Place {
1701 - const instr = lowerExpression(builder, exprPath);
1702 - if (instr.kind === "Identifier") {
1703 - return instr;
1727 + const value = lowerExpression(builder, exprPath);
1728 + if (value.kind === "Identifier") {
1729 + return value;
1730 }
1731 const exprLoc = exprPath.node.loc ?? GeneratedSource;
1732 const place: Place = buildTemporaryPlace(builder, exprLoc);
1733 builder.push({
1734 id: makeInstructionId(0),
1709 - value: instr,
1735 + value: value,
1736 loc: exprLoc,
1737 lvalue: { place: { ...place }, kind: InstructionKind.Const },
1738 });
@@ -1837,7 +1863,7 @@ function lowerAssignment(
1863 case "MemberExpression": {
1864 const lvalue = lvaluePath as NodePath<t.MemberExpression>;
1865 const property = lvalue.get("property");
1840 - const object = lowerExpressionToPlace(builder, lvalue.get("object"));
1866 + const object = lowerExpressionToTemporary(builder, lvalue.get("object"));
1867 let valuePlace: Place;
1868 if (value.kind === "Identifier") {
1869 valuePlace = value;
@@ -1876,7 +1902,7 @@ function lowerAssignment(
1902 });
1903 return { kind: "UnsupportedNode", node: lvalueNode, loc };
1904 }
1879 - const propertyPlace = lowerExpressionToPlace(builder, property);
1905 + const propertyPlace = lowerExpressionToTemporary(builder, property);
1906 return {
1907 kind: "ComputedStore",
1908 object,
@@ -2056,7 +2082,7 @@ function gatherCapturedDeps(
2082
2083 path.skip();
2084 capturedIds.add(binding.identifier);
2059 - capturedRefs.add(lowerExpressionToPlace(builder, path));
2085 + capturedRefs.add(lowerExpressionToTemporary(builder, path));
2086 },
2087 });
2088
compiler/forget/src/Inference/AnalyseFunctions.ts
+39 -32
@@ -1,11 +1,12 @@
1 import invariant from "invariant";
2 import {
3 - HIRFunction,
3 + Effect,
4 FunctionExpression,
5 + HIRFunction,
6 Identifier,
7 mergeConsecutiveBlocks,
8 Place,
8 - Effect,
9 + ReactiveScopeDependency,
10 } from "../HIR";
11 import { constantPropagation } from "../Optimization";
12 import { eliminateRedundantPhi, enterSSA } from "../SSA";
@@ -14,48 +15,58 @@ import { logHIRFunction } from "../Utils/logger";
15 import { inferMutableRanges } from "./InferMutableRanges";
16 import inferReferenceEffects from "./InferReferenceEffects";
17
17 -type Dependency = {
18 - place: Place;
19 - path: Array<string> | null;
20 -};
18 +class State {
19 + properties: Map<Identifier, ReactiveScopeDependency> = new Map();
20
22 -function declareProperty(
23 - properties: Map<Identifier, Dependency>,
24 - lvalue: Place,
25 - object: Place,
26 - property: string
27 -): void {
28 - const objectDependency = properties.get(object.identifier);
29 - let nextDependency: Dependency;
30 - if (objectDependency === undefined) {
31 - nextDependency = { place: object, path: [property] };
32 - } else {
33 - nextDependency = {
34 - place: objectDependency.place,
35 - path: [...(objectDependency.path ?? []), property],
21 + declareProperty(lvalue: Place, object: Place, property: string): void {
22 + const objectDependency = this.properties.get(object.identifier);
23 + let nextDependency: ReactiveScopeDependency;
24 + if (objectDependency === undefined) {
25 + nextDependency = { place: object, path: [property] };
26 + } else {
27 + nextDependency = {
28 + place: objectDependency.place,
29 + path: [...(objectDependency.path ?? []), property],
30 + };
31 + }
32 + this.properties.set(lvalue.identifier, nextDependency);
33 + }
34 +
35 + declareTemporary(lvalue: Place, value: Place): void {
36 + const resolved: ReactiveScopeDependency = this.properties.get(
37 + value.identifier
38 + ) ?? {
39 + place: value,
40 + path: null,
41 };
42 + this.properties.set(lvalue.identifier, resolved);
43 }
38 - properties.set(lvalue.identifier, nextDependency);
44 }
45
46 export default function analyseFunctions(func: HIRFunction) {
42 - const properties: Map<Identifier, Dependency> = new Map();
47 + const state = new State();
48
49 for (const [_, block] of func.body.blocks) {
50 for (const instr of block.instructions) {
51 switch (instr.value.kind) {
52 case "FunctionExpression": {
53 lower(instr.value.loweredFunc);
49 - infer(instr.value, properties, func.context);
54 + infer(instr.value, state, func.context);
55 break;
56 }
57 case "PropertyLoad": {
53 - declareProperty(
54 - properties,
58 + state.declareProperty(
59 instr.lvalue.place,
60 instr.value.object,
61 instr.value.property
62 );
63 + break;
64 + }
65 + case "Identifier": {
66 + if (instr.lvalue.place.identifier.name === null) {
67 + state.declareTemporary(instr.lvalue.place, instr.value);
68 + }
69 + break;
70 }
71 }
72 }
@@ -74,11 +85,7 @@ function lower(func: HIRFunction) {
85 logHIRFunction("AnalyseFunction (inner)", func);
86 }
87
77 -function infer(
78 - value: FunctionExpression,
79 - properties: Map<Identifier, Dependency>,
80 - context: Place[]
81 -) {
88 +function infer(value: FunctionExpression, state: State, context: Place[]) {
89 const mutations = new Set(
90 value.loweredFunc.context
91 .filter((dep) => isMutated(dep.identifier))
@@ -89,8 +96,8 @@ function infer(
96 for (const dep of value.dependencies) {
97 let name: string | null = null;
98
92 - if (properties.has(dep.identifier)) {
93 - const receiver = properties.get(dep.identifier)!;
99 + if (state.properties.has(dep.identifier)) {
100 + const receiver = state.properties.get(dep.identifier)!;
101 name = receiver.place.identifier.name;
102 } else {
103 name = dep.identifier.name;
compiler/forget/src/Inference/InferAliasForStores.ts
+6 -25
@@ -11,7 +11,6 @@ import {
11 InstructionId,
12 Place,
13 } from "../HIR/HIR";
14 -import { printInstructionValue } from "../HIR/PrintHIR";
14 import { eachInstructionValueOperand } from "../HIR/visitors";
15 import DisjointSet from "../Utils/DisjointSet";
16
@@ -25,30 +24,12 @@ export function inferAliasForStores(
24 if (lvalue.place.effect !== Effect.Store) {
25 continue;
26 }
28 - switch (value.kind) {
29 - case "ArrayExpression":
30 - case "ObjectExpression":
31 - case "ComputedStore":
32 - case "PropertyStore":
33 - case "FunctionExpression": {
34 - for (const operand of eachInstructionValueOperand(value)) {
35 - if (
36 - operand.effect === Effect.Capture ||
37 - operand.effect === Effect.Store
38 - ) {
39 - maybeAlias(aliases, lvalue.place, operand, instr.id);
40 - }
41 - }
42 - break;
43 - }
44 - default: {
45 - // Effect.Capture & Effect.Store are only used for aliasing
46 - // instructions.
47 - throw new Error(
48 - `Unexpected capture/store instruction: ${printInstructionValue(
49 - value
50 - )}`
51 - );
27 + for (const operand of eachInstructionValueOperand(value)) {
28 + if (
29 + operand.effect === Effect.Capture ||
30 + operand.effect === Effect.Store
31 + ) {
32 + maybeAlias(aliases, lvalue.place, operand, instr.id);
33 }
34 }
35 }
compiler/forget/src/Inference/InferMutableRanges.ts
+25 -6
@@ -5,7 +5,7 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import { HIRFunction } from "../HIR/HIR";
8 +import { HIRFunction, Identifier } from "../HIR/HIR";
9 import { inferAliases } from "./InferAlias";
10 import { inferAliasForStores } from "./InferAliasForStores";
11 import { inferMutableLifetimes } from "./InferMutableLifetimes";
@@ -17,18 +17,22 @@ export function inferMutableRanges(ir: HIRFunction) {
17
18 // Calculate aliases
19 const aliases = inferAliases(ir);
20 - let size = aliases.size;
20 // Eagerly canonicalize so that if nothing changes we can bail out
21 // after a single iteration
23 - aliases.canonicalize();
24 - do {
25 - size = aliases.size;
22 + let prevAliases: Map<Identifier, Identifier> = aliases.canonicalize();
23 + while (true) {
24 // Infer mutable ranges for aliases that are not fields
25 inferMutableRangesForAlias(ir, aliases);
26
27 // Update aliasing information of fields
28 inferAliasForStores(ir, aliases);
31 - } while (aliases.size > size || !aliases.canonicalize());
29 +
30 + const nextAliases = aliases.canonicalize();
31 + if (areEqualMaps(prevAliases, nextAliases)) {
32 + break;
33 + }
34 + prevAliases = nextAliases;
35 + }
36
37 // Re-infer mutable ranges for all values
38 inferMutableLifetimes(ir, true);
@@ -36,3 +40,18 @@ export function inferMutableRanges(ir: HIRFunction) {
40 // Re-infer mutable ranges for aliases
41 inferMutableRangesForAlias(ir, aliases);
42 }
43 +
44 +function areEqualMaps<T>(a: Map<T, T>, b: Map<T, T>): boolean {
45 + if (a.size !== b.size) {
46 + return false;
47 + }
48 + for (const [key, value] of a) {
49 + if (!b.has(key)) {
50 + return false;
51 + }
52 + if (b.get(key) !== value) {
53 + return false;
54 + }
55 + }
56 + return true;
57 +}
compiler/forget/src/Inference/InferMutableRangesForAlias.ts
+13 -13
@@ -5,19 +5,6 @@ export function inferMutableRangesForAlias(
5 fn: HIRFunction,
6 aliases: DisjointSet<Identifier>
7 ) {
8 - for (const [_, block] of fn.body.blocks) {
9 - for (const phi of block.phis) {
10 - const isPhiMutatedAfterCreation: boolean =
11 - phi.id.mutableRange.end >
12 - (block.instructions.at(0)?.id ?? block.terminal.id);
13 - if (isPhiMutatedAfterCreation) {
14 - for (const [, operand] of phi.operands) {
15 - aliases.union([phi.id, operand]);
16 - }
17 - }
18 - }
19 - }
20 -
8 const aliasSets = aliases.buildSets();
9 for (const aliasSet of aliasSets) {
10 // Update mutableRange.end only if the identifiers have actually been
@@ -44,4 +31,17 @@ export function inferMutableRangesForAlias(
31 }
32 }
33 }
34 +
35 + for (const [_, block] of fn.body.blocks) {
36 + for (const phi of block.phis) {
37 + const isPhiMutatedAfterCreation: boolean =
38 + phi.id.mutableRange.end >
39 + (block.instructions.at(0)?.id ?? block.terminal.id);
40 + if (isPhiMutatedAfterCreation) {
41 + for (const [, operand] of phi.operands) {
42 + aliases.union([phi.id, operand]);
43 + }
44 + }
45 + }
46 + }
47 }
compiler/forget/src/ReactiveScopes/InferReactiveIdentifiers.ts
+16 -10
@@ -28,18 +28,20 @@ class Visitor extends ReactiveFunctionVisitor<IdentifierReactivity> {
28 ) {
29 this.traverseInstruction(instr, reactivityMap);
30 const lval = instr.lvalue;
31 - if (lval == null || reactivityMap.get(lval.place.identifier.id) === true) {
31 + if (lval == null) {
32 return;
33 }
34 const { value } = instr;
35 - let hasReactiveInput = false;
36 - for (const operand of eachReactiveValueOperand(value)) {
37 - // We currently treat free variables (from module or global scope) as
38 - // non-reactive. We may later want type information about specific
39 - // free variables, or a toggle `treatFreeVarsAsReactive`.
40 - if (reactivityMap.get(operand.identifier.id)) {
41 - hasReactiveInput = true;
42 - break;
35 + let hasReactiveInput = reactivityMap.get(lval.place.identifier.id) === true;
36 + if (!hasReactiveInput && value.kind !== "LoadGlobal") {
37 + for (const operand of eachReactiveValueOperand(value)) {
38 + // We currently treat free variables (from module or global scope) as
39 + // non-reactive. We may later want type information about specific
40 + // free variables, or a toggle `treatFreeVarsAsReactive`.
41 + if (reactivityMap.get(operand.identifier.id)) {
42 + hasReactiveInput = true;
43 + break;
44 + }
45 }
46 }
47 if (
@@ -136,7 +138,11 @@ export function inferReactiveIdentifiers(
138 for (const param of fn.params) {
139 reactivityMap.set(param.identifier.id, true);
140 }
139 - visitReactiveFunction(fn, visitor, reactivityMap);
141 + let size: number;
142 + do {
143 + size = reactivityMap.size;
144 + visitReactiveFunction(fn, visitor, reactivityMap);
145 + } while (reactivityMap.size > size);
146
147 const result = new Set<IdentifierId>();
148 reactivityMap.forEach((isReactive, id) => {
compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts
+23 -6
@@ -71,6 +71,7 @@ class Context {
71 // This helps with.. temporaries that are created only for property loads
72 // but can be generalized to all non-allocating temporaries
73 #properties: Map<Identifier, ReactiveScopeDependency> = new Map();
74 + #temporaries: Map<Identifier, Place> = new Map();
75 #scopes: Scopes = [];
76
77 enter(scope: ReactiveScope, fn: () => void): Set<ReactiveScopeDependency> {
@@ -97,11 +98,16 @@ class Context {
98 this.#reassignments.set(identifier, decl);
99 }
100
101 + declareTemporary(lvalue: Place, value: Place): void {
102 + this.#temporaries.set(lvalue.identifier, value);
103 + }
104 +
105 declareProperty(lvalue: Place, object: Place, property: string): void {
101 - const objectDependency = this.#properties.get(object.identifier);
106 + const resolvedObject = this.#temporaries.get(object.identifier) ?? object;
107 + const objectDependency = this.#properties.get(resolvedObject.identifier);
108 let nextDependency: ReactiveScopeDependency;
109 if (objectDependency === undefined) {
104 - nextDependency = { place: object, path: [property] };
110 + nextDependency = { place: resolvedObject, path: [property] };
111 } else {
112 nextDependency = {
113 place: objectDependency.place,
@@ -120,14 +126,16 @@ class Context {
126 }
127
128 visitOperand(place: Place): void {
123 - this.visitDependency({ place, path: null });
129 + const resolved = this.#temporaries.get(place.identifier) ?? place;
130 + this.visitDependency({ place: resolved, path: null });
131 }
132
133 visitProperty(object: Place, property: string): void {
127 - const objectDependency = this.#properties.get(object.identifier);
134 + const resolvedObject = this.#temporaries.get(object.identifier) ?? object;
135 + const objectDependency = this.#properties.get(resolvedObject.identifier);
136 let nextDependency: ReactiveScopeDependency;
137 if (objectDependency === undefined) {
130 - nextDependency = { place: object, path: [property] };
138 + nextDependency = { place: resolvedObject, path: [property] };
139 } else {
140 nextDependency = {
141 place: objectDependency.place,
@@ -362,7 +370,16 @@ function visitInstructionValue(
370 value: ReactiveValue,
371 lvalue: LValue | null
372 ): void {
365 - if (value.kind === "PropertyLoad") {
373 + if (value.kind === "Identifier" && lvalue !== null) {
374 + if (
375 + value.identifier.name !== null &&
376 + lvalue.place.identifier.name === null
377 + ) {
378 + context.declareTemporary(lvalue.place, value);
379 + } else {
380 + context.visitOperand(value);
381 + }
382 + } else if (value.kind === "PropertyLoad") {
383 if (lvalue !== null) {
384 context.declareProperty(lvalue.place, value.object, value.property);
385 } else {
compiler/forget/src/Utils/DisjointSet.ts
+6 -9
@@ -75,19 +75,16 @@ export default class DisjointSet<T> {
75
76 /**
77 * Forces the set into canonical form, ie with all items pointing directly to
78 - * their root. Returns true if the set was already in canonical form, false
79 - * otherwise.
78 + * their root, and returns a Map representing the mapping of items to their roots.
79 */
81 - canonicalize(): boolean {
82 - let isCanonical = true;
80 + canonicalize(): Map<T, T> {
81 + const entries = new Map<T, T>();
82 for (const item of this.#entries.keys()) {
83 const parent = this.#entries.get(item)!;
85 - const root = this.find(item);
86 - if (parent !== root) {
87 - isCanonical = false;
88 - }
84 + const root = this.find(item)!;
85 + entries.set(item, root);
86 }
90 - return isCanonical;
87 + return entries;
88 }
89
90 /**
compiler/forget/src/__tests__/fixtures/hir/_bug.capturing-func-simple-alias.expect.md
+3 -3
@@ -28,12 +28,12 @@ function component(a) {
28 } else {
29 x = $[1];
30 }
31 - const y = undefined;
31 +
32 (function () {
33 y = x;
34 })();
35 - mutate(y);
36 - return y;
35 + mutate(undefined);
36 + return undefined;
37 }
38
39 ```
compiler/forget/src/__tests__/fixtures/hir/_bug_expression-with-assignment.js deleted
-6
@@ -1,6 +0,0 @@
1 -function f() {
2 - let x = 1;
3 - // BUG: `x` has different values within this expression. Currently, the
4 - // assignment is evaluated too early.
5 - return x + (x = 2) + x;
6 -}
compiler/forget/src/__tests__/fixtures/hir/assignment-expression-computed.expect.md
+5 -4
@@ -21,10 +21,11 @@ function Component(props) {
21 let x;
22 if (c_0) {
23 x = [props.x];
24 - const index = 0;
25 - x[index] = x[index] * 2;
26 - const t0 = "0";
27 - x[t0] = x[t0] + 3;
24 +
25 + const t0 = 0;
26 + x[t0] = x[t0] * 2;
27 + const t1 = "0";
28 + x[t1] = x[t1] + 3;
29 $[0] = props.x;
30 $[1] = x;
31 } else {
compiler/forget/src/__tests__/fixtures/hir/assignment-in-nested-if.expect.md
+5 -3
@@ -24,12 +24,14 @@ function useBar(props) {
24 let z = undefined;
25 if (props.a) {
26 if (props.b) {
27 + let t0;
28 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
28 - z = baz();
29 - $[0] = z;
29 + t0 = baz();
30 + $[0] = t0;
31 } else {
31 - z = $[0];
32 + t0 = $[0];
33 }
34 + z = t0;
35 }
36 }
37 return z;
compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-computed-mutate.expect.md
+3 -3
@@ -4,7 +4,7 @@
4 ```javascript
5 function component(a) {
6 let x = { a };
7 - let y;
7 + let y = {};
8 (function () {
9 y["x"] = x;
10 })();
@@ -23,16 +23,16 @@ function component(a) {
23 let y;
24 if (c_0) {
25 const x = { a: a };
26 - y = undefined;
26 + y = {};
27 (function () {
28 y["x"] = x;
29 })();
30 + mutate(y);
31 $[0] = a;
32 $[1] = y;
33 } else {
34 y = $[1];
35 }
35 - mutate(y);
36 return y;
37 }
38
compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-computed-mutate.js
+1 -1
@@ -1,6 +1,6 @@
1 function component(a) {
2 let x = { a };
3 - let y;
3 + let y = {};
4 (function () {
5 y["x"] = x;
6 })();
compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-mutate.expect.md
+3 -3
@@ -4,7 +4,7 @@
4 ```javascript
5 function component(a) {
6 let x = { a };
7 - let y;
7 + let y = {};
8 (function () {
9 y.x = x;
10 })();
@@ -23,16 +23,16 @@ function component(a) {
23 let y;
24 if (c_0) {
25 const x = { a: a };
26 - y = undefined;
26 + y = {};
27 (function () {
28 y.x = x;
29 })();
30 + mutate(y);
31 $[0] = a;
32 $[1] = y;
33 } else {
34 y = $[1];
35 }
35 - mutate(y);
36 return y;
37 }
38
compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-mutate.js
+1 -1
@@ -1,6 +1,6 @@
1 function component(a) {
2 let x = { a };
3 - let y;
3 + let y = {};
4 (function () {
5 y.x = x;
6 })();
compiler/forget/src/__tests__/fixtures/hir/capturing-function-member-expr-call.expect.md
+2 -2
@@ -19,9 +19,9 @@ function component({ mutator }) {
19 ## Code
20
21 ```javascript
22 -function component(t15) {
22 +function component(t23) {
23 const $ = React.unstable_useMemoCache(7);
24 - const t0 = t15;
24 + const t0 = t23;
25 const mutator = t0.mutator;
26 const c_0 = $[0] !== mutator;
27 let poke;
compiler/forget/src/__tests__/fixtures/hir/capturing-function-within-block.expect.md
+5 -4
@@ -30,16 +30,17 @@ function component(a) {
30 z = $[1];
31 }
32 const c_2 = $[2] !== z;
33 - let x;
33 + let t0;
34 if (c_2) {
35 - x = function () {
35 + t0 = function () {
36 z;
37 };
38 $[2] = z;
39 - $[3] = x;
39 + $[3] = t0;
40 } else {
41 - x = $[3];
41 + t0 = $[3];
42 }
43 + const x = t0;
44 return x;
45 }
46
compiler/forget/src/__tests__/fixtures/hir/constant-propagation.expect.md
+1 -3
@@ -28,9 +28,7 @@ function foo() {
28 ```javascript
29 function foo() {
30 console.log("foo");
31 -
32 - const j = -6;
33 - return j;
31 + return -6;
32 }
33
34 ```
compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment-dynamic.expect.md new
+21
@@ -0,0 +1,21 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function f(y) {
6 + let x = y;
7 + return x + (x = 2) + x;
8 +}
9 +
10 +```
11 +
12 +## Code
13 +
14 +```javascript
15 +function f(y) {
16 + const x = y;
17 + return x + 2 + 2;
18 +}
19 +
20 +```
21 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment-dynamic.js new
+4
@@ -0,0 +1,4 @@
1 +function f(y) {
2 + let x = y;
3 + return x + (x = 2) + x;
4 +}
compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment.expect.md renamed
+1 -3
@@ -4,8 +4,6 @@
4 ```javascript
5 function f() {
6 let x = 1;
7 - // BUG: `x` has different values within this expression. Currently, the
8 - // assignment is evaluated too early.
7 return x + (x = 2) + x;
8 }
9
@@ -15,7 +13,7 @@ function f() {
13
14 ```javascript
15 function f() {
18 - return 6;
16 + return 5;
17 }
18
19 ```
compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment.js new
+4
@@ -0,0 +1,4 @@
1 +function f() {
2 + let x = 1;
3 + return x + (x = 2) + x;
4 +}
compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.expect.md
+5 -3
@@ -33,12 +33,14 @@ function Component(props) {
33 if (cond) {
34 a = x;
35 } else {
36 + let t0;
37 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
37 - a = [];
38 - $[0] = a;
38 + t0 = [];
39 + $[0] = t0;
40 } else {
40 - a = $[0];
41 + t0 = $[0];
42 }
43 + a = t0;
44 }
45
46 useFreeze(a);
compiler/forget/src/__tests__/fixtures/hir/obj-literal-cached-in-if-else.expect.md
+10 -6
@@ -23,22 +23,26 @@ function foo(a, b, c, d) {
23 let x = undefined;
24 if (someVal) {
25 const c_0 = $[0] !== b;
26 + let t0;
27 if (c_0) {
27 - x = { b: b };
28 + t0 = { b: b };
29 $[0] = b;
29 - $[1] = x;
30 + $[1] = t0;
31 } else {
31 - x = $[1];
32 + t0 = $[1];
33 }
34 + x = t0;
35 } else {
36 const c_2 = $[2] !== c;
37 + let t1;
38 if (c_2) {
36 - x = { c: c };
39 + t1 = { c: c };
40 $[2] = c;
38 - $[3] = x;
41 + $[3] = t1;
42 } else {
40 - x = $[3];
43 + t1 = $[3];
44 }
45 + x = t1;
46 }
47 return x;
48 }
compiler/forget/src/__tests__/fixtures/hir/object-pattern-params.expect.md
+3 -3
@@ -13,10 +13,10 @@ function component({ a, b }) {
13 ## Code
14
15 ```javascript
16 -function component(t8) {
16 +function component(t12) {
17 const $ = React.unstable_useMemoCache(7);
18 - const a = t8.a;
19 - const b = t8.b;
18 + const a = t12.a;
19 + const b = t12.b;
20 const c_0 = $[0] !== a;
21 let y;
22 if (c_0) {
compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md
+10 -8
@@ -36,27 +36,29 @@ function Component(props) {
36 }
37 const y = x;
38 if (props.p1) {
39 + let t0;
40 if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
40 - x = [];
41 - $[2] = x;
41 + t0 = [];
42 + $[2] = t0;
43 } else {
43 - x = $[2];
44 + t0 = $[2];
45 }
46 + x = t0;
47 }
48
49 y.push(props.p2);
50 const c_3 = $[3] !== x;
51 const c_4 = $[4] !== y;
50 - let t0;
52 + let t1;
53 if (c_3 || c_4) {
52 - t0 = <Component x={x} y={y}></Component>;
54 + t1 = <Component x={x} y={y}></Component>;
55 $[3] = x;
56 $[4] = y;
55 - $[5] = t0;
57 + $[5] = t1;
58 } else {
57 - t0 = $[5];
59 + t1 = $[5];
60 }
59 - return t0;
61 + return t1;
62 }
63
64 ```
compiler/forget/src/__tests__/fixtures/hir/reassignment.expect.md
+10 -8
@@ -30,12 +30,14 @@ function Component(props) {
30 x = [];
31 x.push(props.p0);
32 y = x;
33 + let t0;
34 if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
34 - x = [];
35 - $[4] = x;
35 + t0 = [];
36 + $[4] = t0;
37 } else {
37 - x = $[4];
38 + t0 = $[4];
39 }
40 + x = t0;
41
42 y.push(props.p1);
43 $[0] = props.p0;
@@ -48,16 +50,16 @@ function Component(props) {
50 }
51 const c_5 = $[5] !== x;
52 const c_6 = $[6] !== y;
51 - let t0;
53 + let t1;
54 if (c_5 || c_6) {
53 - t0 = <Component x={x} y={y}></Component>;
55 + t1 = <Component x={x} y={y}></Component>;
56 $[5] = x;
57 $[6] = y;
56 - $[7] = t0;
58 + $[7] = t1;
59 } else {
58 - t0 = $[7];
60 + t1 = $[7];
61 }
60 - return t0;
62 + return t1;
63 }
64
65 ```
compiler/forget/src/__tests__/fixtures/hir/sequence-expression.expect.md
+12 -5
@@ -18,18 +18,25 @@ function foo() {}
18
19 ```javascript
20 function sequence(props) {
21 - const $ = React.unstable_useMemoCache(1);
21 + const $ = React.unstable_useMemoCache(2);
22 Math.max(1, 2);
23 - let x;
23 + let t0;
24 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
25 - x = foo();
25 + t0 = foo();
26 + $[0] = t0;
27 + } else {
28 + t0 = $[0];
29 + }
30 + let x;
31 + if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
32 + x = t0;
33 while ((foo(), true)) {
34 foo();
35 x = 2;
36 }
30 - $[0] = x;
37 + $[1] = x;
38 } else {
32 - x = $[0];
39 + x = $[1];
40 }
41 return x;
42 }
compiler/forget/src/__tests__/fixtures/hir/ssa-arrayexpression.expect.md
+1 -3
@@ -16,11 +16,9 @@ function Component(props) {
16 ```javascript
17 function Component(props) {
18 const $ = React.unstable_useMemoCache(1);
19 - const a = 1;
20 - const b = 2;
19 let x;
20 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
23 - x = [a, b];
21 + x = [1, 2];
22 $[0] = x;
23 } else {
24 x = $[0];
compiler/forget/src/__tests__/fixtures/hir/ssa-nested-loops-no-reassign.expect.md
+1 -2
@@ -22,13 +22,12 @@ function foo(a, b, c) {
22 ```javascript
23 // @xonly
24 function foo(a, b, c) {
25 - const x = 0;
25 while (a) {
26 while (b) {
27 while (c) {}
28 }
29 }
31 - return x;
30 + return 0;
31 }
32
33 ```
compiler/forget/src/__tests__/fixtures/hir/ssa-objectexpression.expect.md
+1 -3
@@ -16,11 +16,9 @@ function Component(props) {
16 ```javascript
17 function Component(props) {
18 const $ = React.unstable_useMemoCache(1);
19 - const a = 1;
20 - const b = 2;
19 let x;
20 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
23 - x = { a: a, b: b };
21 + x = { a: 1, b: 2 };
22 $[0] = x;
23 } else {
24 x = $[0];
compiler/forget/src/__tests__/fixtures/hir/ssa-shadowing.expect.md
+1 -2
@@ -25,8 +25,7 @@ function log() {}
25 function Foo(cond) {
26 let str = "";
27 if (cond) {
28 - const str_0 = "other test";
29 - log(str_0);
28 + log("other test");
29 } else {
30 str = "fallthrough test";
31 }
compiler/forget/src/__tests__/fixtures/hir/ssa-switch.expect.md
+1 -2
@@ -28,8 +28,7 @@ function foo() {
28
29 ```javascript
30 function foo() {
31 - const x = 1;
32 - bb1: switch (x) {
31 + bb1: switch (1) {
32 case 1: {
33 break bb1;
34 }
compiler/forget/src/__tests__/fixtures/hir/ssa-while-no-reassign.expect.md
+1 -2
@@ -17,9 +17,8 @@ function foo() {
17
18 ```javascript
19 function foo() {
20 - const x = 1;
20 while (true) {}
22 - return x;
21 + return 1;
22 }
23
24 ```
compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md
+10 -8
@@ -46,12 +46,14 @@ function Component(props) {
46 }
47 case true: {
48 x.push(props.p2);
49 + let t0;
50 if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
50 - y = [];
51 - $[4] = y;
51 + t0 = [];
52 + $[4] = t0;
53 } else {
53 - y = $[4];
54 + t0 = $[4];
55 }
56 + y = t0;
57 break bb1;
58 }
59 default: {
@@ -81,16 +83,16 @@ function Component(props) {
83 y.push(props.p4);
84 const c_7 = $[7] !== y;
85 const c_8 = $[8] !== child;
84 - let t0;
86 + let t1;
87 if (c_7 || c_8) {
86 - t0 = <Component data={y}>{child}</Component>;
88 + t1 = <Component data={y}>{child}</Component>;
89 $[7] = y;
90 $[8] = child;
89 - $[9] = t0;
91 + $[9] = t1;
92 } else {
91 - t0 = $[9];
93 + t1 = $[9];
94 }
93 - return t0;
95 + return t1;
96 }
97
98 ```
compiler/forget/src/__tests__/fixtures/hir/type-test-primitive.expect.md
+1 -2
@@ -15,8 +15,7 @@ function component() {
15
16 ```javascript
17 function component() {
18 - const y = 2;
19 - return y;
18 + return 2;
19 }
20
21 ```
compiler/forget/src/__tests__/fixtures/hir/unconditional-break-label.expect.md
+1 -2
@@ -17,8 +17,7 @@ function foo(a) {
17
18 ```javascript
19 function foo(a) {
20 - const x = 1;
21 - return a + x;
20 + return a + 1;
21 }
22
23 ```