Commit Graph

1349 Commits

Author SHA1 Message Date
Joe Savona e502e69b2d Restore React.useMemo validation
This got lost by moving the validation earlier.
2023-11-06 08:33:30 -08:00
Joe Savona 0d3f3884b8 Fix for IIFE return values mutated later
Fixes for the previous PR. What was happening is that our inference was 
inferring the correct mutable ranges and reactive scopes, but the inlining 
process left the instructions from the IIFEs inside a separate block, with a 
'label' terminal preceding it. When we converted to ReactiveFunction this was 
preserved as a ReactiveLabelTerminal, which meant that the first instruction for 
the mutable range could be nested inside one LabelTerminal, while more would be 
in a subsequent LabelTerminal. But we close blocks based on the block scope! 
This meant that we'd have leftover instructions (in the second LabelTerminal) 
that got left out of the block. 

Furthermore, because inlining was happening after EnterSSA we weren't creating 
phis correctly. This PR fixes a bunch of these issues, and a subsequent PR 
handles the remaining cases: 

* We move DropManualMemo and InlineIIFEs before EnterSSA. This means we lose the 
ability to use type information, but we ensure that we create proper SSA ids and 
phis for any reassignments within the IIFE 

* We also update PruneUnusedLabels to not just remove the unused labels, but to 
actually remove LabelTerminals that don't need them.
2023-11-06 08:33:29 -08:00
Joe Savona 1607fb39c3 Repro for incorrect memoization of iife
We construct invalid mutable ranges in these cases because the range starts 
within a labeled block. We need to run merge consecutive scopes and EnterSSA 
after inlining so that the code is lifted out of the labeled block to the 
correct scope, and so that we create phis for reassignments within the IIFE.
2023-11-06 08:33:28 -08:00
Sathya Gunasekaran 60af6671d7 [ez] Check for use-before-decl only when gating is on 2023-11-02 12:32:40 -07:00
Lauren Tan 48e709e7ad [eslint] Add test for classes
This used to cause issues because I hadn't configured Babel in the eslint plugin 
properly, so adding this as a regression test
2023-11-03 16:32:53 -04:00
Lauren Tan 3634980183 [eslint] Switch compiler to "infer" mode 2023-11-03 16:27:20 -04:00
Lauren Tan a2aa032276 [eslint] Plugin should never throw
This has caused issues for people when things like Babel cause issues. It's not 
actionable and it crashes eslint. Just like the Babel plugin, the eslint plugin 
should never throw. Instead, let's log the error so the data isn't lost.
2023-11-03 16:27:18 -04:00
Lauren Tan fe147cb16a [babel] Add hermes-parser type definitions to babel-plugin 2023-11-03 15:22:53 -04:00
Lauren Tan a030d05d95 [eslint] Add test for component syntax 2023-11-03 15:22:52 -04:00
Lauren Tan a74982d660 [eslint] Update hermes-parser, add hermes-eslint 2023-11-03 11:49:02 -04:00
Sathya Gunasekaran 4afee45440 [ez] Add missing ts type 2023-11-02 11:10:33 -07:00
Sathya Gunasekaran adadc21021 [babel] Check if the gated component is used before decl 2023-11-02 11:10:30 -07:00
Lauren Tan d49eb8b580 [ez] Run copyright script 2023-11-02 12:43:35 -04:00
Lauren Tan 0cc473fc9a [ez] Move copyright script to top level 2023-11-02 12:43:33 -04:00
Lauren Tan 5bd9642578 [feedback] Combine bundle scripts and don't commit dist
This combines our scripts and makes it so we no longer need to create a separate 
commit to add the forget-feedback/dist directory into the react-forget repo. I 
originally did that so I could run tests here, but now that the external repo is 
created and has its test suite hooked up to CI, this is now unnecessary friction 
to run a sync
2023-11-02 11:58:11 -04:00
Lauren Tan 4a793bb86e [hoisting] Add more repros for hoisting bugs 2023-11-02 11:01:29 -04:00
Sathya Gunasekaran dca54c8932 [playground] Use standalone prettier
All of these warnings go away: ``` ➜  playground git:(remove-prettier) ✗ yarn 
dev yarn run v1.22.19 $ NODE_ENV=development && next dev ready - started server 
on 0.0.0.0:3000, url: http://localhost:3000 warn  - You have enabled 
experimental feature (appDir) in next.config.js. warn  - Experimental features 
are not covered by semver, and may cause unexpected or broken application 
behavior. Use at your own risk. info  - Thank you for testing `appDir` please 
leave your feedback at https://nextjs.link/app-feedback 

event - compiled client and server successfully in 964 ms (199 modules) wait  - 
compiling /page (client and server)... warn  - 
../../node_modules/prettier/index.js Critical dependency: the request of a 
dependency is an expression 

../../node_modules/prettier/index.js Critical dependency: require function is 
used in a way in which dependencies cannot be statically extracted 

../../node_modules/prettier/index.js Critical dependency: the request of a 
dependency is an expression 

../../node_modules/prettier/index.js Critical dependency: the request of a 
dependency is an expression 

../../node_modules/prettier/third-party.js Critical dependency: the request of a 
dependency is an expression wait  - compiling... warn  - 
../../node_modules/prettier/index.js Critical dependency: the request of a 
dependency is an expression 

../../node_modules/prettier/index.js Critical dependency: require function is 
used in a way in which dependencies cannot be statically extracted 

../../node_modules/prettier/index.js Critical dependency: the request of a 
dependency is an expression 

../../node_modules/prettier/index.js Critical dependency: the request of a 
dependency is an expression 

../../node_modules/prettier/third-party.js Critical dependency: the request of a 
dependency is an expression ``` 

