/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef builtin_Sorting_h #define builtin_Sorting_h #include "mozilla/Attributes.h" #include "js/GCVector.h" #include "vm/JSObject.h" // Code used by Array.prototype.sort and %TypedArray%.prototype.sort to sort // objects based on a user-defined comparator function. namespace js { // Note: we use uint32_t because the JIT code uses branch32. enum class ArraySortResult : uint32_t { Failure, Done, CallJS, CallJSSameRealmNoRectifier }; enum class ArraySortKind { Array, TypedArray, }; // We use a JIT trampoline to optimize sorting with a comparator function. The // trampoline frame has an ArraySortData instance that contains all state used // by the sorting algorithm. The sorting algorithm is implemented as a C++ // "generator" that can yield to the trampoline to perform a fast JIT => JIT // call to the comparator function. When the comparator function returns, the // trampoline calls back into C++ to resume the sorting algorithm. // // ArraySortData stores the JS Values in a js::Vector. To ensure we don't leak // its memory, we have debug assertions to check that for each C++ constructor // call we call |freeMallocData| exactly once. C++ code calls |freeMallocData| // when it's done sorting and the JIT exception handler calls it when unwinding // the trampoline frame. class ArraySortData { public: enum class ComparatorKind : uint8_t { Unoptimized, JS, JSSameRealmNoRectifier, }; // Insertion sort is used if the length is <= InsertionSortMaxLength. static constexpr size_t InsertionSortMaxLength = 8; static constexpr size_t ComparatorActualArgs = 2; using ValueVector = GCVector; protected: // Silence Clang warning about unused private fields. // Data for the comparator call. These fields must match the JitFrameLayout // to let us perform efficient calls to the comparator from JIT code. // This is asserted in the JIT trampoline code. // callArgs[0] is also used to store the return value of the sort function and // the comparator. uintptr_t descriptor_; JSObject* comparator_ = nullptr; Value thisv; Value callArgs[ComparatorActualArgs]; private: ValueVector vec; Value item; JSContext* cx_; JSObject* obj_ = nullptr; Value* list; Value* out; // The value of the .length property. uint32_t length; // The number of items to sort. Can be less than |length| if the object has // holes. uint32_t denseLen; uint32_t windowSize; uint32_t start; uint32_t mid; uint32_t end; uint32_t i, j, k; // The state value determines where we resume in sortWithComparator. enum class State : uint8_t { Initial, InsertionSortCall1, InsertionSortCall2, MergeSortCall1, MergeSortCall2 }; State state = State::Initial; ComparatorKind comparatorKind_; // Optional padding to ensure proper alignment of the comparator JIT frame. #if !defined(JS_64BIT) && !defined(DEBUG) protected: // Silence Clang warning about unused private field. size_t padding; #endif private: // Merge sort requires extra scratch space in the vector. Insertion sort // should be used for short arrays that fit in the vector's inline storage, to // avoid extra malloc calls. static_assert(decltype(vec)::InlineLength <= InsertionSortMaxLength); template static MOZ_ALWAYS_INLINE ArraySortResult sortWithComparatorShared(ArraySortData* d); public: explicit inline ArraySortData(JSContext* cx); void MOZ_ALWAYS_INLINE init(JSObject* obj, JSObject* comparator, ValueVector&& vec, uint32_t length, uint32_t denseLen); JSContext* cx() const { return cx_; } JSObject* comparator() const { MOZ_ASSERT(comparator_); return comparator_; } Value returnValue() const { return callArgs[0]; } void setReturnValue(JSObject* obj) { callArgs[0].setObject(*obj); } Value comparatorArg(size_t index) { MOZ_ASSERT(index < ComparatorActualArgs); return callArgs[index]; } Value comparatorThisValue() const { return thisv; } Value comparatorReturnValue() const { return callArgs[0]; } void setComparatorArgs(const Value& x, const Value& y) { callArgs[0] = x; callArgs[1] = y; } void setComparatorReturnValue(const Value& v) { callArgs[0] = v; } ComparatorKind comparatorKind() const { return comparatorKind_; } static ArraySortResult sortArrayWithComparator(ArraySortData* d); static ArraySortResult sortTypedArrayWithComparator(ArraySortData* d); inline void freeMallocData(); void trace(JSTracer* trc); static constexpr int32_t offsetOfDescriptor() { return offsetof(ArraySortData, descriptor_); } static constexpr int32_t offsetOfComparator() { return offsetof(ArraySortData, comparator_); } static constexpr int32_t offsetOfComparatorReturnValue() { return offsetof(ArraySortData, callArgs[0]); } static constexpr int32_t offsetOfComparatorThis() { return offsetof(ArraySortData, thisv); } static constexpr int32_t offsetOfComparatorArgs() { return offsetof(ArraySortData, callArgs); } }; ArraySortResult CallComparatorSlow(ArraySortData* d, const Value& x, const Value& y); } // namespace js #endif /* builtin_Sorting_h */