Commit 2ae50119 authored by jkummerow's avatar jkummerow Committed by Commit bot

[runtime] Speed up C++ version of ArrayPush

Mostly by avoiding unnecessary Handle/HandleScope creation,
"length" property lookups, and length conversions.
This yields about 60% speedup on the microbenchmark I tested with.

Note that the C++ builtin is the middle performance tier of three,
so not every Array.push use case will be affected by this patch.

Review URL: https://codereview.chromium.org/1716833002

Cr-Commit-Position: refs/heads/master@{#34268}
parent e1bed4f9
...@@ -238,55 +238,57 @@ inline bool IsJSArrayFastElementMovingAllowed(Isolate* isolate, ...@@ -238,55 +238,57 @@ inline bool IsJSArrayFastElementMovingAllowed(Isolate* isolate,
return PrototypeHasNoElements(&iter); return PrototypeHasNoElements(&iter);
} }
// Returns |false| if not applicable.
// Returns empty handle if not applicable.
MUST_USE_RESULT MUST_USE_RESULT
inline MaybeHandle<FixedArrayBase> EnsureJSArrayWithWritableFastElements( inline bool EnsureJSArrayWithWritableFastElements(Isolate* isolate,
Isolate* isolate, Handle<Object> receiver, Arguments* args, Handle<Object> receiver,
int first_added_arg) { Arguments* args,
// We explicitly add a HandleScope to avoid creating several copies of the int first_added_arg) {
// same handle which would otherwise cause issue when left-trimming later-on. if (!receiver->IsJSArray()) return false;
HandleScope scope(isolate);
if (!receiver->IsJSArray()) return MaybeHandle<FixedArrayBase>();
Handle<JSArray> array = Handle<JSArray>::cast(receiver); Handle<JSArray> array = Handle<JSArray>::cast(receiver);
// If there may be elements accessors in the prototype chain, the fast path // If there may be elements accessors in the prototype chain, the fast path
// cannot be used if there arguments to add to the array. // cannot be used if there arguments to add to the array.
Heap* heap = isolate->heap(); Heap* heap = isolate->heap();
if (args != NULL && !IsJSArrayFastElementMovingAllowed(isolate, *array)) { if (args != NULL && !IsJSArrayFastElementMovingAllowed(isolate, *array)) {
return MaybeHandle<FixedArrayBase>(); return false;
} }
if (array->map()->is_observed()) return MaybeHandle<FixedArrayBase>(); if (array->map()->is_observed()) return false;
if (!array->map()->is_extensible()) return MaybeHandle<FixedArrayBase>(); if (!array->map()->is_extensible()) return false;
Handle<FixedArrayBase> elms(array->elements(), isolate); Map* map = array->elements()->map();
Map* map = elms->map();
if (map == heap->fixed_array_map()) { if (map == heap->fixed_array_map()) {
if (args == NULL || array->HasFastObjectElements()) { if (args == NULL || array->HasFastObjectElements()) {
return scope.CloseAndEscape(elms); return true;
} }
} else if (map == heap->fixed_cow_array_map()) { } else if (map == heap->fixed_cow_array_map()) {
elms = JSObject::EnsureWritableFastElements(array); // 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);
// TODO(jkummerow/verwaest): Move this call (or this entire function?)
// into the ElementsAccessor so it's only done when needed (e.g. ArrayPush
// can skip it because it must grow the backing store anyway).
JSObject::EnsureWritableFastElements(array);
if (args == NULL || array->HasFastObjectElements()) { if (args == NULL || array->HasFastObjectElements()) {
return scope.CloseAndEscape(elms); return true;
} }
} else if (map == heap->fixed_double_array_map()) { } else if (map == heap->fixed_double_array_map()) {
if (args == NULL) { if (args == NULL) {
return scope.CloseAndEscape(elms); return true;
} }
} else { } else {
return MaybeHandle<FixedArrayBase>(); return false;
} }
// Adding elements to the array prototype would break code that makes sure // Adding elements to the array prototype would break code that makes sure
// it has no elements. Handle that elsewhere. // it has no elements. Handle that elsewhere.
if (isolate->IsAnyInitialArrayPrototype(array)) { if (isolate->IsAnyInitialArrayPrototype(array)) {
return MaybeHandle<FixedArrayBase>(); return false;
} }
// Need to ensure that the arguments passed in args can be contained in // Need to ensure that the arguments passed in args can be contained in
// the array. // the array.
int args_length = args->length(); int args_length = args->length();
if (first_added_arg >= args_length) { if (first_added_arg >= args_length) {
return scope.CloseAndEscape(elms); return true;
} }
ElementsKind origin_kind = array->map()->elements_kind(); ElementsKind origin_kind = array->map()->elements_kind();
...@@ -309,10 +311,12 @@ inline MaybeHandle<FixedArrayBase> EnsureJSArrayWithWritableFastElements( ...@@ -309,10 +311,12 @@ inline MaybeHandle<FixedArrayBase> EnsureJSArrayWithWritableFastElements(
} }
} }
if (target_kind != origin_kind) { if (target_kind != origin_kind) {
// 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, target_kind); JSObject::TransitionElementsKind(array, target_kind);
elms = handle(array->elements(), isolate);
} }
return scope.CloseAndEscape(elms); return true;
} }
...@@ -352,10 +356,7 @@ BUILTIN(EmptyFunction) { return isolate->heap()->undefined_value(); } ...@@ -352,10 +356,7 @@ BUILTIN(EmptyFunction) { return isolate->heap()->undefined_value(); }
BUILTIN(ArrayPush) { BUILTIN(ArrayPush) {
HandleScope scope(isolate); HandleScope scope(isolate);
Handle<Object> receiver = args.receiver(); Handle<Object> receiver = args.receiver();
MaybeHandle<FixedArrayBase> maybe_elms_obj = if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 1)) {
EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 1);
Handle<FixedArrayBase> elms_obj;
if (!maybe_elms_obj.ToHandle(&elms_obj)) {
return CallJsIntrinsic(isolate, isolate->array_push(), args); return CallJsIntrinsic(isolate, isolate->array_push(), args);
} }
// Fast Elements Path // Fast Elements Path
...@@ -365,13 +366,14 @@ BUILTIN(ArrayPush) { ...@@ -365,13 +366,14 @@ BUILTIN(ArrayPush) {
if (push_size == 0) { if (push_size == 0) {
return Smi::FromInt(len); return Smi::FromInt(len);
} }
if (push_size > 0 && DCHECK(push_size > 0);
JSArray::WouldChangeReadOnlyLength(array, len + push_size)) { if (JSArray::HasReadOnlyLength(array)) {
return CallJsIntrinsic(isolate, isolate->array_push(), args); return CallJsIntrinsic(isolate, isolate->array_push(), args);
} }
DCHECK(!array->map()->is_observed()); DCHECK(!array->map()->is_observed());
ElementsAccessor* accessor = array->GetElementsAccessor(); ElementsAccessor* accessor = array->GetElementsAccessor();
int new_length = accessor->Push(array, elms_obj, &args, push_size); int new_length = accessor->Push(array, handle(array->elements(), isolate),
&args, push_size);
return Smi::FromInt(new_length); return Smi::FromInt(new_length);
} }
...@@ -379,10 +381,7 @@ BUILTIN(ArrayPush) { ...@@ -379,10 +381,7 @@ BUILTIN(ArrayPush) {
BUILTIN(ArrayPop) { BUILTIN(ArrayPop) {
HandleScope scope(isolate); HandleScope scope(isolate);
Handle<Object> receiver = args.receiver(); Handle<Object> receiver = args.receiver();
MaybeHandle<FixedArrayBase> maybe_elms_obj = if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, NULL, 0)) {
EnsureJSArrayWithWritableFastElements(isolate, receiver, NULL, 0);
Handle<FixedArrayBase> elms_obj;
if (!maybe_elms_obj.ToHandle(&elms_obj)) {
return CallJsIntrinsic(isolate, isolate->array_pop(), args); return CallJsIntrinsic(isolate, isolate->array_pop(), args);
} }
...@@ -399,7 +398,8 @@ BUILTIN(ArrayPop) { ...@@ -399,7 +398,8 @@ BUILTIN(ArrayPop) {
Handle<Object> result; Handle<Object> result;
if (IsJSArrayFastElementMovingAllowed(isolate, JSArray::cast(*receiver))) { if (IsJSArrayFastElementMovingAllowed(isolate, JSArray::cast(*receiver))) {
// Fast Elements Path // Fast Elements Path
result = array->GetElementsAccessor()->Pop(array, elms_obj); result = array->GetElementsAccessor()->Pop(
array, handle(array->elements(), isolate));
} else { } else {
// Use Slow Lookup otherwise // Use Slow Lookup otherwise
uint32_t new_length = len - 1; uint32_t new_length = len - 1;
...@@ -415,10 +415,7 @@ BUILTIN(ArrayShift) { ...@@ -415,10 +415,7 @@ BUILTIN(ArrayShift) {
HandleScope scope(isolate); HandleScope scope(isolate);
Heap* heap = isolate->heap(); Heap* heap = isolate->heap();
Handle<Object> receiver = args.receiver(); Handle<Object> receiver = args.receiver();
MaybeHandle<FixedArrayBase> maybe_elms_obj = if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, NULL, 0) ||
EnsureJSArrayWithWritableFastElements(isolate, receiver, NULL, 0);
Handle<FixedArrayBase> elms_obj;
if (!maybe_elms_obj.ToHandle(&elms_obj) ||
!IsJSArrayFastElementMovingAllowed(isolate, JSArray::cast(*receiver))) { !IsJSArrayFastElementMovingAllowed(isolate, JSArray::cast(*receiver))) {
return CallJsIntrinsic(isolate, isolate->array_shift(), args); return CallJsIntrinsic(isolate, isolate->array_shift(), args);
} }
...@@ -432,7 +429,8 @@ BUILTIN(ArrayShift) { ...@@ -432,7 +429,8 @@ BUILTIN(ArrayShift) {
return CallJsIntrinsic(isolate, isolate->array_shift(), args); return CallJsIntrinsic(isolate, isolate->array_shift(), args);
} }
Handle<Object> first = array->GetElementsAccessor()->Shift(array, elms_obj); Handle<Object> first = array->GetElementsAccessor()->Shift(
array, handle(array->elements(), isolate));
return *first; return *first;
} }
...@@ -440,10 +438,7 @@ BUILTIN(ArrayShift) { ...@@ -440,10 +438,7 @@ BUILTIN(ArrayShift) {
BUILTIN(ArrayUnshift) { BUILTIN(ArrayUnshift) {
HandleScope scope(isolate); HandleScope scope(isolate);
Handle<Object> receiver = args.receiver(); Handle<Object> receiver = args.receiver();
MaybeHandle<FixedArrayBase> maybe_elms_obj = if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 1)) {
EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 1);
Handle<FixedArrayBase> elms_obj;
if (!maybe_elms_obj.ToHandle(&elms_obj)) {
return CallJsIntrinsic(isolate, isolate->array_unshift(), args); return CallJsIntrinsic(isolate, isolate->array_unshift(), args);
} }
Handle<JSArray> array = Handle<JSArray>::cast(receiver); Handle<JSArray> array = Handle<JSArray>::cast(receiver);
...@@ -461,7 +456,8 @@ BUILTIN(ArrayUnshift) { ...@@ -461,7 +456,8 @@ BUILTIN(ArrayUnshift) {
} }
ElementsAccessor* accessor = array->GetElementsAccessor(); ElementsAccessor* accessor = array->GetElementsAccessor();
int new_length = accessor->Unshift(array, elms_obj, &args, to_add); int new_length = accessor->Unshift(array, handle(array->elements(), isolate),
&args, to_add);
return Smi::FromInt(new_length); return Smi::FromInt(new_length);
} }
...@@ -558,13 +554,10 @@ BUILTIN(ArraySlice) { ...@@ -558,13 +554,10 @@ BUILTIN(ArraySlice) {
BUILTIN(ArraySplice) { BUILTIN(ArraySplice) {
HandleScope scope(isolate); HandleScope scope(isolate);
Handle<Object> receiver = args.receiver(); Handle<Object> receiver = args.receiver();
MaybeHandle<FixedArrayBase> maybe_elms_obj = if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 3) ||
EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 3); // If this is a subclass of Array, then call out to JS.
Handle<FixedArrayBase> elms_obj;
if (!maybe_elms_obj.ToHandle(&elms_obj) ||
// If this is a subclass of Array, then call out to JS
!JSArray::cast(*receiver)->map()->new_target_is_base() || !JSArray::cast(*receiver)->map()->new_target_is_base() ||
// If anything with @@species has been messed with, call out to JS // If anything with @@species has been messed with, call out to JS.
!isolate->IsArraySpeciesLookupChainIntact()) { !isolate->IsArraySpeciesLookupChainIntact()) {
return CallJsIntrinsic(isolate, isolate->array_splice(), args); return CallJsIntrinsic(isolate, isolate->array_splice(), args);
} }
...@@ -613,8 +606,9 @@ BUILTIN(ArraySplice) { ...@@ -613,8 +606,9 @@ BUILTIN(ArraySplice) {
return CallJsIntrinsic(isolate, isolate->array_splice(), args); return CallJsIntrinsic(isolate, isolate->array_splice(), args);
} }
ElementsAccessor* accessor = array->GetElementsAccessor(); ElementsAccessor* accessor = array->GetElementsAccessor();
Handle<JSArray> result_array = accessor->Splice( Handle<JSArray> result_array =
array, elms_obj, actual_start, actual_delete_count, &args, add_count); accessor->Splice(array, handle(array->elements(), isolate), actual_start,
actual_delete_count, &args, add_count);
return *result_array; return *result_array;
} }
......
...@@ -1909,8 +1909,9 @@ Handle<JSArray> LiveEdit::CheckAndDropActivations( ...@@ -1909,8 +1909,9 @@ Handle<JSArray> LiveEdit::CheckAndDropActivations(
FixedArray::cast(old_shared_array->elements())); FixedArray::cast(old_shared_array->elements()));
Handle<JSArray> result = isolate->factory()->NewJSArray(len); Handle<JSArray> result = isolate->factory()->NewJSArray(len);
JSObject::EnsureWritableFastElements(result);
Handle<FixedArray> result_elements = Handle<FixedArray> result_elements =
JSObject::EnsureWritableFastElements(result); handle(FixedArray::cast(result->elements()), isolate);
// Fill the default values. // Fill the default values.
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
......
...@@ -705,7 +705,10 @@ class ElementsAccessorBase : public ElementsAccessor { ...@@ -705,7 +705,10 @@ class ElementsAccessorBase : public ElementsAccessor {
array->initialize_elements(); array->initialize_elements();
} else if (length <= capacity) { } else if (length <= capacity) {
if (array->HasFastSmiOrObjectElements()) { if (array->HasFastSmiOrObjectElements()) {
backing_store = JSObject::EnsureWritableFastElements(array); JSObject::EnsureWritableFastElements(array);
if (array->elements() != *backing_store) {
backing_store = handle(array->elements(), isolate);
}
} }
if (2 * length <= capacity) { if (2 * length <= capacity) {
// If more than half the elements won't be used, trim the array. // If more than half the elements won't be used, trim the array.
......
...@@ -983,19 +983,18 @@ MaybeHandle<JSObject> JSObject::New(Handle<JSFunction> constructor, ...@@ -983,19 +983,18 @@ MaybeHandle<JSObject> JSObject::New(Handle<JSFunction> constructor,
return result; return result;
} }
void JSObject::EnsureWritableFastElements(Handle<JSObject> object) {
Handle<FixedArray> JSObject::EnsureWritableFastElements(
Handle<JSObject> object) {
DCHECK(object->HasFastSmiOrObjectElements() || DCHECK(object->HasFastSmiOrObjectElements() ||
object->HasFastStringWrapperElements()); object->HasFastStringWrapperElements());
Isolate* isolate = object->GetIsolate(); FixedArray* raw_elems = FixedArray::cast(object->elements());
Handle<FixedArray> elems(FixedArray::cast(object->elements()), isolate); Heap* heap = object->GetHeap();
if (elems->map() != isolate->heap()->fixed_cow_array_map()) return elems; if (raw_elems->map() != heap->fixed_cow_array_map()) return;
Isolate* isolate = heap->isolate();
Handle<FixedArray> elems(raw_elems, isolate);
Handle<FixedArray> writable_elems = isolate->factory()->CopyFixedArrayWithMap( Handle<FixedArray> writable_elems = isolate->factory()->CopyFixedArrayWithMap(
elems, isolate->factory()->fixed_array_map()); elems, isolate->factory()->fixed_array_map());
object->set_elements(*writable_elems); object->set_elements(*writable_elems);
isolate->counters()->cow_arrays_converted()->Increment(); isolate->counters()->cow_arrays_converted()->Increment();
return writable_elems;
} }
...@@ -16228,10 +16227,16 @@ bool Map::IsValidElementsTransition(ElementsKind from_kind, ...@@ -16228,10 +16227,16 @@ bool Map::IsValidElementsTransition(ElementsKind from_kind,
bool JSArray::HasReadOnlyLength(Handle<JSArray> array) { bool JSArray::HasReadOnlyLength(Handle<JSArray> array) {
LookupIterator it(array, array->GetIsolate()->factory()->length_string(), Isolate* isolate = array->GetIsolate();
// Optimistic fast path: "length" is usually the first fast property.
DescriptorArray* descriptors = array->map()->instance_descriptors();
if (descriptors->length() >= 1 &&
descriptors->GetKey(0) == isolate->heap()->length_string()) {
return descriptors->GetDetails(0).IsReadOnly();
}
LookupIterator it(array, isolate->factory()->length_string(),
LookupIterator::OWN_SKIP_INTERCEPTOR); LookupIterator::OWN_SKIP_INTERCEPTOR);
CHECK_NE(LookupIterator::ACCESS_CHECK, it.state());
CHECK(it.IsFound());
CHECK_EQ(LookupIterator::ACCESSOR, it.state()); CHECK_EQ(LookupIterator::ACCESSOR, it.state());
return it.IsReadOnly(); return it.IsReadOnly();
} }
......
...@@ -2040,8 +2040,7 @@ class JSObject: public JSReceiver { ...@@ -2040,8 +2040,7 @@ class JSObject: public JSReceiver {
inline SeededNumberDictionary* element_dictionary(); // Gets slow elements. inline SeededNumberDictionary* element_dictionary(); // Gets slow elements.
// Requires: HasFastElements(). // Requires: HasFastElements().
static Handle<FixedArray> EnsureWritableFastElements( static void EnsureWritableFastElements(Handle<JSObject> object);
Handle<JSObject> object);
// Collects elements starting at index 0. // Collects elements starting at index 0.
// Undefined values are placed after non-undefined values. // Undefined values are placed after non-undefined values.
......
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