Sorting-inl.h (9476B)
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_inl_h 8 #define builtin_Sorting_inl_h 9 10 #include "builtin/Sorting.h" 11 12 #include "js/Conversions.h" 13 #include "vm/JSContext.h" 14 #include "vm/JSFunction.h" 15 #include "vm/JSObject.h" 16 17 namespace js { 18 19 void ArraySortData::init(JSObject* obj, JSObject* comparator, ValueVector&& vec, 20 uint32_t length, uint32_t denseLen) { 21 MOZ_ASSERT(!vec.empty(), "must have items to sort"); 22 MOZ_ASSERT(denseLen <= length); 23 24 obj_ = obj; 25 comparator_ = comparator; 26 27 this->length = length; 28 this->denseLen = denseLen; 29 this->vec = std::move(vec); 30 31 auto getComparatorKind = [](JSContext* cx, JSObject* comparator) { 32 if (!comparator->is<JSFunction>()) { 33 return ComparatorKind::Unoptimized; 34 } 35 JSFunction* fun = &comparator->as<JSFunction>(); 36 if (!fun->hasJitEntry() || fun->isClassConstructor()) { 37 return ComparatorKind::Unoptimized; 38 } 39 if (fun->realm() == cx->realm() && fun->nargs() <= ComparatorActualArgs) { 40 return ComparatorKind::JSSameRealmNoUnderflow; 41 } 42 return ComparatorKind::JS; 43 }; 44 comparatorKind_ = getComparatorKind(cx(), comparator); 45 } 46 47 ArraySortData::ArraySortData(JSContext* cx) : cx_(cx) { 48 #ifdef DEBUG 49 cx_->liveArraySortDataInstances++; 50 #endif 51 } 52 53 void ArraySortData::freeMallocData() { 54 vec.clearAndFree(); 55 #ifdef DEBUG 56 MOZ_ASSERT(cx_->liveArraySortDataInstances > 0); 57 cx_->liveArraySortDataInstances--; 58 #endif 59 } 60 61 template <ArraySortKind Kind> 62 static MOZ_ALWAYS_INLINE ArraySortResult 63 MaybeYieldToComparator(ArraySortData* d, const Value& x, const Value& y) { 64 if constexpr (Kind == ArraySortKind::Array) { 65 // https://tc39.es/ecma262/#sec-comparearrayelements 66 // 23.1.3.30.2 CompareArrayElements ( x, y, comparefn ) 67 68 // Steps 1-2. 69 if (x.isUndefined()) { 70 d->setComparatorReturnValue(Int32Value(y.isUndefined() ? 0 : 1)); 71 return ArraySortResult::Done; 72 } 73 74 // Step 3. 75 if (y.isUndefined()) { 76 d->setComparatorReturnValue(Int32Value(-1)); 77 return ArraySortResult::Done; 78 } 79 } else { 80 // https://tc39.es/ecma262/#sec-comparetypedarrayelements 81 // 23.2.4.7 CompareTypedArrayElements ( x, y, comparefn ) 82 83 // Step 1. 84 MOZ_ASSERT((x.isNumber() && y.isNumber()) || 85 (x.isBigInt() && y.isBigInt())); 86 } 87 88 // Yield to the JIT trampoline (or js::array_sort) if the comparator is a JS 89 // function we can call more efficiently from JIT code. 90 auto kind = d->comparatorKind(); 91 if (MOZ_LIKELY(kind != ArraySortData::ComparatorKind::Unoptimized)) { 92 d->setComparatorArgs(x, y); 93 return (kind == ArraySortData::ComparatorKind::JSSameRealmNoUnderflow) 94 ? ArraySortResult::CallJSSameRealmNoUnderflow 95 : ArraySortResult::CallJS; 96 } 97 return CallComparatorSlow(d, x, y); 98 } 99 100 static MOZ_ALWAYS_INLINE bool RvalIsLessOrEqual(ArraySortData* data, 101 bool* lessOrEqual) { 102 // https://tc39.es/ecma262/#sec-comparearrayelements 103 // 23.1.3.30.2 CompareArrayElements ( x, y, comparefn ) 104 // 105 // https://tc39.es/ecma262/#sec-comparetypedarrayelements 106 // 23.2.4.7 CompareTypedArrayElements ( x, y, comparefn ) 107 // 108 // Note: CompareTypedArrayElements step 2 is identical to CompareArrayElements 109 // step 4. 110 111 // Fast path for int32 return values. 112 Value rval = data->comparatorReturnValue(); 113 if (MOZ_LIKELY(rval.isInt32())) { 114 *lessOrEqual = rval.toInt32() <= 0; 115 return true; 116 } 117 118 // Step 4.a. 119 Rooted<Value> rvalRoot(data->cx(), rval); 120 double d; 121 if (MOZ_UNLIKELY(!ToNumber(data->cx(), rvalRoot, &d))) { 122 return false; 123 } 124 125 // Step 4.b-c. 126 *lessOrEqual = std::isnan(d) ? true : (d <= 0); 127 return true; 128 } 129 130 static MOZ_ALWAYS_INLINE void CopyValues(Value* out, const Value* list, 131 uint32_t start, uint32_t end) { 132 for (uint32_t i = start; i <= end; i++) { 133 out[i] = list[i]; 134 } 135 } 136 137 // static 138 template <ArraySortKind Kind> 139 ArraySortResult ArraySortData::sortWithComparatorShared(ArraySortData* d) { 140 auto& vec = d->vec; 141 142 // This function is like a generator that is called repeatedly from the JIT 143 // trampoline or js::array_sort. Resume the sorting algorithm where we left 144 // off before calling the comparator. 145 switch (d->state) { 146 case State::Initial: 147 break; 148 case State::InsertionSortCall1: 149 goto insertion_sort_call1; 150 case State::InsertionSortCall2: 151 goto insertion_sort_call2; 152 case State::MergeSortCall1: 153 goto merge_sort_call1; 154 case State::MergeSortCall2: 155 goto merge_sort_call2; 156 } 157 158 d->list = vec.begin(); 159 160 // Use insertion sort for small arrays. 161 if (d->denseLen <= InsertionSortMaxLength) { 162 for (d->i = 1; d->i < d->denseLen; d->i++) { 163 d->item = vec[d->i]; 164 d->j = d->i - 1; 165 do { 166 { 167 ArraySortResult res = 168 MaybeYieldToComparator<Kind>(d, vec[d->j], d->item); 169 if (res != ArraySortResult::Done) { 170 d->state = State::InsertionSortCall1; 171 return res; 172 } 173 } 174 insertion_sort_call1: 175 bool lessOrEqual; 176 if (!RvalIsLessOrEqual(d, &lessOrEqual)) { 177 return ArraySortResult::Failure; 178 } 179 if (lessOrEqual) { 180 break; 181 } 182 vec[d->j + 1] = vec[d->j]; 183 } while (d->j-- > 0); 184 vec[d->j + 1] = d->item; 185 } 186 } else { 187 static constexpr size_t InitialWindowSize = 4; 188 189 // Use insertion sort for initial ranges. 190 for (d->start = 0; d->start < d->denseLen - 1; 191 d->start += InitialWindowSize) { 192 d->end = 193 std::min<uint32_t>(d->start + InitialWindowSize - 1, d->denseLen - 1); 194 for (d->i = d->start + 1; d->i <= d->end; d->i++) { 195 d->item = vec[d->i]; 196 d->j = d->i - 1; 197 do { 198 { 199 ArraySortResult res = 200 MaybeYieldToComparator<Kind>(d, vec[d->j], d->item); 201 if (res != ArraySortResult::Done) { 202 d->state = State::InsertionSortCall2; 203 return res; 204 } 205 } 206 insertion_sort_call2: 207 bool lessOrEqual; 208 if (!RvalIsLessOrEqual(d, &lessOrEqual)) { 209 return ArraySortResult::Failure; 210 } 211 if (lessOrEqual) { 212 break; 213 } 214 vec[d->j + 1] = vec[d->j]; 215 } while (d->j-- > d->start); 216 vec[d->j + 1] = d->item; 217 } 218 } 219 220 // Merge sort. Set d->out to scratch space initially. 221 d->out = vec.begin() + d->denseLen; 222 for (d->windowSize = InitialWindowSize; d->windowSize < d->denseLen; 223 d->windowSize *= 2) { 224 for (d->start = 0; d->start < d->denseLen; 225 d->start += 2 * d->windowSize) { 226 // The midpoint between the two subarrays. 227 d->mid = d->start + d->windowSize - 1; 228 229 // To keep from going over the edge. 230 d->end = std::min<uint32_t>(d->start + 2 * d->windowSize - 1, 231 d->denseLen - 1); 232 233 // Merge comparator-sorted slices list[start..<=mid] and 234 // list[mid+1..<=end], storing the merged sequence in out[start..<=end]. 235 236 // Skip lopsided runs to avoid doing useless work. 237 if (d->mid >= d->end) { 238 CopyValues(d->out, d->list, d->start, d->end); 239 continue; 240 } 241 242 // Skip calling the comparator if the sub-list is already sorted. 243 { 244 ArraySortResult res = MaybeYieldToComparator<Kind>( 245 d, d->list[d->mid], d->list[d->mid + 1]); 246 if (res != ArraySortResult::Done) { 247 d->state = State::MergeSortCall1; 248 return res; 249 } 250 } 251 merge_sort_call1: 252 bool lessOrEqual; 253 if (!RvalIsLessOrEqual(d, &lessOrEqual)) { 254 return ArraySortResult::Failure; 255 } 256 if (lessOrEqual) { 257 CopyValues(d->out, d->list, d->start, d->end); 258 continue; 259 } 260 261 d->i = d->start; 262 d->j = d->mid + 1; 263 d->k = d->start; 264 265 while (d->i <= d->mid && d->j <= d->end) { 266 { 267 ArraySortResult res = 268 MaybeYieldToComparator<Kind>(d, d->list[d->i], d->list[d->j]); 269 if (res != ArraySortResult::Done) { 270 d->state = State::MergeSortCall2; 271 return res; 272 } 273 } 274 merge_sort_call2: 275 bool lessOrEqual; 276 if (!RvalIsLessOrEqual(d, &lessOrEqual)) { 277 return ArraySortResult::Failure; 278 } 279 d->out[d->k++] = lessOrEqual ? d->list[d->i++] : d->list[d->j++]; 280 } 281 282 // Empty out any remaining elements. Use local variables to let the 283 // compiler generate more efficient code. 284 Value* out = d->out; 285 Value* list = d->list; 286 uint32_t k = d->k; 287 uint32_t mid = d->mid; 288 uint32_t end = d->end; 289 for (uint32_t i = d->i; i <= mid; i++) { 290 out[k++] = list[i]; 291 } 292 for (uint32_t j = d->j; j <= end; j++) { 293 out[k++] = list[j]; 294 } 295 } 296 297 // Swap both lists. 298 std::swap(d->list, d->out); 299 } 300 } 301 302 return ArraySortResult::Done; 303 } 304 305 } // namespace js 306 307 #endif /* builtin_Sorting_inl_h */