mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-09-11 14:46:37 +03:00
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:
parent
47f6f4b495
commit
2ad7ac2fe7
@ -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) {
|
||||
|
11
src/realm.js
11
src/realm.js
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
//
|
||||
|
@ -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 = [];
|
||||
|
@ -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 = {};
|
||||
|
20
test/error-handler/InstantRenderArrayOps3.js
Normal file
20
test/error-handler/InstantRenderArrayOps3.js
Normal 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]);
|
21
test/error-handler/InstantRenderArrayOps4.js
Normal file
21
test/error-handler/InstantRenderArrayOps4.js
Normal 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]);
|
@ -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]);
|
@ -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]);
|
Loading…
Reference in New Issue
Block a user