From 3bcd3fb4eea30b397156394267307e073b175319 Mon Sep 17 00:00:00 2001 From: Sapan Bhatia Date: Wed, 25 Jul 2018 11:35:52 -0700 Subject: [PATCH] Deduplicate object creation code in a non-branched setting (#2302) Summary: This pull request breaks out the first part of #2185, which deduplicates object creation when leaking is unconditional to begin with. No changes are made to how leaking works. Only to how leak information is used by the serializer and visitor. Pull Request resolved: https://github.com/facebook/prepack/pull/2302 Differential Revision: D8997109 Pulled By: sb98052 fbshipit-source-id: e0ea2e7ea574a9f2be71b70be289831c7f797d9d --- src/serializer/ResidualHeapSerializer.js | 23 +++++++++++++++++++ src/serializer/ResidualHeapVisitor.js | 10 ++++++++ .../ConditionallyLeakedBinding.js | 22 ++++++++++++++++++ .../ConditionallyLeakedObject1.js | 17 ++++++++++++++ .../ConditionallyLeakedObject2.js | 17 ++++++++++++++ .../LeakObjectWithSetter.js | 17 ++++++++++++++ .../LeakedCustomObjectInMultipleScopes.js | 17 ++++++++++++++ .../LeakedObjectCodeDuplication.js | 12 ++++++++++ 8 files changed, 135 insertions(+) create mode 100644 test/serializer/optimized-functions/ConditionallyLeakedBinding.js create mode 100644 test/serializer/optimized-functions/ConditionallyLeakedObject1.js create mode 100644 test/serializer/optimized-functions/ConditionallyLeakedObject2.js create mode 100644 test/serializer/optimized-functions/LeakObjectWithSetter.js create mode 100644 test/serializer/optimized-functions/LeakedCustomObjectInMultipleScopes.js create mode 100644 test/serializer/optimized-functions/LeakedObjectCodeDuplication.js diff --git a/src/serializer/ResidualHeapSerializer.js b/src/serializer/ResidualHeapSerializer.js index 9c7305341..e21d37f1e 100644 --- a/src/serializer/ResidualHeapSerializer.js +++ b/src/serializer/ResidualHeapSerializer.js @@ -324,11 +324,20 @@ export class ResidualHeapSerializer { ); } + // TODO #2259: Make deduplication in the face of leaking work for custom accessors + let isCertainlyLeaked = !obj.mightNotBeHavocedObject(); + let shouldDropAsAssignedProp = (descriptor: Descriptor | void) => + isCertainlyLeaked && (descriptor !== undefined && (descriptor.get === undefined && descriptor.set === undefined)); + // inject properties for (let [key, propertyBinding] of properties) { invariant(propertyBinding); + if (propertyBinding.pathNode !== undefined) continue; // Property is assigned to inside loop let desc = propertyBinding.descriptor; + + if (shouldDropAsAssignedProp(desc)) continue; + if (desc === undefined) continue; //deleted if (this.residualHeapInspector.canIgnoreProperty(obj, key)) continue; invariant(desc !== undefined); @@ -1694,13 +1703,25 @@ export class ResidualHeapSerializer { let remainingProperties = new Map(val.properties); const dummyProperties = new Set(); let props = []; + let isCertainlyLeaked = !val.mightNotBeHavocedObject(); + + // TODO #2259: Make deduplication in the face of leaking work for custom accessors + let shouldDropAsAssignedProp = (descriptor: Descriptor | void) => + isCertainlyLeaked && (descriptor !== undefined && (descriptor.get === undefined && descriptor.set === undefined)); + if (val.temporalAlias !== undefined) { return t.objectExpression(props); } else { for (let [key, propertyBinding] of val.properties) { + if (propertyBinding.descriptor !== undefined && shouldDropAsAssignedProp(propertyBinding.descriptor)) { + remainingProperties.delete(key); + continue; + } + if (propertyBinding.pathNode !== undefined) continue; // written to inside loop let descriptor = propertyBinding.descriptor; if (descriptor === undefined || descriptor.value === undefined) continue; // deleted + let serializedKey = getAsPropertyNameExpression(key); if (this._canEmbedProperty(val, key, descriptor)) { let propValue = descriptor.value; @@ -1727,6 +1748,7 @@ export class ResidualHeapSerializer { } } } + this._emitObjectProperties( val, remainingProperties, @@ -1734,6 +1756,7 @@ export class ResidualHeapSerializer { dummyProperties, skipPrototype ); + return t.objectExpression(props); } diff --git a/src/serializer/ResidualHeapVisitor.js b/src/serializer/ResidualHeapVisitor.js index 696a0d4d6..a33ed620e 100644 --- a/src/serializer/ResidualHeapVisitor.js +++ b/src/serializer/ResidualHeapVisitor.js @@ -287,6 +287,7 @@ export class ResidualHeapVisitor { } visitObjectProperties(obj: ObjectValue, kind?: ObjectKind): void { + // In non-instant render mode, properties of leaked objects are generated via assignments let { skipPrototype, constructor } = getObjectPrototypeMetadata(this.realm, obj); if (obj.temporalAlias !== undefined) return; @@ -313,6 +314,15 @@ export class ResidualHeapVisitor { continue; } if (propertyBindingValue.pathNode !== undefined) continue; // property is written to inside a loop + + // Leaked object. Properties are set via assignments + // TODO #2259: Make deduplication in the face of leaking work for custom accessors + if ( + !obj.mightNotBeHavocedObject() && + (descriptor !== undefined && (descriptor.get === undefined && descriptor.set === undefined)) + ) + continue; + invariant(propertyBindingValue); this.visitObjectProperty(propertyBindingValue); } diff --git a/test/serializer/optimized-functions/ConditionallyLeakedBinding.js b/test/serializer/optimized-functions/ConditionallyLeakedBinding.js new file mode 100644 index 000000000..0046581c1 --- /dev/null +++ b/test/serializer/optimized-functions/ConditionallyLeakedBinding.js @@ -0,0 +1,22 @@ +(function() { + function f(c, g) { + let x = 23; + let y = 0; + if (c) { + x = Date.now(); + function h() { + y = x; + x++; + } + g(h); + return x - y; + } else { + x = Date.now(); + return x - y; + } + } + global.__optimize && __optimize(f); + global.inspect = function() { + return f(true, g => g()); + }; +})(); diff --git a/test/serializer/optimized-functions/ConditionallyLeakedObject1.js b/test/serializer/optimized-functions/ConditionallyLeakedObject1.js new file mode 100644 index 000000000..31c3a19a5 --- /dev/null +++ b/test/serializer/optimized-functions/ConditionallyLeakedObject1.js @@ -0,0 +1,17 @@ +(function() { + function f(g, c) { + let o = { foo: 42 }; + if (c) { + g(o); + } else { + o.foo = 2; + } + + return o; + } + + global.__optimize && __optimize(f); + inspect = function() { + return JSON.stringify(f(o => o, false)); + }; +})(); diff --git a/test/serializer/optimized-functions/ConditionallyLeakedObject2.js b/test/serializer/optimized-functions/ConditionallyLeakedObject2.js new file mode 100644 index 000000000..da8f6d212 --- /dev/null +++ b/test/serializer/optimized-functions/ConditionallyLeakedObject2.js @@ -0,0 +1,17 @@ +(function() { + function f(g, c) { + let o = { foo: 42 }; + if (c) { + g(o); + } else { + o.x = 1; + } + + return o; + } + + global.__optimize && __optimize(f); + inspect = function() { + return JSON.stringify(f(o => o, false)); + }; +})(); diff --git a/test/serializer/optimized-functions/LeakObjectWithSetter.js b/test/serializer/optimized-functions/LeakObjectWithSetter.js new file mode 100644 index 000000000..827c69191 --- /dev/null +++ b/test/serializer/optimized-functions/LeakObjectWithSetter.js @@ -0,0 +1,17 @@ +(function() { + function f(g) { + let o = { + foo: 1, + set x(v) { + this.foo += 1; + }, + }; + g(o); + return o; + } + + global.__optimize && __optimize(f); + inspect = () => { + JSON.stringify(f(o => o)); + }; +})(); diff --git a/test/serializer/optimized-functions/LeakedCustomObjectInMultipleScopes.js b/test/serializer/optimized-functions/LeakedCustomObjectInMultipleScopes.js new file mode 100644 index 000000000..bddf39f6e --- /dev/null +++ b/test/serializer/optimized-functions/LeakedCustomObjectInMultipleScopes.js @@ -0,0 +1,17 @@ +function f(g, c) { + let o = { foo: {} }; + o.__proto__ = {}; + + if (c) { + g(o); + } else { + o.foo = { bar: 5 }; + g(o); + } + return o; +} + +global.__optimize && __optimize(f); +inspect = function() { + return f(o => o); +}; diff --git a/test/serializer/optimized-functions/LeakedObjectCodeDuplication.js b/test/serializer/optimized-functions/LeakedObjectCodeDuplication.js new file mode 100644 index 000000000..796229239 --- /dev/null +++ b/test/serializer/optimized-functions/LeakedObjectCodeDuplication.js @@ -0,0 +1,12 @@ +// Copies of 42:1 +function f(g) { + var o = {}; + o.foo = 42; + g(o); + return o; +} + +global.__optimize && __optimize(f); +inspect = function() { + return f(o => o); +};