Remove unnecessary scopes for value blocks

The example earlier in the stack had unreachable code in the output because 
there was an unnecessary memoization block around an assignment. This was a 
holdover from before we moved the logic to expand mutable ranges for phis from 
LeaveSSA to InferMutableRanges. We were conservatively assigning a mutable range 
to all variables with a phi, even those that didn't strictly need one. 

Removing the range extension logic in LeaveSSA fixed the issue, but uncovered 
the fact that AlignReactiveScopesToBlockScopes was missing a case to handle 
optionals. 

## Test Plan 

Synced internally and ran a snapshot/comparison of compilation before/after 
(P1197734337 for those curious). The majority of components get fewer memo slots 
thanks to not needing to memoize non-allocating value block expressions like 
ternaries/optionals. In a few cases, the fact that we're no longer assigning a 
mutable range for value blocks (unless there is actually a mutation!) means we 
get more fine-grained memoization and increase the number of memoization blocks. 
So overall this appears to be correct, improve memoization, and reduce code 
size.
This commit is contained in:
Joe Savona
2024-03-19 13:28:47 -07:00
parent c03f1c809a
commit 93e0815b26
8 changed files with 55 additions and 157 deletions
@@ -84,6 +84,7 @@ class Visitor extends ReactiveFunctionVisitor<Context> {
override visitInstruction(instr: ReactiveInstruction, state: Context): void {
switch (instr.value.kind) {
case "OptionalExpression":
case "SequenceExpression":
case "ConditionalExpression":
case "LogicalExpression": {
@@ -14,7 +14,6 @@ import {
InstructionKind,
LValue,
LValuePattern,
makeInstructionId,
Phi,
Place,
} from "../HIR/HIR";
@@ -312,50 +311,6 @@ export function leaveSSA(fn: HIRFunction): void {
} else {
reassignmentPhis.push({ phi, block: phiBlock });
}
const hasBackEdge = backEdgePhis.has(phi);
const isPhiMutatedAfterCreation: boolean =
phi.id.mutableRange.end >
(phiBlock.instructions.at(0)?.id ?? phiBlock.terminal.id);
/*
* Named variables whose phi doesn't have a back-edge can potentially be independenly
* memoized, depending on whether the phi is after its creation.
*/
if (phi.id.name !== null && !hasBackEdge) {
if (!isPhiMutatedAfterCreation) {
/*
* Simple case: predecesor-only values flowing into a phi, which is never modified:
* adjust the phi's range to clarify that the identifier does not mutate
*/
phi.id.mutableRange.start = terminal.id;
phi.id.mutableRange.end = makeInstructionId(terminal.id + 1);
} else {
/*
* Predecessor only values flow into a phi, which is modified later:
* all operands flow into the phi and can be modified, must extend their ranges
*/
for (const [, operand] of phi.operands) {
operand.mutableRange.end = phi.id.mutableRange.end;
}
}
continue;
}
/*
* Otherwise this is a temporary phi (logical or ternary) or occurs in a loop. In either
* case we can't independently memoize any of the values: unify their ranges to span the
* min(start) to max(end) so that we create a single scope for all the computation.
*/
let start = block.terminal.id as number;
let end = Number.MIN_SAFE_INTEGER;
const operands = [phi.id, ...phi.operands.values()];
for (const operand of operands) {
start = Math.min(start, operand.mutableRange.start);
end = Math.max(end, operand.mutableRange.end);
}
for (const operand of operands) {
operand.mutableRange.start = makeInstructionId(start);
operand.mutableRange.end = makeInstructionId(end);
}
}
}
const fallthroughId = terminalFallthrough(terminal);
@@ -56,7 +56,7 @@ function useFragment(_arg1, _arg2) {
}
function Component(props) {
const $ = useMemoCache(16);
const $ = useMemoCache(14);
const post = useFragment(graphql`...`, props.post);
let media;
let allUrls;
@@ -65,48 +65,40 @@ function Component(props) {
allUrls = [];
const { media: t0, comments: t1, urls: t2 } = post;
media = t0 === undefined ? null : t0;
let t3;
if ($[4] !== t0) {
t3 = t0 === undefined ? null : t0;
$[4] = t0;
if ($[4] !== t1) {
t3 = t1 === undefined ? [] : t1;
$[4] = t1;
$[5] = t3;
} else {
t3 = $[5];
}
media = t3;
const comments = t3;
let t4;
if ($[6] !== t1) {
t4 = t1 === undefined ? [] : t1;
$[6] = t1;
if ($[6] !== t2) {
t4 = t2 === undefined ? [] : t2;
$[6] = t2;
$[7] = t4;
} else {
t4 = $[7];
}
const comments = t4;
const urls = t4;
let t5;
if ($[8] !== t2) {
t5 = t2 === undefined ? [] : t2;
$[8] = t2;
$[9] = t5;
} else {
t5 = $[9];
}
const urls = t5;
let t6;
if ($[10] !== comments.length) {
t6 = (e) => {
if ($[8] !== comments.length) {
t5 = (e) => {
if (!comments.length) {
return;
}
console.log(comments.length);
};
$[10] = comments.length;
$[11] = t6;
$[8] = comments.length;
$[9] = t5;
} else {
t6 = $[11];
t5 = $[9];
}
onClick = t6;
onClick = t5;
allUrls.push(...urls);
$[0] = post;
@@ -119,14 +111,14 @@ function Component(props) {
onClick = $[3];
}
let t0;
if ($[12] !== media || $[13] !== allUrls || $[14] !== onClick) {
if ($[10] !== media || $[11] !== allUrls || $[12] !== onClick) {
t0 = <Stringify media={media} allUrls={allUrls} onClick={onClick} />;
$[12] = media;
$[13] = allUrls;
$[14] = onClick;
$[15] = t0;
$[10] = media;
$[11] = allUrls;
$[12] = onClick;
$[13] = t0;
} else {
t0 = $[15];
t0 = $[13];
}
return t0;
}
@@ -24,7 +24,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(10);
const $ = useMemoCache(6);
let t0;
if ($[0] !== props.a) {
t0 = [props.a];
@@ -52,17 +52,7 @@ function Component(props) {
t2 = $[5];
}
const c = t2;
let t3;
if ($[6] !== a || $[7] !== b || $[8] !== c) {
t3 = (a && b) || c;
$[6] = a;
$[7] = b;
$[8] = c;
$[9] = t3;
} else {
t3 = $[9];
}
return t3;
return (a && b) || c;
}
export const FIXTURE_ENTRYPOINT = {
@@ -14,7 +14,7 @@ function Component(props) {
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(5);
const $ = useMemoCache(2);
let t0;
if ($[0] !== props) {
t0 = makeObject(props);
@@ -24,16 +24,7 @@ function Component(props) {
t0 = $[1];
}
const object = t0;
let t1;
if ($[2] !== object || $[3] !== props) {
t1 = object?.[props.key];
$[2] = object;
$[3] = props;
$[4] = t1;
} else {
t1 = $[4];
}
return t1;
return object?.[props.key];
}
```
@@ -14,7 +14,7 @@ function Component(props) {
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(3);
const $ = useMemoCache(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = makeObject();
@@ -23,15 +23,7 @@ function Component(props) {
t0 = $[0];
}
const x = t0;
let t1;
if ($[1] !== props) {
t1 = x.y?.[props.a?.[props.b?.[props.c]]];
$[1] = props;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
return x.y?.[props.a?.[props.b?.[props.c]]];
}
```
@@ -54,7 +54,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(t0) {
const $ = useMemoCache(11);
const $ = useMemoCache(8);
const { label, highlightedItem } = t0;
const serverTime = useServerTime();
let t1;
@@ -63,16 +63,8 @@ function Component(t0) {
const highlight = new Highlight(highlightedItem);
const time = serverTime.get();
let t2;
if ($[5] !== time || $[6] !== label) {
t2 = time / 1000 || label;
$[5] = time;
$[6] = label;
$[7] = t2;
} else {
t2 = $[7];
}
timestampLabel = t2;
timestampLabel = time / 1000 || label;
t1 = highlight.render();
$[0] = highlightedItem;
@@ -85,18 +77,18 @@ function Component(t0) {
timestampLabel = $[4];
}
let t2;
if ($[8] !== t1 || $[9] !== timestampLabel) {
if ($[5] !== t1 || $[6] !== timestampLabel) {
t2 = (
<>
{t1}
{timestampLabel}
</>
);
$[8] = t1;
$[9] = timestampLabel;
$[10] = t2;
$[5] = t1;
$[6] = timestampLabel;
$[7] = t2;
} else {
t2 = $[10];
t2 = $[7];
}
return t2;
}
@@ -49,60 +49,45 @@ import {
import { ValidateMemoization, identity } from "shared-runtime";
function Component(t0) {
const $ = useMemoCache(10);
const $ = useMemoCache(7);
const { value } = t0;
let t1;
bb13: {
if (value == null) {
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = null;
break bb13;
$[0] = t1;
} else {
t1 = $[0];
}
t1 = null;
break bb13;
}
try {
let t3;
if ($[1] !== value) {
if ($[0] !== value) {
t3 = { value };
$[1] = value;
$[2] = t3;
$[0] = value;
$[1] = t3;
} else {
t3 = $[2];
}
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t1 = t3;
$[3] = t1;
} else {
t1 = $[3];
t3 = $[1];
}
t1 = t3;
} catch (t2) {
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
t1 = null;
$[4] = t1;
} else {
t1 = $[4];
}
t1 = null;
}
}
const result = t1;
let t2;
if ($[5] !== value) {
if ($[2] !== value) {
t2 = [value];
$[5] = value;
$[6] = t2;
$[2] = value;
$[3] = t2;
} else {
t2 = $[6];
t2 = $[3];
}
let t3;
if ($[7] !== t2 || $[8] !== result) {
if ($[4] !== t2 || $[5] !== result) {
t3 = <ValidateMemoization inputs={t2} output={result} />;
$[7] = t2;
$[8] = result;
$[9] = t3;
$[4] = t2;
$[5] = result;
$[6] = t3;
} else {
t3 = $[9];
t3 = $[6];
}
return t3;
}