mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-09-11 14:46:37 +03:00
Leaked declarative bindings are effectively serialized as simple variables (#2125)
Summary: Release notes: Fixes to leaked declarative bindings Leaked declarative bindings need to be accessed out-of-band as far as the serializer is concerned which otherwise only puts final values into locations. To make that work, whenever two states are merged where a declarative binding in one has been leaked but not in the other, this now gets harmonized by also recording the materialization side effect into the generator of the other. The result is that a binding always ends up being fully leaked or not. For leaked mutable bindings, the tracked value becomes irrelevant, and is now truly made unavailable. For leaked immutable bindings, the last value is retained, and we no longer need the strange `leakedImmutableValue` field. Leaked bindings are now referentialized as simple variables, bypassing the delayed captured-scope logic. This produces nicer more efficient code, but, at least for now, has the downside that it rules out sharing of function bodies. This change is a step towards the (to be publicly documented) long-term plan to clean up Prepack's handling of leaking and havocing. (Similar to what this change does for declarative bindings, we'll also must revisit what happens for leaked object properties. But that's for later.) This fixes #2122 (issue havocing value in conditional) and adds regression test. This fixes #2007 (Conceptual issue with havoced bindings) and adds regression test. Closes https://github.com/facebook/prepack/pull/2125 Differential Revision: D8549299 Pulled By: NTillmann fbshipit-source-id: 28c3681beafd3890668b72a828f59582c636a6c9
This commit is contained in:
parent
0cdafba476
commit
d781c5b8ea
@ -66,9 +66,13 @@ export function havocBinding(binding: Binding) {
|
||||
realm.recordModifiedBinding(binding).hasLeaked = true;
|
||||
if (value !== undefined) {
|
||||
let realmGenerator = realm.generator;
|
||||
if (realmGenerator !== undefined) realmGenerator.emitBindingAssignment(binding, value);
|
||||
if (binding.mutable !== true) binding.leakedImmutableValue = value;
|
||||
binding.value = realm.intrinsics.undefined;
|
||||
if (realmGenerator !== undefined && value !== realm.intrinsics.undefined)
|
||||
realmGenerator.emitBindingAssignment(binding, value);
|
||||
if (binding.mutable === true) {
|
||||
// For mutable, i.e. non-const bindings, the actual value is no longer directly available.
|
||||
// Thus, we reset the value to undefined to prevent any use of the last known value.
|
||||
binding.value = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -154,7 +158,6 @@ export type Binding = {
|
||||
// bindings that are assigned to inside loops with abstract termination conditions need temporal locations
|
||||
phiNode?: AbstractValue,
|
||||
hasLeaked: boolean,
|
||||
leakedImmutableValue?: Value,
|
||||
};
|
||||
|
||||
// ECMA262 8.1.1.1
|
||||
@ -330,8 +333,8 @@ export class DeclarativeEnvironmentRecord extends EnvironmentRecord {
|
||||
}
|
||||
|
||||
// 4. Return the value currently bound to N in envRec.
|
||||
if (binding.hasLeaked) {
|
||||
return binding.leakedImmutableValue || deriveGetBinding(realm, binding);
|
||||
if (binding.hasLeaked && binding.mutable) {
|
||||
return deriveGetBinding(realm, binding);
|
||||
}
|
||||
invariant(binding.value);
|
||||
return binding.value;
|
||||
|
@ -451,7 +451,7 @@ export function OrdinaryCallEvaluateBody(
|
||||
realm.savedCompletion = undefined;
|
||||
let c = getCompletion();
|
||||
|
||||
// We are about the leave this function and this presents a join point where all non exeptional control flows
|
||||
// We are about the leave this function and this presents a join point where all non exceptional control flows
|
||||
// converge into a single flow using their joint effects to update the post join point state.
|
||||
if (!(c instanceof ReturnCompletion)) {
|
||||
if (!(c instanceof AbruptCompletion)) {
|
||||
|
@ -489,7 +489,7 @@ export class JoinImplementation {
|
||||
generator: generator1,
|
||||
modifiedBindings: modifiedBindings1,
|
||||
modifiedProperties: modifiedProperties1,
|
||||
createdObjects: createdObject1,
|
||||
createdObjects: createdObjects1,
|
||||
} = e1;
|
||||
|
||||
let {
|
||||
@ -508,27 +508,34 @@ export class JoinImplementation {
|
||||
}
|
||||
} else if (result2 instanceof AbruptCompletion) {
|
||||
invariant(result instanceof PossiblyNormalCompletion);
|
||||
return new Effects(result, generator1, modifiedBindings1, modifiedProperties1, createdObject1);
|
||||
return new Effects(result, generator1, modifiedBindings1, modifiedProperties1, createdObjects1);
|
||||
}
|
||||
|
||||
let bindings = this.joinBindings(realm, joinCondition, modifiedBindings1, modifiedBindings2);
|
||||
let [modifiedGenerator1, modifiedGenerator2, bindings] = this.joinBindings(
|
||||
realm,
|
||||
joinCondition,
|
||||
generator1,
|
||||
modifiedBindings1,
|
||||
generator2,
|
||||
modifiedBindings2
|
||||
);
|
||||
let properties = this.joinPropertyBindings(
|
||||
realm,
|
||||
joinCondition,
|
||||
modifiedProperties1,
|
||||
modifiedProperties2,
|
||||
createdObject1,
|
||||
createdObjects1,
|
||||
createdObjects2
|
||||
);
|
||||
let createdObjects = new Set();
|
||||
createdObject1.forEach(o => {
|
||||
createdObjects1.forEach(o => {
|
||||
createdObjects.add(o);
|
||||
});
|
||||
createdObjects2.forEach(o => {
|
||||
createdObjects.add(o);
|
||||
});
|
||||
|
||||
let generator = joinGenerators(realm, joinCondition, generator1, generator2);
|
||||
let generator = joinGenerators(realm, joinCondition, modifiedGenerator1, modifiedGenerator2);
|
||||
|
||||
return new Effects(result, generator, bindings, properties, createdObjects);
|
||||
}
|
||||
@ -706,42 +713,61 @@ export class JoinImplementation {
|
||||
// sets of m1 and m2. The value of a pair is the join of m1[key] and m2[key]
|
||||
// where the join is defined to be just m1[key] if m1[key] === m2[key] and
|
||||
// and abstract value with expression "joinCondition ? m1[key] : m2[key]" if not.
|
||||
joinBindings(realm: Realm, joinCondition: AbstractValue, m1: Bindings, m2: Bindings): Bindings {
|
||||
joinBindings(
|
||||
realm: Realm,
|
||||
joinCondition: AbstractValue,
|
||||
g1: Generator,
|
||||
m1: Bindings,
|
||||
g2: Generator,
|
||||
m2: Bindings
|
||||
): [Generator, Generator, Bindings] {
|
||||
let getAbstractValue = (v1: void | Value, v2: void | Value) => {
|
||||
return AbstractValue.createFromConditionalOp(realm, joinCondition, v1, v2);
|
||||
};
|
||||
let rewritten1 = false;
|
||||
let rewritten2 = false;
|
||||
let leak = (b: Binding, g: Generator, v: void | Value, rewritten: boolean) => {
|
||||
// just like to what happens in havocBinding, we are going to append a
|
||||
// binding-assignment generator entry; however, we play it safe and don't
|
||||
// mutate the generator; instead, we create a new one that wraps around the old one.
|
||||
if (!rewritten) {
|
||||
let h = new Generator(realm, "RewrittenToAppendBindingAssignments");
|
||||
if (!g.empty()) h.appendGenerator(g, "");
|
||||
g = h;
|
||||
rewritten = true;
|
||||
}
|
||||
if (v !== undefined && v !== realm.intrinsics.undefined) g.emitBindingAssignment(b, v);
|
||||
return [g, rewritten];
|
||||
};
|
||||
let join = (b: Binding, b1: void | BindingEntry, b2: void | BindingEntry) => {
|
||||
let l1 = b1 === undefined ? b.hasLeaked : b1.hasLeaked;
|
||||
let l2 = b2 === undefined ? b.hasLeaked : b2.hasLeaked;
|
||||
let v1 = b1 === undefined ? b.value : b1.value;
|
||||
let v2 = b2 === undefined ? b.value : b2.value;
|
||||
let liv1 = b1 === undefined ? b.leakedImmutableValue : b1.leakedImmutableValue;
|
||||
let liv2 = b2 === undefined ? b.leakedImmutableValue : b2.leakedImmutableValue;
|
||||
let hasLeaked = l1 || l2; // If either has leaked, then this binding has leaked.
|
||||
let value = this.joinValues(realm, v1, v2, getAbstractValue);
|
||||
let leakedImmutableValue =
|
||||
liv1 === undefined && liv2 === undefined ? undefined : this.joinValues(realm, liv1, liv2, getAbstractValue);
|
||||
invariant(value instanceof Value);
|
||||
invariant(leakedImmutableValue === undefined || leakedImmutableValue instanceof Value);
|
||||
let previousLeakedImmutableValue, previousHasLeaked, previousValue;
|
||||
// ensure that if either none or both sides have leaked
|
||||
// note that if one side didn't have a binding entry yet, then there's nothing to actively leak
|
||||
if (!l1 && l2) [g1, rewritten1] = leak(b, g1, v1, rewritten1);
|
||||
else if (l1 && !l2) [g2, rewritten2] = leak(b, g2, v2, rewritten2);
|
||||
let hasLeaked = l1 || l2;
|
||||
// For leaked (and mutable) bindings, the actual value is no longer directly available.
|
||||
// In that case, we reset the value to undefined to prevent any use of the last known value.
|
||||
let value = hasLeaked ? undefined : this.joinValues(realm, v1, v2, getAbstractValue);
|
||||
invariant(value === undefined || value instanceof Value);
|
||||
let previousHasLeaked, previousValue;
|
||||
if (b1 !== undefined) {
|
||||
previousLeakedImmutableValue = b1.previousLeakedImmutableValue;
|
||||
previousHasLeaked = b1.previousHasLeaked;
|
||||
previousValue = b1.previousValue;
|
||||
invariant(
|
||||
b2 === undefined ||
|
||||
(previousLeakedImmutableValue === b2.previousLeakedImmutableValue &&
|
||||
previousHasLeaked === b2.previousHasLeaked &&
|
||||
previousValue === b2.previousValue)
|
||||
b2 === undefined || (previousHasLeaked === b2.previousHasLeaked && previousValue === b2.previousValue)
|
||||
);
|
||||
} else if (b2 !== undefined) {
|
||||
previousLeakedImmutableValue = b2.previousLeakedImmutableValue;
|
||||
previousHasLeaked = b2.previousHasLeaked;
|
||||
previousValue = b2.previousValue;
|
||||
}
|
||||
return { leakedImmutableValue, hasLeaked, value, previousLeakedImmutableValue, previousHasLeaked, previousValue };
|
||||
return { hasLeaked, value, previousHasLeaked, previousValue };
|
||||
};
|
||||
return this.joinMaps(m1, m2, join);
|
||||
let joinedBindings = this.joinMaps(m1, m2, join);
|
||||
return [g1, g2, joinedBindings];
|
||||
}
|
||||
|
||||
// If v1 is known and defined and v1 === v2 return v1,
|
||||
|
@ -169,14 +169,11 @@ export class WidenImplementation {
|
||||
result._buildNode = args => t.identifier(phiName);
|
||||
}
|
||||
invariant(result instanceof Value);
|
||||
let previousLeakedImmutableValue = b2.previousLeakedImmutableValue;
|
||||
let previousHasLeaked = b2.previousHasLeaked;
|
||||
let previousValue = b2.previousValue;
|
||||
return {
|
||||
leakedImmutableValue: previousLeakedImmutableValue,
|
||||
hasLeaked: previousHasLeaked,
|
||||
value: result,
|
||||
previousLeakedImmutableValue,
|
||||
previousHasLeaked,
|
||||
previousValue,
|
||||
};
|
||||
@ -378,8 +375,7 @@ export class WidenImplementation {
|
||||
b1.value === undefined ||
|
||||
b2.value === undefined ||
|
||||
!this._containsValues(b1.value, b2.value) ||
|
||||
b1.hasLeaked !== b2.hasLeaked ||
|
||||
b1.leakedImmutableValue !== b2.leakedImmutableValue
|
||||
b1.hasLeaked !== b2.hasLeaked
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
@ -71,10 +71,8 @@ import type { BabelNode, BabelNodeSourceLocation, BabelNodeLVal, BabelNodeStatem
|
||||
import * as t from "babel-types";
|
||||
|
||||
export type BindingEntry = {
|
||||
leakedImmutableValue: void | Value,
|
||||
hasLeaked: void | boolean,
|
||||
value: void | Value,
|
||||
previousLeakedImmutableValue: void | Value,
|
||||
previousHasLeaked: void | boolean,
|
||||
previousValue: void | Value,
|
||||
};
|
||||
@ -1363,10 +1361,8 @@ export class Realm {
|
||||
|
||||
if (this.modifiedBindings !== undefined && !this.modifiedBindings.has(binding)) {
|
||||
this.modifiedBindings.set(binding, {
|
||||
leakedImmutableValue: undefined,
|
||||
hasLeaked: undefined,
|
||||
value: undefined,
|
||||
previousLeakedImmutableValue: binding.leakedImmutableValue,
|
||||
previousHasLeaked: binding.hasLeaked,
|
||||
previousValue: binding.value,
|
||||
});
|
||||
@ -1440,8 +1436,7 @@ export class Realm {
|
||||
|
||||
redoBindings(modifiedBindings: void | Bindings) {
|
||||
if (modifiedBindings === undefined) return;
|
||||
modifiedBindings.forEach(({ leakedImmutableValue, hasLeaked, value }, binding, m) => {
|
||||
binding.leakedImmutableValue = leakedImmutableValue;
|
||||
modifiedBindings.forEach(({ hasLeaked, value }, binding, m) => {
|
||||
binding.hasLeaked = hasLeaked || false;
|
||||
binding.value = value;
|
||||
});
|
||||
@ -1450,10 +1445,8 @@ export class Realm {
|
||||
undoBindings(modifiedBindings: void | Bindings) {
|
||||
if (modifiedBindings === undefined) return;
|
||||
modifiedBindings.forEach((entry, binding, m) => {
|
||||
if (entry.leakedImmutableValue === undefined) entry.leakedImmutableValue = binding.leakedImmutableValue;
|
||||
if (entry.hasLeaked === undefined) entry.hasLeaked = binding.hasLeaked;
|
||||
if (entry.value === undefined) entry.value = binding.value;
|
||||
binding.leakedImmutableValue = entry.previousLeakedImmutableValue;
|
||||
binding.hasLeaked = entry.previousHasLeaked || false;
|
||||
binding.value = entry.previousValue;
|
||||
});
|
||||
|
@ -41,13 +41,15 @@ export class Referentializer {
|
||||
realm: Realm,
|
||||
options: SerializerOptions,
|
||||
scopeNameGenerator: NameGenerator,
|
||||
scopeBindingNameGenerator: NameGenerator
|
||||
scopeBindingNameGenerator: NameGenerator,
|
||||
leakedNameGenerator: NameGenerator
|
||||
) {
|
||||
this._options = options;
|
||||
this.scopeNameGenerator = scopeNameGenerator;
|
||||
this.scopeBindingNameGenerator = scopeBindingNameGenerator;
|
||||
|
||||
this.referentializationState = new Map();
|
||||
this._leakedNameGenerator = leakedNameGenerator;
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
@ -58,6 +60,7 @@ export class Referentializer {
|
||||
|
||||
_newCapturedScopeInstanceIdx: number;
|
||||
referentializationState: Map<ReferentializationScope, ReferentializationState>;
|
||||
_leakedNameGenerator: NameGenerator;
|
||||
|
||||
getStatistics(): SerializerStatistics {
|
||||
invariant(this.realm.statistics instanceof SerializerStatistics, "serialization requires SerializerStatistics");
|
||||
@ -81,15 +84,23 @@ export class Referentializer {
|
||||
);
|
||||
}
|
||||
|
||||
createLeakedIds(referentializationScope: ReferentializationScope): Array<BabelNodeStatement> {
|
||||
const leakedIds = [];
|
||||
const serializedScopes = this._getReferentializationState(referentializationScope).serializedScopes;
|
||||
for (const scopeBinding of serializedScopes.values()) leakedIds.push(...scopeBinding.leakedIds);
|
||||
if (leakedIds.length === 0) return [];
|
||||
return [t.variableDeclaration("var", leakedIds.map(id => t.variableDeclarator(id)))];
|
||||
}
|
||||
|
||||
createCapturedScopesPrelude(referentializationScope: ReferentializationScope): Array<BabelNodeStatement> {
|
||||
let accessFunctionDeclaration = this._createCaptureScopeAccessFunction(referentializationScope);
|
||||
if (accessFunctionDeclaration === undefined) return [];
|
||||
return [accessFunctionDeclaration, this._createCapturedScopesArrayInitialization(referentializationScope)];
|
||||
}
|
||||
|
||||
// Generate a shared function for accessing captured scope bindings.
|
||||
// TODO: skip generating this function if the captured scope is not shared by multiple residual functions.
|
||||
createCaptureScopeAccessFunction(referentializationScope: ReferentializationScope): BabelNodeStatement {
|
||||
const body = [];
|
||||
const selectorParam = t.identifier("__selector");
|
||||
const captured = t.identifier("__captured");
|
||||
const capturedScopesArray = this._getReferentializationState(referentializationScope).capturedScopesArray;
|
||||
const selectorExpression = t.memberExpression(capturedScopesArray, selectorParam, /*Indexer syntax*/ true);
|
||||
|
||||
_createCaptureScopeAccessFunction(referentializationScope: ReferentializationScope): void | BabelNodeStatement {
|
||||
// One switch case for one scope.
|
||||
const cases = [];
|
||||
const serializedScopes = this._getReferentializationState(referentializationScope).serializedScopes;
|
||||
@ -99,6 +110,7 @@ export class Referentializer {
|
||||
|};
|
||||
const initializationCases: Map<string, InitializationCase> = new Map();
|
||||
for (const scopeBinding of serializedScopes.values()) {
|
||||
if (scopeBinding.initializationValues.length === 0) continue;
|
||||
const expr = t.arrayExpression((scopeBinding.initializationValues: any));
|
||||
const key = generate(expr, {}, "").code;
|
||||
if (!initializationCases.has(key)) {
|
||||
@ -112,6 +124,13 @@ export class Referentializer {
|
||||
ic.scopeIDs.push(scopeBinding.id);
|
||||
}
|
||||
}
|
||||
if (initializationCases.size === 0) return undefined;
|
||||
|
||||
const body = [];
|
||||
const selectorParam = t.identifier("__selector");
|
||||
const captured = t.identifier("__captured");
|
||||
const capturedScopesArray = this._getReferentializationState(referentializationScope).capturedScopesArray;
|
||||
const selectorExpression = t.memberExpression(capturedScopesArray, selectorParam, /*Indexer syntax*/ true);
|
||||
for (const ic of initializationCases.values()) {
|
||||
ic.scopeIDs.forEach((id, i) => {
|
||||
let consequent: Array<BabelNodeStatement> = [];
|
||||
@ -163,6 +182,7 @@ export class Referentializer {
|
||||
name: this.scopeNameGenerator.generate(),
|
||||
id: refState.capturedScopeInstanceIdx++,
|
||||
initializationValues: [],
|
||||
leakedIds: [],
|
||||
referentializationScope,
|
||||
};
|
||||
refState.serializedScopes.set(declarativeEnvironmentRecord, scope);
|
||||
@ -193,7 +213,20 @@ export class Referentializer {
|
||||
return [t.variableDeclaration("var", [t.variableDeclarator(t.identifier(capturedScope), init)])];
|
||||
}
|
||||
|
||||
referentializeBinding(residualBinding: ResidualFunctionBinding): void {
|
||||
referentializeLeakedBinding(residualBinding: ResidualFunctionBinding): void {
|
||||
invariant(residualBinding.hasLeaked);
|
||||
// When simpleClosures is enabled, then space for captured mutable bindings is allocated upfront.
|
||||
let serializedBindingId = t.identifier(this._leakedNameGenerator.generate(residualBinding.name));
|
||||
let scope = this._getSerializedBindingScopeInstance(residualBinding);
|
||||
scope.leakedIds.push(serializedBindingId);
|
||||
residualBinding.serializedValue = residualBinding.serializedUnscopedLocation = serializedBindingId;
|
||||
|
||||
this.getStatistics().referentialized++;
|
||||
}
|
||||
|
||||
referentializeModifiedBinding(residualBinding: ResidualFunctionBinding): void {
|
||||
invariant(residualBinding.modified);
|
||||
|
||||
// Space for captured mutable bindings is allocated lazily.
|
||||
let scope = this._getSerializedBindingScopeInstance(residualBinding);
|
||||
let capturedScope = "__captured" + scope.name;
|
||||
@ -258,6 +291,7 @@ export class Referentializer {
|
||||
let scope = refState.serializedScopes.get(declarativeEnvironmentRecord);
|
||||
if (scope) {
|
||||
scope.initializationValues = [];
|
||||
scope.leakedIds = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -274,7 +308,7 @@ export class Referentializer {
|
||||
// Initialize captured scope at function call instead of globally
|
||||
if (!residualBinding.declarativeEnvironmentRecord) residualBinding.referentialized = true;
|
||||
if (!residualBinding.referentialized) {
|
||||
this._getSerializedBindingScopeInstance(residualBinding);
|
||||
if (!residualBinding.hasLeaked) this._getSerializedBindingScopeInstance(residualBinding);
|
||||
residualBinding.referentialized = true;
|
||||
}
|
||||
|
||||
@ -286,7 +320,7 @@ export class Referentializer {
|
||||
}
|
||||
}
|
||||
|
||||
createCapturedScopesArrayInitialization(referentializationScope: ReferentializationScope): BabelNodeStatement {
|
||||
_createCapturedScopesArrayInitialization(referentializationScope: ReferentializationScope): BabelNodeStatement {
|
||||
return t.variableDeclaration("var", [
|
||||
t.variableDeclarator(
|
||||
this._getReferentializationState(referentializationScope).capturedScopesArray,
|
||||
|
@ -147,7 +147,10 @@ export class ResidualFunctions {
|
||||
let functionInfo = this.residualFunctionInfos.get(funcBody);
|
||||
invariant(functionInfo);
|
||||
let { usesArguments } = functionInfo;
|
||||
return !shouldInlineFunction() && instances.length > 1 && !usesArguments;
|
||||
let hasAnyLeakedIds = false;
|
||||
for (const instance of instances)
|
||||
for (const scope of instance.scopeInstances.values()) if (scope.leakedIds.length > 0) hasAnyLeakedIds = true;
|
||||
return !shouldInlineFunction() && instances.length > 1 && !usesArguments && !hasAnyLeakedIds;
|
||||
}
|
||||
|
||||
_getIdentifierReplacements(
|
||||
@ -617,8 +620,10 @@ export class ResidualFunctions {
|
||||
invariant(serializedValue);
|
||||
return serializedValue;
|
||||
});
|
||||
for (let entry of instance.scopeInstances) {
|
||||
flatArgs.push(t.numericLiteral(entry[1].id));
|
||||
let hasAnyLeakedIds = false;
|
||||
for (const scope of instance.scopeInstances.values()) {
|
||||
flatArgs.push(t.numericLiteral(scope.id));
|
||||
if (scope.leakedIds.length > 0) hasAnyLeakedIds = true;
|
||||
}
|
||||
let funcNode;
|
||||
let firstUsage = this.firstFunctionUsages.get(functionValue);
|
||||
@ -630,7 +635,8 @@ export class ResidualFunctions {
|
||||
usesThis ||
|
||||
hasFunctionArg ||
|
||||
(firstUsage !== undefined && !firstUsage.isNotEarlierThan(insertionPoint)) ||
|
||||
this.functionPrototypes.get(functionValue) !== undefined
|
||||
this.functionPrototypes.get(functionValue) !== undefined ||
|
||||
hasAnyLeakedIds
|
||||
) {
|
||||
let callArgs: Array<BabelNodeExpression | BabelNodeSpreadElement> = [t.thisExpression()];
|
||||
for (let flatArg of flatArgs) callArgs.push(flatArg);
|
||||
@ -673,8 +679,10 @@ export class ResidualFunctions {
|
||||
} else {
|
||||
prelude = this.prelude;
|
||||
}
|
||||
prelude.unshift(this.referentializer.createCaptureScopeAccessFunction(referentializationScope));
|
||||
prelude.unshift(this.referentializer.createCapturedScopesArrayInitialization(referentializationScope));
|
||||
prelude.unshift(
|
||||
...this.referentializer.createCapturedScopesPrelude(referentializationScope),
|
||||
...this.referentializer.createLeakedIds(referentializationScope)
|
||||
);
|
||||
}
|
||||
|
||||
for (let instance of this.functionInstances.reverse()) {
|
||||
|
@ -673,10 +673,15 @@ export class ResidualHeapSerializer {
|
||||
let value = residualFunctionBinding.value;
|
||||
invariant(residualFunctionBinding.declarativeEnvironmentRecord);
|
||||
|
||||
residualFunctionBinding.serializedValue = value !== undefined ? this.serializeValue(value) : voidExpression;
|
||||
if (residualFunctionBinding.modified) {
|
||||
this.referentializer.referentializeBinding(residualFunctionBinding);
|
||||
if (residualFunctionBinding.hasLeaked) {
|
||||
this.referentializer.referentializeLeakedBinding(residualFunctionBinding);
|
||||
} else {
|
||||
residualFunctionBinding.serializedValue = value !== undefined ? this.serializeValue(value) : voidExpression;
|
||||
if (residualFunctionBinding.modified) {
|
||||
this.referentializer.referentializeModifiedBinding(residualFunctionBinding);
|
||||
}
|
||||
}
|
||||
|
||||
if (value !== undefined && value.mightBeObject()) {
|
||||
// Increment ref count one more time to ensure that this object will be assigned a unique id.
|
||||
// This ensures that only once instance is created across all possible residual function invocations.
|
||||
|
@ -602,6 +602,7 @@ export class ResidualHeapVisitor {
|
||||
}
|
||||
|
||||
_visitBindingHelper(residualFunctionBinding: ResidualFunctionBinding) {
|
||||
if (residualFunctionBinding.hasLeaked) return;
|
||||
let environment = residualFunctionBinding.declarativeEnvironmentRecord;
|
||||
invariant(environment !== null);
|
||||
if (residualFunctionBinding.value === undefined) {
|
||||
@ -690,6 +691,7 @@ export class ResidualHeapVisitor {
|
||||
name,
|
||||
value: undefined,
|
||||
modified: true,
|
||||
hasLeaked: false,
|
||||
declarativeEnvironmentRecord: null,
|
||||
potentialReferentializationScopes: new Set(),
|
||||
};
|
||||
@ -717,6 +719,7 @@ export class ResidualHeapVisitor {
|
||||
name,
|
||||
value: undefined,
|
||||
modified: false,
|
||||
hasLeaked: false,
|
||||
declarativeEnvironmentRecord: environment,
|
||||
potentialReferentializationScopes: new Set(),
|
||||
};
|
||||
@ -1153,6 +1156,7 @@ export class ResidualHeapVisitor {
|
||||
visitBindingAssignment: (binding: Binding, value: Value) => {
|
||||
let residualBinding = this.getBinding(binding.environment, binding.name);
|
||||
residualBinding.modified = true;
|
||||
residualBinding.hasLeaked = true;
|
||||
return this.visitEquivalentValue(value);
|
||||
},
|
||||
};
|
||||
|
@ -141,7 +141,8 @@ export class Serializer {
|
||||
this.realm,
|
||||
this.options,
|
||||
preludeGenerator.createNameGenerator("__scope_"),
|
||||
preludeGenerator.createNameGenerator("__get_scope_binding_")
|
||||
preludeGenerator.createNameGenerator("__get_scope_binding_"),
|
||||
preludeGenerator.createNameGenerator("__leaked_")
|
||||
);
|
||||
if (this.realm.react.verbose) {
|
||||
this.logger.logInformation(`Visiting evaluated nodes...`);
|
||||
|
@ -107,6 +107,7 @@ export type ResidualFunctionBinding = {
|
||||
name: string,
|
||||
value: void | Value,
|
||||
modified: boolean,
|
||||
hasLeaked: boolean,
|
||||
// null means a global binding
|
||||
declarativeEnvironmentRecord: null | DeclarativeEnvironmentRecord,
|
||||
// The serializedValue is only not yet present during the initialization of a binding that involves recursive dependencies.
|
||||
@ -127,6 +128,7 @@ export type ScopeBinding = {
|
||||
name: string,
|
||||
id: number,
|
||||
initializationValues: Array<BabelNodeExpression>,
|
||||
leakedIds: Array<BabelNodeIdentifier>,
|
||||
capturedScope?: string,
|
||||
referentializationScope: ReferentializationScope,
|
||||
};
|
||||
|
@ -792,7 +792,14 @@ export type JoinType = {
|
||||
// sets of m1 and m2. The value of a pair is the join of m1[key] and m2[key]
|
||||
// where the join is defined to be just m1[key] if m1[key] === m2[key] and
|
||||
// and abstract value with expression "joinCondition ? m1[key] : m2[key]" if not.
|
||||
joinBindings(realm: Realm, joinCondition: AbstractValue, m1: Bindings, m2: Bindings): Bindings,
|
||||
joinBindings(
|
||||
realm: Realm,
|
||||
joinCondition: AbstractValue,
|
||||
g1: Generator,
|
||||
m1: Bindings,
|
||||
g2: Generator,
|
||||
m2: Bindings
|
||||
): [Generator, Generator, Bindings],
|
||||
|
||||
// If v1 is known and defined and v1 === v2 return v1,
|
||||
// otherwise return getAbstractValue(v1, v2)
|
||||
|
27
test/serializer/optimized-functions/HavocBindings10.js
Normal file
27
test/serializer/optimized-functions/HavocBindings10.js
Normal file
@ -0,0 +1,27 @@
|
||||
// This test is there to check for a regression where code was generated
|
||||
// that used a variable before it was declared, which trips the linter.
|
||||
// The issue arose when joining a binding from a declarative environment record that only existed in one branch of the joined executions.
|
||||
(function() {
|
||||
function makeClosure(bar) {
|
||||
if (bar) return null;
|
||||
var captured = bar;
|
||||
return function closure() {
|
||||
return captured;
|
||||
}
|
||||
}
|
||||
|
||||
function fn(arg) {
|
||||
if (arg) return undefined;
|
||||
var state = {};
|
||||
state.closure = makeClosure(arg.bar);
|
||||
arg.baz(state);
|
||||
}
|
||||
|
||||
global.fn = fn;
|
||||
|
||||
if (global.__optimize) {
|
||||
__optimize(fn);
|
||||
}
|
||||
|
||||
inspect = function() { return fn(true); }
|
||||
})();
|
16
test/serializer/optimized-functions/HavocBindings8.js
Normal file
16
test/serializer/optimized-functions/HavocBindings8.js
Normal file
@ -0,0 +1,16 @@
|
||||
function fn(x, y, abstractVal) {
|
||||
var value = x.toString();
|
||||
|
||||
if (y) {
|
||||
abstractVal(function() {
|
||||
value += "-next";
|
||||
});
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
global.__optimize && __optimize(fn);
|
||||
|
||||
inspect = function() {
|
||||
return fn(10, false, function() {});
|
||||
};
|
19
test/serializer/optimized-functions/HavocBindings9.js
Normal file
19
test/serializer/optimized-functions/HavocBindings9.js
Normal file
@ -0,0 +1,19 @@
|
||||
(function () {
|
||||
function f(c, g) {
|
||||
let x = 23;
|
||||
let y;
|
||||
if (c) {
|
||||
x = Date.now();
|
||||
function h() { y = x; x++; }
|
||||
g(h);
|
||||
return x - y;
|
||||
} else {
|
||||
x = Date.now();
|
||||
function h() { y = x; x++; }
|
||||
g(h);
|
||||
return x - y;
|
||||
}
|
||||
}
|
||||
global.__optimize && __optimize(f);
|
||||
global.inspect = function() { return f(true, g => g()); }
|
||||
})();
|
Loading…
Reference in New Issue
Block a user