Commit Graph
523 Commits
Author SHA1 Message Date
Lauren Tan e4e1af7236 [eslint] Ignore _ prefixed unused variables 2023-02-27 17:12:38 -05:00
mofeiZ 60137a8b64 [rhir][cleanup] remove DeclKind (replaced by pruneNonReactiveDependencies)
DeclKind is no longer read / needed due to pruneNonReactiveDependencies pass.
2023-02-27 11:04:38 -05:00
Joe Savona 1dfaf8a94b Lower all operands to temporaries
This PR changes BuildHIR to lower all operands to temporaries. Example: 

```javascript 

// Input 

a + b; 

// Previous Lowering 

Const t0 = BinaryOperation Place(a) "+" Place(b) 

// New Lowering 

Const t0 = Place(a); 

Const t1 = Place(b); 

BinaryOperation Place(t0) "+" Place(t1) 

``` 

This is necessary to ensure we're always referring to the correct version of a 
variable, even in the case of reassignment mid-expression. For example, we 
previously evaluated `let x=1; x + (x = 2) + x` incorrectly to 6 because we 
lowered the `x = 2` prior to the binary operators. We now lowers each instance 
of x to a temporary, ensuring they refer to the correct SSA version of the 
variable, and produce the correct result (5). 

Note that with this change, the _only_ place a variable can appear as an 
operator is when the InstructionValue is a raw identifier. This was already the 
case for globals (as of the LoadGlobal instruction). All other instruction value 
variants will only ever receive temporaries as arguments. 

This necessitated a few changes to our inference: 

* The logic to extend the range of phi operands (if the phi is mutated) was 
previously in LeaveSSA, but that was actually too late. The introduction of 
lowering to temporaries help discover failing cases, which I fixed earlier in 
the stack by moving the logic to extend the range of phi operands into the 
InferMutableRanges fixpoint loop. 

* PropagateScopeDependencies now has to track variable reassignments in addition 
to tracking property accesses 

* AnalyzeFunctions now has to track variable reassignments in addition to 
tracking property accesses 

* InferReactiveIdentifiers now needs a fixpoint iteration, because identifiers 
don't directly appear together in the same instruction anymore (such that we can 
directly propagate the reactivity between them). Instead, we'll first see that 
the temporaries are reactive, and have to propagate that back to the identifiers 
the temporaries were loaded from. 

Overall while this does introduce a bit more complexity, it also makes the 
compiler more robust. As with the phi example illustrates, there are legitimate 
inputs that can create similar indirections to that introduced by lowering 
identifiers to temporaries. 

Note that there’s a theme to the changes here: several analysis passes need to 
map an operand back to its identifier value. Ideally our HIR structure would 
directly support looking up the value for a temporary. For example, if operands 
were references to eg the index of the instruction that produced them. Because 
we don’t have such a representation yet (it would fall out naturally if we were 
writing in Rust), we have to do some bookkeeping. The key takeaway here is that 
this bookkeeping is incidental complexity given our current representation, not 
fundamental complexity of the algorithm.
2023-02-22 15:53:23 -08:00
Joe Savona 256071460d [destructuring] Cleanup InstructionValue type definition 2023-02-17 15:30:42 -08:00
Joe Savona 1d0aa64b24 Make it easier to debug lambdas 2023-02-22 14:07:33 -08:00
Joe Savona 8da443b673 Print HIR of function expressions for debugging
Example output: 

