mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[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:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user