diff --git a/src/serializer/LazyObjectsSerializer.js b/src/serializer/LazyObjectsSerializer.js index 594984914..428d272ab 100644 --- a/src/serializer/LazyObjectsSerializer.js +++ b/src/serializer/LazyObjectsSerializer.js @@ -70,6 +70,7 @@ export class LazyObjectsSerializer extends ResidualHeapSerializer { additionalFunctionValuesAndEffects: Map | void, additionalFunctionValueInfos: Map, declarativeEnvironmentRecordsBindings: Map>, + globalBindings: Map, referentializer: Referentializer, generatorDAG: GeneratorDAG, conditionalFeasibility: Map, @@ -90,6 +91,7 @@ export class LazyObjectsSerializer extends ResidualHeapSerializer { additionalFunctionValuesAndEffects, additionalFunctionValueInfos, declarativeEnvironmentRecordsBindings, + globalBindings, referentializer, generatorDAG, conditionalFeasibility, diff --git a/src/serializer/ResidualHeapSerializer.js b/src/serializer/ResidualHeapSerializer.js index 9db962609..b28bd0002 100644 --- a/src/serializer/ResidualHeapSerializer.js +++ b/src/serializer/ResidualHeapSerializer.js @@ -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 | void, additionalFunctionValueInfos: Map, declarativeEnvironmentRecordsBindings: Map>, + globalBindings: Map, referentializer: Referentializer, generatorDAG: GeneratorDAG, conditionalFeasibility: Map, @@ -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; rewrittenAdditionalFunctions: Map>; declarativeEnvironmentRecordsBindings: Map>; + globalBindings: Map; residualReactElementSerializer: ResidualReactElementSerializer; referentializer: Referentializer; additionalFunctionGenerators: Map; @@ -269,6 +275,7 @@ export class ResidualHeapSerializer { additionalGeneratorRoots: Map>; declaredGlobalLets: Map; + 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; diff --git a/src/serializer/ResidualHeapVisitor.js b/src/serializer/ResidualHeapVisitor.js index 0fa0bc579..a94d3976a 100644 --- a/src/serializer/ResidualHeapVisitor.js +++ b/src/serializer/ResidualHeapVisitor.js @@ -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); diff --git a/src/serializer/serializer.js b/src/serializer/serializer.js index bcf03dfc4..7f5a3ad60 100644 --- a/src/serializer/serializer.js +++ b/src/serializer/serializer.js @@ -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, diff --git a/src/utils/generator.js b/src/utils/generator.js index 7ef59b182..4349e742f 100644 --- a/src/utils/generator.js +++ b/src/utils/generator.js @@ -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, }) ); diff --git a/test/serializer/optimized-functions/DeadModifiedBindings.js b/test/serializer/optimized-functions/DeadModifiedBindings.js new file mode 100644 index 000000000..6d046162c --- /dev/null +++ b/test/serializer/optimized-functions/DeadModifiedBindings.js @@ -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; +})();