Standalone prettier is meant to be used in the browser: 
https://prettier.io/docs/en/browser
2023-11-02 07:00:05 -07:00
Sathya Gunasekaran 3d3ad1b9ef [LeaveSSA] Process all phis in a block
I don't know if it's possible to write a test for this as I can't seem to get 
the codegen to change. 

For the following testcase: ``` function useFoo(setOne) {   let x;   let y;   if 
(setOne) {     x = 1;     y = 3;   } else {     x = 2;     y = 5;   } 

return { x, y }; } ``` 

The LeaveSSA changes from: ``` .... bb1 (block):   predecessor blocks: bb2 bb3   
x$36:TPrimitive: phi(bb2: x$19, bb3: x$19)   y$21[8:14]:TPrimitive: phi(bb2: 
y$21, bb3: y$21) 

... ``` 

to ``` ... bb1 (block):   predecessor blocks: bb2 bb3   x$36:TPrimitive: 
phi(bb2: x$19, bb3: x$19)   y$38:TPrimitive: phi(bb2: y$21, bb3: y$21) ... ``` 

Notice how `y`'s reassignment got skipped previously.
2023-10-24 16:09:08 +01:00
Joe Savona 3571dfa056 [be] remove dead code in InferReactiveScopeVariables 2023-11-01 17:13:06 -07:00
Joe Savona 3e157bbc27 Propagate reactivity to other operands accounting for mutable ranges
Previously if any operand was reactive, we transferred that reactivity to other 
operands that had a mutable effect (capture, conditionally mutate, mutate, or 
store). But a value can be captured without ever being modified again. This PR 
updates the logic to only transfer reactivity among operands that are actually 
mutable at the given instruction, based on the mutable range. This is strictly 
more precise.
2023-11-01 17:13:05 -07:00
Joe Savona b34172c11e Reactivity inference is single-pass when no loops
We can complete in a single-pass if there are no loops, as a performance 
optimization.
2023-11-01 17:13:04 -07:00
Joe Savona 3ab47ac6bd Replace InferReactiveIdentifiers w new inference
This PR adds one remaining feature to InferReactivePlaces: tracking indirections 
like LoadLocal, PropertyLoad, and similar. Consider something like: 

``` 

// INPUT 

x.push(reactiveValue); 

// HIR 

t0 = LoadLocal 'x' 

t1 = PropertyLoad t0, 'push' 

t2 = LoadLocal 'reactiveValue' // reactive 

t3 = CallExpression mutate t0 . read t1 ( read t2 ) 

``` 

Because a reactive value (`t2`) flows into `t0`, we want to record t0 as 
reactive as well. But that's just the temporary for `LoadLocal 'x'` - what's 
really happening is that from this point, `x` is reactive. 
InferReactiveIdentifiers tracked this, and now that logic is ported into 
InferReactivePlaces as well. That lets us remove all the actual inference from 
InferReactiveIdentifiers.
2023-11-01 17:13:04 -07:00
Joe Savona 8610533ed1 Infer reactive control dependencies
Updates `InferReactivePlaces` to infer control dependencies. We build on the 
formal definition of control dependencies, which is that statement S2 is 
control-dependent on statement S1 if S1 is in the post-dominance-frontier of S2. 
Intuitively, if S1 decides whether S2 is reached or not, then S1 is a control 
dependency of S2. The post dominance frontier of a given statement S is the set 
of statements which may or may not reach S, and captures the intuitive notion. 

We take advantage of phis: phis are the point where a variable may have multiple 
values depending on the path we took. If a phi is not already known to be 
reactive from data dependencies we check for control dependencies. Specifically 
we look at each phi operand. We check if the block that the operand came from 
has any reactive control dependencies, and if so we mark the phi itself as 
reactive. 

