Commit Graph

70 Commits

Author SHA1 Message Date
Sathya Gunasekaran cd3f56bb18 [eslint] Enforce generic array type syntax
Fix and enforce generic array type syntax
2024-04-02 15:31:25 +01:00
Sathya Gunasekaran d5b6e584fb [hir] Add support for directives
Previously, we would drop directives inside a component or hook but this is 
problematic with reanimated which uses `'worklet'` to mark components from 
compilation. 

This PR adds a directive to HIRFunction and ReactiveFunction and codegens the 
directive add the end. No processing is done on the directives themselves. 

Babel seems to store the directives on a BlockStatement, rather than on the 
Function but I've stored it on the Function types because we only support 
compiling functions and the spec defines directives as occuring in the initial 
statement list of a function: > A Directive Prologue is the longest sequence of 
ExpressionStatements > occurring as the initial StatementListItems or 
ModuleItems of a > FunctionBody, a ScriptBody, or a ModuleBody and where each > 
ExpressionStatement in the sequence consists entirely of a > StringLiteral token 
followed by a semicolon.
2024-04-02 11:25:10 +01:00
Mofei Zhang 2782fa2664 [rhir] Preserve block labels + target blockIds for break/continue terminals
RFC: we can either retain break/continue target ids (instead of pruning them in 
`buildReactiveFunction`) or re-implement the same logic in `propagateScopeDeps` 
(ignore implicit breaks; match unlabeled break / continues to their closest loop 
/ while parent terminal). 

If we go ahead with this approach, I'll clean up this PR (add relevant types and 
comments)
2024-03-27 20:26:17 -04:00
Joe Savona e7d0c81d13 Add todo for for loops with context iterator variable
Detect the previous case — for loops where the iterator is a context variables — 
and throw a todo rather than hitting the invariant.
2024-03-22 11:46:28 -07:00
Mofei Zhang 14d54869ed [patch][codegen] don't reuse babel nodes in codegen for dependencies
--- 

Reusing optionalMemberExpression nodes recently led to a bug when compiling 
Forget playground. 

```js 

// the two a?.b's here should be different nodes! 

if (a?.b !== $[0]) { 

// ... 

$[0] = a?.b; 

} 

``` 

Forget playground uses `babel-plugin-react-forget` and `next/babel`. Reusing the 
same node in two positions in the AST lead to invalid mutations: 

- the first `a?.b` is visited and transpiled to `a === void 0 ? ...`, which (1) 
inserts nodes between the original node and its parent and (2) mutates `a?.b` in 
place to a non-optional call 

- the second `a?.b` in source gets updated to `a.b` and does not get visited 
again 

```js 

// Source in `EditorImpl.tsx` 

compilerOutput.kind === "err" ? compilerOutput.error.details : [] 

// Forget transformed: 

if ($[2] !== compilerOutput.kind || $[3] !== compilerOutput.error?.details) { 

t4 = compilerOutput.kind === "err" ? compilerOutput.error.details : []; 

$[2] = compilerOutput.kind; 

// this is good! 

$[3] = compilerOutput.error?.details; 

$[4] = t4; 

} else { 

t4 = $[4]; 

} 

// After next/babel 

if ($[2] !== compilerOutput.kind || $[3] !== ((_compilerOutput$error = 
compilerOutput.error) === null || _compilerOutput$error === void 0 ? void 0 : 
_compilerOutput$error.details)) { 

t4 = compilerOutput.kind === "err" ? compilerOutput.error.details : []; 

$[2] = compilerOutput.kind; 

// Oh no!! 

$[3] = _compilerOutput$error.details; 

$[4] = t4; 

} else { 

t4 = $[4]; 

} 

```
2024-03-20 19:29:02 -04:00
Mofei Zhang b35779c6a4 [logger] Add CompilerDiagnostic category, thread Logger into environment 2024-03-20 13:48:18 -04:00
Joe Savona f2b0b656b2 More accurate source locations for JSX opening/closing tags
<img width="553" alt="Screenshot 2024-03-19 at 4 41 15 PM" 
src="https://github.com/facebook/react-forget/assets/6425824/e87ee704-6c67-4e10-824b-71e97e7e19f5"> 

