diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2015-10-15 09:45:50 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2015-10-15 09:45:50 +0000 |
commit | e15dd966d523731101f70ccf768bba12435a0208 (patch) | |
tree | ae9cb828a24ded2585a41af3f21411523b47897d /Source/JavaScriptCore/runtime/JSArray.cpp | |
download | WebKitGtk-tarball-e15dd966d523731101f70ccf768bba12435a0208.tar.gz |
webkitgtk-2.10.2webkitgtk-2.10.2
Diffstat (limited to 'Source/JavaScriptCore/runtime/JSArray.cpp')
-rw-r--r-- | Source/JavaScriptCore/runtime/JSArray.cpp | 1203 |
1 files changed, 1203 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/runtime/JSArray.cpp b/Source/JavaScriptCore/runtime/JSArray.cpp new file mode 100644 index 000000000..2c5a19ae6 --- /dev/null +++ b/Source/JavaScriptCore/runtime/JSArray.cpp @@ -0,0 +1,1203 @@ +/* + * Copyright (C) 1999-2000 Harri Porten (porten@kde.org) + * Copyright (C) 2003, 2007, 2008, 2009, 2012, 2013, 2015 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 "Error.h" +#include "Executable.h" +#include "GetterSetter.h" +#include "IndexingHeaderInlines.h" +#include "JSCInlines.h" +#include "PropertyNameArray.h" +#include "Reject.h" +#include <wtf/Assertions.h> + +using namespace std; +using namespace WTF; + +namespace JSC { + +STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSArray); + +const ClassInfo JSArray::s_info = {"Array", &JSNonFinalObject::s_info, 0, CREATE_METHOD_TABLE(JSArray)}; + +Butterfly* createArrayButterflyInDictionaryIndexingMode( + VM& vm, JSCell* intendedOwner, unsigned initialLength) +{ + Butterfly* butterfly = Butterfly::create( + vm, intendedOwner, 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->vm()); + + 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, const 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)) { + exec->vm().throwException(exec, createRangeError(exec, ASCIILiteral("Invalid array length"))); + return false; + } + + // Based on SameValue check in 8.12.9, this is always okay. + // FIXME: Nothing prevents this from being called on a RuntimeArray, and the length function will always return 0 in that case. + 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). + if (Optional<uint32_t> optionalIndex = parseIndex(propertyName)) { + // b. Reject if index >= oldLen and oldLenDesc.[[Writable]] is false. + uint32_t index = optionalIndex.value(); + // FIXME: Nothing prevents this from being called on a RuntimeArray, and the length function will always return 0 in that case. + 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(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot) +{ + JSArray* thisObject = jsCast<JSArray*>(object); + if (propertyName == exec->propertyNames().length) { + unsigned attributes = thisObject->isLengthWritable() ? DontDelete | DontEnum : DontDelete | DontEnum | ReadOnly; + slot.setValue(thisObject, attributes, jsNumber(thisObject->length())); + return true; + } + + return JSObject::getOwnPropertySlot(thisObject, exec, propertyName, slot); +} + +// 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)) { + exec->vm().throwException(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(VM& vm, bool addToFront, unsigned count) +{ + ArrayStorage* storage = ensureArrayStorage(vm); + Butterfly* butterfly = storage->butterfly(); + unsigned propertyCapacity = structure(vm)->outOfLineCapacity(); + unsigned propertySize = structure(vm)->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. + + DeferGC deferGC(vm.heap); + 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(vm)); + newStorageCapacity = currentCapacity; + } else { + size_t newSize = Butterfly::totalSize(0, propertyCapacity, true, ArrayStorage::sizeFor(desiredCapacity)); + if (!vm.heap.tryAllocateStorage(this, 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(vm)) || 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(vm))) || (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; + setButterflyWithoutChangingStructure(vm, 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, StrictModeReadonlyPropertyWriteError); + + if (newLength < length) { + // Copy any keys we might be interested in into a vector. + Vector<unsigned, 0, UnsafeVectorOverflow> keys; + keys.reserveInitialCapacity(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 (indexingType()) { + case ArrayClass: + if (!newLength) + return true; + if (newLength >= MIN_SPARSE_ARRAY_INDEX) { + return setLengthWithArrayStorage( + exec, newLength, throwException, + convertContiguousToArrayStorage(exec->vm())); + } + createInitialUndecided(exec->vm(), 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->vm())); + } + if (newLength > m_butterfly->publicLength()) { + ensureLength(exec->vm(), newLength); + return true; + } + + unsigned lengthToClear = m_butterfly->publicLength() - newLength; + unsigned costToAllocateNewButterfly = 64; // a heuristic. + if (lengthToClear > newLength && lengthToClear > costToAllocateNewButterfly) { + reallocateAndShrinkButterfly(exec->vm(), newLength); + return true; + } + + if (indexingType() == ArrayWithDouble) { + for (unsigned i = m_butterfly->publicLength(); i-- > newLength;) + m_butterfly->contiguousDouble()[i] = PNaN; + } 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 (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(); + + RELEASE_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(); + + RELEASE_ASSERT(length < m_butterfly->vectorLength()); + double value = m_butterfly->contiguousDouble()[length]; + if (value == value) { + m_butterfly->contiguousDouble()[length] = PNaN; + 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, StrictModeReadonlyPropertyWriteError); + 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(); + + RELEASE_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, ASCIILiteral("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 (indexingType()) { + case ArrayClass: { + createInitialUndecided(exec->vm(), 0); + FALLTHROUGH; + } + + case ArrayWithUndecided: { + convertUndecidedForValue(exec->vm(), value); + push(exec, value); + return; + } + + case ArrayWithInt32: { + if (!value.isInt32()) { + convertInt32ForValue(exec->vm(), 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(exec->vm())->putByIndex(this, exec, length, value, true); + if (!exec->hadException()) + exec->vm().throwException(exec, createRangeError(exec, ASCIILiteral("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->vm(), this, value); + m_butterfly->setPublicLength(length + 1); + return; + } + + if (length > MAX_ARRAY_INDEX) { + methodTable(exec->vm())->putByIndex(this, exec, length, value, true); + if (!exec->hadException()) + exec->vm().throwException(exec, createRangeError(exec, ASCIILiteral("Invalid array length"))); + return; + } + + putByIndexBeyondVectorLengthWithoutAttributes<ContiguousShape>(exec, length, value); + return; + } + + case ArrayWithDouble: { + if (!value.isNumber()) { + convertDoubleToContiguous(exec->vm()); + push(exec, value); + return; + } + double valueAsDouble = value.asNumber(); + if (valueAsDouble != valueAsDouble) { + convertDoubleToContiguous(exec->vm()); + 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(exec->vm())->putByIndex(this, exec, length, value, true); + if (!exec->hadException()) + exec->vm().throwException(exec, createRangeError(exec, ASCIILiteral("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; + } + FALLTHROUGH; + } + + 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->vm(), 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(exec->vm())->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()) + exec->vm().throwException(exec, createRangeError(exec, ASCIILiteral("Invalid array length"))); + return; + } + + // Handled the same as putIndex. + putByIndexBeyondVectorLengthWithArrayStorage(exec, storage->length(), value, true, storage); + break; + } + + default: + RELEASE_ASSERT_NOT_REACHED(); + } +} + +JSArray* JSArray::fastSlice(ExecState& exec, unsigned startIndex, unsigned count) +{ + auto arrayType = indexingType(); + switch (arrayType) { + case ArrayWithDouble: + case ArrayWithInt32: + case ArrayWithContiguous: { + VM& vm = exec.vm(); + if (count >= MIN_SPARSE_ARRAY_INDEX || structure(vm)->holesMustForwardToPrototype(vm)) + return nullptr; + + Structure* resultStructure = exec.lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(arrayType); + JSArray* resultArray = JSArray::tryCreateUninitialized(vm, resultStructure, count); + if (!resultArray) + return nullptr; + + auto& resultButterfly = *resultArray->butterfly(); + if (arrayType == ArrayWithDouble) + memcpy(resultButterfly.contiguousDouble().data(), m_butterfly->contiguousDouble().data() + startIndex, sizeof(JSValue) * count); + else + memcpy(resultButterfly.contiguous().data(), m_butterfly->contiguous().data() + startIndex, sizeof(JSValue) * count); + resultButterfly.setPublicLength(count); + + return resultArray; + } + default: + return nullptr; + } +} + +EncodedJSValue JSArray::fastConcatWith(ExecState& exec, JSArray& otherArray) +{ + auto newArrayType = indexingType(); + + VM& vm = exec.vm(); + ASSERT(newArrayType == fastConcatType(vm, *this, otherArray)); + + unsigned thisArraySize = m_butterfly->publicLength(); + unsigned otherArraySize = otherArray.m_butterfly->publicLength(); + ASSERT(thisArraySize + otherArraySize < MIN_SPARSE_ARRAY_INDEX); + + Structure* resultStructure = exec.lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(newArrayType); + JSArray* resultArray = JSArray::tryCreateUninitialized(vm, resultStructure, thisArraySize + otherArraySize); + if (!resultArray) + return JSValue::encode(throwOutOfMemoryError(&exec)); + + auto& resultButterfly = *resultArray->butterfly(); + auto& otherButterfly = *otherArray.butterfly(); + if (newArrayType == ArrayWithDouble) { + auto buffer = resultButterfly.contiguousDouble().data(); + memcpy(buffer, m_butterfly->contiguousDouble().data(), sizeof(JSValue) * thisArraySize); + memcpy(buffer + thisArraySize, otherButterfly.contiguousDouble().data(), sizeof(JSValue) * otherArraySize); + } else { + auto buffer = resultButterfly.contiguous().data(); + memcpy(buffer, m_butterfly->contiguous().data(), sizeof(JSValue) * thisArraySize); + memcpy(buffer + thisArraySize, otherButterfly.contiguous().data(), sizeof(JSValue) * otherArraySize); + } + + resultButterfly.setPublicLength(thisArraySize + otherArraySize); + return JSValue::encode(resultArray); +} + +bool JSArray::shiftCountWithArrayStorage(VM& vm, unsigned startIndex, unsigned count, ArrayStorage* storage) +{ + unsigned oldLength = storage->length(); + RELEASE_ASSERT(count <= oldLength); + + // If the array contains holes or is otherwise in an abnormal state, + // use the generic algorithm in ArrayPrototype. + if ((storage->hasHoles() && this->structure(vm)->holesMustForwardToPrototype(vm)) + || hasSparseMap() + || shouldUseSlowPut(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); + + unsigned numElementsBeforeShiftRegion = startIndex; + unsigned firstIndexAfterShiftRegion = startIndex + count; + unsigned numElementsAfterShiftRegion = usedVectorLength - firstIndexAfterShiftRegion; + ASSERT(numElementsBeforeShiftRegion + count + numElementsAfterShiftRegion == usedVectorLength); + + // The point of this comparison seems to be to minimize the amount of elements that have to + // be moved during a shift operation. + if (numElementsBeforeShiftRegion < numElementsAfterShiftRegion) { + // The number of elements before the shift region is less than the number of elements + // after the shift region, so we move the elements before to the right. + if (numElementsBeforeShiftRegion) { + RELEASE_ASSERT(count + startIndex <= vectorLength); + if (storage->hasHoles()) { + for (unsigned i = startIndex; i-- > 0;) { + unsigned destinationIndex = count + i; + JSValue source = storage->m_vector[i].get(); + JSValue dest = storage->m_vector[destinationIndex].get(); + // Any time we overwrite a hole we know we overcounted the number of values we removed + // when we subtracted count from m_numValuesInVector above. + if (!dest && destinationIndex >= firstIndexAfterShiftRegion) + storage->m_numValuesInVector++; + storage->m_vector[count + i].setWithoutWriteBarrier(source); + } + } else { + memmove(storage->m_vector + count, + storage->m_vector, + sizeof(JSValue) * startIndex); + } + } + // Adjust the Butterfly and the index bias. We only need to do this here because we're changing + // the start of the Butterfly, which needs to point at the first indexed property in the used + // portion of the vector. + m_butterfly.setWithoutWriteBarrier(m_butterfly->shift(structure(), count)); + storage = m_butterfly->arrayStorage(); + storage->m_indexBias += count; + + // Since we're consuming part of the vector by moving its beginning to the left, + // we need to modify the vector length appropriately. + storage->setVectorLength(vectorLength - count); + } else { + // The number of elements before the shift region is greater than or equal to the number + // of elements after the shift region, so we move the elements after the shift region to the left. + if (storage->hasHoles()) { + for (unsigned i = 0; i < numElementsAfterShiftRegion; ++i) { + unsigned destinationIndex = startIndex + i; + JSValue source = storage->m_vector[firstIndexAfterShiftRegion + i].get(); + JSValue dest = storage->m_vector[destinationIndex].get(); + // Any time we overwrite a hole we know we overcounted the number of values we removed + // when we subtracted count from m_numValuesInVector above. + if (!dest && destinationIndex < firstIndexAfterShiftRegion) + storage->m_numValuesInVector++; + storage->m_vector[startIndex + i].setWithoutWriteBarrier(source); + } + } else { + memmove(storage->m_vector + startIndex, + storage->m_vector + firstIndexAfterShiftRegion, + sizeof(JSValue) * numElementsAfterShiftRegion); + } + // Clear the slots of the elements we just moved. + unsigned startOfEmptyVectorTail = usedVectorLength - count; + for (unsigned i = startOfEmptyVectorTail; i < usedVectorLength; ++i) + storage->m_vector[i].clear(); + // We don't modify the index bias or the Butterfly pointer in this case because we're not changing + // the start of the Butterfly, which needs to point at the first indexed property in the used + // portion of the vector. We also don't modify the vector length because we're not actually changing + // its length; we're just using less of it. + } + + return true; +} + +bool JSArray::shiftCountWithAnyIndexingType(ExecState* exec, unsigned& startIndex, unsigned count) +{ + VM& vm = exec->vm(); + RELEASE_ASSERT(count > 0); + + switch (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(); + RELEASE_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(vm, startIndex, count, ensureArrayStorage(vm)); + + // 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. + // We have to check for holes before we start moving things around so that we don't get halfway + // through shifting and then realize we should have been in ArrayStorage mode. + unsigned end = oldLength - count; + if (this->structure(vm)->holesMustForwardToPrototype(vm)) { + for (unsigned i = startIndex; i < end; ++i) { + JSValue v = m_butterfly->contiguous()[i + count].get(); + if (UNLIKELY(!v)) { + startIndex = i; + return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm)); + } + m_butterfly->contiguous()[i].setWithoutWriteBarrier(v); + } + } else { + memmove(m_butterfly->contiguous().data() + startIndex, + m_butterfly->contiguous().data() + startIndex + count, + sizeof(JSValue) * (end - startIndex)); + } + + 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(); + RELEASE_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(vm, startIndex, count, ensureArrayStorage(vm)); + + // 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. + // We have to check for holes before we start moving things around so that we don't get halfway + // through shifting and then realize we should have been in ArrayStorage mode. + unsigned end = oldLength - count; + if (this->structure(vm)->holesMustForwardToPrototype(vm)) { + for (unsigned i = startIndex; i < end; ++i) { + double v = m_butterfly->contiguousDouble()[i + count]; + if (UNLIKELY(v != v)) { + startIndex = i; + return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm)); + } + m_butterfly->contiguousDouble()[i] = v; + } + } else { + memmove(m_butterfly->contiguousDouble().data() + startIndex, + m_butterfly->contiguousDouble().data() + startIndex + count, + sizeof(JSValue) * (end - startIndex)); + } + for (unsigned i = end; i < oldLength; ++i) + m_butterfly->contiguousDouble()[i] = PNaN; + + m_butterfly->setPublicLength(oldLength - count); + return true; + } + + case ArrayWithArrayStorage: + case ArrayWithSlowPutArrayStorage: + return shiftCountWithArrayStorage(vm, 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(); + + RELEASE_ASSERT(startIndex <= length); + + // If the array contains holes or is otherwise in an abnormal state, + // use the generic algorithm in ArrayPrototype. + if (storage->hasHoles() || storage->inSparseMode() || shouldUseSlowPut(indexingType())) + return false; + + bool moveFront = !startIndex || startIndex < length / 2; + + unsigned vectorLength = storage->vectorLength(); + + if (moveFront && storage->m_indexBias >= count) { + Butterfly* newButterfly = storage->butterfly()->unshift(structure(), count); + storage = newButterfly->arrayStorage(); + storage->m_indexBias -= count; + storage->setVectorLength(vectorLength + count); + setButterflyWithoutChangingStructure(exec->vm(), newButterfly); + } else if (!moveFront && vectorLength - length >= count) + storage = storage->butterfly()->arrayStorage(); + else if (unshiftCountSlowCase(exec->vm(), 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 (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->vm())); + + ensureLength(exec->vm(), oldLength + count); + + // We have to check for holes before we start moving things around so that we don't get halfway + // through shifting and then realize we should have been in ArrayStorage mode. + for (unsigned i = oldLength; i-- > startIndex;) { + JSValue v = m_butterfly->contiguous()[i].get(); + if (UNLIKELY(!v)) + return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(exec->vm())); + } + + for (unsigned i = oldLength; i-- > startIndex;) { + JSValue v = m_butterfly->contiguous()[i].get(); + ASSERT(v); + 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->vm())); + + ensureLength(exec->vm(), oldLength + count); + + // We have to check for holes before we start moving things around so that we don't get halfway + // through shifting and then realize we should have been in ArrayStorage mode. + for (unsigned i = oldLength; i-- > startIndex;) { + double v = m_butterfly->contiguousDouble()[i]; + if (UNLIKELY(v != v)) + return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(exec->vm())); + } + + for (unsigned i = oldLength; i-- > startIndex;) { + double v = m_butterfly->contiguousDouble()[i]; + ASSERT(v == v); + 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; + } +} + +void JSArray::fillArgList(ExecState* exec, MarkedArgumentBuffer& args) +{ + unsigned i = 0; + unsigned vectorEnd; + WriteBarrier<Unknown>* vector; + + switch (indexingType()) { + case ArrayClass: + return; + + case ArrayWithUndecided: { + vector = 0; + vectorEnd = 0; + break; + } + + case ArrayWithInt32: + case ArrayWithContiguous: { + vectorEnd = m_butterfly->publicLength(); + vector = m_butterfly->contiguous().data(); + 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(); +#if COMPILER_QUIRK(CONSIDERS_UNREACHABLE_CODE) + vector = 0; + vectorEnd = 0; + break; +#endif + } + + for (; i < vectorEnd; ++i) { + WriteBarrier<Unknown>& v = vector[i]; + if (!v) + break; + args.append(v.get()); + } + + // FIXME: What prevents this from being called with a RuntimeArray? The length function will always return 0 in that case. + for (; i < length(); ++i) + args.append(get(exec, i)); +} + +void JSArray::copyToArguments(ExecState* exec, VirtualRegister firstElementDest, unsigned offset, unsigned length) +{ + unsigned i = offset; + WriteBarrier<Unknown>* vector; + unsigned vectorEnd; + length += offset; // We like to think of the length as being our length, rather than the output length. + + // FIXME: What prevents this from being called with a RuntimeArray? The length function will always return 0 in that case. + ASSERT(length == this->length()); + + switch (indexingType()) { + case ArrayClass: + return; + + case ArrayWithUndecided: { + vector = 0; + vectorEnd = 0; + break; + } + + case ArrayWithInt32: + case ArrayWithContiguous: { + vector = m_butterfly->contiguous().data(); + vectorEnd = m_butterfly->publicLength(); + break; + } + + case ArrayWithDouble: { + vector = 0; + vectorEnd = 0; + for (; i < m_butterfly->publicLength(); ++i) { + ASSERT(i < butterfly()->vectorLength()); + double v = m_butterfly->contiguousDouble()[i]; + if (v != v) + break; + exec->r(firstElementDest + i - offset) = 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(); +#if COMPILER_QUIRK(CONSIDERS_UNREACHABLE_CODE) + vector = 0; + vectorEnd = 0; + break; +#endif + } + + for (; i < vectorEnd; ++i) { + WriteBarrier<Unknown>& v = vector[i]; + if (!v) + break; + exec->r(firstElementDest + i - offset) = v.get(); + } + + for (; i < length; ++i) { + exec->r(firstElementDest + i - offset) = get(exec, i); + if (UNLIKELY(exec->vm().exception())) + return; + } +} + +} // namespace JSC |