The post-dominance-frontier (PDF) algorithm requires walking the post-dominator 
tree a bunch, so we cache the PDF of blocks so that we don't have to recalculate 
on subsequent iterations. 

In addition, `InferReactiveIdentifiers` now uses the _union_ of its own 
inference plus the new `InferReactivePlaces` output when deciding what 
identifiers are reactive. This ensures that control dependencies are recorded 
correctly, fixing the previous test cases. The next diff adds the remaining 
features to InferReactivePlaces so that it can fully replace 
InferReactiveIdentifiers.
2023-11-01 17:13:03 -07:00
Joe Savona d5c3fb87e6 HIR-based reactive identifier analysis
See context from #2187 for background about control dependencies. 

Our current `PruneNonReactiveIdentifiers` pass runs on ReactiveFunction, after 
scope construction, and removes scope dependencies that aren't reactive. It 
works by first building up a set of reactive identifiers in 
`InferReactiveIdentifiers`, then walking the ReactiveFunction and pruning any 
scope dependencies that aren't in that set. 

The challenge is control variables, as demonstrated by the test cases in #2184. 
`InferReactiveIdentifiers` runs against ReactiveFunction, and when we initially 
wrote it we didn't consider control variables. To handle control variables we 
really need to use precise control- & data-flow analysis, which is much easier 
with HIR. 

This PR adds the start of `InferReactivePlaces`, which annotates each `Place` 
with whether it is reactive or not. This allows the annotation to survive 
LeaveSSA, which swaps out the identifiers of places but leaves other properties 
as-is. This version does _not_ yet handle control variables, but it's already 
more precise than our existing inference. In our current inference, if `x` is 
ever assigned a reactive value, then all `x`s are marked reactive. In our new 
inference, each instance of `x` (each Place) gets a separate flag based on 
whether x can actually be reactive at that point in the program. 

There are two main next steps (in follow-up PRs): 

* Update the mechanism by which we prune non-reactive dependencies from scopes. 

* Handle control variables. I think we may be able to use dominator trees to 
figure out the set of basic blocks whose reachability is gated by the control 
variables. This should clearly work for if/else and switch, as for loops i'm not 
sure but intuitively it seems right.
2023-11-01 17:13:02 -07:00
Joe Savona 748859a00e Add reactive flag to Place
This is part of a stack for inferring variables which are reactive via *control 
dependencies* as opposed to a data dependency. In compiler engineering, a 
statement S2 is control-dependent on statement S1 if S1 is in the post-dominance 
frontier of S2. Stated more intuitively: if S1 decides whether or not S2 is 
reached, then S1 is a control dependency of S2. 

As a start, we add `Place.reactive: boolean` so that individual places can track 
whether they are reactive or not. This lets us do fine-grained reactivity 
inference on the control-flow graph, even taking into account different SSA 
instances of a variable, so that we can say that a particular SSA version of `x` 
is reactive, while other "versions" of x (due to reassignment) are not.
2023-11-01 17:13:01 -07:00
Lauren Tan 5cdfa7cdc4 [feedback] Don't skip copying dist 2023-11-01 13:45:50 -04:00
Lauren Tan c289d098d7 [babel] Rename useMemoCacheSource to enableUseMemoCachePolyfill 2023-11-01 13:28:18 -04:00
Lauren Tan 139336b87c [feedback] Specify react dep for react-forget-runtime 2023-11-01 13:12:09 -04:00
Lauren Tan 7042983f69 [feedback] Add changelog to build 2023-10-31 18:31:09 -04:00
Lauren Tan 8860faa65c [feedback] Add script to copy forget-feedback to new repo 2023-10-31 17:14:19 -04:00
Lauren Tan 776f2148b4 [feedback] More efficient userspace impl for uMC 2023-10-31 13:17:40 -04:00
Lauren Tan a0de8e6d2d [babel] Disable ts declaration maps
This was unnecessary, switching it back to disabled
2023-10-31 13:06:19 -04:00
Lauren Tan e4acd448f6 [feedback] Add script to bundle for oss 2023-10-31 12:42:39 -04:00
Lauren Tan 0a7bb7ac7a [babel] Clean up import logic a bit
Addressing feedback from #2218
2023-10-31 12:16:34 -04:00
Lauren Tan 847a6f0a09 [feedback] Make babel/types a dep not a peerDep 2023-10-31 12:16:33 -04:00
Lauren Tan 5ebc9fe388 [feedback] Add github action to run e2e test 2023-10-31 12:16:33 -04:00
Lauren Tan 3246dc641f [feedback] Add testapp with e2e test
Adds a toy next.js app which doesn't do anything interesting. It has a single 
e2e test which can be run via `npm run test:e2e`, which checks if a `$` variable 
was injected by the compiler, as a sanity check whenever we commit a new version 
of the compiler to the external repo.
2023-10-31 12:16:32 -04:00
Lauren Tan ac170f4f0d [babel] Allow userspace useMemoCache
Not every consumer of Forget will be able to run an experimental version of 
React. In the meantime before useMemoCache is stable, provide a way for OSS to 
pass in a userspace impl.
2023-10-31 12:16:31 -04:00
Mofei Zhang c7c57ec531 [playground] QoL use webpack to bundle monaco-editor's loader
--- 

