-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bug 1845728 - Ellide keys call in Object.keys(..).length. r=iain
This optimization is made to handle cases where Object.keys(..) is called only to compute the number of owned properties as a short-cut to iterating over the properties of the object. However, this short-cut, which is well deployed on the Web, does not make sense given that Object.keys(..) iterates over the owned properties, to allocate an array, to store the names of the properties. This optimization ellide the allocation of the array when the content of the array is not needed. Note, this optimization skip the case of Proxy where the Object.keys(..) function might be effectful, or if the object might be mutated while Object.keys(..) value is kept alive. Differential Revision: https://phabricator.services.mozilla.com/D192257
- Loading branch information
Showing
28 changed files
with
953 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// This test case is used to test the optimized code path where the computation | ||
// of `Object.keys(...).length` no longer compute `Object.keys(...)` as an | ||
// intermediate result. | ||
// | ||
// This test verifies that the result remain consistent after the optimization. | ||
|
||
function cmp_keys_length(a, b) { | ||
return Object.keys(a).length == Object.keys(b).length; | ||
} | ||
|
||
let objs = [ | ||
{x:0, a: 1, b: 2}, | ||
{x:1, b: 1, c: 2}, | ||
{x:2, c: 1, d: 2}, | ||
{x:3, a: 1, b: 2, c: 3}, | ||
{x:4, b: 1, c: 2, d: 3}, | ||
{x:5, a: 1, b: 2, c: 3, d: 4} | ||
]; | ||
|
||
function max_of(o) { | ||
return o?.d ?? o?.c ?? o?.b ?? 0; | ||
} | ||
|
||
with({}) {} | ||
for (let i = 0; i < 1000; i++) { | ||
for (let o1 of objs) { | ||
for (let o2 of objs) { | ||
assertEq(cmp_keys_length(o1, o2), max_of(o1) == max_of(o2)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
|
||
// This test case is used to test one common configuration seen in the wild and | ||
// that we expect to be successful at optimizing properly. | ||
|
||
// Similar functions are part of popular framework such as React and Angular. | ||
function shallowEqual(o1, o2) { | ||
var k1 = Object.keys(o1); | ||
var k2 = Object.keys(o2); | ||
if (k1.length != k2.length) { | ||
return false; | ||
} | ||
for (var k = 0; k < k1.length; k++) { | ||
if (!Object.hasOwnProperty.call(o2, k1[k]) || !Object.is(o1[k1[k]], o2[k1[k]])) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
let objs = [ | ||
{x:0, a: 1, b: 2}, | ||
{x:1, b: 1, c: 2}, | ||
{x:2, c: 1, d: 2}, | ||
{x:3, a: 1, b: 2, c: 3}, | ||
{x:4, b: 1, c: 2, d: 3}, | ||
{x:5, a: 1, b: 2, c: 3, d: 4} | ||
]; | ||
|
||
with({}) {} | ||
for (let i = 0; i < 1000; i++) { | ||
for (let o1 of objs) { | ||
for (let o2 of objs) { | ||
assertEq(shallowEqual(o1, o2), Object.is(o1, o2)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
|
||
// This test case check some code frequently used in the wild, with some object | ||
// (proxy) for which the optimization we have deployed do not work, as the | ||
// assumption the Object.keys(...) can be elided without having noticeable | ||
// side-effects. | ||
|
||
// Similar functions are part of popular framework such as React and Angular. | ||
function shallowEqual(o1, o2) { | ||
var k1 = Object.keys(o1); | ||
var k2 = Object.keys(o2); | ||
if (k1.length != k2.length) { | ||
return false; | ||
} | ||
for (var k = 0; k < k1.length; k++) { | ||
if (!Object.hasOwnProperty.call(o2, k1[k]) || !Object.is(o1[k1[k]], o2[k1[k]])) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
let sideEffectCounter = 0; | ||
const payload = {x: 5, a: 1, b: 2, c: 3, d: 4}; | ||
const handler = { | ||
ownKeys(target) { | ||
// side-effect that should not be removed. | ||
sideEffectCounter++; | ||
// answer returned. | ||
return Reflect.ownKeys(target); | ||
}, | ||
}; | ||
const proxy = new Proxy(payload, handler); | ||
|
||
let objs = [ | ||
{x:0, a: 1, b: 2}, | ||
{x:1, b: 1, c: 2}, | ||
{x:2, c: 1, d: 2}, | ||
{x:3, a: 1, b: 2, c: 3}, | ||
{x:4, b: 1, c: 2, d: 3}, | ||
proxy | ||
]; | ||
|
||
with({}) {} | ||
let iterMax = 1000; | ||
for (let i = 0; i < iterMax; i++) { | ||
for (let o1 of objs) { | ||
for (let o2 of objs) { | ||
assertEq(shallowEqual(o1, o2), Object.is(o1, o2)); | ||
} | ||
} | ||
} | ||
|
||
let expectedSideEffects = 2 * objs.length * iterMax; | ||
assertEq(sideEffectCounter, expectedSideEffects); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
|
||
// This test case checks that some code can be optimized for non-proxy object, | ||
// and than we can correctly fallback if a proxy object ever flow into this | ||
// code. | ||
|
||
// Similar functions are part of popular framework such as React and Angular. | ||
function shallowEqual(o1, o2) { | ||
var k1 = Object.keys(o1); | ||
var k2 = Object.keys(o2); | ||
if (k1.length != k2.length) { | ||
return false; | ||
} | ||
for (var k = 0; k < k1.length; k++) { | ||
if (!Object.hasOwnProperty.call(o2, k1[k]) || !Object.is(o1[k1[k]], o2[k1[k]])) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
let sideEffectCounter = 0; | ||
const payload = {x: 5, a: 1, b: 2, c: 3, d: 4}; | ||
const handler = { | ||
ownKeys(target) { | ||
// side-effect that should not be removed. | ||
sideEffectCounter++; | ||
// answer returned. | ||
return Reflect.ownKeys(target); | ||
}, | ||
}; | ||
const proxy = new Proxy(payload, handler); | ||
|
||
let objs = [ | ||
{x:0, a: 1, b: 2}, | ||
{x:1, b: 1, c: 2}, | ||
{x:2, c: 1, d: 2}, | ||
{x:3, a: 1, b: 2, c: 3}, | ||
{x:4, b: 1, c: 2, d: 3}, | ||
{x:5, a: 1, b: 2, c: 3, d: 4} | ||
]; | ||
|
||
// Ion compile shallowEqual ... | ||
with({}) {} | ||
let iterMax = 1000; | ||
for (let i = 0; i < iterMax; i++) { | ||
for (let o1 of objs) { | ||
for (let o2 of objs) { | ||
assertEq(shallowEqual(o1, o2), Object.is(o1, o2)); | ||
} | ||
} | ||
} | ||
|
||
assertEq(sideEffectCounter, 0); | ||
|
||
// ... before calling it with a proxy. | ||
// This should bailout with a guard failure. | ||
shallowEqual(objs[0], proxy); | ||
|
||
// Assert that the proxy's ownKeys function has been called. | ||
assertEq(sideEffectCounter, 1); |
Oops, something went wrong.