Changes `shrink()` and `reversePostorderBlocks()` to modify the HIR in-place
rather than return a new function, for consistency with all our other passes
which mutate in-place (for performance reasons).
~~[Fizz] Duplicate completeBoundaryWithStyles to not reference globals~~
## Summary
Follow-up / cleanup PR to #25437
- `completeBoundaryWithStylesInlineLocals` is used by the Fizz external
runtime, which bundles together all Fizz instruction functions (and is
able to reference / rename `completeBoundary` and `resourceMap` as
locals).
- `completeBoundaryWithStylesInlineGlobals` is used by the Fizz inline
script writer, which sends Fizz instruction functions on an as-needed
basis. This version needs to reference `completeBoundary($RC)` and
`resourceMap($RM)` as globals.
Ideally, Closure would take care of inlining a shared implementation,
but I couldn't figure out a zero-overhead inline due to lack of an
`@inline` compiler directive. It seems that Closure thinks that a shared
`completeBoundaryWithStyles` is too large and will always keep it as a
separate function. I've also tried currying / writing a higher order
function (`getCompleteBoundaryWithStyles`) with no luck
## How did you test this change?
- generated Fizz inline instructions should be unchanged
- bundle size for unstable_external_runtime should be slightly smaller
(due to lack of globals)
- `ReactDOMFizzServer-test.js` and `ReactDOMFloat-test.js` should be
unaffected
<!--
Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.
Before submitting a pull request, please make sure the following is
done:
1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn debug-test --watch TestName`, open
`chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
10. If you haven't already, complete the CLA.
Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->
## Summary
<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->
This is the other approach for unifying default and sync lane
https://github.com/facebook/react/pull/25524.
The approach in that PR is to merge default and continuous lane into the
sync lane, and use a new field to track the priority. But there are a
couple places that field will be needed, and it is difficult to
correctly reset the field when there is no sync lane.
In this PR we take the other approach that doesn't remove any lane, but
batch them to get the behavior we want.
## How did you test this change?
<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
If you leave this empty, your PR will very likely be closed.
-->
yarn test
Co-authored-by: Andrew Clark <hi@andrewclark.io>
## Summary
Updating import for babel-code-frame to use the official @babel package,
as babel-code-frame is a ghost dependency. This change is necessary to
avoid potential issues and stay up-to-date with the latest version of
@babel/code-frame, which is already declared in our project's
package.json.
## How did you test this change?
yarn test
Flow introduced a new syntax to annotated the context type of a
function, this tries to update the rest and add 1 example usage.
- 2b1fb91a55 already added the changes
required for eslint.
- Jest transform is updated to use the recommended `hermes-parser` which
can parse current and Flow syntax and will be updated in the future.
- Rollup uses a new plugin to strip the flow types. This isn't ideal as
the npm module is deprecated in favor of using `hermes-parser`, but I
couldn't figure out how to integrate that with Rollup.
## Summary
This PR adds a "perf regression tests" page to react-devtools-shell.
This page is meant to be used as a performance sanity check we will run
whenever we release a new version or finish a major refactor.
Similar to other pages in the shell, this page can load the inline
version of devtools and a test react app on the same page. But this page
does not load devtools automatically like other pages. Instead, it
provides a button that allows us to load devtools on-demand, so that we
can easily compare perf numbers without devtools against the numbers
with devtools.
<img width="561" alt="image"
src="https://user-images.githubusercontent.com/1001890/184059633-e4f0852c-8464-4d94-8064-1684eee626f4.png">
As a first step, this page currently only contain one test:
mount/unmount a large subtree. This is to catch perf issues that
devtools can cause on the react applications it's running on, which was
once a bug fixed in #24863.
In the future, we plan to add:
- more test apps covering different scenarios
- perf numbers within devtools (e.g. initial load)
## How did you test this change?
In order to show this test app can actually catch the perf regression
it's aiming at, I reverted #24863 locally. Here is the result:
https://user-images.githubusercontent.com/1001890/184059214-9c9b308c-173b-4dd7-b815-46fbd7067073.mov
As shown in the video, the time it takes to unmount the large subtree
significantly increased after DevTools is loaded.
For comparison, here is how it looks like before the fix was reverted:
<img width="452" alt="image"
src="https://user-images.githubusercontent.com/1001890/184059743-0968bc7d-4ce4-42cd-b04a-f6cbc078d4f4.png">
## about the `requestAnimationFrame` method
For this test, I used `requestAnimationFrame` to catch the time when
render and commit are done. It aligns very well with the numbers
reported by Chrome DevTools performance profiling. For example, in one
run, the numbers reported by my method are
<img width="464" alt="image"
src="https://user-images.githubusercontent.com/1001890/184060228-990a4c75-f594-411a-9f85-fa5532ec8c37.png">
They are very close to the numbers reported by Chrome profiling:
<img width="456" alt="image"
src="https://user-images.githubusercontent.com/1001890/184060355-a15d1ec5-c296-4016-9c83-03e761f387e3.png">
<img width="354" alt="image"
src="https://user-images.githubusercontent.com/1001890/184060375-19029010-3aed-4a23-890e-397cdba86d9e.png">
`<Profiler>` is not able to catch this issue here.
If you are aware of a better way to do this, please kindly share with
me.
When unwrapping a promise with `use`, we sometimes suspend the work loop
from rendering anything else until the data has resolved. This is
different from how Suspense works in the old throw-a-promise world,
where rather than suspend rendering midway through the render phase, we
prepare a fallback and block the commit at the end, if necessary;
however, the logic for determining whether it's OK to block is the same.
The implementation is only incidentally different because it happens in
two different parts of the code. This means for `use`, we end up doing
the same checks twice, which is wasteful in terms of computation, but
also introduces a risk that the logic will accidentally diverge.
This unifies the implementation by moving it into the SuspenseContext
module. Most of the logic for deciding whether to suspend is already
performed in the begin phase of SuspenseComponent, so it makes sense to
store that information on the stack rather than recompute it on demand.
The way I've chosen to model this is to track whether the work loop is
rendering inside the "shell" of the tree. The shell is defined as the
part of the tree that's visible in the current UI. Once we enter a new
Suspense boundary (or a hidden Offscreen boundary, which acts a Suspense
boundary), we're no longer in the shell. This is already how Suspense
behavior was modeled in terms of UX, so using this concept directly in
the implementation turns out to result in less code than before.
For the most part, this is purely an internal refactor, though it does
fix a bug in the `use` implementation related to nested Suspense
boundaries. I wouldn't be surprised if it happens to fix other bugs that
we haven't yet discovered, especially around Offscreen. I'll add more
tests as I think of them.
This code was originally added in the old ExpirationTime implementation
of Suspense. The idea is that if multiple updates suspend inside the
same Suspense boundary, and both of them resolve, we should render both
results in the same batch, to reduce jank.
This was an incomplete idea, though. We later discovered a stronger
requirement — once we show a fallback, we cannot fill in that fallback
without completing _all_ the updates that were previously skipped over.
Otherwise you get tearing. This was fixed by #18411, then we discovered
additional related flaws that were addressed in #24685. See those PR
descriptions for additional context.
So I believe this older code is no longer necessary.
shrink visits all the fallthroughs even if they are unreachable so this isn't
the right place to prune unreachable blocks.
This PR moves pruning into a separate pass.
Supports computed properties (as LHS and RHS) correctly. Previously we only
handled member expressions where the property was an identifier, and would
incorrect treat `a[b]` the same as `a.b`. Now we correctly distinguish these and
convert `a[b]` as an IndexLoad and `a.b` as a PropertyLoad. Similar for
assignment, `a[b] = c` is an IndexStore. For both IndexLoad and IndexStore we
lower the property to a Place first.
Implements support for array and object de-structuring in variable declarations
and assignment expressions. Note that the code currently makes the overly
optimistic assumption that the RHS is an array or object that can be safely
indexed into. The correct representation would instead treat the RHS as possibly
iterable, but we need to consider the appropriate representation. I think it's
worth landing a first optimistic pass and we can iterate forward, this helps
make it more clear what the ideal representation would have to be and should
make a bunch of examples work. It also allows us to experiment with
representations of, and handling for, scope dependencies that involve computed
property access.
Adds new types for IndexLoad/IndexStore (renamed later in the stack to
ComputedLoad/ComputedStore) which will be used to represent computed property
access/update. The actual lowering to use these is later in the stack.
After the previous PR to change the scope dependency representation,
`Place.memberPath` is now completely unused, this PR deletes that field and all
references. Rejoice!
This is a pre-req to deleting the `Place.memberPath` field. We no longer need
memberPath in the HIR now that we have PropertyStore and PropertyLoad. However,
scope dependencies use memberPath to track the precise fields that a computation
depends upon. This PR changes scopes dependencies to use a new
`ReactiveScopeDependency` type (Place + optional path), which allows the next PR
to remove `Place.memberPath`.
Fixes codegen for chained assignment expressions. Previously each intermediate
assignment would be generated independently _in addition_ to the final chained
expression being emitted. We now emit a single chained expression, almost
exactly matching the input except for expanding from `x += 1` into `x = x + 1`.
There are two key changes:
* Ensuring that assignment expressions always generate an lvalue, which is
necessary for alias analysis to kick in, since it relies on the effect of the
lvalue to know where to look for aliasing.
* The above makes codegen think the entire assignment expression value is a
temporary that can be emitted later, but that isn't true. The new
PruneTemporaryLValue pass nulls out lvalues that are never read later, ensuring
that codegen can eagerly emit the value instead of saving it as a temporary.
<!--
Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.
Before submitting a pull request, please make sure the following is
done:
1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn debug-test --watch TestName`, open
`chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
10. If you haven't already, complete the CLA.
Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->
## Summary
- Fixes https://github.com/facebook/react/issues/25682
## How did you test this change?
I tried this but it didn't work
```
yarn build --type=UMD_DEV react/index,react-dom && cd fixtures/attribute-behavior && yarn install && yarn start
```
Co-authored-by: eps1lon <silbermann.sebastian@gmail.com>
Converts assignment expressions where the lvalue is a MemberExpression to use
PropertyStore, rather than creating an lvalue with a member path. The net effect
is that lvalue will always have a null member path.
There are two main cases:
* `x.y = <value>`. We lower <value> to a Place, lower the object of the member
expression to a place (`object)` create a temporary Place for the result of the
assignment, and then create a `PropertyStore <object>, "<property>", <value>`.
* `x.y += <value>` (and similar update-in-place operands). We extract the object
of the member expression, read the current value via a PropertyLoad, compute the
updated value, and store it back with a PropertyStore (each of these goes into
its own temporary).
Adds a `InstructionValue::PropertyStore` variant and the minimal necessary
handling across the passes to get things to compile. Lowering is in the next PR.
…-realsies"
This reverts commit 3c8700cbc7502e56cca8d4bb77a08e1ee747c166.
I'm not sure how this happened but I accidentally hit up + enter on a previous
`ghstack land` command for a different stack and it landed this one.
Infuriating.
For unknown reasons I didn't have the energy to dig into, some Babel Error
objects can't be written to so despite formatting the error message, the
original one would still be used. To get around this I'm just constructing a
fake object with a `name` and `message` so the correctly formatted messages are
used.
This is an incremental step to removing `Place.memberPath`. This PR changes how
we handle MemberExpressions in rvalue position, converting to a new
`PropertyLoad` InstructionValue variant. Example:
```
let x = a.b;
x.y = b.c;
=>
Const tmp1 = PropertyLoad a, 'b';
Const x = Place tmp1;
Const tmp2 = PropertyLoad b, 'c';
Reassign x.y = tmp2
```
That we already recently made a chance to ensure that _if_ the lvalue is a
member expression, that we convert the RHS to a Place. So although `x.y = b.c`
could technically be lowered to a single instruction (with the`b.c` as a
PropertyLoad), we force this to a temporary to ensure that we can independently
memoize the RHS value.
The net result is that the following combinations are possible:
* `x = y`, lvalue identifier, rvalue identifier
* `x.y = y` lvalue member path, rvalue identifier
* `x = y.z` lvalue identifier, rvalue property load
As noted above, `x.y = a.b` no longer occurs (and there's an invariant for this
in one of the passes).
A follow-up PR will add a PropertyStore instruction so that we can remove member
paths in lvalue position too.
@josephsavona had the intuition that we were picking the wrong root in the
previous infinite loop test case, so the fix for this is to force a root to
always be picked. This works because `find` implements path compression (if a <-
b and b <- c, then we can just point a <- c to "flatten" the tree which makes
subsequent `find` operations more efficient since we don't have to follow the
ancestor chain each time), so we're forcing all those unions to pick one parent.
From some googling it looks like the traditional way to implement union is to
call `find` so this should be the "right" way to fix it (?).
I'm also adding a basic unit test for DisjointSet, I think we could revisit
later and see if property testing is worth it but for now I mainly wanted to
capture the regression test as a unit test.
Discovered this by accident modifying Joe's playground example. This seemed to
be triggered in inferReactiveScopeVariables when iterating over the DisjointSet
of scopeIdentifiers. In particular this test case contains a cycle and
DisjointSet.find would never terminate.
The github action was exceeding maximum allowed memory size because we were no
longer grouping messages correctly prior to formatting them in the script. I
think these were introduced when we integrated the Babel plugin into the
preprocessor. This PR strips out filenames from the message so they can be
grouped together again. Also added some light comments
Test plan: manually ran `scripts/test262.sh` and verified that the JSON was
grouped together correctly
I found this bug when working on a different task.
`pingSuspendedRoot` sometimes calls `prepareFreshStack` to interupt the
work-in-progress tree and force a restart from the root. The idea is
that if the current render is already in a state where it be blocked
from committing, and there's new data that could unblock it, we might as
well restart from the beginning.
The problem is that this is only safe to do if `pingSuspendedRoot` is
called from a non-React task, like an event handler or a microtask.
While this is usually the case, it's entirely possible for a thenable to
resolve (i.e. to call `pingSuspendedRoot`) synchronously while the
render phase is already executing. If that happens, and work loop
attempts to unwind the stack, it causes the render phase to crash.
Previously we weren't ever clearing the `dist` directory so there were likely
some vestigial files left over from previous builds that might have confused the
import path. This commit fixes the import path and also deletes the `dist`
directory on every build to ensure a clean slate.
Given that our type inference needs to be very conservative, there's not a lot
of benefit to having such fine grained type inference.
In the future, we can use type information from flow/ts for inference.
For now, we've decided to punt on super fine grained aliasing of fields (ie,
mutating x.y shouldn't mutate x.z or it's aliases).
This PR removes the code that tracks the aliases of each field. We can re-add
this when we revisit this functionality.
For the rest of the field aliasing, most of it has been replaced by
InferAliasForStores.
Splits handling of simple assignment updates (`=`) from update-assignments (`+=`
etc). This unblocks starting to support destructuring for the simple case in the
subsequent PR.
The test262 preprocessor was correctly running forget against all the functions
in each test file, but it was incorrectly transforming the file contents
overall. Previously we swapped the contents of the file for the transformed
output of the last function, now we use the babel plugin to rewrite functions in
place and keep the rest of the file intact.
Of course, the tests that failed before still fail. But when we fix them the
tests will work now.