From 1dfaf8a94bf5ab3f7fe2afac6f31ac75ce884fed Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Wed, 22 Feb 2023 15:53:23 -0800 Subject: [PATCH] Lower all operands to temporaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- compiler/forget/src/HIR/BuildHIR.ts | 124 +++++++++++------- .../forget/src/Inference/AnalyseFunctions.ts | 71 +++++----- .../src/Inference/InferAliasForStores.ts | 31 +---- .../src/Inference/InferMutableRanges.ts | 31 ++++- .../Inference/InferMutableRangesForAlias.ts | 26 ++-- .../InferReactiveIdentifiers.ts | 26 ++-- .../PropagateScopeDependencies.ts | 29 +++- compiler/forget/src/Utils/DisjointSet.ts | 15 +-- ..._bug.capturing-func-simple-alias.expect.md | 6 +- .../hir/_bug_expression-with-assignment.js | 6 - .../assignment-expression-computed.expect.md | 9 +- .../hir/assignment-in-nested-if.expect.md | 8 +- ...uring-func-alias-computed-mutate.expect.md | 6 +- .../capturing-func-alias-computed-mutate.js | 2 +- .../hir/capturing-func-alias-mutate.expect.md | 6 +- .../hir/capturing-func-alias-mutate.js | 2 +- ...turing-function-member-expr-call.expect.md | 4 +- .../capturing-function-within-block.expect.md | 9 +- .../hir/constant-propagation.expect.md | 4 +- ...pression-with-assignment-dynamic.expect.md | 21 +++ .../hir/expression-with-assignment-dynamic.js | 4 + ...d => expression-with-assignment.expect.md} | 4 +- .../hir/expression-with-assignment.js | 4 + ...reeze-possibly-mutable-arguments.expect.md | 8 +- .../obj-literal-cached-in-if-else.expect.md | 16 ++- .../hir/object-pattern-params.expect.md | 6 +- .../hir/reassignment-conditional.expect.md | 18 +-- .../fixtures/hir/reassignment.expect.md | 18 +-- .../hir/sequence-expression.expect.md | 17 ++- .../hir/ssa-arrayexpression.expect.md | 4 +- .../ssa-nested-loops-no-reassign.expect.md | 3 +- .../hir/ssa-objectexpression.expect.md | 4 +- .../fixtures/hir/ssa-shadowing.expect.md | 3 +- .../fixtures/hir/ssa-switch.expect.md | 3 +- .../hir/ssa-while-no-reassign.expect.md | 3 +- .../hir/switch-non-final-default.expect.md | 18 +-- .../hir/type-test-primitive.expect.md | 3 +- .../hir/unconditional-break-label.expect.md | 3 +- 38 files changed, 330 insertions(+), 245 deletions(-) delete mode 100644 compiler/forget/src/__tests__/fixtures/hir/_bug_expression-with-assignment.js create mode 100644 compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment-dynamic.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment-dynamic.js rename compiler/forget/src/__tests__/fixtures/hir/{_bug_expression-with-assignment.expect.md => expression-with-assignment.expect.md} (52%) create mode 100644 compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment.js diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index 9545271fce..5e88b4c4c4 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -127,7 +127,7 @@ export function lower( const terminal: ReturnTerminal = { kind: "return", loc: GeneratedSource, - value: lowerExpressionToPlace(builder, body), + value: lowerExpressionToTemporary(builder, body), id: makeInstructionId(0), }; builder.terminateWithContinuation(terminal, fallthrough); @@ -169,7 +169,7 @@ function lowerStatement( switch (stmtNode.type) { case "ThrowStatement": { const stmt = stmtPath as NodePath; - const value = lowerExpressionToPlace(builder, stmt.get("argument")); + const value = lowerExpressionToTemporary(builder, stmt.get("argument")); const terminal: ThrowTerminal = { kind: "throw", value, @@ -183,7 +183,10 @@ function lowerStatement( const argument = stmt.get("argument"); const value = argument.node != null - ? lowerExpressionToPlace(builder, argument as NodePath) + ? lowerExpressionToTemporary( + builder, + argument as NodePath + ) : null; const terminal: ReturnTerminal = { kind: "return", @@ -225,7 +228,7 @@ function lowerStatement( // If there is no else clause, use the continuation directly alternateBlock = continuationBlock.id; } - const test = lowerExpressionToPlace(builder, stmt.get("test")); + const test = lowerExpressionToTemporary(builder, stmt.get("test")); const terminal: IfTerminal = { kind: "if", test, @@ -353,7 +356,7 @@ function lowerStatement( builder.terminateWithContinuation( { kind: "branch", - test: lowerExpressionToPlace( + test: lowerExpressionToTemporary( builder, test as NodePath ), @@ -409,7 +412,7 @@ function lowerStatement( * The conditional block is empty and exists solely as conditional for * (re)entering or exiting the loop */ - const test = lowerExpressionToPlace(builder, stmt.get("test")); + const test = lowerExpressionToTemporary(builder, stmt.get("test")); const terminal: BranchTerminal = { kind: "branch", test, @@ -525,7 +528,7 @@ function lowerStatement( }); } } - test = lowerExpressionToPlace( + test = lowerExpressionToTemporary( builder, testExpr as NodePath ); @@ -549,7 +552,10 @@ function lowerStatement( cases.push({ test: null, block: continuationBlock.id }); } - const test = lowerExpressionToPlace(builder, stmt.get("discriminant")); + const test = lowerExpressionToTemporary( + builder, + stmt.get("discriminant") + ); builder.terminateWithContinuation( { kind: "switch", @@ -772,7 +778,7 @@ function lowerExpression( hasError = true; continue; } - const value = lowerExpressionToPlace(builder, valuePath); + const value = lowerExpressionToTemporary(builder, valuePath); properties.set(key.name, value); } return hasError @@ -798,7 +804,7 @@ function lowerExpression( continue; } elements.push( - lowerExpressionToPlace(builder, element as NodePath) + lowerExpressionToTemporary(builder, element as NodePath) ); } return hasError @@ -820,7 +826,7 @@ function lowerExpression( }); return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }; } - const callee = lowerExpressionToPlace(builder, calleePath); + const callee = lowerExpressionToTemporary(builder, calleePath); let args: Place[] = []; let hasError = false; for (const argPath of expr.get("arguments")) { @@ -833,7 +839,7 @@ function lowerExpression( hasError = true; continue; } - args.push(lowerExpressionToPlace(builder, argPath)); + args.push(lowerExpressionToTemporary(builder, argPath)); } return hasError @@ -873,7 +879,7 @@ function lowerExpression( hasError = true; continue; } - args.push(lowerExpressionToPlace(builder, argPath)); + args.push(lowerExpressionToTemporary(builder, argPath)); } if (typeof property === "string") { return { @@ -893,7 +899,7 @@ function lowerExpression( }; } } else { - const callee = lowerExpressionToPlace(builder, calleePath); + const callee = lowerExpressionToTemporary(builder, calleePath); let args: Place[] = []; for (const argPath of expr.get("arguments")) { if (!argPath.isExpression()) { @@ -905,7 +911,7 @@ function lowerExpression( hasError = true; continue; } - args.push(lowerExpressionToPlace(builder, argPath)); + args.push(lowerExpressionToTemporary(builder, argPath)); } return hasError ? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc } @@ -928,8 +934,8 @@ function lowerExpression( }); return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }; } - const left = lowerExpressionToPlace(builder, leftPath); - const right = lowerExpressionToPlace(builder, expr.get("right")); + const left = lowerExpressionToTemporary(builder, leftPath); + const right = lowerExpressionToTemporary(builder, expr.get("right")); const operator = expr.node.operator; return { kind: "BinaryExpression", @@ -945,7 +951,7 @@ function lowerExpression( let last: Place | null = null; for (const item of expr.get("expressions")) { - last = lowerExpressionToPlace(builder, item); + last = lowerExpressionToTemporary(builder, item); } if (last === null) { builder.errors.push({ @@ -971,7 +977,7 @@ function lowerExpression( builder.push({ id: makeInstructionId(0), lvalue: { kind: InstructionKind.Reassign, place: { ...place } }, - value: lowerExpressionToPlace(builder, expr.get("consequent")), + value: lowerExpressionToTemporary(builder, expr.get("consequent")), loc: exprLoc, }); return { @@ -986,7 +992,7 @@ function lowerExpression( builder.push({ id: makeInstructionId(0), lvalue: { kind: InstructionKind.Reassign, place: { ...place } }, - value: lowerExpressionToPlace(builder, expr.get("alternate")), + value: lowerExpressionToTemporary(builder, expr.get("alternate")), loc: exprLoc, }); return { @@ -1007,7 +1013,7 @@ function lowerExpression( }, testBlock ); - const testPlace = lowerExpressionToPlace(builder, expr.get("test")); + const testPlace = lowerExpressionToTemporary(builder, expr.get("test")); builder.terminateWithContinuation( { kind: "branch", @@ -1048,7 +1054,7 @@ function lowerExpression( builder.push({ id: makeInstructionId(0), lvalue: { kind: InstructionKind.Reassign, place: { ...place } }, - value: lowerExpressionToPlace(builder, expr.get("right")), + value: lowerExpressionToTemporary(builder, expr.get("right")), loc: exprLoc, }); return { @@ -1072,7 +1078,7 @@ function lowerExpression( builder.push({ id: makeInstructionId(0), lvalue: { kind: InstructionKind.Reassign, place: { ...leftPlace } }, - value: lowerExpressionToPlace(builder, expr.get("left")), + value: lowerExpressionToTemporary(builder, expr.get("left")), loc: exprLoc, }); builder.terminateWithContinuation( @@ -1093,7 +1099,7 @@ function lowerExpression( if (builder.currentBlockKind() === "value") { // try lowering the RHS in case it also contains errors - lowerExpressionToPlace(builder, expr.get("right")); + lowerExpressionToTemporary(builder, expr.get("right")); builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle AssignmentExpression within a LogicalExpression or ConditionalExpression`, severity: ErrorSeverity.Todo, @@ -1109,7 +1115,8 @@ function lowerExpression( left.node.loc ?? GeneratedSource, InstructionKind.Reassign, left, - lowerExpression(builder, expr.get("right")) + // NOTE: it's okay not to lower to a temporary here because this is the entire RHS value, not a single operand + lowerExpressionToPlace(builder, expr.get("right")) ); } @@ -1130,7 +1137,7 @@ function lowerExpression( const binaryOperator = operators[operator]; if (binaryOperator == null) { builder.errors.push({ - reason: `(BuildHIR::lowerExpression) Handle ${operator} operaators in AssignmentExpression`, + reason: `(BuildHIR::lowerExpression) Handle ${operator} operators in AssignmentExpression`, severity: ErrorSeverity.Todo, nodePath: expr.get("operator"), }); @@ -1141,8 +1148,8 @@ function lowerExpression( switch (leftNode.type) { case "Identifier": { const leftExpr = left as NodePath; - const place = lowerExpressionToPlace(builder, leftExpr); - const right = lowerExpressionToPlace(builder, expr.get("right")); + const place = lowerIdentifier(builder, leftExpr); + const right = lowerExpressionToTemporary(builder, expr.get("right")); builder.push({ id: makeInstructionId(0), lvalue: { place: { ...place }, kind: InstructionKind.Reassign }, @@ -1191,7 +1198,7 @@ function lowerExpression( kind: "BinaryExpression", operator: binaryOperator, left: { ...previousValuePlace }, - right: lowerExpressionToPlace(builder, expr.get("right")), + right: lowerExpressionToTemporary(builder, expr.get("right")), loc: leftExpr.node.loc ?? GeneratedSource, }, loc: leftExpr.node.loc ?? GeneratedSource, @@ -1252,7 +1259,7 @@ function lowerExpression( let hasError = false; for (const attribute of opening.get("attributes")) { if (attribute.isJSXSpreadAttribute()) { - const argument = lowerExpressionToPlace( + const argument = lowerExpressionToTemporary( builder, attribute.get("argument") ); @@ -1281,7 +1288,7 @@ function lowerExpression( const valueExpr = attribute.get("value"); let value; if (valueExpr.isJSXElement() || valueExpr.isStringLiteral()) { - value = lowerExpressionToPlace(builder, valueExpr); + value = lowerExpressionToTemporary(builder, valueExpr); } else { if (!valueExpr.isJSXExpressionContainer()) { builder.errors.push({ @@ -1302,7 +1309,7 @@ function lowerExpression( hasError = true; continue; } - value = lowerExpressionToPlace(builder, expression); + value = lowerExpressionToTemporary(builder, expression); } const prop: string = name.node.name; props.push({ kind: "JsxAttribute", name: prop, place: value }); @@ -1402,7 +1409,7 @@ function lowerExpression( return { kind: "TaggedTemplateExpression", - tag: lowerExpressionToPlace(builder, expr.get("tag")), + tag: lowerExpressionToTemporary(builder, expr.get("tag")), value, loc: exprLoc, }; @@ -1431,7 +1438,7 @@ function lowerExpression( } const subexprPlaces = subexprs.map((e) => - lowerExpressionToPlace(builder, e as NodePath) + lowerExpressionToTemporary(builder, e as NodePath) ); return { @@ -1446,7 +1453,7 @@ function lowerExpression( return { kind: "UnaryExpression", operator: expr.node.operator, - value: lowerExpressionToPlace(builder, expr.get("argument")), + value: lowerExpressionToTemporary(builder, expr.get("argument")), loc: exprLoc, }; } @@ -1454,7 +1461,7 @@ function lowerExpression( let expr = exprPath as NodePath; return { kind: "TypeCastExpression", - value: lowerExpressionToPlace(builder, expr.get("expression")), + value: lowerExpressionToTemporary(builder, expr.get("expression")), type: expr.get("typeAnnotation").node, loc: exprLoc, }; @@ -1493,7 +1500,7 @@ function lowerExpression( loc: expr.node.loc ?? GeneratedSource, }); const identifier = argument as NodePath; - const place = lowerExpressionToPlace(builder, identifier); + const place = lowerIdentifier(builder, identifier); builder.push({ id: makeInstructionId(0), lvalue: { place: { ...place }, kind: InstructionKind.Reassign }, @@ -1525,7 +1532,7 @@ function lowerMemberExpression( ): { object: Place; property: Place | string; value: InstructionValue } { const exprNode = expr.node; const exprLoc = exprNode.loc ?? GeneratedSource; - const object = lowerExpressionToPlace(builder, expr.get("object")); + const object = lowerExpressionToTemporary(builder, expr.get("object")); const property = expr.get("property"); if (!expr.node.computed) { if (!property.isIdentifier()) { @@ -1565,7 +1572,7 @@ function lowerMemberExpression( }, }; } - const propertyPlace = lowerExpressionToPlace(builder, property); + const propertyPlace = lowerExpressionToTemporary(builder, property); const value: InstructionValue = { kind: "ComputedLoad", object: { ...object }, @@ -1635,7 +1642,7 @@ function lowerJsxElement( const exprNode = exprPath.node; const exprLoc = exprNode.loc ?? GeneratedSource; if (exprPath.isJSXElement() || exprPath.isJSXFragment()) { - return lowerExpressionToPlace(builder, exprPath); + return lowerExpressionToTemporary(builder, exprPath); } else if (exprPath.isJSXExpressionContainer()) { const expression = exprPath.get("expression"); if (!expression.isExpression()) { @@ -1657,7 +1664,7 @@ function lowerJsxElement( }); return { ...place }; } - return lowerExpressionToPlace(builder, expression); + return lowerExpressionToTemporary(builder, expression); } else if (exprPath.isJSXText()) { const place: Place = buildTemporaryPlace(builder, exprLoc); builder.push({ @@ -1694,19 +1701,38 @@ function lowerJsxElement( } } -function lowerExpressionToPlace( +function lowerExpressionToTemporary( builder: HIRBuilder, exprPath: NodePath ): Place { - const instr = lowerExpression(builder, exprPath); - if (instr.kind === "Identifier") { - return instr; + const value = lowerExpression(builder, exprPath); + if (value.kind === "Identifier" && value.identifier.name === null) { + return value; } const exprLoc = exprPath.node.loc ?? GeneratedSource; const place: Place = buildTemporaryPlace(builder, exprLoc); builder.push({ id: makeInstructionId(0), - value: instr, + value: value, + loc: exprLoc, + lvalue: { place: { ...place }, kind: InstructionKind.Const }, + }); + return place; +} + +function lowerExpressionToPlace( + builder: HIRBuilder, + exprPath: NodePath +): Place { + const value = lowerExpression(builder, exprPath); + if (value.kind === "Identifier") { + return value; + } + const exprLoc = exprPath.node.loc ?? GeneratedSource; + const place: Place = buildTemporaryPlace(builder, exprLoc); + builder.push({ + id: makeInstructionId(0), + value: value, loc: exprLoc, lvalue: { place: { ...place }, kind: InstructionKind.Const }, }); @@ -1837,7 +1863,7 @@ function lowerAssignment( case "MemberExpression": { const lvalue = lvaluePath as NodePath; const property = lvalue.get("property"); - const object = lowerExpressionToPlace(builder, lvalue.get("object")); + const object = lowerExpressionToTemporary(builder, lvalue.get("object")); let valuePlace: Place; if (value.kind === "Identifier") { valuePlace = value; @@ -1876,7 +1902,7 @@ function lowerAssignment( }); return { kind: "UnsupportedNode", node: lvalueNode, loc }; } - const propertyPlace = lowerExpressionToPlace(builder, property); + const propertyPlace = lowerExpressionToTemporary(builder, property); return { kind: "ComputedStore", object, @@ -2056,7 +2082,7 @@ function gatherCapturedDeps( path.skip(); capturedIds.add(binding.identifier); - capturedRefs.add(lowerExpressionToPlace(builder, path)); + capturedRefs.add(lowerExpressionToTemporary(builder, path)); }, }); diff --git a/compiler/forget/src/Inference/AnalyseFunctions.ts b/compiler/forget/src/Inference/AnalyseFunctions.ts index 692ce782c7..b4659570b5 100644 --- a/compiler/forget/src/Inference/AnalyseFunctions.ts +++ b/compiler/forget/src/Inference/AnalyseFunctions.ts @@ -1,11 +1,12 @@ import invariant from "invariant"; import { - HIRFunction, + Effect, FunctionExpression, + HIRFunction, Identifier, mergeConsecutiveBlocks, Place, - Effect, + ReactiveScopeDependency, } from "../HIR"; import { constantPropagation } from "../Optimization"; import { eliminateRedundantPhi, enterSSA } from "../SSA"; @@ -14,48 +15,58 @@ import { logHIRFunction } from "../Utils/logger"; import { inferMutableRanges } from "./InferMutableRanges"; import inferReferenceEffects from "./InferReferenceEffects"; -type Dependency = { - place: Place; - path: Array | null; -}; +class State { + properties: Map = new Map(); -function declareProperty( - properties: Map, - lvalue: Place, - object: Place, - property: string -): void { - const objectDependency = properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = { place: object, path: [property] }; - } else { - nextDependency = { - place: objectDependency.place, - path: [...(objectDependency.path ?? []), property], + declareProperty(lvalue: Place, object: Place, property: string): void { + const objectDependency = this.properties.get(object.identifier); + let nextDependency: ReactiveScopeDependency; + if (objectDependency === undefined) { + nextDependency = { place: object, path: [property] }; + } else { + nextDependency = { + place: objectDependency.place, + path: [...(objectDependency.path ?? []), property], + }; + } + this.properties.set(lvalue.identifier, nextDependency); + } + + declareTemporary(lvalue: Place, value: Place): void { + const resolved: ReactiveScopeDependency = this.properties.get( + value.identifier + ) ?? { + place: value, + path: null, }; + this.properties.set(lvalue.identifier, resolved); } - properties.set(lvalue.identifier, nextDependency); } export default function analyseFunctions(func: HIRFunction) { - const properties: Map = new Map(); + const state = new State(); for (const [_, block] of func.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { case "FunctionExpression": { lower(instr.value.loweredFunc); - infer(instr.value, properties, func.context); + infer(instr.value, state, func.context); break; } case "PropertyLoad": { - declareProperty( - properties, + state.declareProperty( instr.lvalue.place, instr.value.object, instr.value.property ); + break; + } + case "Identifier": { + if (instr.lvalue.place.identifier.name === null) { + state.declareTemporary(instr.lvalue.place, instr.value); + } + break; } } } @@ -74,11 +85,7 @@ function lower(func: HIRFunction) { logHIRFunction("AnalyseFunction (inner)", func); } -function infer( - value: FunctionExpression, - properties: Map, - context: Place[] -) { +function infer(value: FunctionExpression, state: State, context: Place[]) { const mutations = new Set( value.loweredFunc.context .filter((dep) => isMutated(dep.identifier)) @@ -89,8 +96,8 @@ function infer( for (const dep of value.dependencies) { let name: string | null = null; - if (properties.has(dep.identifier)) { - const receiver = properties.get(dep.identifier)!; + if (state.properties.has(dep.identifier)) { + const receiver = state.properties.get(dep.identifier)!; name = receiver.place.identifier.name; } else { name = dep.identifier.name; diff --git a/compiler/forget/src/Inference/InferAliasForStores.ts b/compiler/forget/src/Inference/InferAliasForStores.ts index cbdef18866..c36b11ee58 100644 --- a/compiler/forget/src/Inference/InferAliasForStores.ts +++ b/compiler/forget/src/Inference/InferAliasForStores.ts @@ -11,7 +11,6 @@ import { InstructionId, Place, } from "../HIR/HIR"; -import { printInstructionValue } from "../HIR/PrintHIR"; import { eachInstructionValueOperand } from "../HIR/visitors"; import DisjointSet from "../Utils/DisjointSet"; @@ -25,30 +24,12 @@ export function inferAliasForStores( if (lvalue.place.effect !== Effect.Store) { continue; } - switch (value.kind) { - case "ArrayExpression": - case "ObjectExpression": - case "ComputedStore": - case "PropertyStore": - case "FunctionExpression": { - for (const operand of eachInstructionValueOperand(value)) { - if ( - operand.effect === Effect.Capture || - operand.effect === Effect.Store - ) { - maybeAlias(aliases, lvalue.place, operand, instr.id); - } - } - break; - } - default: { - // Effect.Capture & Effect.Store are only used for aliasing - // instructions. - throw new Error( - `Unexpected capture/store instruction: ${printInstructionValue( - value - )}` - ); + for (const operand of eachInstructionValueOperand(value)) { + if ( + operand.effect === Effect.Capture || + operand.effect === Effect.Store + ) { + maybeAlias(aliases, lvalue.place, operand, instr.id); } } } diff --git a/compiler/forget/src/Inference/InferMutableRanges.ts b/compiler/forget/src/Inference/InferMutableRanges.ts index 1f48dde39d..0253fe2845 100644 --- a/compiler/forget/src/Inference/InferMutableRanges.ts +++ b/compiler/forget/src/Inference/InferMutableRanges.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { HIRFunction } from "../HIR/HIR"; +import { HIRFunction, Identifier } from "../HIR/HIR"; import { inferAliases } from "./InferAlias"; import { inferAliasForStores } from "./InferAliasForStores"; import { inferMutableLifetimes } from "./InferMutableLifetimes"; @@ -17,18 +17,22 @@ export function inferMutableRanges(ir: HIRFunction) { // Calculate aliases const aliases = inferAliases(ir); - let size = aliases.size; // Eagerly canonicalize so that if nothing changes we can bail out // after a single iteration - aliases.canonicalize(); - do { - size = aliases.size; + let prevAliases: Map = aliases.canonicalize(); + while (true) { // Infer mutable ranges for aliases that are not fields inferMutableRangesForAlias(ir, aliases); // Update aliasing information of fields inferAliasForStores(ir, aliases); - } while (aliases.size > size || !aliases.canonicalize()); + + const nextAliases = aliases.canonicalize(); + if (areEqualMaps(prevAliases, nextAliases)) { + break; + } + prevAliases = nextAliases; + } // Re-infer mutable ranges for all values inferMutableLifetimes(ir, true); @@ -36,3 +40,18 @@ export function inferMutableRanges(ir: HIRFunction) { // Re-infer mutable ranges for aliases inferMutableRangesForAlias(ir, aliases); } + +function areEqualMaps(a: Map, b: Map): boolean { + if (a.size !== b.size) { + return false; + } + for (const [key, value] of a) { + if (!b.has(key)) { + return false; + } + if (b.get(key) !== value) { + return false; + } + } + return true; +} diff --git a/compiler/forget/src/Inference/InferMutableRangesForAlias.ts b/compiler/forget/src/Inference/InferMutableRangesForAlias.ts index 9a8edfb17d..95b96c8ef7 100644 --- a/compiler/forget/src/Inference/InferMutableRangesForAlias.ts +++ b/compiler/forget/src/Inference/InferMutableRangesForAlias.ts @@ -5,19 +5,6 @@ export function inferMutableRangesForAlias( fn: HIRFunction, aliases: DisjointSet ) { - for (const [_, block] of fn.body.blocks) { - for (const phi of block.phis) { - const isPhiMutatedAfterCreation: boolean = - phi.id.mutableRange.end > - (block.instructions.at(0)?.id ?? block.terminal.id); - if (isPhiMutatedAfterCreation) { - for (const [, operand] of phi.operands) { - aliases.union([phi.id, operand]); - } - } - } - } - const aliasSets = aliases.buildSets(); for (const aliasSet of aliasSets) { // Update mutableRange.end only if the identifiers have actually been @@ -44,4 +31,17 @@ export function inferMutableRangesForAlias( } } } + + for (const [_, block] of fn.body.blocks) { + for (const phi of block.phis) { + const isPhiMutatedAfterCreation: boolean = + phi.id.mutableRange.end > + (block.instructions.at(0)?.id ?? block.terminal.id); + if (isPhiMutatedAfterCreation) { + for (const [, operand] of phi.operands) { + aliases.union([phi.id, operand]); + } + } + } + } } diff --git a/compiler/forget/src/ReactiveScopes/InferReactiveIdentifiers.ts b/compiler/forget/src/ReactiveScopes/InferReactiveIdentifiers.ts index 471950993f..b51e11a1f8 100644 --- a/compiler/forget/src/ReactiveScopes/InferReactiveIdentifiers.ts +++ b/compiler/forget/src/ReactiveScopes/InferReactiveIdentifiers.ts @@ -28,18 +28,20 @@ class Visitor extends ReactiveFunctionVisitor { ) { this.traverseInstruction(instr, reactivityMap); const lval = instr.lvalue; - if (lval == null || reactivityMap.get(lval.place.identifier.id) === true) { + if (lval == null) { return; } const { value } = instr; - let hasReactiveInput = false; - for (const operand of eachReactiveValueOperand(value)) { - // We currently treat free variables (from module or global scope) as - // non-reactive. We may later want type information about specific - // free variables, or a toggle `treatFreeVarsAsReactive`. - if (reactivityMap.get(operand.identifier.id)) { - hasReactiveInput = true; - break; + let hasReactiveInput = reactivityMap.get(lval.place.identifier.id) === true; + if (!hasReactiveInput && value.kind !== "LoadGlobal") { + for (const operand of eachReactiveValueOperand(value)) { + // We currently treat free variables (from module or global scope) as + // non-reactive. We may later want type information about specific + // free variables, or a toggle `treatFreeVarsAsReactive`. + if (reactivityMap.get(operand.identifier.id)) { + hasReactiveInput = true; + break; + } } } if ( @@ -136,7 +138,11 @@ export function inferReactiveIdentifiers( for (const param of fn.params) { reactivityMap.set(param.identifier.id, true); } - visitReactiveFunction(fn, visitor, reactivityMap); + let size: number; + do { + size = reactivityMap.size; + visitReactiveFunction(fn, visitor, reactivityMap); + } while (reactivityMap.size > size); const result = new Set(); reactivityMap.forEach((isReactive, id) => { diff --git a/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts index d4b9af87d7..eddad56535 100644 --- a/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts +++ b/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts @@ -71,6 +71,7 @@ class Context { // This helps with.. temporaries that are created only for property loads // but can be generalized to all non-allocating temporaries #properties: Map = new Map(); + #temporaries: Map = new Map(); #scopes: Scopes = []; enter(scope: ReactiveScope, fn: () => void): Set { @@ -97,11 +98,16 @@ class Context { this.#reassignments.set(identifier, decl); } + declareTemporary(lvalue: Place, value: Place): void { + this.#temporaries.set(lvalue.identifier, value); + } + declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.#properties.get(object.identifier); + const resolvedObject = this.#temporaries.get(object.identifier) ?? object; + const objectDependency = this.#properties.get(resolvedObject.identifier); let nextDependency: ReactiveScopeDependency; if (objectDependency === undefined) { - nextDependency = { place: object, path: [property] }; + nextDependency = { place: resolvedObject, path: [property] }; } else { nextDependency = { place: objectDependency.place, @@ -120,14 +126,16 @@ class Context { } visitOperand(place: Place): void { - this.visitDependency({ place, path: null }); + const resolved = this.#temporaries.get(place.identifier) ?? place; + this.visitDependency({ place: resolved, path: null }); } visitProperty(object: Place, property: string): void { - const objectDependency = this.#properties.get(object.identifier); + const resolvedObject = this.#temporaries.get(object.identifier) ?? object; + const objectDependency = this.#properties.get(resolvedObject.identifier); let nextDependency: ReactiveScopeDependency; if (objectDependency === undefined) { - nextDependency = { place: object, path: [property] }; + nextDependency = { place: resolvedObject, path: [property] }; } else { nextDependency = { place: objectDependency.place, @@ -362,7 +370,16 @@ function visitInstructionValue( value: ReactiveValue, lvalue: LValue | null ): void { - if (value.kind === "PropertyLoad") { + if (value.kind === "Identifier" && lvalue !== null) { + if ( + value.identifier.name !== null && + lvalue.place.identifier.name === null + ) { + context.declareTemporary(lvalue.place, value); + } else { + context.visitOperand(value); + } + } else if (value.kind === "PropertyLoad") { if (lvalue !== null) { context.declareProperty(lvalue.place, value.object, value.property); } else { diff --git a/compiler/forget/src/Utils/DisjointSet.ts b/compiler/forget/src/Utils/DisjointSet.ts index c392b6e9d4..3f66105a77 100644 --- a/compiler/forget/src/Utils/DisjointSet.ts +++ b/compiler/forget/src/Utils/DisjointSet.ts @@ -75,19 +75,16 @@ export default class DisjointSet { /** * Forces the set into canonical form, ie with all items pointing directly to - * their root. Returns true if the set was already in canonical form, false - * otherwise. + * their root, and returns a Map representing the mapping of items to their roots. */ - canonicalize(): boolean { - let isCanonical = true; + canonicalize(): Map { + const entries = new Map(); for (const item of this.#entries.keys()) { const parent = this.#entries.get(item)!; - const root = this.find(item); - if (parent !== root) { - isCanonical = false; - } + const root = this.find(item)!; + entries.set(item, root); } - return isCanonical; + return entries; } /** diff --git a/compiler/forget/src/__tests__/fixtures/hir/_bug.capturing-func-simple-alias.expect.md b/compiler/forget/src/__tests__/fixtures/hir/_bug.capturing-func-simple-alias.expect.md index 6a84a90a06..8de657bb48 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/_bug.capturing-func-simple-alias.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/_bug.capturing-func-simple-alias.expect.md @@ -28,12 +28,12 @@ function component(a) { } else { x = $[1]; } - const y = undefined; + (function () { y = x; })(); - mutate(y); - return y; + mutate(undefined); + return undefined; } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/_bug_expression-with-assignment.js b/compiler/forget/src/__tests__/fixtures/hir/_bug_expression-with-assignment.js deleted file mode 100644 index 49ae886dad..0000000000 --- a/compiler/forget/src/__tests__/fixtures/hir/_bug_expression-with-assignment.js +++ /dev/null @@ -1,6 +0,0 @@ -function f() { - let x = 1; - // BUG: `x` has different values within this expression. Currently, the - // assignment is evaluated too early. - return x + (x = 2) + x; -} diff --git a/compiler/forget/src/__tests__/fixtures/hir/assignment-expression-computed.expect.md b/compiler/forget/src/__tests__/fixtures/hir/assignment-expression-computed.expect.md index 62cadca01c..5207533242 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/assignment-expression-computed.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/assignment-expression-computed.expect.md @@ -21,10 +21,11 @@ function Component(props) { let x; if (c_0) { x = [props.x]; - const index = 0; - x[index] = x[index] * 2; - const t0 = "0"; - x[t0] = x[t0] + 3; + + const t0 = 0; + x[t0] = x[t0] * 2; + const t1 = "0"; + x[t1] = x[t1] + 3; $[0] = props.x; $[1] = x; } else { diff --git a/compiler/forget/src/__tests__/fixtures/hir/assignment-in-nested-if.expect.md b/compiler/forget/src/__tests__/fixtures/hir/assignment-in-nested-if.expect.md index 4f1f37ae5c..5ee3cdac92 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/assignment-in-nested-if.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/assignment-in-nested-if.expect.md @@ -24,12 +24,14 @@ function useBar(props) { let z = undefined; if (props.a) { if (props.b) { + let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - z = baz(); - $[0] = z; + t0 = baz(); + $[0] = t0; } else { - z = $[0]; + t0 = $[0]; } + z = t0; } } return z; diff --git a/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-computed-mutate.expect.md b/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-computed-mutate.expect.md index 4fd3f277d3..efaa1a7cae 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-computed-mutate.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-computed-mutate.expect.md @@ -4,7 +4,7 @@ ```javascript function component(a) { let x = { a }; - let y; + let y = {}; (function () { y["x"] = x; })(); @@ -23,16 +23,16 @@ function component(a) { let y; if (c_0) { const x = { a: a }; - y = undefined; + y = {}; (function () { y["x"] = x; })(); + mutate(y); $[0] = a; $[1] = y; } else { y = $[1]; } - mutate(y); return y; } diff --git a/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-computed-mutate.js b/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-computed-mutate.js index d3a669613b..f8a365a283 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-computed-mutate.js +++ b/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-computed-mutate.js @@ -1,6 +1,6 @@ function component(a) { let x = { a }; - let y; + let y = {}; (function () { y["x"] = x; })(); diff --git a/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-mutate.expect.md b/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-mutate.expect.md index abb653084f..00063417a4 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-mutate.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-mutate.expect.md @@ -4,7 +4,7 @@ ```javascript function component(a) { let x = { a }; - let y; + let y = {}; (function () { y.x = x; })(); @@ -23,16 +23,16 @@ function component(a) { let y; if (c_0) { const x = { a: a }; - y = undefined; + y = {}; (function () { y.x = x; })(); + mutate(y); $[0] = a; $[1] = y; } else { y = $[1]; } - mutate(y); return y; } diff --git a/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-mutate.js b/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-mutate.js index 6da8bf9d1a..8ea83e13bc 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-mutate.js +++ b/compiler/forget/src/__tests__/fixtures/hir/capturing-func-alias-mutate.js @@ -1,6 +1,6 @@ function component(a) { let x = { a }; - let y; + let y = {}; (function () { y.x = x; })(); diff --git a/compiler/forget/src/__tests__/fixtures/hir/capturing-function-member-expr-call.expect.md b/compiler/forget/src/__tests__/fixtures/hir/capturing-function-member-expr-call.expect.md index 10adb826ba..73a1740d14 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/capturing-function-member-expr-call.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/capturing-function-member-expr-call.expect.md @@ -19,9 +19,9 @@ function component({ mutator }) { ## Code ```javascript -function component(t15) { +function component(t23) { const $ = React.unstable_useMemoCache(7); - const t0 = t15; + const t0 = t23; const mutator = t0.mutator; const c_0 = $[0] !== mutator; let poke; diff --git a/compiler/forget/src/__tests__/fixtures/hir/capturing-function-within-block.expect.md b/compiler/forget/src/__tests__/fixtures/hir/capturing-function-within-block.expect.md index ebdfd83a0e..d60e6670e9 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/capturing-function-within-block.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/capturing-function-within-block.expect.md @@ -30,16 +30,17 @@ function component(a) { z = $[1]; } const c_2 = $[2] !== z; - let x; + let t0; if (c_2) { - x = function () { + t0 = function () { z; }; $[2] = z; - $[3] = x; + $[3] = t0; } else { - x = $[3]; + t0 = $[3]; } + const x = t0; return x; } diff --git a/compiler/forget/src/__tests__/fixtures/hir/constant-propagation.expect.md b/compiler/forget/src/__tests__/fixtures/hir/constant-propagation.expect.md index 003a163e0e..e53300ae3a 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/constant-propagation.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/constant-propagation.expect.md @@ -28,9 +28,7 @@ function foo() { ```javascript function foo() { console.log("foo"); - - const j = -6; - return j; + return -6; } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment-dynamic.expect.md b/compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment-dynamic.expect.md new file mode 100644 index 0000000000..c4f145c91f --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment-dynamic.expect.md @@ -0,0 +1,21 @@ + +## Input + +```javascript +function f(y) { + let x = y; + return x + (x = 2) + x; +} + +``` + +## Code + +```javascript +function f(y) { + const x = y; + return x + 2 + 2; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment-dynamic.js b/compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment-dynamic.js new file mode 100644 index 0000000000..e3a58967b4 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment-dynamic.js @@ -0,0 +1,4 @@ +function f(y) { + let x = y; + return x + (x = 2) + x; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/_bug_expression-with-assignment.expect.md b/compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment.expect.md similarity index 52% rename from compiler/forget/src/__tests__/fixtures/hir/_bug_expression-with-assignment.expect.md rename to compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment.expect.md index 4d909a7040..0f4e095dd0 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/_bug_expression-with-assignment.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment.expect.md @@ -4,8 +4,6 @@ ```javascript function f() { let x = 1; - // BUG: `x` has different values within this expression. Currently, the - // assignment is evaluated too early. return x + (x = 2) + x; } @@ -15,7 +13,7 @@ function f() { ```javascript function f() { - return 6; + return 5; } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment.js b/compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment.js new file mode 100644 index 0000000000..5d4ec4c51b --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/expression-with-assignment.js @@ -0,0 +1,4 @@ +function f() { + let x = 1; + return x + (x = 2) + x; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.expect.md b/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.expect.md index feec1ee910..77dbdb1337 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.expect.md @@ -33,12 +33,14 @@ function Component(props) { if (cond) { a = x; } else { + let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - a = []; - $[0] = a; + t0 = []; + $[0] = t0; } else { - a = $[0]; + t0 = $[0]; } + a = t0; } useFreeze(a); diff --git a/compiler/forget/src/__tests__/fixtures/hir/obj-literal-cached-in-if-else.expect.md b/compiler/forget/src/__tests__/fixtures/hir/obj-literal-cached-in-if-else.expect.md index c552f392d3..9a1877dcd6 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/obj-literal-cached-in-if-else.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/obj-literal-cached-in-if-else.expect.md @@ -23,22 +23,26 @@ function foo(a, b, c, d) { let x = undefined; if (someVal) { const c_0 = $[0] !== b; + let t0; if (c_0) { - x = { b: b }; + t0 = { b: b }; $[0] = b; - $[1] = x; + $[1] = t0; } else { - x = $[1]; + t0 = $[1]; } + x = t0; } else { const c_2 = $[2] !== c; + let t1; if (c_2) { - x = { c: c }; + t1 = { c: c }; $[2] = c; - $[3] = x; + $[3] = t1; } else { - x = $[3]; + t1 = $[3]; } + x = t1; } return x; } diff --git a/compiler/forget/src/__tests__/fixtures/hir/object-pattern-params.expect.md b/compiler/forget/src/__tests__/fixtures/hir/object-pattern-params.expect.md index 1013fcd7b6..c0a8ec7d70 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/object-pattern-params.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/object-pattern-params.expect.md @@ -13,10 +13,10 @@ function component({ a, b }) { ## Code ```javascript -function component(t8) { +function component(t12) { const $ = React.unstable_useMemoCache(7); - const a = t8.a; - const b = t8.b; + const a = t12.a; + const b = t12.b; const c_0 = $[0] !== a; let y; if (c_0) { diff --git a/compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md b/compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md index e9514dd4a5..c7552085ba 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md @@ -36,27 +36,29 @@ function Component(props) { } const y = x; if (props.p1) { + let t0; if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - x = []; - $[2] = x; + t0 = []; + $[2] = t0; } else { - x = $[2]; + t0 = $[2]; } + x = t0; } y.push(props.p2); const c_3 = $[3] !== x; const c_4 = $[4] !== y; - let t0; + let t1; if (c_3 || c_4) { - t0 = ; + t1 = ; $[3] = x; $[4] = y; - $[5] = t0; + $[5] = t1; } else { - t0 = $[5]; + t1 = $[5]; } - return t0; + return t1; } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/reassignment.expect.md b/compiler/forget/src/__tests__/fixtures/hir/reassignment.expect.md index f3d722749e..0b3212b74d 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/reassignment.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/reassignment.expect.md @@ -30,12 +30,14 @@ function Component(props) { x = []; x.push(props.p0); y = x; + let t0; if ($[4] === Symbol.for("react.memo_cache_sentinel")) { - x = []; - $[4] = x; + t0 = []; + $[4] = t0; } else { - x = $[4]; + t0 = $[4]; } + x = t0; y.push(props.p1); $[0] = props.p0; @@ -48,16 +50,16 @@ function Component(props) { } const c_5 = $[5] !== x; const c_6 = $[6] !== y; - let t0; + let t1; if (c_5 || c_6) { - t0 = ; + t1 = ; $[5] = x; $[6] = y; - $[7] = t0; + $[7] = t1; } else { - t0 = $[7]; + t1 = $[7]; } - return t0; + return t1; } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/sequence-expression.expect.md b/compiler/forget/src/__tests__/fixtures/hir/sequence-expression.expect.md index 83f5f1b7b2..dfbf5abb7f 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/sequence-expression.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/sequence-expression.expect.md @@ -18,18 +18,25 @@ function foo() {} ```javascript function sequence(props) { - const $ = React.unstable_useMemoCache(1); + const $ = React.unstable_useMemoCache(2); Math.max(1, 2); - let x; + let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x = foo(); + t0 = foo(); + $[0] = t0; + } else { + t0 = $[0]; + } + let x; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + x = t0; while ((foo(), true)) { foo(); x = 2; } - $[0] = x; + $[1] = x; } else { - x = $[0]; + x = $[1]; } return x; } diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-arrayexpression.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-arrayexpression.expect.md index b953dd4fb3..fc50027b02 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-arrayexpression.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-arrayexpression.expect.md @@ -16,11 +16,9 @@ function Component(props) { ```javascript function Component(props) { const $ = React.unstable_useMemoCache(1); - const a = 1; - const b = 2; let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x = [a, b]; + x = [1, 2]; $[0] = x; } else { x = $[0]; diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-nested-loops-no-reassign.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-nested-loops-no-reassign.expect.md index 8ec987a29f..4193df4b1f 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-nested-loops-no-reassign.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-nested-loops-no-reassign.expect.md @@ -22,13 +22,12 @@ function foo(a, b, c) { ```javascript // @xonly function foo(a, b, c) { - const x = 0; while (a) { while (b) { while (c) {} } } - return x; + return 0; } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-objectexpression.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-objectexpression.expect.md index ef6b34dc35..7d3b670978 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-objectexpression.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-objectexpression.expect.md @@ -16,11 +16,9 @@ function Component(props) { ```javascript function Component(props) { const $ = React.unstable_useMemoCache(1); - const a = 1; - const b = 2; let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x = { a: a, b: b }; + x = { a: 1, b: 2 }; $[0] = x; } else { x = $[0]; diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-shadowing.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-shadowing.expect.md index 0d979655dd..f486304ece 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-shadowing.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-shadowing.expect.md @@ -25,8 +25,7 @@ function log() {} function Foo(cond) { let str = ""; if (cond) { - const str_0 = "other test"; - log(str_0); + log("other test"); } else { str = "fallthrough test"; } diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-switch.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-switch.expect.md index a1beb758ee..77c8a658e9 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-switch.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-switch.expect.md @@ -28,8 +28,7 @@ function foo() { ```javascript function foo() { - const x = 1; - bb1: switch (x) { + bb1: switch (1) { case 1: { break bb1; } diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-while-no-reassign.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-while-no-reassign.expect.md index 8ec206869f..8a7807a4a5 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-while-no-reassign.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-while-no-reassign.expect.md @@ -17,9 +17,8 @@ function foo() { ```javascript function foo() { - const x = 1; while (true) {} - return x; + return 1; } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md b/compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md index f451be0992..cad7c3388a 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md @@ -46,12 +46,14 @@ function Component(props) { } case true: { x.push(props.p2); + let t0; if ($[4] === Symbol.for("react.memo_cache_sentinel")) { - y = []; - $[4] = y; + t0 = []; + $[4] = t0; } else { - y = $[4]; + t0 = $[4]; } + y = t0; break bb1; } default: { @@ -81,16 +83,16 @@ function Component(props) { y.push(props.p4); const c_7 = $[7] !== y; const c_8 = $[8] !== child; - let t0; + let t1; if (c_7 || c_8) { - t0 = {child}; + t1 = {child}; $[7] = y; $[8] = child; - $[9] = t0; + $[9] = t1; } else { - t0 = $[9]; + t1 = $[9]; } - return t0; + return t1; } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/type-test-primitive.expect.md b/compiler/forget/src/__tests__/fixtures/hir/type-test-primitive.expect.md index c94e6e4dc5..9b0cb3b544 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/type-test-primitive.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/type-test-primitive.expect.md @@ -15,8 +15,7 @@ function component() { ```javascript function component() { - const y = 2; - return y; + return 2; } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/unconditional-break-label.expect.md b/compiler/forget/src/__tests__/fixtures/hir/unconditional-break-label.expect.md index 46200d32a1..417f1362ea 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/unconditional-break-label.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/unconditional-break-label.expect.md @@ -17,8 +17,7 @@ function foo(a) { ```javascript function foo(a) { - const x = 1; - return a + x; + return a + 1; } ```