Joseph Savona 21652a135b Improved LeaveSSA pass
## Problem 

The previous version of LeaveSSA used a very simple approach in which 
identifiers stored their pre-ssa id, and LeaveSSA restored this id back. The 
upside of this approach is that it's very simple and trivially correct (assuming 
no reordering of code). The downside is that after running LeaveSSA we lose all 
information about which versions of variable declarations are distinct, and 
which might merge together in a phi. That information is really useful for scope 
analysis! Consider this input (variables are numbered as they would be in SSA 
form): 

```javascript 

function foo(a, b, c) { 

let x$1 = null; 

if (a) { 

x$2 = b; 

} else { 

x$3 = c; 

} 

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

return x$4; 

``` 

The current LeaveSSA assigns all 4 variables back to `x$1`, with a single let 
declaration at `let x$1 = null`. However, from a reactive scopes perspective, 
there are really just 2 versions of x: the initial x$1 (defined and never used) 
and then x$2, x$3, and x$4, which have to be merged into a single scope because 
they are part of a phi. In other words, we can't independently compute x$2, x$3, 
or x$4 - if any of their inputs changes, we have to redo all the computation. 
However, the existing structure makes it difficult to figure out the correct 
starting point for this scope — there is no initial `let` declaration that we 
can refer to. 

Instead, we can represent the program as follows after LeaveSSA, and then use 
this form for scope analysis: 

```javascript 

function foo(a, b, c) { 

const x$1 = null; // NOTE: rewritten to const 

let x$2; // synthesized declaration to allow later reassignment 

if (a) { 

x$2 = b; 

} else { 

x$2 = c; 

} 

return x$2; 

``` 

Note that there are only 2 versions of x, and we have synthesized a variable 
declaration for x$2 at the appropriate scope. Our scope analysis can then 
determine that the range of x$2 is from the declaration to the end of the if. 

## Approach 

This pass does two main rewrites: 

* For variables that do *not* appear as a phi id or operand, it rewrites the 
declaration to be `const`. You can see this above for x$1. 

* For variables that *do* appear as a phi or operand, it synthesizes a new `let` 
binding at the appropriate scope (ie, in the appropriate block), and updates all 
other operands from the phi to use the same id for the variable.  You can see 
this above for x$2, x$3, and x$4. 

Note that the let binding is generated at the narrowest scope possible. In this 
example, we generate distinct let bindings for the other if and else branches: 

```javascript 

function foo(a, b, c) { 

let x = null; 

if (a) { 

// we generate a `let x$2` here 

if (b) { 

x = 0; // becomes x$2 

} else { 

x = 1;  // becomes x$2 

} 

x // becomes x$2 

} else { 

// we generate a `let x$3` here 

if (c) { 

x = 2; // becomes x$3 

} else { 

x = 3; // becomes x$3 

} 

x; // becomes x$3 

} 

} 

``` 

Because the different x values from the outer if/else can never join in a phi, 
we can treat them as independent variables and (re)compute them independently. 

The algorithm works by iterating in reverse-postorder, and looking ahead at 
fallthrough blocks to find phi nodes that may need a let declaration (see above 
example of where these are generated). It also tracks variables which _don't_ 
participate in a phi so that it can rewrite their declarations to `const`. 

## TODO 

This PR does *not* yet work for cases where there is unconditional assignment 
within a `while` test condition. That would technically create a distinct 
version of the variable that shadows the value for the loop, and you can't have 
variable declarations in a while test condition. 

That case already doesn't work, though, so i'm punting on it for now until we 
figure out a bit more around 

"value" blocks. We have some good options, like desugaring to a `for(;;)` and 
manually implementing the while semantics in that case.
2022-12-08 07:35:14 -08:00
2022-12-08 07:35:14 -08:00
S
Description
No description provided
MIT 1.8 GiB
Languages
JavaScript 67.1%
TypeScript 29.4%
HTML 1.5%
CSS 1.1%
C++ 0.6%
Other 0.2%