This lets us load playground locally, entirely offline (e.g. on an airplane, 
when a cdn is down, at ReactAdvanced, etc) 

Before: 

See external script link 

<img width="691" alt="image" 
src="https://github.com/facebook/react-forget/assets/34200447/85500e86-c1a5-4dff-bdcd-58db0d6fd453"> 

Now: 

No external script 

<img width="476" alt="image" 
src="https://github.com/facebook/react-forget/assets/34200447/fdcbea09-0b5f-42e3-9310-35d8d1d726b7">
2023-10-30 14:53:00 -04:00
Mofei Zhang 16bef168c1 [playground] Move Editor/index for next PR (dynamic import) 2023-10-30 14:53:00 -04:00
Lauren Tan 0841caab8e [rollup] Include typescript definitions in build
Emit our TypeScript definitions as well in the build so things like options have 
typechecking
2023-10-30 13:58:03 -04:00
Lauren Tan 52afd8a566 [ext-preview] Inline @babel/generator
Turns out we were only using this in PrintHIR and in the logger, so it should be 
safe to inline and makes installing the plugin for external collaborators a 
little easier. 

I'm assuming inlining this is ok because we only need to defer to the host's 
Babel version(s) when it affects parsing or the final codegen output, hence why 
we continue to no-inline @babel/types – as it is responsible for creating AST 
nodes during codegen
2023-10-30 13:58:02 -04:00
Mofei Zhang e2836eec5b [bugfix] do not hoist computed memberpaths in lambdas 2023-10-30 12:56:50 -04:00
Mofei Zhang 3e812df89c [repro] Test case for more variable naming collision bugs
--- 

bug repro from @JacksonGL 

(we currently bailout for references to `useMemoCache`, but not other variables 
that may collide with codegenned ones). 

``` 

% yarn sprout --filter 

yarn run v1.22.19 

$ node ../sprout/dist/main.js --filter 

FAIL  todo 

Failures: 

FAIL: todo 

Difference in forget and non-forget results. 

Expected result: { 

"kind": "ok", 

"value": "0", 

"logs": [ 

"'module_$'", 

"'module_t0'", 

"'module_c_0'" 

] 

} 

Found: { 

"kind": "ok", 

"value": "0", 

"logs": [ 

"[ 0, 0 ]", 

"0", 

"true" 

] 

} 

```
2023-10-30 12:01:24 -04:00
Sathya Gunasekaran f648e4ce85 [test/react] Turn on react tests in CI 2023-10-25 12:39:20 +01:00
Sathya Gunasekaran 51baf5807e [test/react] Fix github workflow
The flattening broke the shell script because the directory structure changed. 

Instead of depending on a flaky shell script, this PR encodes the commands as 
part of the github workflow.
2023-10-25 12:36:55 +01:00
Lauren Tan 419310cd15 Bundle babel-plugin-react-forget with rollup
Attempt to bundle the plugin with rollup instead of just tsc. I'm intentionally 
not inlining babel related modules, as we should ideally rely on the host 
environment's version instead
2023-10-27 14:07:56 -04:00
Lauren Tan 473237ba1f Remove prettier from babel-plugin as dependency
We can now fully remove prettier from babel-plugin-react-forget. I moved it to 
devDependencies instead, to make the rollup build simpler and so we can continue 
to prettify our internal source code.
2023-10-27 14:07:56 -04:00
Lauren Tan c72673bd8f [fixtures] Run prettier after babel-plugin
This moves prettier formatting into fixture-test-utils instead, so we can remove 
the dependency on prettier in the babel plugin. I want to do this because I 
don't want to include prettier in the rolledup artifact when we build the babel 
plugin.
2023-10-27 14:07:55 -04:00
Sathya Gunasekaran ad1fe50546 [playground] Turn on Forget
Runs Forget on the playground, so we can dogfood Forget and experiment with 
improvements to UX. 

Opts out of compiling the layout page because thats run on the server which 
doesn't seem to have access to useMemoCache.
2023-10-23 15:41:01 +01:00