Compare commits

...

9 Commits

Author SHA1 Message Date
Mofei Zhang
805efd118a Update (base update)
[ghstack-poisoned]
2024-10-02 16:18:33 -04:00
Mofei Zhang
adb0b37490 Update (base update)
[ghstack-poisoned]
2024-10-02 12:53:59 -04:00
Mofei Zhang
09189fc64a Update (base update)
[ghstack-poisoned]
2024-10-01 13:52:06 -04:00
Mofei Zhang
98f018c80b Update (base update)
[ghstack-poisoned]
2024-09-30 15:56:57 -04:00
Mofei Zhang
199f3d71d0 Update (base update)
[ghstack-poisoned]
2024-09-30 14:27:44 -04:00
Mofei Zhang
8acf15e0af Update (base update)
[ghstack-poisoned]
2024-09-30 12:24:27 -04:00
Mofei Zhang
d2a7be0ac0 Update (base update)
[ghstack-poisoned]
2024-09-26 18:29:53 -04:00
Mofei Zhang
2e6b5cd789 Update (base update)
[ghstack-poisoned]
2024-09-26 17:26:22 -04:00
Mofei Zhang
b27edfbb4b Update (base update)
[ghstack-poisoned]
2024-09-25 19:41:40 -04:00
8 changed files with 250 additions and 71 deletions

View File

