Skip to content
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

[compiler][rewrite] Patch logic for aligning scopes to non-value blocks #29891

Merged
merged 6 commits into from
Jun 25, 2024

Conversation

mofeiZ
Copy link
Contributor

@mofeiZ mofeiZ commented Jun 13, 2024

Stack from ghstack (oldest at bottom):

Our previous logic for aligning scopes to block scopes constructs a tree of block and scope nodes. We ensured that blocks always mapped to the same node as their fallthroughs. e.g.

// source
a();
if (...) {
  b();
}
c();

// HIR
bb0:
a()
if test=... consequent=bb1 fallthrough=bb2

bb1:
b()
goto bb2

bb2:
c()

// AlignReactiveScopesToBlockScopesHIR nodes
Root node (maps to both bb0 and bb2)
  |- bb1
  |- ...

There are two issues with the existing implementation:

  1. Only scopes that overlap with the beginning of a block are aligned correctly. This is because the traversal does not store information about the block-fallthrough pair for scopes that begin within the block-fallthrough range.
\# This case gets handled correctly
         ┌──────────────┐
         │              │
         block start    block end

scope start     scope end
│               │
└───────────────┘

\# But not this one!
┌──────────────┐
│              │
block start    block end

          scope start     scope end
          │               │
          └───────────────┘
  1. Only scopes that are directly used by a block is considered. See the align-scopes-nested-block-structure fixture for details.

Copy link

vercel bot commented Jun 13, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
react-compiler-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 25, 2024 8:20pm

mofeiZ added a commit that referenced this pull request Jun 13, 2024
ghstack-source-id: 18fa632d430e36b62a103de48475d03fac4a915b
Pull Request resolved: #29891
@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Jun 13, 2024
@react-sizebot
Copy link

react-sizebot commented Jun 13, 2024

Comparing: 133ada7...baf7098

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.68 kB 6.68 kB = 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 497.93 kB 497.93 kB = 89.26 kB 89.26 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB +0.05% 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 502.75 kB 502.75 kB = 89.96 kB 89.96 kB
facebook-www/ReactDOM-prod.classic.js = 597.10 kB 597.10 kB = 105.31 kB 105.31 kB
facebook-www/ReactDOM-prod.modern.js = 571.44 kB 571.44 kB = 101.24 kB 101.24 kB
test_utils/ReactAllWarnings.js Deleted 62.88 kB 0.00 kB Deleted 15.69 kB 0.00 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
test_utils/ReactAllWarnings.js Deleted 62.88 kB 0.00 kB Deleted 15.69 kB 0.00 kB

Generated by 🚫 dangerJS against baf7098

mofeiZ added a commit that referenced this pull request Jun 14, 2024
Our previous logic for aligning scopes to block scopes constructs a tree of block and scope nodes. We ensured that blocks always mapped to the same node as their fallthroughs. e.g.
```js
// source
a();
if (...) {
  b();
}
c();

// HIR
bb0:
a()
if test=... consequent=bb1 fallthrough=bb2

bb1:
b()
goto bb2

bb2:
c()

// AlignReactiveScopesToBlockScopesHIR nodes
Root node (maps to both bb0 and bb2)
  |- bb1
  |- ...
```

There are two issues with the existing implementation:
1. Only scopes that overlap with the beginning of a block are aligned correctly. This is because the traversal does not store information about the block-fallthrough pair for scopes that begin *within* the block-fallthrough range.
```
         ┌──────────────┐
         │              │
         block start    block end

scope start     scope end
│               │
└───────────────┘
┌──────────────┐
│              │
block start    block end

          scope start     scope end
          │               │
          └───────────────┘
```
2. Only scopes that are directly used by a block is considered. See the `align-scopes-nested-block-structure` fixture for details.

ghstack-source-id: d9051284cec15caa1e0a6840cd20ead7acff122a
Pull Request resolved: #29891
…-value blocks"

Our previous logic for aligning scopes to block scopes constructs a tree of block and scope nodes. We ensured that blocks always mapped to the same node as their fallthroughs. e.g.
```js
// source
a();
if (...) {
  b();
}
c();

// HIR
bb0:
a()
if test=... consequent=bb1 fallthrough=bb2

bb1:
b()
goto bb2

bb2:
c()

// AlignReactiveScopesToBlockScopesHIR nodes
Root node (maps to both bb0 and bb2)
  |- bb1
  |- ...
```

There are two issues with the existing implementation:
1. Only scopes that overlap with the beginning of a block are aligned correctly. This is because the traversal does not store information about the block-fallthrough pair for scopes that begin *within* the block-fallthrough range.
```
         ┌──────────────┐
         │              │
         block start    block end

scope start     scope end
│               │
└───────────────┘
┌──────────────┐
│              │
block start    block end

          scope start     scope end
          │               │
          └───────────────┘
```
2. Only scopes that are directly used by a block is considered. See the `align-scopes-nested-block-structure` fixture for details.

