Don't emit modified bindings unless they are alive (#2287)

Summary:
Release notes: Enhanced dead code elimination for optimized functions

This resolves #2276: Visiting+emitting modified bindings now participates
in the fixed point computation, checking for whether the modified binding
is actually getting visited for other reasons.

The previously existing code that did CSE by mutating the generator entry
got removed, as it wouldn't work exactly like that anymore (can't return value
because actual analysis gets postponed for fixed point computation). Filed #2286
to keep track of that and more needed changes around CSE for values stored in locations
participating in effects tracking. --- Turns out that we didn't have a test anyway
that would detect whether binding CSE happens or not.

Added regression test.
Pull Request resolved: https://github.com/facebook/prepack/pull/2287

Differential Revision: D8902345

Pulled By: NTillmann

fbshipit-source-id: 6ba3fb9755312956829050f0278dee2d85d6a261
This commit is contained in:
Nikolai Tillmann 2018-07-18 14:36:57 -07:00 committed by Facebook Github Bot
parent 7d355ef4c5
commit eeb081a545
6 changed files with 122 additions and 79 deletions

View File

@ -70,6 +70,7 @@ export class LazyObjectsSerializer extends ResidualHeapSerializer {
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects> | void,
additionalFunctionValueInfos: Map<FunctionValue, AdditionalFunctionInfo>,
declarativeEnvironmentRecordsBindings: Map<DeclarativeEnvironmentRecord, Map<string, ResidualFunctionBinding>>,
globalBindings: Map<string, ResidualFunctionBinding>,
referentializer: Referentializer,
generatorDAG: GeneratorDAG,
conditionalFeasibility: Map<AbstractValue, { t: boolean, f: boolean }>,
@ -90,6 +91,7 @@ export class LazyObjectsSerializer extends ResidualHeapSerializer {
additionalFunctionValuesAndEffects,
additionalFunctionValueInfos,
declarativeEnvironmentRecordsBindings,
globalBindings,
referentializer,
generatorDAG,
conditionalFeasibility,

View File

@ -78,7 +78,7 @@ import { canHoistFunction } from "../react/hoisting.js";
import { To } from "../singletons.js";
import { ResidualReactElementSerializer } from "./ResidualReactElementSerializer.js";
import type { Binding } from "../environment.js";
import { DeclarativeEnvironmentRecord, FunctionEnvironmentRecord } from "../environment.js";
import { GlobalEnvironmentRecord, DeclarativeEnvironmentRecord, FunctionEnvironmentRecord } from "../environment.js";
import type { Referentializer } from "./Referentializer.js";
import { GeneratorDAG } from "./GeneratorDAG.js";
import { type Replacement, getReplacement } from "./ResidualFunctionInstantiator";
@ -124,6 +124,7 @@ export class ResidualHeapSerializer {
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects> | void,
additionalFunctionValueInfos: Map<FunctionValue, AdditionalFunctionInfo>,
declarativeEnvironmentRecordsBindings: Map<DeclarativeEnvironmentRecord, Map<string, ResidualFunctionBinding>>,
globalBindings: Map<string, ResidualFunctionBinding>,
referentializer: Referentializer,
generatorDAG: GeneratorDAG,
conditionalFeasibility: Map<AbstractValue, { t: boolean, f: boolean }>,
@ -211,12 +212,16 @@ export class ResidualHeapSerializer {
this.additionalFunctionValueInfos = additionalFunctionValueInfos;
this.rewrittenAdditionalFunctions = new Map();
this.declarativeEnvironmentRecordsBindings = declarativeEnvironmentRecordsBindings;
this.globalBindings = globalBindings;
this.generatorDAG = generatorDAG;
this.conditionalFeasibility = conditionalFeasibility;
this.additionalFunctionGenerators = new Map();
this.declaredGlobalLets = new Map();
this._objectSemaphores = new Map();
this.additionalGeneratorRoots = additionalGeneratorRoots;
let environment = realm.$GlobalEnv.environmentRecord;
invariant(environment instanceof GlobalEnvironmentRecord);
this.globalEnvironmentRecord = environment;
}
emitter: Emitter;
@ -256,6 +261,7 @@ export class ResidualHeapSerializer {
additionalFunctionValueInfos: Map<FunctionValue, AdditionalFunctionInfo>;
rewrittenAdditionalFunctions: Map<FunctionValue, Array<BabelNodeStatement>>;
declarativeEnvironmentRecordsBindings: Map<DeclarativeEnvironmentRecord, Map<string, ResidualFunctionBinding>>;
globalBindings: Map<string, ResidualFunctionBinding>;
residualReactElementSerializer: ResidualReactElementSerializer;
referentializer: Referentializer;
additionalFunctionGenerators: Map<FunctionValue, Generator>;
@ -269,6 +275,7 @@ export class ResidualHeapSerializer {
additionalGeneratorRoots: Map<Generator, Set<ObjectValue>>;
declaredGlobalLets: Map<string, Value>;
globalEnvironmentRecord: GlobalEnvironmentRecord;
getStatistics(): SerializerStatistics {
invariant(this.realm.statistics instanceof SerializerStatistics, "serialization requires SerializerStatistics");
@ -967,17 +974,23 @@ export class ResidualHeapSerializer {
return name;
}
serializeBinding(binding: Binding): BabelNodeIdentifier | BabelNodeMemberExpression {
let record = binding.environment;
invariant(record instanceof DeclarativeEnvironmentRecord, "only declarative environments has bindings");
_getResidualFunctionBinding(binding: Binding): void | ResidualFunctionBinding {
let environment = binding.environment;
if (environment === this.globalEnvironmentRecord.$DeclarativeRecord) environment = this.globalEnvironmentRecord;
let residualFunctionBindings = this.declarativeEnvironmentRecordsBindings.get(record);
invariant(
residualFunctionBindings,
"all bindings that create abstract values must have at least one call emitted to the generator so the function environment should have been visited"
);
let residualBinding = residualFunctionBindings.get(binding.name);
invariant(residualBinding, "any referenced residual binding should have been visited");
if (environment === this.globalEnvironmentRecord) {
return this.globalBindings.get(binding.name);
}
invariant(environment instanceof DeclarativeEnvironmentRecord, "only declarative environments have bindings");
let residualFunctionBindings = this.declarativeEnvironmentRecordsBindings.get(environment);
if (residualFunctionBindings === undefined) return undefined;
return residualFunctionBindings.get(binding.name);
}
serializeBinding(binding: Binding): BabelNodeIdentifier | BabelNodeMemberExpression {
let residualBinding = this._getResidualFunctionBinding(binding);
invariant(residualBinding !== undefined, "any referenced residual binding should have been visited");
this._serializeDeclarativeEnvironmentRecordBinding(residualBinding);
@ -2150,6 +2163,25 @@ export class ResidualHeapSerializer {
declare: (value: AbstractValue | ObjectValue) => {
this.emitter.declare(value);
},
emitBindingModification: (binding: Binding) => {
let residualFunctionBinding = this._getResidualFunctionBinding(binding);
if (residualFunctionBinding !== undefined) {
invariant(residualFunctionBinding.referentialized);
invariant(
residualFunctionBinding.serializedValue,
"ResidualFunctionBinding must be referentialized before serializing a mutation to it."
);
let newValue = binding.value;
invariant(newValue);
let bindingReference = ((residualFunctionBinding.serializedValue: any): BabelNodeLVal);
invariant(
t.isLVal(bindingReference),
"Referentialized values must be LVals even though serializedValues may be any Expression"
);
let serializedNewValue = this.serializeValue(newValue);
this.emitter.emit(t.expressionStatement(t.assignmentExpression("=", bindingReference, serializedNewValue)));
}
},
emitPropertyModification: (propertyBinding: PropertyBinding) => {
let desc = propertyBinding.descriptor;
let object = propertyBinding.object;

View File

@ -682,6 +682,21 @@ export class ResidualHeapVisitor {
}
}
hasBinding(environment: EnvironmentRecord, name: string): boolean {
if (environment === this.globalEnvironmentRecord.$DeclarativeRecord) environment = this.globalEnvironmentRecord;
if (environment === this.globalEnvironmentRecord) {
// Global Binding
return this.globalBindings.get(name) !== undefined;
} else {
invariant(environment instanceof DeclarativeEnvironmentRecord);
// DeclarativeEnvironmentRecord binding
let residualFunctionBindings = this.declarativeEnvironmentRecordsBindings.get(environment);
if (residualFunctionBindings === undefined) return false;
return residualFunctionBindings.get(name) !== undefined;
}
}
// Visits a binding, returns a ResidualFunctionBinding
getBinding(environment: EnvironmentRecord, name: string): ResidualFunctionBinding {
if (environment === this.globalEnvironmentRecord.$DeclarativeRecord) environment = this.globalEnvironmentRecord;
@ -1104,7 +1119,7 @@ export class ResidualHeapVisitor {
action: () => entry.visit(callbacks, generator),
});
},
visitModifiedObjectProperty: (binding: PropertyBinding) => {
visitModifiedProperty: (binding: PropertyBinding) => {
let fixpoint_rerun = () => {
if (this.values.has(binding.object)) {
if (binding.internalSlot) {
@ -1130,38 +1145,46 @@ export class ResidualHeapVisitor {
fixpoint_rerun();
},
visitModifiedBinding: (modifiedBinding: Binding) => {
invariant(additionalFunctionInfo);
let { functionValue } = additionalFunctionInfo;
invariant(functionValue instanceof ECMAScriptSourceFunctionValue);
let residualBinding = this.getBinding(modifiedBinding.environment, modifiedBinding.name);
let funcInstance = additionalFunctionInfo.instance;
invariant(funcInstance !== undefined);
funcInstance.residualFunctionBindings.set(modifiedBinding.name, residualBinding);
let newValue = modifiedBinding.value;
invariant(newValue);
newValue = this.visitEquivalentValue(newValue);
residualBinding.modified = true;
let otherFunc = residualBinding.additionalFunctionOverridesValue;
if (otherFunc !== undefined && otherFunc !== functionValue) {
let otherNameVal = otherFunc._SafeGetDataPropertyValue("name");
let otherNameStr = otherNameVal instanceof StringValue ? otherNameVal.value : "unknown function";
let funcNameVal = functionValue._SafeGetDataPropertyValue("name");
let funNameStr = funcNameVal instanceof StringValue ? funcNameVal.value : "unknown function";
let error = new CompilerDiagnostic(
`Variable ${
modifiedBinding.name
} written to in optimized function ${funNameStr} conflicts with write in another optimized function ${otherNameStr}`,
funcNameVal.expressionLocation,
"PP1001",
"RecoverableError"
);
if (functionValue.$Realm.handleError(error) === "Fail") throw new FatalError();
}
residualBinding.additionalFunctionOverridesValue = functionValue;
additionalFunctionInfo.modifiedBindings.set(modifiedBinding, residualBinding);
// TODO nested optimized functions: revisit adding GLOBAL as outer optimized function
residualBinding.potentialReferentializationScopes.add("GLOBAL");
return [residualBinding, newValue];
let fixpoint_rerun = () => {
if (this.hasBinding(modifiedBinding.environment, modifiedBinding.name)) {
invariant(additionalFunctionInfo);
let { functionValue } = additionalFunctionInfo;
invariant(functionValue instanceof ECMAScriptSourceFunctionValue);
let residualBinding = this.getBinding(modifiedBinding.environment, modifiedBinding.name);
let funcInstance = additionalFunctionInfo.instance;
invariant(funcInstance !== undefined);
funcInstance.residualFunctionBindings.set(modifiedBinding.name, residualBinding);
let newValue = modifiedBinding.value;
invariant(newValue);
this.visitValue(newValue);
residualBinding.modified = true;
let otherFunc = residualBinding.additionalFunctionOverridesValue;
if (otherFunc !== undefined && otherFunc !== functionValue) {
let otherNameVal = otherFunc._SafeGetDataPropertyValue("name");
let otherNameStr = otherNameVal instanceof StringValue ? otherNameVal.value : "unknown function";
let funcNameVal = functionValue._SafeGetDataPropertyValue("name");
let funNameStr = funcNameVal instanceof StringValue ? funcNameVal.value : "unknown function";
let error = new CompilerDiagnostic(
`Variable ${
modifiedBinding.name
} written to in optimized function ${funNameStr} conflicts with write in another optimized function ${otherNameStr}`,
funcNameVal.expressionLocation,
"PP1001",
"RecoverableError"
);
if (functionValue.$Realm.handleError(error) === "Fail") throw new FatalError();
}
residualBinding.additionalFunctionOverridesValue = functionValue;
additionalFunctionInfo.modifiedBindings.set(modifiedBinding, residualBinding);
// TODO nested optimized functions: revisit adding GLOBAL as outer optimized function
residualBinding.potentialReferentializationScopes.add("GLOBAL");
return true;
} else {
this._enqueueWithUnrelatedScope(this.scope, fixpoint_rerun);
return false;
}
};
fixpoint_rerun();
},
visitBindingAssignment: (binding: Binding, value: Value) => {
let residualBinding = this.getBinding(binding.environment, binding.name);

View File

@ -241,6 +241,7 @@ export class Serializer {
additionalFunctionValuesAndEffects,
residualHeapVisitor.additionalFunctionValueInfos,
residualHeapVisitor.declarativeEnvironmentRecordsBindings,
residualHeapVisitor.globalBindings,
referentializer,
residualHeapVisitor.generatorDAG,
residualHeapVisitor.conditionalFeasibility,
@ -271,6 +272,7 @@ export class Serializer {
additionalFunctionValuesAndEffects,
residualHeapVisitor.additionalFunctionValueInfos,
residualHeapVisitor.declarativeEnvironmentRecordsBindings,
residualHeapVisitor.globalBindings,
referentializer,
residualHeapVisitor.generatorDAG,
residualHeapVisitor.conditionalFeasibility,

View File

@ -11,7 +11,6 @@
import type { Realm, Effects } from "../realm.js";
import type { ConsoleMethodTypes, Descriptor, PropertyBinding, DisplayResult } from "../types.js";
import type { ResidualFunctionBinding } from "../serializer/types.js";
import type { Binding } from "../environment.js";
import {
AbstractObjectValue,
@ -76,6 +75,7 @@ export type SerializationContext = {|
canOmit: Value => boolean,
declare: (AbstractValue | ObjectValue) => void,
emitPropertyModification: PropertyBinding => void,
emitBindingModification: Binding => void,
options: SerializerOptions,
|};
@ -85,8 +85,8 @@ export type VisitEntryCallbacks = {|
canOmit: Value => boolean,
recordDeclaration: (AbstractValue | ObjectValue) => void,
recordDelayedEntry: (Generator, GeneratorEntry) => void,
visitModifiedObjectProperty: PropertyBinding => void,
visitModifiedBinding: Binding => [ResidualFunctionBinding, Value],
visitModifiedProperty: PropertyBinding => void,
visitModifiedBinding: Binding => void,
visitBindingAssignment: (Binding, Value) => Value,
|};
@ -310,7 +310,7 @@ class ModifiedPropertyEntry extends GeneratorEntry {
);
let desc = this.propertyBinding.descriptor;
invariant(desc === this.newDescriptor);
context.visitModifiedObjectProperty(this.propertyBinding);
context.visitModifiedProperty(this.propertyBinding);
return true;
}
@ -321,7 +321,6 @@ class ModifiedPropertyEntry extends GeneratorEntry {
type ModifiedBindingEntryArgs = {|
modifiedBinding: Binding,
newValue: void | Value,
containingGenerator: Generator,
|};
@ -333,30 +332,13 @@ class ModifiedBindingEntry extends GeneratorEntry {
containingGenerator: Generator;
modifiedBinding: Binding;
newValue: void | Value;
residualFunctionBinding: void | ResidualFunctionBinding;
toDisplayString(): string {
return `[ModifiedBinding ${this.modifiedBinding.name}]`;
}
serialize(context: SerializationContext): void {
let residualFunctionBinding = this.residualFunctionBinding;
invariant(residualFunctionBinding !== undefined);
invariant(residualFunctionBinding.referentialized);
invariant(
residualFunctionBinding.serializedValue,
"ResidualFunctionBinding must be referentialized before serializing a mutation to it."
);
let newValue = this.newValue;
invariant(newValue);
let bindingReference = ((residualFunctionBinding.serializedValue: any): BabelNodeLVal);
invariant(
t.isLVal(bindingReference),
"Referentialized values must be LVals even though serializedValues may be any Expression"
);
let serializedNewValue = context.serializeValue(newValue);
context.emit(t.expressionStatement(t.assignmentExpression("=", bindingReference, serializedNewValue)));
context.emitBindingModification(this.modifiedBinding);
}
visit(context: VisitEntryCallbacks, containingGenerator: Generator): boolean {
@ -364,17 +346,7 @@ class ModifiedBindingEntry extends GeneratorEntry {
containingGenerator === this.containingGenerator,
"This entry requires effects to be applied and may not be moved"
);
invariant(
this.modifiedBinding.value === this.newValue,
"ModifiedBinding's value has been changed since last visit."
);
let [residualBinding, newValue] = context.visitModifiedBinding(this.modifiedBinding);
invariant(
this.residualFunctionBinding === undefined || this.residualFunctionBinding === residualBinding,
"ResidualFunctionBinding has been changed since last visit."
);
this.residualFunctionBinding = residualBinding;
this.newValue = newValue;
context.visitModifiedBinding(this.modifiedBinding);
return true;
}
@ -641,7 +613,6 @@ export class Generator {
this._entries.push(
new ModifiedBindingEntry(this.realm, {
modifiedBinding,
newValue: modifiedBinding.value,
containingGenerator: this,
})
);

View File

@ -0,0 +1,13 @@
// does not contain:reachMe
(function() {
var x;
function g() {
x = { canYou: "reachMe?" };
}
function f() {
g();
return 42;
}
if (global.__optimize) __optimize(f);
global.inspect = f;
})();