Slightly improves source locations for JSX elements so that the opening and 
closing tag have distinct locations that match up with source. The identifier 
itself within the closing tag still has the wrong location, but at least this is 
an improvement. 

Doesn't fix the fbt thing but it was worth a try.
2024-03-19 16:26:32 -07:00
Mofei Zhang 81695f62c2 [refactor] Refactor Memoize to two instructions: Start and Finish
--- 

Previously, we always emitted `Memoize dep` instructions after the function 
expression literal and depslist instructions 

```js 

// source 

useManualMemo(() => {...}, [arg]) 

// lowered 

$0 = FunctionExpression(...) 

$1 = LoadLocal (arg) 

$2 = ArrayExpression [$1] 

$3 = Memoize (arg) 

$4 = Call / LoadLocal 

$5 = Memoize $4 

``` 

Now, we insert `Memoize dep` before the corresponding function expression 
literal: 

```js 

// lowered 

$0 = StartMemoize (arg)      <---- this moved up! 

$1 = FunctionExpression(...) 

$2 = LoadLocal (arg) 

$3 = ArrayExpression [$2] 

$4 = Call / LoadLocal 

$5 = FinishMemoize $4 

``` 

Design considerations: 

- #2663 needs to understand which lowered instructions belong to a manual 
memoization block, so we need to emit `StartMemoize` instructions before the 
`useMemo/useCallback` function argument, which contains relevant memoized 
instructions 

- we choose to insert StartMemoize instructions to (1) avoid unsafe instruction 
reordering of source and (2) to ensure that Forget output does not change when 
enabling validation 

This PR only renames `Memoize` -> `Start/FinishMemoize` and hoists 
`StartMemoize` as described. The latter may help with stricter validation for 
`useCallback`s, although testing is left to the next PR. 

#2663 contains all validation changes
2024-03-18 12:09:37 -04:00
Joe Savona d5e2d9f8d5 Handle fbt:param with only leading or trailing whitespace
Fixes T180504437. We expected `<fbt:param>` to always have no surrounding 
whitespace or have both leading and trailing whitespace, it can have one but not 
the other, though such cases are rare in practice.
2024-03-13 14:54:39 -07:00
Joe Savona d5eca2ed85 Repro for scope with no declarations (already fixed on this stack)
Repro from T180504728 which reproduced internally and on playground, neither of 
which have #2687 yet. That PR (earlier in this stack) already fixes the issue, 
so i'm just adding the repro to help prevent regressions.
2024-03-15 08:26:43 -07:00
Joe Savona 1b5ae0638e Fix block scoping of declarations with early return
I addressed some of the cases that lead to this invariant but there were still 
more. In this case, we have scopes like this: 

``` 

scope @1 declarations=[t$0] { 

let t$0 = ArrayExpression [] 

if (...) { 

return null; 

} 

} 

scope @2 deps=[t$0] declarations=[t$1] { 

let t$1 = Jsx children=[t$0] ... 

} 

``` 

Because scope 1 has an early return, PropagateEarlyReturns wraps its contents in 
a label and converts the returns to breaks: 

``` 

scope @1 declarations=[t$0] earlyReturn={t$2} { 

let t$2 

bb0: { 

let t$0 = ArrayExpression [] 

if (...) { 

t$2 = null; 

break bb0; 

} 

} 

} 

scope @2 deps=[t$0] declarations=[t$1] { 

let t$1 = Jsx children=[t$0] ... 

} 

``` 

But then MergeReactiveScopesThatInvalidateTogether smushes them together: 

``` 

scope @1 declarations=[t$1] earlyReturn={t$2} { 

let t$2 

bb0: { 

let t$0 = ArrayExpression [] // <--- Oops! We're inside a block now 

if (...) { 

t$2 = null; 

break bb0; 

} 

} 

let t$1 = Jsx children=[t$0] ... 

} 

``` 

