Revert "[array] Move Array#sort pre-processing to Torque"
This reverts commit 2b0ac2fb9f
.
Reason for revert: Breaks scrollingcoordinator/non-fast-scrollable-region-nested.html layout test on https://ci.chromium.org/p/v8/builders/ci/V8-Blink%20Linux%2064/32241
Original change's description:
> [array] Move Array#sort pre-processing to Torque
>
> This CL removes the "PrepareElementsForSort" runtime function, and
> replaces it with a simpler version in Torque. The biggest difference
> is that certain sparse configurations no longer have a fast-path.
>
> The Torque pre-processing step replaces the existing Torque mechanism that
> copied already pre-processed elements into the "work" FixedArray. The Torque
> compacting works as follows:
> - Iterate all elements from 0 to {length}
> - If the element is the hole: Do nothing.
> - If the element is "undefined": Increment undefined counter.
> - In all other cases, push the element into the "work" FixedArray.
>
> Then the "work" FixedArray is sorted as before. Writing the elements from
> the "work" array back into the receiver, after sorting, has three steps:
> 1. Copy the sorted elements from the "work" FixedArray to the receiver.
> 2. Add previously counted number of "undefined" to the receiver.
> 3. Depending on the backing store either delete properties or
> set them to the Hole up to {length}.
>
> Bug: v8:8714
> Change-Id: I14eccb7cfd2e4618bce2a85cba0689d7e0380ad2
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1619756
> Commit-Queue: Simon Zünd <szuend@chromium.org>
> Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
> Reviewed-by: Jakob Gruber <jgruber@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#61812}
TBR=peter.wm.wong@gmail.com,jgruber@chromium.org,tebbi@chromium.org,szuend@chromium.org
Change-Id: If1c1bc07f38dfbd4bf6b6ce8f9d70714e7526877
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: v8:8714
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1627976
Reviewed-by: Simon Zünd <szuend@chromium.org>
Commit-Queue: Simon Zünd <szuend@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61814}
This commit is contained in:
parent
b851d753c9
commit
70eeb22d1c
@ -1024,7 +1024,6 @@ const V8_TYPED_ARRAY_MAX_SIZE_IN_HEAP:
|
||||
constexpr int31 generates 'V8_TYPED_ARRAY_MAX_SIZE_IN_HEAP';
|
||||
const kMaxSafeInteger: constexpr float64 generates 'kMaxSafeInteger';
|
||||
const kSmiMaxValue: constexpr uintptr generates 'kSmiMaxValue';
|
||||
const kSmiMax: uintptr = kSmiMaxValue;
|
||||
const kStringMaxLength: constexpr int31 generates 'String::kMaxLength';
|
||||
const kFixedArrayMaxLength:
|
||||
constexpr int31 generates 'FixedArray::kMaxLength';
|
||||
|
@ -263,6 +263,7 @@ bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) {
|
||||
V(HasFastPackedElements) \
|
||||
V(NewArray) \
|
||||
V(NormalizeElements) \
|
||||
V(PrepareElementsForSort) \
|
||||
V(TypedArrayGetBuffer) \
|
||||
/* Errors */ \
|
||||
V(NewTypeError) \
|
||||
@ -512,6 +513,7 @@ DebugInfo::SideEffectState BuiltinGetSideEffectState(Builtins::Name id) {
|
||||
case Builtins::kArrayPrototypeKeys:
|
||||
case Builtins::kArrayPrototypeLastIndexOf:
|
||||
case Builtins::kArrayPrototypeSlice:
|
||||
case Builtins::kArrayPrototypeSort:
|
||||
case Builtins::kArrayPrototypeToLocaleString:
|
||||
case Builtins::kArrayPrototypeToString:
|
||||
case Builtins::kArrayForEach:
|
||||
@ -785,7 +787,6 @@ DebugInfo::SideEffectState BuiltinGetSideEffectState(Builtins::Name id) {
|
||||
case Builtins::kArrayPrototypeReverse:
|
||||
case Builtins::kArrayPrototypeShift:
|
||||
case Builtins::kArrayPrototypeUnshift:
|
||||
case Builtins::kArrayPrototypeSort:
|
||||
case Builtins::kArrayPrototypeSplice:
|
||||
case Builtins::kArrayUnshift:
|
||||
// Map builtins.
|
||||
|
@ -140,6 +140,8 @@ class FixedArray : public FixedArrayBase {
|
||||
|
||||
inline ObjectSlot GetFirstElementAddress();
|
||||
inline bool ContainsOnlySmisOrHoles();
|
||||
// Returns true iff the elements are Numbers and sorted ascending.
|
||||
bool ContainsSortedNumbers();
|
||||
|
||||
// Gives access to raw memory which stores the array's data.
|
||||
inline ObjectSlot data_start();
|
||||
|
@ -1963,6 +1963,17 @@ MaybeHandle<FixedArray> JSReceiver::GetOwnEntries(Handle<JSReceiver> object,
|
||||
try_fast_path, true);
|
||||
}
|
||||
|
||||
Handle<FixedArray> JSReceiver::GetOwnElementIndices(Isolate* isolate,
|
||||
Handle<JSReceiver> receiver,
|
||||
Handle<JSObject> object) {
|
||||
KeyAccumulator accumulator(isolate, KeyCollectionMode::kOwnOnly,
|
||||
ALL_PROPERTIES);
|
||||
accumulator.CollectOwnElementIndices(receiver, object);
|
||||
Handle<FixedArray> keys =
|
||||
accumulator.GetKeys(GetKeysConversion::kKeepNumbers);
|
||||
DCHECK(keys->ContainsSortedNumbers());
|
||||
return keys;
|
||||
}
|
||||
Maybe<bool> JSReceiver::SetPrototype(Handle<JSReceiver> object,
|
||||
Handle<Object> value, bool from_javascript,
|
||||
ShouldThrow should_throw) {
|
||||
|
@ -259,6 +259,9 @@ class JSReceiver : public HeapObject {
|
||||
Handle<JSReceiver> object, PropertyFilter filter,
|
||||
bool try_fast_path = true);
|
||||
|
||||
V8_WARN_UNUSED_RESULT static Handle<FixedArray> GetOwnElementIndices(
|
||||
Isolate* isolate, Handle<JSReceiver> receiver, Handle<JSObject> object);
|
||||
|
||||
static const int kHashMask = PropertyArray::HashField::kMask;
|
||||
|
||||
DEFINE_FIELD_OFFSET_CONSTANTS(HeapObject::kHeaderSize,
|
||||
|
@ -3811,6 +3811,20 @@ Handle<FixedArray> FixedArray::SetAndGrow(Isolate* isolate,
|
||||
return new_array;
|
||||
}
|
||||
|
||||
bool FixedArray::ContainsSortedNumbers() {
|
||||
for (int i = 1; i < length(); ++i) {
|
||||
Object a_obj = get(i - 1);
|
||||
Object b_obj = get(i);
|
||||
if (!a_obj.IsNumber() || !b_obj.IsNumber()) return false;
|
||||
|
||||
uint32_t a = NumberToUint32(a_obj);
|
||||
uint32_t b = NumberToUint32(b_obj);
|
||||
|
||||
if (a > b) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Handle<FixedArray> FixedArray::ShrinkOrEmpty(Isolate* isolate,
|
||||
Handle<FixedArray> array,
|
||||
int new_length) {
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "src/objects/elements.h"
|
||||
#include "src/objects/hash-table-inl.h"
|
||||
#include "src/objects/js-array-inl.h"
|
||||
#include "src/objects/keys.h"
|
||||
#include "src/objects/prototype.h"
|
||||
#include "src/runtime/runtime-utils.h"
|
||||
|
||||
@ -41,6 +42,386 @@ RUNTIME_FUNCTION(Runtime_TransitionElementsKindWithKind) {
|
||||
return *object;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Find the next free position. undefined and holes are both considered
|
||||
// free spots. Returns "Nothing" if an exception occurred.
|
||||
V8_WARN_UNUSED_RESULT
|
||||
Maybe<uint32_t> FindNextFreePosition(Isolate* isolate,
|
||||
Handle<JSReceiver> receiver,
|
||||
uint32_t current_pos) {
|
||||
for (uint32_t position = current_pos;; ++position) {
|
||||
Maybe<bool> has_element = JSReceiver::HasOwnProperty(receiver, position);
|
||||
MAYBE_RETURN(has_element, Nothing<uint32_t>());
|
||||
if (!has_element.FromJust()) return Just(position);
|
||||
|
||||
Handle<Object> element;
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
||||
isolate, element, JSReceiver::GetElement(isolate, receiver, position),
|
||||
Nothing<uint32_t>());
|
||||
if (element->IsUndefined(isolate)) return Just(position);
|
||||
}
|
||||
}
|
||||
|
||||
// As RemoveArrayHoles, but also handles Dictionary elements that stay
|
||||
// Dictionary (requires_slow_elements() is true), proxies and objects that
|
||||
// might have accessors.
|
||||
V8_WARN_UNUSED_RESULT
|
||||
Object RemoveArrayHolesGeneric(Isolate* isolate, Handle<JSReceiver> receiver,
|
||||
uint32_t limit) {
|
||||
HandleScope scope(isolate);
|
||||
|
||||
// For proxies, we do not collect the keys, instead we use all indices in
|
||||
// the full range of [0, limit).
|
||||
Handle<FixedArray> keys;
|
||||
if (!receiver->IsJSProxy()) {
|
||||
keys = JSReceiver::GetOwnElementIndices(isolate, receiver,
|
||||
Handle<JSObject>::cast(receiver));
|
||||
}
|
||||
|
||||
uint32_t num_undefined = 0;
|
||||
uint32_t current_pos = 0;
|
||||
uint32_t num_indices = keys.is_null() ? limit : keys->length();
|
||||
|
||||
// Compact keys with undefined values and moves non-undefined
|
||||
// values to the front.
|
||||
// The loop does two things simultaneously:
|
||||
// (1) Count the number of 'undefined', i.e.
|
||||
// i.e.: HasProperty(receiver, key) && Get(receiver, key) == undefined
|
||||
// (2) Move all non-undefined values to the front. The variable current_pos
|
||||
// is used to track free spots in the array starting at the beginning.
|
||||
// Holes and 'undefined' are considered free spots.
|
||||
// A hole is when HasElement(receiver, key) is false.
|
||||
for (uint32_t i = 0; i < num_indices; ++i) {
|
||||
uint32_t key = keys.is_null() ? i : NumberToUint32(keys->get(i));
|
||||
|
||||
// We only care about array indices that are smaller than the limit.
|
||||
// The keys are sorted, so we can break as soon as we encounter the first.
|
||||
if (key >= limit) break;
|
||||
|
||||
Maybe<bool> has_element = JSReceiver::HasElement(receiver, key);
|
||||
MAYBE_RETURN(has_element, ReadOnlyRoots(isolate).exception());
|
||||
if (!has_element.FromJust()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Handle<Object> element;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, element, JSReceiver::GetElement(isolate, receiver, key));
|
||||
|
||||
if (element->IsUndefined(isolate)) {
|
||||
++num_undefined;
|
||||
} else {
|
||||
// Find next free position to move elements to.
|
||||
Maybe<uint32_t> free_position =
|
||||
FindNextFreePosition(isolate, receiver, current_pos);
|
||||
MAYBE_RETURN(free_position, ReadOnlyRoots(isolate).exception());
|
||||
current_pos = free_position.FromJust();
|
||||
|
||||
// Do not move elements that are already in the "packed" area.
|
||||
if (key <= current_pos) continue;
|
||||
|
||||
// array[current_pos] = array[key].
|
||||
// Deleting array[key] is done later. This is to preserve the same
|
||||
// semantics as the old JS implementation when working with non-extensible
|
||||
// objects:
|
||||
// If the array contains undefineds, the position at 'key' might later
|
||||
// bet set to 'undefined'. If we delete the element now and later set it
|
||||
// to undefined, the set operation would throw an exception.
|
||||
// Instead, to mark it up as a free space, we set array[key] to undefined.
|
||||
// As 'key' will be incremented afterward, this undefined value will not
|
||||
// affect 'num_undefined', and the logic afterwards will correctly set
|
||||
// the remaining undefineds or delete the remaining properties.
|
||||
RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, Object::SetElement(isolate, receiver, current_pos, element,
|
||||
ShouldThrow::kThrowOnError));
|
||||
RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, Object::SetElement(isolate, receiver, key,
|
||||
isolate->factory()->undefined_value(),
|
||||
ShouldThrow::kThrowOnError));
|
||||
++current_pos;
|
||||
}
|
||||
}
|
||||
|
||||
// current_pos points to the next free space in the array/object. In most
|
||||
// cases this corresponds to the 'length' or to the number of non-undefined
|
||||
// elements.
|
||||
// In cases where an object is 'packed' and 'length' is smaller, e.g.:
|
||||
// { 0: 5, 1: 4, 2: 3, length: 2 }
|
||||
// current_pos will be greater than limit, thus, we need to take the minimum.
|
||||
uint32_t result = std::min(current_pos, limit);
|
||||
|
||||
// Set [current_pos, current_pos + num_undefined) to undefined.
|
||||
for (uint32_t i = 0; i < num_undefined; ++i) {
|
||||
RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, Object::SetElement(isolate, receiver, current_pos++,
|
||||
isolate->factory()->undefined_value(),
|
||||
ShouldThrow::kThrowOnError));
|
||||
}
|
||||
// TODO(szuend): Re-enable when we also copy from the prototype chain for
|
||||
// JSArrays. Then we can use HasOwnProperty instead of
|
||||
// HasElement and this condition will hold.
|
||||
// DCHECK_LE(current_pos, num_indices);
|
||||
|
||||
// Deleting everything after the undefineds up unto the limit.
|
||||
for (uint32_t i = num_indices; i > 0;) {
|
||||
--i;
|
||||
uint32_t key = keys.is_null() ? i : NumberToUint32(keys->get(i));
|
||||
if (key < current_pos) break;
|
||||
if (key >= limit) continue;
|
||||
|
||||
Maybe<bool> delete_result = JSReceiver::DeleteElement(receiver, key);
|
||||
MAYBE_RETURN(delete_result, ReadOnlyRoots(isolate).exception());
|
||||
}
|
||||
|
||||
return *isolate->factory()->NewNumberFromUint(result);
|
||||
}
|
||||
|
||||
// Collects all defined (non-hole) and non-undefined (array) elements at the
|
||||
// start of the elements array. If the object is in dictionary mode, it is
|
||||
// converted to fast elements mode. Undefined values are placed after
|
||||
// non-undefined values. Returns the number of non-undefined values.
|
||||
V8_WARN_UNUSED_RESULT
|
||||
Object RemoveArrayHoles(Isolate* isolate, Handle<JSReceiver> receiver,
|
||||
uint32_t limit) {
|
||||
if (receiver->IsJSProxy()) {
|
||||
return RemoveArrayHolesGeneric(isolate, receiver, limit);
|
||||
}
|
||||
|
||||
Handle<JSObject> object = Handle<JSObject>::cast(receiver);
|
||||
if (object->HasStringWrapperElements()) {
|
||||
int len = String::cast(Handle<JSValue>::cast(object)->value()).length();
|
||||
DCHECK_LE(len, limit);
|
||||
return Smi::FromInt(len);
|
||||
}
|
||||
|
||||
if (object->HasSloppyArgumentsElements() || !object->map().is_extensible()) {
|
||||
return RemoveArrayHolesGeneric(isolate, receiver, limit);
|
||||
}
|
||||
|
||||
JSObject::ValidateElements(*object);
|
||||
if (object->HasDictionaryElements()) {
|
||||
// Convert to fast elements containing only the existing properties.
|
||||
// Ordering is irrelevant, since we are going to sort anyway.
|
||||
Handle<NumberDictionary> dict(object->element_dictionary(), isolate);
|
||||
if (object->IsJSArray() || dict->requires_slow_elements() ||
|
||||
dict->max_number_key() >= limit) {
|
||||
return RemoveArrayHolesGeneric(isolate, receiver, limit);
|
||||
}
|
||||
// Convert to fast elements.
|
||||
Handle<Map> new_map =
|
||||
JSObject::GetElementsTransitionMap(object, HOLEY_ELEMENTS);
|
||||
|
||||
AllocationType allocation = ObjectInYoungGeneration(*object)
|
||||
? AllocationType::kYoung
|
||||
: AllocationType::kOld;
|
||||
Handle<FixedArray> fast_elements =
|
||||
isolate->factory()->NewFixedArray(dict->NumberOfElements(), allocation);
|
||||
dict->CopyValuesTo(*fast_elements);
|
||||
|
||||
JSObject::SetMapAndElements(object, new_map, fast_elements);
|
||||
JSObject::ValidateElements(*object);
|
||||
} else if (object->HasFixedTypedArrayElements()) {
|
||||
// Typed arrays cannot have holes or undefined elements.
|
||||
// TODO(bmeurer, v8:4153): Change this to size_t later.
|
||||
uint32_t array_length =
|
||||
static_cast<uint32_t>(Handle<JSTypedArray>::cast(receiver)->length());
|
||||
return Smi::FromInt(Min(limit, array_length));
|
||||
} else if (!object->HasDoubleElements()) {
|
||||
JSObject::EnsureWritableFastElements(object);
|
||||
}
|
||||
DCHECK(object->HasSmiOrObjectElements() || object->HasDoubleElements());
|
||||
|
||||
// Collect holes at the end, undefined before that and the rest at the
|
||||
// start, and return the number of non-hole, non-undefined values.
|
||||
|
||||
Handle<FixedArrayBase> elements_base(object->elements(), isolate);
|
||||
uint32_t elements_length = static_cast<uint32_t>(elements_base->length());
|
||||
if (limit > elements_length) {
|
||||
limit = elements_length;
|
||||
}
|
||||
if (limit == 0) {
|
||||
return Smi::kZero;
|
||||
}
|
||||
|
||||
uint32_t result = 0;
|
||||
if (elements_base->map() == ReadOnlyRoots(isolate).fixed_double_array_map()) {
|
||||
FixedDoubleArray elements = FixedDoubleArray::cast(*elements_base);
|
||||
// Split elements into defined and the_hole, in that order.
|
||||
unsigned int holes = limit;
|
||||
// Assume most arrays contain no holes and undefined values, so minimize the
|
||||
// number of stores of non-undefined, non-the-hole values.
|
||||
for (unsigned int i = 0; i < holes; i++) {
|
||||
if (elements.is_the_hole(i)) {
|
||||
holes--;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
// Position i needs to be filled.
|
||||
while (holes > i) {
|
||||
if (elements.is_the_hole(holes)) {
|
||||
holes--;
|
||||
} else {
|
||||
elements.set(i, elements.get_scalar(holes));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
result = holes;
|
||||
while (holes < limit) {
|
||||
elements.set_the_hole(holes);
|
||||
holes++;
|
||||
}
|
||||
} else {
|
||||
FixedArray elements = FixedArray::cast(*elements_base);
|
||||
DisallowHeapAllocation no_gc;
|
||||
|
||||
// Split elements into defined, undefined and the_hole, in that order. Only
|
||||
// count locations for undefined and the hole, and fill them afterwards.
|
||||
WriteBarrierMode write_barrier = elements.GetWriteBarrierMode(no_gc);
|
||||
unsigned int undefs = limit;
|
||||
unsigned int holes = limit;
|
||||
// Assume most arrays contain no holes and undefined values, so minimize the
|
||||
// number of stores of non-undefined, non-the-hole values.
|
||||
for (unsigned int i = 0; i < undefs; i++) {
|
||||
Object current = elements.get(i);
|
||||
if (current.IsTheHole(isolate)) {
|
||||
holes--;
|
||||
undefs--;
|
||||
} else if (current.IsUndefined(isolate)) {
|
||||
undefs--;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
// Position i needs to be filled.
|
||||
while (undefs > i) {
|
||||
current = elements.get(undefs);
|
||||
if (current.IsTheHole(isolate)) {
|
||||
holes--;
|
||||
undefs--;
|
||||
} else if (current.IsUndefined(isolate)) {
|
||||
undefs--;
|
||||
} else {
|
||||
elements.set(i, current, write_barrier);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
result = undefs;
|
||||
while (undefs < holes) {
|
||||
elements.set_undefined(isolate, undefs);
|
||||
undefs++;
|
||||
}
|
||||
while (holes < limit) {
|
||||
elements.set_the_hole(isolate, holes);
|
||||
holes++;
|
||||
}
|
||||
}
|
||||
|
||||
DCHECK_LE(result, limit);
|
||||
return *isolate->factory()->NewNumberFromUint(result);
|
||||
}
|
||||
|
||||
// Copy element at index from source to target only if target does not have the
|
||||
// element on its own. Returns true if a copy occurred, false if not
|
||||
// and Nothing if an exception occurred.
|
||||
V8_WARN_UNUSED_RESULT
|
||||
Maybe<bool> ConditionalCopy(Isolate* isolate, Handle<JSReceiver> source,
|
||||
Handle<JSReceiver> target, uint32_t index) {
|
||||
Maybe<bool> source_has_prop = JSReceiver::HasOwnProperty(source, index);
|
||||
MAYBE_RETURN(source_has_prop, Nothing<bool>());
|
||||
if (!source_has_prop.FromJust()) return Just(false);
|
||||
|
||||
Maybe<bool> target_has_prop = JSReceiver::HasOwnProperty(target, index);
|
||||
MAYBE_RETURN(target_has_prop, Nothing<bool>());
|
||||
if (target_has_prop.FromJust()) return Just(false);
|
||||
|
||||
Handle<Object> source_element;
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
||||
isolate, source_element, JSReceiver::GetElement(isolate, target, index),
|
||||
Nothing<bool>());
|
||||
|
||||
Handle<Object> set_result;
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
||||
isolate, set_result,
|
||||
Object::SetElement(isolate, target, index, source_element,
|
||||
ShouldThrow::kThrowOnError),
|
||||
Nothing<bool>());
|
||||
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
// Copy elements in the range 0..length from objects prototype chain
|
||||
// to object itself, if object has holes. Returns null on error and undefined on
|
||||
// success.
|
||||
V8_WARN_UNUSED_RESULT
|
||||
MaybeHandle<Object> CopyFromPrototype(Isolate* isolate,
|
||||
Handle<JSReceiver> object,
|
||||
uint32_t length) {
|
||||
for (PrototypeIterator iter(isolate, object, kStartAtPrototype);
|
||||
!iter.IsAtEnd(); iter.Advance()) {
|
||||
Handle<JSReceiver> current(PrototypeIterator::GetCurrent<JSReceiver>(iter));
|
||||
|
||||
if (current->IsJSProxy()) {
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
MAYBE_RETURN_NULL(ConditionalCopy(isolate, current, object, i));
|
||||
}
|
||||
} else {
|
||||
Handle<FixedArray> keys = JSReceiver::GetOwnElementIndices(
|
||||
isolate, object, Handle<JSObject>::cast(current));
|
||||
|
||||
uint32_t num_indices = keys->length();
|
||||
for (uint32_t i = 0; i < num_indices; ++i) {
|
||||
uint32_t idx = NumberToUint32(keys->get(i));
|
||||
|
||||
// Prototype might have indices that go past length, but we are only
|
||||
// interested in the range [0, length).
|
||||
if (idx >= length) break;
|
||||
|
||||
MAYBE_RETURN_NULL(ConditionalCopy(isolate, current, object, idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
return isolate->factory()->undefined_value();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_PrepareElementsForSort) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(2, args.length());
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, object, 0);
|
||||
CONVERT_NUMBER_CHECKED(uint32_t, length, Uint32, args[1]);
|
||||
|
||||
if (isolate->debug_execution_mode() == DebugInfo::kSideEffects) {
|
||||
if (!isolate->debug()->PerformSideEffectCheckForObject(object)) {
|
||||
return ReadOnlyRoots(isolate).exception();
|
||||
}
|
||||
}
|
||||
|
||||
// Counter for sorting arrays that have non-packed elements and where either
|
||||
// the ElementsProtector is invalid or the prototype does not match
|
||||
// Array.prototype.
|
||||
JSObject initial_array_proto = JSObject::cast(
|
||||
isolate->native_context()->get(Context::INITIAL_ARRAY_PROTOTYPE_INDEX));
|
||||
if (object->IsJSArray() &&
|
||||
!Handle<JSArray>::cast(object)->HasFastPackedElements()) {
|
||||
if (!isolate->IsNoElementsProtectorIntact() ||
|
||||
object->map().prototype() != initial_array_proto) {
|
||||
isolate->CountUsage(
|
||||
v8::Isolate::kArrayPrototypeSortJSArrayModifiedPrototype);
|
||||
}
|
||||
}
|
||||
|
||||
// Skip copying from prototype for JSArrays with ElementsProtector intact and
|
||||
// the original array prototype.
|
||||
if (!object->IsJSArray() || !isolate->IsNoElementsProtectorIntact() ||
|
||||
object->map().prototype() != initial_array_proto) {
|
||||
RETURN_FAILURE_ON_EXCEPTION(isolate,
|
||||
CopyFromPrototype(isolate, object, length));
|
||||
}
|
||||
return RemoveArrayHoles(isolate, object, length);
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_NewArray) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_LE(3, args.length());
|
||||
|
@ -46,6 +46,7 @@ namespace internal {
|
||||
I(IsArray, 1, 1) \
|
||||
F(NewArray, -1 /* >= 3 */, 1) \
|
||||
F(NormalizeElements, 1, 1) \
|
||||
F(PrepareElementsForSort, 2, 1) \
|
||||
F(TransitionElementsKind, 2, 1) \
|
||||
F(TransitionElementsKindWithKind, 2, 1) \
|
||||
|
||||
|
@ -93,9 +93,7 @@ function CreateHoleyObjectArray() {
|
||||
|
||||
function CreateDictionaryArray() {
|
||||
array_to_sort = Array.from(template_array);
|
||||
Object.defineProperty(array_to_sort, kArraySize - 2,
|
||||
{ get: () => this.foo,
|
||||
set: (v) => this.foo = v });
|
||||
array_to_sort[%MaxSmi()] = 42;
|
||||
|
||||
AssertDictionaryElements();
|
||||
}
|
||||
|
@ -127,8 +127,9 @@ function TestSparseNonArraySorting(length) {
|
||||
assertFalse(4 in obj, "objsort non-existing retained");
|
||||
}
|
||||
|
||||
TestSparseNonArraySorting(1000);
|
||||
TestSparseNonArraySorting(5000);
|
||||
TestSparseNonArraySorting(500000);
|
||||
TestSparseNonArraySorting(Math.pow(2, 31) + 1);
|
||||
|
||||
|
||||
function TestArrayLongerLength(length) {
|
||||
@ -146,7 +147,8 @@ function TestArrayLongerLength(length) {
|
||||
TestArrayLongerLength(4);
|
||||
TestArrayLongerLength(10);
|
||||
TestArrayLongerLength(1000);
|
||||
TestArrayLongerLength(5000);
|
||||
TestArrayLongerLength(500000);
|
||||
TestArrayLongerLength(Math.pow(2,32) - 1);
|
||||
|
||||
|
||||
function TestNonArrayLongerLength(length) {
|
||||
@ -164,7 +166,8 @@ function TestNonArrayLongerLength(length) {
|
||||
TestNonArrayLongerLength(4);
|
||||
TestNonArrayLongerLength(10);
|
||||
TestNonArrayLongerLength(1000);
|
||||
TestNonArrayLongerLength(5000);
|
||||
TestNonArrayLongerLength(500000);
|
||||
TestNonArrayLongerLength(Math.pow(2,32) - 1);
|
||||
|
||||
|
||||
function TestNonArrayWithAccessors() {
|
||||
|
@ -22,5 +22,5 @@ Array.prototype.sort.call(xs);
|
||||
// the spec:
|
||||
// - "xs" is sparse and IsExtensible(xs) is false (its frozen).
|
||||
// - "xs" is sparse and the prototype has properties in the sort range.
|
||||
assertEquals(1, xs[0]);
|
||||
assertEquals(2, xs[1]);
|
||||
assertEquals(2, xs[0]);
|
||||
assertEquals(1, xs[1]);
|
||||
|
@ -72,3 +72,21 @@ float_array[0] = 1e51;
|
||||
%OptimizeFunctionOnNextCall(f);
|
||||
f();
|
||||
})();
|
||||
|
||||
// crbug.com/935133
|
||||
(function() {
|
||||
var called_has = false;
|
||||
var proxy = new Proxy({}, {
|
||||
has: function(x, p) {
|
||||
called_has = true;
|
||||
throw "The test may finish now";
|
||||
},
|
||||
});
|
||||
proxy.length = 2147483648;
|
||||
try {
|
||||
Array.prototype.sort.call(proxy);
|
||||
} catch(e) {
|
||||
assertTrue(e === "The test may finish now");
|
||||
}
|
||||
assertTrue(called_has);
|
||||
})();
|
||||
|
@ -163,13 +163,6 @@
|
||||
# https://crbug.com/v8/8120
|
||||
'ecma_3/Array/regress-322135-04': [SKIP],
|
||||
|
||||
# These tests try to sort very large arrays. Array#sort pre-processing does
|
||||
# not support huge sparse Arrays, so these tests run a very long time.
|
||||
# https://crbug.com/v8/8714
|
||||
'js1_5/Array/regress-330812': [SKIP],
|
||||
'js1_5/Regress/regress-422348': [SKIP],
|
||||
'js1_5/Array/regress-157652': [SKIP],
|
||||
|
||||
##################### SLOW TESTS #####################
|
||||
|
||||
# Compiles a long chain of && or || operations, can time out under slower
|
||||
@ -516,6 +509,7 @@
|
||||
'ecma_3/extensions/regress-274152': [FAIL_OK],
|
||||
'js1_5/Regress/regress-372364': [FAIL_OK],
|
||||
'js1_5/Regress/regress-420919': [FAIL_OK],
|
||||
'js1_5/Regress/regress-422348': [FAIL_OK],
|
||||
'js1_5/Regress/regress-410852': [FAIL_OK],
|
||||
'ecma_3/RegExp/regress-375715-04': [FAIL_OK],
|
||||
'js1_5/decompilation/regress-456964-01': [FAIL_OK],
|
||||
|
@ -36,11 +36,6 @@
|
||||
# Irregexp interpreter overflows stack. We should just not crash.
|
||||
'fast/js/regexp-stack-overflow': [PASS, FAIL],
|
||||
|
||||
# This test tries to sort very large array. Array#sort pre-processing does
|
||||
# not support huge sparse Arrays, so this test runs a very long time.
|
||||
# https://crbug.com/v8/8714
|
||||
'array-sort-small-sparse-array-with-large-length': [SKIP],
|
||||
|
||||
# Slow tests.
|
||||
'dfg-double-vote-fuzz': [PASS, SLOW],
|
||||
}], # ALWAYS
|
||||
|
369
third_party/v8/builtins/array-sort.tq
vendored
369
third_party/v8/builtins/array-sort.tq
vendored
@ -34,7 +34,7 @@ namespace array {
|
||||
ResetToGenericAccessor() {
|
||||
this.loadFn = Load<GenericElementsAccessor>;
|
||||
this.storeFn = Store<GenericElementsAccessor>;
|
||||
this.deleteFn = Delete<GenericElementsAccessor>;
|
||||
this.bailoutStatus = kSuccess;
|
||||
}
|
||||
|
||||
// The receiver of the Array.p.sort call.
|
||||
@ -54,14 +54,17 @@ namespace array {
|
||||
// uses ToString and a lexicographical compare.
|
||||
sortComparePtr: CompareBuiltinFn;
|
||||
|
||||
// The following four function pointer represent a Accessor/Path.
|
||||
// These are used to Load/Store/Delete elements and to check whether
|
||||
// to bail to the baseline GenericElementsAccessor.
|
||||
// The following three function pointer represent a Accessor/Path.
|
||||
// These are used to Load/Store elements and to check whether to bail to the
|
||||
// baseline GenericElementsAccessor.
|
||||
loadFn: LoadFn;
|
||||
storeFn: StoreFn;
|
||||
deleteFn: DeleteFn;
|
||||
canUseSameAccessorFn: CanUseSameAccessorFn;
|
||||
|
||||
// If this field has the value kFailure, we need to bail to the baseline
|
||||
// GenericElementsAccessor.
|
||||
bailoutStatus: Smi;
|
||||
|
||||
// This controls when we get *into* galloping mode. It's initialized to
|
||||
// kMinGallop. mergeLow and mergeHigh tend to nudge it higher for random
|
||||
// data, and lower for highly structured data.
|
||||
@ -87,76 +90,48 @@ namespace array {
|
||||
|
||||
// Pointer to the temporary array.
|
||||
tempArray: FixedArray;
|
||||
|
||||
// The initialReceiverLength converted and clamped to Smi.
|
||||
sortLength: Smi;
|
||||
|
||||
// The number of undefined that need to be inserted after sorting
|
||||
// when the elements are copied back from the workArray to the receiver.
|
||||
numberOfUndefined: Smi;
|
||||
}
|
||||
|
||||
type FastSmiElements;
|
||||
type FastObjectElements;
|
||||
|
||||
transitioning macro NewSortState(implicit context: Context)(
|
||||
receiver: JSReceiver, comparefn: Undefined | Callable,
|
||||
initialReceiverLength: Number): SortState {
|
||||
initialReceiverLength: Number, sortLength: Smi,
|
||||
forceGeneric: constexpr bool): SortState {
|
||||
const sortComparePtr =
|
||||
comparefn != Undefined ? SortCompareUserFn : SortCompareDefault;
|
||||
const map = receiver.map;
|
||||
let loadFn: LoadFn;
|
||||
let storeFn: StoreFn;
|
||||
let deleteFn: DeleteFn;
|
||||
let canUseSameAccessorFn: CanUseSameAccessorFn;
|
||||
let loadFn = Load<GenericElementsAccessor>;
|
||||
let storeFn = Store<GenericElementsAccessor>;
|
||||
let canUseSameAccessorFn = CanUseSameAccessor<GenericElementsAccessor>;
|
||||
|
||||
try {
|
||||
GotoIfForceSlowPath() otherwise Slow;
|
||||
let a: FastJSArray = Cast<FastJSArray>(receiver) otherwise Slow;
|
||||
if constexpr (!forceGeneric) {
|
||||
GotoIfForceSlowPath() otherwise Slow;
|
||||
let a: FastJSArray = Cast<FastJSArray>(receiver) otherwise Slow;
|
||||
|
||||
const elementsKind: ElementsKind = map.elements_kind;
|
||||
if (IsDoubleElementsKind(elementsKind)) {
|
||||
loadFn = Load<FastDoubleElements>;
|
||||
storeFn = Store<FastDoubleElements>;
|
||||
deleteFn = Delete<FastDoubleElements>;
|
||||
canUseSameAccessorFn = CanUseSameAccessor<FastDoubleElements>;
|
||||
} else if (IsFastSmiElementsKind(elementsKind)) {
|
||||
loadFn = Load<FastSmiElements>;
|
||||
storeFn = Store<FastSmiElements>;
|
||||
deleteFn = Delete<FastSmiElements>;
|
||||
canUseSameAccessorFn = CanUseSameAccessor<FastSmiElements>;
|
||||
} else {
|
||||
loadFn = Load<FastObjectElements>;
|
||||
storeFn = Store<FastObjectElements>;
|
||||
deleteFn = Delete<FastObjectElements>;
|
||||
canUseSameAccessorFn = CanUseSameAccessor<FastObjectElements>;
|
||||
const elementsKind: ElementsKind = map.elements_kind;
|
||||
if (IsDoubleElementsKind(elementsKind)) {
|
||||
loadFn = Load<FastDoubleElements>;
|
||||
storeFn = Store<FastDoubleElements>;
|
||||
canUseSameAccessorFn = CanUseSameAccessor<FastDoubleElements>;
|
||||
} else if (elementsKind == PACKED_SMI_ELEMENTS) {
|
||||
loadFn = Load<FastPackedSmiElements>;
|
||||
storeFn = Store<FastPackedSmiElements>;
|
||||
canUseSameAccessorFn = CanUseSameAccessor<FastPackedSmiElements>;
|
||||
} else {
|
||||
loadFn = Load<FastSmiOrObjectElements>;
|
||||
storeFn = Store<FastSmiOrObjectElements>;
|
||||
canUseSameAccessorFn = CanUseSameAccessor<FastSmiOrObjectElements>;
|
||||
}
|
||||
}
|
||||
}
|
||||
label Slow {
|
||||
loadFn = Load<GenericElementsAccessor>;
|
||||
storeFn = Store<GenericElementsAccessor>;
|
||||
deleteFn = Delete<GenericElementsAccessor>;
|
||||
canUseSameAccessorFn = CanUseSameAccessor<GenericElementsAccessor>;
|
||||
}
|
||||
|
||||
// With the pre-processing step in Torque, the exact number of elements
|
||||
// to sort is unknown at this time. The 'length' property is an upper bound
|
||||
// (as per spec) while the actual size of the backing store is a good guess.
|
||||
// After the pre-processing step, the workarray won't change in length.
|
||||
let workArrayLength: intptr =
|
||||
Signed(Convert<uintptr>(initialReceiverLength));
|
||||
try {
|
||||
const object = Cast<JSObject>(receiver) otherwise NoJsObject;
|
||||
const elementsLength = Convert<intptr>(object.elements.length);
|
||||
|
||||
// In some cases, elements are only on prototypes, but not on the receiver
|
||||
// itself. Do nothing then, as {workArrayLength} got initialized with the
|
||||
// {length} property.
|
||||
if (elementsLength != 0) {
|
||||
workArrayLength = IntPtrMin(workArrayLength, elementsLength);
|
||||
if (map.elements_kind == DICTIONARY_ELEMENTS && IsExtensibleMap(map) &&
|
||||
!IsCustomElementsReceiverInstanceType(map.instance_type)) {
|
||||
loadFn = Load<DictionaryElements>;
|
||||
storeFn = Store<DictionaryElements>;
|
||||
canUseSameAccessorFn = CanUseSameAccessor<DictionaryElements>;
|
||||
}
|
||||
}
|
||||
label NoJsObject {}
|
||||
|
||||
return new SortState{
|
||||
receiver,
|
||||
@ -166,18 +141,17 @@ namespace array {
|
||||
sortComparePtr,
|
||||
loadFn,
|
||||
storeFn,
|
||||
deleteFn,
|
||||
canUseSameAccessorFn,
|
||||
bailoutStatus: kSuccess,
|
||||
minGallop: kMinGallopWins,
|
||||
pendingRunsSize: 0,
|
||||
pendingRuns: AllocateZeroedFixedArray(Convert<intptr>(kMaxMergePending)),
|
||||
workArray: AllocateZeroedFixedArray(workArrayLength),
|
||||
tempArray: kEmptyFixedArray,
|
||||
sortLength: 0,
|
||||
numberOfUndefined: 0
|
||||
workArray: AllocateZeroedFixedArray(Convert<intptr>(sortLength)),
|
||||
tempArray: kEmptyFixedArray
|
||||
};
|
||||
}
|
||||
|
||||
const kFailure: Smi = -1;
|
||||
const kSuccess: Smi = 0;
|
||||
|
||||
// The maximum number of entries in a SortState's pending-runs stack.
|
||||
@ -197,7 +171,6 @@ namespace array {
|
||||
|
||||
type LoadFn = builtin(Context, SortState, Smi) => Object;
|
||||
type StoreFn = builtin(Context, SortState, Smi, Object) => Smi;
|
||||
type DeleteFn = builtin(Context, SortState, Smi) => Smi;
|
||||
type CanUseSameAccessorFn = builtin(Context, JSReceiver, Object, Number) =>
|
||||
Boolean;
|
||||
type CompareBuiltinFn = builtin(Context, Object, Object, Object) => Number;
|
||||
@ -210,23 +183,28 @@ namespace array {
|
||||
|
||||
transitioning builtin Load<ElementsAccessor: type>(
|
||||
context: Context, sortState: SortState, index: Smi): Object {
|
||||
const receiver = sortState.receiver;
|
||||
if (!HasProperty_Inline(receiver, index)) return Hole;
|
||||
return GetProperty(receiver, index);
|
||||
return GetProperty(sortState.receiver, index);
|
||||
}
|
||||
|
||||
Load<FastSmiElements>(context: Context, sortState: SortState, index: Smi):
|
||||
Object {
|
||||
Load<FastPackedSmiElements>(
|
||||
context: Context, sortState: SortState, index: Smi): Object {
|
||||
const object = UnsafeCast<JSObject>(sortState.receiver);
|
||||
const elements = UnsafeCast<FixedArray>(object.elements);
|
||||
return elements.objects[index];
|
||||
}
|
||||
|
||||
Load<FastObjectElements>(context: Context, sortState: SortState, index: Smi):
|
||||
Object {
|
||||
Load<FastSmiOrObjectElements>(
|
||||
context: Context, sortState: SortState, index: Smi): Object {
|
||||
const object = UnsafeCast<JSObject>(sortState.receiver);
|
||||
const elements = UnsafeCast<FixedArray>(object.elements);
|
||||
return elements.objects[index];
|
||||
const result: Object = elements.objects[index];
|
||||
if (IsTheHole(result)) {
|
||||
// The pre-processing step removed all holes by compacting all elements
|
||||
// at the start of the array. Finding a hole means the cmp function or
|
||||
// ToString changes the array.
|
||||
return Failure(sortState);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Load<FastDoubleElements>(context: Context, sortState: SortState, index: Smi):
|
||||
@ -234,11 +212,28 @@ namespace array {
|
||||
try {
|
||||
const object = UnsafeCast<JSObject>(sortState.receiver);
|
||||
const elements = UnsafeCast<FixedDoubleArray>(object.elements);
|
||||
const value = LoadDoubleWithHoleCheck(elements, index) otherwise IfHole;
|
||||
const value = LoadDoubleWithHoleCheck(elements, index) otherwise Bailout;
|
||||
return AllocateHeapNumberWithValue(value);
|
||||
}
|
||||
label IfHole {
|
||||
return Hole;
|
||||
label Bailout {
|
||||
// The pre-processing step removed all holes by compacting all elements
|
||||
// at the start of the array. Finding a hole means the cmp function or
|
||||
// ToString changes the array.
|
||||
return Failure(sortState);
|
||||
}
|
||||
}
|
||||
|
||||
Load<DictionaryElements>(context: Context, sortState: SortState, index: Smi):
|
||||
Object {
|
||||
try {
|
||||
const object = UnsafeCast<JSObject>(sortState.receiver);
|
||||
const dictionary = UnsafeCast<NumberDictionary>(object.elements);
|
||||
const intptrIndex = Convert<intptr>(index);
|
||||
return BasicLoadNumberDictionaryElement(dictionary, intptrIndex)
|
||||
otherwise Bailout, Bailout;
|
||||
}
|
||||
label Bailout {
|
||||
return Failure(sortState);
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,7 +243,7 @@ namespace array {
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
Store<FastSmiElements>(
|
||||
Store<FastPackedSmiElements>(
|
||||
context: Context, sortState: SortState, index: Smi, value: Object): Smi {
|
||||
const object = UnsafeCast<JSObject>(sortState.receiver);
|
||||
const elements = UnsafeCast<FixedArray>(object.elements);
|
||||
@ -257,7 +252,7 @@ namespace array {
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
Store<FastObjectElements>(
|
||||
Store<FastSmiOrObjectElements>(
|
||||
context: Context, sortState: SortState, index: Smi, value: Object): Smi {
|
||||
const object = UnsafeCast<JSObject>(sortState.receiver);
|
||||
const elements = UnsafeCast<FixedArray>(object.elements);
|
||||
@ -275,42 +270,26 @@ namespace array {
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
transitioning builtin Delete<ElementsAccessor: type>(
|
||||
context: Context, sortState: SortState, index: Smi): Smi {
|
||||
const receiver = sortState.receiver;
|
||||
if (!HasProperty_Inline(receiver, index)) return kSuccess;
|
||||
DeleteProperty(receiver, index, kSloppy);
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
Delete<FastSmiElements>(context: Context, sortState: SortState, index: Smi):
|
||||
Smi {
|
||||
assert(IsHoleyFastElementsKind(sortState.receiver.map.elements_kind));
|
||||
|
||||
Store<DictionaryElements>(
|
||||
context: Context, sortState: SortState, index: Smi, value: Object): Smi {
|
||||
const object = UnsafeCast<JSObject>(sortState.receiver);
|
||||
const elements = UnsafeCast<FixedArray>(object.elements);
|
||||
elements.objects[index] = Hole;
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
Delete<FastObjectElements>(
|
||||
context: Context, sortState: SortState, index: Smi): Smi {
|
||||
assert(IsHoleyFastElementsKind(sortState.receiver.map.elements_kind));
|
||||
|
||||
const object = UnsafeCast<JSObject>(sortState.receiver);
|
||||
const elements = UnsafeCast<FixedArray>(object.elements);
|
||||
elements.objects[index] = Hole;
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
Delete<FastDoubleElements>(
|
||||
context: Context, sortState: SortState, index: Smi): Smi {
|
||||
assert(IsHoleyFastElementsKind(sortState.receiver.map.elements_kind));
|
||||
|
||||
const object = UnsafeCast<JSObject>(sortState.receiver);
|
||||
const elements = UnsafeCast<FixedDoubleArray>(object.elements);
|
||||
StoreFixedDoubleArrayHoleSmi(elements, index);
|
||||
return kSuccess;
|
||||
const dictionary = UnsafeCast<NumberDictionary>(object.elements);
|
||||
const intptrIndex = Convert<intptr>(index);
|
||||
try {
|
||||
BasicStoreNumberDictionaryElement(dictionary, intptrIndex, value)
|
||||
otherwise Fail, Fail, ReadOnly;
|
||||
return kSuccess;
|
||||
}
|
||||
label ReadOnly {
|
||||
// We cannot write to read-only data properties. Throw the same TypeError
|
||||
// as SetProperty would.
|
||||
const receiver = sortState.receiver;
|
||||
ThrowTypeError(
|
||||
kStrictReadOnlyProperty, index, Typeof(receiver), receiver);
|
||||
}
|
||||
label Fail {
|
||||
return Failure(sortState);
|
||||
}
|
||||
}
|
||||
|
||||
transitioning builtin SortCompareDefault(
|
||||
@ -376,6 +355,12 @@ namespace array {
|
||||
return True;
|
||||
}
|
||||
|
||||
CanUseSameAccessor<DictionaryElements>(
|
||||
context: Context, receiver: JSReceiver, initialReceiverMap: Object,
|
||||
initialReceiverLength: Number): Boolean {
|
||||
return SelectBooleanConstant(receiver.map == initialReceiverMap);
|
||||
}
|
||||
|
||||
// Re-loading the stack-size is done in a few places. The small macro allows
|
||||
// for easier invariant checks at all use sites.
|
||||
macro GetPendingRunsSize(implicit context: Context)(sortState: SortState):
|
||||
@ -434,6 +419,36 @@ namespace array {
|
||||
return tempArray;
|
||||
}
|
||||
|
||||
// This macro jumps to the Bailout label iff kBailoutStatus is kFailure.
|
||||
macro EnsureSuccess(implicit context: Context)(sortState:
|
||||
SortState) labels Bailout {
|
||||
if (sortState.bailoutStatus == kFailure) goto Bailout;
|
||||
}
|
||||
|
||||
// Sets kBailoutStatus to kFailure and returns kFailure.
|
||||
macro Failure(sortState: SortState): Smi {
|
||||
sortState.bailoutStatus = kFailure;
|
||||
return kFailure;
|
||||
}
|
||||
|
||||
// The following Call* macros wrap builtin calls, making call sites more
|
||||
// readable since we can use labels and do not have to check kBailoutStatus
|
||||
// or the return value.
|
||||
|
||||
macro CallLoad(implicit context: Context, sortState: SortState)(
|
||||
load: LoadFn, index: Smi): Object
|
||||
labels Bailout {
|
||||
const result: Object = load(context, sortState, index);
|
||||
EnsureSuccess(sortState) otherwise Bailout;
|
||||
return result;
|
||||
}
|
||||
|
||||
macro CallStore(implicit context: Context, sortState: SortState)(
|
||||
store: StoreFn, index: Smi, value: Object) labels Bailout {
|
||||
store(context, sortState, index, value);
|
||||
EnsureSuccess(sortState) otherwise Bailout;
|
||||
}
|
||||
|
||||
transitioning builtin
|
||||
Copy(implicit context: Context)(
|
||||
source: FixedArray, srcPos: Smi, target: FixedArray, dstPos: Smi,
|
||||
@ -1253,87 +1268,49 @@ namespace array {
|
||||
}
|
||||
|
||||
transitioning macro
|
||||
CompactReceiverElementsIntoWorkArray(
|
||||
implicit context: Context, sortState: SortState)(): Smi {
|
||||
let growableWorkArray = growable_fixed_array::GrowableFixedArray{
|
||||
array: sortState.workArray,
|
||||
capacity: Convert<intptr>(sortState.workArray.length),
|
||||
length: 0
|
||||
};
|
||||
CopyReceiverElementsToWorkArray(
|
||||
implicit context: Context, sortState: SortState)(length: Smi) {
|
||||
// TODO(szuend): Investigate if we can use COW arrays or a memcpy + range
|
||||
// barrier to speed this step up.
|
||||
let loadFn = sortState.loadFn;
|
||||
const workArray = sortState.workArray;
|
||||
|
||||
const loadFn = sortState.loadFn;
|
||||
|
||||
// TODO(szuend): Implement full range sorting, not only up to MaxSmi.
|
||||
// https://crbug.com/v8/7970.
|
||||
const receiverLength: Number = sortState.initialReceiverLength;
|
||||
assert(IsNumberNormalized(receiverLength));
|
||||
|
||||
const sortLength: Smi = TaggedIsSmi(receiverLength) ?
|
||||
UnsafeCast<Smi>(receiverLength) :
|
||||
Convert<PositiveSmi>(kSmiMax) otherwise unreachable;
|
||||
|
||||
// Move all non-undefined elements into {sortState.workArray}, holes
|
||||
// are ignored.
|
||||
let numberOfUndefined: Smi = 0;
|
||||
for (let i: Smi = 0; i < receiverLength; ++i) {
|
||||
const element: Object = loadFn(context, sortState, i);
|
||||
|
||||
if (element == Hole) {
|
||||
// Do nothing for holes. The result is that elements are
|
||||
// compacted at the front of the work array.
|
||||
} else if (element == Undefined) {
|
||||
numberOfUndefined++;
|
||||
} else {
|
||||
growableWorkArray.Push(element);
|
||||
for (let i: Smi = 0; i < length; ++i) {
|
||||
try {
|
||||
workArray.objects[i] = CallLoad(loadFn, i) otherwise Bailout;
|
||||
}
|
||||
label Bailout deferred {
|
||||
sortState.ResetToGenericAccessor();
|
||||
loadFn = sortState.loadFn;
|
||||
workArray.objects[i] = CallLoad(loadFn, i) otherwise unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the workArray on the frameState, as it may have grown.
|
||||
sortState.workArray = growableWorkArray.array;
|
||||
sortState.sortLength = sortLength;
|
||||
sortState.numberOfUndefined = numberOfUndefined;
|
||||
|
||||
return Convert<Smi>(growableWorkArray.length);
|
||||
}
|
||||
|
||||
transitioning macro
|
||||
CopyWorkArrayToReceiver(implicit context: Context, sortState: SortState)(
|
||||
numberOfNonUndefined: Smi) {
|
||||
const storeFn = sortState.storeFn;
|
||||
length: Smi) {
|
||||
// TODO(szuend): Build fast-path that simply installs the work array as the
|
||||
// new backing store where applicable.
|
||||
let storeFn = sortState.storeFn;
|
||||
const workArray = sortState.workArray;
|
||||
|
||||
assert(numberOfNonUndefined <= workArray.length);
|
||||
assert(
|
||||
numberOfNonUndefined + sortState.numberOfUndefined <=
|
||||
sortState.sortLength);
|
||||
|
||||
// Writing the elements back is a 3 step process:
|
||||
// 1. Copy the sorted elements from the workarray to the receiver.
|
||||
// 2. Add {nOfUndefined} undefineds to the receiver.
|
||||
// 3. Depending on the backing store either delete properties or
|
||||
// set them to the Hole up to {sortState.sortLength}.
|
||||
let index: Smi = 0;
|
||||
for (; index < numberOfNonUndefined; ++index) {
|
||||
storeFn(context, sortState, index, workArray.objects[index]);
|
||||
}
|
||||
|
||||
const numberOfUndefinedEnd: Smi =
|
||||
sortState.numberOfUndefined + numberOfNonUndefined;
|
||||
for (; index < numberOfUndefinedEnd; ++index) {
|
||||
storeFn(context, sortState, index, Undefined);
|
||||
}
|
||||
|
||||
const end: Smi = sortState.sortLength;
|
||||
const deleteFn = sortState.deleteFn;
|
||||
for (; index < end; ++index) {
|
||||
deleteFn(context, sortState, index);
|
||||
for (let i: Smi = 0; i < length; ++i) {
|
||||
try {
|
||||
CallStore(storeFn, i, workArray.objects[i]) otherwise Bailout;
|
||||
}
|
||||
label Bailout deferred {
|
||||
sortState.ResetToGenericAccessor();
|
||||
storeFn = sortState.storeFn;
|
||||
CallStore(storeFn, i, workArray.objects[i]) otherwise unreachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transitioning builtin
|
||||
ArrayTimSort(context: Context, sortState: SortState): Object {
|
||||
const numberOfNonUndefined: Smi = CompactReceiverElementsIntoWorkArray();
|
||||
ArrayTimSortImpl(context, sortState, numberOfNonUndefined);
|
||||
ArrayTimSort(context: Context, sortState: SortState, length: Smi): Object {
|
||||
CopyReceiverElementsToWorkArray(length);
|
||||
ArrayTimSortImpl(context, sortState, length);
|
||||
|
||||
try {
|
||||
// The comparison function or toString might have changed the
|
||||
@ -1344,10 +1321,24 @@ namespace array {
|
||||
sortState.ResetToGenericAccessor();
|
||||
}
|
||||
|
||||
CopyWorkArrayToReceiver(numberOfNonUndefined);
|
||||
CopyWorkArrayToReceiver(length);
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
// For compatibility with JSC, we also sort elements inherited from
|
||||
// the prototype chain on non-Array objects.
|
||||
// We do this by copying them to this object and sorting only
|
||||
// own elements. This is not very efficient, but sorting with
|
||||
// inherited elements happens very, very rarely, if at all.
|
||||
// The specification allows "implementation dependent" behavior
|
||||
// if an element on the prototype chain has an element that
|
||||
// might interact with sorting.
|
||||
//
|
||||
// We also move all non-undefined elements to the front of the
|
||||
// array and move the undefineds after that. Holes are removed.
|
||||
// This happens for Array as well as non-Array objects.
|
||||
extern runtime PrepareElementsForSort(Context, Object, Number): Smi;
|
||||
|
||||
// https://tc39.github.io/ecma262/#sec-array.prototype.sort
|
||||
transitioning javascript builtin
|
||||
ArrayPrototypeSort(context: Context, receiver: Object, ...arguments): Object {
|
||||
@ -1365,8 +1356,16 @@ namespace array {
|
||||
|
||||
if (len < 2) return receiver;
|
||||
|
||||
const sortState: SortState = NewSortState(obj, comparefn, len);
|
||||
ArrayTimSort(context, sortState);
|
||||
// TODO(szuend): Investigate performance tradeoff of skipping this step
|
||||
// for PACKED_* and handling Undefineds during sorting.
|
||||
const nofNonUndefined: Smi = PrepareElementsForSort(context, obj, len);
|
||||
assert(nofNonUndefined <= len);
|
||||
|
||||
if (nofNonUndefined < 2) return receiver;
|
||||
|
||||
const sortState: SortState =
|
||||
NewSortState(obj, comparefn, len, nofNonUndefined, false);
|
||||
ArrayTimSort(context, sortState, nofNonUndefined);
|
||||
|
||||
return receiver;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user