Purge some local bindings that are not captured in closures. (#2152)

Summary:
Release note: none

This reverses PR #2136, which Dan has noted to lead to a 3x slowdown for an internal bundle. In order to fix the problem that Nikolai's PR addressed, the original code has been modified to not purge any local bindings that could possibly have been captured in a closure.

I expect that most effects will not be in a context that contains a closure, so this should help. It would be nice, however, to get some feedback on whether this is actually the case.
Closes https://github.com/facebook/prepack/pull/2152

Differential Revision: D8617877

Pulled By: hermanventer

fbshipit-source-id: 8e907ba1dded0428388fd42132658555e3b8e82e
This commit is contained in:
Herman Venter 2018-06-25 11:26:58 -07:00 committed by Facebook Github Bot
parent 5a5a100483
commit f9e102c0d2
3 changed files with 95 additions and 0 deletions

View File

@ -155,6 +155,7 @@ export type Binding = {
environment: EnvironmentRecord,
name: string,
isGlobal: boolean,
mightHaveBeenCaptured: boolean,
// bindings that are assigned to inside loops with abstract termination conditions need temporal locations
phiNode?: AbstractValue,
hasLeaked: boolean,
@ -203,6 +204,7 @@ export class DeclarativeEnvironmentRecord extends EnvironmentRecord {
environment: envRec,
name: N,
isGlobal: isGlobal,
mightHaveBeenCaptured: false,
hasLeaked: false,
});
@ -229,6 +231,7 @@ export class DeclarativeEnvironmentRecord extends EnvironmentRecord {
environment: envRec,
name: N,
isGlobal: isGlobal,
mightHaveBeenCaptured: false,
hasLeaked: false,
};
this.bindings[N] = skipRecord ? binding : realm.recordModifiedBinding(binding);

View File

@ -746,6 +746,9 @@ export class FunctionImplementation {
Body: BabelNodeBlockStatement,
Scope: LexicalEnvironment
): ECMAScriptSourceFunctionValue {
// Tell the realm to mark any local bindings that are visible to this function as being potentially captured by it.
realm.markVisibleLocalBindingsAsPotentiallyCaptured();
// Note that F is a new object, and we can thus write to internal slots
invariant(realm.isNewObject(F));

View File

@ -499,10 +499,48 @@ export class Realm {
return context;
}
clearBlockBindings(modifiedBindings: void | Bindings, environmentRecord: DeclarativeEnvironmentRecord) {
if (modifiedBindings === undefined) return;
for (let b of modifiedBindings.keys()) {
if (b.mightHaveBeenCaptured) continue;
if (environmentRecord.bindings[b.name] && environmentRecord.bindings[b.name] === b) modifiedBindings.delete(b);
}
}
clearBlockBindingsFromCompletion(completion: Completion, environmentRecord: DeclarativeEnvironmentRecord) {
if (completion instanceof PossiblyNormalCompletion) {
this.clearBlockBindings(completion.alternateEffects.modifiedBindings, environmentRecord);
this.clearBlockBindings(completion.consequentEffects.modifiedBindings, environmentRecord);
if (completion.savedEffects !== undefined)
this.clearBlockBindings(completion.savedEffects.modifiedBindings, environmentRecord);
if (completion.alternate instanceof Completion)
this.clearBlockBindingsFromCompletion(completion.alternate, environmentRecord);
if (completion.consequent instanceof Completion)
this.clearBlockBindingsFromCompletion(completion.consequent, environmentRecord);
} else if (completion instanceof ForkedAbruptCompletion) {
this.clearBlockBindings(completion.alternateEffects.modifiedBindings, environmentRecord);
this.clearBlockBindings(completion.consequentEffects.modifiedBindings, environmentRecord);
if (completion.alternate instanceof Completion)
this.clearBlockBindingsFromCompletion(completion.alternate, environmentRecord);
if (completion.consequent instanceof Completion)
this.clearBlockBindingsFromCompletion(completion.consequent, environmentRecord);
}
}
// Call when a scope falls out of scope and should be destroyed.
// Clears the Bindings corresponding to the disappearing Scope from ModifiedBindings
onDestroyScope(lexicalEnvironment: LexicalEnvironment) {
invariant(this.activeLexicalEnvironments.has(lexicalEnvironment));
let modifiedBindings = this.modifiedBindings;
if (modifiedBindings) {
// Don't undo things to global scope because it's needed past its destruction point (for serialization)
let environmentRecord = lexicalEnvironment.environmentRecord;
if (environmentRecord instanceof DeclarativeEnvironmentRecord) {
this.clearBlockBindings(modifiedBindings, environmentRecord);
if (this.savedCompletion !== undefined)
this.clearBlockBindingsFromCompletion(this.savedCompletion, environmentRecord);
}
}
// Ensures if we call onDestroyScope too early, there will be a failure.
this.activeLexicalEnvironments.delete(lexicalEnvironment);
@ -516,7 +554,58 @@ export class Realm {
this.contextStack.push(context);
}
markVisibleLocalBindingsAsPotentiallyCaptured() {
let context = this.getRunningContext();
if (context.function === undefined) return;
let lexEnv = context.lexicalEnvironment;
while (lexEnv != null) {
let envRec = lexEnv.environmentRecord;
if (envRec instanceof DeclarativeEnvironmentRecord) {
let bindings = envRec.bindings;
for (let name in bindings) {
let binding = bindings[name];
binding.mightHaveBeenCaptured = true;
}
}
lexEnv = lexEnv.parent;
}
}
clearFunctionBindings(modifiedBindings: void | Bindings, funcVal: FunctionValue) {
if (modifiedBindings === undefined) return;
for (let b of modifiedBindings.keys()) {
if (b.mightHaveBeenCaptured) continue;
if (b.environment instanceof FunctionEnvironmentRecord && b.environment.$FunctionObject === funcVal)
modifiedBindings.delete(b);
}
}
clearFunctionBindingsFromCompletion(completion: Completion, funcVal: FunctionValue) {
if (completion instanceof PossiblyNormalCompletion) {
this.clearFunctionBindings(completion.alternateEffects.modifiedBindings, funcVal);
this.clearFunctionBindings(completion.consequentEffects.modifiedBindings, funcVal);
if (completion.savedEffects !== undefined)
this.clearFunctionBindings(completion.savedEffects.modifiedBindings, funcVal);
if (completion.alternate instanceof Completion)
this.clearFunctionBindingsFromCompletion(completion.alternate, funcVal);
if (completion.consequent instanceof Completion)
this.clearFunctionBindingsFromCompletion(completion.consequent, funcVal);
} else if (completion instanceof ForkedAbruptCompletion) {
this.clearFunctionBindings(completion.alternateEffects.modifiedBindings, funcVal);
this.clearFunctionBindings(completion.consequentEffects.modifiedBindings, funcVal);
if (completion.alternate instanceof Completion)
this.clearFunctionBindingsFromCompletion(completion.alternate, funcVal);
if (completion.consequent instanceof Completion)
this.clearFunctionBindingsFromCompletion(completion.consequent, funcVal);
}
}
popContext(context: ExecutionContext): void {
let funcVal = context.function;
if (funcVal) {
this.clearFunctionBindings(this.modifiedBindings, funcVal);
if (this.savedCompletion !== undefined) this.clearFunctionBindingsFromCompletion(this.savedCompletion, funcVal);
}
let c = this.contextStack.pop();
invariant(c === context);
}