Sorting.h (5641B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef builtin_Sorting_h 8 #define builtin_Sorting_h 9 10 #include "mozilla/Attributes.h" 11 12 #include "js/GCVector.h" 13 #include "vm/JSObject.h" 14 15 // Code used by Array.prototype.sort and %TypedArray%.prototype.sort to sort 16 // objects based on a user-defined comparator function. 17 18 namespace js { 19 20 // Note: we use uint32_t because the JIT code uses branch32. 21 enum class ArraySortResult : uint32_t { 22 Failure, 23 Done, 24 CallJS, 25 CallJSSameRealmNoUnderflow 26 }; 27 28 enum class ArraySortKind { 29 Array, 30 TypedArray, 31 }; 32 33 // We use a JIT trampoline to optimize sorting with a comparator function. The 34 // trampoline frame has an ArraySortData instance that contains all state used 35 // by the sorting algorithm. The sorting algorithm is implemented as a C++ 36 // "generator" that can yield to the trampoline to perform a fast JIT => JIT 37 // call to the comparator function. When the comparator function returns, the 38 // trampoline calls back into C++ to resume the sorting algorithm. 39 // 40 // ArraySortData stores the JS Values in a js::Vector. To ensure we don't leak 41 // its memory, we have debug assertions to check that for each C++ constructor 42 // call we call |freeMallocData| exactly once. C++ code calls |freeMallocData| 43 // when it's done sorting and the JIT exception handler calls it when unwinding 44 // the trampoline frame. 45 class alignas(8) ArraySortData { 46 public: 47 enum class ComparatorKind : uint8_t { 48 Unoptimized, 49 JS, 50 JSSameRealmNoUnderflow, 51 }; 52 53 // Insertion sort is used if the length is <= InsertionSortMaxLength. 54 static constexpr size_t InsertionSortMaxLength = 8; 55 56 static constexpr size_t ComparatorActualArgs = 2; 57 58 using ValueVector = GCVector<Value, 8, SystemAllocPolicy>; 59 60 protected: // Silence Clang warning about unused private fields. 61 // Data for the comparator call. These fields must match the JitFrameLayout 62 // to let us perform efficient calls to the comparator from JIT code. 63 // This is asserted in the JIT trampoline code. 64 // callArgs[0] is also used to store the return value of the sort function and 65 // the comparator. 66 uintptr_t descriptor_; 67 JSObject* comparator_ = nullptr; 68 Value thisv; 69 Value callArgs[ComparatorActualArgs]; 70 71 private: 72 ValueVector vec; 73 Value item; 74 JSContext* cx_; 75 JSObject* obj_ = nullptr; 76 77 Value* list; 78 Value* out; 79 80 // The value of the .length property. 81 uint32_t length; 82 83 // The number of items to sort. Can be less than |length| if the object has 84 // holes. 85 uint32_t denseLen; 86 87 uint32_t windowSize; 88 uint32_t start; 89 uint32_t mid; 90 uint32_t end; 91 uint32_t i, j, k; 92 93 // The state value determines where we resume in sortWithComparator. 94 enum class State : uint8_t { 95 Initial, 96 InsertionSortCall1, 97 InsertionSortCall2, 98 MergeSortCall1, 99 MergeSortCall2 100 }; 101 State state = State::Initial; 102 ComparatorKind comparatorKind_; 103 104 // Optional padding to ensure proper alignment of the comparator JIT frame. 105 #if !defined(JS_64BIT) && !defined(DEBUG) 106 protected: // Silence Clang warning about unused private field. 107 uint32_t padding[2]; 108 #endif 109 110 private: 111 // Merge sort requires extra scratch space in the vector. Insertion sort 112 // should be used for short arrays that fit in the vector's inline storage, to 113 // avoid extra malloc calls. 114 static_assert(decltype(vec)::InlineLength <= InsertionSortMaxLength); 115 116 template <ArraySortKind Kind> 117 static MOZ_ALWAYS_INLINE ArraySortResult 118 sortWithComparatorShared(ArraySortData* d); 119 120 public: 121 explicit inline ArraySortData(JSContext* cx); 122 123 void MOZ_ALWAYS_INLINE init(JSObject* obj, JSObject* comparator, 124 ValueVector&& vec, uint32_t length, 125 uint32_t denseLen); 126 127 JSContext* cx() const { return cx_; } 128 129 JSObject* comparator() const { 130 MOZ_ASSERT(comparator_); 131 return comparator_; 132 } 133 134 Value returnValue() const { return callArgs[0]; } 135 void setReturnValue(JSObject* obj) { callArgs[0].setObject(*obj); } 136 137 Value comparatorArg(size_t index) { 138 MOZ_ASSERT(index < ComparatorActualArgs); 139 return callArgs[index]; 140 } 141 Value comparatorThisValue() const { return thisv; } 142 Value comparatorReturnValue() const { return callArgs[0]; } 143 void setComparatorArgs(const Value& x, const Value& y) { 144 callArgs[0] = x; 145 callArgs[1] = y; 146 } 147 void setComparatorReturnValue(const Value& v) { callArgs[0] = v; } 148 149 ComparatorKind comparatorKind() const { return comparatorKind_; } 150 151 static ArraySortResult sortArrayWithComparator(ArraySortData* d); 152 static ArraySortResult sortTypedArrayWithComparator(ArraySortData* d); 153 154 inline void freeMallocData(); 155 void trace(JSTracer* trc); 156 157 static constexpr int32_t offsetOfDescriptor() { 158 return offsetof(ArraySortData, descriptor_); 159 } 160 static constexpr int32_t offsetOfComparator() { 161 return offsetof(ArraySortData, comparator_); 162 } 163 static constexpr int32_t offsetOfComparatorReturnValue() { 164 return offsetof(ArraySortData, callArgs[0]); 165 } 166 static constexpr int32_t offsetOfComparatorThis() { 167 return offsetof(ArraySortData, thisv); 168 } 169 static constexpr int32_t offsetOfComparatorArgs() { 170 return offsetof(ArraySortData, callArgs); 171 } 172 }; 173 174 ArraySortResult CallComparatorSlow(ArraySortData* d, const Value& x, 175 const Value& y); 176 177 } // namespace js 178 179 #endif /* builtin_Sorting_h */