prepack/test
Sebastian Markbage 6b926d2aff Allow Side-effectful Abstract Functions in Pure Additional Functions
Summary:
TL;DR this tracks any reachable object or binding that a side-effectful function could mutate as having an unknown value after that call.

In the React compiler we see a lot of examples like this:

```js
return <div>{obj.unknown.toString()}</div>
```

Where we're calling an abstract function, or even calling a generic abstract value due to unknown being abstract.

This is an example that we currently use Flow types for. Flow lets us know that this is a number and therefore toString is known to be pure. However, fully plugging in Flow will be limiting and hard. Flow is also overkill for our use case. We still can't compile away the function call since we'll have to leave a residual call to toString anyway.

Now we could assume that all calls are pure, however, we also see a lot of patterns like this:

```js
var children = [];
obj.items.forEach(item => children.push(<Item item={item} />);
var data = knownFunction(obj.data);
return <div><Foo>{children}</Foo><Bar data={data} /></div>;
```

In this case the children array gets locally mutated. If we knew the `forEach` function here was an array (as opposed to some other `forEach` side-effectful method), then we know what it could do. Even if we knew this would deal with a loop over an abstract bound. But this assumes that we even know this is an array.

It would be sufficient for us to just leave this call in an treat the children as abstract. Even with best effort, that is more or less what's going to happen anyway.

We don't want to completely bail out on the whole function because we want to know the fact that this always returns a `<div>` with a `<Foo />` and a `<Bar />` in it so that we can continue with those. We can also inline the knownFunction call.

Once we're inside a "pure function" such as a React component or additional function, there can be mutations.

Such a function can only mutate newly created objects and bindings, and read immutable data. That's not technically enforced but that's what the whole principle builds on.

This means that any abstract call to a function that was passed in, or lives in global scope, cannot have side-effects on anything except what was passed in as an argument (or `this`).

This PR introducing a new concept of tainting objects and bindings as abstract values after they've been passed to an impure function call.

After they have been tainted all mutations to them are tracked through a generator.

Any subsequent operations on these objects or bindings behave like access to abstract values.

When we enter evaluation of a "pure function" we track that scope and track all newly created objects within it as "impure".

When an abstract function call happens, I visit all arguments that are in the impure list recursively. I stop when I hit an object not on the list, since that can't be mutated.

For objects, I mark them as "tainted", reset all properties and make it partial. That way all subsequent property accesses are unknown. The abstract function call could've done anything with it, including deleting all properties.

Currently I always serialize tainted objects as empty, but I set all the properties to what they were at the first call.

Once an object is tainted, any subsequent operations on it emit to the generator.

If a function is passed in, I also taint all the _newly created_ bindings that are reachable from that closure.

Reads on a tainted binding return a new derived abstract value that replays a read of that binding. To do that I had to add a way to expand `buildNode` to be able to accept `Binding` as an argument. Not just `Value`. That way the `Referentializer` can decide what name the binding needs to serialize as.

I currently left out error handling. Normally that doesn't matter, because if the function call throws, it will bubble up to the pure function call and act like it would've anyway.

Errors only matters if there is a `try`/`catch` anywhere between the abstract call and the pure function call. That should be solvable but even this case is so rare that it is probably fine to fatal in that case.

Once we have function calls in place, we can later follow up by unlocking other operations that currently only fatals because we don't know if they will invoke side-effectful functions.
Closes https://github.com/facebook/prepack/pull/1142

Differential Revision: D6606775

Pulled By: sebmarkbage

fbshipit-source-id: 3305a58ad7b311d222a4b3ce8437056497154edf
2017-12-19 19:09:08 -08:00
..
error-handler fix #1239 - Command-line based syntax errors now print location 2017-12-18 13:56:26 -08:00
node-cli Add minimal test case for the node-cli mode 2017-05-18 15:08:39 -07:00
react Hoist and re-use React Elements when it's safe to do so 2017-12-15 08:08:19 -08:00
residual Partial evaluator for calls (#713) 2017-06-12 13:07:50 -07:00
serializer Allow Side-effectful Abstract Functions in Pure Additional Functions 2017-12-19 19:09:08 -08:00
source-maps Initial commit 2017-03-28 20:52:41 -07:00
std-in fix #1239 - Command-line based syntax errors now print location 2017-12-18 13:56:26 -08:00
test262@098f9ca3de Initial commit 2017-03-28 20:52:41 -07:00