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:
parent
e18b2e11be
commit
a26adb2b41
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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]);
|
||||
|
||||
})();
|
||||
|
@ -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]);
|
||||
|
||||
})();
|
||||
|
@ -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]);
|
||||
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user