8000 [compiler][bugfix] expand StoreContext to const / let / function variants by mofeiZ · Pull Request #32747 · facebook/react · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[compiler][bugfix] expand StoreContext to const / let / function variants #32747

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 30, 2025

Conversation

mofeiZ
Copy link
Contributor
@mofeiZ mofeiZ commented Mar 25, 2025
function Component() {
  useEffect(() => {
    let hasCleanedUp = false;
    document.addEventListener(..., () => hasCleanedUp ? foo() : bar());
    // effect return values shouldn't be typed as frozen
    return () => {
      hasCleanedUp = true;
    }
  };
}

Problem

PruneHoistedContexts currently strips hoisted declarations and rewrites the first StoreContext reassignment to a declaration. For example, in the following example, instruction 0 is removed while a synthetic DeclareContext let is inserted before instruction 1.

// source
const cb = () => x; // reference that causes x to be hoisted

let x = 4;
x = 5;

// React Compiler IR
[0] DeclareContext HoistedLet 'x'
...
[1] StoreContext reassign 'x' = 4
[2] StoreContext reassign 'x' = 5

Currently, we don't account for DeclareContext let. As a result, we're rewriting to insert duplicate declarations.

// source
const cb = () => x; // reference that causes x to be hoisted

let x;
x = 5;

// React Compiler IR
[0] DeclareContext HoistedLet 'x'
...
[1] DeclareContext Let 'x'
[2] StoreContext reassign 'x' = 5

Solution

Instead of always lowering context variables to a DeclareContext followed by a StoreContext reassign, we can keep kind: 'Const' | 'Let' | 'Reassign' | etc on StoreContext.
Pros:

  • retain more information in HIR, so we can codegen easily const and let context variable declarations back
  • pruning hoisted DeclareContext instructions is simple.

Cons:

  • passes are more verbose as we need to check for both DeclareContext and StoreContext declarations

(note: also see alternative implementation in #32745)

Testing

Context variables are tricky. I synced and diffed changes in a large meta codebase and feel pretty confident about landing this. About 0.01% of compiled files changed. Among these changes, ~25% were direct bugfixes. The other changes were primarily due to changed (corrected) mutable ranges from #33047. I tried to represent most interesting changes in new test fixtures

`

@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label Mar 25, 2025
Comment on lines +904 to +917
/**
* StoreContext kinds:
* Reassign: context variable reassignment in source
* Const: const declaration + assignment in source
* ('const' context vars are ones whose declarations are hoisted)
* Let: let declaration + assignment in source
* Function: function declaration in source (similar to `const`)
*/
lvalue: {
kind: InstructionKind.Reassign;
kind:
| InstructionKind.Reassign
| InstructionKind.Const
| InstructionKind.Let
| InstructionKind.Function;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the key change. Instead of always lowering context variables to a DeclareContext followed by a StoreContext reassign, we can keep the kind

@mofeiZ mofeiZ marked this pull request as ready for review March 25, 2025 18:33
mofeiZ added a commit that referenced this pull request Mar 25, 2025
…tions

(note: also see alternative implementation in #32747)

`PruneHoistedContexts` currently strips hoisted declarations and rewrites the first `StoreContext` reassignment to a declaration. For example, in the following example, instruction 0 is removed while a synthetic `DeclareContext let` is inserted before instruction 1.

```js
// source
const cb = () => x; // reference that causes x to be hoisted

let x = 4;
x = 5;

// React Compiler IR
[0] DeclareContext HoistedLet 'x'
...
[1] StoreContext reassign 'x' = 4
[2] StoreContext reassign 'x' = 5
```

Currently, we don't account for `DeclareContext let`. As a result, we're rewriting to insert duplicate declarations.
For the below example, we should only remove instruction 0 (no need to insert a DeclareContext `let` since one is already present).
```js
// source
const cb = () => x; // reference that causes x to be hoisted

let x;
x = 5;

// React Compiler IR
[0] DeclareContext HoistedLet 'x'
...
[1] DeclareContext Let 'x'
[2] StoreContext reassign 'x' = 5
```
@mofeiZ mofeiZ changed the title [compiler][bugfix] expand StoreContext to const / let / function variants [compiler][bugfix] expand StoreContext to const / let / function variants, after fixes, tmp Apr 28, 2025
Copy link
Member
@josephsavona josephsavona left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice attention to detail

@@ -30,8 +30,7 @@ function Component(props) {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
let x;
x = null;
let x = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so good

mofeiZ added a commit that referenced this pull request Apr 30, 2025
Fixes an edge case in React Compiler's effects inference model.

Returned values should only be typed as 'frozen' if they are (1) local
and (2) not a function expression which may capture and mutate this
function's outer context. See test fixtures for details
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33047).
* #32765
* #32747
* __->__ #33047
github-actions bot pushed a commit that referenced this pull request Apr 30, 2025
Fixes an edge case in React Compiler's effects inference model.

Returned values should only be typed as 'frozen' if they are (1) local
and (2) not a function expression which may capture and mutate this
function's outer context. See test fixtures for details
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33047).
* #32765
* #32747
* __->__ #33047

DiffTrain build for [12f4cb8](12f4cb8)
@mofeiZ mofeiZ changed the title [compiler][bugfix] expand StoreContext to const / let / function variants, after fixes, tmp [compiler][bugfix] expand StoreContext to const / let / function variants Apr 30, 2025
…ants

```js
function Component() {
  useEffect(() => {
    let hasCleanedUp = false;
    document.addEventListener(..., () => hasCleanedUp ? foo() : bar());
    // effect return values shouldn't be typed as frozen
    return () => {
      hasCleanedUp = true;
    }
  };
}
```
### Problem
`PruneHoistedContexts` currently strips hoisted declarations and rewrites the first `StoreContext` reassignment to a declaration. For example, in the following example, instruction 0 is removed while a synthetic `DeclareContext let` is inserted before instruction 1.

```js
// source
const cb = () => x; // reference that causes x to be hoisted

let x = 4;
x = 5;

// React Compiler IR
[0] DeclareContext HoistedLet 'x'
...
[1] StoreContext reassign 'x' = 4
[2] StoreContext reassign 'x' = 5
```

Currently, we don't account for `DeclareContext let`. As a result, we're rewriting to insert duplicate declarations.
```js
// source
const cb = () => x; // reference that causes x to be hoisted

let x;
x = 5;

// React Compiler IR
[0] DeclareContext HoistedLet 'x'
...
[1] DeclareContext Let 'x'
[2] StoreContext reassign 'x' = 5
```

### Solution

Instead of always lowering context variables to a DeclareContext followed by a StoreContext reassign, we can keep `kind: 'Const' | 'Let' | 'Reassign' | etc` on StoreContext.
Pros:
- retain more information in HIR, so we can codegen easily `const` and `let` context variable declarations back
- pruning hoisted `DeclareContext` instructions is simple.

Cons:
- passes are more verbose as we need to check for both `DeclareContext` and `StoreContext` declarations

~(note: also see alternative implementation in #32745

### Testing
Context variables are tricky. I synced and diffed changes in a large meta codebase and feel pretty confident about landing this. About 0.01% of compiled files changed. Among these changes, ~25% were [direct bugfixes](https://www.internalfb.com/phabricator/paste/view/P1800029094). The [other changes](https://www.internalfb.com/phabricator/paste/view/P1800028575) were primarily due to changed (corrected) mutable ranges from #33047. I tried to represent most interesting changes in new test fixtures

`
@mofeiZ mofeiZ merged commit 9d795d3 into main Apr 30, 2025
19 of 20 checks passed
github-actions bot pushed a commit that referenced this pull request Apr 30, 2025
…ants (#32747)

```js
function Component() {
  useEffect(() => {
    let hasCleanedUp = false;
    document.addEventListener(..., () => hasCleanedUp ? foo() : bar());
    // effect return values shouldn't be typed as frozen
    return () => {
      hasCleanedUp = true;
    }
  };
}
```
### Problem
`PruneHoistedContexts` currently strips hoisted declarations and
rewrites the first `StoreContext` reassignment to a declaration. For
example, in the following example, instruction 0 is removed while a
synthetic `DeclareContext let` is inserted before instruction 1.

```js
// source
const cb = () => x; // reference that causes x to be hoisted

let x = 4;
x = 5;

// React Compiler IR
[0] DeclareContext HoistedLet 'x'
...
[1] StoreContext reassign 'x' = 4
[2] StoreContext reassign 'x' = 5
```

Currently, we don't account for `DeclareContext let`. As a result, we're
rewriting to insert duplicate declarations.
```js
// source
const cb = () => x; // reference that causes x to be hoisted

let x;
x = 5;

// React Compiler IR
[0] DeclareContext HoistedLet 'x'
...
[1] DeclareContext Let 'x'
[2] StoreContext reassign 'x' = 5
```

### Solution

Instead of always lowering context variables to a DeclareContext
followed by a StoreContext reassign, we can keep `kind: 'Const' | 'Let'
| 'Reassign' | etc` on StoreContext.
Pros:
- retain more information in HIR, so we can codegen easily `const` and
`let` context variable declarations back
- pruning hoisted `DeclareContext` instructions is simple.

Cons:
- passes are more verbose as we need to check for both `DeclareContext`
and `StoreContext` declarations

~(note: also see alternative implementation in
#32745

### Testing
Context variables are tricky. I synced and diffed changes in a large
meta codebase and feel pretty confident about landing this. About 0.01%
of compiled files changed. Among these changes, ~25% were [direct
bugfixes](https://www.internalfb.com/phabricator/paste/view/P1800029094).
The [other
changes](https://www.internalfb.com/phabricator/paste/view/P1800028575)
were primarily due to changed (corrected) mutable ranges from
#33047. I tried to represent most
interesting changes in new test fixtures

`

DiffTrain build for [9d795d3](9d795d3)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0