``` 

bb0 (block): 

[1] Const mutate $9$ = read a$8 

[2] Const store x$10:TObject$ = Object { a: read $9 } 

[3] Const mutate $11:TObject$ = capture x$10:TObject 

[4] Const store $12[4:6]:TFunction$ = Function @deps[read $11:TObject]: 

bb0 (block): 

[1] Const mutate q$7$ = capture x$6 

[2] Const mutate $8$ = capture q$7 

[3] Const mutate $9$ = PropertyLoad read $8.b 

[4] Const store $10[4:6]:TFunction$ = Function @deps[read $9]: 

bb0 (block): 

[1] Const mutate $7:TPrimitive$ = 1 

[2] Const mutate $8[2:4]$ = capture q$6[0:4] 

[3] Const store $9$ = PropertyStore mutate $8[2:4].b = read $7:TPrimitive 

[4] Return 

[5] Const mutate $11$ = Call mutate $10[4:6]:TFunction() 

[6] Return 

[5] Const mutate $13$ = Call mutate $12[4:6]:TFunction() 

[6] Const mutate $14:TObject$ = capture x$10:TObject 

[7] Return freeze $14:TObject 

```
2023-02-22 13:03:00 -08:00
Joe Savona bae2cb5f89 Remove now-unnecessary mutable range extension in LeaveSSA
This is no longer necessary now that we extend phi operands' ranges in 
InferMutableRanges
2023-02-21 15:29:30 -08:00
Joe Savona 9a25b5a12a Fix previous examples, alias across phis
Within the InferMutableRanges fixpoint iteration, we need to alias phi operands 
with the phi id if the phi id is later mutated.
2023-02-21 15:29:30 -08:00
Joe Savona 79eb250187 Bug repro for unobserved aliased mutation w phi
I found this while working to ensure that we always lower all operands to 
temporaries. This works: 

```javascript 

// the whole computation of x is memoized in one block, bc of the mutation after 
the phi 

let x; 

if (cond) { 

x = someObj(); 

} else { 

x = someObj(); 

} 

mutate(x); 

``` 

However, if you alias either of the operands, we lose the mutation: 

```javascript 

let x; 

if (cond) { 

const y = someObj(); // OOPS this gets independently memoized 

x = y; 

} else { 

x = someObj(); 

} 

mutate(x); 

``` 

The core issue is that InferMutableRanges does not take into account mutation of 
phis. ~~My first thought is that we need an additional, outer fixpoint iteration 
loop to flow mutation back "up" to phi operands~~ 

edit: there was a much easier fix, we need to alias phi operands and phi id 
within the existing fixpoint iteration. See follow-up PR which fixes.
2023-02-21 08:29:37 -08:00
Joe Savona fcdcd6038f Constant propagation converts computed access to static property where possible 2023-02-17 12:32:30 -08:00
Joe Savona 2b3902f043 Bailout on assigning to module-scope variables 2023-02-17 12:18:17 -08:00
Joe Savona a2ebea4cde Bailout on assigning to globals 2023-02-17 10:02:56 -08:00
Joe Savona ac9212f24e Make globals configurable; populate a reasonable default list
This is a precursor to validating that all identifiers are defined - we need to 
know about gobals and module declarations, so this PR adds the ability to 
configure a Set<string> of defined globals. The default list is inspired by the 
globals that prepack defines, which just comes from the spec definition.
2023-02-17 09:47:59 -08:00
Joe Savona 6b654f306c Construct LoadGlobal; consume hook info from types
Updates BuildHIR to produce LoadGlobal instructions for references to globals. 
Note that this breaks our previous strategy of finding hook calls: that relied 
on looking at the callee of a CallExpression and checking its name, which relied 
on the callee not being lowered to a temporary. By lowering the name (eg 
`useState`) to a temporary first, we now no longer see the name at the callsite. 

Thankfully @gsathya solved this for us already by teaching type inference about 
hooks, and more generally implementing type inference. I updated this so that we 
infer the type of a LoadGlobal if the name is a hook: the type inference picks 
this up and propagates the type forward correctly. So now, all places that 
needed to check for a hook can just look at the type and everything works. 

This is much more robust than before - you can now reassign a hook to a local 
variable and we'll still detect that when you call it, you're calling a hook.
2023-02-17 09:47:58 -08:00
Joe Savona 326e8c13f7 Scaffolding for LoadGlobal instruction
Adds a new `LoadGlobal` InstructionValue variant which will be used to represent 
identifiers that refer to globals. We don't construct this value type yet.
2023-02-16 15:17:30 -08:00
Joe Savona 0c72eed413 Pass environment options through babel plugin
Updates the babel plugin so that environment options — including custom hook 
definitions — can be passed in through the plugin: 