Note that the `t$0` binding is now created inside the labeled block, so it's no 
longer accessible to the Jsx instruction which follows the labeled block. This 
isn't an issue with promoting temporaries or propagating outputs, but a simple 
issue of the labeled block (used for early return) introducing a new block 
scope. The solution here is to simply reorder the passes so that we transform 
for early returns after other optimizations. This means the jsx element will 
basically move inside the labeled block, solving the scoping issue: 

``` 

scope @1 declarations=[t$1] earlyReturn={t$2} { 

let t$2 

bb0: { 

let t$0 = ArrayExpression [] // ok, same block scope as its use 

if (...) { 

t$2 = null; 

break bb0; 

} 

let t$1 = Jsx children=[t$0] // note this moved inside the labeled block 

} 

} 

```
2024-03-13 21:29:58 -07:00
Joe Savona ca8e0d4527 Support multiple declarations in for init
This was an oversight in codegen. The entire pipeline supports multiple values 
in a for initializer, but codegen was dropping all but the first initializer.
2024-03-13 15:45:50 -07:00
Joe Savona 16852386a5 Support more cases of reassignment within value blocks
Fixes T175283039 — it's totally fine to have a StoreLocal as an instruction in a 
value block, so long as its a reassignment.
2024-03-08 13:59:22 -08:00
Joe Savona 52875a72fe Invariant if codegen tries to emit a temporary as an identifier
For T181507827 — adds an invariant in codegen when emitting identifiers to 
ensure that we only create babel Identifier nodes for nodes that the compiler 
has explicitly promoted to valid, named identifiers. This means that we'll fail 
for unnamed temporaries (previously caught), as well as promoted temporaries 
that somehow didn't get renamed by RenameVariables (newly caught).
2024-03-06 21:50:03 -08:00
Joe Savona e7fcc4e6a8 Rename promoteTemporary helpers
Renames the helpers for promoting temporaries to named identifiers, per feedback 
earlier in the stack.
2024-03-06 11:16:44 -08:00
Joe Savona b7026ede39 Avoid generating conflicts w global names
Adds a visitor to collect all the globals that are referenced within the 
function, and then uses this list to avoid synthesizing variables with 
conflicting names. This is used in both RenameVariables (for promoted 
temporaries) and Codegen (for `$` and change variables only, so far, but this 
can be extended in follow-ups).
2024-03-06 11:07:11 -08:00
Joe Savona 8faed2af4c Avoid conflicting names for reactive scope codegen
This is a key part of avoiding generating conflicting names in our output. To 
start, RenameVariables now returns a Set of the unique identifier names that 
exist in the function. Codegen uses this to avoid generating duplicate names for 
change variables and for the `$` useMemoCache variable. Rather than always emit 
`$` or `c_N`, codegen checks that this name would not conflict and appends an 
incrementing suffix until it finds a unique name. 

Note that it's still possible for us to generate conflicts with global 
variables, both during RenameVariable and Codegen. The next step will be to 
avoid conflicts with globals.
2024-03-06 11:07:11 -08:00
Joe Savona 2ae0f36543 Promote and rename within nested functions
Another title for this PR could be "Yet another reason for HIR-everywhere" 

ReactiveFunctionVisitor doesn't traverse into HIRFunctions from 
FunctionExpression and ObjectMethod values. This means that 
PromoteUsedTemporaries and RenameVariables also weren't traversing into such 
functions, and those values weren't getting promoted and renamed correctly. 

This PR updates ReactiveFunctionVisitor with a method that can optionally be 
invoked to traverse an HIRFunction and call the appropriate visitor methods. 
PromoteUsedTemporaries and RenameVariables invoke this to ensure they visit all 
places, even in nested HIRFunctions.
2024-03-06 11:07:10 -08:00
Joe Savona 99b9da4b00 Move remaining promotion of temporaries out of codegen
I realized that codegen still had a fallback for generating identifier nodes for 
unnamed temporaries. This PR updates codegen to throw if it needs to generate an 
identifier for a temporary, and updates earlier passes to promote temporaries to 
named values in all the cases that were missed: 

* BuildHIR needs to promote temporaries for temporaries in destructuring 
bindings and catch clause bindings 