[ghstack-poisoned]
@mofeiZ mofeiZ changed the title [compiler][draft] Patch logic for aligning scopes to non-value blocks [compiler][rewrite] Patch logic for aligning scopes to non-value blocks Jun 14, 2024
mofeiZ added a commit that referenced this pull request Jun 14, 2024
Our previous logic for aligning scopes to block scopes constructs a tree of block and scope nodes. We ensured that blocks always mapped to the same node as their fallthroughs. e.g.
```js
// source
a();
if (...) {
  b();
}
c();

// HIR
bb0:
a()
if test=... consequent=bb1 fallthrough=bb2

bb1:
b()
goto bb2

bb2:
c()

// AlignReactiveScopesToBlockScopesHIR nodes
Root node (maps to both bb0 and bb2)
  |- bb1
  |- ...
```

There are two issues with the existing implementation:
1. Only scopes that overlap with the beginning of a block are aligned correctly. This is because the traversal does not store information about the block-fallthrough pair for scopes that begin *within* the block-fallthrough range.
```
         ┌──────────────┐
         │              │
         block start    block end

scope start     scope end
│               │
└───────────────┘
┌──────────────┐
│              │
block start    block end

          scope start     scope end
          │               │
          └───────────────┘
```
2. Only scopes that are directly used by a block is considered. See the `align-scopes-nested-block-structure` fixture for details.

ghstack-source-id: d9051284cec15caa1e0a6840cd20ead7acff122a
Pull Request resolved: #29891
…-value blocks"

Our previous logic for aligning scopes to block scopes constructs a tree of block and scope nodes. We ensured that blocks always mapped to the same node as their fallthroughs. e.g.
```js
// source
a();
if (...) {
  b();
}
c();

// HIR
bb0:
a()
if test=... consequent=bb1 fallthrough=bb2

bb1:
b()
goto bb2

bb2:
c()

// AlignReactiveScopesToBlockScopesHIR nodes
Root node (maps to both bb0 and bb2)
  |- bb1
  |- ...
```

There are two issues with the existing implementation:
1. Only scopes that overlap with the beginning of a block are aligned correctly. This is because the traversal does not store information about the block-fallthrough pair for scopes that begin *within* the block-fallthrough range.
```
\# This case gets handled correctly
         ┌──────────────┐
         │              │
         block start    block end

scope start     scope end
│               │
└───────────────┘

\# But not this one!
┌──────────────┐
│              │
block start    block end

          scope start     scope end
          │               │
          └───────────────┘
```
2. Only scopes that are directly used by a block is considered. See the `align-scopes-nested-block-structure` fixture for details.

[ghstack-poisoned]
mofeiZ added a commit that referenced this pull request Jun 14, 2024
Our previous logic for aligning scopes to block scopes constructs a tree of block and scope nodes. We ensured that blocks always mapped to the same node as their fallthroughs. e.g.
```js
// source
a();
if (...) {
  b();
}
c();

// HIR
bb0:
a()
if test=... consequent=bb1 fallthrough=bb2

bb1:
b()
goto bb2

bb2:
c()

// AlignReactiveScopesToBlockScopesHIR nodes
Root node (maps to both bb0 and bb2)
  |- bb1
  |- ...
```

There are two issues with the existing implementation:
1. Only scopes that overlap with the beginning of a block are aligned correctly. This is because the traversal does not store information about the block-fallthrough pair for scopes that begin *within* the block-fallthrough range.
```
\# This case gets handled correctly
         ┌──────────────┐
         │              │
         block start    block end

scope start     scope end
│               │
└───────────────┘

\# But not this one!
┌──────────────┐
│              │
block start    block end

          scope start     scope end
          │               │
          └───────────────┘
```
2. Only scopes that are directly used by a block is considered. See the `align-scopes-nested-block-structure` fixture for details.

ghstack-source-id: d9051284cec15caa1e0a6840cd20ead7acff122a
Pull Request resolved: #29891
@mofeiZ mofeiZ marked this pull request as ready for review June 14, 2024 00:34
@mofeiZ
Copy link
Contributor Author

mofeiZ commented Jun 14, 2024

Tested by syncing internally. See that we previously had many bailouts in P1413569380. After syncing this change, there are no bailouts of this category in P1413721911. Also observe that no compilation output changed (P1414504972)

@josephsavona
Copy link
Contributor

Awesome, thanks for the thorough testing! I’ll review in the morning

@@ -41,7 +38,7 @@
* │return s; │◄──┘
* └───────────┘
Copy link
Contributor

@josephsavona josephsavona Jun 14, 2024

Choose a reason for hiding this comment

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

important question: what did you use to make this diagram?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I used https://asciiflow.com with a little manual editing!

Copy link
Contributor

@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.

I need to take another read through this to internalize and might have more feedback, but let's ship given the thorough testing.

