This is an alternate take on phi elimination to the one we pursued over VC w
@poteto driving. This version exploits the RPO ordering of blocks to do phi
elimination in a single pass when there are no loops, and to minimize repeated
visits when there are loops. The main difference is when redundant phis are
removed. Rather than eagerly walking through the CFG for each pruned phi to
rewrite its uses, we build up a mapping of rewritten identifiers. As we walk
through subsequent instructions, we rewrite each place based on that mapping. We
continue cycling through the blocks so long as a given iteration *both* added
new rewrites (meaning there may be subsequent uses to rewrite) *and* there are
back-edges. With no loops this results in a single visit of each block and of
each instruction, but even with loops this is bounded.
#686 added an option to validate generated code after transformation and adds an
ESLint-based validator function to transform-test. Unfortunately it isn't super
easy to wire up ESLint for use in a browser: traditionally the ESLint project
specifically did _not_ support browser builds, but they recently have relaxed
this because they added a browser playground on their website. There isn't
official support, but the [playground
repo](https://github.com/eslint/playground/blob/f3b1f78cc1c06dadfe7bb50c6c0f913c0d23670d/webpack.config.js)
has a webpack config that, when combined with requiring a specific file, allows
making things work in a browser.
I tried using this directly in our playground app but Next's default webpack
config doesn't work. So I created a separate package, playground-validator,
which exports a webpack-built version of `eslint.Linter`. Then the playground
can consume that, and everything works:
## Test Plan 👀
Confirmed that a known problematic example displays the validation message in
playground (both locally and on the preview deployment):
<img width="1500" alt="Screen Shot 2022-10-20 at 12 22 59 PM"
src="https://user-images.githubusercontent.com/6425824/197041265-966ffda2-a3d0-450e-8fc4-fd1a7ca06e1a.png">
Adds a new compiler option `validateNoUseBeforeDefine`, which enables a
post-codegen pass to validate that there are no usages of values before they are
defined (which causes a ReferenceError at runtime). This can occur when a value
is accessed when its in the TDZ (temporary dead zone), after the hoisted
_declaration_ but before the variable is defined:
```javascript
function foo() {
x; // x is in the TDX here: the binding from the subsequent statement is
hoisted, but x is not yet defined.
let x;
}
```
* The validation is off by default, but enabled in transform-test
* The validation crashes compilation, rather than bailout, because the code has
already been mangled and we can't roll back at the point the validation runs.
* The validator uses ESLint's no-use-before-define rule by printing the program
to source and then configuring ESLint to use Hermes parser.
* transform-test now supports tests prefixed with "error." to indicate tests for
which compilation is expected to crash (not just bailout), and the expect file
includes the error message.
This implements an alternative approach to reference kind inference in the new
architecture based on feedback. Here, we track an environment that maps
top-level identifiers (IdentifierId) to the "kind" of value stored: immutable,
mutable, frozen, or maybe-frozen. We then do a forward data flow analysis
updating this environment based on the semantics of each instruction combined
with the types of values present. For example a reference of a value in a
"mutable" position is inferred as readonly if the value is known to be frozen or
immutable. Similarly, a usage of a reference in a "freeze" position is inferred
as a freeze if the value is not yet definitively frozen, and inferred as
readonly if the value is already frozen.
When multiple control paths converge we merge the previous and new incoming
environments, and only reprocess the block if the environment changed relative
to the previous value. This has some noticeable benefits over the previous
version:
* We now infer precisely where `makeReadOnly()` calls need to be inserted, aka
points where a value needs to be frozen may not yet be frozen.
* We track immutable values and can infer their usage as readonly rather than
mutable.
* The system handles aliasing by representing values as distinct from variables,
so that we can handle situations such as:
```javascript
const a = []; // env: {a: value0; value0: mutable}
const b = a; // env: {a: value0, b: value0; value0: mutable}
freeze(a); // env: {a: value0, b: value0; value0: frozen}
mayMutate(b); // ordinarily inferred as a mutable reference, but we know its
readonly
```
* [hir] Core data types and lowering for new model
* Handle more expressions, including using babel for binding resolution
* test setup with pretty printing of ir
* Basic codegen and improved pretty printing
* avoid else block when if has no fallthrough
* emit function declarations with mapped name/params
* start of scope analysis
* saving state pre-run
* add slightly more complex example and flush out lowering/printing (jsx, new, variables)
* Various improvements:
* Convert logical expressions (|| and &&) to control flow, accounting
for lazy evaluation semantics.
* Handle expression statements
* Improve printing of HIR for unsupported node kinds
* Handle more cases of JSX by falling by to OtherStatement to wrap
subtrees at coarse granularity.
* improve HIR printing, lowering of expression statements
* handle object expression printing
* improve IR model for values/places along w codegen
* more test cases
* start of mutability inference
* passable but still incorrect mutability inference
* improved mutability inference, should cover most cases now
* visualization of reference graph
* correctly flow mutability backwards (have to actually set the capability)
* separate visualization in output
* consolidate on frozen/readonly/mutable capabilities
* cleanup
* conditional reassignment test (not quite working)
* hack to output svg files for debugging
* handle conditional reassignment
* improve capture analysis
* treat jsx as (interior) mutable; handle memberexpression lvalues
* lots of comments; hook return is frozen
* update main comment
* inference for switch, which reveals a bug
* fix yarn.lock