* PromoteUsedTemporaries has to promote temporaries for destructured function 
parameters or function params that are context variables.
2024-03-06 11:07:09 -08:00
Joe Savona 31e128a441 Use opaque type for Identifier.name for improved correctness
Uses an enum for Identifier.name to distinguish originally named identifiers vs 
promoted temporaries. An opaque type for the named identifier variant makes it 
hard to accidentally create that type.
2024-03-06 11:07:08 -08:00
Mofei Zhang 9d25ced018 [instrument] Optional globalGating means no global gating 2024-02-29 10:04:37 -08:00
Mofei Zhang 01454d1414 [other] Make global gating (e.g. DEV) check configurable 2024-02-29 09:44:28 -08:00
Mofei Zhang a06ded902a [other] Change instrumentation to use an optional gating identifier; record
filepath 

Internal rollout currently has a good number of test failures. 
`enableEmitInstrumentForget` can help developers understand which functions / 
files they should look at: 

``` 

// input 

function Foo() { 

userCode(); 

// ... 

} 

// output 

function Foo() { 

if (__DEV__ && inE2eTestMode) { 

logRender("Foo", "/path/to/filename.js"); 

} 

const $ = useMemoCache(...); 

userCode(); 

} 

```
2024-02-27 22:06:36 -08:00
Joe Savona 84ed1f2a37 Update scope declaration invariant to support error grouping
This invariant interpolated values into the `reason` which prevent our internal 
tooling from grouping related errors. This PR updates to make the reason static 
and interpolate the description.
2024-02-26 15:08:42 -08:00
Joe Savona 191064b55c Add ReactiveFunctionValue variant
Per the previous PR, we don't have a way to rewrite an arbitrary subset of a
ReactiveFunction into a function expression, since FunctionExpression's contents
is still in HIR.

While long-term our plan is to move to HIR everywhere, this PR adds a stopgap of
adding a ReactiveFunctionValue variant of ReactiveValue. As a reminder,
ReactiveValue is a union of (HIR) InstructionValue | SequenceExpression |
LogicalExpression | ConditionalExpression.

For now i did a first stab at the visitors and transforms with the idea that:

* By default, visitors/transforms _don't_ look into these function expressions,
since we didn't previously traverse into (HIR-based) FunctionExpression either

