mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Use sequence terminal, fix most remaining order-of-evaluation bugs
Changes the lowering for sequence expressions to use the new terminal. When converting to a ReactiveFunction, we convert these terminals into ReactiveSequenceValues, which nests the instructions and preserves order of evaluation in the output. The only catch is constant propagation — constant propagation breaks order-of-evaluation because it can effectively copy the final value of a sequence elsewhere, leaving the original sequence in the wrong place. I'll address that in a follow-up.
This commit is contained in:
@@ -1168,23 +1168,48 @@ function lowerExpression(
|
||||
const expr = exprPath as NodePath<t.SequenceExpression>;
|
||||
const exprLoc = expr.node.loc ?? GeneratedSource;
|
||||
|
||||
let last: Place | null = null;
|
||||
for (const item of expr.get("expressions")) {
|
||||
last = lowerExpressionToTemporary(builder, item);
|
||||
}
|
||||
if (last === null) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Expected SequenceExpression to have at least one expression`,
|
||||
severity: ErrorSeverity.InvalidInput,
|
||||
nodePath: expr,
|
||||
});
|
||||
return { kind: "UnsupportedNode", node: expr.node, loc: exprLoc };
|
||||
}
|
||||
return {
|
||||
kind: "LoadLocal", // TODO: LoadTemp
|
||||
place: last,
|
||||
loc: last.loc,
|
||||
};
|
||||
const continuationBlock = builder.reserve(builder.currentBlockKind());
|
||||
const place = buildTemporaryPlace(builder, exprLoc);
|
||||
|
||||
const sequenceBlock = builder.enter("value", (_) => {
|
||||
let last: Place | null = null;
|
||||
for (const item of expr.get("expressions")) {
|
||||
last = lowerExpressionToTemporary(builder, item);
|
||||
}
|
||||
if (last === null) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Expected SequenceExpression to have at least one expression`,
|
||||
severity: ErrorSeverity.InvalidInput,
|
||||
nodePath: expr,
|
||||
});
|
||||
} else {
|
||||
lowerValueToTemporary(builder, {
|
||||
kind: "StoreLocal",
|
||||
lvalue: { kind: InstructionKind.Const, place: { ...place } },
|
||||
value: last,
|
||||
loc: exprLoc,
|
||||
});
|
||||
}
|
||||
return {
|
||||
kind: "goto",
|
||||
id: makeInstructionId(0),
|
||||
block: continuationBlock.id,
|
||||
loc: exprLoc,
|
||||
variant: GotoVariant.Break,
|
||||
};
|
||||
});
|
||||
|
||||
builder.terminateWithContinuation(
|
||||
{
|
||||
kind: "sequence",
|
||||
block: sequenceBlock,
|
||||
fallthrough: continuationBlock.id,
|
||||
id: makeInstructionId(0),
|
||||
loc: exprLoc,
|
||||
},
|
||||
continuationBlock
|
||||
);
|
||||
return { kind: "LoadLocal", place, loc: place.loc };
|
||||
}
|
||||
case "ConditionalExpression": {
|
||||
const expr = exprPath as NodePath<t.ConditionalExpression>;
|
||||
|
||||
@@ -44,7 +44,7 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
|
||||
"Expected predecessor %s to exist",
|
||||
predecessorId
|
||||
);
|
||||
if (predecessor.terminal.kind !== "goto") {
|
||||
if (predecessor.terminal.kind !== "goto" || predecessor.kind !== "block") {
|
||||
// The predecessor is not guaranteed to transfer control to this block,
|
||||
// they aren't consecutive.
|
||||
continue;
|
||||
|
||||
@@ -771,6 +771,15 @@ class Driver {
|
||||
id: InstructionId;
|
||||
} {
|
||||
switch (terminal.kind) {
|
||||
case "sequence": {
|
||||
const block = this.visitValueBlock(terminal.block, terminal.loc);
|
||||
return {
|
||||
value: block.value,
|
||||
place: block.place,
|
||||
fallthrough: terminal.fallthrough,
|
||||
id: terminal.id,
|
||||
};
|
||||
}
|
||||
case "optional": {
|
||||
const test = this.visitValueBlock(terminal.test, terminal.loc);
|
||||
const testBlock = this.cx.ir.blocks.get(test.block)!;
|
||||
|
||||
+2
-5
@@ -46,11 +46,8 @@ function Component() {
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
x = { f: t1 };
|
||||
|
||||
console.log("A");
|
||||
console.log("B");
|
||||
changeF(x);
|
||||
console.log("arg");
|
||||
x.f(1);
|
||||
console.log("B"), "f";
|
||||
(console.log("A"), x).f((changeF(x), console.log("arg"), 1));
|
||||
$[2] = x;
|
||||
} else {
|
||||
x = $[2];
|
||||
|
||||
+1
-2
@@ -36,8 +36,7 @@ function Component(props) {
|
||||
|
||||
T0 = Tag;
|
||||
t1 = "\n ";
|
||||
Tag = props.alternateComponent;
|
||||
t2 = maybeMutate(maybeMutable);
|
||||
t2 = ((Tag = props.alternateComponent), maybeMutate(maybeMutable));
|
||||
$[0] = props.component;
|
||||
$[1] = props.alternateComponent;
|
||||
$[2] = Tag;
|
||||
|
||||
@@ -20,6 +20,8 @@ function Component(props) {
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function Component(props) {
|
||||
const $ = useMemoCache(3);
|
||||
|
||||
const t1 = props.value;
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <HScroll />;
|
||||
@@ -27,21 +29,21 @@ function Component(props) {
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const c_1 = $[1] !== props.value;
|
||||
let t1;
|
||||
const c_1 = $[1] !== t1;
|
||||
let t2;
|
||||
if (c_1) {
|
||||
t1 = (
|
||||
t2 = (
|
||||
<View>
|
||||
{props.value}
|
||||
{t1}
|
||||
{t0}
|
||||
</View>
|
||||
);
|
||||
$[1] = props.value;
|
||||
$[2] = t1;
|
||||
$[1] = t1;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
t2 = $[2];
|
||||
}
|
||||
return t1;
|
||||
return t2;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+1
-4
@@ -46,10 +46,7 @@ function Component() {
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
x = { f: t1 };
|
||||
|
||||
console.log("A");
|
||||
changeF(x);
|
||||
console.log("arg");
|
||||
x.f(1);
|
||||
(console.log("A"), x).f((changeF(x), console.log("arg"), 1));
|
||||
$[2] = x;
|
||||
} else {
|
||||
x = $[2];
|
||||
@@ -19,25 +19,16 @@ function foo() {}
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function sequence(props) {
|
||||
const $ = useMemoCache(2);
|
||||
Math.max(1, 2);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = foo();
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const $ = useMemoCache(1);
|
||||
let x;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
x = t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
x = (Math.max(1, 2), foo());
|
||||
while ((foo(), true)) {
|
||||
foo();
|
||||
x = 2;
|
||||
x = (foo(), 2);
|
||||
}
|
||||
$[1] = x;
|
||||
$[0] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
x = $[0];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user