| /* |
| * Copyright (C) 1999-2000 Harri Porten (porten@kde.org) |
| * Copyright (C) 2003, 2007, 2008, 2009, 2012 Apple Inc. All rights reserved. |
| * Copyright (C) 2003 Peter Kelly (pmk@post.com) |
| * Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com) |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #include "config.h" |
| #include "JSArray.h" |
| |
| #include "ArrayPrototype.h" |
| #include "ButterflyInlines.h" |
| #include "CachedCall.h" |
| #include "CopiedSpace.h" |
| #include "CopiedSpaceInlines.h" |
| #include "Error.h" |
| #include "Executable.h" |
| #include "GetterSetter.h" |
| #include "IndexingHeaderInlines.h" |
| #include "PropertyNameArray.h" |
| #include "Reject.h" |
| #include <wtf/AVLTree.h> |
| #include <wtf/Assertions.h> |
| #include <wtf/OwnPtr.h> |
| #include <Operations.h> |
| |
| using namespace std; |
| using namespace WTF; |
| |
| namespace JSC { |
| |
| ASSERT_HAS_TRIVIAL_DESTRUCTOR(JSArray); |
| |
| const ClassInfo JSArray::s_info = {"Array", JSNonFinalObject::s_classinfo(), 0, 0, CREATE_METHOD_TABLE(JSArray)}; |
| |
| Butterfly* createArrayButterflyInDictionaryIndexingMode(JSGlobalData& globalData, unsigned initialLength) |
| { |
| Butterfly* butterfly = Butterfly::create( |
| globalData, 0, 0, true, IndexingHeader(), ArrayStorage::sizeFor(0)); |
| ArrayStorage* storage = butterfly->arrayStorage(); |
| storage->setLength(initialLength); |
| storage->setVectorLength(0); |
| storage->m_indexBias = 0; |
| storage->m_sparseMap.clear(); |
| storage->m_numValuesInVector = 0; |
| return butterfly; |
| } |
| |
| void JSArray::setLengthWritable(ExecState* exec, bool writable) |
| { |
| ASSERT(isLengthWritable() || !writable); |
| if (!isLengthWritable() || writable) |
| return; |
| |
| enterDictionaryIndexingMode(exec->globalData()); |
| |
| SparseArrayValueMap* map = arrayStorage()->m_sparseMap.get(); |
| ASSERT(map); |
| map->setLengthIsReadOnly(); |
| } |
| |
| // Defined in ES5.1 15.4.5.1 |
| bool JSArray::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor, bool throwException) |
| { |
| JSArray* array = jsCast<JSArray*>(object); |
| |
| // 3. If P is "length", then |
| if (propertyName == exec->propertyNames().length) { |
| // All paths through length definition call the default [[DefineOwnProperty]], hence: |
| // from ES5.1 8.12.9 7.a. |
| if (descriptor.configurablePresent() && descriptor.configurable()) |
| return reject(exec, throwException, "Attempting to change configurable attribute of unconfigurable property."); |
| // from ES5.1 8.12.9 7.b. |
| if (descriptor.enumerablePresent() && descriptor.enumerable()) |
| return reject(exec, throwException, "Attempting to change enumerable attribute of unconfigurable property."); |
| |
| // a. If the [[Value]] field of Desc is absent, then |
| // a.i. Return the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", Desc, and Throw as arguments. |
| if (descriptor.isAccessorDescriptor()) |
| return reject(exec, throwException, "Attempting to change access mechanism for an unconfigurable property."); |
| // from ES5.1 8.12.9 10.a. |
| if (!array->isLengthWritable() && descriptor.writablePresent() && descriptor.writable()) |
| return reject(exec, throwException, "Attempting to change writable attribute of unconfigurable property."); |
| // This descriptor is either just making length read-only, or changing nothing! |
| if (!descriptor.value()) { |
| if (descriptor.writablePresent()) |
| array->setLengthWritable(exec, descriptor.writable()); |
| return true; |
| } |
| |
| // b. Let newLenDesc be a copy of Desc. |
| // c. Let newLen be ToUint32(Desc.[[Value]]). |
| unsigned newLen = descriptor.value().toUInt32(exec); |
| // d. If newLen is not equal to ToNumber( Desc.[[Value]]), throw a RangeError exception. |
| if (newLen != descriptor.value().toNumber(exec)) { |
| throwError(exec, createRangeError(exec, "Invalid array length")); |
| return false; |
| } |
| |
| // Based on SameValue check in 8.12.9, this is always okay. |
| if (newLen == array->length()) { |
| if (descriptor.writablePresent()) |
| array->setLengthWritable(exec, descriptor.writable()); |
| return true; |
| } |
| |
| // e. Set newLenDesc.[[Value] to newLen. |
| // f. If newLen >= oldLen, then |
| // f.i. Return the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", newLenDesc, and Throw as arguments. |
| // g. Reject if oldLenDesc.[[Writable]] is false. |
| if (!array->isLengthWritable()) |
| return reject(exec, throwException, "Attempting to change value of a readonly property."); |
| |
| // h. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true. |
| // i. Else, |
| // i.i. Need to defer setting the [[Writable]] attribute to false in case any elements cannot be deleted. |
| // i.ii. Let newWritable be false. |
| // i.iii. Set newLenDesc.[[Writable] to true. |
| // j. Let succeeded be the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", newLenDesc, and Throw as arguments. |
| // k. If succeeded is false, return false. |
| // l. While newLen < oldLen repeat, |
| // l.i. Set oldLen to oldLen - 1. |
| // l.ii. Let deleteSucceeded be the result of calling the [[Delete]] internal method of A passing ToString(oldLen) and false as arguments. |
| // l.iii. If deleteSucceeded is false, then |
| if (!array->setLength(exec, newLen, throwException)) { |
| // 1. Set newLenDesc.[[Value] to oldLen+1. |
| // 2. If newWritable is false, set newLenDesc.[[Writable] to false. |
| // 3. Call the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", newLenDesc, and false as arguments. |
| // 4. Reject. |
| if (descriptor.writablePresent()) |
| array->setLengthWritable(exec, descriptor.writable()); |
| return false; |
| } |
| |
| // m. If newWritable is false, then |
| // i. Call the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", |
| // Property Descriptor{[[Writable]]: false}, and false as arguments. This call will always |
| // return true. |
| if (descriptor.writablePresent()) |
| array->setLengthWritable(exec, descriptor.writable()); |
| // n. Return true. |
| return true; |
| } |
| |
| // 4. Else if P is an array index (15.4), then |
| // a. Let index be ToUint32(P). |
| unsigned index = propertyName.asIndex(); |
| if (index != PropertyName::NotAnIndex) { |
| // b. Reject if index >= oldLen and oldLenDesc.[[Writable]] is false. |
| if (index >= array->length() && !array->isLengthWritable()) |
| return reject(exec, throwException, "Attempting to define numeric property on array with non-writable length property."); |
| // c. Let succeeded be the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing P, Desc, and false as arguments. |
| // d. Reject if succeeded is false. |
| // e. If index >= oldLen |
| // e.i. Set oldLenDesc.[[Value]] to index + 1. |
| // e.ii. Call the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", oldLenDesc, and false as arguments. This call will always return true. |
| // f. Return true. |
| return array->defineOwnIndexedProperty(exec, index, descriptor, throwException); |
| } |
| |
| return array->JSObject::defineOwnNonIndexProperty(exec, propertyName, descriptor, throwException); |
| } |
| |
| bool JSArray::getOwnPropertySlot(JSCell* cell, ExecState* exec, PropertyName propertyName, PropertySlot& slot) |
| { |
| JSArray* thisObject = jsCast<JSArray*>(cell); |
| if (propertyName == exec->propertyNames().length) { |
| slot.setValue(jsNumber(thisObject->length())); |
| return true; |
| } |
| |
| return JSObject::getOwnPropertySlot(thisObject, exec, propertyName, slot); |
| } |
| |
| bool JSArray::getOwnPropertyDescriptor(JSObject* object, ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor) |
| { |
| JSArray* thisObject = jsCast<JSArray*>(object); |
| if (propertyName == exec->propertyNames().length) { |
| descriptor.setDescriptor(jsNumber(thisObject->length()), thisObject->isLengthWritable() ? DontDelete | DontEnum : DontDelete | DontEnum | ReadOnly); |
| return true; |
| } |
| |
| return JSObject::getOwnPropertyDescriptor(thisObject, exec, propertyName, descriptor); |
| } |
| |
| // ECMA 15.4.5.1 |
| void JSArray::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) |
| { |
| JSArray* thisObject = jsCast<JSArray*>(cell); |
| |
| if (propertyName == exec->propertyNames().length) { |
| unsigned newLength = value.toUInt32(exec); |
| if (value.toNumber(exec) != static_cast<double>(newLength)) { |
| throwError(exec, createRangeError(exec, ASCIILiteral("Invalid array length"))); |
| return; |
| } |
| thisObject->setLength(exec, newLength, slot.isStrictMode()); |
| return; |
| } |
| |
| JSObject::put(thisObject, exec, propertyName, value, slot); |
| } |
| |
| bool JSArray::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName) |
| { |
| JSArray* thisObject = jsCast<JSArray*>(cell); |
| |
| if (propertyName == exec->propertyNames().length) |
| return false; |
| |
| return JSObject::deleteProperty(thisObject, exec, propertyName); |
| } |
| |
| static int compareKeysForQSort(const void* a, const void* b) |
| { |
| unsigned da = *static_cast<const unsigned*>(a); |
| unsigned db = *static_cast<const unsigned*>(b); |
| return (da > db) - (da < db); |
| } |
| |
| void JSArray::getOwnNonIndexPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) |
| { |
| JSArray* thisObject = jsCast<JSArray*>(object); |
| |
| if (mode == IncludeDontEnumProperties) |
| propertyNames.add(exec->propertyNames().length); |
| |
| JSObject::getOwnNonIndexPropertyNames(thisObject, exec, propertyNames, mode); |
| } |
| |
| // This method makes room in the vector, but leaves the new space for count slots uncleared. |
| bool JSArray::unshiftCountSlowCase(JSGlobalData& globalData, bool addToFront, unsigned count) |
| { |
| ArrayStorage* storage = ensureArrayStorage(globalData); |
| Butterfly* butterfly = storage->butterfly(); |
| unsigned propertyCapacity = structure()->outOfLineCapacity(); |
| unsigned propertySize = structure()->outOfLineSize(); |
| |
| // If not, we should have handled this on the fast path. |
| ASSERT(!addToFront || count > storage->m_indexBias); |
| |
| // Step 1: |
| // Gather 4 key metrics: |
| // * usedVectorLength - how many entries are currently in the vector (conservative estimate - fewer may be in use in sparse vectors). |
| // * requiredVectorLength - how many entries are will there be in the vector, after allocating space for 'count' more. |
| // * currentCapacity - what is the current size of the vector, including any pre-capacity. |
| // * desiredCapacity - how large should we like to grow the vector to - based on 2x requiredVectorLength. |
| |
| unsigned length = storage->length(); |
| unsigned usedVectorLength = min(storage->vectorLength(), length); |
| ASSERT(usedVectorLength <= MAX_STORAGE_VECTOR_LENGTH); |
| // Check that required vector length is possible, in an overflow-safe fashion. |
| if (count > MAX_STORAGE_VECTOR_LENGTH - usedVectorLength) |
| return false; |
| unsigned requiredVectorLength = usedVectorLength + count; |
| ASSERT(requiredVectorLength <= MAX_STORAGE_VECTOR_LENGTH); |
| // The sum of m_vectorLength and m_indexBias will never exceed MAX_STORAGE_VECTOR_LENGTH. |
| ASSERT(storage->vectorLength() <= MAX_STORAGE_VECTOR_LENGTH && (MAX_STORAGE_VECTOR_LENGTH - storage->vectorLength()) >= storage->m_indexBias); |
| unsigned currentCapacity = storage->vectorLength() + storage->m_indexBias; |
| // The calculation of desiredCapacity won't overflow, due to the range of MAX_STORAGE_VECTOR_LENGTH. |
| unsigned desiredCapacity = min(MAX_STORAGE_VECTOR_LENGTH, max(BASE_VECTOR_LEN, requiredVectorLength) << 1); |
| |
| // Step 2: |
| // We're either going to choose to allocate a new ArrayStorage, or we're going to reuse the existing one. |
| |
| void* newAllocBase = 0; |
| unsigned newStorageCapacity; |
| // If the current storage array is sufficiently large (but not too large!) then just keep using it. |
| if (currentCapacity > desiredCapacity && isDenseEnoughForVector(currentCapacity, requiredVectorLength)) { |
| newAllocBase = butterfly->base(structure()); |
| newStorageCapacity = currentCapacity; |
| } else { |
| size_t newSize = Butterfly::totalSize(0, propertyCapacity, true, ArrayStorage::sizeFor(desiredCapacity)); |
| if (!globalData.heap.tryAllocateStorage(newSize, &newAllocBase)) |
| return false; |
| newStorageCapacity = desiredCapacity; |
| } |
| |
| // Step 3: |
| // Work out where we're going to move things to. |
| |
| // Determine how much of the vector to use as pre-capacity, and how much as post-capacity. |
| // If we're adding to the end, we'll add all the new space to the end. |
| // If the vector had no free post-capacity (length >= m_vectorLength), don't give it any. |
| // If it did, we calculate the amount that will remain based on an atomic decay - leave the |
| // vector with half the post-capacity it had previously. |
| unsigned postCapacity = 0; |
| if (!addToFront) |
| postCapacity = max(newStorageCapacity - requiredVectorLength, count); |
| else if (length < storage->vectorLength()) { |
| // Atomic decay, + the post-capacity cannot be greater than what is available. |
| postCapacity = min((storage->vectorLength() - length) >> 1, newStorageCapacity - requiredVectorLength); |
| // If we're moving contents within the same allocation, the post-capacity is being reduced. |
| ASSERT(newAllocBase != butterfly->base(structure()) || postCapacity < storage->vectorLength() - length); |
| } |
| |
| unsigned newVectorLength = requiredVectorLength + postCapacity; |
| unsigned newIndexBias = newStorageCapacity - newVectorLength; |
| |
| Butterfly* newButterfly = Butterfly::fromBase(newAllocBase, newIndexBias, propertyCapacity); |
| |
| if (addToFront) { |
| ASSERT(count + usedVectorLength <= newVectorLength); |
| memmove(newButterfly->arrayStorage()->m_vector + count, storage->m_vector, sizeof(JSValue) * usedVectorLength); |
| memmove(newButterfly->propertyStorage() - propertySize, butterfly->propertyStorage() - propertySize, sizeof(JSValue) * propertySize + sizeof(IndexingHeader) + ArrayStorage::sizeFor(0)); |
| } else if ((newAllocBase != butterfly->base(structure())) || (newIndexBias != storage->m_indexBias)) { |
| memmove(newButterfly->propertyStorage() - propertySize, butterfly->propertyStorage() - propertySize, sizeof(JSValue) * propertySize + sizeof(IndexingHeader) + ArrayStorage::sizeFor(0)); |
| memmove(newButterfly->arrayStorage()->m_vector, storage->m_vector, sizeof(JSValue) * usedVectorLength); |
| |
| WriteBarrier<Unknown>* newVector = newButterfly->arrayStorage()->m_vector; |
| for (unsigned i = requiredVectorLength; i < newVectorLength; i++) |
| newVector[i].clear(); |
| } |
| |
| newButterfly->arrayStorage()->setVectorLength(newVectorLength); |
| newButterfly->arrayStorage()->m_indexBias = newIndexBias; |
| |
| m_butterfly = newButterfly; |
| |
| return true; |
| } |
| |
| bool JSArray::setLengthWithArrayStorage(ExecState* exec, unsigned newLength, bool throwException, ArrayStorage* storage) |
| { |
| unsigned length = storage->length(); |
| |
| // If the length is read only then we enter sparse mode, so should enter the following 'if'. |
| ASSERT(isLengthWritable() || storage->m_sparseMap); |
| |
| if (SparseArrayValueMap* map = storage->m_sparseMap.get()) { |
| // Fail if the length is not writable. |
| if (map->lengthIsReadOnly()) |
| return reject(exec, throwException, GetStrictModeReadonlyPropertyWriteError()); |
| |
| if (newLength < length) { |
| // Copy any keys we might be interested in into a vector. |
| Vector<unsigned> keys; |
| keys.reserveCapacity(min(map->size(), static_cast<size_t>(length - newLength))); |
| SparseArrayValueMap::const_iterator end = map->end(); |
| for (SparseArrayValueMap::const_iterator it = map->begin(); it != end; ++it) { |
| unsigned index = static_cast<unsigned>(it->key); |
| if (index < length && index >= newLength) |
| keys.append(index); |
| } |
| |
| // Check if the array is in sparse mode. If so there may be non-configurable |
| // properties, so we have to perform deletion with caution, if not we can |
| // delete values in any order. |
| if (map->sparseMode()) { |
| qsort(keys.begin(), keys.size(), sizeof(unsigned), compareKeysForQSort); |
| unsigned i = keys.size(); |
| while (i) { |
| unsigned index = keys[--i]; |
| SparseArrayValueMap::iterator it = map->find(index); |
| ASSERT(it != map->notFound()); |
| if (it->value.attributes & DontDelete) { |
| storage->setLength(index + 1); |
| return reject(exec, throwException, "Unable to delete property."); |
| } |
| map->remove(it); |
| } |
| } else { |
| for (unsigned i = 0; i < keys.size(); ++i) |
| map->remove(keys[i]); |
| if (map->isEmpty()) |
| deallocateSparseIndexMap(); |
| } |
| } |
| } |
| |
| if (newLength < length) { |
| // Delete properties from the vector. |
| unsigned usedVectorLength = min(length, storage->vectorLength()); |
| for (unsigned i = newLength; i < usedVectorLength; ++i) { |
| WriteBarrier<Unknown>& valueSlot = storage->m_vector[i]; |
| bool hadValue = valueSlot; |
| valueSlot.clear(); |
| storage->m_numValuesInVector -= hadValue; |
| } |
| } |
| |
| storage->setLength(newLength); |
| |
| return true; |
| } |
| |
| bool JSArray::setLength(ExecState* exec, unsigned newLength, bool throwException) |
| { |
| switch (structure()->indexingType()) { |
| case ArrayClass: |
| if (!newLength) |
| return true; |
| if (newLength >= MIN_SPARSE_ARRAY_INDEX) { |
| return setLengthWithArrayStorage( |
| exec, newLength, throwException, |
| convertContiguousToArrayStorage(exec->globalData())); |
| } |
| createInitialUndecided(exec->globalData(), newLength); |
| return true; |
| |
| case ArrayWithUndecided: |
| case ArrayWithInt32: |
| case ArrayWithDouble: |
| case ArrayWithContiguous: |
| if (newLength == m_butterfly->publicLength()) |
| return true; |
| if (newLength >= MAX_ARRAY_INDEX // This case ensures that we can do fast push. |
| || (newLength >= MIN_SPARSE_ARRAY_INDEX |
| && !isDenseEnoughForVector(newLength, countElements()))) { |
| return setLengthWithArrayStorage( |
| exec, newLength, throwException, |
| ensureArrayStorage(exec->globalData())); |
| } |
| if (newLength > m_butterfly->publicLength()) { |
| ensureLength(exec->globalData(), newLength); |
| return true; |
| } |
| if (structure()->indexingType() == ArrayWithDouble) { |
| for (unsigned i = m_butterfly->publicLength(); i-- > newLength;) |
| m_butterfly->contiguousDouble()[i] = QNaN; |
| } else { |
| for (unsigned i = m_butterfly->publicLength(); i-- > newLength;) |
| m_butterfly->contiguous()[i].clear(); |
| } |
| m_butterfly->setPublicLength(newLength); |
| return true; |
| |
| case ArrayWithArrayStorage: |
| case ArrayWithSlowPutArrayStorage: |
| return setLengthWithArrayStorage(exec, newLength, throwException, arrayStorage()); |
| |
| default: |
| CRASH(); |
| return false; |
| } |
| } |
| |
| JSValue JSArray::pop(ExecState* exec) |
| { |
| switch (structure()->indexingType()) { |
| case ArrayClass: |
| return jsUndefined(); |
| |
| case ArrayWithUndecided: |
| if (!m_butterfly->publicLength()) |
| return jsUndefined(); |
| // We have nothing but holes. So, drop down to the slow version. |
| break; |
| |
| case ArrayWithInt32: |
| case ArrayWithContiguous: { |
| unsigned length = m_butterfly->publicLength(); |
| |
| if (!length--) |
| return jsUndefined(); |
| |
| ASSERT(length < m_butterfly->vectorLength()); |
| JSValue value = m_butterfly->contiguous()[length].get(); |
| if (value) { |
| m_butterfly->contiguous()[length].clear(); |
| m_butterfly->setPublicLength(length); |
| return value; |
| } |
| break; |
| } |
| |
| case ArrayWithDouble: { |
| unsigned length = m_butterfly->publicLength(); |
| |
| if (!length--) |
| return jsUndefined(); |
| |
| ASSERT(length < m_butterfly->vectorLength()); |
| double value = m_butterfly->contiguousDouble()[length]; |
| if (value == value) { |
| m_butterfly->contiguousDouble()[length] = QNaN; |
| m_butterfly->setPublicLength(length); |
| return JSValue(JSValue::EncodeAsDouble, value); |
| } |
| break; |
| } |
| |
| case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES: { |
| ArrayStorage* storage = m_butterfly->arrayStorage(); |
| |
| unsigned length = storage->length(); |
| if (!length) { |
| if (!isLengthWritable()) |
| throwTypeError(exec, GetStrictModeReadonlyPropertyWriteError()); |
| return jsUndefined(); |
| } |
| |
| unsigned index = length - 1; |
| if (index < storage->vectorLength()) { |
| WriteBarrier<Unknown>& valueSlot = storage->m_vector[index]; |
| if (valueSlot) { |
| --storage->m_numValuesInVector; |
| JSValue element = valueSlot.get(); |
| valueSlot.clear(); |
| |
| ASSERT(isLengthWritable()); |
| storage->setLength(index); |
| return element; |
| } |
| } |
| break; |
| } |
| |
| default: |
| CRASH(); |
| return JSValue(); |
| } |
| |
| unsigned index = getArrayLength() - 1; |
| // Let element be the result of calling the [[Get]] internal method of O with argument indx. |
| JSValue element = get(exec, index); |
| if (exec->hadException()) |
| return jsUndefined(); |
| // Call the [[Delete]] internal method of O with arguments indx and true. |
| if (!deletePropertyByIndex(this, exec, index)) { |
| throwTypeError(exec, "Unable to delete property."); |
| return jsUndefined(); |
| } |
| // Call the [[Put]] internal method of O with arguments "length", indx, and true. |
| setLength(exec, index, true); |
| // Return element. |
| return element; |
| } |
| |
| // Push & putIndex are almost identical, with two small differences. |
| // - we always are writing beyond the current array bounds, so it is always necessary to update m_length & m_numValuesInVector. |
| // - pushing to an array of length 2^32-1 stores the property, but throws a range error. |
| void JSArray::push(ExecState* exec, JSValue value) |
| { |
| switch (structure()->indexingType()) { |
| case ArrayClass: { |
| createInitialUndecided(exec->globalData(), 0); |
| // Fall through. |
| } |
| |
| case ArrayWithUndecided: { |
| convertUndecidedForValue(exec->globalData(), value); |
| push(exec, value); |
| return; |
| } |
| |
| case ArrayWithInt32: { |
| if (!value.isInt32()) { |
| convertInt32ForValue(exec->globalData(), value); |
| push(exec, value); |
| return; |
| } |
| |
| unsigned length = m_butterfly->publicLength(); |
| ASSERT(length <= m_butterfly->vectorLength()); |
| if (length < m_butterfly->vectorLength()) { |
| m_butterfly->contiguousInt32()[length].setWithoutWriteBarrier(value); |
| m_butterfly->setPublicLength(length + 1); |
| return; |
| } |
| |
| if (length > MAX_ARRAY_INDEX) { |
| methodTable()->putByIndex(this, exec, length, value, true); |
| if (!exec->hadException()) |
| throwError(exec, createRangeError(exec, "Invalid array length")); |
| return; |
| } |
| |
| putByIndexBeyondVectorLengthWithoutAttributes<Int32Shape>(exec, length, value); |
| return; |
| } |
| |
| case ArrayWithContiguous: { |
| unsigned length = m_butterfly->publicLength(); |
| ASSERT(length <= m_butterfly->vectorLength()); |
| if (length < m_butterfly->vectorLength()) { |
| m_butterfly->contiguous()[length].set(exec->globalData(), this, value); |
| m_butterfly->setPublicLength(length + 1); |
| return; |
| } |
| |
| if (length > MAX_ARRAY_INDEX) { |
| methodTable()->putByIndex(this, exec, length, value, true); |
| if (!exec->hadException()) |
| throwError(exec, createRangeError(exec, "Invalid array length")); |
| return; |
| } |
| |
| putByIndexBeyondVectorLengthWithoutAttributes<ContiguousShape>(exec, length, value); |
| return; |
| } |
| |
| case ArrayWithDouble: { |
| if (!value.isNumber()) { |
| convertDoubleToContiguous(exec->globalData()); |
| push(exec, value); |
| return; |
| } |
| double valueAsDouble = value.asNumber(); |
| if (valueAsDouble != valueAsDouble) { |
| convertDoubleToContiguous(exec->globalData()); |
| push(exec, value); |
| return; |
| } |
| |
| unsigned length = m_butterfly->publicLength(); |
| ASSERT(length <= m_butterfly->vectorLength()); |
| if (length < m_butterfly->vectorLength()) { |
| m_butterfly->contiguousDouble()[length] = valueAsDouble; |
| m_butterfly->setPublicLength(length + 1); |
| return; |
| } |
| |
| if (length > MAX_ARRAY_INDEX) { |
| methodTable()->putByIndex(this, exec, length, value, true); |
| if (!exec->hadException()) |
| throwError(exec, createRangeError(exec, "Invalid array length")); |
| return; |
| } |
| |
| putByIndexBeyondVectorLengthWithoutAttributes<DoubleShape>(exec, length, value); |
| break; |
| } |
| |
| case ArrayWithSlowPutArrayStorage: { |
| unsigned oldLength = length(); |
| if (attemptToInterceptPutByIndexOnHole(exec, oldLength, value, true)) { |
| if (!exec->hadException() && oldLength < 0xFFFFFFFFu) |
| setLength(exec, oldLength + 1, true); |
| return; |
| } |
| // Fall through. |
| } |
| |
| case ArrayWithArrayStorage: { |
| ArrayStorage* storage = m_butterfly->arrayStorage(); |
| |
| // Fast case - push within vector, always update m_length & m_numValuesInVector. |
| unsigned length = storage->length(); |
| if (length < storage->vectorLength()) { |
| storage->m_vector[length].set(exec->globalData(), this, value); |
| storage->setLength(length + 1); |
| ++storage->m_numValuesInVector; |
| return; |
| } |
| |
| // Pushing to an array of invalid length (2^31-1) stores the property, but throws a range error. |
| if (storage->length() > MAX_ARRAY_INDEX) { |
| methodTable()->putByIndex(this, exec, storage->length(), value, true); |
| // Per ES5.1 15.4.4.7 step 6 & 15.4.5.1 step 3.d. |
| if (!exec->hadException()) |
| throwError(exec, createRangeError(exec, "Invalid array length")); |
| return; |
| } |
| |
| // Handled the same as putIndex. |
| putByIndexBeyondVectorLengthWithArrayStorage(exec, storage->length(), value, true, storage); |
| break; |
| } |
| |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| bool JSArray::shiftCountWithArrayStorage(unsigned startIndex, unsigned count, ArrayStorage* storage) |
| { |
| unsigned oldLength = storage->length(); |
| ASSERT(count <= oldLength); |
| |
| // If the array contains holes or is otherwise in an abnormal state, |
| // use the generic algorithm in ArrayPrototype. |
| if (oldLength != storage->m_numValuesInVector || inSparseIndexingMode() || shouldUseSlowPut(structure()->indexingType())) |
| return false; |
| |
| if (!oldLength) |
| return true; |
| |
| unsigned length = oldLength - count; |
| |
| storage->m_numValuesInVector -= count; |
| storage->setLength(length); |
| |
| unsigned vectorLength = storage->vectorLength(); |
| if (!vectorLength) |
| return true; |
| |
| if (startIndex >= vectorLength) |
| return true; |
| |
| if (startIndex + count > vectorLength) |
| count = vectorLength - startIndex; |
| |
| unsigned usedVectorLength = min(vectorLength, oldLength); |
| |
| vectorLength -= count; |
| storage->setVectorLength(vectorLength); |
| |
| if (vectorLength) { |
| if (startIndex < usedVectorLength - (startIndex + count)) { |
| if (startIndex) { |
| memmove( |
| storage->m_vector + count, |
| storage->m_vector, |
| sizeof(JSValue) * startIndex); |
| } |
| m_butterfly = m_butterfly->shift(structure(), count); |
| storage = m_butterfly->arrayStorage(); |
| storage->m_indexBias += count; |
| } else { |
| memmove( |
| storage->m_vector + startIndex, |
| storage->m_vector + startIndex + count, |
| sizeof(JSValue) * (usedVectorLength - (startIndex + count))); |
| for (unsigned i = usedVectorLength - count; i < usedVectorLength; ++i) |
| storage->m_vector[i].clear(); |
| } |
| } |
| return true; |
| } |
| |
| bool JSArray::shiftCountWithAnyIndexingType(ExecState* exec, unsigned startIndex, unsigned count) |
| { |
| ASSERT(count > 0); |
| |
| switch (structure()->indexingType()) { |
| case ArrayClass: |
| return true; |
| |
| case ArrayWithUndecided: |
| // Don't handle this because it's confusing and it shouldn't come up. |
| return false; |
| |
| case ArrayWithInt32: |
| case ArrayWithContiguous: { |
| unsigned oldLength = m_butterfly->publicLength(); |
| ASSERT(count <= oldLength); |
| |
| // We may have to walk the entire array to do the shift. We're willing to do |
| // so only if it's not horribly slow. |
| if (oldLength - (startIndex + count) >= MIN_SPARSE_ARRAY_INDEX) |
| return shiftCountWithArrayStorage(startIndex, count, ensureArrayStorage(exec->globalData())); |
| |
| unsigned end = oldLength - count; |
| for (unsigned i = startIndex; i < end; ++i) { |
| // Storing to a hole is fine since we're still having a good time. But reading |
| // from a hole is totally not fine, since we might have to read from the proto |
| // chain. |
| JSValue v = m_butterfly->contiguous()[i + count].get(); |
| if (UNLIKELY(!v)) { |
| // The purpose of this path is to ensure that we don't make the same |
| // mistake in the future: shiftCountWithArrayStorage() can't do anything |
| // about holes (at least for now), but it can detect them quickly. So |
| // we convert to array storage and then allow the array storage path to |
| // figure it out. |
| return shiftCountWithArrayStorage(startIndex, count, ensureArrayStorage(exec->globalData())); |
| } |
| // No need for a barrier since we're just moving data around in the same vector. |
| // This is in line with our standing assumption that we won't have a deletion |
| // barrier. |
| m_butterfly->contiguous()[i].setWithoutWriteBarrier(v); |
| } |
| for (unsigned i = end; i < oldLength; ++i) |
| m_butterfly->contiguous()[i].clear(); |
| |
| m_butterfly->setPublicLength(oldLength - count); |
| return true; |
| } |
| |
| case ArrayWithDouble: { |
| unsigned oldLength = m_butterfly->publicLength(); |
| ASSERT(count <= oldLength); |
| |
| // We may have to walk the entire array to do the shift. We're willing to do |
| // so only if it's not horribly slow. |
| if (oldLength - (startIndex + count) >= MIN_SPARSE_ARRAY_INDEX) |
| return shiftCountWithArrayStorage(startIndex, count, ensureArrayStorage(exec->globalData())); |
| |
| unsigned end = oldLength - count; |
| for (unsigned i = startIndex; i < end; ++i) { |
| // Storing to a hole is fine since we're still having a good time. But reading |
| // from a hole is totally not fine, since we might have to read from the proto |
| // chain. |
| double v = m_butterfly->contiguousDouble()[i + count]; |
| if (UNLIKELY(v != v)) { |
| // The purpose of this path is to ensure that we don't make the same |
| // mistake in the future: shiftCountWithArrayStorage() can't do anything |
| // about holes (at least for now), but it can detect them quickly. So |
| // we convert to array storage and then allow the array storage path to |
| // figure it out. |
| return shiftCountWithArrayStorage(startIndex, count, ensureArrayStorage(exec->globalData())); |
| } |
| // No need for a barrier since we're just moving data around in the same vector. |
| // This is in line with our standing assumption that we won't have a deletion |
| // barrier. |
| m_butterfly->contiguousDouble()[i] = v; |
| } |
| for (unsigned i = end; i < oldLength; ++i) |
| m_butterfly->contiguousDouble()[i] = QNaN; |
| |
| m_butterfly->setPublicLength(oldLength - count); |
| return true; |
| } |
| |
| case ArrayWithArrayStorage: |
| case ArrayWithSlowPutArrayStorage: |
| return shiftCountWithArrayStorage(startIndex, count, arrayStorage()); |
| |
| default: |
| CRASH(); |
| return false; |
| } |
| } |
| |
| // Returns true if the unshift can be handled, false to fallback. |
| bool JSArray::unshiftCountWithArrayStorage(ExecState* exec, unsigned startIndex, unsigned count, ArrayStorage* storage) |
| { |
| unsigned length = storage->length(); |
| |
| ASSERT(startIndex <= length); |
| |
| // If the array contains holes or is otherwise in an abnormal state, |
| // use the generic algorithm in ArrayPrototype. |
| if (length != storage->m_numValuesInVector || storage->inSparseMode() || shouldUseSlowPut(structure()->indexingType())) |
| return false; |
| |
| bool moveFront = !startIndex || startIndex < length / 2; |
| |
| unsigned vectorLength = storage->vectorLength(); |
| |
| if (moveFront && storage->m_indexBias >= count) { |
| m_butterfly = storage->butterfly()->unshift(structure(), count); |
| storage = m_butterfly->arrayStorage(); |
| storage->m_indexBias -= count; |
| storage->setVectorLength(vectorLength + count); |
| } else if (!moveFront && vectorLength - length >= count) |
| storage = storage->butterfly()->arrayStorage(); |
| else if (unshiftCountSlowCase(exec->globalData(), moveFront, count)) |
| storage = arrayStorage(); |
| else { |
| throwOutOfMemoryError(exec); |
| return true; |
| } |
| |
| WriteBarrier<Unknown>* vector = storage->m_vector; |
| |
| if (startIndex) { |
| if (moveFront) |
| memmove(vector, vector + count, startIndex * sizeof(JSValue)); |
| else if (length - startIndex) |
| memmove(vector + startIndex + count, vector + startIndex, (length - startIndex) * sizeof(JSValue)); |
| } |
| |
| for (unsigned i = 0; i < count; i++) |
| vector[i + startIndex].clear(); |
| return true; |
| } |
| |
| bool JSArray::unshiftCountWithAnyIndexingType(ExecState* exec, unsigned startIndex, unsigned count) |
| { |
| switch (structure()->indexingType()) { |
| case ArrayClass: |
| case ArrayWithUndecided: |
| // We could handle this. But it shouldn't ever come up, so we won't. |
| return false; |
| |
| case ArrayWithInt32: |
| case ArrayWithContiguous: { |
| unsigned oldLength = m_butterfly->publicLength(); |
| |
| // We may have to walk the entire array to do the unshift. We're willing to do so |
| // only if it's not horribly slow. |
| if (oldLength - startIndex >= MIN_SPARSE_ARRAY_INDEX) |
| return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(exec->globalData())); |
| |
| ensureLength(exec->globalData(), oldLength + count); |
| |
| for (unsigned i = oldLength; i-- > startIndex;) { |
| JSValue v = m_butterfly->contiguous()[i].get(); |
| if (UNLIKELY(!v)) |
| return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(exec->globalData())); |
| m_butterfly->contiguous()[i + count].setWithoutWriteBarrier(v); |
| } |
| |
| // NOTE: we're leaving being garbage in the part of the array that we shifted out |
| // of. This is fine because the caller is required to store over that area, and |
| // in contiguous mode storing into a hole is guaranteed to behave exactly the same |
| // as storing over an existing element. |
| |
| return true; |
| } |
| |
| case ArrayWithDouble: { |
| unsigned oldLength = m_butterfly->publicLength(); |
| |
| // We may have to walk the entire array to do the unshift. We're willing to do so |
| // only if it's not horribly slow. |
| if (oldLength - startIndex >= MIN_SPARSE_ARRAY_INDEX) |
| return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(exec->globalData())); |
| |
| ensureLength(exec->globalData(), oldLength + count); |
| |
| for (unsigned i = oldLength; i-- > startIndex;) { |
| double v = m_butterfly->contiguousDouble()[i]; |
| if (UNLIKELY(v != v)) |
| return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(exec->globalData())); |
| m_butterfly->contiguousDouble()[i + count] = v; |
| } |
| |
| // NOTE: we're leaving being garbage in the part of the array that we shifted out |
| // of. This is fine because the caller is required to store over that area, and |
| // in contiguous mode storing into a hole is guaranteed to behave exactly the same |
| // as storing over an existing element. |
| |
| return true; |
| } |
| |
| case ArrayWithArrayStorage: |
| case ArrayWithSlowPutArrayStorage: |
| return unshiftCountWithArrayStorage(exec, startIndex, count, arrayStorage()); |
| |
| default: |
| CRASH(); |
| return false; |
| } |
| } |
| |
| static int compareNumbersForQSortWithInt32(const void* a, const void* b) |
| { |
| int32_t ia = static_cast<const JSValue*>(a)->asInt32(); |
| int32_t ib = static_cast<const JSValue*>(b)->asInt32(); |
| return ia - ib; |
| } |
| |
| static int compareNumbersForQSortWithDouble(const void* a, const void* b) |
| { |
| double da = *static_cast<const double*>(a); |
| double db = *static_cast<const double*>(b); |
| return (da > db) - (da < db); |
| } |
| |
| static int compareNumbersForQSort(const void* a, const void* b) |
| { |
| double da = static_cast<const JSValue*>(a)->asNumber(); |
| double db = static_cast<const JSValue*>(b)->asNumber(); |
| return (da > db) - (da < db); |
| } |
| |
| static int compareByStringPairForQSort(const void* a, const void* b) |
| { |
| const ValueStringPair* va = static_cast<const ValueStringPair*>(a); |
| const ValueStringPair* vb = static_cast<const ValueStringPair*>(b); |
| return codePointCompare(va->second, vb->second); |
| } |
| |
| template<IndexingType indexingType> |
| void JSArray::sortNumericVector(ExecState* exec, JSValue compareFunction, CallType callType, const CallData& callData) |
| { |
| ASSERT(indexingType == ArrayWithInt32 || indexingType == ArrayWithDouble || indexingType == ArrayWithContiguous || indexingType == ArrayWithArrayStorage); |
| |
| unsigned lengthNotIncludingUndefined; |
| unsigned newRelevantLength; |
| compactForSorting<indexingType>( |
| lengthNotIncludingUndefined, |
| newRelevantLength); |
| |
| WriteBarrier<Unknown>* data = indexingData<indexingType>(); |
| |
| if (indexingType == ArrayWithArrayStorage && arrayStorage()->m_sparseMap.get()) { |
| throwOutOfMemoryError(exec); |
| return; |
| } |
| |
| if (!lengthNotIncludingUndefined) |
| return; |
| |
| bool allValuesAreNumbers = true; |
| switch (indexingType) { |
| case ArrayWithInt32: |
| case ArrayWithDouble: |
| break; |
| |
| default: |
| for (size_t i = 0; i < newRelevantLength; ++i) { |
| if (!data[i].isNumber()) { |
| allValuesAreNumbers = false; |
| break; |
| } |
| } |
| break; |
| } |
| |
| if (!allValuesAreNumbers) |
| return sort(exec, compareFunction, callType, callData); |
| |
| // For numeric comparison, which is fast, qsort is faster than mergesort. We |
| // also don't require mergesort's stability, since there's no user visible |
| // side-effect from swapping the order of equal primitive values. |
| int (*compare)(const void*, const void*); |
| switch (indexingType) { |
| case ArrayWithInt32: |
| compare = compareNumbersForQSortWithInt32; |
| break; |
| |
| case ArrayWithDouble: |
| compare = compareNumbersForQSortWithDouble; |
| ASSERT(sizeof(WriteBarrier<Unknown>) == sizeof(double)); |
| break; |
| |
| default: |
| compare = compareNumbersForQSort; |
| break; |
| } |
| |
| qsort(data, newRelevantLength, sizeof(WriteBarrier<Unknown>), compare); |
| return; |
| } |
| |
| void JSArray::sortNumeric(ExecState* exec, JSValue compareFunction, CallType callType, const CallData& callData) |
| { |
| ASSERT(!inSparseIndexingMode()); |
| |
| switch (structure()->indexingType()) { |
| case ArrayClass: |
| return; |
| |
| case ArrayWithInt32: |
| sortNumericVector<ArrayWithInt32>(exec, compareFunction, callType, callData); |
| break; |
| |
| case ArrayWithDouble: |
| sortNumericVector<ArrayWithDouble>(exec, compareFunction, callType, callData); |
| break; |
| |
| case ArrayWithContiguous: |
| sortNumericVector<ArrayWithContiguous>(exec, compareFunction, callType, callData); |
| return; |
| |
| case ArrayWithArrayStorage: |
| sortNumericVector<ArrayWithArrayStorage>(exec, compareFunction, callType, callData); |
| return; |
| |
| default: |
| CRASH(); |
| return; |
| } |
| } |
| |
| template<IndexingType indexingType> |
| void JSArray::sortCompactedVector(ExecState* exec, void* begin, unsigned relevantLength) |
| { |
| if (!relevantLength) |
| return; |
| |
| JSGlobalData& globalData = exec->globalData(); |
| |
| // Converting JavaScript values to strings can be expensive, so we do it once up front and sort based on that. |
| // This is a considerable improvement over doing it twice per comparison, though it requires a large temporary |
| // buffer. Besides, this protects us from crashing if some objects have custom toString methods that return |
| // random or otherwise changing results, effectively making compare function inconsistent. |
| |
| Vector<ValueStringPair> values(relevantLength); |
| if (!values.begin()) { |
| throwOutOfMemoryError(exec); |
| return; |
| } |
| |
| Heap::heap(this)->pushTempSortVector(&values); |
| |
| bool isSortingPrimitiveValues = true; |
| switch (indexingType) { |
| case ArrayWithInt32: |
| for (size_t i = 0; i < relevantLength; i++) { |
| JSValue value = static_cast<WriteBarrier<Unknown>*>(begin)[i].get(); |
| ASSERT(value.isInt32()); |
| values[i].first = value; |
| } |
| break; |
| |
| case ArrayWithDouble: |
| for (size_t i = 0; i < relevantLength; i++) { |
| double value = static_cast<double*>(begin)[i]; |
| ASSERT(value == value); |
| values[i].first = JSValue(JSValue::EncodeAsDouble, value); |
| } |
| break; |
| |
| default: |
| for (size_t i = 0; i < relevantLength; i++) { |
| JSValue value = static_cast<WriteBarrier<Unknown>*>(begin)[i].get(); |
| ASSERT(!value.isUndefined()); |
| values[i].first = value; |
| isSortingPrimitiveValues = isSortingPrimitiveValues && value.isPrimitive(); |
| } |
| break; |
| } |
| |
| // FIXME: The following loop continues to call toString on subsequent values even after |
| // a toString call raises an exception. |
| |
| for (size_t i = 0; i < relevantLength; i++) |
| values[i].second = values[i].first.toWTFStringInline(exec); |
| |
| if (exec->hadException()) { |
| Heap::heap(this)->popTempSortVector(&values); |
| return; |
| } |
| |
| // FIXME: Since we sort by string value, a fast algorithm might be to use a radix sort. That would be O(N) rather |
| // than O(N log N). |
| |
| #if HAVE(MERGESORT) |
| if (isSortingPrimitiveValues) |
| qsort(values.begin(), values.size(), sizeof(ValueStringPair), compareByStringPairForQSort); |
| else |
| mergesort(values.begin(), values.size(), sizeof(ValueStringPair), compareByStringPairForQSort); |
| #else |
| // FIXME: The qsort library function is likely to not be a stable sort. |
| // ECMAScript-262 does not specify a stable sort, but in practice, browsers perform a stable sort. |
| qsort(values.begin(), values.size(), sizeof(ValueStringPair), compareByStringPairForQSort); |
| #endif |
| |
| // If the toString function changed the length of the array or vector storage, |
| // increase the length to handle the orignal number of actual values. |
| switch (indexingType) { |
| case ArrayWithInt32: |
| case ArrayWithDouble: |
| case ArrayWithContiguous: |
| ensureLength(globalData, relevantLength); |
| break; |
| |
| case ArrayWithArrayStorage: |
| if (arrayStorage()->vectorLength() < relevantLength) { |
| increaseVectorLength(exec->globalData(), relevantLength); |
| begin = arrayStorage()->m_vector; |
| } |
| if (arrayStorage()->length() < relevantLength) |
| arrayStorage()->setLength(relevantLength); |
| break; |
| |
| default: |
| CRASH(); |
| } |
| |
| for (size_t i = 0; i < relevantLength; i++) { |
| if (indexingType == ArrayWithDouble) |
| static_cast<double*>(begin)[i] = values[i].first.asNumber(); |
| else |
| static_cast<WriteBarrier<Unknown>*>(begin)[i].set(globalData, this, values[i].first); |
| } |
| |
| Heap::heap(this)->popTempSortVector(&values); |
| } |
| |
| void JSArray::sort(ExecState* exec) |
| { |
| ASSERT(!inSparseIndexingMode()); |
| |
| switch (structure()->indexingType()) { |
| case ArrayClass: |
| case ArrayWithUndecided: |
| return; |
| |
| case ArrayWithInt32: { |
| unsigned lengthNotIncludingUndefined; |
| unsigned newRelevantLength; |
| compactForSorting<ArrayWithInt32>( |
| lengthNotIncludingUndefined, newRelevantLength); |
| |
| sortCompactedVector<ArrayWithInt32>( |
| exec, m_butterfly->contiguousInt32(), lengthNotIncludingUndefined); |
| return; |
| } |
| |
| case ArrayWithDouble: { |
| unsigned lengthNotIncludingUndefined; |
| unsigned newRelevantLength; |
| compactForSorting<ArrayWithDouble>( |
| lengthNotIncludingUndefined, newRelevantLength); |
| |
| sortCompactedVector<ArrayWithDouble>( |
| exec, m_butterfly->contiguousDouble(), lengthNotIncludingUndefined); |
| return; |
| } |
| |
| case ArrayWithContiguous: { |
| unsigned lengthNotIncludingUndefined; |
| unsigned newRelevantLength; |
| compactForSorting<ArrayWithContiguous>( |
| lengthNotIncludingUndefined, newRelevantLength); |
| |
| sortCompactedVector<ArrayWithContiguous>( |
| exec, m_butterfly->contiguous(), lengthNotIncludingUndefined); |
| return; |
| } |
| |
| case ArrayWithArrayStorage: { |
| unsigned lengthNotIncludingUndefined; |
| unsigned newRelevantLength; |
| compactForSorting<ArrayWithArrayStorage>( |
| lengthNotIncludingUndefined, newRelevantLength); |
| ArrayStorage* storage = m_butterfly->arrayStorage(); |
| ASSERT(!storage->m_sparseMap); |
| |
| sortCompactedVector<ArrayWithArrayStorage>( |
| exec, storage->m_vector, lengthNotIncludingUndefined); |
| return; |
| } |
| |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| struct AVLTreeNodeForArrayCompare { |
| JSValue value; |
| |
| // Child pointers. The high bit of gt is robbed and used as the |
| // balance factor sign. The high bit of lt is robbed and used as |
| // the magnitude of the balance factor. |
| int32_t gt; |
| int32_t lt; |
| }; |
| |
| struct AVLTreeAbstractorForArrayCompare { |
| typedef int32_t handle; // Handle is an index into m_nodes vector. |
| typedef JSValue key; |
| typedef int32_t size; |
| |
| Vector<AVLTreeNodeForArrayCompare> m_nodes; |
| ExecState* m_exec; |
| JSValue m_compareFunction; |
| CallType m_compareCallType; |
| const CallData* m_compareCallData; |
| OwnPtr<CachedCall> m_cachedCall; |
| |
| handle get_less(handle h) { return m_nodes[h].lt & 0x7FFFFFFF; } |
| void set_less(handle h, handle lh) { m_nodes[h].lt &= 0x80000000; m_nodes[h].lt |= lh; } |
| handle get_greater(handle h) { return m_nodes[h].gt & 0x7FFFFFFF; } |
| void set_greater(handle h, handle gh) { m_nodes[h].gt &= 0x80000000; m_nodes[h].gt |= gh; } |
| |
| int get_balance_factor(handle h) |
| { |
| if (m_nodes[h].gt & 0x80000000) |
| return -1; |
| return static_cast<unsigned>(m_nodes[h].lt) >> 31; |
| } |
| |
| void set_balance_factor(handle h, int bf) |
| { |
| if (bf == 0) { |
| m_nodes[h].lt &= 0x7FFFFFFF; |
| m_nodes[h].gt &= 0x7FFFFFFF; |
| } else { |
| m_nodes[h].lt |= 0x80000000; |
| if (bf < 0) |
| m_nodes[h].gt |= 0x80000000; |
| else |
| m_nodes[h].gt &= 0x7FFFFFFF; |
| } |
| } |
| |
| int compare_key_key(key va, key vb) |
| { |
| ASSERT(!va.isUndefined()); |
| ASSERT(!vb.isUndefined()); |
| |
| if (m_exec->hadException()) |
| return 1; |
| |
| double compareResult; |
| if (m_cachedCall) { |
| m_cachedCall->setThis(jsUndefined()); |
| m_cachedCall->setArgument(0, va); |
| m_cachedCall->setArgument(1, vb); |
| compareResult = m_cachedCall->call().toNumber(m_cachedCall->newCallFrame(m_exec)); |
| } else { |
| MarkedArgumentBuffer arguments; |
| arguments.append(va); |
| arguments.append(vb); |
| compareResult = call(m_exec, m_compareFunction, m_compareCallType, *m_compareCallData, jsUndefined(), arguments).toNumber(m_exec); |
| } |
| return (compareResult < 0) ? -1 : 1; // Not passing equality through, because we need to store all values, even if equivalent. |
| } |
| |
| int compare_key_node(key k, handle h) { return compare_key_key(k, m_nodes[h].value); } |
| int compare_node_node(handle h1, handle h2) { return compare_key_key(m_nodes[h1].value, m_nodes[h2].value); } |
| |
| static handle null() { return 0x7FFFFFFF; } |
| }; |
| |
| template<IndexingType indexingType> |
| void JSArray::sortVector(ExecState* exec, JSValue compareFunction, CallType callType, const CallData& callData) |
| { |
| ASSERT(!inSparseIndexingMode()); |
| ASSERT(indexingType == structure()->indexingType()); |
| |
| // FIXME: This ignores exceptions raised in the compare function or in toNumber. |
| |
| // The maximum tree depth is compiled in - but the caller is clearly up to no good |
| // if a larger array is passed. |
| ASSERT(m_butterfly->publicLength() <= static_cast<unsigned>(std::numeric_limits<int>::max())); |
| if (m_butterfly->publicLength() > static_cast<unsigned>(std::numeric_limits<int>::max())) |
| return; |
| |
| unsigned usedVectorLength = relevantLength<indexingType>(); |
| unsigned nodeCount = usedVectorLength; |
| |
| if (!nodeCount) |
| return; |
| |
| AVLTree<AVLTreeAbstractorForArrayCompare, 44> tree; // Depth 44 is enough for 2^31 items |
| tree.abstractor().m_exec = exec; |
| tree.abstractor().m_compareFunction = compareFunction; |
| tree.abstractor().m_compareCallType = callType; |
| tree.abstractor().m_compareCallData = &callData; |
| tree.abstractor().m_nodes.grow(nodeCount); |
| |
| if (callType == CallTypeJS) |
| tree.abstractor().m_cachedCall = adoptPtr(new CachedCall(exec, jsCast<JSFunction*>(compareFunction), 2)); |
| |
| if (!tree.abstractor().m_nodes.begin()) { |
| throwOutOfMemoryError(exec); |
| return; |
| } |
| |
| // FIXME: If the compare function modifies the array, the vector, map, etc. could be modified |
| // right out from under us while we're building the tree here. |
| |
| unsigned numDefined = 0; |
| unsigned numUndefined = 0; |
| |
| // Iterate over the array, ignoring missing values, counting undefined ones, and inserting all other ones into the tree. |
| for (; numDefined < usedVectorLength; ++numDefined) { |
| if (numDefined > m_butterfly->vectorLength()) |
| break; |
| JSValue v = getHolyIndexQuickly(numDefined); |
| if (!v || v.isUndefined()) |
| break; |
| tree.abstractor().m_nodes[numDefined].value = v; |
| tree.insert(numDefined); |
| } |
| for (unsigned i = numDefined; i < usedVectorLength; ++i) { |
| if (i > m_butterfly->vectorLength()) |
| break; |
| JSValue v = getHolyIndexQuickly(i); |
| if (v) { |
| if (v.isUndefined()) |
| ++numUndefined; |
| else { |
| tree.abstractor().m_nodes[numDefined].value = v; |
| tree.insert(numDefined); |
| ++numDefined; |
| } |
| } |
| } |
| |
| unsigned newUsedVectorLength = numDefined + numUndefined; |
| |
| // The array size may have changed. Figure out the new bounds. |
| unsigned newestUsedVectorLength = currentRelevantLength(); |
| |
| unsigned elementsToExtractThreshold = min(min(newestUsedVectorLength, numDefined), static_cast<unsigned>(tree.abstractor().m_nodes.size())); |
| unsigned undefinedElementsThreshold = min(newestUsedVectorLength, newUsedVectorLength); |
| unsigned clearElementsThreshold = min(newestUsedVectorLength, usedVectorLength); |
| |
| // Copy the values back into m_storage. |
| AVLTree<AVLTreeAbstractorForArrayCompare, 44>::Iterator iter; |
| iter.start_iter_least(tree); |
| JSGlobalData& globalData = exec->globalData(); |
| for (unsigned i = 0; i < elementsToExtractThreshold; ++i) { |
| if (structure()->indexingType() == ArrayWithDouble) |
| butterfly()->contiguousDouble()[i] = tree.abstractor().m_nodes[*iter].value.asNumber(); |
| else |
| currentIndexingData()[i].set(globalData, this, tree.abstractor().m_nodes[*iter].value); |
| ++iter; |
| } |
| // Put undefined values back in. |
| switch (structure()->indexingType()) { |
| case ArrayWithInt32: |
| case ArrayWithDouble: |
| ASSERT(elementsToExtractThreshold == undefinedElementsThreshold); |
| break; |
| |
| default: |
| for (unsigned i = elementsToExtractThreshold; i < undefinedElementsThreshold; ++i) |
| currentIndexingData()[i].setUndefined(); |
| } |
| |
| // Ensure that unused values in the vector are zeroed out. |
| for (unsigned i = undefinedElementsThreshold; i < clearElementsThreshold; ++i) { |
| if (structure()->indexingType() == ArrayWithDouble) |
| butterfly()->contiguousDouble()[i] = QNaN; |
| else |
| currentIndexingData()[i].clear(); |
| } |
| |
| if (hasArrayStorage(structure()->indexingType())) |
| arrayStorage()->m_numValuesInVector = newUsedVectorLength; |
| } |
| |
| void JSArray::sort(ExecState* exec, JSValue compareFunction, CallType callType, const CallData& callData) |
| { |
| ASSERT(!inSparseIndexingMode()); |
| |
| switch (structure()->indexingType()) { |
| case ArrayClass: |
| case ArrayWithUndecided: |
| return; |
| |
| case ArrayWithInt32: |
| sortVector<ArrayWithInt32>(exec, compareFunction, callType, callData); |
| return; |
| |
| case ArrayWithDouble: |
| sortVector<ArrayWithDouble>(exec, compareFunction, callType, callData); |
| return; |
| |
| case ArrayWithContiguous: |
| sortVector<ArrayWithContiguous>(exec, compareFunction, callType, callData); |
| return; |
| |
| case ArrayWithArrayStorage: |
| sortVector<ArrayWithArrayStorage>(exec, compareFunction, callType, callData); |
| return; |
| |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| void JSArray::fillArgList(ExecState* exec, MarkedArgumentBuffer& args) |
| { |
| unsigned i = 0; |
| unsigned vectorEnd; |
| WriteBarrier<Unknown>* vector; |
| |
| switch (structure()->indexingType()) { |
| case ArrayClass: |
| return; |
| |
| case ArrayWithUndecided: { |
| vector = 0; |
| vectorEnd = 0; |
| break; |
| } |
| |
| case ArrayWithInt32: |
| case ArrayWithContiguous: { |
| vectorEnd = m_butterfly->publicLength(); |
| vector = m_butterfly->contiguous(); |
| break; |
| } |
| |
| case ArrayWithDouble: { |
| vector = 0; |
| vectorEnd = 0; |
| for (; i < m_butterfly->publicLength(); ++i) { |
| double v = butterfly()->contiguousDouble()[i]; |
| if (v != v) |
| break; |
| args.append(JSValue(JSValue::EncodeAsDouble, v)); |
| } |
| break; |
| } |
| |
| case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES: { |
| ArrayStorage* storage = m_butterfly->arrayStorage(); |
| |
| vector = storage->m_vector; |
| vectorEnd = min(storage->length(), storage->vectorLength()); |
| break; |
| } |
| |
| default: |
| CRASH(); |
| vector = 0; |
| vectorEnd = 0; |
| break; |
| } |
| |
| for (; i < vectorEnd; ++i) { |
| WriteBarrier<Unknown>& v = vector[i]; |
| if (!v) |
| break; |
| args.append(v.get()); |
| } |
| |
| for (; i < length(); ++i) |
| args.append(get(exec, i)); |
| } |
| |
| void JSArray::copyToArguments(ExecState* exec, CallFrame* callFrame, uint32_t length) |
| { |
| unsigned i = 0; |
| WriteBarrier<Unknown>* vector; |
| unsigned vectorEnd; |
| |
| ASSERT(length == this->length()); |
| switch (structure()->indexingType()) { |
| case ArrayClass: |
| return; |
| |
| case ArrayWithUndecided: { |
| vector = 0; |
| vectorEnd = 0; |
| break; |
| } |
| |
| case ArrayWithInt32: |
| case ArrayWithContiguous: { |
| vector = m_butterfly->contiguous(); |
| vectorEnd = m_butterfly->publicLength(); |
| break; |
| } |
| |
| case ArrayWithDouble: { |
| vector = 0; |
| vectorEnd = 0; |
| for (; i < m_butterfly->publicLength(); ++i) { |
| double v = m_butterfly->contiguousDouble()[i]; |
| if (v != v) |
| break; |
| callFrame->setArgument(i, JSValue(JSValue::EncodeAsDouble, v)); |
| } |
| break; |
| } |
| |
| case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES: { |
| ArrayStorage* storage = m_butterfly->arrayStorage(); |
| vector = storage->m_vector; |
| vectorEnd = min(length, storage->vectorLength()); |
| break; |
| } |
| |
| default: |
| CRASH(); |
| vector = 0; |
| vectorEnd = 0; |
| break; |
| } |
| |
| for (; i < vectorEnd; ++i) { |
| WriteBarrier<Unknown>& v = vector[i]; |
| if (!v) |
| break; |
| callFrame->setArgument(i, v.get()); |
| } |
| |
| for (; i < length; ++i) |
| callFrame->setArgument(i, get(exec, i)); |
| } |
| |
| template<IndexingType indexingType> |
| void JSArray::compactForSorting(unsigned& numDefined, unsigned& newRelevantLength) |
| { |
| ASSERT(!inSparseIndexingMode()); |
| ASSERT(indexingType == structure()->indexingType()); |
| |
| unsigned myRelevantLength = relevantLength<indexingType>(); |
| |
| numDefined = 0; |
| unsigned numUndefined = 0; |
| |
| for (; numDefined < myRelevantLength; ++numDefined) { |
| if (indexingType == ArrayWithInt32) { |
| JSValue v = m_butterfly->contiguousInt32()[numDefined].get(); |
| if (!v) |
| break; |
| ASSERT(v.isInt32()); |
| continue; |
| } |
| if (indexingType == ArrayWithDouble) { |
| double v = m_butterfly->contiguousDouble()[numDefined]; |
| if (v != v) |
| break; |
| continue; |
| } |
| JSValue v = indexingData<indexingType>()[numDefined].get(); |
| if (!v || v.isUndefined()) |
| break; |
| } |
| |
| for (unsigned i = numDefined; i < myRelevantLength; ++i) { |
| if (indexingType == ArrayWithInt32) { |
| JSValue v = m_butterfly->contiguousInt32()[i].get(); |
| if (!v) |
| continue; |
| ASSERT(v.isInt32()); |
| m_butterfly->contiguousInt32()[numDefined++].setWithoutWriteBarrier(v); |
| continue; |
| } |
| if (indexingType == ArrayWithDouble) { |
| double v = m_butterfly->contiguousDouble()[i]; |
| if (v != v) |
| continue; |
| m_butterfly->contiguousDouble()[numDefined++] = v; |
| continue; |
| } |
| JSValue v = indexingData<indexingType>()[i].get(); |
| if (v) { |
| if (v.isUndefined()) |
| ++numUndefined; |
| else |
| indexingData<indexingType>()[numDefined++].setWithoutWriteBarrier(v); |
| } |
| } |
| |
| newRelevantLength = numDefined + numUndefined; |
| |
| if (hasArrayStorage(indexingType)) |
| ASSERT(!arrayStorage()->m_sparseMap); |
| |
| switch (indexingType) { |
| case ArrayWithInt32: |
| case ArrayWithDouble: |
| ASSERT(numDefined == newRelevantLength); |
| break; |
| |
| default: |
| for (unsigned i = numDefined; i < newRelevantLength; ++i) |
| indexingData<indexingType>()[i].setUndefined(); |
| break; |
| } |
| for (unsigned i = newRelevantLength; i < myRelevantLength; ++i) { |
| if (indexingType == ArrayWithDouble) |
| m_butterfly->contiguousDouble()[i] = QNaN; |
| else |
| indexingData<indexingType>()[i].clear(); |
| } |
| |
| if (hasArrayStorage(indexingType)) |
| arrayStorage()->m_numValuesInVector = newRelevantLength; |
| } |
| |
| } // namespace JSC |