Commit Graph
158 Commits
Author SHA1 Message Date
Jan Kassens df58546f4e Codegen TValue are just Expressions (#839) 2022-12-01 12:56:45 -05:00
Joe Savona 509aa9f0e5 HIRVisitor - change labels to block ids
HIRTreeVisitor previously passed a string label (for certain blocks). This 
changes to pass the raw BlockId, and have codegen convert that to a string. I'm 
not sure if we'll need this but it would be helpful for eg visiting the IR and 
emitting a new IR, while mapping block ids forward. Even if we don't need that 
it makes sense for Codegen to decide how to convert a block id into a label 
(which has to obey the rules of an identifier, not the visitor's concern).
2022-11-30 10:24:51 -08:00
Jan Kassens aa2b152ea8 Fixture tests for 2 cases of expressions that produce incorrect output
They're fairly related, but I figured it's worth keeping more examples. 

- For the `while` example we need to codegen into a single expression. - For the 
expression with contained assignment we need to either keep the SSA ids around 
or re-create a similar expression during codegen.
2022-11-29 18:33:00 -05:00
Lauren TanandJoe Savona <joesavonafb.com> b3a26f1de2 Infer reactive scope dependencies
This builds upon @josephsavona's prior PR #817 to add support for inferring 
reactive scope dependencies for all instructions and terminals. 

Still TODO (probably in follow up PRs): 

- [ ] fix duplicate/different identity scope issue (see this [test 
fixture](https://github.com/facebook/react-forget/blob/5f3b260aaa3e936fb013716df3ef4e7676b7e9ea/forget/src/__tests__/fixtures/hir/overlapping-scopes-while.expect.md)) 
- [ ] also collect outputs of each scope - [ ] add new tree visitor that only 
visits, consider renaming the current `visitTree` to `mapTree` or similar 

Co-authored-by: Joe Savona <joesavonafb.com>
2022-11-29 17:07:55 -05:00
Lauren Tan ccca2d38cf [ez] Delete some old files 2022-11-29 15:23:56 -05:00
Lauren Tan 179a7f9605 Fix output tabs growing infinitely in height
The `automaticLayout` config option for Monaco causes it to remeasure itself, 
and the parent container's height being longer than the screen causes it to 
constantly grow infinitely. You can observe this bug by going to the playground, 
expanding any tab, and watch the scrollbar grow as the editor quickly grows to 
ridiculous heights and your laptop starts glowing red hot and̶͙̕ H̴͉͘e comes 
t̵͙́o ̴̜̿de̷̼̚s̷̻̍ec̶̮͒rate all knowled̵̥̆ge
2022-11-29 11:07:03 -05:00
Jan Kassens 9f7878c7c0 [easy] remove isFallthrough arg from mapTerminalSuccessors
The argument was unused and a confusing boolean argument that's easy to mix up. 
Suggesting to remove it until we see a need for it at which point we might want 
to introduce an enum to make the argument more obvious.
2022-11-28 17:48:59 -05:00
Jan Kassens 0bbb36e1a4 [codegen] more locations: binary, return, call (#820) 2022-11-28 16:54:46 -05:00
jacdebug b932ce19a7 [playground] TabbedWindowItem lifting tabs state up
- List local state in TabbedWindowItem to Editor/index
2022-11-25 09:00:30 +00:00
Sathya Gunasekaran 9ae698c734 [playground] Add tab to display output from old architecture 2022-11-24 18:04:16 +00:00
Sathya Gunasekaran 76a4bd67f4 [playground] Remove unused kind parameter 2022-11-24 17:56:49 +00:00
jacdebug c203d07d8c [Playground] vertical tabs to see multiple stages same time
# Add vertical tabs to see multiple stages same time 

<img width="781" alt="image" 
src="https://user-images.githubusercontent.com/430289/203837762-ccf37491-2006-4512-8014-0b7893f3396f.png">
2022-11-24 17:25:13 +00:00
Sathya Gunasekaran d958d4b73e [playground] Remove line number from output
We already have instruction numbers as part of the output so line numbers just add extra visual noise.
2022-11-24 16:04:27 +00:00
Sathya Gunasekaran 06ca8b1026 [playground] Refactor compilation of HIR
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with 
[ReviewStack](https://reviewstack.dev/facebook/react-forget/pull/825). * __->__ 
#825 

[playground] Refactor compilation of HIR
2022-11-24 15:28:38 +00:00
Sathya Gunasekaran e2cfbeb3d3 [playground] Display only new HIR
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with 
[ReviewStack](https://reviewstack.dev/facebook/react-forget/pull/824). * #825 * 
__->__ #824 

[playground] Display only new HIR
2022-11-24 15:24:40 +00:00
Jan Kassens da6d84049e easy: name InstructionKind enum values (#818) 2022-11-23 10:48:53 -05:00
Joe Savona e51553f5da InferReactiveScopes considers all operands, incl terminals 2022-11-22 14:10:06 -08:00
Joe Savona a0054a1837 Visit terminal ids before terminal branches
This is a follow-up to merging ranges. I realized that we need to mark terminal 
ids as visited _before_ processing the branches of that terminal (whereas before 
we were marking terminal ids only _after_ processing the branches). That exposed 
another bug where interleaving could fail to be detected (with an error, 
thankfully) if one of the branches had completed already. 

Our small test suite is already really good!
2022-11-22 12:10:26 -08:00
Joe Savona d2e5c31fc6 Test case showing reassignment block scoping problem
This demonstrates a situation we don't handle well today. The basic structure is 
that you have some variable defined at the top level, then some control flow 
like if/switch where _all_ branches reassign the variable, then some code after 
that references the resulting phi node: 

```javascript 

let x1; 

// ... mutate/read x1 

if (cond) { 

x2 = {}; 

} else { 

x3 = {}; 

} 

x4 = phi(x2, x3); 

``` 

We currently group x3, x3, and x4 into a scope together, but note that...there's 
no `let` declaration for any of those! This means that it looks like the scope 
for x2 and x3 start in the consequent/alternate, but the true scope spans from 
before-after the if. I'm inclined to say that LeaveSSA should run _before_ scope 
analysis, and produce something like the following in this case: 

```javascript 

let x1; 

// ...mutate/read x1 

let x2; // new variable declaration for the new version of x 

if (cond) { 

x2 = {}; 

} else { 

x2 = {}; 

} 

x2; 

``` 

This then allows us to construct a correct range for x2, which starts in the 
other block.
2022-11-22 10:42:36 -08:00
Joe Savona 4a3640f25c Merge scopes that interleave or cross control-flow boundaries together
- [x] Merge scopes that are interleaved 

- [x] Merge scopes if they both cross control-flow boundaries together 

- [x] Don't merge scopes that strictly shadow 

Still WIP because I want to double-check and see if i can find a simpler 
algorithm for this. But it works.
2022-11-22 10:42:33 -08:00
Sathya Gunasekaran d9c6d61192 [test] Print HIR before leaving SSA
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with 
[ReviewStack](https://reviewstack.dev/facebook/react-forget/pull/812). * __->__ 
#812 

[test] Print HIR before leaving SSA Looking at the HIR before it leaves SSA 
helps debugging better.
2022-11-22 17:59:42 +00:00
Lauren Tan 1f4705bbab [ez] Update test262 2022-11-22 11:39:42 -05:00
Joe Savona 6bf5a1b396 [codegen] Elide final return stmt if no value
This is a random driveby improvement. I realized that we can eliminate `return` 
statements if a) they have no value and b) they are in the top-level block. 
Functions implicitly return at that point — there can't be any succeeding 
instructions anyway — so we can save bytes in the output.
2022-11-22 08:03:40 -08:00
Joe Savona 1976fd03b4 Adjust scope ranges to block boundaries
Visits the HIR as a tree and updates mutable ranges to ensure their range end is 
aligned with the block in which the scope is declared: 

```javascript 

function foo(cond, a) { 

⌵ original scope 

⌵ expanded scope 

const x = [];    ⌝    ⌝ 

if (cond) {      ⎮    ⎮ 

...            ⎮    ⎮ 

x.push(a);     ⌟    ⎮ 

...                 ⎮ 

}                     ⎮ 

...                   ⌟ 

} 

``` 

The implementation tracks the block in which each scope "starts" (first 
instruction with an operand in that scope), and then finds the first instruction 
at that block (or a parent) which is after the scope's end.
2022-11-22 08:03:39 -08:00
Joe Savona 796e14f38e Identifier.scope includes scope id and range
Refactors `Identifier.scope` to be a `ReactiveScope` object with an id and 
range. This gives us a place to later add a list of dependencies for the scope.
2022-11-22 08:03:38 -08:00
Jan Kassens d44082d63c [codegen] location refinements
- Add a `loc` to the `while` terminal node. 

- Move location data for assignments 1 level higher as that seems to work better 
in the generated code (not tested with actual debugger yet though, we'll 
probably want to look at this more closely.
2022-11-21 15:35:39 -05:00
Jan Kassens 1228bc8ce5 [codegen] assign some locations in generated code (#802) 2022-11-21 15:14:20 -05:00
Joseph Savona 855d13f4b2 Add instruction ids to terminals
[ghstack-poisoned] 

closes #799
2022-11-21 11:51:19 -08:00
Joseph Savona 9948e389f1 Opaque InstructionId type
[ghstack-poisoned] 

closes #797
2022-11-21 11:47:14 -08:00
Joseph Savona 391aeab5aa MutableRange is start(inclusive) to end(exclusive)
closes #796
2022-11-21 11:44:02 -08:00
Jan Kassens e59c17ad85 [playground] upgrade to Next 13'
- update dependencies - migrate to app directory
2022-11-21 14:03:30 -05:00
Joe Savona 5f2833bd5c Add HIR "tree" visitor and use it for codegen, alternate printer
Refactors Codegen to extract the core "visit IR as a tree" logic separately from 
the code to emit JS: 

* `HIRTreeVisitor` is a new helper that visits the HIR as a tree. You call 
`visitTree(ir, yourVisitor)` and it drives visiting of the IR, tracking blocks 
and scopes and calling methods as appropriate. 

* `Codegen` is now implemented as a Visitor implementation. For example 
`enterBlock()` creates an empty `Array<t.Statement>`, `leaveBlock()` wraps that 
in a `t.BlockStatement`, etc. 

* `printHIRTree()` is a new IR printer (implemented as a visitor) that prints 
the HIR in tree form, so it retains the original shape of the code but with each 
block replaced with its IR equivalent.
2022-11-18 17:15:16 -08:00
Sathya Gunasekaran 0c1f88db6c [test262] Use compiler pipeline in test262 runner (#800) 2022-11-21 18:55:57 +00:00
Sathya Gunasekaran 61e0186950 Add compiler pipeline (#795) 2022-11-21 18:42:58 +00:00
Sathya Gunasekaran 584eff2d38 [hir] Handle simple aliases when inferring mutable lifetimes (#794) 2022-11-21 18:35:14 +00:00
Jan Kassens 43686787a0 [playground] add link to view sourcemap (#798) 2022-11-21 13:19:36 -05:00
Joe Savona 92afc1a50b Stub for final phase of reactive scope construction w docs 2022-11-18 14:59:33 -08:00
Lauren Tan 71a7f0d6b2 Wrap mermaid labels as strings
The new pretty printed scopes "syntax" breaks mermaid labels because the `@` 
character seems to be reserved. This wraps them all as a string so they work 
again.
2022-11-18 11:09:21 -05:00
Joe Savona 70e7a632bb Annotate mutable range for each scope
Expands InferReactiveScopeVariables to update the mutableRange of all 
identifiers to be the range of its scope. The result is that all identifiers in 
a given scope will have the same range, whose start is the minimum of the 
identifiers range starts, and end is the maximum.
2022-11-17 15:13:35 -08:00
Joe Savona 2d8dc3ec4f Phi operands belong in the same reactive scope
This completes the implementation of InferReactiveScopeVariables, adding support 
for phi nodes. Example: 

```javascript 

let x$0 = null; 

mutate(x$0); 

if (cond) { 

x$1 = a; 

mutate(x$1) 

} else { 

x$2 = b; 

} 

x$3 = phi(x$1, x$2); 

mutate(x$3); 

``` 

We now add x$1, x$2, and x$3 to the same reactive scope. This reflects the fact 
that x$3 cannot be computed without also computing both x$1 and x$2. Note that 
x$3 can never be x$0, so x$0 is _not_ added to the same scope. This allows us to 
take advantage of SSA form to note that _some_ instances of an identifier really 
are distinct.
2022-11-17 09:35:00 -08:00
Lauren Tan cf7176d3f2 Add prettier script
Copies over the prettier script from the React repo with a few tiny tweaks for a 
nicer experience running prettier on changed files
2022-11-17 10:31:34 -05:00
Lauren Tan 188c1501be Condense test262 output in ci
There's no option to output the results of the test262 harness in silent mode so 
every pass and failure outputs multiple lines to stdout. Since there are many 
thousands of tests this results in unusable log files that are over 200k lines 
long. This PR redirects stdout to a tmp file and then we reformat the result 
into a small JSON object, grouped by the failure message with count. 

Example: 

```json [   {     "pass": false,     "data": {       "message": "Expected no 
error, got Error: TODO: Support complex object assignment",       "count": 66    
 }   },   {     "pass": false,     "data": {       "message": "Expected no 
error, got Error: TODO: lowerExpression(FunctionExpression)",       "count": 4   
  }   },   {     "pass": false,     "data": {       "message": "Expected no 
error, got Error: TODO: lowerExpression(UnaryExpression)",       "count": 6     
}   },   {     "pass": false,     "data": {       "message": "Expected no error, 
got Error: todo: lower initializer in ForStatement",       "count": 28     }   
},   {     "pass": false,     "data": {       "message": "Expected no error, got 
Invariant Violation: Expected value for identifier `15` to be initialized.",     
  "count": 14     }   },   {     "pass": false,     "data": {       "message": 
"Expected no error, got Invariant Violation: `var` declarations are not 
supported, use let or const",       "count": 76     }   },   {     "pass": true, 
    "data": {       "message": null,       "count": 1     }   } ] ```
2022-11-16 18:24:07 -05:00
Lauren Tan 9058797e6a Add test262 github action
Add a new workflow to run the test262 tests on commits to main but not in pull 
requests. This is to keep this test non-blocking on PRs but lets us track pass 
rates over time
2022-11-16 10:45:36 -05:00
Joe Savona 4a21bb8856 Infer sets of variables in each reactive scope
Adds a new pass `InferReactiveScopeVariables` which determines the sets of 
variables (by Identifier) which "construct together" and belong in the same 
reactive scope. Concretely, `Identifier` gets a new property `scope: ScopeId`, 
and this pass assigns each identifier a ScopeId value. The algorithm iterates 
over all instructions in all blocks (in a single pass) and builds up disjoint 
sets of identifiers that appear as mutable operands in the same instruction. 

The algorithm is relatively simple (especially since I had already implemented a 
union-find data structure): however looking at some examples reinforced that 
other planned todos around alias analysis are really important. We also have to 
think more about what "mutable lifetime" means in the context of SSA: currently 
variables that are reassigned (but never "mutated", eg bc they're assigned a 
value type) never appear as mutable.
2022-11-15 14:26:18 -08:00
Lauren Tan e6956bd4d8 [ez] Remove test262-all script
Just realized we can run all tests without encountering the arg limit if a 
string is passed in. 

This is much better because the test runner will count all tests in the parent 
test directory rather than run the tests in each subdirectory
2022-11-15 17:01:28 -05:00
Lauren Tan dff31d80eb [ez] Don't output the command when running test262
Currently yarn prints out the command prior to executing it which makes test 
output for test262 very verbose
2022-11-15 14:06:49 -05:00
Lauren Tan ec64edae21 Create identifier for any global or unscoped variable
Noticed this while running test262 tests that many variables were throwing an 
invariant for being undefined. This includes things like the special `arguments` 
object, a global `assert` function used by test262, etc.
2022-11-15 13:32:53 -05:00
Lauren Tan 9027117c4f Scaffolding for test262
- Adds a shallow git submodule for test262 as the tests aren't available   as an 
npm module - To run all tests: `yarn test262:all`. Note that this chunks up the  
 tests by test262 folder as there are over 50k+ tests and the test harness only 
accepts arrays of filepaths which exceeds arg limits - To run a specific test: 
`yarn test262 test262/test/folder/file.js`.   You can also pass globs which 
expand into an array of filepaths: `yarn test262 test262/test/folder/**/*.js` - 
More instructions for the test-harness can be found here: 
https://github.com/bterlson/test262-harness
2022-11-15 13:24:14 -05:00
Joseph Savona e149413057 Distinguish identifier id and ssa id
I noticed on @kassens's #771 that despite running LeaveSSA there are still cases 
where we still reassign to a unique identifier: functions that have reassignment 
but no phi nodes, such as: 

```javascript 

function foo() { 

let x$1 = 0; 

x$2 = x$1 + 1; 

} 

``` 

Here SSA form rewrote the second statement's LHS, but bc there's no phi node we 
can't recover what the original was supposed to be (`x = x + 1`). This was my 
oversight when suggesting the simpler LeaveSSA algorithm, it works for 
eliminating phis but not other reassignments. The only alternative to removing 
SSA form is to add assignment statements, which we obviously don't want to do 
since that generates bloat. 

This PR addresses the issue by adding an additional, optional property to 
`Identifier` called `preSsaId` that starts off null. When entering SSA we save 
the original id in this property and update id to a new SSA value. LeaveSSA does 
the inverse, setting id = preSsaId and nulling out the latter. This means that 
an identifier can always be uniquely identified by its `id` value at any point 
in the compiler, while it's trivial to correctly undo SSA form. 

```typescript 

type Identifier = { 

// Unique value for each original identifier 

id: IdentifierId; 

// The original, un-mangled variable name if this was a variable present in the 
source (null if it's generated) 

name: string | null; 

// When in SSA mode, this is set to the original, pre-SSA `id` value 

preSsaId: IdentifierId | null; 

} 

```
2022-11-15 10:36:54 -08:00
Jan Kassens 5f40a1bf75 [bug] fix codegen for LVals with memberPath (#772) 2022-11-14 11:35:47 -05:00