Commit Graph

1089 Commits

Author SHA1 Message Date
Joe Savona c3ad8a86b1 [rust] Semantic analysis resolves break/continue 2023-08-10 10:59:51 -04:00
Joe Savona ee13c30d10 [rust] Sketch of ReactiveIR (prev ReactiveFunction)
Initial data types for `forget_reactive_ir`, which is the Rust analogue of 
`ReactiveFunction` in the TS compiler. I'm renaming here for clarity, though 
naming suggestions are very welcome!
2023-08-10 10:59:51 -04:00
Joe Savona 7c38b15078 [rust] Align instruction data model closer to JS
Aligns the data model for `Instruction` closer to JS: * Adds an `lvalue: 
IdentifierOperand` property (Identifier + Effect) * Changes rvalues from being a 
reference to the definining instruction by   instruction offset (InstrIx) to be 
a variable reference (IdentifierOperand) 

There are pros and cons to the previous offset-based approach. It's definitely 
convenient to be able to jump directly to the instruction that defined an 
operand value. However: * Many passes need to track things like basic 
reassignments, which mean they need to build   up mappings of IdentifierId to 
some data. When all operands are IdentifierOperands, this   can be a single 
mapping. * It aligns closer to JS, which makes porting a bit easier. 

But most of all: porting the `ReactiveFunction` data type — which is in tree 
form - is non-trivial with the offset-based approach. As we map HIR to the tree 
form, we'd have to remap every operand offset. Using identifiers for operands 
simplifies this. 

We can always revisit this design choice later.
2023-08-10 10:59:50 -04:00
Joe Savona cbb815e907 [rust] Port EliminateRedundantPhi fix 2023-08-10 10:59:50 -04:00
Joe Savona 74a062ba9d [rust] Autofix lints 2023-08-10 10:59:50 -04:00
Joe Savona b8f1d37b6a [rust] Remove SWC
After the previous PR we no longer depend on SWC for the critical path of 
development/testing. Notably this unblocks adding support for the rest of the 
language: the new `forget_hermes_parser` crate automatically converts Hermes 
Parser's AST into our `forget_estree` format via codegen. Achieving full 
language support with SWC would have required manually defining the remaining 
conversions for the rest of the language. 

Long-term we'll need to revisit how to integrate into SWC, and more generally 
into setups build atop SWC such as Next.js's Turbopack-based build 
configuration. But i'll delete for now since we don't depend on it for 
iteration, it's slow to build, and requires us to opt-in to nightly Rust.
2023-08-10 10:59:49 -04:00
Joe Savona 7feaabfe68 [rust] Use hermes parser for Forget fixture tests
Replaces the use of SWC parser in the Forget fixture tests with Hermes Parser. 
This includes aligning on a single type to represent JS Values and numbers 
(combining semi-duplicated code from forget_hir and forget_estree) and adding 
support for lowering babel-style 
NumericLiteral/BooleanLiteral/StringLiteral/NullLiteral node types (since Hermes 
Parser produces a mix of estree/babel node types)
2023-08-10 10:59:49 -04:00
Joe Savona 6bca9fb7a3 [rust] Update HIR builder to use new semantic analysis
Updates HIR builder to rely on the new semantic analysis instead of assuming 
that the ast nodes will already have binding info attached. That was a stopgap 
until we had our own name resolution :-) 

Once this lands we can remove SWC and switch everything to forget_hermes_parser, 
and also remove the non-spec Identifier.binding field (which stored the 
temporary name resolution data).
2023-08-10 10:59:48 -04:00
Sathya Gunasekaran 5d8587b81c Add option to compile ReactScript 2023-08-09 14:49:29 -04:00
Sathya Gunasekaran 27f38ec321 Add hermes-parser 2023-08-09 14:47:56 -04:00
Sathya Gunasekaran 6075d7ea34 Upgrade babel 2023-08-09 14:47:56 -04:00
Joe Savona 43677da7e4 Add instructions for UpdateExpression variants
Adds new instructions to accurately model UpdateExpression semantics, since 
`x++` is un-intuitively not the same as `x = x + 1`. There are a few different 
ways to model the combination of prefix/postfix and increment/decrement: 

* One instruction for all combinations of prefix/postfix and 
increment/decrement, eg 'UpdateExpression' 

* Instructions for Increment/Decrement, each with a property to distinguish 
prefix/postfix 

* Instructions for Prefix/Postfix, each with aproperty to distinguish 
increment/decrement. 

I chose the latter, `PrefixUpdate` and `PostfixUpdate`, because it keeps the 
number of new instructions minimal while keeping separate instructions for the 
most important distinction: whether the result of the instruction is the value 
before applying the operation or after. I'm open to suggestions about this 
though. 

