diff --git a/src/serializer/ResidualHeapSerializer.js b/src/serializer/ResidualHeapSerializer.js index 38b0255472..22c7de13b3 100644 --- a/src/serializer/ResidualHeapSerializer.js +++ b/src/serializer/ResidualHeapSerializer.js @@ -79,7 +79,7 @@ import { canHoistFunction } from "../react/hoisting.js"; import { To } from "../singletons.js"; import { ResidualReactElementSerializer } from "./ResidualReactElementSerializer.js"; import type { Binding } from "../environment.js"; -import { GlobalEnvironmentRecord, DeclarativeEnvironmentRecord } 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.js"; @@ -746,13 +746,34 @@ export class ResidualHeapSerializer { } let additionalFVEffects = this.additionalFunctionValuesAndEffects; if (additionalFVEffects) { + // for optimized functions, we should use created objects let maybeParentFunctionInfo = additionalFVEffects.get(maybeParentFunction); if (maybeParentFunctionInfo && maybeParentFunctionInfo.effects.createdObjects.has(childFunction)) return true; + } else { + // for other functions, check environment records + let env = childFunction.$Environment; + while (env.parent !== null) { + let envRecord = env.environmentRecord; + if (envRecord instanceof FunctionEnvironmentRecord && envRecord.$FunctionObject === maybeParentFunction) + return true; + env = env.parent; + } } } return false; } + // Check if an optimized function defines the given set of functions. + definesFunctions(possibleParentFunction: FunctionValue, functions: Set<FunctionValue>): boolean { + let additionalFVEffects = this.additionalFunctionValuesAndEffects; + invariant(additionalFVEffects); + let maybeParentFunctionInfo = additionalFVEffects.get(possibleParentFunction); + invariant(maybeParentFunctionInfo); + let createdObjects = maybeParentFunctionInfo.effects.createdObjects; + for (let func of functions) if (func !== possibleParentFunction && !createdObjects.has(func)) return false; + return true; + } + // Try and get the root optimized function when passed in an optimized function // that may or may not be nested in the tree of said root, or is the root optimized function tryGetOptimizedFunctionRoot(val: Value): void | FunctionValue { @@ -768,28 +789,35 @@ export class ResidualHeapSerializer { invariant(s instanceof FunctionValue); functionValues.add(s); } - let additionalFunction; + let outermostAdditionalFunctions = new Set(); + // Get the set of optimized functions that may be the root for (let functionValue of functionValues) { if (this.additionalFunctionGenerators.has(functionValue)) { - if (this.isDefinedInsideFunction(functionValue, functionValues)) { - continue; - } - if (additionalFunction !== undefined && additionalFunction !== functionValue) { - return undefined; - } - additionalFunction = functionValue; + if (!this.isDefinedInsideFunction(functionValue, functionValues)) + outermostAdditionalFunctions.add(functionValue); } else { let f = this.tryGetOptimizedFunctionRoot(functionValue); if (f === undefined) return undefined; - if (this.isDefinedInsideFunction(f, functionValues)) { - continue; - } - if (additionalFunction !== undefined && additionalFunction !== f) return undefined; - additionalFunction = f; + if (!this.isDefinedInsideFunction(f, functionValues)) outermostAdditionalFunctions.add(f); } } - return additionalFunction; + if (outermostAdditionalFunctions.size === 1) return [...outermostAdditionalFunctions][0]; + + let additionalFVEffects = this.additionalFunctionValuesAndEffects; + invariant(additionalFVEffects); + + // See if any of the outermost (or any of their parents) are the outermost optimized function + let possibleRoots = [...outermostAdditionalFunctions]; + while (possibleRoots.length > 0) { + let possibleRoot = possibleRoots.shift(); + if (this.definesFunctions(possibleRoot, outermostAdditionalFunctions)) return possibleRoot; + let additionalFunctionEffects = additionalFVEffects.get(possibleRoot); + invariant(additionalFunctionEffects); + let parent = additionalFunctionEffects.parentAdditionalFunction; + if (parent) possibleRoots.push(parent); + } + return undefined; } _getActiveBodyOfGenerator(generator: Generator): void | SerializedBody { diff --git a/test/serializer/optimized-functions/CommonParentScopeCapture.js b/test/serializer/optimized-functions/CommonParentScopeCapture.js new file mode 100644 index 0000000000..fd287868ff --- /dev/null +++ b/test/serializer/optimized-functions/CommonParentScopeCapture.js @@ -0,0 +1,26 @@ +(function() { + function middle(cond) { + if (cond) { + var value = 42; + function inner1() { + return value; + } + function inner2() { + return value; + } + global.__optimize && __optimize(inner1); + global.__optimize && __optimize(inner2); + return [inner1, inner2]; + } + } + function outer(cond) { + return [middle(cond), middle(cond)]; + } + global.outer = outer; + global.__optimize && __optimize(outer); + + global.inspect = function() { + let [[i1, i2], [i3, i4]] = outer(true); + return i1() + i2() + i3() + i4(); + }; +})(); diff --git a/test/serializer/optimized-functions/Issue2399.js b/test/serializer/optimized-functions/Issue2399.js new file mode 100644 index 0000000000..3d62db73ca --- /dev/null +++ b/test/serializer/optimized-functions/Issue2399.js @@ -0,0 +1,23 @@ +(function() { + function outer(cond) { + if (cond) { + var value = 42; + function inner1() { + return value; + } + function inner2() { + return value; + } + global.__optimize && __optimize(inner1); + global.__optimize && __optimize(inner2); + return [inner1, inner2]; + } + } + global.outer = outer; + global.__optimize && __optimize(outer); + + global.inspect = function() { + let [i1, i2] = outer(true); + return i1() + i2(); + }; +})(); diff --git a/test/serializer/optimized-functions/OptimizedResidualOptimized.js b/test/serializer/optimized-functions/OptimizedResidualOptimized.js new file mode 100644 index 0000000000..6ae6e9922d --- /dev/null +++ b/test/serializer/optimized-functions/OptimizedResidualOptimized.js @@ -0,0 +1,27 @@ +(function() { + function middle(cond) { + if (cond) { + var value = { x: 42 }; + function inner1() { + return value; + } + function inner2() { + return value; + } + global.__optimize && __optimize(inner1); + global.__optimize && __optimize(inner2); + return [inner1, inner2]; + } + } + function outer(cond) { + return [middle(cond), middle(cond)]; + } + global.outer = outer; + global.__optimize && __optimize(outer); + + global.inspect = function() { + let funcs = [].concat.apply([], outer(true)); + let objs = funcs.map(f => f()); + return " " + (objs[0] === objs[1]) + " " + (objs[1] === objs[2]); + }; +})();