Enable and constrain optimized array operator support for InstantRender (#2547)

Summary:
This change ensures that the code generated via optimized array loop operators is correct in the InstantRender setting. It enables the optimization of such functions when the `--instantRender` option is set. Materialization is prevented by ensuring that post-optimization, objects that are reachable from the function are not mutated. Recoverable errors are issued. We also degrade all InstantRender bailouts to recoverable errors, to facilitate debugging. We add a constraint that optimized functions may not be reused.

Resolves #2451 #2448
Pull Request resolved: https://github.com/facebook/prepack/pull/2547

Differential Revision: D9816268

Pulled By: sb98052

fbshipit-source-id: 2112b199de50b80a7a9852a794c082be3bf122e9
This commit is contained in:
Sapan Bhatia 2018-09-17 14:43:18 -07:00 committed by Facebook Github Bot
parent 47f6f4b495
commit 2ad7ac2fe7
11 changed files with 137 additions and 39 deletions

View File

@ -231,16 +231,23 @@ function ensureIsNotFinal(realm: Realm, O: ObjectValue, P: void | PropertyKeyVal
return;
}
// We can't continue because this object is already in its final state.
let error = new CompilerDiagnostic(
"Mutating a final object, or an object with unknown properties, after some of those " +
"properties have already been used, is not supported.",
realm.currentLocation,
"PP0026",
"FatalError"
);
realm.handleError(error);
throw new FatalError();
// We can't continue because this object is already in its final state
if (realm.instantRender.enabled) {
realm.instantRenderBailout(
"Object mutations that require materialization are currently not supported by InstantRender",
realm.currentLocation
);
} else {
let error = new CompilerDiagnostic(
"Mutating a final object, or an object with unknown properties, after some of those " +
"properties have already been used, is not supported.",
realm.currentLocation,
"PP0026",
"FatalError"
);
realm.handleError(error);
throw new FatalError();
}
}
function isWidenedValue(v: void | Value) {

View File

@ -336,7 +336,8 @@ export class Realm {
this.debugNames = opts.debugNames;
this._checkedObjectIds = new Map();
this.optimizedFunctions = new Map();
this.arrayNestedOptimizedFunctionsEnabled = opts.arrayNestedOptimizedFunctionsEnabled || false;
this.arrayNestedOptimizedFunctionsEnabled =
opts.arrayNestedOptimizedFunctionsEnabled || opts.instantRender || false;
}
statistics: RealmStatistics;
@ -1655,6 +1656,14 @@ export class Realm {
return previousValue;
}
/* Since it makes strong assumptions, Instant Render is likely to have a large
number of unsupported scenarios. We group all associated compiler diagnostics here. */
instantRenderBailout(message: string, loc: ?BabelNodeSourceLocation) {
if (loc === undefined) loc = this.currentLocation;
let error = new CompilerDiagnostic(message, loc, "PP0039", "RecoverableError");
if (this.handleError(error) === "Fail") throw new FatalError();
}
reportIntrospectionError(message?: void | string | StringValue): void {
if (message === undefined) message = "";
if (typeof message === "string") message = new StringValue(this, message);

View File

@ -1348,14 +1348,10 @@ export class ResidualHeapSerializer {
to the __empty built-in */
if (instantRenderMode) {
if (this.emitter.getReasonToWaitForDependencies(elemVal)) {
let error = new CompilerDiagnostic(
this.realm.instantRenderBailout(
"InstantRender does not yet support cyclical arrays or objects",
array.expressionLocation,
"PP0039",
"FatalError"
array.expressionLocation
);
this.realm.handleError(error);
throw new FatalError();
}
delayReason = undefined;
} else {
@ -1855,14 +1851,10 @@ export class ResidualHeapSerializer {
let delayReason;
if (instantRenderMode) {
if (this.emitter.getReasonToWaitForDependencies(propValue)) {
let error = new CompilerDiagnostic(
"InstantRender does not yet support cyclical arays or objects",
val.expressionLocation,
"PP0039",
"FatalError"
this.realm.instantRenderBailout(
"InstantRender does not yet support cyclical arrays or objects",
val.expressionLocation
);
this.realm.handleError(error);
throw new FatalError();
}
delayReason = undefined;
} else {

View File

@ -630,19 +630,22 @@ export class MaterializeImplementation {
let visitedValues: Set<Value> = new Set();
computeFromValue(outlinedFunction);
if (objectsToMaterialize.size !== 0 && realm.instantRender.enabled) {
let error = new CompilerDiagnostic(
"Instant Render does not support array operators that reference objects via non-local bindings",
outlinedFunction.expressionLocation,
"PP0042",
"FatalError"
);
realm.handleError(error);
throw new FatalError();
let handleMaterialization: ObjectValue => void;
if (realm.instantRender.enabled) {
// We prevent mutations to objects so that non-final
// values cannot occur, and hence materialization is avoided.
// The values of properties needed are issued via object literals.
handleMaterialization = o => {
o.makeFinal();
};
} else {
handleMaterialization = o => {
if (!TestIntegrityLevel(realm, o, "frozen")) materializeObject(realm, o);
};
}
for (let object of objectsToMaterialize) {
if (!TestIntegrityLevel(realm, object, "frozen")) materializeObject(realm, object);
handleMaterialization(object);
}
return;

View File

@ -43,6 +43,11 @@ function evaluatePossibleNestedOptimizedFunctionsAndStoreEffects(
thisValue = func.$BoundThis;
}
invariant(funcToModel instanceof ECMAScriptSourceFunctionValue);
if (realm.instantRender.enabled && realm.collectedNestedOptimizedFunctionEffects.has(funcToModel)) {
realm.instantRenderBailout("Array operators may only be optimized once", funcToModel.expressionLocation);
}
let funcCall = Utils.createModelledFunctionCall(realm, funcToModel, undefined, thisValue);
// We take the modelled function and wrap it in a pure evaluation so we can check for
// side-effects that occur when evaluating the function. If there are side-effects, then
@ -59,6 +64,12 @@ function evaluatePossibleNestedOptimizedFunctionsAndStoreEffects(
// the default behaviour and leaked the nested functions so any bindings
// within the function properly leak and materialize.
if (e instanceof NestedOptimizedFunctionSideEffect) {
if (realm.instantRender.enabled) {
realm.instantRenderBailout(
"InstantRender does not support impure array operators",
funcCall.expressionLocation
);
}
Leak.value(realm, func);
return;
}
@ -69,9 +80,8 @@ function evaluatePossibleNestedOptimizedFunctionsAndStoreEffects(
//
// Assumptions:
// 1. We are here because the array op is pure, havocing of bindings is not needed.
// 2. The array op is only used once. To be enforced: #2448
// 3. Aliasing effects will lead to a fatal error. To be enforced: #2449
// 4. Indices of a widened array are not backed by locations
// 2. Aliasing effects will lead to a fatal error. To be enforced: #2449
// 3. Indices of a widened array are not backed by locations
//
// Transitive materialization is needed to unblock this issue: #2405
//

View File

@ -1,5 +1,5 @@
// instant render
// expected errors: [{"location":{"start":{"line":5,"column":10},"end":{"line":5,"column":12},"source":"test/error-handler/EmptyBuiltInArrayCycle.js"},"severity":"FatalError","errorCode":"PP0039"}]
// expected errors: [{"location":{"start":{"line":5,"column":10},"end":{"line":5,"column":12},"source":"test/error-handler/EmptyBuiltInArrayCycle.js"},"severity":"RecoverableError","errorCode":"PP0039"}]
(function() {
var a = [];

View File

@ -1,5 +1,5 @@
// instant render
// expected errors: [{"location":{"start":{"line":5,"column":10},"end":{"line":5,"column":12},"source":"test/error-handler/EmptyBuiltInPropsCycle.js"},"severity":"FatalError","errorCode":"PP0039"}]
// expected errors: [{"location":{"start":{"line":5,"column":10},"end":{"line":5,"column":12},"source":"test/error-handler/EmptyBuiltInPropsCycle.js"},"severity":"RecoverableError","errorCode":"PP0039"}]
(function() {
var a = {};

View File

@ -0,0 +1,20 @@
// instant render
// expected errors:[{"location":{"start":{"line":8,"column":2},"end":{"line":10,"column":3},"source":"test/error-handler/InstantRenderArrayOps3.js"},"severity":"RecoverableError","errorCode":"PP0039"}]
function f(c) {
var arr = Array.from(c);
let x = 1;
function op(x) {
return x + 41;
}
let mapped = arr.map(op);
x = 2;
let mapped2 = mapped.map(op);
return mapped2;
}
global.__optimize && __optimize(f);
inspect = () => f([0]);

View File

@ -0,0 +1,21 @@
// instant render
// expected errors:[{"location":{"start":{"line":15,"column":12},"end":{"line":15,"column":13},"source":"test/error-handler/InstantRenderArrayOps4.js"},"severity":"RecoverableError","errorCode":"PP0039"}]
function f(c) {
var arr = Array.from(c);
let obj = { foo: 1 };
function op(x) {
return obj;
}
let mapped = arr.map(op);
let val = arr[0].foo;
let ret = mapped[0].foo;
obj.foo = 2; // Not allowed - requires materialization via mutations
return ret;
}
global.__optimize && __optimize(f);
inspect = () => f([0]);

View File

@ -0,0 +1,18 @@
// instant render
// does contain:42
function f(c) {
var arr = Array.from(c);
let obj = { foo: 1 };
function op(x) {
return obj.foo + 41;
}
let mapped = arr.map(op);
return mapped;
}
global.__optimize && __optimize(f);
inspect = () => f([0]);

View File

@ -0,0 +1,18 @@
// instant render
// does contain:42
function f(c) {
var arr = Array.from(c);
let foo = 1;
function op(x) {
return foo + 41;
}
let mapped = arr.map(op);
return mapped;
}
global.__optimize && __optimize(f);
inspect = () => f([0]);