* Renames `CompilerFlags` => `PluginOptions` since they are specific to the 
babel plugin, and are no longer just flags. 

* Moves the definition of `useFreeze()` out of the builtin hook list and instead 
passes it when our unit tests configure the plugin.
2023-02-16 14:24:15 -08:00
Joe Savona 8810076bc6 Lookup hook declarations on environment
Changes from calling the global parseHookCall() function to looking up the hook 
declaration on the environment.
2023-02-16 14:06:17 -08:00
Joe Savona cee25928bf Rename Environment => State for infer reference effects for clarity
InferReferenceEffects needs to be able to pass around the function's 
Environment, but there is already a local class with that name. It's confusing 
to have two "environment" concepts in one file, so this PR renames that local 
class to the more appropriate `InferenceState` and renames local variables and 
updates comments accordingly.
2023-02-16 14:06:16 -08:00
Joe Savona eece73262e Start of making globals and hook declarations configurable
Some refactoring to allow the environment options to be passed in from the 
outside.
2023-02-16 14:06:15 -08:00
Joe Savona 158c9e4fc1 [be] Extract Environment to a separate file
Precursor to making the environment configurable
2023-02-16 10:47:27 -08:00
Joe Savona 530231712f [be] Extract hooks helpers to separate file
This is a precursor to allowing the set of custom hooks (and their behavior) to 
be configurable.
2023-02-16 10:38:06 -08:00
Joe Savona b05fc6a945 Repro of tagged template as hook arg 2023-02-16 10:21:53 -08:00
Joe Savona 26e9e14d85 Fix typo 2023-02-16 09:04:45 -08:00
Joe Savona 849198da9d Pass cache size to useMemoCache() 2023-02-16 08:57:42 -08:00
Joe Savona 93775440a6 Minimal repros of product patterns 2023-02-16 08:57:38 -08:00
Joe Savona fe5ca23384 Fix PrintHIR for TaggedTemplateExpression 2023-02-16 08:57:34 -08:00
Joe Savona e45f69154f Fix dropped temporary in value block 2023-02-16 08:57:30 -08:00
Joe Savona e504e1212a [be] Import from index instead of direct path 2023-02-16 08:57:26 -08:00
mofeiZ 55ca5fc26c [playground] add flow parsing
I don't think there is a monaco config to parse flow (for IDE integration), but 
now we should be able to parse flow for the Forget compiler. 

Since typescript and flow has different parsing rules, playground will only use 
flow parser if `// @flow` is at the top of the file. 

