mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-08-17 10:30:32 +03:00
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:
parent
7d355ef4c5
commit
eeb081a545
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
})
|
||||
);
|
||||
|
13
test/serializer/optimized-functions/DeadModifiedBindings.js
Normal file
13
test/serializer/optimized-functions/DeadModifiedBindings.js
Normal 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;
|
||||
})();
|
Loading…
Reference in New Issue
Block a user