From 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c Mon Sep 17 00:00:00 2001 From: Lorry Tar Creator Date: Tue, 27 Jun 2017 06:07:23 +0000 Subject: webkitgtk-2.16.5 --- Source/JavaScriptCore/runtime/JSArray.cpp | 1337 +++++++++++------------------ 1 file changed, 517 insertions(+), 820 deletions(-) (limited to 'Source/JavaScriptCore/runtime/JSArray.cpp') diff --git a/Source/JavaScriptCore/runtime/JSArray.cpp b/Source/JavaScriptCore/runtime/JSArray.cpp index 9007b5f7d..efd79dd8c 100644 --- a/Source/JavaScriptCore/runtime/JSArray.cpp +++ b/Source/JavaScriptCore/runtime/JSArray.cpp @@ -1,6 +1,6 @@ /* * Copyright (C) 1999-2000 Harri Porten (porten@kde.org) - * Copyright (C) 2003, 2007, 2008, 2009, 2012, 2013 Apple Inc. All rights reserved. + * Copyright (C) 2003-2017 Apple Inc. All rights reserved. * Copyright (C) 2003 Peter Kelly (pmk@post.com) * Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com) * @@ -25,28 +25,26 @@ #include "ArrayPrototype.h" #include "ButterflyInlines.h" -#include "CachedCall.h" -#include "CopiedSpace.h" -#include "CopiedSpaceInlines.h" +#include "CodeBlock.h" #include "Error.h" -#include "Executable.h" #include "GetterSetter.h" #include "IndexingHeaderInlines.h" +#include "JSArrayInlines.h" +#include "JSCInlines.h" #include "PropertyNameArray.h" -#include "Reject.h" -#include +#include "TypeError.h" #include -#include -#include using namespace std; using namespace WTF; namespace JSC { +static const char* const LengthExceededTheMaximumArrayLengthError = "Length exceeded the maximum array length"; + STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSArray); -const ClassInfo JSArray::s_info = {"Array", &JSNonFinalObject::s_info, 0, 0, CREATE_METHOD_TABLE(JSArray)}; +const ClassInfo JSArray::s_info = {"Array", &JSNonFinalObject::s_info, 0, CREATE_METHOD_TABLE(JSArray)}; Butterfly* createArrayButterflyInDictionaryIndexingMode( VM& vm, JSCell* intendedOwner, unsigned initialLength) @@ -62,6 +60,54 @@ Butterfly* createArrayButterflyInDictionaryIndexingMode( return butterfly; } +JSArray* JSArray::tryCreateForInitializationPrivate(VM& vm, GCDeferralContext* deferralContext, Structure* structure, unsigned initialLength) +{ + if (UNLIKELY(initialLength > MAX_STORAGE_VECTOR_LENGTH)) + return 0; + + unsigned outOfLineStorage = structure->outOfLineCapacity(); + + Butterfly* butterfly; + IndexingType indexingType = structure->indexingType(); + if (LIKELY(!hasAnyArrayStorage(indexingType))) { + ASSERT( + hasUndecided(indexingType) + || hasInt32(indexingType) + || hasDouble(indexingType) + || hasContiguous(indexingType)); + + unsigned vectorLength = Butterfly::optimalContiguousVectorLength(structure, initialLength); + void* temp = vm.auxiliarySpace.tryAllocate(deferralContext, Butterfly::totalSize(0, outOfLineStorage, true, vectorLength * sizeof(EncodedJSValue))); + if (UNLIKELY(!temp)) + return nullptr; + butterfly = Butterfly::fromBase(temp, 0, outOfLineStorage); + butterfly->setVectorLength(vectorLength); + butterfly->setPublicLength(initialLength); + if (hasDouble(indexingType)) { + for (unsigned i = initialLength; i < vectorLength; ++i) + butterfly->contiguousDouble()[i] = PNaN; + } else { + for (unsigned i = initialLength; i < vectorLength; ++i) + butterfly->contiguous()[i].clear(); + } + } else { + unsigned vectorLength = ArrayStorage::optimalVectorLength(0, structure, initialLength); + void* temp = vm.auxiliarySpace.tryAllocate(deferralContext, Butterfly::totalSize(0, outOfLineStorage, true, ArrayStorage::sizeFor(vectorLength))); + if (UNLIKELY(!temp)) + return nullptr; + butterfly = Butterfly::fromBase(temp, 0, outOfLineStorage); + *butterfly->indexingHeader() = indexingHeaderForArrayStorage(initialLength, vectorLength); + ArrayStorage* storage = butterfly->arrayStorage(); + storage->m_indexBias = 0; + storage->m_sparseMap.clear(); + storage->m_numValuesInVector = initialLength; + for (unsigned i = initialLength; i < vectorLength; ++i) + storage->m_vector[i].clear(); + } + + return createWithButterfly(vm, deferralContext, structure, butterfly); +} + void JSArray::setLengthWritable(ExecState* exec, bool writable) { ASSERT(isLengthWritable() || !writable); @@ -78,25 +124,28 @@ void JSArray::setLengthWritable(ExecState* exec, bool writable) // Defined in ES5.1 15.4.5.1 bool JSArray::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool throwException) { + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + JSArray* array = jsCast(object); // 3. If P is "length", then - if (propertyName == exec->propertyNames().length) { + if (propertyName == vm.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."); + return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeConfigurabilityError)); // 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."); + return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeEnumerabilityError)); // 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."); + return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeAccessMechanismError)); // 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."); + return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeWritabilityError)); // This descriptor is either just making length read-only, or changing nothing! if (!descriptor.value()) { if (descriptor.writablePresent()) @@ -109,11 +158,12 @@ bool JSArray::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName 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, "Invalid array length")); + JSC::throwException(exec, scope, 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()); @@ -125,7 +175,7 @@ bool JSArray::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName // 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."); + return typeError(exec, scope, throwException, ASCIILiteral(ReadonlyPropertyChangeError)); // h. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true. // i. Else, @@ -138,7 +188,9 @@ bool JSArray::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName // 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)) { + bool success = array->setLength(exec, newLen, throwException); + ASSERT(!scope.exception() || !success); + if (!success) { // 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. @@ -160,20 +212,23 @@ bool JSArray::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName // 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) { + if (std::optional 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."); + return typeError(exec, scope, throwException, ASCIILiteral("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. + scope.release(); return array->defineOwnIndexedProperty(exec, index, descriptor, throwException); } + scope.release(); return array->JSObject::defineOwnNonIndexProperty(exec, propertyName, descriptor, throwException); } @@ -190,21 +245,31 @@ bool JSArray::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName } // ECMA 15.4.5.1 -void JSArray::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) +bool JSArray::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) { + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + JSArray* thisObject = jsCast(cell); + if (UNLIKELY(isThisValueAltered(slot, thisObject))) { + scope.release(); + return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode()); + } + if (propertyName == exec->propertyNames().length) { unsigned newLength = value.toUInt32(exec); + RETURN_IF_EXCEPTION(scope, false); if (value.toNumber(exec) != static_cast(newLength)) { - exec->vm().throwException(exec, createRangeError(exec, ASCIILiteral("Invalid array length"))); - return; + throwException(exec, scope, createRangeError(exec, ASCIILiteral("Invalid array length"))); + return false; } - thisObject->setLength(exec, newLength, slot.isStrictMode()); - return; + scope.release(); + return thisObject->setLength(exec, newLength, slot.isStrictMode()); } - JSObject::put(thisObject, exec, propertyName, value, slot); + scope.release(); + return JSObject::put(thisObject, exec, propertyName, value, slot); } bool JSArray::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName) @@ -228,20 +293,21 @@ void JSArray::getOwnNonIndexPropertyNames(JSObject* object, ExecState* exec, Pro { JSArray* thisObject = jsCast(object); - if (mode == IncludeDontEnumProperties) + 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) +bool JSArray::unshiftCountSlowCase(const AbstractLocker&, VM& vm, DeferGC&, bool addToFront, unsigned count) { ArrayStorage* storage = ensureArrayStorage(vm); Butterfly* butterfly = storage->butterfly(); - unsigned propertyCapacity = structure()->outOfLineCapacity(); - unsigned propertySize = structure()->outOfLineSize(); - + Structure* structure = this->structure(vm); + 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); @@ -253,7 +319,8 @@ bool JSArray::unshiftCountSlowCase(VM& vm, bool addToFront, unsigned count) // * 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); + unsigned oldVectorLength = storage->vectorLength(); + unsigned usedVectorLength = min(oldVectorLength, 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) @@ -264,23 +331,29 @@ bool JSArray::unshiftCountSlowCase(VM& vm, bool addToFront, unsigned count) 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); + // FIXME: This code should be fixed to avoid internal fragmentation. It's not super high + // priority since increaseVectorLength() will "fix" any mistakes we make, but it would be cool + // to get this right eventually. + unsigned desiredCapacity = min(MAX_STORAGE_VECTOR_LENGTH, max(BASE_ARRAY_STORAGE_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; + bool allocatedNewStorage; // 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()); + newAllocBase = butterfly->base(structure); newStorageCapacity = currentCapacity; + allocatedNewStorage = false; } else { size_t newSize = Butterfly::totalSize(0, propertyCapacity, true, ArrayStorage::sizeFor(desiredCapacity)); - if (!vm.heap.tryAllocateStorage(this, newSize, &newAllocBase)) + newAllocBase = vm.auxiliarySpace.tryAllocate(newSize); + if (!newAllocBase) return false; newStorageCapacity = desiredCapacity; + allocatedNewStorage = true; } // Step 3: @@ -298,7 +371,7 @@ bool JSArray::unshiftCountSlowCase(VM& vm, bool addToFront, unsigned count) // 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); + ASSERT(newAllocBase != butterfly->base(structure) || postCapacity < storage->vectorLength() - length); } unsigned newVectorLength = requiredVectorLength + postCapacity; @@ -310,33 +383,43 @@ bool JSArray::unshiftCountSlowCase(VM& vm, bool addToFront, unsigned count) 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)) { + + if (allocatedNewStorage) { + // We will set the vectorLength to newVectorLength. We populated requiredVectorLength + // (usedVectorLength + count), which is less. Clear the difference. + for (unsigned i = requiredVectorLength; i < newVectorLength; ++i) + newButterfly->arrayStorage()->m_vector[i].clear(); + } + } 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* newVector = newButterfly->arrayStorage()->m_vector; + for (unsigned i = requiredVectorLength; i < newVectorLength; i++) - newVector[i].clear(); + newButterfly->arrayStorage()->m_vector[i].clear(); } newButterfly->arrayStorage()->setVectorLength(newVectorLength); newButterfly->arrayStorage()->m_indexBias = newIndexBias; - setButterflyWithoutChangingStructure(vm, newButterfly); + + setButterfly(vm, newButterfly); return true; } bool JSArray::setLengthWithArrayStorage(ExecState* exec, unsigned newLength, bool throwException, ArrayStorage* storage) { - unsigned length = storage->length(); + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + 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); + return typeError(exec, scope, throwException, ASCIILiteral(ReadonlyPropertyWriteError)); if (newLength < length) { // Copy any keys we might be interested in into a vector. @@ -361,7 +444,7 @@ bool JSArray::setLengthWithArrayStorage(ExecState* exec, unsigned newLength, boo ASSERT(it != map->notFound()); if (it->value.attributes & DontDelete) { storage->setLength(index + 1); - return reject(exec, throwException, "Unable to delete property."); + return typeError(exec, scope, throwException, ASCIILiteral(UnableToDeletePropertyError)); } map->remove(it); } @@ -379,7 +462,7 @@ bool JSArray::setLengthWithArrayStorage(ExecState* exec, unsigned newLength, boo unsigned usedVectorLength = min(length, storage->vectorLength()); for (unsigned i = newLength; i < usedVectorLength; ++i) { WriteBarrier& valueSlot = storage->m_vector[i]; - bool hadValue = valueSlot; + bool hadValue = !!valueSlot; valueSlot.clear(); storage->m_numValuesInVector -= hadValue; } @@ -390,49 +473,118 @@ bool JSArray::setLengthWithArrayStorage(ExecState* exec, unsigned newLength, boo return true; } +bool JSArray::appendMemcpy(ExecState* exec, VM& vm, unsigned startIndex, JSC::JSArray* otherArray) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + if (!canFastCopy(vm, otherArray)) + return false; + + IndexingType type = indexingType(); + IndexingType copyType = mergeIndexingTypeForCopying(otherArray->indexingType()); + if (type == ArrayWithUndecided && copyType != NonArray) { + if (copyType == ArrayWithInt32) + convertUndecidedToInt32(vm); + else if (copyType == ArrayWithDouble) + convertUndecidedToDouble(vm); + else if (copyType == ArrayWithContiguous) + convertUndecidedToContiguous(vm); + else { + ASSERT(copyType == ArrayWithUndecided); + return true; + } + } else if (type != copyType) + return false; + + unsigned otherLength = otherArray->length(); + Checked checkedNewLength = startIndex; + checkedNewLength += otherLength; + + unsigned newLength; + if (checkedNewLength.safeGet(newLength) == CheckedState::DidOverflow) { + throwException(exec, scope, createRangeError(exec, ASCIILiteral(LengthExceededTheMaximumArrayLengthError))); + return false; + } + + if (newLength >= MIN_SPARSE_ARRAY_INDEX) + return false; + + if (!ensureLength(vm, newLength)) { + throwOutOfMemoryError(exec, scope); + return false; + } + ASSERT(copyType == indexingType()); + + if (type == ArrayWithDouble) + memcpy(butterfly()->contiguousDouble().data() + startIndex, otherArray->butterfly()->contiguousDouble().data(), sizeof(JSValue) * otherLength); + else + memcpy(butterfly()->contiguous().data() + startIndex, otherArray->butterfly()->contiguous().data(), sizeof(JSValue) * otherLength); + + return true; +} + bool JSArray::setLength(ExecState* exec, unsigned newLength, bool throwException) { - switch (structure()->indexingType()) { + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + Butterfly* butterfly = m_butterfly.get(); + switch (indexingType()) { case ArrayClass: if (!newLength) return true; if (newLength >= MIN_SPARSE_ARRAY_INDEX) { + scope.release(); return setLengthWithArrayStorage( exec, newLength, throwException, - convertContiguousToArrayStorage(exec->vm())); + ensureArrayStorage(vm)); } - createInitialUndecided(exec->vm(), newLength); + createInitialUndecided(vm, newLength); return true; case ArrayWithUndecided: case ArrayWithInt32: case ArrayWithDouble: - case ArrayWithContiguous: - if (newLength == m_butterfly->publicLength()) + case ArrayWithContiguous: { + if (newLength == 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()))) { + scope.release(); return setLengthWithArrayStorage( exec, newLength, throwException, - ensureArrayStorage(exec->vm())); + ensureArrayStorage(vm)); } - if (newLength > m_butterfly->publicLength()) { - ensureLength(exec->vm(), newLength); + if (newLength > butterfly->publicLength()) { + if (!ensureLength(vm, newLength)) { + throwOutOfMemoryError(exec, scope); + return false; + } + return true; + } + + unsigned lengthToClear = butterfly->publicLength() - newLength; + unsigned costToAllocateNewButterfly = 64; // a heuristic. + if (lengthToClear > newLength && lengthToClear > costToAllocateNewButterfly) { + reallocateAndShrinkButterfly(vm, newLength); return true; } - if (structure()->indexingType() == ArrayWithDouble) { - for (unsigned i = m_butterfly->publicLength(); i-- > newLength;) - m_butterfly->contiguousDouble()[i] = QNaN; + + if (indexingType() == ArrayWithDouble) { + for (unsigned i = butterfly->publicLength(); i-- > newLength;) + butterfly->contiguousDouble()[i] = PNaN; } else { - for (unsigned i = m_butterfly->publicLength(); i-- > newLength;) - m_butterfly->contiguous()[i].clear(); + for (unsigned i = butterfly->publicLength(); i-- > newLength;) + butterfly->contiguous()[i].clear(); } - m_butterfly->setPublicLength(newLength); + butterfly->setPublicLength(newLength); return true; + } case ArrayWithArrayStorage: case ArrayWithSlowPutArrayStorage: + scope.release(); return setLengthWithArrayStorage(exec, newLength, throwException, arrayStorage()); default: @@ -443,56 +595,61 @@ bool JSArray::setLength(ExecState* exec, unsigned newLength, bool throwException JSValue JSArray::pop(ExecState* exec) { - switch (structure()->indexingType()) { + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + Butterfly* butterfly = m_butterfly.get(); + + switch (indexingType()) { case ArrayClass: return jsUndefined(); case ArrayWithUndecided: - if (!m_butterfly->publicLength()) + if (!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(); + unsigned length = butterfly->publicLength(); if (!length--) return jsUndefined(); - RELEASE_ASSERT(length < m_butterfly->vectorLength()); - JSValue value = m_butterfly->contiguous()[length].get(); + RELEASE_ASSERT(length < butterfly->vectorLength()); + JSValue value = butterfly->contiguous()[length].get(); if (value) { - m_butterfly->contiguous()[length].clear(); - m_butterfly->setPublicLength(length); + butterfly->contiguous()[length].clear(); + butterfly->setPublicLength(length); return value; } break; } case ArrayWithDouble: { - unsigned length = m_butterfly->publicLength(); + unsigned length = butterfly->publicLength(); if (!length--) return jsUndefined(); - RELEASE_ASSERT(length < m_butterfly->vectorLength()); - double value = m_butterfly->contiguousDouble()[length]; + RELEASE_ASSERT(length < butterfly->vectorLength()); + double value = butterfly->contiguousDouble()[length]; if (value == value) { - m_butterfly->contiguousDouble()[length] = QNaN; - m_butterfly->setPublicLength(length); + butterfly->contiguousDouble()[length] = PNaN; + butterfly->setPublicLength(length); return JSValue(JSValue::EncodeAsDouble, value); } break; } case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES: { - ArrayStorage* storage = m_butterfly->arrayStorage(); + ArrayStorage* storage = butterfly->arrayStorage(); unsigned length = storage->length(); if (!length) { if (!isLengthWritable()) - throwTypeError(exec, StrictModeReadonlyPropertyWriteError); + throwTypeError(exec, scope, ASCIILiteral(ReadonlyPropertyWriteError)); return jsUndefined(); } @@ -520,14 +677,16 @@ JSValue JSArray::pop(ExecState* exec) 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(); + RETURN_IF_EXCEPTION(scope, JSValue()); // Call the [[Delete]] internal method of O with arguments indx and true. - if (!deletePropertyByIndex(this, exec, index)) { - throwTypeError(exec, "Unable to delete property."); + bool success = deletePropertyByIndex(this, exec, index); + RETURN_IF_EXCEPTION(scope, JSValue()); + if (!success) { + throwTypeError(exec, scope, ASCIILiteral(UnableToDeletePropertyError)); return jsUndefined(); } // Call the [[Put]] internal method of O with arguments "length", indx, and true. + scope.release(); setLength(exec, index, true); // Return element. return element; @@ -538,130 +697,146 @@ JSValue JSArray::pop(ExecState* exec) // - 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()) { + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + Butterfly* butterfly = m_butterfly.get(); + + switch (indexingType()) { case ArrayClass: { - createInitialUndecided(exec->vm(), 0); + createInitialUndecided(vm, 0); FALLTHROUGH; } case ArrayWithUndecided: { - convertUndecidedForValue(exec->vm(), value); + convertUndecidedForValue(vm, value); + scope.release(); push(exec, value); return; } case ArrayWithInt32: { if (!value.isInt32()) { - convertInt32ForValue(exec->vm(), value); + convertInt32ForValue(vm, value); + scope.release(); 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); + unsigned length = butterfly->publicLength(); + ASSERT(length <= butterfly->vectorLength()); + if (length < butterfly->vectorLength()) { + butterfly->contiguousInt32()[length].setWithoutWriteBarrier(value); + butterfly->setPublicLength(length + 1); return; } - if (length > MAX_ARRAY_INDEX) { - methodTable()->putByIndex(this, exec, length, value, true); - if (!exec->hadException()) - exec->vm().throwException(exec, createRangeError(exec, "Invalid array length")); + if (UNLIKELY(length > MAX_ARRAY_INDEX)) { + methodTable(vm)->putByIndex(this, exec, length, value, true); + if (!scope.exception()) + throwException(exec, scope, createRangeError(exec, ASCIILiteral(LengthExceededTheMaximumArrayLengthError))); return; } - + + scope.release(); putByIndexBeyondVectorLengthWithoutAttributes(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); + unsigned length = butterfly->publicLength(); + ASSERT(length <= butterfly->vectorLength()); + if (length < butterfly->vectorLength()) { + butterfly->contiguous()[length].set(vm, this, value); + butterfly->setPublicLength(length + 1); return; } - if (length > MAX_ARRAY_INDEX) { - methodTable()->putByIndex(this, exec, length, value, true); - if (!exec->hadException()) - exec->vm().throwException(exec, createRangeError(exec, "Invalid array length")); + if (UNLIKELY(length > MAX_ARRAY_INDEX)) { + methodTable(vm)->putByIndex(this, exec, length, value, true); + if (!scope.exception()) + throwException(exec, scope, createRangeError(exec, ASCIILiteral(LengthExceededTheMaximumArrayLengthError))); return; } - + + scope.release(); putByIndexBeyondVectorLengthWithoutAttributes(exec, length, value); return; } case ArrayWithDouble: { if (!value.isNumber()) { - convertDoubleToContiguous(exec->vm()); + convertDoubleToContiguous(vm); + scope.release(); push(exec, value); return; } double valueAsDouble = value.asNumber(); if (valueAsDouble != valueAsDouble) { - convertDoubleToContiguous(exec->vm()); + convertDoubleToContiguous(vm); + scope.release(); 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); + unsigned length = butterfly->publicLength(); + ASSERT(length <= butterfly->vectorLength()); + if (length < butterfly->vectorLength()) { + butterfly->contiguousDouble()[length] = valueAsDouble; + butterfly->setPublicLength(length + 1); return; } - if (length > MAX_ARRAY_INDEX) { - methodTable()->putByIndex(this, exec, length, value, true); - if (!exec->hadException()) - exec->vm().throwException(exec, createRangeError(exec, "Invalid array length")); + if (UNLIKELY(length > MAX_ARRAY_INDEX)) { + methodTable(vm)->putByIndex(this, exec, length, value, true); + if (!scope.exception()) + throwException(exec, scope, createRangeError(exec, ASCIILiteral(LengthExceededTheMaximumArrayLengthError))); return; } - + + scope.release(); putByIndexBeyondVectorLengthWithoutAttributes(exec, length, value); - break; + return; } case ArrayWithSlowPutArrayStorage: { unsigned oldLength = length(); - if (attemptToInterceptPutByIndexOnHole(exec, oldLength, value, true)) { - if (!exec->hadException() && oldLength < 0xFFFFFFFFu) + bool putResult = false; + if (attemptToInterceptPutByIndexOnHole(exec, oldLength, value, true, putResult)) { + if (!scope.exception() && oldLength < 0xFFFFFFFFu) { + scope.release(); setLength(exec, oldLength + 1, true); + } return; } FALLTHROUGH; } case ArrayWithArrayStorage: { - ArrayStorage* storage = m_butterfly->arrayStorage(); + ArrayStorage* storage = 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->m_vector[length].set(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()->putByIndex(this, exec, storage->length(), value, true); + if (UNLIKELY(storage->length() > MAX_ARRAY_INDEX)) { + methodTable(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, "Invalid array length")); + if (!scope.exception()) + throwException(exec, scope, createRangeError(exec, ASCIILiteral(LengthExceededTheMaximumArrayLengthError))); return; } // Handled the same as putIndex. + scope.release(); putByIndexBeyondVectorLengthWithArrayStorage(exec, storage->length(), value, true, storage); - break; + return; } default: @@ -669,15 +844,53 @@ void JSArray::push(ExecState* exec, JSValue value) } } -bool JSArray::shiftCountWithArrayStorage(unsigned startIndex, unsigned count, ArrayStorage* storage) +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; + + JSGlobalObject* lexicalGlobalObject = exec.lexicalGlobalObject(); + Structure* resultStructure = lexicalGlobalObject->arrayStructureForIndexingTypeDuringAllocation(arrayType); + if (UNLIKELY(hasAnyArrayStorage(resultStructure->indexingType()))) + return nullptr; + + ASSERT(!lexicalGlobalObject->isHavingABadTime()); + JSArray* resultArray = JSArray::tryCreateForInitializationPrivate(vm, resultStructure, count); + if (UNLIKELY(!resultArray)) + return nullptr; + + auto& resultButterfly = *resultArray->butterfly(); + if (arrayType == ArrayWithDouble) + memcpy(resultButterfly.contiguousDouble().data(), m_butterfly.get()->contiguousDouble().data() + startIndex, sizeof(JSValue) * count); + else + memcpy(resultButterfly.contiguous().data(), m_butterfly.get()->contiguous().data() + startIndex, sizeof(JSValue) * count); + resultButterfly.setPublicLength(count); + + return resultArray; + } + default: + return nullptr; + } +} + +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 (oldLength != storage->m_numValuesInVector || inSparseIndexingMode() || shouldUseSlowPut(structure()->indexingType())) + if ((storage->hasHoles() && this->structure(vm)->holesMustForwardToPrototype(vm)) + || hasSparseMap() + || shouldUseSlowPut(indexingType())) { return false; + } if (!oldLength) return true; @@ -694,6 +907,9 @@ bool JSArray::shiftCountWithArrayStorage(unsigned startIndex, unsigned count, Ar if (startIndex >= vectorLength) return true; + DisallowGC disallowGC; + auto locker = holdLock(*this); + if (startIndex + count > vectorLength) count = vectorLength - startIndex; @@ -711,16 +927,29 @@ bool JSArray::shiftCountWithArrayStorage(unsigned startIndex, unsigned count, Ar // after the shift region, so we move the elements before to the right. if (numElementsBeforeShiftRegion) { RELEASE_ASSERT(count + startIndex <= vectorLength); - memmove( - storage->m_vector + count, - storage->m_vector, - sizeof(JSValue) * startIndex); + 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(); + Butterfly* butterfly = m_butterfly.get()->shift(structure(), count); + setButterfly(vm, butterfly); + storage = butterfly->arrayStorage(); storage->m_indexBias += count; // Since we're consuming part of the vector by moving its beginning to the left, @@ -729,10 +958,22 @@ bool JSArray::shiftCountWithArrayStorage(unsigned startIndex, unsigned count, Ar } 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. - memmove( - storage->m_vector + startIndex, - storage->m_vector + firstIndexAfterShiftRegion, - sizeof(JSValue) * numElementsAfterShiftRegion); + 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) @@ -746,11 +987,14 @@ bool JSArray::shiftCountWithArrayStorage(unsigned startIndex, unsigned count, Ar return true; } -bool JSArray::shiftCountWithAnyIndexingType(ExecState* exec, unsigned startIndex, unsigned count) +bool JSArray::shiftCountWithAnyIndexingType(ExecState* exec, unsigned& startIndex, unsigned count) { + VM& vm = exec->vm(); RELEASE_ASSERT(count > 0); + + Butterfly* butterfly = m_butterfly.get(); - switch (structure()->indexingType()) { + switch (indexingType()) { case ArrayClass: return true; @@ -760,78 +1004,84 @@ bool JSArray::shiftCountWithAnyIndexingType(ExecState* exec, unsigned startIndex case ArrayWithInt32: case ArrayWithContiguous: { - unsigned oldLength = m_butterfly->publicLength(); + unsigned oldLength = 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(startIndex, count, ensureArrayStorage(exec->vm())); + 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; - for (unsigned i = startIndex; i < end; ++i) { - JSValue v = m_butterfly->contiguous()[i + count].get(); - if (UNLIKELY(!v)) - return shiftCountWithArrayStorage(startIndex, count, ensureArrayStorage(exec->vm())); + if (this->structure(vm)->holesMustForwardToPrototype(vm)) { + for (unsigned i = startIndex; i < end; ++i) { + JSValue v = butterfly->contiguous()[i + count].get(); + if (UNLIKELY(!v)) { + startIndex = i; + return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm)); + } + butterfly->contiguous()[i].setWithoutWriteBarrier(v); + } + } else { + memmove(butterfly->contiguous().data() + startIndex, + butterfly->contiguous().data() + startIndex + count, + sizeof(JSValue) * (end - startIndex)); } - for (unsigned i = startIndex; i < end; ++i) { - JSValue v = m_butterfly->contiguous()[i + count].get(); - ASSERT(v); - // 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(); + butterfly->contiguous()[i].clear(); + + butterfly->setPublicLength(oldLength - count); + + // Our memmoving of values around in the array could have concealed some of them from + // the collector. Let's make sure that the collector scans this object again. + vm.heap.writeBarrier(this); - m_butterfly->setPublicLength(oldLength - count); return true; } case ArrayWithDouble: { - unsigned oldLength = m_butterfly->publicLength(); + unsigned oldLength = 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(startIndex, count, ensureArrayStorage(exec->vm())); + 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; - for (unsigned i = startIndex; i < end; ++i) { - double v = m_butterfly->contiguousDouble()[i + count]; - if (UNLIKELY(v != v)) - return shiftCountWithArrayStorage(startIndex, count, ensureArrayStorage(exec->vm())); - } - - for (unsigned i = startIndex; i < end; ++i) { - double v = m_butterfly->contiguousDouble()[i + count]; - ASSERT(v == v); - // 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; + if (this->structure(vm)->holesMustForwardToPrototype(vm)) { + for (unsigned i = startIndex; i < end; ++i) { + double v = butterfly->contiguousDouble()[i + count]; + if (UNLIKELY(v != v)) { + startIndex = i; + return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm)); + } + butterfly->contiguousDouble()[i] = v; + } + } else { + memmove(butterfly->contiguousDouble().data() + startIndex, + butterfly->contiguousDouble().data() + startIndex + count, + sizeof(JSValue) * (end - startIndex)); } for (unsigned i = end; i < oldLength; ++i) - m_butterfly->contiguousDouble()[i] = QNaN; + butterfly->contiguousDouble()[i] = PNaN; - m_butterfly->setPublicLength(oldLength - count); + butterfly->setPublicLength(oldLength - count); return true; } case ArrayWithArrayStorage: case ArrayWithSlowPutArrayStorage: - return shiftCountWithArrayStorage(startIndex, count, arrayStorage()); + return shiftCountWithArrayStorage(vm, startIndex, count, arrayStorage()); default: CRASH(); @@ -842,31 +1092,39 @@ bool JSArray::shiftCountWithAnyIndexingType(ExecState* exec, unsigned startIndex // Returns true if the unshift can be handled, false to fallback. bool JSArray::unshiftCountWithArrayStorage(ExecState* exec, unsigned startIndex, unsigned count, ArrayStorage* storage) { + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + 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 (length != storage->m_numValuesInVector || storage->inSparseMode() || shouldUseSlowPut(structure()->indexingType())) + if (storage->hasHoles() || storage->inSparseMode() || shouldUseSlowPut(indexingType())) return false; bool moveFront = !startIndex || startIndex < length / 2; unsigned vectorLength = storage->vectorLength(); + // Need to have GC deferred around the unshiftCountSlowCase(), since that leaves the butterfly in + // a weird state: some parts of it will be left uninitialized, which we will fill in here. + DeferGC deferGC(vm.heap); + auto locker = holdLock(*this); + 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); + setButterfly(vm, newButterfly); } else if (!moveFront && vectorLength - length >= count) storage = storage->butterfly()->arrayStorage(); - else if (unshiftCountSlowCase(exec->vm(), moveFront, count)) + else if (unshiftCountSlowCase(locker, vm, deferGC, moveFront, count)) storage = arrayStorage(); else { - throwOutOfMemoryError(exec); + throwOutOfMemoryError(exec, scope); return true; } @@ -881,12 +1139,18 @@ bool JSArray::unshiftCountWithArrayStorage(ExecState* exec, unsigned startIndex, 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()) { + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + Butterfly* butterfly = m_butterfly.get(); + + switch (indexingType()) { case ArrayClass: case ArrayWithUndecided: // We could handle this. But it shouldn't ever come up, so we won't. @@ -894,29 +1158,41 @@ bool JSArray::unshiftCountWithAnyIndexingType(ExecState* exec, unsigned startInd case ArrayWithInt32: case ArrayWithContiguous: { - unsigned oldLength = m_butterfly->publicLength(); + unsigned oldLength = 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())); + if (oldLength - startIndex >= MIN_SPARSE_ARRAY_INDEX) { + scope.release(); + return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(vm)); + } - ensureLength(exec->vm(), oldLength + count); + if (!ensureLength(vm, oldLength + count)) { + throwOutOfMemoryError(exec, scope); + return false; + } + butterfly = m_butterfly.get(); // 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())); + JSValue v = butterfly->contiguous()[i].get(); + if (UNLIKELY(!v)) { + scope.release(); + return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(vm)); + } } for (unsigned i = oldLength; i-- > startIndex;) { - JSValue v = m_butterfly->contiguous()[i].get(); + JSValue v = butterfly->contiguous()[i].get(); ASSERT(v); - m_butterfly->contiguous()[i + count].setWithoutWriteBarrier(v); + butterfly->contiguous()[i + count].setWithoutWriteBarrier(v); } + // Our memmoving of values around in the array could have concealed some of them from + // the collector. Let's make sure that the collector scans this object again. + vm.heap.writeBarrier(this); + // 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 @@ -926,27 +1202,35 @@ bool JSArray::unshiftCountWithAnyIndexingType(ExecState* exec, unsigned startInd } case ArrayWithDouble: { - unsigned oldLength = m_butterfly->publicLength(); + unsigned oldLength = 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())); + if (oldLength - startIndex >= MIN_SPARSE_ARRAY_INDEX) { + scope.release(); + return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(vm)); + } - ensureLength(exec->vm(), oldLength + count); + if (!ensureLength(vm, oldLength + count)) { + throwOutOfMemoryError(exec, scope); + return false; + } + butterfly = m_butterfly.get(); // 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())); + double v = butterfly->contiguousDouble()[i]; + if (UNLIKELY(v != v)) { + scope.release(); + return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(vm)); + } } for (unsigned i = oldLength; i-- > startIndex;) { - double v = m_butterfly->contiguousDouble()[i]; + double v = butterfly->contiguousDouble()[i]; ASSERT(v == v); - m_butterfly->contiguousDouble()[i + count] = v; + butterfly->contiguousDouble()[i + count] = v; } // NOTE: we're leaving being garbage in the part of the array that we shifted out @@ -959,6 +1243,7 @@ bool JSArray::unshiftCountWithAnyIndexingType(ExecState* exec, unsigned startInd case ArrayWithArrayStorage: case ArrayWithSlowPutArrayStorage: + scope.release(); return unshiftCountWithArrayStorage(exec, startIndex, count, arrayStorage()); default: @@ -967,526 +1252,15 @@ bool JSArray::unshiftCountWithAnyIndexingType(ExecState* exec, unsigned startInd } } -static int compareNumbersForQSortWithInt32(const void* a, const void* b) -{ - int32_t ia = static_cast(a)->asInt32(); - int32_t ib = static_cast(b)->asInt32(); - return ia - ib; -} - -static int compareNumbersForQSortWithDouble(const void* a, const void* b) -{ - double da = *static_cast(a); - double db = *static_cast(b); - return (da > db) - (da < db); -} - -static int compareNumbersForQSort(const void* a, const void* b) -{ - double da = static_cast(a)->asNumber(); - double db = static_cast(b)->asNumber(); - return (da > db) - (da < db); -} - -static int compareByStringPairForQSort(const void* a, const void* b) -{ - const ValueStringPair* va = static_cast(a); - const ValueStringPair* vb = static_cast(b); - return codePointCompare(va->second, vb->second); -} - -template -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( - lengthNotIncludingUndefined, - newRelevantLength); - - ContiguousJSValues data = indexingData(); - - 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) == sizeof(double)); - break; - - default: - compare = compareNumbersForQSort; - break; - } - ASSERT(data.length() >= newRelevantLength); - qsort(data.data(), newRelevantLength, sizeof(WriteBarrier), 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(exec, compareFunction, callType, callData); - break; - - case ArrayWithDouble: - sortNumericVector(exec, compareFunction, callType, callData); - break; - - case ArrayWithContiguous: - sortNumericVector(exec, compareFunction, callType, callData); - return; - - case ArrayWithArrayStorage: - sortNumericVector(exec, compareFunction, callType, callData); - return; - - default: - CRASH(); - return; - } -} - -template struct ContiguousTypeAccessor { - typedef WriteBarrier Type; - static JSValue getAsValue(ContiguousData data, size_t i) { return data[i].get(); } - static void setWithValue(VM& vm, JSArray* thisValue, ContiguousData data, size_t i, JSValue value) - { - data[i].set(vm, thisValue, value); - } - static void replaceDataReference(ContiguousData* outData, ContiguousJSValues inData) - { - *outData = inData; - } -}; - -template <> struct ContiguousTypeAccessor { - typedef double Type; - static JSValue getAsValue(ContiguousData data, size_t i) { ASSERT(data[i] == data[i]); return JSValue(JSValue::EncodeAsDouble, data[i]); } - static void setWithValue(VM&, JSArray*, ContiguousData data, size_t i, JSValue value) - { - data[i] = value.asNumber(); - } - static NO_RETURN_DUE_TO_CRASH void replaceDataReference(ContiguousData*, ContiguousJSValues) - { - RELEASE_ASSERT_WITH_MESSAGE(0, "Inconsistent indexing types during compact array sort."); - } -}; - - -template -void JSArray::sortCompactedVector(ExecState* exec, ContiguousData data, unsigned relevantLength) -{ - if (!relevantLength) - return; - - VM& vm = exec->vm(); - - // 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 values(relevantLength); - if (!values.begin()) { - throwOutOfMemoryError(exec); - return; - } - - Heap::heap(this)->pushTempSortVector(&values); - - bool isSortingPrimitiveValues = true; - - for (size_t i = 0; i < relevantLength; i++) { - JSValue value = ContiguousTypeAccessor::getAsValue(data, i); - ASSERT(indexingType != ArrayWithInt32 || value.isInt32()); - ASSERT(!value.isUndefined()); - values[i].first = value; - if (indexingType != ArrayWithDouble && indexingType != ArrayWithInt32) - isSortingPrimitiveValues = isSortingPrimitiveValues && value.isPrimitive(); - } - - // 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(vm, relevantLength); - break; - - case ArrayWithArrayStorage: - if (arrayStorage()->vectorLength() < relevantLength) { - increaseVectorLength(exec->vm(), relevantLength); - ContiguousTypeAccessor::replaceDataReference(&data, arrayStorage()->vector()); - } - if (arrayStorage()->length() < relevantLength) - arrayStorage()->setLength(relevantLength); - break; - - default: - CRASH(); - } - - for (size_t i = 0; i < relevantLength; i++) - ContiguousTypeAccessor::setWithValue(vm, this, data, i, 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( - lengthNotIncludingUndefined, newRelevantLength); - - sortCompactedVector( - exec, m_butterfly->contiguousInt32(), lengthNotIncludingUndefined); - return; - } - - case ArrayWithDouble: { - unsigned lengthNotIncludingUndefined; - unsigned newRelevantLength; - compactForSorting( - lengthNotIncludingUndefined, newRelevantLength); - - sortCompactedVector( - exec, m_butterfly->contiguousDouble(), lengthNotIncludingUndefined); - return; - } - - case ArrayWithContiguous: { - unsigned lengthNotIncludingUndefined; - unsigned newRelevantLength; - compactForSorting( - lengthNotIncludingUndefined, newRelevantLength); - - sortCompactedVector( - exec, m_butterfly->contiguous(), lengthNotIncludingUndefined); - return; - } - - case ArrayWithArrayStorage: { - unsigned lengthNotIncludingUndefined; - unsigned newRelevantLength; - compactForSorting( - lengthNotIncludingUndefined, newRelevantLength); - ArrayStorage* storage = m_butterfly->arrayStorage(); - ASSERT(!storage->m_sparseMap); - - sortCompactedVector(exec, storage->vector(), lengthNotIncludingUndefined); - return; - } - - default: - RELEASE_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 m_nodes; - ExecState* m_exec; - JSValue m_compareFunction; - CallType m_compareCallType; - const CallData* m_compareCallData; - OwnPtr 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(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_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 -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(std::numeric_limits::max())); - if (m_butterfly->publicLength() > static_cast(std::numeric_limits::max())) - return; - - unsigned usedVectorLength = relevantLength(); - unsigned nodeCount = usedVectorLength; - - if (!nodeCount) - return; - - AVLTree 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(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(tree.abstractor().m_nodes.size())); - unsigned undefinedElementsThreshold = min(newestUsedVectorLength, newUsedVectorLength); - unsigned clearElementsThreshold = min(newestUsedVectorLength, usedVectorLength); - - // Copy the values back into m_storage. - AVLTree::Iterator iter; - iter.start_iter_least(tree); - VM& vm = exec->vm(); - for (unsigned i = 0; i < elementsToExtractThreshold; ++i) { - ASSERT(i < butterfly()->vectorLength()); - if (structure()->indexingType() == ArrayWithDouble) - butterfly()->contiguousDouble()[i] = tree.abstractor().m_nodes[*iter].value.asNumber(); - else - currentIndexingData()[i].set(vm, 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) { - ASSERT(i < butterfly()->vectorLength()); - currentIndexingData()[i].setUndefined(); - } - } - - // Ensure that unused values in the vector are zeroed out. - for (unsigned i = undefinedElementsThreshold; i < clearElementsThreshold; ++i) { - ASSERT(i < butterfly()->vectorLength()); - 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(exec, compareFunction, callType, callData); - return; - - case ArrayWithDouble: - sortVector(exec, compareFunction, callType, callData); - return; - - case ArrayWithContiguous: - sortVector(exec, compareFunction, callType, callData); - return; - - case ArrayWithArrayStorage: - sortVector(exec, compareFunction, callType, callData); - return; - - default: - RELEASE_ASSERT_NOT_REACHED(); - } -} - void JSArray::fillArgList(ExecState* exec, MarkedArgumentBuffer& args) { unsigned i = 0; unsigned vectorEnd; WriteBarrier* vector; + + Butterfly* butterfly = m_butterfly.get(); - switch (structure()->indexingType()) { + switch (indexingType()) { case ArrayClass: return; @@ -1498,16 +1272,16 @@ void JSArray::fillArgList(ExecState* exec, MarkedArgumentBuffer& args) case ArrayWithInt32: case ArrayWithContiguous: { - vectorEnd = m_butterfly->publicLength(); - vector = m_butterfly->contiguous().data(); + vectorEnd = butterfly->publicLength(); + vector = butterfly->contiguous().data(); break; } case ArrayWithDouble: { vector = 0; vectorEnd = 0; - for (; i < m_butterfly->publicLength(); ++i) { - double v = butterfly()->contiguousDouble()[i]; + for (; i < butterfly->publicLength(); ++i) { + double v = butterfly->contiguousDouble()[i]; if (v != v) break; args.append(JSValue(JSValue::EncodeAsDouble, v)); @@ -1516,7 +1290,7 @@ void JSArray::fillArgList(ExecState* exec, MarkedArgumentBuffer& args) } case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES: { - ArrayStorage* storage = m_butterfly->arrayStorage(); + ArrayStorage* storage = butterfly->arrayStorage(); vector = storage->m_vector; vectorEnd = min(storage->length(), storage->vectorLength()); @@ -1525,9 +1299,11 @@ void JSArray::fillArgList(ExecState* exec, MarkedArgumentBuffer& args) default: CRASH(); +#if COMPILER_QUIRK(CONSIDERS_UNREACHABLE_CODE) vector = 0; vectorEnd = 0; break; +#endif } for (; i < vectorEnd; ++i) { @@ -1536,19 +1312,27 @@ void JSArray::fillArgList(ExecState* exec, MarkedArgumentBuffer& args) 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, CallFrame* callFrame, uint32_t length) +void JSArray::copyToArguments(ExecState* exec, VirtualRegister firstElementDest, unsigned offset, unsigned length) { - unsigned i = 0; + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + unsigned i = offset; WriteBarrier* 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 (structure()->indexingType()) { + + Butterfly* butterfly = m_butterfly.get(); + switch (indexingType()) { case ArrayClass: return; @@ -1560,26 +1344,26 @@ void JSArray::copyToArguments(ExecState* exec, CallFrame* callFrame, uint32_t le case ArrayWithInt32: case ArrayWithContiguous: { - vector = m_butterfly->contiguous().data(); - vectorEnd = m_butterfly->publicLength(); + vector = butterfly->contiguous().data(); + vectorEnd = 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]; + for (; i < butterfly->publicLength(); ++i) { + ASSERT(i < butterfly->vectorLength()); + double v = butterfly->contiguousDouble()[i]; if (v != v) break; - callFrame->setArgument(i, JSValue(JSValue::EncodeAsDouble, v)); + exec->r(firstElementDest + i - offset) = JSValue(JSValue::EncodeAsDouble, v); } break; } case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES: { - ArrayStorage* storage = m_butterfly->arrayStorage(); + ArrayStorage* storage = butterfly->arrayStorage(); vector = storage->m_vector; vectorEnd = min(length, storage->vectorLength()); break; @@ -1587,111 +1371,24 @@ void JSArray::copyToArguments(ExecState* exec, CallFrame* callFrame, uint32_t le default: CRASH(); +#if COMPILER_QUIRK(CONSIDERS_UNREACHABLE_CODE) vector = 0; vectorEnd = 0; break; +#endif } for (; i < vectorEnd; ++i) { WriteBarrier& v = vector[i]; if (!v) break; - callFrame->setArgument(i, v.get()); + exec->r(firstElementDest + i - offset) = v.get(); } - for (; i < length; ++i) - callFrame->setArgument(i, get(exec, i)); -} - -template -void JSArray::compactForSorting(unsigned& numDefined, unsigned& newRelevantLength) -{ - ASSERT(!inSparseIndexingMode()); - ASSERT(indexingType == structure()->indexingType()); - - unsigned myRelevantLength = relevantLength(); - - numDefined = 0; - unsigned numUndefined = 0; - - for (; numDefined < myRelevantLength; ++numDefined) { - ASSERT(numDefined < m_butterfly->vectorLength()); - 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()[numDefined].get(); - if (!v || v.isUndefined()) - break; - } - - for (unsigned i = numDefined; i < myRelevantLength; ++i) { - ASSERT(i < m_butterfly->vectorLength()); - if (indexingType == ArrayWithInt32) { - JSValue v = m_butterfly->contiguousInt32()[i].get(); - if (!v) - continue; - ASSERT(v.isInt32()); - ASSERT(numDefined < m_butterfly->vectorLength()); - m_butterfly->contiguousInt32()[numDefined++].setWithoutWriteBarrier(v); - continue; - } - if (indexingType == ArrayWithDouble) { - double v = m_butterfly->contiguousDouble()[i]; - if (v != v) - continue; - ASSERT(numDefined < m_butterfly->vectorLength()); - m_butterfly->contiguousDouble()[numDefined++] = v; - continue; - } - JSValue v = indexingData()[i].get(); - if (v) { - if (v.isUndefined()) - ++numUndefined; - else { - ASSERT(numDefined < m_butterfly->vectorLength()); - indexingData()[numDefined++].setWithoutWriteBarrier(v); - } - } - } - - newRelevantLength = numDefined + numUndefined; - - if (hasArrayStorage(indexingType)) - RELEASE_ASSERT(!arrayStorage()->m_sparseMap); - - switch (indexingType) { - case ArrayWithInt32: - case ArrayWithDouble: - RELEASE_ASSERT(numDefined == newRelevantLength); - break; - - default: - for (unsigned i = numDefined; i < newRelevantLength; ++i) { - ASSERT(i < m_butterfly->vectorLength()); - indexingData()[i].setUndefined(); - } - break; + for (; i < length; ++i) { + exec->r(firstElementDest + i - offset) = get(exec, i); + RETURN_IF_EXCEPTION(scope, void()); } - for (unsigned i = newRelevantLength; i < myRelevantLength; ++i) { - ASSERT(i < m_butterfly->vectorLength()); - if (indexingType == ArrayWithDouble) - m_butterfly->contiguousDouble()[i] = QNaN; - else - indexingData()[i].clear(); - } - - if (hasArrayStorage(indexingType)) - arrayStorage()->m_numValuesInVector = newRelevantLength; } } // namespace JSC -- cgit v1.2.1