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",
......
......@@ -79,11 +79,14 @@ inline bool HasOnlySimpleElements(Isolate* isolate, JSReceiver* receiver) {
}
// Returns |false| if not applicable.
// TODO(szuend): Refactor this function because it is getting hard to
// understand what each call-site actually checks.
V8_WARN_UNUSED_RESULT
inline bool EnsureJSArrayWithWritableFastElements(Isolate* isolate,
Handle<Object> receiver,
BuiltinArguments* args,
int first_added_arg) {
int first_arg_index,
int num_arguments) {
if (!receiver->IsJSArray()) return false;
Handle<JSArray> array = Handle<JSArray>::cast(receiver);
ElementsKind origin_kind = array->GetElementsKind();
......@@ -102,13 +105,14 @@ inline bool EnsureJSArrayWithWritableFastElements(Isolate* isolate,
// Need to ensure that the arguments passed in args can be contained in
// the array.
int args_length = args->length();
if (first_added_arg >= args_length) return true;
if (first_arg_index >= args_length) return true;
if (IsObjectElementsKind(origin_kind)) return true;
ElementsKind target_kind = origin_kind;
{
DisallowHeapAllocation no_gc;
for (int i = first_added_arg; i < args_length; i++) {
int last_arg_index = std::min(first_arg_index + num_arguments, args_length);
for (int i = first_arg_index; i < last_arg_index; i++) {
Object* arg = (*args)[i];
if (arg->IsHeapObject()) {
if (arg->IsHeapNumber()) {
......@@ -142,6 +146,170 @@ V8_WARN_UNUSED_RESULT static Object* CallJsIntrinsic(
Execution::Call(isolate, function, args.receiver(), argc, argv.start()));
}
// If |index| is Undefined, returns init_if_undefined.
// If |index| is negative, returns length + index.
// If |index| is positive, returns index.
// Returned value is guaranteed to be in the interval of [0, length].
V8_WARN_UNUSED_RESULT Maybe<double> GetRelativeIndex(Isolate* isolate,
double length,
Handle<Object> index,
double init_if_undefined) {
double relative_index = init_if_undefined;
if (!index->IsUndefined()) {
Handle<Object> relative_index_obj;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, relative_index_obj,
Object::ToInteger(isolate, index),
Nothing<double>());
relative_index = relative_index_obj->Number();
}
if (relative_index < 0) {
return Just(std::max(length + relative_index, 0.0));
}
return Just(std::min(relative_index, length));
}
// Returns "length", has "fast-path" for JSArrays.
V8_WARN_UNUSED_RESULT Maybe<double> GetLengthProperty(
Isolate* isolate, Handle<JSReceiver> receiver) {
if (receiver->IsJSArray()) {
Handle<JSArray> array = Handle<JSArray>::cast(receiver);
double length = array->length()->Number();
DCHECK(0 <= length && length <= kMaxSafeInteger);
return Just(length);
}
Handle<Object> raw_length_number;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, raw_length_number,
Object::GetLengthFromArrayLike(isolate, receiver), Nothing<double>());
return Just(raw_length_number->Number());
}
V8_WARN_UNUSED_RESULT Object* GenericArrayFill(Isolate* isolate,
Handle<JSReceiver> receiver,
Handle<Object> value,
double start, double end) {
// 7. Repeat, while k < final.
while (start < end) {
// a. Let Pk be ! ToString(k).
Handle<String> index = isolate->factory()->NumberToString(
isolate->factory()->NewNumber(start));
// b. Perform ? Set(O, Pk, value, true).
RETURN_FAILURE_ON_EXCEPTION(
isolate, Object::SetPropertyOrElement(isolate, receiver, index, value,
LanguageMode::kStrict));
// c. Increase k by 1.
++start;
}
// 8. Return O.
return *receiver;
}
V8_WARN_UNUSED_RESULT bool TryFastArrayFill(
Isolate* isolate, BuiltinArguments* args, Handle<JSReceiver> receiver,
Handle<Object> value, double start_index, double end_index) {
// If indices are too large, use generic path since they are stored as
// properties, not in the element backing store.
if (end_index > kMaxUInt32) return false;
if (!receiver->IsJSObject()) return false;
Handle<JSObject> object = Handle<JSObject>::cast(receiver);
if (receiver->IsJSArray()) {
if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, args, 1, 1)) {
return false;
}
Handle<JSArray> array = Handle<JSArray>::cast(receiver);
// If no argument was provided, we fill the array with 'undefined'.
// EnsureJSArrayWith... does not handle that case so we do it here.
// TODO(szuend): Pass target elements kind to EnsureJSArrayWith... when
// it gets refactored.
if (args->length() == 1 && array->GetElementsKind() != PACKED_ELEMENTS) {
// Use a short-lived HandleScope to avoid creating several copies of the
// elements handle which would cause issues when left-trimming later-on.
HandleScope scope(isolate);
JSObject::TransitionElementsKind(array, PACKED_ELEMENTS);
}
} else if (!HasOnlySimpleReceiverElements(isolate, *object)) {
return false;
}
DCHECK_LE(start_index, kMaxUInt32);
DCHECK_LE(end_index, kMaxUInt32);
uint32_t start, end;
CHECK(DoubleToUint32IfEqualToSelf(start_index, &start));
CHECK(DoubleToUint32IfEqualToSelf(end_index, &end));
ElementsAccessor* accessor = object->GetElementsAccessor();
accessor->Fill(isolate, object, value, start, end);
return true;
}
} // namespace
BUILTIN(ArrayPrototypeFill) {
HandleScope scope(isolate);
if (isolate->debug_execution_mode() == DebugInfo::kSideEffects) {
if (!isolate->debug()->PerformSideEffectCheckForObject(args.receiver())) {
return ReadOnlyRoots(isolate).exception();
}
}
// 1. Let O be ? ToObject(this value).
Handle<JSReceiver> receiver;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, receiver, Object::ToObject(isolate, args.receiver()));
// 2. Let len be ? ToLength(? Get(O, "length")).
double length;
MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, length, GetLengthProperty(isolate, receiver));
// 3. Let relativeStart be ? ToInteger(start).
// 4. If relativeStart < 0, let k be max((len + relativeStart), 0);
// else let k be min(relativeStart, len).
Handle<Object> start = args.atOrUndefined(isolate, 2);
double start_index;
MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, start_index, GetRelativeIndex(isolate, length, start, 0));
// 5. If end is undefined, let relativeEnd be len;
// else let relativeEnd be ? ToInteger(end).
// 6. If relativeEnd < 0, let final be max((len + relativeEnd), 0);
// else let final be min(relativeEnd, len).
Handle<Object> end = args.atOrUndefined(isolate, 3);
double end_index;
MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, end_index, GetRelativeIndex(isolate, length, end, length));
if (start_index >= end_index) return *receiver;
// Ensure indexes are within array bounds
DCHECK_LE(0, start_index);
DCHECK_LE(start_index, end_index);
DCHECK_LE(end_index, length);
Handle<Object> value = args.atOrUndefined(isolate, 1);
if (TryFastArrayFill(isolate, &args, receiver, value, start_index,
end_index)) {
return *receiver;
}
return GenericArrayFill(isolate, receiver, value, start_index, end_index);
}
namespace {
V8_WARN_UNUSED_RESULT Object* GenericArrayPush(Isolate* isolate,
BuiltinArguments* args) {
// 1. Let O be ? ToObject(this value).
......@@ -210,7 +378,8 @@ V8_WARN_UNUSED_RESULT Object* GenericArrayPush(Isolate* isolate,
BUILTIN(ArrayPush) {
HandleScope scope(isolate);
Handle<Object> receiver = args.receiver();
if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 1)) {
if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 1,
args.length() - 1)) {
return GenericArrayPush(isolate, &args);
}
......@@ -293,7 +462,8 @@ V8_WARN_UNUSED_RESULT Object* GenericArrayPop(Isolate* isolate,
BUILTIN(ArrayPop) {
HandleScope scope(isolate);
Handle<Object> receiver = args.receiver();
if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, nullptr, 0)) {
if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, nullptr, 0,
0)) {
return GenericArrayPop(isolate, &args);
}
Handle<JSArray> array = Handle<JSArray>::cast(receiver);
......@@ -324,7 +494,8 @@ BUILTIN(ArrayShift) {
HandleScope scope(isolate);
Heap* heap = isolate->heap();
Handle<Object> receiver = args.receiver();
if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, nullptr, 0) ||
if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, nullptr, 0,
0) ||
!IsJSArrayFastElementMovingAllowed(isolate, JSArray::cast(*receiver))) {
return CallJsIntrinsic(isolate, isolate->array_shift(), args);
}
......@@ -344,7 +515,8 @@ BUILTIN(ArrayShift) {
BUILTIN(ArrayUnshift) {
HandleScope scope(isolate);
Handle<Object> receiver = args.receiver();
if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 1)) {
if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 1,
args.length() - 1)) {
return CallJsIntrinsic(isolate, isolate->array_unshift(), args);
}
Handle<JSArray> array = Handle<JSArray>::cast(receiver);
......@@ -367,7 +539,8 @@ BUILTIN(ArraySplice) {
HandleScope scope(isolate);
Handle<Object> receiver = args.receiver();
if (V8_UNLIKELY(
!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 3) ||
!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 3,
args.length() - 3) ||
// If this is a subclass of Array, then call out to JS.
!Handle<JSArray>::cast(receiver)->HasArrayPrototype(isolate) ||
// If anything with @@species has been messed with, call out to JS.
......
......@@ -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