[new-arch] Scaffolding and fixtures for mutability lifetime inference

Scaffolds out mutable lifetime inference and the potential two-pass approach, 
with motivating examples. Also adds some fixtures that collectively demonstrate 
a bunch of cases of aliasing: 

* direct assignment `a = b` 

* property assignment `a.b = b` 

* array literals `a = [b]` 

* object literals `a = {b}` 

* mutable arguments to the same call `foo(mut a, mut b)` 

* return values aliasing arguments `a = foo(mut b)` 

* aliasing that occurs only after multiple loop iterations 

All of these fixtures use an empty `if (varName) {}` as a way to check that an 
otherwise readonly usage of a variable is correctly inferred as mutable.
This commit is contained in:
Joseph Savona
2022-10-12 10:54:09 -07:00
parent 94adcf91b1
commit 3d4130140a
5 changed files with 456 additions and 0 deletions
@@ -0,0 +1,52 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { HIRFunction } from "./HIR";
/**
* For each usage of a value in the given function, determines if the usage
* may be succeeded by a mutable usage of that same value and if so updates
* the usage to be mutable.
*
* Stated differently, this inference ensures that inferred capabilities of
* each reference are as follows:
* - freeze: the value is frozen at this point
* - readonly: the value is not modified at this point *or any subsequent
* point*
* - mutable: the value is modified at this point *or some subsequent point*.
*
* Note that this refines the capabilities inferered by InferReferenceCapability,
* which looks at individual references and not the lifetime of a value's mutability.
*
* == Algorithm
*
* 1. Forward data-flow analysis to determine aliasing. Unlike InferReferenceCapability
* which only tracks aliasing of top-level variables (`y = x`), this analysis needs
* to know if a value is aliased anywhere (`y.x = x`). The forward data flow tracks
* all possible locations which may have aliased a value. The concrete result is
* a mapping of each Place to the set of possibly-mutable values it may alias.
*
* ```
* const x = []; // {x: v0; v0: mutable []}
* const y = {}; // {x: v0, y: v1; v0: mutable [], v1: mutable []}
* y.x = x; // {x: v0, y: v1; v0: mutable [v1], v1: mutable [v0]}
* read(x); // {x: v0, y: v1; v0: mutable [v1], v1: mutable [v0]}
* mutate(y); // can infer that y mutates v0 and v1
* ```
*
* 2. Backward data-flow analysis to compute mutability liveness. Walk backwards over
* the CFG and track which values are mutated in a successor. Then when visiting
* preceding statements, mark any reference to a value that is known mutated as
* mutable.
*
* ```
* mutate(y); // mutable y => v0, v1 mutated
* read(x); // x maps to v0, v1, those are in the mutated-later set, so x is mutable here
* ...
* ```
*/
function inferMutableLifetimes(fn: HIRFunction) {}
@@ -0,0 +1,119 @@
## Input
```javascript
function mutate(x, y) {}
function cond(x) {}
function Component(props) {
let a = {};
let b = {};
let c = {};
let d = {};
while (true) {
let z = a;
a = b;
b = c;
c = d;
d = z;
mutate(a, b);
if (cond(a)) {
break;
}
}
// all of these tests are seemingly readonly, since the values are never directly
// mutated again. but they are all aliased by `d`, which is later modified, and
// these are therefore mutable references:
if (a) {
}
if (b) {
}
if (c) {
}
if (d) {
}
mutate(d, null);
}
```
## HIR
```
bb0:
Return
```
## Code
```javascript
function mutate$0(x$1, y$2) {
return;
}
```
## HIR
```
bb0:
Return
```
## Code
```javascript
function cond$0(x$1) {
return;
}
```
## HIR
```
bb0:
Let mutable a$2 = Object { }
Let mutable b$3 = Object { }
Let mutable c$4 = Object { }
Let mutable d$5 = Object { }
Goto bb1
bb1:
Const mutable $10 = true
If (readonly $10) then:bb3 else:bb2
bb3:
Let mutable z$6 = readonly a$2
Reassign mutable a$2 = readonly b$3
Reassign mutable b$3 = readonly c$4
Reassign mutable c$4 = readonly d$5
Reassign mutable d$5 = readonly z$6
Call mutable mutate$7(mutable a$2, mutable b$3)
Const mutable $9 = Call mutable cond$8(mutable a$2)
If (readonly $9) then:bb2 else:bb1
bb2:
If (readonly a$2) then:bb7 else:bb7
bb7:
If (readonly b$3) then:bb9 else:bb9
bb9:
If (readonly c$4) then:bb11 else:bb11
bb11:
If (readonly d$5) then:bb13 else:bb13
bb13:
Const mutable $11 = null
Call mutable mutate$7(mutable d$5, readonly $11)
Return
```
## Code
```javascript
function Component$0(props$1) {
let a$2 = {};
let b$3 = {};
let c$4 = {};
let d$5 = {};
("<<TODO: handle complex control flow in codegen>>");
}
```
@@ -0,0 +1,34 @@
function mutate(x, y) {}
function cond(x) {}
function Component(props) {
let a = {};
let b = {};
let c = {};
let d = {};
while (true) {
let z = a;
a = b;
b = c;
c = d;
d = z;
mutate(a, b);
if (cond(a)) {
break;
}
}
// all of these tests are seemingly readonly, since the values are never directly
// mutated again. but they are all aliased by `d`, which is later modified, and
// these are therefore mutable references:
if (a) {
}
if (b) {
}
if (c) {
}
if (d) {
}
mutate(d, null);
}
@@ -0,0 +1,221 @@
## Input
```javascript
function mutate(x, y) {}
function Component(props) {
const a = {};
const b = [a]; // array elements alias
const c = {};
const d = { c }; // object values alias
// capture all the values into this object
const x = {};
x.b = b;
const y = mutate(x, d); // mutation aliases the arg and return value
// all of these tests are seemingly readonly, since the values are never directly
// mutated again. but they are all aliased by `x`, which is later modified, and
// these are therefore mutable references:
if (a) {
}
if (b) {
}
if (c) {
}
if (d) {
}
if (y) {
}
// could in theory mutate any of a/b/c/x/z, so the above should be inferred as mutable
mutate(x, null);
}
```
## HIR
```
bb0:
Return
```
## Code
```javascript
function mutate$0(x$1, y$2) {
return;
}
```
## HIR
```
bb0:
Const mutable a$2 = Object { }
Const mutable b$3 = Array [readonly a$2]
Const mutable c$4 = Object { }
Const mutable d$5 = Object { c: readonly c$4 }
Const mutable x$6 = Object { }
Reassign mutable x$6.b = readonly b$3
Const mutable y$7 = Call mutable mutate$8(mutable x$6, mutable d$5)
If (readonly a$2) then:bb1 else:bb1
bb1:
If (readonly b$3) then:bb3 else:bb3
bb3:
If (readonly c$4) then:bb5 else:bb5
bb5:
If (readonly d$5) then:bb7 else:bb7
bb7:
If (readonly y$7) then:bb9 else:bb9
bb9:
Const mutable $9 = null
Call mutable mutate$8(mutable x$6, readonly $9)
Return
```
## Code
```javascript
function Component$0(props$1) {
const a$2 = {};
const b$3 = [a$2];
const c$4 = {};
const d$5 = {
c: c$4,
};
const x$6 = {};
x$6 = b$3;
const y$7 = mutate$8(x$6, d$5);
if (a$2) {
if (b$3) {
if (c$4) {
if (d$5) {
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
if (d$5) {
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
if (c$4) {
if (d$5) {
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
if (d$5) {
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
if (b$3) {
if (c$4) {
if (d$5) {
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
if (d$5) {
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
if (c$4) {
if (d$5) {
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
if (d$5) {
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
if (y$7) {
mutate$8(x$6, null);
return;
}
mutate$8(x$6, null);
return;
}
```
@@ -0,0 +1,30 @@
function mutate(x, y) {}
function Component(props) {
const a = {};
const b = [a]; // array elements alias
const c = {};
const d = { c }; // object values alias
// capture all the values into this object
const x = {};
x.b = b;
const y = mutate(x, d); // mutation aliases the arg and return value
// all of these tests are seemingly readonly, since the values are never directly
// mutated again. but they are all aliased by `x`, which is later modified, and
// these are therefore mutable references:
if (a) {
}
if (b) {
}
if (c) {
}
if (d) {
}
if (y) {
}
// could in theory mutate any of a/b/c/x/z, so the above should be inferred as mutable
mutate(x, null);
}