• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "ecmascript/base/sort_helper.h"
17 #include "ecmascript/base/array_helper.h"
18 #include "ecmascript/tagged_array-inl.h"
19 
20 namespace panda::ecmascript::base {
Sort(JSThread * thread,JSHandle<TaggedArray> & elements,const JSHandle<JSTaggedValue> & fn)21 void TimSort::Sort(JSThread *thread, JSHandle<TaggedArray> &elements, const JSHandle<JSTaggedValue> &fn)
22 {
23     uint32_t low = 0;
24     uint32_t len = elements->GetLength();
25     uint32_t nRemaining = len;
26     if (nRemaining < 2) { // 2: means sorted.
27         return;
28     }
29     if (nRemaining < MIN_MERGE) {
30         int initRunLen = CountRunAndMakeAscending(thread, elements, low, len, fn);
31         BinarySort(thread, elements, low, len, low + initRunLen, fn);
32         return;
33     }
34 
35     TimSort ts(thread, elements, fn);
36     uint32_t minRunLength = static_cast<uint32_t>(ComputeMinRunLength(len));
37 
38     while (nRemaining != 0) {
39         uint32_t runLen = static_cast<uint32_t>(CountRunAndMakeAscending(thread, elements, low, len, fn));
40         if (runLen < minRunLength) {
41             uint32_t force = std::min(nRemaining, minRunLength);
42             BinarySort(thread, elements, low, low + force, low + runLen, fn);
43             runLen = force;
44         }
45 
46         ts.PushRun(low, runLen);
47         ts.MergeCollapse();
48         low += runLen;
49         nRemaining -= runLen;
50     }
51     ASSERT(low = len);
52     ts.MergeForceCollapse();
53     ASSERT(ts.pending_.size() == 1);
54 }
55 
CountRunAndMakeAscending(JSThread * thread,JSHandle<TaggedArray> & array,int lo,int hi,const JSHandle<JSTaggedValue> & fn)56 int TimSort::CountRunAndMakeAscending(JSThread *thread, JSHandle<TaggedArray> &array,
57                                       int lo, int hi, const JSHandle<JSTaggedValue> &fn)
58 {
59     ASSERT(lo < hi);
60     int runHi = lo + 1;
61     if (runHi == hi) {
62         return 1;
63     }
64     int runLength = 2;
65     JSMutableHandle<JSTaggedValue> runHiValue(thread, array->Get(runHi));
66     JSMutableHandle<JSTaggedValue> previousValue(thread, array->Get(runHi - 1));
67     double order = ArrayHelper::SortCompare(thread, fn, runHiValue, previousValue);
68     bool isDescending = order < 0 ? true : false;
69     previousValue.Update(runHiValue.GetTaggedValue());
70     for (int i = runHi + 1; i < hi; i++) {
71         runHiValue.Update(array->Get(i));
72         order = ArrayHelper::SortCompare(thread, fn, runHiValue, previousValue);
73         if (isDescending) {
74             if (order >= 0) break;
75         } else {
76             if (order < 0) break;
77         }
78         previousValue.Update(runHiValue.GetTaggedValue());
79         ++runLength;
80     }
81     if (isDescending) {
82         ReverseRange(thread, array, lo, lo + runLength);
83     }
84     return runLength;
85 }
86 
ReverseRange(JSThread * thread,JSHandle<TaggedArray> & array,int from,int to)87 void TimSort::ReverseRange(JSThread *thread, JSHandle<TaggedArray> &array, int from, int to)
88 {
89     int low = from;
90     int high = to - 1;
91     JSMutableHandle<JSTaggedValue> elementLow(thread, JSTaggedValue::Undefined());
92     JSMutableHandle<JSTaggedValue> elementHigh(thread, JSTaggedValue::Undefined());
93     while (low < high) {
94         elementLow.Update(array->Get(low));
95         elementHigh.Update(array->Get(high));
96         array->Set(thread, low++, elementHigh);
97         array->Set(thread, high--, elementLow);
98     }
99 }
100 
BinarySort(JSThread * thread,JSHandle<TaggedArray> & array,int lo,int hi,int start,JSHandle<JSTaggedValue> fn)101 void TimSort::BinarySort(JSThread *thread, JSHandle<TaggedArray> &array,
102                          int lo, int hi, int start, JSHandle<JSTaggedValue> fn)
103 {
104     ASSERT(lo <= start && start <= hi);
105     if (start == lo) {
106         start++;
107     }
108     JSMutableHandle<JSTaggedValue> midVal(thread, JSTaggedValue::Undefined());
109     JSMutableHandle<JSTaggedValue> tmpVal(thread, JSTaggedValue::Undefined());
110     JSMutableHandle<JSTaggedValue> pivotVal(thread, JSTaggedValue::Undefined());
111     for (; start < hi; start++) {
112         int left = lo;
113         int right = start;
114         pivotVal.Update(array->Get(right));
115         ASSERT(left <= right);
116         while (left < right) {
117             int mid = (left + right) >> 1;
118             midVal.Update(array->Get(mid));
119             if (ArrayHelper::SortCompare(thread, fn, pivotVal, midVal) < 0) {
120                 right = mid;
121             } else {
122                 left = mid + 1;
123             }
124         }
125         ASSERT(left == right);
126 
127         for (int p = start; p > left; --p) {
128             tmpVal.Update(array->Get(p - 1));
129             array->Set(thread, p, tmpVal);
130         }
131         array->Set(thread, left, pivotVal);
132     }
133 }
134 
MergeCollapse()135 void TimSort::MergeCollapse()
136 {
137     while (pending_.size() > 1) {
138         int n = static_cast<int>(pending_.size() - 2);
139         if ((n > 0 && pending_[n - 1].len <= pending_[n].len + pending_[n + 1].len) ||
140             (n > 1 && pending_[n - 2].len <= pending_[n - 1].len + pending_[n].len)) { //2: means minus 2
141             if (pending_[n - 1].len < pending_[n + 1].len) {
142                 --n;
143             }
144             MergeAt(n);
145         } else if (pending_[n].len <= pending_[n + 1].len) {
146             MergeAt(n);
147         } else {
148             break;
149         }
150     }
151 }
152 
MergeForceCollapse()153 void TimSort::MergeForceCollapse()
154 {
155     while (pending_.size() > 1) {
156         int n = static_cast<int>(pending_.size() - 2);
157         if (n > 0 && pending_[n - 1].len < pending_[n + 1].len) {
158             --n;
159         }
160         MergeAt(n);
161     }
162 }
163 
MergeAt(int i)164 void TimSort::MergeAt(int i)
165 {
166     const int stackSize = static_cast<int>(pending_.size());
167     ASSERT(stackSize >= 2); // 2: stackSize
168     ASSERT(i >= 0);
169     ASSERT(i == stackSize - 2 || i == stackSize - 3);  // the 2nd-last and 3rd-last run.
170 
171     int base1 = pending_[i].base;
172     int len1 = pending_[i].len;
173     int base2 = pending_[i + 1].base;
174     int len2 = pending_[i + 1].len;
175 
176     ASSERT(len1 > 0 && len2 > 0);
177     ASSERT(base1 + len1 == base2);
178 
179     pending_[i].len = len1 + len2;
180     if (i == stackSize - 3) { // 3: 3rd-last run
181         pending_[i + 1] = pending_[i + 2]; // 2: means plus 2
182     }
183     pending_.pop_back();
184 
185     JSHandle<JSTaggedValue> key1(thread_, elements_->Get(base2));
186     int k = GallopRight(elements_, key1, base1, len1, 0);
187     ASSERT(k >= 0);
188     base1 += k;
189     len1 -= k;
190     if (len1 == 0) {
191         return;
192     }
193     JSHandle<JSTaggedValue> key2(thread_, elements_->Get(base1 + len1 - 1));
194     len2 = GallopLeft(elements_, key2, base2, len2, len2 - 1);
195     ASSERT(len2 >= 0);
196     if (len2 == 0) {
197         return;
198     }
199     // Merge remaining runs, using tmp array with min(len1, len2) elements_
200     if (len1 <= len2) {
201         MergeLo(base1, len1, base2, len2);
202     } else {
203         MergeHi(base1, len1, base2, len2);
204     }
205 }
206 
GallopLeft(JSHandle<TaggedArray> & array,JSHandle<JSTaggedValue> key,int base,int len,int hint)207 int TimSort::GallopLeft(JSHandle<TaggedArray> &array,
208                         JSHandle<JSTaggedValue> key, int base, int len, int hint)
209 {
210     ASSERT(len > 0 && hint >= 0 && hint < len);
211     int lastOfs = 0;
212     int ofs = 1;
213     JSHandle<JSTaggedValue> baseHintElement(thread_, array->Get(base + hint));
214     JSMutableHandle<JSTaggedValue> offsetElement(thread_, JSTaggedValue::Undefined());
215     JSMutableHandle<JSTaggedValue> mElement(thread_, JSTaggedValue::Undefined());
216     double order = ArrayHelper::SortCompare(thread_, fn_, key, baseHintElement);
217     if (order > 0) {
218         int maxOfs = len - hint;
219         while (ofs < maxOfs) {
220             offsetElement.Update(array->Get(base + hint + ofs));
221             order = ArrayHelper::SortCompare(thread_, fn_, key, offsetElement);
222             if (order <= 0) break;
223 
224             lastOfs = ofs;
225             ofs = (ofs << 1) + 1;
226             if (ofs <= 0) {
227                 ofs = maxOfs;
228             }
229         }
230         if (ofs > maxOfs) {
231             ofs = maxOfs;
232         }
233         lastOfs += hint;
234         ofs += hint;
235     } else {
236         int maxOfs = hint + 1;
237         while (ofs < maxOfs) {
238             offsetElement.Update(array->Get(base + hint - ofs));
239             order = ArrayHelper::SortCompare(thread_, fn_, key, offsetElement);
240             if (order > 0) break;
241 
242             lastOfs = ofs;
243             ofs = (ofs << 1) + 1;
244             if (ofs <= 0) {
245                 ofs = maxOfs;
246             }
247         }
248         if (ofs > maxOfs) {
249             ofs = maxOfs;
250         }
251         int tmp = lastOfs;
252         lastOfs = hint - ofs;
253         ofs = hint - tmp;
254     }
255     ASSERT(-1 <= lastOfs && lastOfs < ofs && ofs <= len);
256     lastOfs++;
257     while (lastOfs < ofs) {
258         int m = lastOfs + ((ofs - lastOfs) >> 1);
259         mElement.Update(array->Get(base + m));
260         if (ArrayHelper::SortCompare(thread_, fn_, key, mElement) > 0) {
261             lastOfs = m + 1;
262         } else {
263             ofs = m;
264         }
265     }
266     ASSERT(lastOfs == ofs);
267     return ofs;
268 }
269 
GallopRight(JSHandle<TaggedArray> & array,JSHandle<JSTaggedValue> key,int base,int len,int hint)270 int TimSort::GallopRight(JSHandle<TaggedArray> &array,
271                          JSHandle<JSTaggedValue> key, int base, int len, int hint)
272 {
273     ASSERT(len > 0 && hint >= 0 && hint < len);
274     int lastOfs = 0;
275     int ofs = 1;
276     JSHandle<JSTaggedValue> baseHintElement(thread_, array->Get(base + hint));
277     JSMutableHandle<JSTaggedValue> offsetElement(thread_, JSTaggedValue::Undefined());
278     JSMutableHandle<JSTaggedValue> mElement(thread_, JSTaggedValue::Undefined());
279     double order = ArrayHelper::SortCompare(thread_, fn_, key, baseHintElement);
280     if (order < 0) {
281         int maxOfs = hint + 1;
282         while (ofs < maxOfs) {
283             offsetElement.Update(array->Get(base + hint - ofs));
284             order = ArrayHelper::SortCompare(thread_, fn_, key, offsetElement);
285             if (order >= 0) break;
286 
287             lastOfs = ofs;
288             ofs = (ofs << 1) + 1;
289             if (ofs <= 0) {
290                 ofs = maxOfs;
291             }
292         }
293         if (ofs > maxOfs) {
294             ofs = maxOfs;
295         }
296         int tmp = lastOfs;
297         lastOfs = hint - ofs;
298         ofs = hint - tmp;
299     } else {
300         int maxOfs = len - hint;
301         while (ofs < maxOfs) {
302             offsetElement.Update(array->Get(base + hint + ofs));
303             order = ArrayHelper::SortCompare(thread_, fn_, key, offsetElement);
304             if (order < 0) break;
305 
306             lastOfs = ofs;
307             ofs = (ofs << 1) + 1;
308             if (ofs <= 0) {
309                 ofs = maxOfs;
310             }
311         }
312         if (ofs > maxOfs) {
313             ofs = maxOfs;
314         }
315         lastOfs += hint;
316         ofs += hint;
317     }
318     ASSERT(-1 <= lastOfs && lastOfs < ofs && ofs <= len);
319     lastOfs++;
320     while (lastOfs < ofs) {
321         int m = lastOfs + ((ofs - lastOfs) >> 1);
322         mElement.Update(array->Get(base + m));
323         if (ArrayHelper::SortCompare(thread_, fn_, key, mElement) < 0) {
324             ofs = m;
325         } else {
326             lastOfs = m + 1;
327         }
328     }
329     return ofs;
330 }
331 
MergeLo(int base1,int len1,int base2,int len2)332 void TimSort::MergeLo(int base1, int len1, int base2, int len2)
333 {
334     ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2);
335     JSHandle<TaggedArray> workArray = elements_;
336     JSHandle<TaggedArray> tmpArray = GetTempArray(len1);
337     this->CopyArray(workArray, base1, tmpArray, 0, len1);
338 
339     JSMutableHandle<JSTaggedValue> tmpElement(thread_, JSTaggedValue::Undefined());
340     JSMutableHandle<JSTaggedValue> cmp1Element(thread_, JSTaggedValue::Undefined());
341     JSMutableHandle<JSTaggedValue> cmp2Element(thread_, JSTaggedValue::Undefined());
342 
343     int cursor1 = 0;
344     int cursor2 = base2;
345     int dest = base1;
346     this->CopyArray(workArray, base1, tmpArray, cursor1, len1);
347 
348     tmpElement.Update(workArray->Get(cursor2++));
349     workArray->Set(thread_, dest++, tmpElement);
350 
351     if (--len2 == 0) {
352         this->CopyArray(tmpArray, cursor1, workArray, dest, len1);
353         return;
354     }
355     if (len1 == 1) {
356         this->CopyArray(workArray, cursor2, workArray, dest, len2);
357         tmpElement.Update(tmpArray->Get(cursor1));
358         workArray->Set(thread_, dest + len2, tmpElement);
359         return;
360     }
361     int minGallop = minGallop_;
362     while (true) {
363         int count1 = 0;
364         int count2 = 0;
365         do {
366             ASSERT(len1 > 1 && len2 > 0);
367             cmp1Element.Update(workArray->Get(cursor2));
368             cmp2Element.Update(tmpArray->Get(cursor1));
369 
370             if (ArrayHelper::SortCompare(thread_, fn_, cmp1Element, cmp2Element) < 0) {
371                 tmpElement.Update(workArray->Get(cursor2++));
372                 workArray->Set(thread_, dest++, tmpElement);
373                 count2++;
374                 count1 = 0;
375                 if (--len2 == 0) {
376                     goto epilogue;
377                 }
378             } else {
379                 tmpElement.Update(tmpArray->Get(cursor1++));
380                 workArray->Set(thread_, dest++, tmpElement);
381                 count1++;
382                 count2 = 0;
383                 if (--len1 == 1) {
384                     goto epilogue;
385                 }
386             }
387         } while ((count1 | count2) < minGallop);
388 
389         do {
390             ASSERT(len1 > 1 && len2 > 0);
391             JSHandle<JSTaggedValue> cursorVal(thread_, workArray->Get(cursor2));
392             count1 = GallopRight(tmpArray, cursorVal, cursor1, len1, 0);
393             if (count1 != 0) {
394                 this->CopyArray(tmpArray, cursor1, workArray, dest, count1);
395                 dest += count1;
396                 cursor1 += count1;
397                 len1 -= count1;
398                 if (len1 <= 1) {
399                     goto epilogue;
400                 }
401             }
402             tmpElement.Update(workArray->Get(cursor2++));
403             workArray->Set(thread_, dest++, tmpElement);
404             if (--len2 == 0) {
405                 goto epilogue;
406             }
407             JSHandle<JSTaggedValue> cursorVal2(thread_, tmpArray->Get(cursor1));
408             count2 = GallopLeft(workArray, cursorVal2, cursor2, len2, 0);
409             if (count2 != 0) {
410                 this->CopyArray(workArray, cursor2, workArray, dest, count2);
411                 dest += count2;
412                 cursor2 += count2;
413                 len2 -= count2;
414                 if (len2 == 0) {
415                     goto epilogue;
416                 }
417             }
418             tmpElement.Update(tmpArray->Get(cursor1++));
419             workArray->Set(thread_, dest++, tmpElement);
420             if (--len1 == 1) {
421                 goto epilogue;
422             }
423             minGallop--;
424         } while ((count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP));
425 
426         if (minGallop < 0) {
427             minGallop = 0;
428         }
429         minGallop += 2; // 2: means plus 2
430     }
431 
432     epilogue: // merge what is left from either cursor1 or cursor2
433 
434     minGallop_ = std::min(minGallop, 1);
435     if (len1 == 1) {
436         ASSERT(len2 > 0);
437         this->CopyArray(workArray, cursor2, workArray, dest, len2);
438         tmpElement.Update(tmpArray->Get(cursor1));
439         workArray->Set(thread_, dest + len2, tmpElement);
440     } else {
441         ASSERT(len1 != 0);
442         ASSERT(len2 == 0 && len1 > 1);
443         this->CopyArray(tmpArray, cursor1, workArray, dest, len1);
444     }
445 }
446 
MergeHi(int base1,int len1,int base2,int len2)447 void TimSort::MergeHi(int base1, int len1, int base2, int len2)
448 {
449     JSHandle<TaggedArray> workArray = elements_;
450     JSHandle<TaggedArray> tmpArray = GetTempArray(len2);
451 
452     JSMutableHandle<JSTaggedValue> tmpElement(thread_, JSTaggedValue::Undefined());
453     JSMutableHandle<JSTaggedValue> cmp1Element(thread_, JSTaggedValue::Undefined());
454     JSMutableHandle<JSTaggedValue> cmp2Element(thread_, JSTaggedValue::Undefined());
455 
456     this->CopyArray(workArray, base2, tmpArray, 0, len2);
457     int cursor1 = base1 + len1 - 1;
458     int cursor2 =  len2 - 1;
459     int dest = base2 + len2 - 1;
460 
461     tmpElement.Update(workArray->Get(cursor1--));
462     workArray->Set(thread_, dest--, tmpElement);
463 
464     if (--len1 == 0) {
465         this->CopyArray(tmpArray, 0, workArray, dest - (len2 - 1), len2);
466         return;
467     }
468     if (len2 == 1) {
469         dest -= len1;
470         cursor1 -= len1;
471         this->CopyArray(workArray, cursor1 + 1, workArray, dest + 1, len1);
472         tmpElement.Update(tmpArray->Get(cursor2));
473         workArray->Set(thread_, dest, tmpElement);
474         return;
475     }
476     int minGallop = minGallop_;
477     while (true) {
478         int count1 = 0;
479         int count2 = 0;
480         do {
481             ASSERT(len1 > 0 && len2 > 1);
482             cmp1Element.Update(workArray->Get(cursor1));
483             cmp2Element.Update(tmpArray->Get(cursor2));
484 
485             if (ArrayHelper::SortCompare(thread_, fn_, cmp2Element, cmp1Element) < 0) {
486                 tmpElement.Update(workArray->Get(cursor1--));
487                 workArray->Set(thread_, dest--, tmpElement);
488                 count1++;
489                 count2 = 0;
490                 if (--len1 == 0) {
491                     goto epilogue;
492                 }
493             } else {
494                 tmpElement.Update(tmpArray->Get(cursor2--));
495                 workArray->Set(thread_, dest--, tmpElement);
496                 count2++;
497                 count1 = 0;
498                 if (--len2 == 1) {
499                     goto epilogue;
500                 }
501             }
502         } while ((count1 | count2) < minGallop);
503 
504         do {
505             ASSERT(len1 > 0 && len2 > 1);
506             JSHandle<JSTaggedValue> cursorVal(thread_, tmpArray->Get(cursor2));
507             count1 = len1 - GallopRight(workArray, cursorVal, base1, len1, len1 - 1);
508             if (count1 != 0) {
509                 dest -= count1;
510                 cursor1 -= count1;
511                 len1 -= count1;
512                 this->CopyArray(workArray, cursor1 + 1, workArray, dest + 1, count1);
513                 if (len1 == 0) {
514                     goto epilogue;
515                 }
516             }
517             tmpElement.Update(tmpArray->Get(cursor2--));
518             workArray->Set(thread_, dest--, tmpElement);
519             if (--len2 == 1) {
520                 goto epilogue;
521             }
522             JSHandle<JSTaggedValue> cursorVal2(thread_, workArray->Get(cursor1));
523             count2 = len2 - GallopLeft(tmpArray, cursorVal2, 0, len2, len2 - 1);
524             if (count2 != 0) {
525                 dest -= count2;
526                 cursor2 -= count2;
527                 len2 -= count2;
528                 this->CopyArray(tmpArray, cursor2 + 1, workArray, dest + 1, count2);
529                 if (len2 <= 1) {
530                     goto epilogue;
531                 }
532             }
533             tmpElement.Update(workArray->Get(cursor1--));
534             workArray->Set(thread_, dest--, tmpElement);
535             if (--len1 == 0) {
536                 goto epilogue;
537             }
538             minGallop--;
539         } while ((count1 >= MIN_GALLOP) | (count2 >= MIN_GALLOP));
540 
541         if (minGallop < 0) {
542             minGallop = 0;
543         }
544         minGallop += 2; // 2: means plus 2
545     }
546 
547     epilogue: // merge what is left from either cursor1 or cursor2
548 
549     minGallop_ = std::min(minGallop, 1);
550     if (len2 == 1) {
551         ASSERT(len1 > 0);
552         dest -= len1;
553         cursor1 -= len1;
554         this->CopyArray(workArray, cursor1 + 1, workArray, dest + 1, len1);
555         tmpElement.Update(tmpArray->Get(cursor2));
556         workArray->Set(thread_, dest, tmpElement);
557     } else {
558         ASSERT(len2 != 0);
559         ASSERT(len1 == 0 && len2 > 1);
560         this->CopyArray(tmpArray, 0, workArray, dest - (len2 - 1), len2);
561     }
562 }
563 
CopyArray(JSHandle<TaggedArray> & src,int srcPos,JSHandle<TaggedArray> & dst,int dstPos,int length)564 void TimSort::CopyArray(JSHandle<TaggedArray> &src, int srcPos,
565                         JSHandle<TaggedArray> &dst, int dstPos, int length)
566 {
567     DISALLOW_GARBAGE_COLLECTION;
568     ASSERT(srcPos >= 0);
569     ASSERT(dstPos >= 0);
570     ASSERT(static_cast<int64_t>(srcPos) <= static_cast<int64_t>(src->GetLength() - length));
571     ASSERT(static_cast<int64_t>(dstPos) <= static_cast<int64_t>(dst->GetLength() - length));
572 
573     if (srcPos < dstPos) {
574         int srcIdx = srcPos + length - 1;
575         int dstIdx = dstPos + length - 1;
576         while (srcIdx >= srcPos) {
577             dst->Set(thread_, dstIdx--, src->Get(srcIdx--));
578         }
579     } else {
580         int srcIdx = srcPos;
581         int dstIdx = dstPos;
582         int to = srcPos + length;
583         while (srcIdx < to) {
584             dst->Set(thread_, dstIdx++, src->Get(srcIdx++));
585         }
586     }
587 }
588 }