summaryrefslogtreecommitdiffstats
path: root/JavaScriptCore/kjs/array_instance.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'JavaScriptCore/kjs/array_instance.cpp')
-rw-r--r--JavaScriptCore/kjs/array_instance.cpp605
1 files changed, 605 insertions, 0 deletions
diff --git a/JavaScriptCore/kjs/array_instance.cpp b/JavaScriptCore/kjs/array_instance.cpp
new file mode 100644
index 0000000..1118f40
--- /dev/null
+++ b/JavaScriptCore/kjs/array_instance.cpp
@@ -0,0 +1,605 @@
+/*
+ * Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
+ * Copyright (C) 2003, 2007, 2008 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 "array_instance.h"
+
+#include "JSGlobalObject.h"
+#include "PropertyNameArray.h"
+#include <wtf/Assertions.h>
+
+using std::min;
+
+namespace KJS {
+
+typedef HashMap<unsigned, JSValue*> SparseArrayValueMap;
+
+struct ArrayStorage {
+ unsigned m_numValuesInVector;
+ SparseArrayValueMap* m_sparseValueMap;
+ JSValue* m_vector[1];
+};
+
+// 0xFFFFFFFF is a bit weird -- is not an array index even though it's an integer
+static const unsigned maxArrayIndex = 0xFFFFFFFEU;
+
+// Our policy for when to use a vector and when to use a sparse map.
+// For all array indices under sparseArrayCutoff, we always use a vector.
+// When indices greater than sparseArrayCutoff are involved, we use a vector
+// as long as it is 1/8 full. If more sparse than that, we use a map.
+static const unsigned sparseArrayCutoff = 10000;
+static const unsigned minDensityMultiplier = 8;
+
+static const unsigned copyingSortCutoff = 50000;
+
+const ClassInfo ArrayInstance::info = {"Array", 0, 0};
+
+static inline size_t storageSize(unsigned vectorLength)
+{
+ return sizeof(ArrayStorage) - sizeof(JSValue*) + vectorLength * sizeof(JSValue*);
+}
+
+static inline unsigned increasedVectorLength(unsigned newLength)
+{
+ return (newLength * 3 + 1) / 2;
+}
+
+static inline bool isDenseEnoughForVector(unsigned length, unsigned numValues)
+{
+ return length / minDensityMultiplier <= numValues;
+}
+
+ArrayInstance::ArrayInstance(JSObject* prototype, unsigned initialLength)
+ : JSObject(prototype)
+{
+ unsigned initialCapacity = min(initialLength, sparseArrayCutoff);
+
+ m_length = initialLength;
+ m_vectorLength = initialCapacity;
+ m_storage = static_cast<ArrayStorage*>(fastZeroedMalloc(storageSize(initialCapacity)));
+
+ Collector::reportExtraMemoryCost(initialCapacity * sizeof(JSValue*));
+}
+
+ArrayInstance::ArrayInstance(JSObject* prototype, const List& list)
+ : JSObject(prototype)
+{
+ unsigned length = list.size();
+
+ m_length = length;
+ m_vectorLength = length;
+
+ ArrayStorage* storage = static_cast<ArrayStorage*>(fastMalloc(storageSize(length)));
+
+ storage->m_numValuesInVector = length;
+ storage->m_sparseValueMap = 0;
+
+ size_t i = 0;
+ List::const_iterator end = list.end();
+ for (List::const_iterator it = list.begin(); it != end; ++it, ++i)
+ storage->m_vector[i] = *it;
+
+ m_storage = storage;
+
+ // When the array is created non-empty, its cells are filled, so it's really no worse than
+ // a property map. Therefore don't report extra memory cost.
+}
+
+ArrayInstance::~ArrayInstance()
+{
+ delete m_storage->m_sparseValueMap;
+ fastFree(m_storage);
+}
+
+JSValue* ArrayInstance::getItem(unsigned i) const
+{
+ ASSERT(i <= maxArrayIndex);
+
+ ArrayStorage* storage = m_storage;
+
+ if (i < m_vectorLength) {
+ JSValue* value = storage->m_vector[i];
+ return value ? value : jsUndefined();
+ }
+
+ SparseArrayValueMap* map = storage->m_sparseValueMap;
+ if (!map)
+ return jsUndefined();
+
+ JSValue* value = map->get(i);
+ return value ? value : jsUndefined();
+}
+
+JSValue* ArrayInstance::lengthGetter(ExecState*, JSObject*, const Identifier&, const PropertySlot& slot)
+{
+ return jsNumber(static_cast<ArrayInstance*>(slot.slotBase())->m_length);
+}
+
+ALWAYS_INLINE bool ArrayInstance::inlineGetOwnPropertySlot(ExecState* exec, unsigned i, PropertySlot& slot)
+{
+ ArrayStorage* storage = m_storage;
+
+ if (i >= m_length) {
+ if (i > maxArrayIndex)
+ return getOwnPropertySlot(exec, Identifier::from(i), slot);
+ return false;
+ }
+
+ if (i < m_vectorLength) {
+ JSValue*& valueSlot = storage->m_vector[i];
+ if (valueSlot) {
+ slot.setValueSlot(this, &valueSlot);
+ return true;
+ }
+ } else if (SparseArrayValueMap* map = storage->m_sparseValueMap) {
+ if (i >= sparseArrayCutoff) {
+ SparseArrayValueMap::iterator it = map->find(i);
+ if (it != map->end()) {
+ slot.setValueSlot(this, &it->second);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool ArrayInstance::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
+{
+ if (propertyName == exec->propertyNames().length) {
+ slot.setCustom(this, lengthGetter);
+ return true;
+ }
+
+ bool isArrayIndex;
+ unsigned i = propertyName.toArrayIndex(&isArrayIndex);
+ if (isArrayIndex)
+ return inlineGetOwnPropertySlot(exec, i, slot);
+
+ return JSObject::getOwnPropertySlot(exec, propertyName, slot);
+}
+
+bool ArrayInstance::getOwnPropertySlot(ExecState* exec, unsigned i, PropertySlot& slot)
+{
+ return inlineGetOwnPropertySlot(exec, i, slot);
+}
+
+// ECMA 15.4.5.1
+void ArrayInstance::put(ExecState* exec, const Identifier& propertyName, JSValue* value)
+{
+ bool isArrayIndex;
+ unsigned i = propertyName.toArrayIndex(&isArrayIndex);
+ if (isArrayIndex) {
+ put(exec, i, value);
+ return;
+ }
+
+ if (propertyName == exec->propertyNames().length) {
+ unsigned newLength = value->toUInt32(exec);
+ if (value->toNumber(exec) != static_cast<double>(newLength)) {
+ throwError(exec, RangeError, "Invalid array length.");
+ return;
+ }
+ setLength(newLength);
+ return;
+ }
+
+ JSObject::put(exec, propertyName, value);
+}
+
+void ArrayInstance::put(ExecState* exec, unsigned i, JSValue* value)
+{
+ if (i > maxArrayIndex) {
+ put(exec, Identifier::from(i), value);
+ return;
+ }
+
+ ArrayStorage* storage = m_storage;
+
+ unsigned length = m_length;
+ if (i >= length) {
+ length = i + 1;
+ m_length = length;
+ }
+
+ if (i < m_vectorLength) {
+ JSValue*& valueSlot = storage->m_vector[i];
+ storage->m_numValuesInVector += !valueSlot;
+ valueSlot = value;
+ return;
+ }
+
+ if (i < sparseArrayCutoff) {
+ increaseVectorLength(i + 1);
+ storage = m_storage;
+ ++storage->m_numValuesInVector;
+ storage->m_vector[i] = value;
+ return;
+ }
+
+ SparseArrayValueMap* map = storage->m_sparseValueMap;
+ if (!map || map->isEmpty()) {
+ if (isDenseEnoughForVector(i + 1, storage->m_numValuesInVector + 1)) {
+ increaseVectorLength(i + 1);
+ storage = m_storage;
+ ++storage->m_numValuesInVector;
+ storage->m_vector[i] = value;
+ return;
+ }
+ if (!map) {
+ map = new SparseArrayValueMap;
+ storage->m_sparseValueMap = map;
+ }
+ map->set(i, value);
+ return;
+ }
+
+ unsigned newNumValuesInVector = storage->m_numValuesInVector + 1;
+ if (!isDenseEnoughForVector(i + 1, newNumValuesInVector)) {
+ map->set(i, value);
+ return;
+ }
+
+ unsigned newVectorLength = increasedVectorLength(i + 1);
+ for (unsigned j = m_vectorLength; j < newVectorLength; ++j)
+ newNumValuesInVector += map->contains(j);
+ newNumValuesInVector -= map->contains(i);
+ if (isDenseEnoughForVector(newVectorLength, newNumValuesInVector)) {
+ unsigned proposedNewNumValuesInVector = newNumValuesInVector;
+ while (true) {
+ unsigned proposedNewVectorLength = increasedVectorLength(newVectorLength + 1);
+ for (unsigned j = newVectorLength; j < proposedNewVectorLength; ++j)
+ proposedNewNumValuesInVector += map->contains(j);
+ if (!isDenseEnoughForVector(proposedNewVectorLength, proposedNewNumValuesInVector))
+ break;
+ newVectorLength = proposedNewVectorLength;
+ newNumValuesInVector = proposedNewNumValuesInVector;
+ }
+ }
+
+ storage = static_cast<ArrayStorage*>(fastRealloc(storage, storageSize(newVectorLength)));
+
+ unsigned vectorLength = m_vectorLength;
+ if (newNumValuesInVector == storage->m_numValuesInVector + 1) {
+ for (unsigned j = vectorLength; j < newVectorLength; ++j)
+ storage->m_vector[j] = 0;
+ map->remove(i);
+ } else {
+ for (unsigned j = vectorLength; j < newVectorLength; ++j)
+ storage->m_vector[j] = map->take(j);
+ }
+
+ storage->m_vector[i] = value;
+
+ m_vectorLength = newVectorLength;
+ storage->m_numValuesInVector = newNumValuesInVector;
+}
+
+bool ArrayInstance::deleteProperty(ExecState* exec, const Identifier& propertyName)
+{
+ bool isArrayIndex;
+ unsigned i = propertyName.toArrayIndex(&isArrayIndex);
+ if (isArrayIndex)
+ return deleteProperty(exec, i);
+
+ if (propertyName == exec->propertyNames().length)
+ return false;
+
+ return JSObject::deleteProperty(exec, propertyName);
+}
+
+bool ArrayInstance::deleteProperty(ExecState* exec, unsigned i)
+{
+ ArrayStorage* storage = m_storage;
+
+ if (i < m_vectorLength) {
+ JSValue*& valueSlot = storage->m_vector[i];
+ bool hadValue = valueSlot;
+ valueSlot = 0;
+ storage->m_numValuesInVector -= hadValue;
+ return hadValue;
+ }
+
+ if (SparseArrayValueMap* map = storage->m_sparseValueMap) {
+ if (i >= sparseArrayCutoff) {
+ SparseArrayValueMap::iterator it = map->find(i);
+ if (it != map->end()) {
+ map->remove(it);
+ return true;
+ }
+ }
+ }
+
+ if (i > maxArrayIndex)
+ return deleteProperty(exec, Identifier::from(i));
+
+ return false;
+}
+
+void ArrayInstance::getPropertyNames(ExecState* exec, PropertyNameArray& propertyNames)
+{
+ // FIXME: Filling PropertyNameArray with an identifier for every integer
+ // is incredibly inefficient for large arrays. We need a different approach.
+
+ ArrayStorage* storage = m_storage;
+
+ unsigned usedVectorLength = min(m_length, m_vectorLength);
+ for (unsigned i = 0; i < usedVectorLength; ++i) {
+ if (storage->m_vector[i])
+ propertyNames.add(Identifier::from(i));
+ }
+
+ if (SparseArrayValueMap* map = storage->m_sparseValueMap) {
+ SparseArrayValueMap::iterator end = map->end();
+ for (SparseArrayValueMap::iterator it = map->begin(); it != end; ++it)
+ propertyNames.add(Identifier::from(it->first));
+ }
+
+ JSObject::getPropertyNames(exec, propertyNames);
+}
+
+void ArrayInstance::increaseVectorLength(unsigned newLength)
+{
+ ArrayStorage* storage = m_storage;
+
+ unsigned vectorLength = m_vectorLength;
+ ASSERT(newLength > vectorLength);
+ unsigned newVectorLength = increasedVectorLength(newLength);
+
+ storage = static_cast<ArrayStorage*>(fastRealloc(storage, storageSize(newVectorLength)));
+ m_vectorLength = newVectorLength;
+
+ for (unsigned i = vectorLength; i < newVectorLength; ++i)
+ storage->m_vector[i] = 0;
+
+ m_storage = storage;
+}
+
+void ArrayInstance::setLength(unsigned newLength)
+{
+ ArrayStorage* storage = m_storage;
+
+ unsigned length = m_length;
+
+ if (newLength < length) {
+ unsigned usedVectorLength = min(length, m_vectorLength);
+ for (unsigned i = newLength; i < usedVectorLength; ++i) {
+ JSValue*& valueSlot = storage->m_vector[i];
+ bool hadValue = valueSlot;
+ valueSlot = 0;
+ storage->m_numValuesInVector -= hadValue;
+ }
+
+ if (SparseArrayValueMap* map = storage->m_sparseValueMap) {
+ SparseArrayValueMap copy = *map;
+ SparseArrayValueMap::iterator end = copy.end();
+ for (SparseArrayValueMap::iterator it = copy.begin(); it != end; ++it) {
+ if (it->first >= newLength)
+ map->remove(it->first);
+ }
+ if (map->isEmpty()) {
+ delete map;
+ storage->m_sparseValueMap = 0;
+ }
+ }
+ }
+
+ m_length = newLength;
+}
+
+void ArrayInstance::mark()
+{
+ JSObject::mark();
+
+ ArrayStorage* storage = m_storage;
+
+ unsigned usedVectorLength = min(m_length, m_vectorLength);
+ for (unsigned i = 0; i < usedVectorLength; ++i) {
+ JSValue* value = storage->m_vector[i];
+ if (value && !value->marked())
+ value->mark();
+ }
+
+ if (SparseArrayValueMap* map = storage->m_sparseValueMap) {
+ SparseArrayValueMap::iterator end = map->end();
+ for (SparseArrayValueMap::iterator it = map->begin(); it != end; ++it) {
+ JSValue* value = it->second;
+ if (!value->marked())
+ value->mark();
+ }
+ }
+}
+
+static int compareByStringPairForQSort(const void* a, const void* b)
+{
+ const std::pair<JSValue*, UString>* va = static_cast<const std::pair<JSValue*, UString>*>(a);
+ const std::pair<JSValue*, UString>* vb = static_cast<const std::pair<JSValue*, UString>*>(b);
+ return compare(va->second, vb->second);
+}
+
+static ExecState* execForCompareByStringForQSort = 0;
+static int compareByStringForQSort(const void* a, const void* b)
+{
+ ExecState* exec = execForCompareByStringForQSort;
+
+ JSValue* va = *static_cast<JSValue* const*>(a);
+ JSValue* vb = *static_cast<JSValue* const*>(b);
+ ASSERT(!va->isUndefined());
+ ASSERT(!vb->isUndefined());
+
+ return compare(va->toString(exec), vb->toString(exec));
+}
+
+void ArrayInstance::sort(ExecState* exec)
+{
+ unsigned lengthNotIncludingUndefined = compactForSorting();
+
+ if (lengthNotIncludingUndefined < copyingSortCutoff) {
+ // 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. For large arrays, we fall back to a qsort on the JavaScriptValues to avoid creating copies.
+
+ Vector<std::pair<JSValue*, UString> > values(lengthNotIncludingUndefined);
+ for (size_t i = 0; i < lengthNotIncludingUndefined; i++) {
+ JSValue* value = m_storage->m_vector[i];
+ ASSERT(!value->isUndefined());
+ values[i].first = value;
+ values[i].second = value->toString(exec);
+ }
+
+ // 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)
+ mergesort(values.begin(), values.size(), sizeof(std::pair<JSValue*, UString>), compareByStringPairForQSort);
+#else
+ qsort(values.begin(), values.size(), sizeof(std::pair<JSValue*, UString>), compareByStringPairForQSort);
+#endif
+ for (size_t i = 0; i < lengthNotIncludingUndefined; i++)
+ m_storage->m_vector[i] = values[i].first;
+ return;
+ }
+
+ ExecState* oldExec = execForCompareByStringForQSort;
+ execForCompareByStringForQSort = exec;
+ qsort(m_storage->m_vector, lengthNotIncludingUndefined, sizeof(JSValue*), compareByStringForQSort);
+ execForCompareByStringForQSort = oldExec;
+}
+
+struct CompareWithCompareFunctionArguments {
+ CompareWithCompareFunctionArguments(ExecState *e, JSObject *cf)
+ : exec(e)
+ , compareFunction(cf)
+ , globalObject(e->dynamicGlobalObject())
+ {
+ }
+
+ ExecState *exec;
+ JSObject *compareFunction;
+ List arguments;
+ JSGlobalObject* globalObject;
+};
+
+static CompareWithCompareFunctionArguments* compareWithCompareFunctionArguments = 0;
+
+static int compareWithCompareFunctionForQSort(const void* a, const void* b)
+{
+ CompareWithCompareFunctionArguments *args = compareWithCompareFunctionArguments;
+
+ JSValue* va = *static_cast<JSValue* const*>(a);
+ JSValue* vb = *static_cast<JSValue* const*>(b);
+ ASSERT(!va->isUndefined());
+ ASSERT(!vb->isUndefined());
+
+ args->arguments.clear();
+ args->arguments.append(va);
+ args->arguments.append(vb);
+ double compareResult = args->compareFunction->call
+ (args->exec, args->globalObject, args->arguments)->toNumber(args->exec);
+ return compareResult < 0 ? -1 : compareResult > 0 ? 1 : 0;
+}
+
+void ArrayInstance::sort(ExecState* exec, JSObject* compareFunction)
+{
+ size_t lengthNotIncludingUndefined = compactForSorting();
+
+ CompareWithCompareFunctionArguments* oldArgs = compareWithCompareFunctionArguments;
+ CompareWithCompareFunctionArguments args(exec, compareFunction);
+ compareWithCompareFunctionArguments = &args;
+
+#if HAVE(MERGESORT)
+ // Because mergesort usually does fewer compares, it is faster than qsort here.
+ // However, because it requires extra copies of the storage buffer, don't use it for very
+ // large arrays.
+
+ // FIXME: A tree sort using a perfectly balanced tree (e.g. an AVL tree) could do an even
+ // better job of minimizing compares.
+
+ if (lengthNotIncludingUndefined < copyingSortCutoff) {
+ // During the sort, we could do a garbage collect, and it's important to still
+ // have references to every object in the array for ArrayInstance::mark.
+ // The mergesort algorithm does not guarantee this, so we sort a copy rather
+ // than the original.
+ size_t size = storageSize(m_vectorLength);
+ ArrayStorage* copy = static_cast<ArrayStorage*>(fastMalloc(size));
+ memcpy(copy, m_storage, size);
+ mergesort(copy->m_vector, lengthNotIncludingUndefined, sizeof(JSValue*), compareWithCompareFunctionForQSort);
+ fastFree(m_storage);
+ m_storage = copy;
+ compareWithCompareFunctionArguments = oldArgs;
+ return;
+ }
+#endif
+
+ qsort(m_storage->m_vector, lengthNotIncludingUndefined, sizeof(JSValue*), compareWithCompareFunctionForQSort);
+ compareWithCompareFunctionArguments = oldArgs;
+}
+
+unsigned ArrayInstance::compactForSorting()
+{
+ ArrayStorage* storage = m_storage;
+
+ unsigned usedVectorLength = min(m_length, m_vectorLength);
+
+ unsigned numDefined = 0;
+ unsigned numUndefined = 0;
+
+ for (; numDefined < usedVectorLength; ++numDefined) {
+ JSValue* v = storage->m_vector[numDefined];
+ if (!v || v->isUndefined())
+ break;
+ }
+ for (unsigned i = numDefined; i < usedVectorLength; ++i) {
+ if (JSValue* v = storage->m_vector[i]) {
+ if (v->isUndefined())
+ ++numUndefined;
+ else
+ storage->m_vector[numDefined++] = v;
+ }
+ }
+
+ unsigned newUsedVectorLength = numDefined + numUndefined;
+
+ if (SparseArrayValueMap* map = storage->m_sparseValueMap) {
+ newUsedVectorLength += map->size();
+ if (newUsedVectorLength > m_vectorLength) {
+ increaseVectorLength(newUsedVectorLength);
+ storage = m_storage;
+ }
+
+ SparseArrayValueMap::iterator end = map->end();
+ for (SparseArrayValueMap::iterator it = map->begin(); it != end; ++it)
+ storage->m_vector[numDefined++] = it->second;
+
+ delete map;
+ storage->m_sparseValueMap = 0;
+ }
+
+ for (unsigned i = numDefined; i < newUsedVectorLength; ++i)
+ storage->m_vector[i] = jsUndefined();
+ for (unsigned i = newUsedVectorLength; i < usedVectorLength; ++i)
+ storage->m_vector[i] = 0;
+
+ return numDefined;
+}
+
+}