Author: [email protected]
Date: Mon Apr 27 04:16:59 2009
New Revision: 1800
Added:
branches/bleeding_edge/test/mjsunit/regress/regress-326.js
Modified:
branches/bleeding_edge/src/array.js
branches/bleeding_edge/src/globals.h
branches/bleeding_edge/src/objects.cc
branches/bleeding_edge/src/objects.h
branches/bleeding_edge/src/runtime.cc
branches/bleeding_edge/src/runtime.h
branches/bleeding_edge/test/mjsunit/array-sort.js
Log:
Fix Issue 326. Handle sorting of non-array objects correctly.
Change handling of sorting to be the same for all JS-arrays.
Collect undefined values as well while removing holes.
Review URL: http://codereview.chromium.org/92123
Modified: branches/bleeding_edge/src/array.js
==============================================================================
--- branches/bleeding_edge/src/array.js (original)
+++ branches/bleeding_edge/src/array.js Mon Apr 27 04:16:59 2009
@@ -709,33 +709,12 @@
QuickSort(a, high_start, to);
}
- var old_length = ToUint32(this.length);
- if (old_length < 2) return this;
-
- %RemoveArrayHoles(this);
-
var length = ToUint32(this.length);
+ if (length < 2) return this;
- // Move undefined elements to the end of the array.
- for (var i = 0; i < length; ) {
- if (IS_UNDEFINED(this[i])) {
- length--;
- this[i] = this[length];
- this[length] = void 0;
- } else {
- i++;
- }
- }
-
- QuickSort(this, 0, length);
+ var num_non_undefined = %RemoveArrayHoles(this, length);
- // We only changed the length of the this object (in
- // RemoveArrayHoles) if it was an array. We are not allowed to set
- // the length of the this object if it is not an array because this
- // might introduce a new length property.
- if (IS_ARRAY(this)) {
- this.length = old_length;
- }
+ QuickSort(this, 0, num_non_undefined);
return this;
}
Modified: branches/bleeding_edge/src/globals.h
==============================================================================
--- branches/bleeding_edge/src/globals.h (original)
+++ branches/bleeding_edge/src/globals.h Mon Apr 27 04:16:59 2009
@@ -86,6 +86,8 @@
const int kMaxInt = 0x7FFFFFFF;
const int kMinInt = -kMaxInt - 1;
+const uint32_t kMaxUInt32 = 0xFFFFFFFFu;
+
const int kCharSize = sizeof(char); // NOLINT
const int kShortSize = sizeof(short); // NOLINT
const int kIntSize = sizeof(int); // NOLINT
Modified: branches/bleeding_edge/src/objects.cc
==============================================================================
--- branches/bleeding_edge/src/objects.cc (original)
+++ branches/bleeding_edge/src/objects.cc Mon Apr 27 04:16:59 2009
@@ -2850,22 +2850,26 @@
Object* FixedArray::AddKeysFromJSArray(JSArray* array) {
- // Remove array holes from array if any.
- Object* object = array->RemoveHoles();
- if (object->IsFailure()) return object;
- JSArray* compacted_array = JSArray::cast(object);
+ if (array->HasFastElements()) {
+ return UnionOfKeys(array->elements());
+ }
+ ASSERT(!array->HasFastElements());
+ Dictionary* dict = array->element_dictionary();
+ int size = dict->NumberOfElements();
// Allocate a temporary fixed array.
- int compacted_array_length =
Smi::cast(compacted_array->length())->value();
- object = Heap::AllocateFixedArray(compacted_array_length);
+ Object* object = Heap::AllocateFixedArray(size);
if (object->IsFailure()) return object;
FixedArray* key_array = FixedArray::cast(object);
+ int capacity = dict->Capacity();
+ int pos = 0;
// Copy the elements from the JSArray to the temporary fixed array.
- for (int i = 0; i < compacted_array_length; i++) {
- key_array->set(i, compacted_array->GetElement(i));
+ for (int i = 0; i < capacity; i++) {
+ if (dict->IsKey(dict->KeyAt(i))) {
+ key_array->set(pos++, dict->ValueAt(i));
+ }
}
-
// Compute the union of this and the temporary fixed array.
return UnionOfKeys(key_array);
}
@@ -2881,9 +2885,12 @@
// Compute how many elements are not in this.
int extra = 0;
for (int y = 0; y < len1; y++) {
- if (!HasKey(this, other->get(y))) extra++;
+ Object* value = other->get(y);
+ if (!value->IsTheHole() && !HasKey(this, value)) extra++;
}
+ if (extra == 0) return this;
+
// Allocate the result
Object* obj = Heap::AllocateFixedArray(len0 + extra);
if (obj->IsFailure()) return obj;
@@ -2896,7 +2903,8 @@
// Fill in the extra keys.
int index = 0;
for (int y = 0; y < len1; y++) {
- if (!HasKey(this, other->get(y))) {
+ Object* value = other->get(y);
+ if (!value->IsTheHole() && !HasKey(this, value)) {
result->set(len0 + index, other->get(y), mode);
index++;
}
@@ -5628,75 +5636,18 @@
}
-Object* Dictionary::RemoveHoles() {
- int capacity = Capacity();
- Object* obj = Allocate(NumberOfElements());
- if (obj->IsFailure()) return obj;
- Dictionary* dict = Dictionary::cast(obj);
- uint32_t pos = 0;
- for (int i = 0; i < capacity; i++) {
- Object* k = KeyAt(i);
- if (IsKey(k)) {
- dict->AddNumberEntry(pos++, ValueAt(i), DetailsAt(i));
- }
- }
- return dict;
-}
-
-
void Dictionary::CopyValuesTo(FixedArray* elements) {
int pos = 0;
int capacity = Capacity();
+ WriteBarrierMode mode = elements->GetWriteBarrierMode();
for (int i = 0; i < capacity; i++) {
Object* k = KeyAt(i);
- if (IsKey(k)) elements->set(pos++, ValueAt(i));
+ if (IsKey(k)) elements->set(pos++, ValueAt(i), mode);
}
ASSERT(pos == elements->length());
}
-Object* JSArray::RemoveHoles() {
- if (HasFastElements()) {
- int len = Smi::cast(length())->value();
- int pos = 0;
- FixedArray* elms = FixedArray::cast(elements());
- for (int index = 0; index < len; index++) {
- Object* e = elms->get(index);
- if (!e->IsTheHole()) {
- if (index != pos) elms->set(pos, e);
- pos++;
- }
- }
- set_length(Smi::FromInt(pos), SKIP_WRITE_BARRIER);
- for (int index = pos; index < len; index++) {
- elms->set_the_hole(index);
- }
- return this;
- }
-
- // Compact the sparse array if possible.
- Dictionary* dict = element_dictionary();
- int length = dict->NumberOfElements();
-
- // Try to make this a fast array again.
- if (length <= kMaxFastElementsLength) {
- Object* obj = Heap::AllocateFixedArray(length);
- if (obj->IsFailure()) return obj;
- dict->CopyValuesTo(FixedArray::cast(obj));
- set_length(Smi::FromInt(length), SKIP_WRITE_BARRIER);
- set_elements(FixedArray::cast(obj));
- return this;
- }
-
- // Make another dictionary with smaller indices.
- Object* obj = dict->RemoveHoles();
- if (obj->IsFailure()) return obj;
- set_length(Smi::FromInt(length), SKIP_WRITE_BARRIER);
- set_elements(Dictionary::cast(obj));
- return this;
-}
-
-
InterceptorInfo* JSObject::GetNamedInterceptor() {
ASSERT(map()->has_named_interceptor());
JSFunction* constructor = JSFunction::cast(map()->constructor());
@@ -6446,6 +6397,176 @@
// Force instantiation of EvalCache's base class
template class HashTable<0, 2>;
+
+
+// Collates undefined and unexisting elements below limit from position
+// zero of the elements. The object stays in Dictionary mode.
+Object* JSObject::PrepareSlowElementsForSort(uint32_t limit) {
+ ASSERT(!HasFastElements());
+ // Must stay in dictionary mode, either because of
requires_slow_elements,
+ // or because we are not going to sort (and therefore compact) all of the
+ // elements.
+ Dictionary* dict = element_dictionary();
+ HeapNumber* result_double = NULL;
+ if (limit > static_cast<uint32_t>(Smi::kMaxValue)) {
+ // Allocate space for result before we start mutating the object.
+ Object* new_double = Heap::AllocateHeapNumber(0.0);
+ if (new_double->IsFailure()) return new_double;
+ result_double = HeapNumber::cast(new_double);
+ }
+
+ int capacity = dict->Capacity();
+ Object* obj = Dictionary::Allocate(dict->Capacity());
+ if (obj->IsFailure()) return obj;
+ Dictionary* new_dict = Dictionary::cast(obj);
+
+ AssertNoAllocation no_alloc;
+
+ // Loose all details on properties when moving them around.
+ // Elements do not have special details like properties.
+ PropertyDetails no_details = PropertyDetails(NONE, NORMAL);
+
+ uint32_t pos = 0;
+ uint32_t undefs = 0;
+ for (int i = 0; i < capacity; i++) {
+ Object* k = dict->KeyAt(i);
+ if (dict->IsKey(k)) {
+ ASSERT(k->IsNumber());
+ ASSERT(!k->IsSmi() || Smi::cast(k)->value() >= 0);
+ ASSERT(!k->IsHeapNumber() || HeapNumber::cast(k)->value() >= 0);
+ ASSERT(!k->IsHeapNumber() || HeapNumber::cast(k)->value() <=
kMaxUInt32);
+ Object* value = dict->ValueAt(i);
+ uint32_t key = NumberToUint32(k);
+ if (key < limit) {
+ if (value->IsUndefined()) {
+ undefs++;
+ } else {
+ new_dict->AddNumberEntry(pos, value, no_details);
+ pos++;
+ }
+ } else {
+ new_dict->AddNumberEntry(key, value, no_details);
+ }
+ }
+ }
+
+ uint32_t result = pos;
+ while (undefs > 0) {
+ new_dict->AddNumberEntry(pos, Heap::undefined_value(), no_details);
+ pos++;
+ undefs--;
+ }
+
+ set_elements(new_dict);
+
+ if (result <= static_cast<uint32_t>(Smi::kMaxValue)) {
+ return Smi::FromInt(static_cast<int>(result));
+ }
+
+ ASSERT_NE(NULL, result_double);
+ result_double->set_value(static_cast<double>(result));
+ return result_double;
+}
+
+
+// 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.
+Object* JSObject::PrepareElementsForSort(uint32_t limit) {
+ if (!HasFastElements()) {
+ // Convert to fast elements containing only the existing properties.
+ // Ordering is irrelevant, since we are going to sort anyway.
+ Dictionary* dict = element_dictionary();
+ if (IsJSArray() || dict->requires_slow_elements() ||
+ dict->max_number_key() >= limit) {
+ return PrepareSlowElementsForSort(limit);
+ }
+ // Convert to fast elements.
+
+ PretenureFlag tenure = Heap::InNewSpace(this) ? NOT_TENURED: TENURED;
+ Object* new_array =
+ Heap::AllocateFixedArray(dict->NumberOfElements(), tenure);
+ if (new_array->IsFailure()) {
+ return new_array;
+ }
+ FixedArray* fast_elements = FixedArray::cast(new_array);
+ dict->CopyValuesTo(fast_elements);
+ set_elements(fast_elements);
+ }
+ ASSERT(HasFastElements());
+
+ // Collect holes at the end, undefined before that and the rest at the
+ // start, and return the number of non-hole, non-undefined values.
+
+ FixedArray* elements = this->elements();
+ uint32_t elements_length = static_cast<uint32_t>(elements->length());
+ if (limit > elements_length) {
+ limit = elements_length ;
+ }
+ if (limit == 0) {
+ return Smi::FromInt(0);
+ }
+
+ HeapNumber* result_double = NULL;
+ if (limit > static_cast<uint32_t>(Smi::kMaxValue)) {
+ // Pessimistically allocate space for return value before
+ // we start mutating the array.
+ Object* new_double = Heap::AllocateHeapNumber(0.0);
+ if (new_double->IsFailure()) return new_double;
+ result_double = HeapNumber::cast(new_double);
+ }
+
+ AssertNoAllocation no_alloc;
+
+ // 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();
+ 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()) {
+ holes--;
+ undefs--;
+ } else if (current->IsUndefined()) {
+ undefs--;
+ } else {
+ continue;
+ }
+ // Position i needs to be filled.
+ while (undefs > i) {
+ current = elements->get(undefs);
+ if (current->IsTheHole()) {
+ holes--;
+ undefs--;
+ } else if (current->IsUndefined()) {
+ undefs--;
+ } else {
+ elements->set(i, current, write_barrier);
+ break;
+ }
+ }
+ }
+ uint32_t result = undefs;
+ while (undefs < holes) {
+ elements->set_undefined(undefs);
+ undefs++;
+ }
+ while (holes < limit) {
+ elements->set_the_hole(holes);
+ holes++;
+ }
+
+ if (result <= static_cast<uint32_t>(Smi::kMaxValue)) {
+ return Smi::FromInt(static_cast<int>(result));
+ }
+ ASSERT_NE(NULL, result_double);
+ result_double->set_value(static_cast<double>(result));
+ return result_double;
+}
Object* SymbolTable::LookupString(String* string, Object** s) {
Modified: branches/bleeding_edge/src/objects.h
==============================================================================
--- branches/bleeding_edge/src/objects.h (original)
+++ branches/bleeding_edge/src/objects.h Mon Apr 27 04:16:59 2009
@@ -1178,6 +1178,14 @@
inline bool HasFastElements();
inline Dictionary* element_dictionary(); // Gets slow elements.
+ // Collects elements starting at index 0.
+ // Undefined values are placed after non-undefined values.
+ // Returns the number of non-undefined values.
+ Object* PrepareElementsForSort(uint32_t limit);
+ // As PrepareElementsForSort, but only on objects where elements is
+ // a dictionary, and it will stay a dictionary.
+ Object* PrepareSlowElementsForSort(uint32_t limit);
+
Object* SetProperty(String* key,
Object* value,
PropertyAttributes attributes);
@@ -2008,7 +2016,6 @@
void RemoveNumberEntries(uint32_t from, uint32_t to);
// Sorting support
- Object* RemoveHoles();
void CopyValuesTo(FixedArray* elements);
// Casting.
@@ -3891,9 +3898,6 @@
// Set the content of the array to the content of storage.
inline void SetContent(FixedArray* storage);
-
- // Support for sorting
- Object* RemoveHoles();
// Casting.
static inline JSArray* cast(Object* obj);
Modified: branches/bleeding_edge/src/runtime.cc
==============================================================================
--- branches/bleeding_edge/src/runtime.cc (original)
+++ branches/bleeding_edge/src/runtime.cc Mon Apr 27 04:16:59 2009
@@ -5244,12 +5244,16 @@
return string;
}
-
+// Moves all own elements of an object, that are below a limit, to
positions
+// starting at zero. All undefined values are placed after non-undefined
values,
+// and are followed by non-existing element. Does not change the length
+// property.
+// Returns the number of non-undefined elements collected.
static Object* Runtime_RemoveArrayHoles(Arguments args) {
- ASSERT(args.length() == 1);
- // Ignore the case if this is not a JSArray.
- if (!args[0]->IsJSArray()) return args[0];
- return JSArray::cast(args[0])->RemoveHoles();
+ ASSERT(args.length() == 2);
+ CONVERT_CHECKED(JSObject, object, args[0]);
+ CONVERT_NUMBER_CHECKED(uint32_t, limit, Uint32, args[1]);
+ return object->PrepareElementsForSort(limit);
}
Modified: branches/bleeding_edge/src/runtime.h
==============================================================================
--- branches/bleeding_edge/src/runtime.h (original)
+++ branches/bleeding_edge/src/runtime.h Mon Apr 27 04:16:59 2009
@@ -205,7 +205,7 @@
F(IgnoreAttributesAndSetProperty, -1 /* 3 or 4 */) \
\
/* Arrays */ \
- F(RemoveArrayHoles, 1) \
+ F(RemoveArrayHoles, 2) \
F(GetArrayKeys, 2) \
F(MoveArrayContents, 2) \
F(EstimateNumberOfElements, 1) \
Modified: branches/bleeding_edge/test/mjsunit/array-sort.js
==============================================================================
--- branches/bleeding_edge/test/mjsunit/array-sort.js (original)
+++ branches/bleeding_edge/test/mjsunit/array-sort.js Mon Apr 27 04:16:59
2009
@@ -152,3 +152,60 @@
}
TestArraySortingWithUnsoundComparisonFunction();
+
+function TestSparseNonArraySorting(length) {
+ assertTrue(length > 101);
+ var obj = {length: length};
+ obj[0] = 42;
+ obj[10] = 37;
+ obj[100] = undefined;
+ obj[length - 1] = null;
+ Array.prototype.sort.call(obj);
+ assertEquals(length, obj.length, "objsort length unaffected");
+ assertEquals(37, obj[0], "objsort smallest number");
+ assertEquals(42, obj[1], "objsort largest number");
+ assertEquals(null, obj[2], "objsort null");
+ assertEquals(undefined, obj[3], "objsort undefined");
+ assertTrue(3 in obj, "objsort undefined retained");
+ assertFalse(4 in obj, "objsort non-existing retained");
+}
+
+TestSparseNonArraySorting(5000);
+TestSparseNonArraySorting(500000);
+TestSparseNonArraySorting(Math.pow(2, 31) + 1);
+
+function TestArrayLongerLength(length) {
+ var x = new Array(4);
+ x[0] = 42;
+ x[2] = 37;
+ x.length = length;
+ Array.prototype.sort.call(x);
+ assertEquals(length, x.length, "longlength length");
+ assertEquals(37, x[0], "longlength first");
+ assertEquals(42, x[1], "longlength second");
+ assertFalse(2 in x,"longlength third");
+}
+
+TestArrayLongerLength(4);
+TestArrayLongerLength(10);
+TestArrayLongerLength(1000);
+TestArrayLongerLength(500000);
+TestArrayLongerLength(Math.pow(2,32) - 1);
+
+function TestNonArrayLongerLength(length) {
+ var x = {};
+ x[0] = 42;
+ x[2] = 37;
+ x.length = length;
+ Array.prototype.sort.call(x);
+ assertEquals(length, x.length, "longlength length");
+ assertEquals(37, x[0], "longlength first");
+ assertEquals(42, x[1], "longlength second");
+ assertFalse(2 in x,"longlength third");
+}
+
+TestNonArrayLongerLength(4);
+TestNonArrayLongerLength(10);
+TestNonArrayLongerLength(1000);
+TestNonArrayLongerLength(500000);
+TestNonArrayLongerLength(Math.pow(2,32) - 1);
Added: branches/bleeding_edge/test/mjsunit/regress/regress-326.js
==============================================================================
--- (empty file)
+++ branches/bleeding_edge/test/mjsunit/regress/regress-326.js Mon Apr 27
04:16:59 2009
@@ -0,0 +1,40 @@
+// Copyright 2009 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Should not crash or raise an exception.
+// Should sort non-array into equivalent of [37,42,undefined,,0]
+
+var nonArray = { length: 4, 0: 42, 2: 37, 3: undefined, 4: 0 };
+Array.prototype.sort.call(nonArray);
+
+assertEquals(4, nonArray.length, "preserve length");
+assertEquals(37, nonArray[0], "sort smallest first");
+assertEquals(42, nonArray[1], "sort largest last");
+assertTrue(2 in nonArray, "don't delete undefined");
+assertEquals(undefined, nonArray[2], "sort undefined after largest");
+assertFalse(3 in nonArray, "don't create non-existing");
+assertEquals(0, nonArray[4], "don't affect after length.");
--~--~---------~--~----~------------~-------~--~----~
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev
-~----------~----~----~----~------~----~------~--~---