[compiler] PruneNonEscapingScopes understands terminal operands

We weren't treating terminal operands as eligible for memoization in PruneNonEscapingScopes, which meant that they could end up un-memoized. Terminal operands can also be compound ReactiveValues like SequenceExpressions, so part of the fix is to make sure we don't just recurse into compound values but record the full aliasing information we would for top-level instructions.

Still WIP, this needs to handle terminals other than for..of.

ghstack-source-id: 09a29230514e3bc95d1833cd4392de238fabbeda
Pull Request resolved: https://github.com/facebook/react/pull/33062
This commit is contained in:
Joe Savona
2025-05-01 12:41:27 +09:00
parent f06b6e969e
commit 08ce0c428d
6 changed files with 681 additions and 468 deletions
@@ -255,6 +255,12 @@ function writeReactiveValue(writer: Writer, value: ReactiveValue): void {
}
}
export function printReactiveTerminal(terminal: ReactiveTerminal): string {
const writer = new Writer();
writeTerminal(writer, terminal);
return writer.complete();
}
function writeTerminal(writer: Writer, terminal: ReactiveTerminal): void {
switch (terminal.kind) {
case 'break': {
@@ -0,0 +1,67 @@
## Input
```javascript
function useHook(nodeID, condition) {
const graph = useContext(GraphContext);
const node = nodeID != null ? graph[nodeID] : null;
for (const key of Object.keys(node?.fields ?? {})) {
if (condition) {
return new Class(node.fields?.[field]);
}
}
return new Class();
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function useHook(nodeID, condition) {
const $ = _c(7);
const graph = useContext(GraphContext);
const node = nodeID != null ? graph[nodeID] : null;
let t0;
if ($[0] !== node?.fields) {
t0 = Object.keys(node?.fields ?? {});
$[0] = node?.fields;
$[1] = t0;
} else {
t0 = $[1];
}
let t1;
if ($[2] !== condition || $[3] !== node || $[4] !== t0) {
t1 = Symbol.for("react.early_return_sentinel");
bb0: for (const key of t0) {
if (condition) {
t1 = new Class(node.fields?.[field]);
break bb0;
}
}
$[2] = condition;
$[3] = node;
$[4] = t0;
$[5] = t1;
} else {
t1 = $[5];
}
if (t1 !== Symbol.for("react.early_return_sentinel")) {
return t1;
}
let t2;
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
t2 = new Class();
$[6] = t2;
} else {
t2 = $[6];
}
return t2;
}
```
### Eval output
(kind: exception) Fixture not implemented
@@ -0,0 +1,11 @@
function useHook(nodeID, condition) {
const graph = useContext(GraphContext);
const node = nodeID != null ? graph[nodeID] : null;
for (const key of Object.keys(node?.fields ?? {})) {
if (condition) {
return new Class(node.fields?.[field]);
}
}
return new Class();
}
@@ -0,0 +1,62 @@
## Input
```javascript
function useHook(nodeID, condition) {
const graph = useContext(GraphContext);
const node = nodeID != null ? graph[nodeID] : null;
// (2) Instead we can create a scope around the loop since the loop produces an escaping value
let value;
for (const key of Object.keys(node?.fields ?? {})) {
if (condition) {
// (1) We currently create a scope just for this instruction, then later prune the scope because
// it's inside a loop
value = new Class(node.fields?.[field]);
break;
}
}
return value;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function useHook(nodeID, condition) {
const $ = _c(6);
const graph = useContext(GraphContext);
const node = nodeID != null ? graph[nodeID] : null;
let value;
let t0;
if ($[0] !== node?.fields) {
t0 = Object.keys(node?.fields ?? {});
$[0] = node?.fields;
$[1] = t0;
} else {
t0 = $[1];
}
if ($[2] !== condition || $[3] !== node || $[4] !== t0) {
for (const key of t0) {
if (condition) {
value = new Class(node.fields?.[field]);
break;
}
}
$[2] = condition;
$[3] = node;
$[4] = t0;
$[5] = value;
} else {
value = $[5];
}
return value;
}
```
### Eval output
(kind: exception) Fixture not implemented
@@ -0,0 +1,16 @@
function useHook(nodeID, condition) {
const graph = useContext(GraphContext);
const node = nodeID != null ? graph[nodeID] : null;
// (2) Instead we can create a scope around the loop since the loop produces an escaping value
let value;
for (const key of Object.keys(node?.fields ?? {})) {
if (condition) {
// (1) We currently create a scope just for this instruction, then later prune the scope because
// it's inside a loop
value = new Class(node.fields?.[field]);
break;
}
}
return value;
}