@@ -15,7 +15,7 @@ import {
HIRFunction,
Identifier,
IdentifierId,
InstructionId,
InstructionValue,
ReactiveScopeDependency,
ScopeId,
} from './HIR';
@@ -209,33 +209,23 @@ class PropertyPathRegistry {
}
}
function addNonNullPropertyPath(
source: Identifier,
sourceNode: PropertyPathNode,
instrId: InstructionId,
knownImmutableIdentifiers: Set<IdentifierId>,
result: Set<PropertyPathNode>,
): void {
/**
* Since this runs *after* buildReactiveScopeTerminals, identifier mutable ranges
* are not valid with respect to current instruction id numbering.
* We use attached reactive scope ranges as a proxy for mutable range, but this
* is an overestimate as (1) scope ranges merge and align to form valid program
* blocks and (2) passes like MemoizeFbtAndMacroOperands may assign scopes to
* non-mutable identifiers.
*
* See comment at top of function for why we track known immutable identifiers.
*/
const isMutableAtInstr =
source.mutableRange.end > source.mutableRange.start + 1 &&
source.scope != null &&
inRange({id: instrId}, source.scope.range);
if (
!isMutableAtInstr ||
knownImmutableIdentifiers.has(sourceNode.fullPath.identifier.id)
) {
result.add(sourceNode);
function getMaybeNonNullInInstruction(
instr: InstructionValue,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
registry: PropertyPathRegistry,
): PropertyPathNode | null {
let path = null;
if (instr.kind === 'PropertyLoad') {
path = temporaries.get(instr.object.identifier.id) ?? {
identifier: instr.object.identifier,
path: [],
};
} else if (instr.kind === 'Destructure') {
path = temporaries.get(instr.value.identifier.id) ?? null;
} else if (instr.kind === 'ComputedLoad') {
path = temporaries.get(instr.object.identifier.id) ?? null;
}
return path != null ? registry.getOrCreateProperty(path) : null;
}
function collectNonNullsInBlocks(
@@ -286,41 +276,38 @@ function collectNonNullsInBlocks(
);
}
for (const instr of block.instructions) {
if (instr.value.kind === 'PropertyLoad') {
const source = temporaries.get(instr.value.object.identifier.id) ?? {
identifier: instr.value.object.identifier,
path: [],
};
addNonNullPropertyPath(
instr.value.object.identifier,
registry.getOrCreateProperty(source),
instr.id,
knownImmutableIdentifiers,
assumedNonNullObjects,
);
} else if (instr.value.kind === 'Destructure') {
const source = instr.value.value.identifier.id;
const sourceNode = temporaries.get(source);
if (sourceNode != null) {
addNonNullPropertyPath(
instr.value.value.identifier,
registry.getOrCreateProperty(sourceNode),
instr.id,
knownImmutableIdentifiers,
assumedNonNullObjects,
);
}
} else if (instr.value.kind === 'ComputedLoad') {
const source = instr.value.object.identifier.id;
const sourceNode = temporaries.get(source);
if (sourceNode != null) {
addNonNullPropertyPath(
instr.value.object.identifier,
registry.getOrCreateProperty(sourceNode),
instr.id,
knownImmutableIdentifiers,
assumedNonNullObjects,
const maybeNonNull = getMaybeNonNullInInstruction(
instr.value,
temporaries,
registry,
);
if (maybeNonNull != null) {
const baseIdentifier = maybeNonNull.fullPath.identifier;
/**
* Since this runs *after* buildReactiveScopeTerminals, identifier mutable ranges
* are not valid with respect to current instruction id numbering.
* We use attached reactive scope ranges as a proxy for mutable range, but this
* is an overestimate as (1) scope ranges merge and align to form valid program
* blocks and (2) passes like MemoizeFbtAndMacroOperands may assign scopes to
* non-mutable identifiers.
*
* See comment at top of function for why we track known immutable identifiers.
*/
const isMutableAtInstr =
baseIdentifier.mutableRange.end >
baseIdentifier.mutableRange.start + 1 &&
baseIdentifier.scope != null &&
inRange(
{
id: instr.id,
},
baseIdentifier.scope.range,
);
if (
!isMutableAtInstr ||
knownImmutableIdentifiers.has(baseIdentifier.id)
) {
assumedNonNullObjects.add(maybeNonNull);
}
}
}

View File

@@ -0,0 +1,65 @@
## Input
```javascript
import {identity} from 'shared-runtime';
/**
* Not safe to hoist read of maybeNullObject.value.inner outside of the
* try-catch block, as that might throw
*/
function useFoo(maybeNullObject: {value: {inner: number}} | null) {
const y = [];
try {
y.push(identity(maybeNullObject.value.inner));
} catch {
y.push('null');
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [null],
sequentialRenders: [null, {value: 2}, {value: 3}, null],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { identity } from "shared-runtime";
/**
* Not safe to hoist read of maybeNullObject.value.inner outside of the
* try-catch block, as that might throw
*/
function useFoo(maybeNullObject) {
const $ = _c(2);
let y;
if ($[0] !== maybeNullObject.value.inner) {
y = [];
try {
y.push(identity(maybeNullObject.value.inner));
} catch {
y.push("null");
}
$[0] = maybeNullObject.value.inner;
$[1] = y;
} else {
y = $[1];
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [null],
sequentialRenders: [null, { value: 2 }, { value: 3 }, null],
};
```

View File

@@ -0,0 +1,22 @@
import {identity} from 'shared-runtime';
/**
* Not safe to hoist read of maybeNullObject.value.inner outside of the
* try-catch block, as that might throw
*/
function useFoo(maybeNullObject: {value: {inner: number}} | null) {
const y = [];
try {
y.push(identity(maybeNullObject.value.inner));
} catch {
y.push('null');
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [null],
sequentialRenders: [null, {value: 2}, {value: 3}, null],
};

View File

@@ -0,0 +1,79 @@
## Input
```javascript
// @enablePropagateDepsInHIR
import {identity} from 'shared-runtime';
/**
* Not safe to hoist read of maybeNullObject.value.inner outside of the
* try-catch block, as that might throw
*/
function useFoo(maybeNullObject: {value: {inner: number}} | null) {
const y = [];
try {
y.push(identity(maybeNullObject.value.inner));
} catch {
y.push('null');
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [null],
sequentialRenders: [null, {value: 2}, {value: 3}, null],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
import { identity } from "shared-runtime";
/**
* Not safe to hoist read of maybeNullObject.value.inner outside of the
* try-catch block, as that might throw
*/
function useFoo(maybeNullObject) {
const $ = _c(4);
let y;
if ($[0] !== maybeNullObject) {
y = [];
try {
let t0;
if ($[2] !== maybeNullObject.value.inner) {
t0 = identity(maybeNullObject.value.inner);
$[2] = maybeNullObject.value.inner;
$[3] = t0;
} else {
t0 = $[3];
}
y.push(t0);
} catch {
y.push("null");
}
$[0] = maybeNullObject;
$[1] = y;
} else {
y = $[1];
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [null],
sequentialRenders: [null, { value: 2 }, { value: 3 }, null],
};
```
### Eval output
(kind: ok) ["null"]
[null]
[null]
["null"]

View File

@@ -0,0 +1,23 @@
// @enablePropagateDepsInHIR
import {identity} from 'shared-runtime';
/**
* Not safe to hoist read of maybeNullObject.value.inner outside of the
* try-catch block, as that might throw
*/
function useFoo(maybeNullObject: {value: {inner: number}} | null) {
const y = [];
try {
y.push(identity(maybeNullObject.value.inner));
} catch {
y.push('null');
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [null],
sequentialRenders: [null, {value: 2}, {value: 3}, null],
};

View File

@@ -32,9 +32,9 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
const { throwInput } = require("shared-runtime");
function Component(props) {
const $ = _c(2);
const $ = _c(3);
let x;
if ($[0] !== props) {
if ($[0] !== props.y || $[1] !== props.e) {
try {
const y = [];
y.push(props.y);
@@ -44,10 +44,11 @@ function Component(props) {
e.push(props.e);
x = e;
}
$[0] = props;
$[1] = x;
$[0] = props.y;
$[1] = props.e;
$[2] = x;
} else {
x = $[1];
x = $[2];
}
return x;
}

View File

@@ -31,9 +31,9 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
const { throwInput } = require("shared-runtime");
function Component(props) {
const $ = _c(2);
const $ = _c(3);
let t0;
if ($[0] !== props) {
if ($[0] !== props.y || $[1] !== props.e) {
t0 = Symbol.for("react.early_return_sentinel");
bb0: {
try {
@@ -47,10 +47,11 @@ function Component(props) {
break bb0;
}
}
$[0] = props;
$[1] = t0;
$[0] = props.y;
$[1] = props.e;
$[2] = t0;
} else {
t0 = $[1];
t0 = $[2];
}
if (t0 !== Symbol.for("react.early_return_sentinel")) {
return t0;

View File

@@ -478,6 +478,7 @@ const skipFilter = new Set([
'fbt/bug-fbt-plural-multiple-function-calls',
'fbt/bug-fbt-plural-multiple-mixed-call-tag',
'bug-invalid-hoisting-functionexpr',
'bug-try-catch-maybe-null-dependency',
'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond',
'original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block',
'original-reactive-scopes-fork/bug-hoisted-declaration-with-scope',