A few quick notes: 

* Constant propagation is supported but only for numbers (we don't support 
bigint yet anyway) 

* LeaveSSA needs to know about these instructions since their presence requires 
making the original variable declaration Let, not Const. 

* EnterSSA mapped lvalues before rvalues, which is out of order but didn't 
previously matter. I just had to flip the order and everything worked.
2023-08-07 13:50:32 -07:00
Mofei Zhang e33c9c43cc [ssa] Patch: propagate every rewrite to function expressions
--- 

#1899 only propagated eliminated phi nodes to function expressions on the first 
iteration through blocks. However, this was buggy as later iterations could 
introduce rewrites that need to be propagated. 

[playground 
repro](https://0xeac7-forget.vercel.app/#eyJzb3VyY2UiOiJmdW5jdGlvbiBDb21wb25lbnQoKSB7XG4gIGNvbnN0IHggPSA0O1xuXG4gIGNvbnN0IGdldDQgPSAoKSA9PiB7XG4gICAgd2hpbGUgKGJhcigpKSB7XG4gICAgICBpZiAoYmF6KSB7XG4gICAgICAgIGJhcigpO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gKCkgPT4geDtcbiAgfTtcblxuICByZXR1cm4gZ2V0NDtcbn0ifQ==). 

I manually synced #1907 to check that this fix works for the VR Store codebase.
2023-08-07 17:41:35 -04:00
Mofei Zhang ff2f2e0685 [patch] Gate EliminatePhi fix behind feature flag 2023-08-07 17:41:34 -04:00
Sathya Gunasekaran adfae63f20 [hir] Remove unnecessary rewrite of instr.lvalue
The for loop over eachInstructionLValue already rewrites instr.lvalue: ```       
  for (const place of eachInstructionLValue(instr)) {           
rewritePlace(place, rewrites);         } ```
2023-08-07 13:41:59 -04:00
Joe Savona 461083d7bd [rust] Deprecate old manual ast visitor 2023-08-04 14:44:54 -07:00
Joe Savona ab5213e8f4 [rust][sema] Comments 2023-08-04 12:16:08 -07:00
Joe Savona 3df04725bd [rust][sema] For statements and JSX
Adds semantic analysis support for normal `for` statements and for JSX. The main 
catch with JSX is that there are a bunch of identifiers that we have to ignore 
since they aren't variable references: jsx attribute names, namespace names, jsx 
member expression properties, and closing elements.
2023-08-04 12:02:21 -07:00
Joe Savona 4756c5ab26 [rust][sema] Shared logic for for-in/for-of
Shares the code for for..in and for..of, along with a few other internal 
refactorings.
2023-08-04 12:02:18 -07:00
Joe Savona e6e2d9437e [rust] Improved name resolution
This is the start of an improved semantic analysis pass, reusing the 
ScopeManager added earlier in the stack but with new analysis built using the 
new visitor trait. The logic is a rough port of 


https://github.com/facebook/hermes/blob/main/tools/hermes-parser/js/hermes-eslint/src/scope-manager/referencer/Referencer.js 

Lots of bits are still missing, i'm starting with the parts that Forget needs.
2023-08-04 09:45:56 -07:00
Joe Savona c6ae535e94 [rust] Codegen AST visitor
Updates `estree` codegen to emit a Visitor trait (temporarily named `Visitor2` 
since there is a hand-rolled one that some code is using). We use knowledge of 
the grammar to only visit fields whose type is a Node or Enum, or an "object" 
type that opts into being visitable. The latter is used for Function and Class. 

This will make it much easier to write the semantic analyzer.
2023-08-03 17:00:02 -07:00
Joe Savona a67fefdebd [rust] Data structures for semantic analysis
This is two things: 

* A toy semantic analysis that handles a tiny subset of JS, including labeled 
statements, labeled break/continue, and variable 
declaration/reference/reassignment. This only exists as a way to prove out the 
API for the more important bit: 

* More importantly, this defines a data model for the semantic analysis results 
and an API for building up the semantic analysis. 

Subsequent diffs will replace the first bit (toy analysis impl), while keeping 
the second part.
2023-08-03 12:41:50 -07:00
Joe Savona 8042970ea4 [rust] Scaffolding for semantic analysis 2023-08-01 15:55:53 -07:00
Sathya Gunasekaran 3e9e3a1c92 [e2e] Add test for useState 2023-08-04 15:50:42 +01:00
Mofei Zhang 015029dc65 [patch] Fix for constant propagation bug in VR Store
--- 

Changes in `@enableOptimizeFunctionExpressions` caused a bug in the last Forget 
sync to VR Store. The repro can be summarized to something like this: 

```js 

function foo() { 

const x = true; // some constant or global 

// Add some branching for type inference 

// This can be a Logical expression as well (e.g. `4 || 5`) 

if (...) { } 

// In this HIR block, SSA inserts a `x$2 = phi(x$1, x$1)`. 

// EliminateRedundantPhiNodes needs to rewrite all references of `x$2` to `x$1` 

const accessXInLambda = () => x; 

return accessXInLambda; 

}
2023-08-02 17:30:57 -04:00
Mofei Zhang 50b0954279 [tests] Add typed CompilerOptions for e2e tests 2023-08-02 17:30:56 -04:00
Mofei Zhang 2a5a8d552f [tests] Revive Forget e2e tests
--- 

Revives e2e test infra from #587. 

- All React component-like functions are compiled. 

- `yarn jest` runs each e2e test twice (forget and no forget) 

Github Actions is already running `yarn test`, which includes all jest tests 

``` 

Run yarn test 

yarn run v1.22.19 

$ yarn workspaces run test 

> babel-plugin-react-forget 

$ yarn jest && yarn snap:build && yarn snap 

$ tsc && jest 

PASS main src/__tests__/Result-test.ts 

PASS main src/__tests__/DisjointSet-test.ts 

PASS e2e with forget src/__tests__/e2e/hello.e2e.js 

PASS e2e no forget src/__tests__/e2e/hello.e2e.js 

Test Suites: 
[4](https://github.com/facebook/react-forget/actions/runs/5732016200/job/15534129231?pr=1881#step:8:5) 
passed, 4 total 

Tests:       23 passed, 23 total 

Snapshots:   11 passed, 11 total 

Time:        
6.1[5](https://github.com/facebook/react-forget/actions/runs/5732016200/job/15534129231?pr=1881#step:8:6)3 
s 

```
2023-08-02 17:30:55 -04:00
Lauren Tan 2d8da0d32d [babel] Don't console.error on unknown options
Internally these are considered errors in our pipelines
2023-08-02 12:34:18 -04:00
Joe Savona 203d5ad43b [rust] ESTree cleanup 2023-08-01 13:55:19 -07:00
Joe Savona c09bfff04c [rust] Tests for HermesParser->ESTree conversion 2023-08-01 13:55:17 -07:00
Joe Savona e353f3702e [rust][wip] Codegen conversion from HermesParser (C++) 2023-08-01 13:55:13 -07:00
Joe Savona ce2725eda5 [rust] Very rough sketch of consuming Hermes parser results 2023-07-28 16:12:18 -07:00
Joe Savona b61a47eb2c Parser benchmark 2023-07-28 16:12:17 -07:00
Joe Savona 01ad9d895e [rust] Remove arena allocator
Using an arena allocator can be faster, but it comes with several challenges: 

* It requires tediously tagging nearly every value with a lifetime. Any function 
that has to deal with arena-allocated data (which is every meaningful function) 
ends up with a lifetime parameter. Bleh. We also have to thread the allocator 
itself wherever we need to allocate, though this is less of a problem since 
_most_ functions already need the Environment and we can store the allocator 
there. 

* There is not yet widespread support in the Rust ecosystem for using custom 
allocators with custom data types. This means that things like HashMap/Set and 
IndexMap/Set can't be arena allocated. This means that either we have to add 
support to these data types (by upstreaming or forking) or just accept that 
we're only partially using the arena allocator. 

* Finally, `bumpalo`'s `Box` cannot be moved out of, which turns out to be an 
annoying limitation that i've already had to work around several times. 

In the end i'm not sure arena allocators are worth it at this stage of the 
project. Relay Compiler has been successful without one, and other Rust-based 
internal compilers get by without them too.
2023-07-28 16:12:17 -07:00
Joe Savona 0e4656ac1c [rust][wip] swc->estree for jsx 2023-07-28 16:12:16 -07:00
Lauren Tan 5230e6d849 Add failing test case for ValidateNoSetStateInRender bug
The current heuristic to check if setState is called in render is based on 
whether the lambda containing the call to setState has a mutable range that got 
extended. This doesn't seem to work in all cases so this validation needs a bit 
more work before we can turn it on by default
2023-07-27 17:08:01 -04:00
Lauren Tan e77d975b16 Flag ValidateNoSetStateInRender
This needs a bit more work before we can turn it on by default, see the next PR 
for a failing test case.
2023-07-27 17:08:01 -04:00
Lauren Tan 12e6cee06f [eslint-plugin] Update hermes-parser to 0.15.0
This includes a fix where the HermesToBabelAdapter was incorrectly outputting a 
`ClassMethod` instead of `ClassPrivateMethod` in certain scenarios. This was 
causing issues with our eslint plugin as it would crash on any file containing 
private methods because a malformed AST was formed.
2023-07-27 11:46:50 -04:00
Lauren Tan 20b175fcd5 [ci] Pass correct flags to yarn install
Turns out `--immutable` and `--immutable-cache` are for yarn 2, yarn 1.x needs 
to use the `--frozen-lockfile` flag instead
2023-07-27 11:06:29 -04:00
Lauren Tan f45a4611e7 [ci] Don't run build on react
Building artifacts shouldn't be required for the tests, this should shave off a 
few minutes from CI
2023-07-27 11:06:27 -04:00
Lauren Tan 8405d8644f [eslint] Add plugin-proposal-private-methods
There was a bug in our internal Hermes to Babel adapter which caused some 
malformed AST to be constructed, which babel would then validate as being 
incorrect. That bug was fixed, but we still have to add this plugin so that 
private class methods can be parsed by babel. 

Tested internally
2023-07-27 10:52:28 -04:00
Joseph Savona 6912c31056 [CI] Run cargo build/test
Adds GitHub actions to build and test the Rust version of Forget on every commit 
to main. I didn't enable this on PRs for now just to avoid slowing down other 
folks, this seemed like a reasonable compromise while we're still in the 
experimentation phase. I test locally, and CI will catch if I or anyone else 
slips up.
2023-07-26 14:01:57 -07:00
Joe Savona d4a1356eca [rust] swc->estree for optional chaining
Handles conversion of optional chains from swc -> estree.
2023-07-25 16:56:29 -07:00
Joe Savona 8bc6184728 [rust] deny unused fields on non-node structs
Rejects unknown fields during deserialization of helper structs (which don't 
have a `type` field). This would be super useful for AST `Node` types, too, but 
that requires implementing a custom deserializer so i'm punting on that for now.
2023-07-25 16:22:57 -07:00
Joe Savona f21270a97d [rust] Complete estree through ES2021
Adds more AST types to handle the full ES2021 spec. At least, in theory I 
defined the schema correctly, and we'll have to just fix any bugs as we 
encounter them.
2023-07-25 15:58:58 -07:00
Joe Savona 90c3a3866d [rust] Improve estree serialization
Updates to use our own codegen'd `Serialize` implementation for AST node types. 
Notably, this allows us to ensure that we always emit a `type` field, even when 
the type is obvious from context (serde doesn't do this) and avoid 
double-emitting `type` fields (which serde can do if you try to force a tag to 
be emitted). We still use the Deserialize impl because it works fine for our 
purposes.
2023-07-25 15:16:22 -07:00
Joe Savona c19286fce7 [rust] More es2015 ast types
Adds almost all the remaining AST types from ES2015 to `estree`, and updates the 
swc->estree->hir conversions accordingly. In a few places i punted with a 
`Diagnostic::todo()`.
2023-07-25 14:08:09 -07:00
Joe Savona 59c1f22057 [rust] port inline_use_memo
Ports InlineUseMemo to Rust, this is the only missing transform pass on the 
pipeline up through constant propagation. I wanted to finish this so that we 
could do benchmarking of the early phase of compilation. UseMemo does a bunch of 
rewriting so it seemed worth comparing performance. 

Included: 

* Add swc -> estree and estree -> HIR conversions for arrow functions and call 
expressions 

* Add HIR definition for labeled terminals and call expressions 

* Utility for inlining one function into another — note that this requires 
remapping InstrIx operands since instruction indices will change. An alternative 
would be to store all the instructions for both outer/inner functions in the 
same array, in which case we wouldn't need to remap. 

* Helpers for mutably iterating the operands of an instruction or terminal. 

* The actual inline_use_memo() function. The overall logic is similar, i just 
had to slightly shuffle the order of operations to satisfy the borrow checker.
2023-07-25 09:24:19 -07:00
Sathya Gunasekaran 4976f5c1be [deps] Update ts-jest version
Console warnings when running tests: ``` ts-jest[versions] (WARN) Version 5.1.3 
of typescript installed has not been tested with ts-jest. ``` 

Our typescript version is ahead of what's supported in ts-jest. This PR updates 
ts-jest to work with the latest typescript compiler.
2023-07-26 12:11:53 +01:00
Sathya Gunasekaran 25527b3c0a [hir] Add support for holey arrays
This PR adds a Hole kind that can be present in both ArrayExpression and 
ArrayPatterns. 

This Hole type is not interesting for our inference passes and is skipped over 
for all of the pipeline.
2023-07-26 12:03:19 +01:00