Test: 
[link](https://react-forget-playground-j7nd7j8cv-fbopensource.vercel.app/#eyJzb3VyY2UiOiIvLyBAZmxvd1xuZnVuY3Rpb24gQ29tcG9uZW50KHByb3BzKSB7XG4gIGxldCB4ID0gKHByb3BzOiBudW1iZXIpO1xuICByZXR1cm4geDtcbn1cbiJ9)
2023-02-16 11:54:27 -05:00
Lauren Tan 0d0d3a4038 Ensure stable variable names after leaving SSA
With this PR we now no longer emit copy instructions during LeaveSSA, and 
restore the original identifier name.
2023-02-15 15:22:03 -05:00
Lauren Tan d1fd0449ee [be] Linter fixes for LeaveSSA 2023-02-15 15:22:01 -05:00
Sathya Gunasekaran 06e5681198 [hir] Use receiver of CallExpression as dep 2023-02-15 15:03:29 +00:00
Joe Savona 008cebd633 [facepalm] Fix bug w missing deps
While reviewing @poteto's PR I noticed that there were some cases of missing 
dependencies. I tracked it down to a bug I introduced 
[here](https://github.com/facebook/react-forget/commit/5b827eb85ce0b09a72e620449d1d676071c2e0b9#r100646304). 
Decl.id is meant to be the id of the instruction that declares the variable. We 
then test to see if a dependency is later than that. If the Decl.id is 
incorrectly too high, then we miss some dependencies thinking they aren't 
defined yet.
2023-02-14 16:22:58 -08:00
Joe Savona b7b0118afa InferReactiveIdentifiers handles Capture effect 2023-02-14 15:48:52 -08:00
Joe Savona 9493e13709 [be] Use switch for exhaustiveness 2023-02-14 15:48:49 -08:00
Joe Savona eda7788c48 InferReactiveIdentifiers uses IdentifierId
This is to help prep for @poteto's renaming PR. To make that PR work we 
generally need to use IdentifierId to distinguish "the same identifier" rather 
than Identifier object identity.
2023-02-14 15:44:46 -08:00
Joe Savona 490c204dcf Move logic for making reactive scope decls all reactive
InferReactiveIdentifiers has some extra logic to find identifiers declared in 
the same scope, and promote non-reactive identifiers to reactive if they appear 
inside a reactive scope (reactive scope == scope with one or more (reactive) 
dependencies). Even though the identifier alone might not be technically 
reactive (have no reactive inputs), it can get re-recreated if the scope 
re-evaluates. 

We can now do this during PruneNonReactiveDependencies as we exit out of each 
scope.
2023-02-14 15:27:52 -08:00
Joe Savona b1ee356805 InferReactiveIdentifiers: fixpoint iteration is now unnecessary
I removed fixpoint iteration and all tests pass, which matches my intuition that 
it's really that we need strictly two passes. Removing to simplify and for 
performance (avoid unnecessary extra visits of the ast)
2023-02-14 15:24:01 -08:00
Joe Savona 2b47cac5fd Create a separate pass to prune non-reactive dependencies
The fact that InferReactiveIdentifiers is integrated directly into 
PropagateScopeDependencies has made the latter pretty tricky to debug at times. 
If a dependency is missing, we have to introspect and figure out if that's 
because it was somehow inferred as non-reactive. This PR creates a new 
PruneNonReactiveDependencies pass to separate out these phases.
2023-02-14 14:09:53 -08:00
Sathya Gunasekaran fb8f293c32 [hir] Add a DropMemoCall pass
This drops the memo hook calls from the IR
2023-02-14 23:07:19 +00:00
Sathya Gunasekaran e0562bbd51 [typer] Type hook callee as Hook type 2023-02-14 23:07:18 +00:00
Sathya Gunasekaran d0f1a98144 [typer] Introduce a Hook type 2023-02-14 23:07:17 +00:00
Sathya Gunasekaran 1dfd51cdb0 [hir] Add name field to Hook 2023-02-14 23:07:17 +00:00
Sathya Gunasekaran a65bf197d7 [hir] Add Memo hooks 2023-02-14 23:07:16 +00:00
Sathya Gunasekaran 12ca0ba61b [hir] Simplify HookKind 2023-02-14 23:07:16 +00:00
Sathya Gunasekaran 81b23f9242 [typer][be] Add helper for checking type.kind 2023-02-14 19:10:37 +00:00
Joe Savona 5673588be4 [be] Tidy up some mutableRange logic 2023-02-13 15:26:07 -08:00
Joe Savona 225fe0835c Optimize DCE to visit CFG only once when there are no loops
Optimizes dead code elimination. Currently it keeps iterating the control flow 
graph until no new usages have been discovered, which accounts for usages across 
loops. However, when there are no loops it's sufficient to iterate the CFG 
exactly once.
2023-02-13 13:59:44 -08:00
Joe Savona 6b67f597a6 Capturing a frozen value is a Read
If a value is known to be frozen (or potentially frozen), then it doesn't need 
to be considered 'captured' since no mutation can occur via aliasing.
2023-02-14 09:33:23 -08:00
Lauren Tan 759a7e027b Fix incorrectly recording declarations in reassignments in
PropagateScopeDependencies 

This was incorrectly added in #1190, oops!
2023-02-13 16:52:08 -05:00