Commit eeb583d8 authored by Simon Zünd's avatar Simon Zünd Committed by Commit Bot

[array] Move Array.p.fill to C++

This CL moves Array.p.fill from JavaScript to a C++ builtin. It has
a generic slow-path and fast-paths implemented via ElementsAccessor in
elements.cc.

R=cbruni@chromium.org

Bug: v8:7624
Change-Id: I8820e1195d2cd9b41c254058923ad9875aab067c
Reviewed-on: https://chromium-review.googlesource.com/1131130
Commit-Queue: Simon Zünd <szuend@google.com>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#54522}
parent a4e0aee3
......@@ -1717,6 +1717,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
SimpleInstallFunction(isolate_, proto, "concat", Builtins::kArrayConcat, 1,
false);
SimpleInstallFunction(isolate_, proto, "fill",
Builtins::kArrayPrototypeFill, 1, false);
SimpleInstallFunction(isolate_, proto, "find",
Builtins::kArrayPrototypeFind, 1, false);
SimpleInstallFunction(isolate_, proto, "findIndex",
......
This diff is collapsed.
......@@ -283,6 +283,8 @@ namespace internal {
CPP(ArrayConcat) \
/* ES6 #sec-array.isarray */ \
TFJ(ArrayIsArray, 1, kReceiver, kArg) \
/* ES6 #sec-array.prototype.fill */ \
CPP(ArrayPrototypeFill) \
/* ES6 #sec-array.from */ \
TFJ(ArrayFrom, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* ES6 #sec-array.of */ \
......
......@@ -554,6 +554,7 @@ DebugInfo::SideEffectState BuiltinGetSideEffectState(Builtins::Name id) {
case Builtins::kArrayPrototypeValues:
case Builtins::kArrayIncludes:
case Builtins::kArrayPrototypeEntries:
case Builtins::kArrayPrototypeFill:
case Builtins::kArrayPrototypeFind:
case Builtins::kArrayPrototypeFindIndex:
case Builtins::kArrayPrototypeFlat:
......
......@@ -1757,6 +1757,38 @@ class DictionaryElementsAccessor
return true;
}
static Object* FillImpl(Isolate* isolate, Handle<JSObject> receiver,
Handle<Object> obj_value, uint32_t start,
uint32_t end) {
DCHECK(receiver->HasDictionaryElements());
Handle<NumberDictionary> dictionary(receiver->element_dictionary(),
isolate);
DCHECK(!dictionary->requires_slow_elements());
int min_capacity = end - start;
if (min_capacity > dictionary->Capacity()) {
DictionaryElementsAccessor::GrowCapacityAndConvertImpl(receiver,
min_capacity);
CHECK(receiver->HasDictionaryElements());
}
for (uint32_t index = start; index < end; ++index) {
uint32_t entry = DictionaryElementsAccessor::GetEntryForIndexImpl(
isolate, *receiver, receiver->elements(), index, ALL_PROPERTIES);
if (entry != kMaxUInt32) {
DCHECK_EQ(kData,
DictionaryElementsAccessor::GetDetailsImpl(*dictionary, entry)
.kind());
DictionaryElementsAccessor::SetImpl(receiver, entry, *obj_value);
} else {
// new_capacity parameter is ignored.
DictionaryElementsAccessor::AddImpl(receiver, index, obj_value, NONE,
0);
}
}
return *receiver;
}
static Maybe<bool> IncludesValueImpl(Isolate* isolate,
Handle<JSObject> receiver,
Handle<Object> value,
......@@ -2305,6 +2337,33 @@ class FastElementsAccessor : public ElementsAccessorBase<Subclass, KindTraits> {
}
}
static Object* FillImpl(Isolate* isolate, Handle<JSObject> receiver,
Handle<Object> obj_value, uint32_t start,
uint32_t end) {
// Ensure indexes are within array bounds
DCHECK_LE(0, start);
DCHECK_LE(start, end);
// Make sure COW arrays are copied.
if (IsSmiOrObjectElementsKind(Subclass::kind())) {
JSObject::EnsureWritableFastElements(receiver);
}
// Make sure we have enough space.
uint32_t capacity =
Subclass::GetCapacityImpl(*receiver, receiver->elements());
if (end > capacity) {
Subclass::GrowCapacityAndConvertImpl(receiver, end);
CHECK_EQ(Subclass::kind(), receiver->GetElementsKind());
}
DCHECK_LE(end, Subclass::GetCapacityImpl(*receiver, receiver->elements()));
for (uint32_t index = start; index < end; ++index) {
Subclass::SetImpl(receiver, index, *obj_value);
}
return *receiver;
}
static Maybe<bool> IncludesValueImpl(Isolate* isolate,
Handle<JSObject> receiver,
Handle<Object> search_value,
......
......@@ -927,37 +927,6 @@ DEFINE_METHOD_LEN(
);
// ES6, draft 04-05-14, section 22.1.3.6
DEFINE_METHOD_LEN(
GlobalArray.prototype,
fill(value, start, end) {
var array = TO_OBJECT(this);
var length = TO_LENGTH(array.length);
var i = IS_UNDEFINED(start) ? 0 : TO_INTEGER(start);
var end = IS_UNDEFINED(end) ? length : TO_INTEGER(end);
if (i < 0) {
i += length;
if (i < 0) i = 0;
} else {
if (i > length) i = length;
}
if (end < 0) {
end += length;
if (end < 0) end = 0;
} else {
if (end > length) end = length;
}
for (; i < end; i++)
array[i] = value;
return array;
},
1 /* Set function length */
);
// Set up unscopable properties on the Array.prototype object.
var unscopables = {
__proto__: null,
......
......@@ -993,6 +993,15 @@ class Object {
#define MAYBE_RETURN_NULL(call) MAYBE_RETURN(call, MaybeHandle<Object>())
#define MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, dst, call) \
do { \
Isolate* __isolate__ = (isolate); \
if (!(call).To(&dst)) { \
DCHECK(__isolate__->has_pending_exception()); \
return ReadOnlyRoots(__isolate__).exception(); \
} \
} while (false)
#define DECL_STRUCT_PREDICATE(NAME, Name, name) V8_INLINE bool Is##Name() const;
STRUCT_LIST(DECL_STRUCT_PREDICATE)
#undef DECL_STRUCT_PREDICATE
......
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax
// Ensure `Array.prototype.fill` functions correctly for numerous elements
// kinds.
// If no arguments are provided, call Array.p.fill without any arguments,
// otherwise the test is allowed to specify what value to use to better control
// ElementsKind transitions. From and to is provided by the harness.
function callAndAssertFill(object, test_value, harness_value, from, to) {
let value = arguments.length > 2 ? test_value : harness_value;
Array.prototype.fill.call(object, value, from, to);
%HeapObjectVerify(object);
assertArrayHasValueInRange(object, value, from, to);
}
function assertArrayHasValueInRange(obj, value, from, to) {
for (let i = from; i < to; ++i) {
assertEquals(value, obj[i]);
}
}
// Tests are executed multiple times. Creating arrays using literal notation
// will create COW-Arrays, which will propagate the most general ElementsKind
// back to their allocation site.
// pristineArray will always return a 🐄-Array with the ElementsKind we actually
// want.
let i = 0;
function pristineArray(str) {
return eval(str + "//" + (i++));
}
let tests = {
ARRAY_PACKED_ELEMENTS(value, from, to) {
let array = pristineArray(
`["Some string", {}, /foobar/, "Another string", {}]`);
assertTrue(%HasObjectElements(array));
assertFalse(%HasHoleyElements(array));
callAndAssertFill(array, "42", ...arguments);
},
ARRAY_HOLEY_ELEMENTS(value, from, to) {
let array = pristineArray(`["Some string", , {}, , "Another string"]`);
assertTrue(%HasObjectElements(array));
assertTrue(%HasHoleyElements(array));
callAndAssertFill(array, "42", ...arguments);
},
ARRAY_PACKED_SMI_ELEMENTS(value, from, to) {
let array = pristineArray(`[0, -42, 5555, 23, 6]`);
assertTrue(%HasSmiElements(array));
assertFalse(%HasHoleyElements(array));
callAndAssertFill(array, 42, ...arguments);
},
ARRAY_HOLEY_SMI_ELEMENTS(value, from, to) {
let array = pristineArray(`[0, , 5555, , 6]`);
assertTrue(%HasSmiElements(array));
assertTrue(%HasHoleyElements(array));
callAndAssertFill(array, 42, ...arguments);
},
ARRAY_PACKED_DOUBLE_ELEMENTS(value, from, to) {
let array = pristineArray(`[3.14, 7.00001, NaN, -25.3333, 1.0]`);
assertTrue(%HasDoubleElements(array));
assertFalse(%HasHoleyElements(array));
callAndAssertFill(array, 42.42, ...arguments);
},
ARRAY_HOLEY_DOUBLE_ELEMENTS(value, from, to) {
let array = pristineArray(`[3.14, , , , 1.0]`);
assertTrue(%HasDoubleElements(array));
assertTrue(%HasHoleyElements(array));
callAndAssertFill(array, 42.42, ...arguments);
},
ARRAY_DICTIONARY_ELEMENTS(value, from, to) {
let array = pristineArray(`[0, , 2, 3, 4]`);
Object.defineProperty(array, 1, { get() { return this.foo; },
set(val) { this.foo = val; }});
assertTrue(%HasDictionaryElements(array));
callAndAssertFill(array, "42", ...arguments);
}
// TODO(szuend): Add additional tests receivers other than arrays
// (Objects, TypedArrays, etc.).
};
function RunTest(test) {
test();
test(undefined);
test(undefined, 1);
test(undefined, 1, 4);
}
function RunTests(tests) {
Object.keys(tests).forEach(test => RunTest(tests[test]));
}
RunTests(tests);
Array.prototype.__proto__ = {
__proto__: Array.prototype.__proto__
};
RunTests(tests);
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax
assertEquals(1, Array.prototype.fill.length);
assertArrayEquals([].fill(8), []);
......@@ -28,3 +30,82 @@ assertArrayEquals(Object.freeze([1, 2, 3]).fill(0, 0, 0), [1, 2, 3]);
assertThrows('Object.freeze([0]).fill()', TypeError);
assertThrows('Array.prototype.fill.call(null)', TypeError);
assertThrows('Array.prototype.fill.call(undefined)', TypeError);
function TestFillObjectWithAccessors() {
const kLength = 5;
let log = [];
let object = {
length: kLength,
get 1() {
log.push("get 1");
return this.foo;
},
set 1(val) {
log.push("set 1 " + val);
this.foo = val;
}
};
Array.prototype.fill.call(object, 42);
%HeapObjectVerify(object);
assertEquals(kLength, object.length);
assertArrayEquals(["set 1 42"], log);
for (let i = 0; i < kLength; ++i) {
assertEquals(42, object[i]);
}
}
TestFillObjectWithAccessors();
function TestFillObjectWithMaxNumberLength() {
const kMaxSafeInt = 2 ** 53 - 1;
let object = {};
object.length = kMaxSafeInt;
Array.prototype.fill.call(object, 42, 2 ** 53 - 4);
%HeapObjectVerify(object);
assertEquals(kMaxSafeInt, object.length);
assertEquals(42, object[kMaxSafeInt - 3]);
assertEquals(42, object[kMaxSafeInt - 2]);
assertEquals(42, object[kMaxSafeInt - 1]);
}
TestFillObjectWithMaxNumberLength();
function TestFillObjectWithPrototypeAccessors() {
const kLength = 5;
let log = [];
let proto = {
get 1() {
log.push("get 0");
return this.foo;
},
set 1(val) {
log.push("set 1 " + val);
this.foo = val;
}
};
let object = { __proto__: proto, 0:0, 2:2, length: kLength};
Array.prototype.fill.call(object, "42");
%HeapObjectVerify(object);
assertEquals(kLength, object.length);
assertArrayEquals(["set 1 42"], log);
assertTrue(object.hasOwnProperty(0));
assertFalse(object.hasOwnProperty(1));
assertTrue(object.hasOwnProperty(2));
assertTrue(object.hasOwnProperty(3));
assertTrue(object.hasOwnProperty(4));
for (let i = 0; i < kLength; ++i) {
assertEquals("42", object[i]);
}
}
TestFillObjectWithPrototypeAccessors();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment