Optimize array.map for sealed, frozen objects

Extend same approach for FastJSArray to FastJSArrayForRead in ArrayMap builtin

~6x perf improvement in micro-benchmark JSTests/ObjectFreeze
Before:
ArrayMap
ArrayMap-Numbers(Score): 0.0887

After:
ArrayMap
ArrayMap-Numbers(Score): 0.531

Bug: v8:6831
Change-Id: I06cba44ca4c9198977c6da522b782b61f9df04fa
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1653732
Commit-Queue: Z Nguyen-Huu <duongn@microsoft.com>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Reviewed-by: Simon Zünd <szuend@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62127}
This commit is contained in:
Z Nguyen-Huu 2019-06-12 10:37:14 -07:00 committed by Commit Bot
parent e18b2e11be
commit a26adb2b41
5 changed files with 313 additions and 4 deletions

View File

@ -182,11 +182,11 @@ namespace array_map {
}
transitioning macro FastArrayMap(implicit context: Context)(
fastO: FastJSArray, len: Smi, callbackfn: Callable,
fastO: FastJSArrayForRead, len: Smi, callbackfn: Callable,
thisArg: Object): JSArray
labels Bailout(JSArray, Smi) {
let k: Smi = 0;
let fastOW = NewFastJSArrayWitness(fastO);
let fastOW = NewFastJSArrayForReadWitness(fastO);
let vector = NewVector(len);
// Build a fast loop over the smi array.
@ -245,7 +245,7 @@ namespace array_map {
try {
// 5. Let A be ? ArraySpeciesCreate(O, len).
if (IsArraySpeciesProtectorCellInvalid()) goto SlowSpeciesCreate;
const o: FastJSArray = Cast<FastJSArray>(receiver)
const o: FastJSArrayForRead = Cast<FastJSArrayForRead>(receiver)
otherwise SlowSpeciesCreate;
const smiLength: Smi = Cast<Smi>(len)
otherwise SlowSpeciesCreate;

View File

@ -2712,12 +2712,57 @@ macro NewFastJSArrayWitness(array: FastJSArray): FastJSArrayWitness {
stable: array,
unstable: array,
map: array.map,
hasDoubles: !IsElementsKindLessThanOrEqual(kind, HOLEY_ELEMENTS),
hasDoubles: IsDoubleElementsKind(kind),
hasSmis: IsElementsKindLessThanOrEqual(kind, HOLEY_SMI_ELEMENTS),
arrayIsPushable: false
};
}
struct FastJSArrayForReadWitness {
Get(): FastJSArrayForRead {
return this.unstable;
}
Recheck() labels CastError {
if (this.stable.map != this.map) goto CastError;
// We don't need to check elements kind or whether the prototype
// has changed away from the default JSArray prototype, because
// if the map remains the same then those properties hold.
//
// However, we have to make sure there are no elements in the
// prototype chain.
if (IsNoElementsProtectorCellInvalid()) goto CastError;
this.unstable = %RawDownCast<FastJSArrayForRead>(this.stable);
}
LoadElementNoHole(implicit context: Context)(k: Smi): Object
labels FoundHole {
if (this.hasDoubles) {
return LoadElementNoHole<FixedDoubleArray>(this.unstable, k)
otherwise FoundHole;
} else {
return LoadElementNoHole<FixedArray>(this.unstable, k)
otherwise FoundHole;
}
}
const stable: JSArray;
unstable: FastJSArrayForRead;
const map: Map;
const hasDoubles: bool;
}
macro NewFastJSArrayForReadWitness(array: FastJSArrayForRead):
FastJSArrayForReadWitness {
const kind = array.map.elements_kind;
return FastJSArrayForReadWitness{
stable: array,
unstable: array,
map: array.map,
hasDoubles: IsDoubleElementsKind(kind)
};
}
extern macro TransitionElementsKind(
JSObject, Map, constexpr ElementsKind,
constexpr ElementsKind): void labels Bailout;

View File

@ -793,3 +793,91 @@ function checkUndefined() {
assertTrue(checkUndefined(...arr));
assertTrue(checkUndefined(...[...arr]));
assertTrue(checkUndefined.apply(this, [...arr]));
//
// Array.prototype.map
//
(function() {
var a = Object.freeze(['0','1','2','3','4']);
// Simple use.
var result = [1,2,3,4,5];
assertArrayEquals(result, a.map(function(n) { return Number(n) + 1; }));
// Use specified object as this object when calling the function.
var o = { delta: 42 }
result = [42,43,44,45,46];
assertArrayEquals(result, a.map(function(n) { return this.delta + Number(n); }, o));
// Modify original array.
b = Object.freeze(['0','1','2','3','4']);
result = [1,2,3,4,5];
assertArrayEquals(result,
b.map(function(n, index, array) {
array[index] = Number(n) + 1; return Number(n) + 1;
}));
assertArrayEquals(b, a);
// Only loop through initial part of array and elements are not
// added.
a = Object.freeze(['0','1','2','3','4']);
result = [1,2,3,4,5];
assertArrayEquals(result,
a.map(function(n, index, array) { assertThrows(() => { array.push(n) }); return Number(n) + 1; }));
assertArrayEquals(['0','1','2','3','4'], a);
// Respect holes.
a = new Array(20);
a[1] = '2';
Object.freeze(a);
a = Object.freeze(a).map(function(n) { return 2*Number(n); });
for (var i in a) {
assertEquals(4, a[i]);
assertEquals('1', i);
}
// Skip over missing properties.
a = {
"0": 1,
"2": 2,
length: 3
};
var received = [];
assertArrayEquals([2, , 4],
Array.prototype.map.call(Object.freeze(a), function(n) {
received.push(n);
return n * 2;
}));
assertArrayEquals([1, 2], received);
// Modify array prototype
a = ['1', , 2];
received = [];
assertThrows(() => {
Array.prototype.map.call(Object.freeze(a), function(n) {
a.__proto__ = null;
received.push(n);
return n * 2;
});
}, TypeError);
assertArrayEquals([], received);
// Create a new object in each function call when receiver is a
// primitive value. See ECMA-262, Annex C.
a = [];
Object.freeze(['1', '2']).map(function() { a.push(this) }, "");
assertTrue(a[0] !== a[1]);
// Do not create a new object otherwise.
a = [];
Object.freeze(['1', '2']).map(function() { a.push(this) }, {});
assertSame(a[0], a[1]);
// In strict mode primitive values should not be coerced to an object.
a = [];
Object.freeze(['1', '2']).map(function() { 'use strict'; a.push(this); }, "");
assertEquals("", a[0]);
assertEquals(a[0], a[1]);
})();

View File

@ -459,3 +459,91 @@ function checkUndefined() {
assertTrue(checkUndefined(...arr));
assertTrue(checkUndefined(...[...arr]));
assertTrue(checkUndefined.apply(this, [...arr]));
//
// Array.prototype.map
//
(function() {
var a = Object.preventExtensions(['0','1','2','3','4']);
// Simple use.
var result = [1,2,3,4,5];
assertArrayEquals(result, a.map(function(n) { return Number(n) + 1; }));
// Use specified object as this object when calling the function.
var o = { delta: 42 }
result = [42,43,44,45,46];
assertArrayEquals(result, a.map(function(n) { return this.delta + Number(n); }, o));
// Modify original array.
b = Object.preventExtensions(['0','1','2','3','4']);
result = [1,2,3,4,5];
assertArrayEquals(result,
b.map(function(n, index, array) {
array[index] = Number(n) + 1; return Number(n) + 1;
}));
assertArrayEquals(b, result);
// Only loop through initial part of array and elements are not
// added.
a = Object.preventExtensions(['0','1','2','3','4']);
result = [1,2,3,4,5];
assertArrayEquals(result,
a.map(function(n, index, array) { assertThrows(() => { array.push(n) }); return Number(n) + 1; }));
assertArrayEquals(['0','1','2','3','4'], a);
// Respect holes.
a = new Array(20);
a[1] = '2';
Object.preventExtensions(a);
a = Object.preventExtensions(a).map(function(n) { return 2*Number(n); });
for (var i in a) {
assertEquals(4, a[i]);
assertEquals('1', i);
}
// Skip over missing properties.
a = {
"0": 1,
"2": 2,
length: 3
};
var received = [];
assertArrayEquals([2, , 4],
Array.prototype.map.call(Object.preventExtensions(a), function(n) {
received.push(n);
return n * 2;
}));
assertArrayEquals([1, 2], received);
// Modify array prototype
a = ['1', , 2];
received = [];
assertThrows(() => {
Array.prototype.map.call(Object.preventExtensions(a), function(n) {
a.__proto__ = null;
received.push(n);
return n * 2;
});
}, TypeError);
assertArrayEquals([], received);
// Create a new object in each function call when receiver is a
// primitive value. See ECMA-262, Annex C.
a = [];
Object.preventExtensions(['1', '2']).map(function() { a.push(this) }, "");
assertTrue(a[0] !== a[1]);
// Do not create a new object otherwise.
a = [];
Object.preventExtensions(['1', '2']).map(function() { a.push(this) }, {});
assertSame(a[0], a[1]);
// In strict mode primitive values should not be coerced to an object.
a = [];
Object.preventExtensions(['1', '2']).map(function() { 'use strict'; a.push(this); }, "");
assertEquals("", a[0]);
assertEquals(a[0], a[1]);
})();

View File

@ -765,3 +765,91 @@ function checkUndefined() {
assertTrue(checkUndefined(...arr));
assertTrue(checkUndefined(...[...arr]));
assertTrue(checkUndefined.apply(this, [...arr]));
//
// Array.prototype.map
//
(function() {
var a = Object.seal(['0','1','2','3','4']);
// Simple use.
var result = [1,2,3,4,5];
assertArrayEquals(result, a.map(function(n) { return Number(n) + 1; }));
// Use specified object as this object when calling the function.
var o = { delta: 42 }
result = [42,43,44,45,46];
assertArrayEquals(result, a.map(function(n) { return this.delta + Number(n); }, o));
// Modify original array.
b = Object.seal(['0','1','2','3','4']);
result = [1,2,3,4,5];
assertArrayEquals(result,
b.map(function(n, index, array) {
array[index] = Number(n) + 1; return Number(n) + 1;
}));
assertArrayEquals(b, result);
// Only loop through initial part of array and elements are not
// added.
a = Object.seal(['0','1','2','3','4']);
result = [1,2,3,4,5];
assertArrayEquals(result,
a.map(function(n, index, array) { assertThrows(() => { array.push(n) }); return Number(n) + 1; }));
assertArrayEquals(['0','1','2','3','4'], a);
// Respect holes.
a = new Array(20);
a[1] = '2';
Object.seal(a);
a = Object.seal(a).map(function(n) { return 2*Number(n); });
for (var i in a) {
assertEquals(4, a[i]);
assertEquals('1', i);
}
// Skip over missing properties.
a = {
"0": 1,
"2": 2,
length: 3
};
var received = [];
assertArrayEquals([2, , 4],
Array.prototype.map.call(Object.seal(a), function(n) {
received.push(n);
return n * 2;
}));
assertArrayEquals([1, 2], received);
// Modify array prototype
a = ['1', , 2];
received = [];
assertThrows(() => {
Array.prototype.map.call(Object.seal(a), function(n) {
a.__proto__ = null;
received.push(n);
return n * 2;
});
}, TypeError);
assertArrayEquals([], received);
// Create a new object in each function call when receiver is a
// primitive value. See ECMA-262, Annex C.
a = [];
Object.seal(['1', '2']).map(function() { a.push(this) }, "");
assertTrue(a[0] !== a[1]);
// Do not create a new object otherwise.
a = [];
Object.seal(['1', '2']).map(function() { a.push(this) }, {});
assertSame(a[0], a[1]);
// In strict mode primitive values should not be coerced to an object.
a = [];
Object.seal(['1', '2']).map(function() { 'use strict'; a.push(this); }, "");
assertEquals("", a[0]);
assertEquals(a[0], a[1]);
})();