Comment on lines 28 to 37
function retainWhere_Set<T>(
items: Set<T>,
predicate: (item: T) => boolean
): void {
for (const item of items) {
if (!predicate(item)) {
items.delete(item);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: let's move to utils

description: `No node for block bb${block.id} (${block.kind})`,
});
const startingId = block.instructions[0]?.id ?? block.terminal.id;
retainWhere_Set(activeScopes, (scope) => scope.range.end >= startingId);
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be >, right? End is exclusive. I think this probably happens to work just because any mutating instruction within the inner blocks will be followed by a terminal successor s.t. it doesn't matter, but it's more technically correct to use > here.

id: makeInstructionId(0),
};
blockNodes.set(fn.body.entry, rootNode);
const activeInnerBlockRanges: Array<{
Copy link
Contributor

Choose a reason for hiding this comment

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

The naming of this was confusing, since it's really tracking outer block -> fallthrough ranges. We use this to extend the range of inner blocks, but it doesn't store inner block ranges. Let's rename?

…-value blocks"

Our previous logic for aligning scopes to block scopes constructs a tree of block and scope nodes. We ensured that blocks always mapped to the same node as their fallthroughs. e.g.
```js
// source
a();
if (...) {
  b();
}
c();

// HIR
bb0:
a()
if test=... consequent=bb1 fallthrough=bb2

bb1:
b()
goto bb2

bb2:
c()

// AlignReactiveScopesToBlockScopesHIR nodes
Root node (maps to both bb0 and bb2)
  |- bb1
  |- ...
```

There are two issues with the existing implementation:
1. Only scopes that overlap with the beginning of a block are aligned correctly. This is because the traversal does not store information about the block-fallthrough pair for scopes that begin *within* the block-fallthrough range.
```
\# This case gets handled correctly
         ┌──────────────┐
         │              │
         block start    block end

scope start     scope end
│               │
└───────────────┘

\# But not this one!
┌──────────────┐
│              │
block start    block end

          scope start     scope end
          │               │
          └───────────────┘
```
2. Only scopes that are directly used by a block is considered. See the `align-scopes-nested-block-structure` fixture for details.

[ghstack-poisoned]
Copy link
Contributor

@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.

Nice job, the algorithm makes sense to me, especially w the renaming to clarify intent. Tests look great but one question on a case of missing memoization (?)

try {
let thing = null;
if (cond) {
thing = makeObject_Primitives();
Copy link
Contributor

Choose a reason for hiding this comment

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

just curious, why do we lose memoization on this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh memoization was pruned because we didn't return item. Just updated the fixture to return item to avoid pruning!

…-value blocks"

Our previous logic for aligning scopes to block scopes constructs a tree of block and scope nodes. We ensured that blocks always mapped to the same node as their fallthroughs. e.g.
```js
// source
a();
if (...) {
  b();
}
c();

// HIR
bb0:
a()
if test=... consequent=bb1 fallthrough=bb2

bb1:
b()
goto bb2

bb2:
c()

// AlignReactiveScopesToBlockScopesHIR nodes
Root node (maps to both bb0 and bb2)
  |- bb1
  |- ...
```

There are two issues with the existing implementation:
1. Only scopes that overlap with the beginning of a block are aligned correctly. This is because the traversal does not store information about the block-fallthrough pair for scopes that begin *within* the block-fallthrough range.
```
\# This case gets handled correctly
         ┌──────────────┐
         │              │
         block start    block end

scope start     scope end
│               │
└───────────────┘

\# But not this one!
┌──────────────┐
│              │
block start    block end

          scope start     scope end
          │               │
          └───────────────┘
```
2. Only scopes that are directly used by a block is considered. See the `align-scopes-nested-block-structure` fixture for details.

[ghstack-poisoned]
@mofeiZ mofeiZ merged commit baf7098 into gh/mofeiZ/2/base Jun 25, 2024
36 checks passed
mofeiZ added a commit that referenced this pull request Jun 25, 2024
Our previous logic for aligning scopes to block scopes constructs a tree of block and scope nodes. We ensured that blocks always mapped to the same node as their fallthroughs. e.g.
```js
// source
a();
if (...) {
  b();
}
c();

// HIR
bb0:
a()
if test=... consequent=bb1 fallthrough=bb2

bb1:
b()
goto bb2

bb2:
c()

// AlignReactiveScopesToBlockScopesHIR nodes
Root node (maps to both bb0 and bb2)
  |- bb1
  |- ...
```

There are two issues with the existing implementation:
1. Only scopes that overlap with the beginning of a block are aligned correctly. This is because the traversal does not store information about the block-fallthrough pair for scopes that begin *within* the block-fallthrough range.
```
\# This case gets handled correctly
         ┌──────────────┐
         │              │
         block start    block end

scope start     scope end
│               │
└───────────────┘

\# But not this one!
┌──────────────┐
│              │
block start    block end

          scope start     scope end
          │               │
          └───────────────┘
```
2. Only scopes that are directly used by a block is considered. See the `align-scopes-nested-block-structure` fixture for details.

ghstack-source-id: 327dec5019483666f81c8156ac0c666ccad511b3
Pull Request resolved: #29891
@mofeiZ mofeiZ deleted the gh/mofeiZ/2/head branch June 25, 2024 21:00
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.

4 participants