* But there is a visitor/transform method that you can override if you need to.
2024-02-16 14:59:00 -08:00
Joe Savona fea7b5ac0d Move useMemoCache outside of hook guards
The hook guards are incompatible with using a forget-runtime. Specifically, 
forget-runtime needs to make a call to `useState()` or some other hook to attach 
data to the fiber, but all the builtin hooks are overridden to disallow calling 
them outside of explicit boundaries. We'd either have to wrap the useMemoCache 
call in a push/pop to allow it to call other hooks, or as in this PR, just move 
it outside the enforcement.
2024-02-13 16:45:18 -08:00
Mofei Zhang c3e9cba0bb [patch] PruneHoistedContexts should traverse lambdas 2024-01-29 17:45:44 -05:00
Joe Savona 7ca3b004ae Early branch with new type inference foundation
It's starting to get complex just with a couple of extra
passes — we either need to substantially extend the HIR or (as i've done so far)
pass information from early passes to later ones. This PR changes things so that
very early in the babel plugin we fork into a separate mode. Forest has
its own `compileProgram()` equivalent, its own pipeline, its own codegen, etc.
2024-01-03 10:47:47 -08:00
Joe Savona fcc2182641 Handle scopes with only early return and no decls/deps/reassigns
Fixes the case from the previous PR by using a different sentinel for 
uninitialized cache values and early returns. I confirmed with console.log that 
the reactive scope for `x` only evaluates on the first execution, after which we 
figure out that we don't need to execute it again.
2023-12-20 13:52:42 -08:00
Joe Savona a86d279c46 Initial (flagged) support for reactive scopes with early return
Adds support for early returns within reactive scopes, behind a new feature 
flag. The flag is off by default, where this case continues to throw a Todo 
bailout. 

Since implementing a sketch of the codegen in the previous PR I realized that 
it's easy enough to implement the more optimal output, so i've updated that 
here. Rather than both the if and else branch of the reactive scope having an 
"if the return value was not a sentinel return it" check, we instead make the 
return temporary a proper declaration of the reactive scope. Then, since it's 
actually an output it's available in the outer block scope, and we could do a 
single if-return after the reactive scope. 

Edit: see comment below for thoughts on test cases.
2023-12-20 13:52:40 -08:00
Joe Savona 3175056935 Codegen for early returns in reactive scopes
Implements codegen for reactive scopes with early returns, though we don't ever 
construct such a case yet. See comments in the code. There is a slightly more 
optimal output that would require a larger refactor (also described in code 
comments), for now i'm starting with the simpler approach since this is 
relatively rare so we don't need to optimize code size / runtime as much.
2023-12-20 13:52:39 -08:00
Joe Savona ed9d6a2ca1 Scaffolding for early return from reactive scopes
Adds a new `earlyReturnValue` property on ReactiveScope which will be set if the 
scope had one or more early returns, with information about the temporary 
identifier that the early return value will be assigned to, as well as the label 
to be used for breaking (to simulate the early-return). The next PR shows the 
intended codegen.
2023-12-20 13:52:38 -08:00
Joe Savona 86e2edfa87 Prune memoize instructions in codegen
The previous PR introduced `memoize` instructions whose lvalues aren't used, but 
which can't be pruned by DCE due to pipeline ordering. Here we change to make 
memoize an instruction intended for its side effects only, and prune during 
codegen.
2023-12-15 13:47:23 -08:00
Joe Savona 2abd439b43 Option to preserve existing memoization guarantees
Adds an option to preserve existing memoization guarantees for values produced 
with useMemo and useCallback. We still discard the calls to these hooks, but we 
preserve the information that the value is frozen at that point in the program. 
Because these values are produced solely within the useMemo/useCallback 
callback, their mutation cannot have any interspersed hook calls. This means 
that the values mutable range will never span a hook and end at the point of the 
useMemo, ensuring that they are memoized at the same point. 

The main things that can change (relative to the orignal code) are: 

* Forget will infer a precise set of dependencies, ignoring the user-provided 
values. In practice this should only occur if the original code had a lint 
violation, which Forget would bail out on. So in practice this shouldn't happen 
unless the code doesn't use the React linter. 

* Forget may start the memoization block earlier than the developer did if other 
values are mutated along with the value being produced. This can cause 
memoization to fail, but only in situations where it would have failed 
previously: 

```javascript 

const a = []; 

useFoo(); 

const b = useMemo(() => { 

const c = a; 

c.push(1); 

return c; 

}, [a]); 

``` 

In this example (sans Forget) the useMemo will invalidate on every render 
because `a` will always be a new array and its listed as a dependency of the 
useMemo. Forget would correctly determine that the memoization would have to 
work as follows: 

```javascript 

let c; 

if (...) { 

const a = [] 

useFoo(); // OOPS we made a hook call conditional 

const t0 = a; 

t0.push(1); 

c = t0; 

... 

} else { 

c = $[...] 

} 

``` 

Because this is invalid, Forget would (later in the pipeline) strip out this 
memoization block and (as with the original) leave `c` un-memoized. 

In this same example, removing the hook would cause Forget to be able to memoize 
a value that wasn't memoized before: 

```javascript 

const a = []; 

const b = useMemo(() => { 

const c = a; 

c.push(1); 

return c; 

}, [a]); 

``` 

This invalidates every render without Forget, but would memoize correctly with 
Forget (it would expand the memoization block to include the declaration of 
`a`).
2023-12-15 13:47:20 -08:00
Mofei Zhang 06376b906d [patch] Generate hook guards as function expressions
--- 

Arrow functions are not compatible with es3, which we sometimes target. This 
helps us make babel transform logic less complex
2023-12-14 19:42:24 -05:00
Joe Savona d163c0b7c3 Support TS type annotations in TypeCastExpression instr
Adds support for TSAsExpression in BuildHIR, which lowers to a 
TypeCastExpression, and to Codegen to reproduce the `as` expression.
2023-12-11 11:34:28 -08:00
Joe Savona 1debb59830 🌲 Remove reactive scope handling from codegen
The previous PR converts reactive scopes to normal instructions, so that Forest 
mode won't have any scopes left by the time we reach codegen. This PR removes 
the now-unused codegen logic for forest.
2023-12-11 11:34:22 -08:00
Joe Savona b3391295c8 🌲 Separate pass for lowering reactive scopes
For Forest, we previously converted reactive scopes into derived signals during 
Codegen. I'm moving this to a separate pass primarily to keep codegen simple 
since there's enough complexity just dealing with core JS semantics. Ideally 
we'd do a similar setup even for regular Forget, ie lower reactive scopes just 
prior to codegen. 

At the same time i also reordered the forget passes to be just before codegen, 
and cleaned things up a bit. For state lowering, we now just rewrite `useState` 
-> `createState`, because we actually need to keep around the setter function to 
trigger scheduling updates in addition to writing the signal value.
2023-12-11 11:34:22 -08:00
Mofei Zhang 7e9f6ecfea [patch] Make holey array handling compatible with older babel versions
--- 

Copied from comments 

Older versions of babel have a validation bug fixed by 

- https://github.com/babel/babel/pull/10917 

- (code pointer) 
https://github.com/babel/babel/commit/e7b80a2cb93cf28010207fc3cdd19b4568ca35b9#diff-19b555d2f3904c206af406540d9df200b1e16befedb83ff39ebfcbd876f7fa8aL52 

Link to buggy older version (observe that elements must be PatternLikes here) 


https://github.com/babel/babel/blob/v7.7.4/packages/babel-types/src/definitions/es2015.js#L50-L53 

Link to newer versions with correct validation (observe elements can be 
PatternLike | null) 


https://github.com/babel/babel/blob/v7.23.0/packages/babel-types/src/definitions/core.ts#L1306-L1311 

Tested on 3000+ components in fb: 

P898166848 

> Unexpected Errors 

> (count = 0) 

[patch] Make holey array handling compatible with older babel versions 

--- 

Copied from comments 

Older versions of babel have a validation bug fixed by 

- https://github.com/babel/babel/pull/10917 

- (code pointer) 
https://github.com/babel/babel/commit/e7b80a2cb93cf28010207fc3cdd19b4568ca35b9#diff-19b555d2f3904c206af406540d9df200b1e16befedb83ff39ebfcbd876f7fa8aL52 

Link to buggy older version (observe that elements must be PatternLikes here) 


https://github.com/babel/babel/blob/v7.7.4/packages/babel-types/src/definitions/es2015.js#L50-L53 

Link to newer versions with correct validation (observe elements can be 
PatternLike | null) 


https://github.com/babel/babel/blob/v7.23.0/packages/babel-types/src/definitions/core.ts#L1306-L1311 

Tested on 3000+ components in fb: 

P898166848 

> Unexpected Errors 

> (count = 0)
2023-12-06 13:20:49 -05:00
Mofei Zhang 01c1b16db4 [patch] Make Codegen ObjectMethod call compatible with babel v7.7
--- 

Tested on - with RunForget 
([before](P897673438), 
[after](P897678035)) 

Compiles 12 more files
2023-12-06 13:20:48 -05:00
Mofei Zhang 41b164ed24 [validation] Runtime validation for hook calls
--- 

I modeled guards as try-finally blocks to be extremely explicit. An alternative 
implementation could flatten all nested hooks and only set / restore hook guards 
when entering / exiting a React function (i.e. hook or component) -- this 
alternative approach would be the easiest to represent as a separate pass 

```js 

// source 

function Foo() { 

const result = useHook(useContext(Context)); 

... 

} 

// current output 

function Foo() { 

try { 

pushHookGuard(); 

const result = (() => { 

try { 

pushEnableHook(); 

return useHook((() => { 

try { 

pushEnableHook(); 

return useContext(Context); 

} finally { 

popEnableHook(); 

} 

})()); 

} finally { 

popEnableHook(); 

}; 

})(); 

// ... 

} finally { 

popHookGuard(); 

} 

} 

// alternative output 

function Foo() { 

try { 

// check current is not lazyDispatcher; 

// save originalDispatcher, set lazyDispatcher 

pushHookGuard(); 

allowHook(); // always set originalDispatcher 

const t0 = useContext(Context); 

disallowHook(); // always set LazyDispatcher 

allowHook(); // always set originalDispatcher 

const result = useHook(t0); 

disallowHook(); // always set LazyDispatcher 

// ... 

} finally { 

popHookGuard(); // restore originalDispatcher 

} 

} 

``` 

Checked that IG Web works as expected 

Unless I add a sneaky useState: 

<img width="705" alt="Screenshot 2023-12-05 at 6 44 59 PM" 
src="https://github.com/facebook/react-forget/assets/34200447/3790bd76-7d71-44b5-a62e-f53256fb5736">
2023-12-05 15:02:34 -05:00
Mofei Zhang 37e1975050 [be] Move codegen logic Program.ts -> Codegen
--- 

Prior to this PR, we were mutating functions after CodegenReactiveFunction 
completes (in `Entrypoint/Program.ts`). 

The reasoning for this separation was that we wanted to keep non-compiler logic 
out of the core Pipeline. However, it made our code difficult to read and reason 
about. 

Open to other alternatives, like adding a pass after Codegen.
2023-12-05 15:02:33 -05:00
Joe Savona 9fb3a26174 Add logging for number of memo *blocks* (in addition to slots)
Useful for understanding and comparing how much memoization Forget applies vs 
how much developers previously memoized by hand.
2023-11-30 15:20:01 -08:00
Joe Savona 8d41529600 Support computed object keys 2023-11-13 09:55:52 -08:00
Joe Savona f92a058ca5 Codegen comments to explain output 2023-11-10 11:45:29 -08:00
Sathya Gunasekaran 42497b60f5 [error] Prefix side-effecting function names with throw
I did a double take when I thought we didn't handle returning the 

error when reading the code and when I edited the code, typescript told 

me that there's no need to return as creating the error will throw. 

This PR makes it clear from the name of the function that we will throw.
2023-11-08 16:09:13 +00:00
Sathya Gunasekaran 6405c980eb Use starred-block for multi line comments 2023-11-08 08:27:41 +00:00
Joe Savona fa2a67dd1d Feature flag to opt-in to emitting change variables 2023-10-12 16:26:42 -07:00
Joe Savona 28767a7afb Inline reactive scope dep checks; no more change vars
Reactive scopes are currently preceded by individual variable declarations, one 
for each of the scope's dependencies. Only after checking independently if each 
of these dependencies has changed do we then do an "or" to check if we should 
actually recompute the body of the scope. 

But that's wasteful: it's more efficient to rely on `||` short-circuiting, and 
recompute as soon as any input changes. 

The main reason I can see not to do this is debugging. Having the change 
variables makes it easy to see what changed. But at this point I think it makes 
sense to optimize for code size and performance; we can always add back a 
dev-only mode that uses change variables if that turns out to help debugging.
2023-10-12 12:03:42 -07:00
Joe Savona 52c6a34da3 [be] Limit scope of temporaries in codegen
This morning @mofeiZ reminded me that our codegen doesn't really have any guards 
against reordering, since temporaries are lazily emitted. We're relying on the 
fact that our lowering and memoization carefully preserves order of evaluation, 
such that delaying the instructions in codegen doesn't change semantics. 

To help catch any mistakes with this, I had previously added code that reset the 
codegen context's temporaries before/after exiting a reactive scope. That 
ensured that temporaries from within the scope weren't accessible outside it. 
This PR extends that approach to _all_ blocks, so that temporaries created 
within a block aren't accessible outside it. 

I'm also going to explore more actively resetting temporaries after they 
"should" be used. There are a couple cases where temporaries are reused, though, 
which we have to change first.
2023-10-10 13:15:51 -07:00